/*
 * static char *rcsid_player_c =
 *   "$Id: player.c,v 1.67 1998/01/05 10:55:12 master Exp master $";
 */

/*
    CrossFire, A Multiplayer game for X-windows

    Copyright (C) 1994 Mark Wedel
    Copyright (C) 1992 Frank Tore Johansen

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    The author can be reached via e-mail to master@rahul.net
*/

#include <pwd.h>
#include <global.h>
#ifndef __CEXTRACT__
#include <sproto.h>
#endif
#include <player.h>
#include <sounds.h>
#include <living.h>
#include <object.h>
#include <spells.h>
#include <skills.h>
#include <newclient.h>

/* This function returns TRUE if the object should be shown in the
 * inventory window (due to show_what flags and object status).  This
 * should be a macro.
 * By putting this in a function, it makes it easier to add new display
 * types, and eliminates repetitive code.
 *
 * This function will only be used in the new client once all the
 * old X11 stuff in the server is removed.
 */

int show_what_object(object *tmp, int show_what)
{
    if (tmp->invisible) return 0;
    switch (show_what) {
	case show_all:
	    return 1;

	case show_applied:
	    if (QUERY_FLAG(tmp, FLAG_APPLIED)) return 1;
	    else return 0;

	case show_unapplied:
	    if (QUERY_FLAG(tmp, FLAG_APPLIED)) return 0;
	    else return 1;

	case show_unpaid:
	    if (QUERY_FLAG(tmp, FLAG_UNPAID)) return 1;
	    else return 0;

	case show_cursed:
	    if (QUERY_FLAG(tmp, FLAG_KNOWN_CURSED)) return 1;
	    else return 0;

	case show_magical:
	case show_nonmagical: {
	    int status = QUERY_FLAG(tmp, FLAG_KNOWN_MAGICAL) ||
		(QUERY_FLAG(tmp, FLAG_IDENTIFIED) && is_magical(tmp));

	    return (show_what==show_magical? status : !status);
	}
	default:
	    LOG(llevError,"show_what_object: unknown show_what state: %d\n",
		show_what);
    }
    return 0;
}

void display_motd(object *op) {
#ifdef MOTD
  char buf[MAX_BUF];
  FILE *fp;
  int comp;

  sprintf(buf,"%s/%s",settings.libdir,MOTD);
  if((fp=open_and_uncompress(buf,0,&comp))==NULL) {
    return;
  }
  while(fgets(buf,MAX_BUF,fp)!=NULL) {
    char *cp;
    if(*buf=='#')
      continue;
    cp=strchr(buf,'\n');
    if(cp!=NULL)
      *cp='\0';
    new_draw_info(NDI_UNIQUE, 0,op,buf);
  }
  close_and_delete(fp, comp);
  new_draw_info(NDI_UNIQUE, 0,op," ");
#endif
}

int playername_ok(char *cp) {
  for(;*cp!='\0';cp++)
    if(!((*cp>='a'&&*cp<='z')||(*cp>='A'&&*cp<='Z'))&&*cp!='-'&&*cp!='_')
      return 0;
  return 1;
}

int add_player(char *disp, char *username, mapstruct *m, int esrv_client) {
  player *p;
  char buf[MAX_BUF],*cp,*defname = "nobody";
  struct passwd *pwent;

  if (username == NULL) {
    pwent = getpwuid(getuid());
    if (pwent)
      username = pwent->pw_name;
    else
      username = defname;
  }

  /* Check for banned players and sites */

  if (checkbanned (username, disp)){
    fprintf (logfile, "Banned player tryed to add. [%s@%s]\n", username, disp);
    fflush (logfile);
    return 0;
  }


  init_beforeplay(); /* Make sure everything is ready */
  if(m == NULL || m->in_memory != MAP_IN_MEMORY)
    m = ready_map_name(first_map_path,0);
  if(m == NULL)
    fatal(MAP_ERROR);
  m->timeout = MAP_TIMEOUT(m); /* Just in case we fail to add the player */
  p=get_player_ob();
  p->eric_server = esrv_client;

  p->name=add_string(disp);
  p->username = strdup_local(username);
  p->writing=0,
  p->weapon_sp=0,p->last_weapon_sp= -1;
  p->last_armour= (-1);
  p->has_hit=0;
  p->scroll_inv=0;
  p->scroll_look=0;

  if (p->show_inv_icon)
    strcpy(p->format_inv,"%-20.20s%-6s"); /* This can be changed by resize */
  else
    strcpy(p->format_inv,"%-24.24s%-6s"); /* This can be changed by resize */

  strcpy(p->format_look,"%-24.24s%-6s");
  p->inv_size=INV_SIZE;
  p->look_size=LOOK_SIZE;
  p->inv_chars=30;
  p->look_chars=30;
  p->barlength_inv=BARLENGTH_INV;
  p->barlength_look=BARLENGTH_LOOK;
  p->last_scroll_inv=p->last_scroll_look=1,
  p->scrollsize_inv= p->scrollsize_look= p->scrollsize_hp=p->scrollsize_sp=
  p->scrollsize_food=p->scrollstart_inv= p->scrollsize_grace=p->scrollstart_look=0,
  p->nrofdrawn_inv=0,p->nrofdrawn_look=0;
  p->peaceful=1;			/* default peaceful */
  p->do_los=1;
  p->scroll=0;				/* default without scroll */
  p->split_window=settings.splitwindows;/* See -w flag */
  p->last_weight= -1;
  p->freeze_inv=p->freeze_look=0;
  p->viewmap=0;
  p->mapres=1;
  p->mapdelx[0]= -1;
#ifdef EXPLORE_MODE
  p->explore=0;
#endif  
  p->menu_strings = NULL;
  if(settings.displaymode==Dm_Bitmap)
    p->use_pixmaps = 1;
  else if (settings.displaymode==Dm_Pixmap) {
	p->use_pixmaps = 1;
	p->color_pixmaps = 1;
  }
  if (esrv_client>0) {
    p->gdisp = NULL;
    load_default_keys(p);
    p->gdisp = NULL;
  } else {
    strncpy(buf,disp,MAX_BUF);
    if(strchr(buf,':')==NULL)
      strcat(buf,":0");
    if(get_root_display(p,buf)  || get_game_display(p,buf) ||
       get_stats_display(p,buf) || get_info_display(p,buf) ||
       get_inv_display(p,buf)   || get_look_display(p,buf) ||
       get_message_display(p,buf)) {
      free_player(p);
      return 1;
    }

    setuperrors();
    /* Is there any reason that the values checked for in the if statements
     * need to be re-set below them?  MSW (master@rahul.net)
     */
    if (p->color_pixmaps) {
	p->color_pixmaps = 1;
	p->use_pixmaps = 1;
	if (ReadPixmaps(p->gdisp, &p->pixmaps, &p->masks,&p->colormap)) {
	    if (!p->split_window)
		XSetWindowColormap(p->gdisp, p->win_root, p->colormap);
	    XSetWindowColormap(p->gdisp, p->win_game, p->colormap);
	    XSetWindowColormap(p->gdisp, p->win_info, p->colormap);
	    XSetWindowColormap(p->gdisp, p->win_message, p->colormap);
	    XSetWindowColormap(p->gdisp, p->win_inv, p->colormap);
	    XSetWindowColormap(p->gdisp, p->win_look, p->colormap);
	}
    }
    else if(p->use_pixmaps) {
      p->use_pixmaps = 1;
      p->pixmaps = ReadBitmaps (p->gdisp);
    }

    load_default_keys(p);

    Protocol_atom = XInternAtom(p->gdisp, "WM_PROTOCOLS", False);
    Kill_atom = XInternAtom(p->gdisp, "WM_DELETE_WINDOW", False);
    if(!p->split_window)
      XSetWMProtocols(p->gdisp, p->win_root, &Kill_atom, 1);
    else { /* I hope this is correct... */
      XSetWMProtocols(p->gdisp, p->win_game, &Kill_atom, 1);
      XSetWMProtocols(p->gdisp, p->win_stats, &Kill_atom, 1);
      XSetWMProtocols(p->gdisp, p->win_info, &Kill_atom, 1);
      XSetWMProtocols(p->gdisp, p->win_inv, &Kill_atom, 1);
      XSetWMProtocols(p->gdisp, p->win_look, &Kill_atom, 1);
      XSetWMProtocols(p->gdisp, p->win_message, &Kill_atom, 1);
    }
  }
  free_string(p->ob->name);
  p->ob->name = NULL;
  free_object(p->ob);
  p->ob=get_player(p,m);
  add_friendly_object(p->ob);
#ifdef MOTD
  display_motd(p->ob);
#endif
  if (esrv_client) {
    /* These things get set below - set them here to default values for
     * ericserver.
     */
#ifdef SOUND_EFFECTS
    p->rplay_fd = 0;
#endif
    p->peaceful=0;
    p->berzerk=0;
    p->scroll=0;
    p->own_title[0]='\0';
    get_name(p->ob);
  } else {
    if ((cp = XGetDefault(p->gdisp, name, "wimpy")) != NULL)
      p->ob->run_away = atoi(cp);
#ifdef SOUND_EFFECTS
    if ((cp = XGetDefault(p->gdisp, name, "sounds")) != NULL)
      {
	if (!strcmp("on", cp) || !strcmp("yes", cp))
	  p->rplay_fd = init_disp_sound(disp);
	else if (!strcmp("off", cp) || !strcmp("no", cp))
	  p->rplay_fd = 0;
      }
    else
      p->rplay_fd = init_disp_sound(disp);
    LOG(llevDebug, "Rplay fd: %d\n", p->rplay_fd);
    play_sound_player_only(p, SOUND_NEW_PLAYER);
#endif

    if((cp=XGetDefault(p->gdisp,name,"name"))!=NULL&&check_name(p,cp)) {
      if(p->ob->name!=NULL)
	free_string(p->ob->name);
      p->ob->name=add_string(cp);
      remove_lock(p);
      get_password(p->ob);
      p->name_changed=1;
    } else {
      get_name(p->ob);
      cp=strchr(buf,'.');
      if(cp!=NULL) *cp='\0';
      cp=strchr(buf,':');
      if(cp!=NULL) *cp='\0';
      p->ob->name=add_string(buf);
      p->name_changed=0;
    }
    if((cp=XGetDefault(p->gdisp,name,"peaceful"))!=NULL)
      if(!strcmp("on",cp)||!strcmp("yes",cp))
	p->peaceful=1;
      else if(!strcmp("off",cp)||!strcmp("no",cp))
	p->peaceful=0;
    if((cp=XGetDefault(p->gdisp,name,"berzerk"))!=NULL)
      if(!strcmp("on",cp)||!strcmp("yes",cp))
	p->berzerk=1;
      else if(!strcmp("off",cp)||!strcmp("no",cp))
	p->berzerk=0;
    if((cp=XGetDefault(p->gdisp,name,"scroll"))!=NULL)
      if(!strcmp("on",cp)||!strcmp("yes",cp))
	p->scroll=1;
      else if(!strcmp("off",cp)||!strcmp("no",cp))
	p->scroll=0;
    if((cp=XGetDefault(p->gdisp,name,"title"))!=NULL && strlen(cp)<MAX_NAME)
	strcpy (p->own_title, cp);
    else
	p->own_title[0]='\0';
  }
  return 0;
}

/*
 * get_player_archetype() return next player archetype from archetype
 * list. Not very efficient routine, but used only creating new players.
 * Note: there MUST be at least one player archetype!
 */
archetype *get_player_archetype(archetype* at)
{
    archetype *start = at;
    for (;;) {
	if (at==NULL || at->next==NULL)
	    at=first_archetype;
	else
	    at=at->next;
	if(at->clone.type==PLAYER)
	    return at;
	if (at == start) {
	    LOG (llevError, "No Player achetypes\n");
	    exit (-1);
	}
    }
}


object *get_player(player *p, mapstruct *m) {
  object *op=arch_to_object(get_player_archetype(NULL));
  int i;

  p->loading = NULL;
  op->map=m;
  if(m->in_memory != MAP_IN_MEMORY) {
    p->loading = m;
    p->new_x = 0;
    p->new_y = 0;
    p->removed = 0;
    op->x=0;
    op->y=0;
  } else {
    i=find_free_spot(NULL,m,EXIT_X(m->map_object),EXIT_Y(m->map_object),
	0,SIZEOFFREE);
    op->x=EXIT_X(m->map_object)+freearr_x[i];
    op->y=EXIT_Y(m->map_object)+freearr_y[i];
  }
  p->fire_on=0,p->run_on=0;
  p->count=0;
  p->count_left=0;
  p->prev_cmd=' ';
  p->prev_fire_on=0;
  p->mode=0;
  p->berzerk=1;
  p->idle=0;
#ifdef AUTOSAVE
  p->last_save_tick = 9999999;
#endif
  *p->maplevel=0;

  op->contr=p; /* this aren't yet in archetype */
  op->speed_left=0.5;
  op->speed=1.0;
  op->direction=0;
  op->stats.wc=2;
  op->run_away = 25; /* Then we panick... */

  roll_stats(op);
  p->state=ST_ROLL_STAT;
  clear_los(op);
  p->last_stats.Str=0,  p->last_stats.Dex=0,
  p->last_stats.Int=0,  p->last_stats.Con=0,
  p->last_stats.Wis=0,  p->last_stats.Cha=0;
  p->last_stats.Pow=0,  p->last_stats.Pow=0;
  p->last_stats.hp=0,   p->last_stats.maxhp=0;
  p->last_stats.wc=0,   p->last_stats.ac=0;
  p->last_stats.sp=0,   p->last_stats.maxsp=0;
  p->last_stats.grace=0, p->last_stats.maxgrace=0;
  p->last_stats.exp= -1,p->last_stats.food=0;
  p->digestion=0,p->gen_hp=0,p->gen_sp=0,p->gen_grace=0;
  p->last_spell= -1;
  p->last_value= -1;
  p->last_speed= -1;
  p->shoottype=range_none,p->shootstrength=5;
  p->last_shoot= range_bottom;
  p->listening=9;
  p->golem=NULL;
  p->last_used=NULL;
  p->last_used_id=0;
  strncpy(p->title,op->arch->clone.name,MAX_NAME);
  op->race = add_string (op->arch->clone.race);

  (void)memset((void *)op->contr->drawn,'\0',
	       sizeof(Fontindex)*(WINRIGHT-WINLEFT+1)*(WINLOWER-WINUPPER+1));
  for(i=0;i<NROFREALSPELLS;i++)
    p->known_spells[i]= -1;
  p->nrofknownspells=0;
  p->chosen_spell = -1;
  p->ob->chosen_skill = NULL;
#ifdef LINKED_SKILL_LIST
  p->ob->sk_list = NULL;
#endif
  if(QUERY_FLAG(op,FLAG_READY_SKILL))
        CLEAR_FLAG(op,FLAG_READY_SKILL); 
  draw_all_inventory(op);
  draw_all_look(op);
  return op;
}

object *get_nearest_player(object *mon) {
  object *op = NULL;
  objectlink *ol;
  int lastdist,tmp;

  for(ol=first_friendly_object,lastdist=1000;ol!=NULL;ol=ol->next) {
    /* Remove special check for player from this.  First, it looks to cause
     * some crashes (ol->ob->contr not set properly?), but secondly, a more
     * complicated method of state checking would be needed in any case -
     * as it was, a clever player could type quit, and the function would  
     * skip them over while waiting for confirmation.
     */
    if(!can_detect_enemy(mon,ol->ob)||ol->ob->map!=mon->map)
#if 0
       ||((ol->ob->invisible&&QUERY_FLAG(ol->ob,FLAG_UNDEAD)==QUERY_FLAG(mon,FLAG_UNDEAD))
	  &&!QUERY_FLAG(mon,FLAG_SEE_INVISIBLE))||ol->ob->map!=mon->map)
#endif
      continue;
    tmp=distance(ol->ob,mon);
    if(lastdist>tmp) {
      op=ol->ob;
      lastdist=tmp;
    }
  }
#if 0
  LOG(llevDebug,"get_nearest_player() finds player: %s\n",op?op->name:"(null)");
#endif
  return op;
}

/*
 * Returns the direction to the player, if valid.  Returns 0 otherwise.
 * modified to verify there is a path to the player.  Does this by stepping towards
 * player and if path is blocked then see if blockage is close enough to player that
 * direction to player is changed (ie zig or zag).  Continue zig zag until either
 * reach player or path is blocked.  Thus, will only return true if there is a free
 * path to player.  Though path may not be a straight line.  Note that it will find
 * player hiding along a corridor at right angles to the corridor with the monster.
 */
int path_to_player(object *mon, object *pl,int mindiff) {
  int dir,x,y,diff;
  int oldx,oldy,olddir,newdir;

  x=mon->x, y=mon->y;
  dir=find_dir_2(x-pl->x,y-pl->y);

  /* diff is how many steps from monster to player */
  diff= abs(x-pl->x) > abs(y-pl->y) ? abs(x-pl->x) : abs(y-pl->y);
  if(diff<mindiff) return 0;

  newdir = olddir = dir;
  oldx = x, oldy=y;

  while( diff>1 ) {
    for( ; diff>1; diff--) {
      oldx=x, oldy=y;
      x+=freearr_x[newdir],y+=freearr_y[newdir];
      if(blocked(mon->map,x,y)){
        if (olddir == (newdir=find_dir_2(oldx-pl->x, oldy-pl->y))){
          if ( (newdir%2) == 0)
            return 0;
            /* if heading straight then try diag step towards player.  needed to find
             * player in a L position compared to monster along corridors.  the 2* and
			 * RANDOM are to go around a block and find the player behind. */
          if ((newdir==1) || (newdir==5)) {
            ( (2*x) > (2*(pl->x+RANDOM()%2)-1) ) ? x-- : x++;
          } else if((newdir==3) || (newdir==7)) {
            ( (2*y) > (2*(pl->y+RANDOM()%2)-1) ) ? y-- : y++;
          }
          if(blocked(mon->map,x,y))  
            return 0;        /* path blocked to player */
          diff = abs(x-pl->x) > abs(y-pl->y) ? abs(x-pl->x) : abs(y-pl->y);
        } else {        /*have a new direction to try */
          olddir = newdir, x=oldx, y=oldy;
          diff = abs(oldx-pl->x) > abs(oldy-pl->y) ? abs(oldx-pl->x) : abs(oldy-pl->y);
        }
      }
    }
    /* diff may have headed us all the way down that direction, but not next to the player
     * so head towards player.  If next to player then will exit while loop */
    newdir=find_dir_2(x-pl->x, y-pl->y);
    diff = abs(x-pl->x) > abs(y-pl->y) ? abs(x-pl->x) : abs(y-pl->y);
  }
  return dir;
}

void give_initial_items(object *pl) {
    object *op,*next=NULL;
    static uint8 start_spells[] = {0, 1, 4, 5, 7,17,168};
    static uint8 start_prayers[] = {19, 31, 32, 129}; 
    pl->contr->freeze_inv=1;
    if(pl->randomitems!=NULL)
	create_treasure(pl->randomitems,pl,GT_INVENTORY,1,0);

    for (op=pl->inv; op; op=next) {
	next = op->below;
	if(op->nrof<2 && op->type!=CONTAINER && op->type!=MONEY && 
	   !QUERY_FLAG(op,FLAG_IS_THROWN))
		SET_FLAG(op,FLAG_STARTEQUIP);
	/* Not marked as starting equipment, so set 0 value. */
	if (QUERY_FLAG(op,FLAG_IS_THROWN))
	    op->value=0;
	if(op->type==SPELLBOOK) { /* fix spells for first level spells */
	    if(!strcmp(op->arch->name,"cleric_book")) 
		op->stats.sp=start_prayers[RANDOM()%(sizeof(start_prayers)/sizeof(uint8))];
	    else
		op->stats.sp=start_spells[RANDOM()%sizeof(start_spells)];
	}
	/* Give starting characters identified, uncursed, and undamned
	 * items.  Just don't identify gold or silver, or it won't be
	 * merged properly.
	 */
	if (need_identify(op)) {
	    SET_FLAG(op, FLAG_IDENTIFIED);
	    CLEAR_FLAG(op, FLAG_CURSED);
	    CLEAR_FLAG(op, FLAG_DAMNED);
	}
#ifndef ALLOW_SKILLS	/* no reason to start with these if no skills exist! */ 
	if(op->type==SKILLSCROLL || op->type==SKILL)  {
	    remove_ob(op);
	    free_object(op);
	}
#endif
	if(op->type==ABILITY)  {
	    pl->contr->known_spells[pl->contr->nrofknownspells++]=op->stats.sp;
	    remove_ob(op);
	    free_object(op);
	}
    }
    pl->contr->freeze_inv=0;
}

void get_name(object *op) {
  op->contr->write_buf[0]='\0';
  op->contr->writing=0;
  op->contr->state=ST_GET_NAME;
  new_draw_info(NDI_UNIQUE, 0,op,"What is your name?");
  if (op->contr->eric_server>0)
	send_query(op->contr->eric_server,0,":");
  else
    write_ch(op,':');
}

void get_password(object *op) {
  op->contr->write_buf[0]='\0';
  op->contr->writing=0;
  op->contr->state=ST_GET_PASSWORD;
  new_draw_info(NDI_UNIQUE, 0,op,"What is your password?");
  if (op->contr->eric_server>0)
	send_query(op->contr->eric_server,CS_QUERY_HIDEINPUT, ":");
  else {
    write_ch(op,':');
    op->contr->no_echo=1;
  }
}

void play_again(object *op)
{
     new_draw_info(NDI_UNIQUE, 0,op,"Do you want to play again (a/q)?");
     op->contr->state=ST_PLAY_AGAIN;
     remove_lock(op->contr);
     if (op->contr->eric_server>0)
	send_query(op->contr->eric_server, CS_QUERY_SINGLECHAR, "");
}

void confirm_password(object *op) {

  op->contr->write_buf[0]='\0';
  op->contr->writing=0;
  op->contr->state=ST_CONFIRM_PASSWORD;
  new_draw_info(NDI_UNIQUE, 0,op,"Please type your password again.");
  if (op->contr->eric_server>0)
	send_query(op->contr->eric_server, CS_QUERY_HIDEINPUT, ":");
  else
    write_ch(op,':');
  op->contr->no_echo=1;
}

#ifdef SIMPLE_PARTY_SYSTEM
void get_party_password(object *op, int partyid) {
  op->contr->write_buf[0]='\0';
  op->contr->writing=0;
  op->contr->state=ST_GET_PARTY_PASSWORD;
  op->contr->party_number_to_join = partyid;
  new_draw_info(NDI_UNIQUE, 0,op,"What is the password?");
  if (op->contr->eric_server>0)
	send_query(op->contr->eric_server, CS_QUERY_HIDEINPUT, ":");
  else
    write_ch(op,':');
}
#endif /* SIMPLE_PARTY_SYSTEM */

int roll_stat() {
  int a[4],i,j,k;
  for(i=0;i<4;i++)
    a[i]=(int)RANDOM()%6+1;
  for(i=0,j=0,k=7;i<4;i++)
    if(a[i]<k)
      k=a[i],j=i;
  for(i=0,k=0;i<4;i++) {
    if(i!=j)
      k+=a[i];
  }
  return k;
}

void roll_stats(object *op) {
  int sum=0;
  do {
    op->stats.Str=roll_stat();
    op->stats.Dex=roll_stat();
    op->stats.Int=roll_stat();
    op->stats.Con=roll_stat();
    op->stats.Wis=roll_stat();
    op->stats.Pow=roll_stat();
    op->stats.Cha=roll_stat();
    sum=op->stats.Str+op->stats.Dex+op->stats.Int+
	op->stats.Con+op->stats.Wis+op->stats.Pow+
	op->stats.Cha;
  } while(sum<82||sum>116);
#if defined( USE_SWAP_STATS) && defined(SORT_ROLLED_STATS)
	/* Sort the stats so that rerolling is easier... */
	{
	        int             i = 0, j = 0;
	        int             statsort[7];

	        statsort[0] = op->stats.Str;
	        statsort[1] = op->stats.Dex;
	        statsort[2] = op->stats.Int;
	        statsort[3] = op->stats.Con;
	        statsort[4] = op->stats.Wis;
	        statsort[5] = op->stats.Pow;
	        statsort[6] = op->stats.Cha;

	        /* a quick and dirty bubblesort? */
	        do {
	                if (statsort[i] < statsort[i + 1]) {
	                        j = statsort[i];
	                        statsort[i] = statsort[i + 1];
	                        statsort[i + 1] = j;
	                        i = 0;
	              } else {
	                        i++;
	              }
	      } while (i < 6);

	        op->stats.Str = statsort[0];
	        op->stats.Dex = statsort[1];
	        op->stats.Con = statsort[2];
	        op->stats.Int = statsort[3];
	        op->stats.Wis = statsort[4];
	        op->stats.Pow = statsort[5];
	        op->stats.Cha = statsort[6];
      }
#endif /* SWAP_STATS */

#if 1
  op->contr->orig_stats.Str=op->stats.Str;
  op->contr->orig_stats.Dex=op->stats.Dex;
  op->contr->orig_stats.Int=op->stats.Int;
  op->contr->orig_stats.Con=op->stats.Con;
  op->contr->orig_stats.Wis=op->stats.Wis;
  op->contr->orig_stats.Pow=op->stats.Pow;
  op->contr->orig_stats.Cha=op->stats.Cha;
#endif
  op->stats.hp= -10000;
  op->level=0;
  op->stats.exp=0;
  op->stats.sp=0;
  op->stats.grace=0;
  op->stats.ac=0;
  add_exp(op,0);
  op->stats.sp=op->stats.maxsp;
  op->stats.hp=op->stats.maxhp;
#ifndef ALLOW_SKILLS /* start grace at maxgrace if no skills */
  op->stats.grace=op->stats.maxgrace; 
#else
  op->stats.grace=0;
#endif
  op->contr->orig_stats=op->stats;
}

void Roll_Again(object *op)
{
#ifndef USE_SWAP_STATS
  new_draw_info(NDI_UNIQUE, 0,op,"Roll again (y/n)? ");
#else
  new_draw_info(NDI_UNIQUE, 0,op,"[y] to roll new stats [n] to use stats");
  new_draw_info(NDI_UNIQUE, 0,op,"[1-7] [1-7] to swap stats.\n");
  new_draw_info(NDI_UNIQUE, 0,op,"Roll again (y/n/1-7)? ");
#endif /* USE_SWAP_STATS */
  if (op->contr->eric_server>0)
    send_query(op->contr->eric_server,CS_QUERY_SINGLECHAR,"");

}

void Swap_Stat(object *op,int Swap_Second)
{
#ifdef USE_SWAP_STATS
  signed char tmp;
  char buf[MAX_BUF];

    if ( op->contr->Swap_First == -1 ) {
	new_draw_info(NDI_UNIQUE, 0,op,"How the hell did you get here?!?!!!");
	new_draw_info(NDI_UNIQUE, 0,op,"Error in Swap_Stat code,");
	new_draw_info(NDI_UNIQUE, 0,op,"mail korg@rdt.monash.edu.au");
	return;
    }

    tmp = get_attr_value(&op->contr->orig_stats, op->contr->Swap_First);

    set_attr_value(&op->contr->orig_stats, op->contr->Swap_First,
	get_attr_value(&op->contr->orig_stats, Swap_Second));

    set_attr_value(&op->contr->orig_stats, Swap_Second, tmp);

    sprintf(buf,"%s done\n", short_stat_name[Swap_Second]);
    new_draw_info(NDI_UNIQUE, 0,op, buf);
    op->stats.Str = op->contr->orig_stats.Str;
    op->stats.Dex = op->contr->orig_stats.Dex;
    op->stats.Con = op->contr->orig_stats.Con;
    op->stats.Int = op->contr->orig_stats.Int;
    op->stats.Wis = op->contr->orig_stats.Wis;
    op->stats.Pow = op->contr->orig_stats.Pow;
    op->stats.Cha = op->contr->orig_stats.Cha;
    op->stats.hp= -10000;
    op->level=0;
    op->stats.exp=0;
    op->stats.sp=0;
    op->stats.grace=0;
    op->stats.ac=0;
    add_exp(op,0);
    op->stats.sp=op->stats.maxsp;
#ifndef ALLOW_SKILLS
    op->stats.grace=op->stats.maxgrace; 
#else
    op->stats.grace=0;
#endif
    op->stats.hp=op->stats.maxhp;
    add_exp(op,0);
    op->contr->Swap_First=-1;
#endif /* USE_SWAP_STATS */
}


/* This code has been greatly reduced, because with set_attr_value
 * and get_attr_value, the stats can be accessed just numeric
 * ids.  stat_trans is a table that translate the number entered
 * into the actual stat.  It is needed because the order the stats
 * are displayed in the stat window is not the same as how
 * the number's access that stat.  The table does that translation.
 */
int key_roll_stat(object *op, char key)
{
    int keynum = key -'0';
    char buf[MAX_BUF];
    static sint8 stat_trans[] = {-1, STR, DEX, CON, INT, WIS, POW, CHA};

#ifdef USE_SWAP_STATS
    if (keynum>0 && keynum<=7) {
	if (op->contr->Swap_First==-1) {
	    op->contr->Swap_First=stat_trans[keynum];
	    sprintf(buf,"%s ->", short_stat_name[stat_trans[keynum]]);
	    new_draw_info(NDI_UNIQUE, 0,op,buf);
	}
	else
	    Swap_Stat(op,stat_trans[keynum]);

	if (op->contr->eric_server>0)
	    send_query(op->contr->eric_server,CS_QUERY_SINGLECHAR,"");
	return 1;
    }
#endif
    switch (key) {
     case 'n':
     case 'N':
      SET_FLAG(op, FLAG_WIZ);
      if(op->map==NULL) {
	LOG(llevError,"Map == NULL in state 2\n");
	break;
      }
      /* So that enter_exit will put us at startx/starty */
      op->x= -1;
      enter_exit(op,NULL);
      /* Enter exit adds a player otherwise */
      if(op->contr->loading == NULL) {
	insert_ob_in_map(op,op->map);
      }
      else {
	op->contr->removed = 0; /* Will insert pl. when map is loaded */
      }
      add_statbonus(op);
      new_draw_info(NDI_UNIQUE, 0,op,"Now choose a character.");
      new_draw_info(NDI_UNIQUE, 0,op,"Press any key to change outlook.");
      new_draw_info(NDI_UNIQUE, 0,op,"Press `d' when you're pleased.");
      op->contr->state = ST_CHANGE_CLASS;
      if (op->contr->eric_server>0)
	  send_query(op->contr->eric_server,CS_QUERY_SINGLECHAR,"");
      return 0;

     case 'y':
     case 'Y':
      roll_stats(op);
      if (op->contr->eric_server>0)
	  send_query(op->contr->eric_server,CS_QUERY_SINGLECHAR,"");
      return 1;

     case 'q':
     case 'Q':
      play_again(op);
      return 1;

     default:
#ifndef USE_SWAP_STATS
      new_draw_info(NDI_UNIQUE, 0,op,"Yes, No or Quit. Roll again?");
#else
      new_draw_info(NDI_UNIQUE, 0,op,"Yes, No, Quit or 1-6.  Roll again?");
#endif /* USE_SWAP_STATS */
      if (op->contr->eric_server>0)
	  send_query(op->contr->eric_server,CS_QUERY_SINGLECHAR,"");
      return 0;
    }
    return 0;
}

/* This function takes the key that is passed, and does the
 * appropriate action with it (change class, or other things.
 */

int key_change_class(object *op, char key)
{
      int tmp_loop;

    if(key=='q'||key=='Q') {
      remove_ob(op);
      play_again(op);
      return 0;
    }
    if(key=='d'||key=='D') {
	/* this must before then initial items are given */
	if (op->contr->eric_server > 0)
	  esrv_new_player(op->contr->eric_server, op->count, op->name, 
		op->weight, op->face->number);
	create_treasure(find_treasurelist("starting_wealth"),op, 0, 0, 0);

	op->contr->state=ST_PLAYING;
#ifdef AUTOSAVE
	op->contr->last_save_tick = pticks;
#endif
	start_info(op);
	CLEAR_FLAG(op, FLAG_WIZ);
#ifdef ALLOW_SKILLS
	(void) init_player_exp(op);
#endif
	give_initial_items(op);
#ifdef ALLOW_SKILLS
	(void) link_player_skills(op);
#endif
	if (op->contr->eric_server > 0)
	  esrv_send_inventory(op, op);
	else
	  draw_all_inventory(op);
	return 0;
    }

    /* Following actually changes the class - this is the default command
     * if we don't match with one of the options above.
     */

    tmp_loop = 0;
    while(!tmp_loop) {
      char *name = add_string (op->name);
      int x = op->x, y = op->y;
      remove_statbonus(op);
      remove_ob (op);
      op->arch = get_player_archetype(op->arch);
      copy_object (&op->arch->clone, op);
      op->stats = op->contr->orig_stats;
      free_string (op->name);
      op->name = name;
      op->x = x;
      op->y = y;
      insert_ob_in_map (op, op->map);
      strncpy(op->contr->title,op->arch->clone.name,MAX_NAME);
      add_statbonus(op);
      tmp_loop=allowed_class(op);
    }
    update_object(op);
    fix_player(op);
    op->stats.hp=op->stats.maxhp;
    op->stats.sp=op->stats.maxsp;
#ifndef ALLOW_SKILLS
	 op->stats.grace=op->stats.maxgrace; 
#else
	 op->stats.grace=0;
#endif
    op->contr->last_value= -1;
    draw_stats(op);
    if (op->contr->eric_server>0)
	send_query(op->contr->eric_server, CS_QUERY_SINGLECHAR,"");
    return 0;
}

int key_confirm_quit(object *op, char key)
{
    char buf[MAX_BUF];

    if(key!='y'&&key!='Y'&&key!='q'&&key!='Q') {
      op->contr->state=ST_PLAYING;
      new_draw_info(NDI_UNIQUE, 0,op,"OK, continuing to play.");
      return 1;
    }
    terminate_all_pets(op);
    remove_ob(op);
    op->direction=0;
    op->contr->count_left=0;
    op->map->players--;
    /* Just in case we fail to add the player */
    op->map->timeout = MAP_TIMEOUT(op->map);
    new_draw_info_format(NDI_UNIQUE | NDI_ALL, 5, NULL,
	"%s quits the game.",op->name);

    strcpy(op->contr->killer,"quit");
    check_score(op);
#ifdef SIMPLE_PARTY_SYSTEM
    op->contr->party_number=(-1);
#endif /* SIMPLE_PARTY_SYSTEM */
#ifdef SET_TITLE
    op->contr->own_title[0]='\0';
#endif /* SET_TITLE */
    load_default_keys(op->contr);
    if(!QUERY_FLAG(op,FLAG_WAS_WIZ)) {
      sprintf(buf,"%s/%s/%s.pl",settings.libdir,settings.playerdir,op->name);
      if(unlink(buf)== -1 && settings.debug)
        perror("crossfire (delete character)");
    }
    play_again(op);
    return 1;
}


void flee_player(object *op) {
  int dir,diff;
  if(op->stats.hp < 0) {
    LOG(llevDebug, "Fleeing player is dead.\n");
    CLEAR_FLAG(op, FLAG_SCARED);
    return;
  }
  if(op->enemy==NULL) {
    LOG(llevDebug,"Fleeing player had no enemy.\n");
    CLEAR_FLAG(op, FLAG_SCARED);
    return;
  }
  if(!(RANDOM()%5)&&RANDOM()%20+1>=savethrow[op->level]) {
    op->enemy=NULL;
    CLEAR_FLAG(op, FLAG_SCARED);
    return;
  }
  dir=absdir(4+find_dir_2(op->x-op->enemy->x,op->y-op->enemy->y));
  for(diff=0;diff<3;diff++) {
    int m=1-(RANDOM()&2);
    if(move_ob(op,absdir(dir+diff*m))||
       (diff==0&&move_ob(op,absdir(dir-diff*m)))) {
      draw(op);
      return;
    }
  }
  /* Cornered, get rid of scared */
  CLEAR_FLAG(op, FLAG_SCARED);
  op->enemy=NULL;
}


/* check_pick sees if there is stuff to be picked up/picks up stuff.
 * IT returns 1 if the player should keep on moving, 0 if he should
 * stop.
 */
int check_pick(object *op) {
  int item_picked=0;		/* set to true if an item is picked up */

  if(QUERY_FLAG(op,FLAG_FLYING) || op->below==NULL || !can_pick(op,op->below))
    return 1;

#ifdef SEARCH_ITEMS
  if(op->contr->search_str[0]!='\0')
    {
      object *next,*tmp;
      tmp=op->below;
      while(tmp!=NULL&&can_pick(op,tmp))
	{
	  next=tmp->below;
	  if(item_matched_string(op, tmp, op->contr->search_str)) {
	    pick_up(op,tmp);
	    item_picked=1;
	  }
	  tmp=next;
	}
    }
#endif /* SEARCH_ITEMS */


  switch (op->contr->mode) {
	case 0:	return 1;	/* don't pick up */

	case 1:
		pick_up(op,op->below);
		return 1;

	case 2:
		pick_up(op,op->below);
		return 0;

	case 3: return 0;	/* stop before pickup */

	case 4:
	case 5: 
	case 6:
	case 7: {
		object *item,*temp;

		item = op->below;
		op->contr->freeze_inv=1;
		while (item) {
		    temp = item->below;
		    if (can_pick(op, item)) {
			if (op->contr->mode==6) {
			    if (QUERY_FLAG(item, FLAG_KNOWN_MAGICAL) &&
			      !QUERY_FLAG(item, FLAG_KNOWN_CURSED)) {
				pick_up(op, item);
				item_picked=1;
			    }
			}
			else if (op->contr->mode==7) {
			    if (item->type==MONEY || item->type==GEM) {
				item_picked=1;
				pick_up(op, item);
			    }
			} else {
			    item_picked=1;
			    pick_up(op, item);
			}
		    }
		    item = temp;
		}
		op->contr->freeze_inv=0;
		if (item_picked && !op->contr->eric_server)
		    draw_inventory(op);
		return (op->contr->mode != 4 ? 1 : 0);
	}

	/* use value density */
	default: {
	    object * item,*temp;

	    item=op->below;
	    op->contr->freeze_inv=1;
	    while(item) {
		temp=item->below;
		if(can_pick(op, item) && !(QUERY_FLAG(item, FLAG_UNPAID)) &&
		 (query_cost(item,op,F_TRUE)*100/
		    (item->weight * MAX(item->nrof,1))>= op->contr->mode) ) {
			item_picked=1;
			pick_up(op,item);
		}
		item=temp;
	    }
	    op->contr->freeze_inv=0;
	    if (item_picked && !op->contr->eric_server)
		draw_inventory(op);
	    return 1;
	}

  }
  return 1; /* Statement supposedly can't be reached */
}

/*
 *  Find an arrow in the inventory and after that
 *  in the right type container (quiver). Pointer to the 
 *  found object is returned.
 */
object *find_arrow(object *op, char *type)
{
  object *tmp = NULL;

  for(op=op->inv; op; op=op->below)
    if(!tmp && op->type==CONTAINER && op->race==type &&
      QUERY_FLAG(op,FLAG_APPLIED))
      tmp = find_arrow (op, type);
    else if (op->type==ARROW && op->race==type)
      return op;
  return tmp;
}

/*
 *  Player fires a bow. This probably should be combined with
 *  monster_use_bow().
 */
static void fire_bow(object *op, int dir)
{
  object *bow, *arrow = NULL, *left;
  for(bow=op->inv; bow; bow=bow->below)
    if(bow->type==BOW && QUERY_FLAG(bow, FLAG_APPLIED))
      break;
#ifdef MANY_CORES /* there must be applied bow if shoot_type is range_bow */
  if (!bow) {
    LOG (llevError, "Range: bow without activated bow.\n");
    abort();
  }
#endif
  if( !bow->race ) {
    sprintf (errmsg, "Your %s is broken.", bow->name);
    new_draw_info(NDI_UNIQUE, 0,op, errmsg);
    op->contr->count_left=0;
    return;
  }
  if ((arrow=find_arrow(op, bow->race)) == NULL) {
    sprintf (errmsg, "You have no %s left.", bow->race);
    new_draw_info(NDI_UNIQUE, 0,op,errmsg);
    op->contr->count_left=0;
    return;
  }
  if(wall(op->map,op->x+freearr_x[dir],op->y+freearr_y[dir])) {
    new_draw_info(NDI_UNIQUE, 0,op,"Something is in the way.");
    op->contr->count_left=0;
    return;
  }
  /* this should not happen, but sometimes does */
  if (arrow->nrof==0) {
	remove_ob(arrow);
	free_object(arrow);
	return;
  }
  left = arrow; /* these are arrows left to the player */
  arrow = get_split_ob(arrow, 1);
  set_owner(arrow,op);
  arrow->direction=dir;
  arrow->x = op->x;
  arrow->y = op->y;
  arrow->speed = 1;
  op->speed_left = 0.01 - (float) FABS(op->speed) * 100 / bow->stats.sp;
  fix_player(op);
  update_ob_speed(arrow);
  arrow->speed_left = 0;
  arrow->face  = &new_faces[arrow->arch->faces[dir]];
  arrow->stats.sp = arrow->stats.wc; /* save original wc and dam */
  arrow->stats.hp = arrow->stats.dam; 
  arrow->stats.dam += (QUERY_FLAG(bow, FLAG_NO_STRENGTH) ?
		      0 : dam_bonus[op->stats.Str]) +
			bow->stats.dam + bow->magic + arrow->magic;
  arrow->stats.wc = 20 - bow->magic - arrow->magic - SK_level(op) -
    dex_bonus[op->stats.Dex] - thaco_bonus[op->stats.Str] - arrow->stats.wc -
    bow->stats.wc;

  arrow->map = op->map;
  SET_FLAG(arrow, FLAG_FLYING);
  SET_FLAG(arrow, FLAG_FLY_ON);
  SET_FLAG(arrow, FLAG_WALK_ON);
#ifdef SOUND_EFFECTS
  play_sound_map(op->map, op->x, op->y, SOUND_FIRE_ARROW);
#endif
  D_LOCK(op);
  insert_ob_in_map(arrow,op->map);
  move_arrow(arrow);
  D_UNLOCK(op);
  if (op->contr->eric_server > 0) {
    if (QUERY_FLAG(left, FLAG_FREED))
      esrv_del_item(op->contr->eric_server, left->count);
    else
      esrv_send_item(op, left);
  }  
}

void fire(object *op,int dir) {
  object *weap=NULL;
  int spellcost=0;

  /* check for loss of invisiblity/hide */
  if (action_makes_visible(op)) make_visible(op);

   /* a check for players, make sure things are groovy. This routine
    * will change the skill of the player as appropriate in order to
    * fire whatever is requested. In the case of spells (range_magic)
    * it handles whether cleric or mage spell is requested to be cast. 
    * -b.t. 
    */ 
#ifdef ALLOW_SKILLS 
  if(op->type==PLAYER&&!QUERY_FLAG(op,FLAG_WIZ)) 
	if(!check_skill_to_fire(op)) return;
#endif

  switch(op->contr->shoottype) {
  case range_none:
    return;

  case range_bow:
    fire_bow(op, dir);
    return;

  case range_magic: /* Casting spells */

    op->contr->shoottype= range_magic;

    spellcost=cast_spell(op,op,dir,op->contr->chosen_spell,0,spellNormal,NULL);

    if(spells[op->contr->chosen_spell].cleric)
        op->stats.grace-=spellcost;
    else
	op->stats.sp-=spellcost;

    draw_stats(op);

    return;

  case range_wand:
    for(weap=op->inv;weap!=NULL;weap=weap->below)
      if(weap->type==WAND&&QUERY_FLAG(weap, FLAG_APPLIED))
	break;
    if(weap==NULL) {
      new_draw_info(NDI_UNIQUE, 0,op,"You have no wand readied.");
      op->contr->count_left=0;
      return;
    }
    if(weap->stats.food<=0) {
#ifdef SOUND_EFFECTS
      play_sound_player_only(op->contr, SOUND_WAND_POOF);
#endif
      new_draw_info(NDI_UNIQUE, 0,op,"The wand says poof.");
      return;
    }
    if(cast_spell(op,weap,dir,op->contr->chosen_item_spell,0,spellWand,NULL)) {
      SET_FLAG(op, FLAG_BEEN_APPLIED); /* You now know something about it */
      if (!(--weap->stats.food))
      {
	object *tmp;
	if (weap->arch) {
	  CLEAR_FLAG(weap, FLAG_ANIMATE);
	  weap->face = weap->arch->clone.face;
	  weap->speed = 0;
	  update_ob_speed(weap);
	}
	if ((tmp=is_player_inv(weap)))
	  draw_inventory_faces(tmp);
      }
    }
    return;
  case range_rod:
  case range_horn:
    for(weap=op->inv;weap!=NULL;weap=weap->below)
      if(QUERY_FLAG(weap, FLAG_APPLIED)&&
	 weap->type==(op->contr->shoottype==range_rod?ROD:HORN))
	break;
    if(weap==NULL) {
      char buf[MAX_BUF];
      sprintf(buf, "You have no %s readied.",
	op->contr->shoottype == range_rod ? "rod" : "horn");
      new_draw_info(NDI_UNIQUE, 0,op, buf);
      op->contr->count_left=0;
      return;
    }
    if(weap->stats.hp<spells[weap->stats.sp].sp) {
      LOG(llevDebug,"Horn/rod: %d < %d (%d)\n", weap->stats.hp, spells[weap->stats.sp].sp, weap->stats.sp);
#ifdef SOUND_EFFECTS
      play_sound_player_only(op->contr, SOUND_WAND_POOF);
#endif
      if (op->contr->shoottype == range_rod)
	new_draw_info(NDI_UNIQUE, 0,op,"The rod whines for a while, but nothing happens.");
      else
	new_draw_info(NDI_UNIQUE, 0,op,
	          "No matter how hard you try you can't get another note out.");
      return;
    }
    if(cast_spell(op,weap,dir,op->contr->chosen_item_spell,0,
       op->contr->shoottype == range_rod ? spellRod : spellHorn,NULL)) {
      SET_FLAG(op, FLAG_BEEN_APPLIED); /* You now know something about it */
      drain_rod_charge(weap);
    }
    return;
  case range_scroll: /* Control summoned monsters from scrolls */
    if(op->contr->golem==NULL) {
      op->contr->shoottype=range_none;
      op->contr->chosen_spell= -1;
      draw_stats(op);
    }
    else 
	control_golem(op->contr->golem, dir);
    return;
  case range_skill:
    if(!op->chosen_skill) { 
	if(op->type==PLAYER)
      	    new_draw_info(NDI_UNIQUE, 0,op,"You have no applicable skill to use.");
	return;
    }
    (void) do_skill(op,dir,NULL);
    return;
  default:
    new_draw_info(NDI_UNIQUE, 0,op,"Illegal shoot type.");
    op->contr->count_left=0;
    return;
  }
}

/* This function is just part of a breakup from move_player.
 * It should keep the code cleaner.
 * When this is called, the players direction has been updated
 * (taking into accoutn confusion.)  The player is also actually
 * going to try and move (not fire weapons).
 */

void move_player_attack(object *op, int dir)
{
  object *tmp, *tmp2;
  int nx=freearr_x[dir]+op->x,ny=freearr_y[dir]+op->y;


  /* If braced, or can't move to the square, and it is not out of the
   * map, attack it.  Note order of if statement is important - don't
   * want to be calling move_ob if braced, because move_ob will move the
   * player.  This is a pretty nasty hack, because if we could
   * move to some space, it then means that if we are braced, we should
   * do nothing at all.  As it is, if we are braced, we go through
   * quite a bit of processing.  However, it probably is less than what
   * move_ob uses.
   */
  if ((op->contr->braced || !move_ob(op,dir)) &&
    !out_of_map(op->map,nx,ny)) {
    
    op->contr->has_hit = 1; /* The last action was to hit, so use weapon_sp */

    if ((tmp=get_map_ob(op->map,nx,ny))==NULL) {
/*	LOG(llevError,"player_move_attack: get_map_ob returns NULL, but player can not more there.\n");*/
	return;
    }

    /* Go through all the objects, and stop if we find one of interest. */
    while (tmp->above!=NULL) {
      if ((QUERY_FLAG(tmp,FLAG_ALIVE) || QUERY_FLAG(tmp,FLAG_CAN_ROLL)
	    || tmp->type ==LOCKED_DOOR) && tmp!=op)
	    break;
      tmp=tmp->above;
    }
    
    if (tmp==NULL)	/* This happens anytime the player tries to move */
	return;		/* into a wall */

    if(tmp->head != NULL)
      tmp = tmp->head;

    /* for 'fragile' forms of invisibiity, eg hidden or invisibility spell, 
     * any of the following actions, if true,  will make us become seen */

    /* This blocks deals with opening a normal door.  We look for a key,
     * and if we found one, break the door.  If not, let normal attack
     * code deal with it.
     */
    if (tmp->type==DOOR && tmp->stats.hp>=0) {
      tmp2=op->inv;
      while(tmp2!=NULL&&tmp2->type!=KEY) /* Find a key */
	tmp2=tmp2->below;

      if(tmp2!=NULL) {	/* we found a key */
#ifdef SOUND_EFFECTS
	play_sound_map(op->map, op->x, op->y, SOUND_OPEN_DOOR);
#endif
	decrease_ob(tmp2); /* Use up one of the keys */
	hit_player(tmp,9999,op,AT_PHYSICAL); /* Break through the door */
	if(tmp->inv && tmp->inv->type ==RUNE) spring_trap(tmp->inv,op);	
      }
      if(action_makes_visible(op)) make_visible(op);
    }

    /* This area deals with locked doors.  These are doors that require
     * special keys.
     */

    if(tmp->type==LOCKED_DOOR) {
      tmp2=op->inv;
      while(tmp2 && (tmp2->type != SPECIAL_KEY ||
	    tmp2->slaying != tmp->slaying)) /* Find the key */
	tmp2=tmp2->below;

      if(tmp2) {
	  decrease_ob_nr(tmp2, 1); /* Use the key */
	  remove_door2(tmp); /* remove door without violence ;-) */
#ifdef SOUND_EFFECTS
	  play_sound_map(op->map, op->x, op->y, SOUND_OPEN_DOOR);
#endif
      } else if (tmp->msg) /* show door's message if present */
	    new_draw_info(NDI_UNIQUE | NDI_NAVY, 0, op, tmp->msg);
      if(action_makes_visible(op)) make_visible(op);
    }

    /* The following deals with possibly attacking peaceful
     * or frienddly creatures.  Basically, all players are considered
     * unaggressive.  If the moving player has peaceful set, then the
     * object should be pushed instead of attacked.  It is assumed that
     * if you are braced, you will not attack friends accidently,
     * and thus will not push them.
     */

    if (tmp->enemy != op &&
      (tmp->type==PLAYER || QUERY_FLAG(tmp,FLAG_UNAGGRESSIVE)
	|| QUERY_FLAG(tmp, FLAG_FRIENDLY)) &&
	op->contr->peaceful && (!op->contr->braced)) {
#ifdef SOUND_EFFECTS
	  play_sound_map(op->map, op->x, op->y, SOUND_PUSH_PLAYER);
#endif
	  (void) push_ob(tmp,dir,op);
          if(op->contr->tmp_invis||op->hide) make_visible(op);
	}

      /* If the object is a boulder or other rollable object, then
       * roll it if not braced.  You can't roll it if you are braced.
       */
      else if(QUERY_FLAG(tmp,FLAG_CAN_ROLL)&&(!op->contr->braced)) {
	  recursive_roll(tmp,dir,op);
          if(action_makes_visible(op)) make_visible(op);

      }

      /* Any generic living creature.  Including things like doors.
       * Way it works is like this:  First, it must have some hit points
       * and be living.  Then, it must be one of the following:
       * 1) Not a player, 2) A player, but of a different party.  Note
       * that party_number -1 is no party, so attacks can still happen.
       */

      else 
#ifdef SIMPLE_PARTY_SYSTEM	
	if ((tmp->stats.hp>=0) && QUERY_FLAG(tmp, FLAG_ALIVE) &&
	((tmp->type!=PLAYER || op->contr->party_number==-1 ||
	op->contr->party_number!=tmp->contr->party_number))) {
#else
	if ((tmp->stats.hp>=0) && QUERY_FLAG(tmp, FLAG_ALIVE)) {
#endif

#ifdef ALLOW_SKILLS
            skill_attack(tmp, op, 0, NULL); 
#else 
            (void) attack_ob(tmp, op);
#endif 
	  /* If attacking another player, that player gets automatic
	   * hitback, and doesn't loose luck either.
	   */
	  if (tmp->type == PLAYER && tmp->stats.hp >= 0 &&
	     !tmp->contr->has_hit)
	  {
	    short luck = tmp->stats.luck;
	    tmp->contr->has_hit = 1;
#ifdef ALLOW_SKILLS
	    skill_attack(op, tmp, 0, NULL); 
#else
	    (void) attack_ob(op, tmp);
#endif
	    tmp->stats.luck = luck;
	  }
	  if(action_makes_visible(op)) make_visible(op);
	}
    }
}

int move_player(object *op,int dir) {
  int face = dir ? (dir - 1) / 2 : -1;

  if(op->map == NULL || op->map->in_memory != MAP_IN_MEMORY)
    return 0;

  /* peterm:  added following line */
  op->facing = dir;
  if(QUERY_FLAG(op,FLAG_CONFUSED) && dir)
    dir = absdir(dir + RANDOM()%3 + RANDOM()%3 - 2);

  if(op->hide) do_hidden_move(op);

  if(op->contr->fire_on)
    fire(op,dir);
  else move_player_attack(op,dir);

  if((check_pick(op)&&op->contr->run_on)||
     (op->contr->berzerk&&op->contr->run_on))
    op->direction=dir;
  else
    op->direction=0;
  if(face != -1)
    op->face = &new_faces[op->arch->faces[face]];
  update_object(op);
  return 0;
}

int has_cleared_window(Window w,Window *win_clr,int max) {
  for(max--;max>=0;max--)
    if(win_clr[max]==w)
      return 1;
  return 0;
}

void handle_win_message_button(object *op)
{
    int i,y=op->contr->gevent.xbutton.y,x=op->contr->gevent.xbutton.x,
	button=op->contr->gevent.xbutton.button;

    if (op->contr->show_buttons) {
	if (y>62) {
	    extern int opX [NUMOPBUTTS];

	    for (i=0; i< NUMOPBUTTS+1; i++) {
		if (x>opX[i]&&x<opX[i]+64&&y>opY[i]&&y<opY[i]+18) {
		    if  (i == CHANGE_button) {
			if (button == 1)
			    change_spell (op,'+');
			else if (button == 2)
			    change_spell (op,'-');
		    } else if (i == APPLY_button) {
			if (button == 1)
			    apply_below (op);
			else if (button ==2) {
			    apply_inventory(op);
			    if (op->contr->show_what == show_applied ||
				op->contr->show_what == show_unapplied)
				    draw_inventory(op);
			}
		    } else if (i == TALK_button)
			;
		    else if (i == PEACE_button) {
			char pbuf[]="peaceful"; /*parse_string needs a writable string */
			   parse_command (op,pbuf);
		    }
		} /* If we got a button match */
	    } /* cycle through buttons */
	} else { /*direction button */
	    for (i=0;i<NUMDIRBUTTS+1;i++)
	        if(x>dirX[i]&&x<dirX[i]+dirW&&y>dirY[i]&&y<dirY[i]+18) {
		    if (button == 2)
		        op->contr->fire_on=1;
		    else if (button == 3)
		        op->contr->run_on=1;

		    switch (i) {
			case NW_button: move_player (op,8);break;
			case NO_button: move_player (op,1);break;
			case NE_button: move_player (op,2);break;
			case WE_button: move_player (op,7);break;
			case BR_button: break;
			case EA_button: move_player (op,3);break;
			case SW_button: move_player (op,6);break;
			case SO_button: move_player (op,5);break;
			case SE_button: move_player (op,4);break;
			default:break;
		    }
		    op->contr->fire_on=0;
		    op->contr->run_on=0;
		} /* got a button match */
	} /* is in the direction button area */
    } /* we are showing the button */
}

/* splitting this out created a 292 line function !!!!!!*/
/* Returns whether or not to keep processing events */
int handle_buttonpress(object *op)
{
  /* Need some more local variables */
  int i=0,y=op->contr->gevent.xbutton.y,x=op->contr->gevent.xbutton.x,
  button=op->contr->gevent.xbutton.button;
  int dx=(x-2)/24-5,dy=(y-2)/24-5;
  object *inv;

  inv = op->inv;
  if(op->contr->state||QUERY_FLAG(op,FLAG_REMOVED))
    return 1;
  if(op->above) {
    remove_ob(op);
    insert_ob_in_map(op,op->map);
  }
  /* Game window is where the map is located */
  if(op->contr->gevent.xbutton.window==op->contr->win_game) {
    switch (button) {
     case 1:
      {
	if(dx<WINLEFT||dx>WINRIGHT||dy<WINUPPER||dy>WINLOWER)
	  return 1;
	if(op->contr->blocked_los[dx+5][dy+5])
	  return 1;
	look_at(op,dx,dy);
	op->contr->last_cmd=(dx||dy)?0:2;
      }
      return 1;

     case 2:
     case 3:
      if (button == 2)
	op->contr->fire_on=1;
      if (x<115)
	i = 0;
      else if (x>149)
	i = 6;
      else i =3;

      if (y>152)
	i += 2;
      else if (y>115)
	i++;

      switch (i) {
       case 0: move_player (op,8);break;
       case 1: move_player (op,7);break;
       case 2: move_player (op,6);break;
       case 3: move_player (op,1);break;
       case 5: move_player (op,5);break;
       case 6: move_player (op,2);break;
       case 7: move_player (op,3);break;
       case 8: move_player (op,4);break;
      }
      /* Move player doesn't decrease the speed, so we will */
      op->speed_left--;
      if (button == 2)
	op->contr->fire_on=0;
      if (op->speed_left>0) return 1;
      return 0;
    }
    return 1;
  } else if(op->contr->gevent.xbutton.window == op->contr->win_message) {
	handle_win_message_button(op);
	return 1;
  } else if(op->contr->gevent.xbutton.window==op->contr->win_inv) {
    int dy=(y-16)/24+op->contr->scroll_inv;
    object *tmp;

    /* Need to deal with the scrollbar */
    if (x>(op->contr->invhint.width-30)) {
      dy-=op->contr->scroll_inv;
      switch(button) {
       case 1:
      op->contr->scroll_inv-=dy>0?dy:1;
      draw_inventory(op);
      return 1;
       case 2:
      {
	int objects,scroll;
	object *tmp;

	for(tmp=inv,objects=0;tmp!=NULL;tmp=tmp->below)
	  if (show_what_object(tmp, op->contr->show_what)) 
	    objects++;
	scroll=(y-17)*objects/op->contr->barlength_inv;
	if(op->contr->scroll_inv==scroll)
	  return 1;
	op->contr->scroll_inv=scroll;
	draw_inventory(op);
	return 1;
      }
       case 3:
      op->contr->scroll_inv+=dy>0?dy:1;
      draw_inventory(op);
      return 1;
      }
      return 1;
    }
    if(dy<0)
      return 1;
    for(tmp=inv,i=0;tmp!=NULL;tmp=tmp->below) {
      while(tmp != NULL && (!show_what_object(tmp, op->contr->show_what)))
      tmp = tmp->below;
      if (tmp == NULL)
	return 1;
      if (i++ == dy) {
      switch(button) {
       case 1:
	if(op->contr->gevent.xbutton.state == ShiftMask) {
	  if(QUERY_FLAG(tmp,FLAG_INV_LOCKED))
	    unlock_inv(op,tmp);
	  else
	    lock_inv(op,tmp);
	}
	else
	  examine(op,tmp);
	return 1;
       case 2:
	if (op->contr->gevent.xbutton.state == ShiftMask) {
            op->contr->mark=tmp;
            op->contr->mark_count=tmp->count;
            new_draw_info_format(NDI_UNIQUE,0,op,"Marked item %s", query_name(tmp));
	} else {
	    apply(op,tmp);
	    if (op->contr->show_what == show_applied || op->contr->show_what==show_unapplied)
		draw_inventory(op);
	}
	return 1;
       case 3:
	if(tmp->type==CONTAINER && QUERY_FLAG(tmp,FLAG_APPLIED)) {
	  if(op->container==tmp) return 1; /* fix an endless loop problem */
	  for(inv=tmp->inv;inv!=NULL;) {
	    /* The drop may fail if dropping from container TO container */
	    tmp=inv->below;
	    drop(op,inv);
	    inv=tmp;
	  }
	}
	else drop(op,tmp);
	return 1;
      }
      return 1;
      }
    }
    return 1;
  /* look window is used for what the character is standing on. */
  } else if(op->contr->gevent.xbutton.window==op->contr->win_look) {
    int dy=(y-16)/24+op->contr->scroll_look;
    object *top, *tmp;
 
    if(x>(op->contr->lookhint.width-30)) {
      dy-=op->contr->scroll_look;
      switch(button) {
       case 1:
      op->contr->scroll_look-=dy>0?dy:1;
      draw_look(op);
      return 1;
       case 2:
      {
	int objects,scroll;

	/* Eneq(@csd.uu.se): ... */    

	for(tmp=(op->container?op->container->inv:op->below),objects=0;tmp!=NULL;tmp=tmp->below)
	  if(!(tmp->invisible))
	    objects++;
	scroll=(y-17)*objects/op->contr->barlength_look;
	if(op->contr->scroll_look==scroll)
	  return 1;
	op->contr->scroll_look=scroll;
	draw_look(op);
	return 1;
      }
       case 3:
      op->contr->scroll_look+=dy>0?dy:1;
      draw_look(op);
      return 1;
      }
      return 1;
    }
    if(dy<0)
      return 1;
    if(wall(op->map,op->x,op->y))
      return 1;
	  
    /* Eneq(@csd.uu.se): Altered container-treatment, displays
       contents in look-window. */
    if (op->container)
      top=op->container->inv;
    else
      for(top=get_map_ob(op->map,op->x,op->y);
	top!=NULL&&top->above!=NULL&&top->above!=op;top=top->above);

    /* Moved i<dy out of for loop.  This is because with it in
     * the for loop, a check to see if it is a valid item for the
     * look window is never made.  master@rahul.net
     */
    for(tmp=top,i=0;(tmp!=NULL)&&!(tmp->above&&
	                         QUERY_FLAG(tmp->above,FLAG_IS_FLOOR))
      ;tmp=tmp->below)
      if (LOOK_OBJ(tmp)) {
      i++;
      if (i>dy) break;
      }
    if(i<=dy)                 /* means we did not find enough valid objects */
	return 1;
    switch(button) {
     case 1:
      examine(op,tmp);
      return 1;
     case 2:
      apply(op,tmp);
      draw_look(op);
      return 1;
     case 3:
      pick_up(op,tmp);
      return 1;
    }
    return 1;
  } else if(op->contr->gevent.xbutton.window==op->contr->win_info) {
    object *tmp;
    switch(button) {
     case 1:
     case 2:
      if(op->contr->last_cmd!=1) {
      inventory(op,NULL);
      op->contr->last_cmd=1;
      return 1;
      }
      for(tmp=inv,i=0;tmp!=NULL&&i<(y-13)/13;tmp=tmp->below,i++);
      if(tmp!=NULL) {
      if(button==1)
	apply(op,tmp);
      else
	drop(op,tmp);
      inventory(op,NULL);
      op->contr->last_cmd=1;
      }
      return 1;
     case 3:
      if(op->contr->last_cmd!=2) {
      look_at(op,0,0);
      op->contr->last_cmd=2;
      return 1;
      }
      if(wall(op->map,op->x,op->y))
      return 1;
      for(tmp=op->below,i=0;tmp!=NULL&&
	(QUERY_FLAG(op,FLAG_WIZ)||(tmp->type!=EARTHWALL&&tmp!=op))&&i<(y-13)/13;
	tmp=tmp->below,i++);
      if(tmp!=NULL) {
      pick_up(op,tmp);
      op->contr->last_cmd=0;
      }
      return 1;
     default:
      new_draw_info(NDI_UNIQUE, 0,op,"Undefined button.");
      return 1;
    }
  }
  abort(); /* eanders: claim is this should never be reached; 
	    if it is, look at 91.5 code to determine what the value of
	    repeat was supposed to be and make sure you don't reach here. */
}

int receive_play_again(object *op, char key)
{
    if(key=='q'||key=='Q') {
      remove_friendly_object(op);
      leave(op->contr);
      return 2;
    }
    else if(key=='a'||key=='A') {
      object *tmp;
      remove_friendly_object(op);
      op->contr->ob=get_player(op->contr,op->map);
      tmp=op->contr->ob;
      add_friendly_object(tmp);
      tmp->contr->password[0]='~';
      if(tmp->name!=NULL)
          free_string(tmp->name);
      get_name(tmp);
      add_refcount(tmp->name = op->name);
      op->type=DEAD_OBJECT;
      free_object(op);
      op=tmp;
    }
    return 0;
}

/* splitting this out produced a 250 line function !!!!! */
/* return 1 = repeat, return 0 = don't repeat, return 2 = immediate return */
/* needs keycode, gkey, char (text[0]) */
int handle_keypress(object *op,unsigned int keycode,KeySym keysym,char key)
{
  op->contr->count_left=0;
  switch(op->contr->state) {
   case ST_PLAYING:
    return parse_key(op,key,keycode,
	           keysym);

   case ST_PLAY_AGAIN:
    return receive_play_again(op, key);

   case ST_ROLL_STAT:
	return key_roll_stat(op, key);

   case ST_CHANGE_CLASS:
	return key_change_class(op, key);

   case ST_CONFIRM_QUIT:
	return key_confirm_quit(op, key);

   case ST_CONFIGURE:
    configure_keys(op, keycode, keysym);
    return 1;

   case ST_GET_NAME:          /* Waiting for player name */
    receive_player_name(op,key);
    return 1;

   case ST_GET_PASSWORD:      /* Waiting for player password */
   case ST_CONFIRM_PASSWORD:  /* Confirm new password */
    receive_player_password(op,key);
    return 1;


   case ST_MENU_MORE:         /* more items to be listed */
    shop_listing_more(op);
    return 0;


#ifdef SIMPLE_PARTY_SYSTEM
   case ST_GET_PARTY_PASSWORD:        /* Get password for party */
    receive_party_password(op,key);
    return 1;
#endif /* SIMPLE_PARTY_SYSTEM */


   default:
    LOG(llevError,"Illegal state: %d\n",op->contr->state);
    return 0;
  }
  abort(); /* eanders: claim is this should never be reached;
	    if it is, look at 91.5 code to determine what the value of repeat
	    was supposed to be and return the right thing. */
}

/* This is similar to handle_player, below, but is only used by the
 * new client/server stuff.
 * This is sort of special, in that the new client/server actually uses
 * the new speed values for commands.
 *
 * Returns true if there are more actions we can do.
 */
int handle_newcs_player(object *op)
{
    if(op->contr->state == ST_PLAYING && op->contr->loading != NULL) {
	if(op->contr->loading->in_memory == MAP_IN_MEMORY) {
	    LOG(llevDebug,"In handle player, entering map\n");
	    enter_map(op);
	}
	else
	    return 0;
    }
    if(op->invisible&&!(QUERY_FLAG(op,FLAG_MAKE_INVIS))) {
	op->invisible--;
	if(!op->invisible) make_visible(op);
    }
    /* This should really be called after we move, since somethings
     * could actually damage us.
     */
    esrv_update_stats(op->contr->eric_server,op);

    if(op->direction && (op->contr->run_on || op->contr->fire_on)) {
	/* All move commands take 1 tick, at least for now */
	op->speed_left--;
	/* Instead of all the stuff below, let move_player take care
	 * of it.  Also, some of the skill stuff is only put in
	 * there, as well as the confusion stuff.
	 */
	move_player(op, op->direction);
#if 0
	if(op->contr->berzerk&&(op->contr->braced||(!op->contr->fire_on)))
	    move_player(op,op->direction);
	else if((!move_ob(op,op->direction) && !op->contr->run_on)
		||!check_pick(op))
	    op->direction=0;
#endif
	if (op->speed_left>0) return 1;
	else return 0;
    }
    return 0;
}

void handle_player(object *op) {
  int repeat,windex=0;
  char text[10];
  Window win_clr[10];

  if(op->contr->state == ST_PLAYING && op->contr->loading != NULL) {
    op->speed_left++;
    if(op->contr->loading->in_memory == MAP_IN_MEMORY) {
      LOG(llevDebug,"In handle player, entering map\n");
      enter_map(op);
    }
    else
      return;
  }
  /* Have part of an unprocessed complex command to do still */
  if (op->contr->input_buf[0]!='\0') {
    parse_command(op, op->contr->input_buf);
    /* If the player still has time left, handle_player will just be called again */
    return;
  }

  if(op->invisible&&!(QUERY_FLAG(op,FLAG_MAKE_INVIS))) {
    op->invisible--;
    if(!op->invisible) make_visible(op);
  }
  op->contr->has_hit=0;
  draw_stats(op);
  if (op->contr->eric_server>0) {
	LOG(llevError,"new client/server player called handle_player\n");
	return;
  }

  repeat=1;
  while(repeat) {
    if (QUERY_FLAG(op,FLAG_SCARED)) {
      flee_player(op);
      return;
    }
    repeat=0;
    if (XEventsQueued(op->contr->gdisp, QueuedAfterReading)==0) {

      if(op->contr->count_left>0) {
	int tmp_fire_on=op->contr->fire_on;
	op->contr->fire_on=op->contr->prev_fire_on;
	op->contr->count_left--;
	parse_key(op, op->contr->prev_cmd,
		  op->contr->prev_keycode, op->contr->prev_keysym);
	op->contr->fire_on=tmp_fire_on;
      } else if(op->direction)
	if(op->contr->berzerk&&(op->contr->braced||(!op->contr->fire_on)))
	  move_player(op,op->direction);
	else if(!move_ob(op,op->direction)||!check_pick(op))
	  op->direction=0;
      return;
    }
    op->contr->idle=0;
    XNextEvent(op->contr->gdisp,&op->contr->gevent);
    switch(op->contr->gevent.type) {
    case ConfigureNotify:
      if(op->contr->gevent.xconfigure.window==op->contr->win_info)
	resize_win_info(op->contr,&op->contr->gevent);
      else if(op->contr->gevent.xconfigure.window==op->contr->win_inv)
	resize_win_inv(op->contr,&op->contr->gevent);
      else if(op->contr->gevent.xconfigure.window==op->contr->win_look)
	resize_win_look(op->contr,&op->contr->gevent);
      break;
    case Expose:
      repeat=1;
      if(has_cleared_window(op->contr->gevent.xexpose.window,win_clr,windex))
	break;
      win_clr[windex++]=op->contr->gevent.xexpose.window;
      if(op->contr->gevent.xexpose.window==op->contr->win_stats) {
	XClearWindow(op->contr->gdisp,op->contr->win_stats);
	op->contr->last_value= -1;
	draw_stats(op);
      } else if(op->contr->gevent.xexpose.window==op->contr->win_info)
	draw_all_info(op);
      else if(op->contr->gevent.xexpose.window==op->contr->win_inv)
	draw_all_inventory(op);
      else if(op->contr->gevent.xexpose.window==op->contr->win_look)
	draw_all_look(op);
      else if(op->contr->gevent.xexpose.window==op->contr->win_message)
	draw_all_message(op);
      else if(op->contr->gevent.xexpose.window==op->contr->win_game)
	refresh(op);
      else if(!op->contr->split_window&&
	      op->contr->gevent.xexpose.window==op->contr->win_root) {
	XClearWindow(op->contr->gdisp,op->contr->win_root);
      }
      op->speed_left++;
      break;
    case MappingNotify:
      op->speed_left++;
      XRefreshKeyboardMapping(&op->contr->gevent.xmapping);
      repeat=1;
      break;
    case ClientMessage:
      cmev = (XClientMessageEvent *) & op->contr->gevent;
      if (cmev->message_type == Protocol_atom && cmev->data.l[0] == Kill_atom) {
	LOG(llevDebug,"Got WM_DELETE_WINDOW from %s.\n",op->name);
	if (op->map->in_memory == MAP_IN_MEMORY)
	{
	  if (!QUERY_FLAG(op,FLAG_REMOVED)) {
		op->map->players--;
		remove_ob(op);
	  }
	}
	op->contr->state = 1;
	op->direction = 0;
	op->contr->count_left = 0;
	new_draw_info_format(NDI_UNIQUE|NDI_ALL, 0, NULL,
		"%s lost connection.", op->name);

	strcpy(op->contr->killer, "lost connection");
	check_score(op);
	if (!save_player(op, 0)) {
		object *tmp;

		LOG(llevError,"save failed - trying manual removal of objects\n");
		while ((tmp=op->inv))
			destroy_object(tmp);
		}
	remove_lock(op->contr);

	free_player(op->contr);
	if(first_player==NULL) {
	   if (settings.servermode != SERVER_ENABLED)
	     exit(0);
	   XCloseDisplay(op->contr->gdisp);
	   LOG(llevDebug, "In server mode: continuing action.\n");
	 }
#if 0 /* This should crash if it doesn't return! -Frank */
	if (first_player==NULL)
#endif /* (Also, free_player() must be before the NULL test!) */
	return;
      }
      break;
    case ButtonPress:
	repeat = handle_buttonpress(op);
	break;

    case KeyRelease:
      repeat=1;
      XLookupString(&op->contr->gevent.xkey,text,10,&op->contr->gkey,NULL);
      parse_key_release(op);
      break;
    case KeyPress:
      if(!XLookupString(&op->contr->gevent.xkey,text,10,
	 &op->contr->gkey,NULL)) {
	text[0]='\0';
      }
      repeat = handle_keypress(op,op->contr->gevent.xkey.keycode,
	                     op->contr->gkey,text[0]);
      if (repeat==2)
	return;
      break;

    default:
      repeat=1;
    }
  }
  if (!op->contr->keyboard_flush) return;
/* This should work better at removing keystrokes from the Queue
 * than the old method.  The old method could only remove keystrokes if
 * they where the first events.  But, if the queue was keyboard, 
 * keyboard, window, keyboard, it would stop at the window event,
 * and one keyboard event would still be around.  The following should
 * remove all wanted events, and nothing more.
 */

  while(XCheckMaskEvent(op->contr->gdisp, KeyPressMask | KeyReleaseMask | ButtonPressMask, &op->contr->gevent)) {
    switch(op->contr->gevent.type) {
    case KeyPress:
      XLookupString(&op->contr->gevent.xkey,text,10,&op->contr->gkey,NULL);
    /* Some keypresses we can't just throw away...: */
      if((op->contr->gevent.xkey.keycode==op->contr->firekey[0] &&
	  op->contr->gkey==op->contr->firekeysym[0]) ||
	 (op->contr->gevent.xkey.keycode==op->contr->firekey[1] &&
	  op->contr->gkey==op->contr->firekeysym[1])) {
	op->contr->fire_on=1;
	draw_message_window(op);
      }
      if((op->contr->gevent.xkey.keycode==op->contr->runkey[0] &&
	  op->contr->gkey==op->contr->runkeysym[0]) ||
	 (op->contr->gevent.xkey.keycode==op->contr->runkey[1] &&
	  op->contr->gkey==op->contr->runkeysym[1])) {
	op->contr->run_on=1;
	draw_message_window(op);
      }
/*	LOG(llevDebug,"Through away key %s from input",text);*/
      continue;
    case KeyRelease:
      XLookupString(&op->contr->gevent.xkey,text,10,&op->contr->gkey,NULL);
      parse_key_release(op);
      continue;
    case ButtonPress:
      continue;
    default:
	LOG(llevError,"While flushing keyboard, got unkown X event (%d)\n", op->contr->gevent.type);
	break;
    }
    break;
  }
}


object *esrv_getopfromcid(long client_id)
{
  struct pl *l;
  for(l = first_player;l!= NULL;l=l->next) {
    if (l->eric_server == client_id)
      break;
  }
  if (l==NULL) {
/*    LOG(llevError,"Tried to get CID(%d) which doesn't exist.\n",client_id);*/
    return NULL;
  }
  return l->ob;
}

object *esrv_get_ob_from_count(object *pl, long count)
{
    object *op, *tmp;

    if (pl->count == count)
	return pl;

    for(op = pl->inv; op; op = op->below)
	if (op->count == count)
	    return op;
	else if (op->type == CONTAINER && pl->container == op)
	    for(tmp = op->inv; tmp; tmp = tmp->below)
		if (tmp->count == count)
		    return tmp;

    for(op = get_map_ob (pl->map, pl->x, pl->y); op; op = op->above)
	if (op->count == count)
	    return op;
	else if (op->type == CONTAINER && pl->container == op)
	    for(tmp = op->inv; tmp; tmp = tmp->below)
		if (tmp->count == count)
		    return tmp;
    return NULL;
}

void esrv_examine_object (object *pl, long tag)
{
    object *op = esrv_get_ob_from_count(pl, tag);

    if (!op) {
      LOG(llevDebug, "Player '%s' tried examine the unknown object (%d)\n",
	  pl->name, tag);
      return;
    }
    examine (pl, op);
}

void esrv_apply_object (object *pl, long tag)
{
    object *op = esrv_get_ob_from_count(pl, tag);

    if (!op) {
      LOG(llevDebug, "Player '%s' tried apply the unknown object (%d)\n",
	  pl->name, tag);
      return;
    }
    apply (pl, op);
}

void esrv_move_object (object *pl, long to, long tag, long nrof)
{
    object *op, *env;

    printf ("esrv_move_object:\n");
    printf ("Trying to locate object %ld from player %s.\n", tag, pl->name);
    op = esrv_get_ob_from_count(pl, tag);
    if (!op) {
      LOG(llevDebug, "Player '%s' tried to move the unknown object (%ld)\n",
	  pl->name, tag);
      return;
    }
    printf ("and the object was '%s'.\n", op->name);

    if (!to) {	/* drop it to the ground */
	printf ("Drop it on the ground.\n");
      drop_object (pl, op, nrof);
      return;
    } else if (to == pl->count) {     /* pick it up to the inventory */
      pick_up_object (pl, pl, op, nrof);
      return ;
    }
    /* If not dropped or picked up, we are putting it into a sack */
    env = esrv_get_ob_from_count(pl, to);
    if (!env) {
      LOG(llevDebug, 
	  "Player '%s' tried to move object to the unknown location (%d)\n",
	  pl->name, to);
      return;
    }
    printf ("Sacks name was '%s'.\n", env->name);
    put_object_in_sack (pl, env, op, nrof);
}


void esrv_quit_player(long client_id)
{
  struct pl *l;
  for(l = first_player;l!= NULL;l=l->next) {
    if (l->eric_server == client_id)
      break;
  }
  if (l!=NULL) {
    if(!QUERY_FLAG(l->ob,FLAG_REMOVED)) {
      terminate_all_pets(l->ob);
      remove_ob(l->ob);
    }
    leave(l);
  }
}


int save_life(object *op) {
  object *tmp;
  char buf[MAX_BUF];
  if(!QUERY_FLAG(op,FLAG_LIFESAVE))
    return 0;
  for(tmp=op->inv;tmp!=NULL;tmp=tmp->below)
    if(QUERY_FLAG(tmp, FLAG_APPLIED)&&QUERY_FLAG(tmp,FLAG_LIFESAVE)) {
#ifdef SOUND_EFFECTS
      play_sound_map(op->map, op->x, op->y, SOUND_OB_EVAPORATE);
#endif
      sprintf(buf,"Your %s vibrates violently, then evaporates.",
	      query_name(tmp));
      new_draw_info(NDI_UNIQUE, 0,op,buf);
      remove_ob(tmp);
      free_object(tmp);
      CLEAR_FLAG(op, FLAG_LIFESAVE);
      if(op->stats.hp<0)
	op->stats.hp = op->stats.maxhp;
      if(op->stats.food<0)
	op->stats.food = 999;
      return 1;
    }
  LOG(llevError,"Error: LIFESAVE set without applied object.\n");
  CLEAR_FLAG(op, FLAG_LIFESAVE);
  return 0;
}

/* This goes throws the inventory and removes unpaid objects, and puts them
 * back in the map (location and map determined by values of env).  This
 * function will descend into containers.  op is the object to start the search
 * from.
 */
void remove_unpaid_objects(object *op, object *env)
{
    object *next;

    while (op) {
	next=op->below;	/* Make sure we have a good value, in case 
			 * we remove object 'op'
			 */
	if (QUERY_FLAG(op, FLAG_UNPAID)) {
	    remove_ob(op);
	    op->x = env->x;
	    op->y = env->y;
	    insert_ob_in_map(op, env->map);
	}
	else if (op->inv) remove_unpaid_objects(op->inv, env);
	op=next;
    }
}


void do_some_living(object *op) {
  char buf[MAX_BUF];
  object *tmp;
  int last_food=op->stats.food;
  int gen_hp, gen_sp, gen_grace;
  int x,y,i;  /*  these are for resurrection */
  mapstruct *map;  /*  this is for resurrection */

  if (op->contr->outputs_sync) {
    for (i=0; i<NUM_OUTPUT_BUFS; i++)
      if (op->contr->outputs[i].buf!=NULL &&
	(op->contr->outputs[i].first_update+op->contr->outputs_sync)<pticks)
	    flush_output_element(op, &op->contr->outputs[i]);
  }

  if(op->contr->state==ST_PLAYING) {
    gen_hp=(op->contr->gen_hp+1)*op->stats.maxhp;
    gen_sp=(op->contr->gen_sp+1)*op->stats.maxsp;
	 gen_grace=(op->contr->gen_grace+1)*op->stats.maxgrace;
    if(op->contr->golem==NULL&&--op->last_sp<0) {
		 if(op->stats.sp<op->stats.maxsp)
			 op->stats.sp++,op->stats.food--;
		 op->last_sp=2000/(gen_sp<20 ? 30 : gen_sp+10);
		 for(tmp=op->inv;tmp!=NULL;tmp=tmp->below)
			 if((tmp->type==ARMOUR||tmp->type==HELMET||tmp->type==BOOTS||
				  tmp->type==SHIELD||tmp->type==GLOVES||tmp->type==GIRDLE||
				  tmp->type==AMULET)
				 &&QUERY_FLAG(tmp, FLAG_APPLIED))
				 op->last_sp+=ARMOUR_SPELLS(tmp);
    }

	 /* regenerate grace */
	/* I altered this a little - maximum grace is ony achieved through prayer -b.t.*/
	 if(--op->last_grace<0) {
#ifndef ALLOW_SKILLS /* allow regen 'naturally' to only 1/2 maxgrace w/ skills code */ 
		 if(op->stats.grace<op->stats.maxgrace) 
#else
		 if(op->stats.grace<op->stats.maxgrace/2)
#endif
			 op->stats.grace++;  /* no penalty in food for regaining grace */
		 op->last_grace=2500/(gen_grace<20? 30:gen_grace+10);
		 /* wearing stuff doesn't detract from grace generation. */
	 }

    if(--op->last_heal<0) {
		 if(op->stats.hp<op->stats.maxhp)
			 op->stats.hp++,op->stats.food--;
		 op->last_heal=1200/(gen_hp<20 ? 30 : gen_hp+10);
		 if(op->contr->digestion<0)
			 op->stats.food+=op->contr->digestion;
		 else if(op->contr->digestion>0&&RANDOM()%(1+op->contr->digestion))
			 op->stats.food=last_food;
    }
    if(--op->last_eat<0) {
		 int bonus=op->contr->digestion>0?op->contr->digestion:0,
		 penalty=op->contr->digestion<0?-op->contr->digestion:0;

		 op->last_eat=25*(1+bonus)/(op->contr->gen_hp+penalty+1);
		 op->stats.food--;
    }
 }
  if(op->contr->state==ST_PLAYING&&op->stats.food<0&&op->stats.hp>=0) {
	  object *tmp;
	  for(tmp=op->inv;tmp!=NULL;tmp=tmp->below)
		  if(!QUERY_FLAG(tmp, FLAG_UNPAID)&&
			  (tmp->type==FOOD||tmp->type==DRINK||tmp->type==POISON))
			  {
		new_draw_info(NDI_UNIQUE, 0,op,"You blindly grab for a bite of food.");
	apply(op,tmp);
	if(op->stats.food>=0||op->stats.hp<0)
	  break;
      }
  }
  while(op->stats.food<0&&op->stats.hp>0)
    op->stats.food++,op->stats.hp--;

  if (!op->contr->state&&!QUERY_FLAG(op,FLAG_WIZ)&&(op->stats.hp<0||op->stats.food<0)) {
    if(save_life(op))
      return;
    if(op->stats.food<0) {
#ifdef EXPLORE_MODE
      if (op->contr->explore) {
      new_draw_info(NDI_UNIQUE, 0,op,"You would have starved, but you are");
      new_draw_info(NDI_UNIQUE, 0,op,"in explore mode, so...");
      op->stats.food=999;
      return;
      }
#endif /* EXPLORE_MODE */
      sprintf(buf,"%s starved to death.",op->name);
      strcpy(op->contr->killer,"starvation");
    }
    else
#ifdef EXPLORE_MODE
      if (op->contr->explore) {
      new_draw_info(NDI_UNIQUE, 0,op,"You would have died, but you are");
      new_draw_info(NDI_UNIQUE, 0,op,"in explore mode, so...");
      op->stats.hp=op->stats.maxhp;
      return;
      }
#endif /* EXPLORE_MODE */
      sprintf(buf,"%s died.",op->name);
#ifdef SOUND_EFFECTS
      play_sound_player_only(op->contr, SOUND_PLAYER_DIES);
#endif
    /*  save the map location for corpse, gravestone*/
    x=op->x;y=op->y;map=op->map;


#ifdef NOT_PERMADEATH
/****************************************************************************/
/* Patch: NOT_PERMADEATH                Author: Charles Henrich             */
/* Email: henrich@crh.cl.msu.edu        Date  : April 9, 1993               */
/*                                                                          */
/* Purpose: This patch changes death from being a permanent, very painful   */
/*          thing, to a painful, but survivable thing.  More mudlike in     */
/*          nature.  With this patch defined, when a player dies, they will */
/*          permanently lose one point off of a random stat, as well as     */
/*          losing 20% of their experience points.  Then they are whisked   */
/*          to the start map.  Although this sounds very nice here, it is   */
/*          still REAL painful to die, 20% of a million is alot!            */
/*                                                                          */
/****************************************************************************/

 /**************************************/
 /*                                    */
 /* Pick a stat, and steal on pt from  */
 /* it...                              */
 /*                                    */
 /**************************************/
    i = RANDOM() % 7;
    change_attr_value(&(op->stats), i,-1);
    check_stat_bounds(&(op->stats));
    change_attr_value(&(op->contr->orig_stats), i,-1);
    check_stat_bounds(&(op->contr->orig_stats));
    new_draw_info(NDI_UNIQUE, 0,op, lose_msg[i]);


 /**************************************/
 /*                                    */
 /* Lets make up a gravestone to put   */
 /* here... We put experience lost on  */
 /* it for kicks....                   */
 /*                                    */
 /**************************************/

    tmp=arch_to_object(find_archetype("gravestone"));
    sprintf(buf,"%s's gravestone",op->name);
    tmp->name=add_string(buf);
    sprintf(buf,"RIP\nHere rests the hero %s the %s,\n"
	        "who lost %d experience when killed\n"
	        "by %s.\n",
	        op->name, op->contr->title, (int)(op->stats.exp * 0.20),
	        op->contr->killer);
    tmp->msg = add_string(buf);
    tmp->x=op->x,tmp->y=op->y;
    insert_ob_in_map(tmp,op->map);

 /**************************************/
 /*                                    */
 /* Subtract the experience points,    */
 /* if we died cause of food, give us  */
 /* food, and reset HP's...            */
 /*                                    */
 /**************************************/

    /* remove any poisoning and confusion the character may be suffering. */
    cast_heal(op, 0, SP_CURE_POISON);
    cast_heal(op, 0, SP_CURE_CONFUSION);
	
    add_exp(op, (op->stats.exp * -0.20));
    if(op->stats.food < 0) op->stats.food = 500;
    op->stats.hp = op->stats.maxhp;

 /*
  * Check to see if the player is in a shop.  IF so, then check to see if
  * the player has any unpaid items.  If so, remove them and put them back
  * in the map.
  */

    tmp= get_map_ob(op->map, op->x, op->y);
    if (tmp && tmp->type == SHOP_FLOOR) {
	remove_unpaid_objects(op->inv, op);
    }
 

 /**************************************/
 /*                                    */
 /* Move the player to the beginning   */
 /* map....                            */
 /*                                    */
 /**************************************/

    tmp=get_object();

    EXIT_PATH(tmp) = add_string(first_map_path);
    enter_exit(op,tmp);

/* commenting this out seems to fix core dumps on some systems. */
    free_object(tmp);

 /**************************************/
 /*                                    */
 /* Repaint the characters inv, and    */
 /* stats, and show a nasty message ;) */
 /*                                    */
 /**************************************/

    draw_stats(op);
/*  draw_inventory(op);   inventory hasn't changed */
    new_draw_info(NDI_UNIQUE, 0,op,"YOU HAVE DIED.");
    save_player(op,1);
    return;
#endif

/* If NOT_PERMADETH is set, then the rest of this is not reachable.  This
 * should probably be embedded in an else statement.
 */

#ifdef SIMPLE_PARTY_SYSTEM
    op->contr->party_number=(-1);
#endif /* SIMPLE_PARTY_SYSTEM */
#ifdef SET_TITLE
    op->contr->own_title[0]='\0';
#endif /* SET_TITLE */
    op->contr->count_left=0;
    load_default_keys(op->contr);
    new_draw_info(NDI_UNIQUE|NDI_ALL, 0,NULL, buf);
    check_score(op);
    if(op->contr->golem!=NULL) {
      remove_friendly_object(op->contr->golem);
      remove_ob(op->contr->golem);
      free_object(op->contr->golem);
      op->contr->golem=NULL;
    }
    op->contr->freeze_inv=1;
    op->contr->freeze_look=1;
    loot_object(op); /* Remove some of the items for good */
    op->contr->freeze_inv=0;
    op->contr->freeze_look=0;
    remove_ob(op);
    op->direction=0;
    if(!QUERY_FLAG(op,FLAG_WAS_WIZ)&&op->stats.exp) {
      delete_character(op->name);
#ifndef NOT_PERMADEATH
#ifdef RESURRECTION
	/* save playerfile sans equipment when player dies
	** then save it as player.pl.dead so that future resurrection
	** type spells will work on them nicely
	*/
	op->stats.hp = op->stats.maxhp;
	op->stats.food = 999;

	/*  set the location of where the person will reappear when  */
	/* maybe resurrection code should fix map also */
	strcpy(op->contr->maplevel, EMERGENCY_MAPPATH);
	if(op->map!=NULL)
	    op->map = NULL;
	op->x = EMERGENCY_X;
	op->y = EMERGENCY_Y;
	save_player(op,0);
	op->map = map;
	/* please see resurrection.c: peterm */
	dead_player(op);
#endif
#endif
    }
    play_again(op);
#ifdef NOT_PERMADEATH
    tmp=arch_to_object(find_archetype("gravestone"));
    sprintf(buf,"%s's gravestone",op->name);
    tmp->name=add_string(buf);
    sprintf(buf,"RIP\nHere rests the hero %s the %s,\nwho was killed by %s.\n",
	    op->name, op->contr->title, op->contr->killer);
    tmp->msg = add_string(buf);
    tmp->x=x,tmp->y=y;
    insert_ob_in_map(tmp,map);
#else
    /*  peterm:  added to create a corpse at deathsite.  */
    tmp=arch_to_object(find_archetype("corpse_pl"));
    sprintf(buf,"%s", op->name);
    if (tmp->name)
	free_string (tmp->name);
    tmp->name=add_string(buf);
    tmp->level=op->level;
    tmp->x=x;tmp->y=y;
    if (tmp->msg)
	free_string(tmp->msg);
    tmp->msg = gravestone_text(op);
    SET_FLAG (tmp, FLAG_UNIQUE);
    insert_ob_in_map(tmp,map);
#endif
  }
}

void loot_object(object *op) { /* Grab and destroy some treasure */
  object *tmp,*tmp2,*next;

  if (op->container) { /* close open sack first */
    if (op->contr->eric_server > 0)
      esrv_apply_container (op, op->container);
    else
      apply_container (op, op->container);
  }

  for(tmp=op->inv;tmp!=NULL;tmp=next) {
    next=tmp->below;
    if (tmp->type==EXPERIENCE) continue;
    remove_ob(tmp);
    tmp->x=op->x,tmp->y=op->y;
    if (tmp->type == CONTAINER) { /* empty container to ground */
	loot_object(tmp);
    }
    if(!QUERY_FLAG(tmp, FLAG_UNIQUE) && (QUERY_FLAG(tmp, FLAG_STARTEQUIP) 
       || QUERY_FLAG(tmp,FLAG_NO_DROP) || !(RANDOM()%3))) {
      if(tmp->nrof>1) {
	tmp2=get_split_ob(tmp,1+RANDOM()%(tmp->nrof-1));
	free_object(tmp2);
	insert_ob_in_map(tmp,op->map);
      } else
	free_object(tmp);
    } else
      insert_ob_in_map(tmp,op->map);
  }
}

/*
 * fix_weight(): Check recursively the weight of all players, and fix
 * what needs to be fixed.  Refresh windows and fix speed if anything
 * was changed.
 */

void fix_weight() {
  player *pl;
  for (pl = first_player; pl != NULL; pl = pl->next) {
    int old = pl->ob->carrying, sum = sum_weight(pl->ob);
    if(old == sum)
      continue;
    fix_player(pl->ob);
    draw_inventory(pl->ob);
    LOG(llevDebug,"Fixed inventory in %s (%d -> %d)\n",
	pl->ob->name, old, sum);
  }
}

void fix_luck() {
  player *pl;
  for (pl = first_player; pl != NULL; pl = pl->next)
    if (!pl->ob->contr->state)
      change_luck(pl->ob, 0);
}


void client_speed(long cid)
{
  player *pl;
  for(pl=first_player;pl != NULL; pl = pl->next)
    if (pl->eric_server == cid)
      printf("speed left = %.2f\n",pl->ob->speed_left);
}

/* cast_dust() - handles op throwing objects of type 'DUST' */
 
void cast_dust (object *op, object *throw_ob, int dir) {
  archetype *arch = find_archetype(spells[throw_ob->stats.sp].archname);
 
  /* casting POTION 'dusts' is really a use_magic_item skill */
  if(op->type==PLAYER&&throw_ob->type==POTION
	&&!change_skill(op,SK_USE_MAGIC_ITEM)) {
      LOG(llevError,"Player %s lacks critical skill use_magic_item!\n",
                op->name);
      return;
  }
 
  if(throw_ob->type==POTION&&arch!= NULL)
    cast_cone(op,throw_ob,dir,10,throw_ob->stats.sp,arch,1);
  else if((arch=find_archetype("dust_effect"))!=NULL) { /* dust_effect */
    cast_cone(op,throw_ob,dir,1,0,arch,0);
  } else /* problem occured! */ 
    LOG(llevError,"cast_dust() can't find an archetype to use!\n");
 
  if (op->type==PLAYER&&arch)
    new_draw_info_format(NDI_UNIQUE, 0,op,"You cast %s.",query_name(throw_ob));
  if(!QUERY_FLAG(throw_ob,FLAG_REMOVED)) remove_ob(throw_ob);
  free_object(throw_ob);
}

void make_visible (object *op) {
    op->hide = 0;
    op->invisible = 0;
    if(op->type==PLAYER) 
      op->contr->tmp_invis = 0;
    if(QUERY_FLAG(op, FLAG_UNDEAD)&&!is_true_undead(op)) 
      CLEAR_FLAG(op, FLAG_UNDEAD);
    update_object(op);
}

int is_true_undead(object *op) {
  object *tmp=NULL;
  
  if(QUERY_FLAG(&op->arch->clone,FLAG_UNDEAD)) return 1;

  if(op->type==PLAYER)
    for(tmp=op->inv;tmp;tmp=tmp->below)
       if(tmp->type==EXPERIENCE && tmp->stats.Wis)
	  if(QUERY_FLAG(tmp,FLAG_UNDEAD)) return 1;
  return 0;
}

/* look at the surrounding terrain to determine
 * the hideability of this object. Positive levels
 * indicate greater hideability.
 */

int hideability(object *ob) {
  int i,x,y,level=0;

  if(!ob||!ob->map) return 0;

  /* so, on normal lighted maps, its hard to hide */
  level=ob->map->darkness - 2;

  /* this also picks up whether the object is glowing.
   * If you carry a light on a non-dark map, its not
   * as bad as carrying a light on a pitch dark map */
  if(has_carried_lights(ob)) level =- (10 + (2*ob->map->darkness));

  /* scan through all nearby squares for terrain to hide in */
  for(i=0,x=ob->x,y=ob->y;i<9;i++,x=ob->x+freearr_x[i],y=ob->y+freearr_y[i]) { 
    if(out_of_map(ob->map,x,y)) { continue; }
    if(blocks_view(ob->map,x,y)) /* something to hide near! */
      level += 2;
    else /* open terrain! */
      level -= 1;
  }
  
#if 0
  LOG(llevDebug,"hideability of %s is %d\n",ob->name,level);
#endif
  return level;
}

/* For Hidden creatures - a chance of becoming 'unhidden'
 * every time they move - as we subtract off 'invisibility'
 * AND, for players, if they move into a ridiculously unhideable
 * spot (surrounded by clear terrain in broad daylight). -b.t.
 */

void do_hidden_move (object *op) {
    int hide=0, num=RANDOM()%20;
    
    if(!op || !op->map) return;

    /* its *extremely* hard to run and sneak/hide at the same time! */
    if(op->type==PLAYER && op->contr->run_on)
      if(num >= SK_level(op)) {
        new_draw_info(NDI_UNIQUE,0,op,"You ran too much! You are no longer hidden!");
        make_visible(op);
        return;
      } else num += 20;
    num += op->map->difficulty;
    hide=hideability(op); /* modify by terrain hidden level */
    num -= hide;
    if((op->type==PLAYER&&hide<-10) || ((op->invisible-=num)<=0)) {
      make_visible(op);
      if(op->type==PLAYER) new_draw_info(NDI_UNIQUE, 0,op,
          "You moved out of hiding! You are visible!");
    }
}

/* determine if who is standing near a hostile creature. */

int stand_near_hostile( object *who ) {
  object *tmp=NULL;
  int i,friendly=0,player=0;

  if(!who) return 0;

  if(who->type==PLAYER) player=1; 
  else friendly = QUERY_FLAG(who,FLAG_FRIENDLY);

  /* search adjacent squares */
  for(i=1;i<9;i++)
    for(tmp=get_map_ob(who->map,who->x+freearr_x[i],who->y+freearr_y[i]);tmp;tmp=tmp->above) 
      if((player||friendly)
          &&QUERY_FLAG(tmp,FLAG_MONSTER)&&!QUERY_FLAG(tmp,FLAG_UNAGGRESSIVE)) 
        return 1;
      else if(tmp->type==PLAYER) return 1;

  return 0;
}

/* check the player los field for viewability of the 
 * object op. This function works fine for monsters,
 * but we dont worry if the object isnt the top one in 
 * a pile (say a coin under a table would return "viewable"
 * by this routine). Another question, should we be
 * concerned with the direction the player is looking 
 * in? Realistically, most of use cant see stuff behind
 * our backs...on the other hand, does the "facing" direction
 * imply the way your head, or body is facing? Its possible
 * for them to differ. Sigh, this fctn could get a bit more complex.
 * -b.t. */

int player_can_view (object *pl,object *op) {

  if(pl->type!=PLAYER) {
    LOG(llevError,"player_can_view() called for non-player object\n");
    return -1;
  }

  if(pl&&op&&pl->map==op->map) { 
    /* starting with the 'head' part, lets loop
     * through the object and find if it has any
     * part that is in the los array but isnt on 
     * a blocked los square. */
    if(op->head) { op = op->head; }
    while(op) {
      if(pl->y + WINUPPER <= op->y && pl->y + WINLOWER >= op->y 
          && pl->x + WINLEFT <= op->x && pl->x + WINRIGHT >= op->x
          && !pl->contr->blocked_los[op->x-pl->x+5][op->y-pl->y+5] ) 
        return 1;
      op = op->more;
    }
  }
  return 0;
}

/* routine for both players and monsters. We call this when
 * there is a possibility for our action distrubing our hiding
 * place or invisiblity spell. Artefact invisiblity is not
 * effected by this. If we arent invisible to begin with, we 
 * return 0. 
 */
int action_makes_visible (object *op) {

  if(op->invisible && QUERY_FLAG(op,FLAG_ALIVE)) 
    if(!QUERY_FLAG(op,FLAG_MAKE_INVIS)) 
      return 0; 
    else if(op->hide || (op->contr&&op->contr->tmp_invis)
    ) { 
      new_draw_info_format(NDI_UNIQUE, 0,op,"You become %!",op->hide?"unhidden":"visible");
      return 1; 
    } else if(op->contr)  
        /* improved invis is lost EXCEPT for case of casting of magic */
        if(!op->contr->shoottype==range_magic) { 
          new_draw_info(NDI_UNIQUE, 0,op,"Your invisibility spell is broken!");
          return 1;
        }

  return 0;
}

