/*  object.c-- functions to manipulate rooms, nouns, and creatures  */
/* Copyright (C) 1996-1999,2001  Robert Masenten          */
/* This program may be redistributed under the terms of the
   GNU General Public License, version 2; see agility.h for details. */
/*                                                        */
/* This is part of the source for AGiliTy                           */
/*                                                                  */
/* This module contains most of the routines that implement the     */
/*   "physics" of the AGT world, including the scope routines.      */

#include <stddef.h>
#include <assert.h>
#include <ctype.h>
#include "agility.h"
#include "interp.h"
#include "exec.h"


/* ------------------------------------------------------------------- */
/*  Functions for manipulating parse_recs */
/* ------------------------------------------------------------------- */

/* Make artificial parse record for an object */
parse_rec *make_parserec(int obj,parse_rec *rec)
{
  if (rec==NULL) rec=rmalloc(sizeof(parse_rec));
  rec->obj=obj;
  rec->info=D_NOUN; 
  rec->noun=it_name(obj);
  rec->adj=it_adj(obj);
  rec->num=0;
  return rec;
}

/* This is used by the parser to initialize a blank parse_rec */
void tmpobj(parse_rec *objrec)
{
  objrec->info=D_NOUN;
  objrec->num=0;
  objrec->noun=objrec->adj=0;
  objrec->obj=0;
}

parse_rec *copy_parserec(parse_rec *rec)
{
  parse_rec *newrec;
  if (rec==NULL) return NULL;
  newrec=rmalloc(sizeof(parse_rec));
  memcpy(newrec,rec,sizeof(parse_rec));
  return newrec;
}

void free_all_parserec(void)
{
  rfree(actor_rec);rfree(dobj_rec);rfree(iobj_rec);
}


/* ------------------------------------------------------------------- */
/* Functions for doing basic manipulation of items and extracting      */
/* nformation about these items                                         */
/* (often used to blackbox the difference between nouns and creatures) */



rbool matchclass(int obj, int oclass)
{
  int i;
  if (oclass==0) return 0;
  for(i=obj;i!=oclass && i!=0;i=it_class(i));
  return i==oclass;
}


/* Functions for getting bascic information about items */

static const char *it_sdesc(int item)
{
   if (tnoun(item)) return noun[item-first_noun].shortdesc;
   if (tcreat(item)) return creature[item-first_creat].shortdesc;
   if (item<0) return dict[-item];
   return NULL;
}

rbool it_possess(int item)  
{
  int l;

  l=it_loc(item);
  return (l==1 || l==1000);
}

rbool it_proper(int item) 
{
  if (tcreat(item))
    return (!PURE_PROPER) || creature[item-first_creat].proper;
  if (tnoun(item))
    return noun[item-first_noun].proper;
  return 0;
}


static rbool invischeck(const char *s) 
{
  while(rspace(*s)) s++;
  return strncasecmp(s,"INVISIBLE",9)==0; 
}


static rbool it_invisible(int item, rbool sdesc_flag)
{
  if (sdesc_flag) 
    return invischeck(it_sdesc(item));   
  else {
    char *s;
    rbool tmp;

    if (it_name(item)==0 && it_adj(item)==0) return 1;
    s=objname(item); /* Must remember to rfree s before exiting */
    tmp=invischeck(s);
    rfree(s);
    return tmp;
  }
}


static rbool it_appears_empty(int item)
{
  int i;
  int sdesc_flag;

  if (item<0) return 1;
  sdesc_flag=!player_has(item);

  contloop(i,item)
    if (!it_invisible(i,sdesc_flag)) return 0;
  return 1;
}

/* We classify something as a weapon if it kills something. */
rbool it_isweapon(int objnum)
{
  int i;

  creatloop(i)
    if (matchclass(objnum,creature[i].weapon)) return 1;
  return 0;
}

/* This used to be a macro like troom, tnoun, and tcreat, but it
   got too complicated and isn't used in time-critical areas,
   anyhow */

rbool it_door(int obj,word nword)
{
  if (aver>=AGX00) return 0; /* No doors under Magx */
  if (tdoor(obj)) return 1; /* The basic door */
  if (it_loc(obj)==loc+first_room) return 0;
  return (nword==ext_code[wdoor]);
}


/* ------------------------------------------------------------------- */
/* Routines that manipulate the linked lists representing containment  */
/*  information   */

static void set_contents(int p, int newval)
{
  if (troom(p)) room[p-first_room].contents=newval;
  else if (p==1) player_contents=newval;
  else if (p==1000) player_worn=newval;
  else if (tnoun(p)) noun[p-first_noun].contents=newval;
  else if (tcreat(p)) creature[p-first_creat].contents=newval;
  else {writeln("INT ERR: Invalid object heading chain.");return;}
}

static void set_next(int p, int newval)
{
  if (tnoun(p)) noun[p-first_noun].next=newval;
  else if (tcreat(p)) creature[p-first_creat].next=newval;
  else {writeln("INT ERR: Invalid object in chain.");return;}
}

void add_object(int loc,int item)
{
  int p, q;

  set_next(item,0);

  if (loc==0) return;
  p=it_contents(loc);
 
  if (p==0 || p>item) {   
    set_contents(loc,item);
    set_next(item,p);
  } else {  /* Figure out where to put the item */
    do {
      q=p;
      p=it_next(p);
    } while (p!=0 && p<item);
   
    set_next(q,item);  
    set_next(item,p);
  }
}


static void set_location(int item, int newloc)
/* This routine assumes item is either a noun or a creature */
{
  int p,q;

  p=it_loc(item);
  if (p!=0) {    /* Fix .next values */
    q=it_contents(p);
    if (q==item) set_contents(p,it_next(item));
    else {
      while(q!=item && q!=0) {p=q;q=it_next(p);}
      assert(q!=0); /* This would mean the list structure was corrupted */     
      set_next(p,it_next(item));
    }
  }
  /* We've unlinked it from the list at this point. */
  
  if (tnoun(item))  
    noun[item-first_noun].location=newloc;
  else if (tcreat(item)) 
    creature[item-first_creat].location=newloc;

  add_object(newloc,item);
}


void it_reposition(int item,int newloc,rbool save_pos)
{
  integer i;

  if (tnoun(item)) {
    if (player_has(item)) totwt-=noun[item-first_noun].weight;
    if (it_loc(item)==1) totsize-=noun[item-first_noun].size;
    
    /* Set position to NULL */
    if (!save_pos) {
      noun[item-first_noun].pos_prep=0;
      noun[item-first_noun].pos_name=0;
      noun[item-first_noun].nearby_noun=0;
      noun[item-first_noun].position=NULL;
#if 0  /* I think this was wrong, so I'm commenting it out. */
      noun[item-first_noun].initdesc=0;
#endif
    }
    
    set_location(item,newloc);

    if (player_has(item))
      {
	totwt+=noun[item-first_noun].weight;
	if (noun[item-first_noun].win)
	  winflag=1;
      }
  if (it_loc(item)==1) /* only things you are carrying directly count vs.
			  size limit. */
    totsize+=noun[item-first_noun].size;
  }
  else if (tcreat(item)) {
    if (newloc==0) {
      creature[item-first_creat].timecounter=0; /* Reset attack counter */
      creature[item-first_creat].counter=0;
    }
    set_location(item,newloc);
  }

  nounloop(i) 
    if (noun[i].nearby_noun==item) {
      noun[i].nearby_noun=0;
      noun[i].pos_prep=0;
      noun[i].pos_name=0;
      noun[i].position=NULL;
    }
}



/* ------------------------------------------------------------------- */
/*  Routines to deal with size and weight      */

static long contsize(integer obj)
{
  int i;
  long net;

  net=0;
  contloop(i,obj) {
    if (tnoun(i)) 
      net+=noun[i-first_noun].size;
    if (aver<AGX00)  /* Under Magx, size isn't recursive */
      net+=contsize(i);
  }
  return net;
}

static long contweight(integer obj)
{
  int i;
  long net;

  net=0;
  contloop(i,obj) {
    if (tnoun(i)) 
      net+=noun[i-first_noun].weight;
    net+=contweight(i);
  }
  return net;
}

rbool is_within(integer obj1, integer obj2, rbool stop_if_closed)
     /* True if obj1 is contained in obj2 */     
{
  int i;
  long cnt;

  for(i=obj1, cnt=0;
      i!=obj2 && i>=maxroom && i!=1000 && cnt<40000L;
      cnt++) {
    i=it_loc(i);
    if (stop_if_closed && !it_open(i)) break;
  }
  if (cnt>=40000L) {
    /* writeln("GAME ERROR: Loop in object tree.");*/
    return 0;
  }
  if (i==obj2) return 1;
  return 0;
}



int check_fit(int obj1, int obj2)
     /* Does obj1 fit inside obj2?  Return one of the FIT_... values 
	defined in exec.h */
{
  int size, weight;
  long net;

  assert(tnoun(obj1)); /* This should have been checked earlier */
  if (obj2==1000) obj2=1; 

  if (obj2==1) size=weight=100;
  else {
    assert(tnoun(obj2)); /* check_fit shouldn't be called otherwise */
    size=noun[obj2-first_noun].size;
    weight=noun[obj2-first_noun].weight;
  }

  /* Weight */
  if (obj2==1 || (aver>AGTME15 && aver<AGX00) ) {
    /* Pre-1.56 interpreters forgot to check this;
       Magx deliberatly *doesn't* check this */

    net=noun[obj1-first_noun].weight;
    if (aver>=AGX00) net+=contweight(obj1);
    if (net>weight) return FIT_WEIGHT;

    if (obj2==1){
      if (is_within(obj1,1,0) || is_within(obj1,1000,0)) net=0;
      net+=contweight(1);
      if (aver>=AGX00) 
	net+=contweight(1000); 
      if (!PURE_SIZE) net=0;
    } else {
      if (is_within(obj1,obj2,0)) net=0; /* Avoid double-counting */
      net+=contweight(obj2); /* Net size of contents of obj2 */
    }
    if (net>weight) return FIT_NETWEIGHT;
  }

  net=noun[obj1-first_noun].size;
  if (net>size) return FIT_SIZE;

  if (obj2==1 && !PURE_SIZE) return FIT_OK;

  if (obj2==1 || aver>AGTME15) {
    /* Pre-ME/1.56 interpreters didn't check this except for the player's
       inventory */
    if (it_loc(obj1)==obj2 
	|| (aver<AGX00 && is_within(obj1,obj2,0)) )
      net=0; /* Avoid double-counting */
    net+=contsize(obj2); /* Net size of contents of obj2 */
    if (net>size) return FIT_NETSIZE;
  }
  
  return FIT_OK;
}



/* ------------------------------------------------------------------- */
/*  Scope and visibility routines                                      */

integer it_room(int item)
{
  int tmploc;
  long cnt;

  cnt=0;
  while(!troom(item)) {
    tmploc=item;
    if (item==0) return 0;    
    if (item==1 || item==1000) item=loc;
    else item=it_loc(item);
    if (item==tmploc || ++cnt>=40000L) {
      /* writeln("GAME ERROR: Loop in object tree."); */
      return 0;
    }
  }
  return item;
}

rbool player_has(int item)
{
  return is_within(item,1,0) || is_within(item,1000,0);
}


rbool in_scope(int item)
/* strictly speaking, visible actually checks scope; this routine
   determines if an object would be in scope if there were no light
   problems. */
{
  int curloc;
  int tmp;
  long cnt;

 if (it_isglobal(item)) return 1;  /* Global objects always in scope. */

 /* Flag objects in scope if their associated flag is set. */
 tmp=it_flagnum(item);
 if (tmp && 
     (room[loc].flag_noun_bits & (1L<<(tmp-1)))) return 1;

 curloc=it_loc(item); /* Should work for nouns or creatures */
 cnt=0;
 while (curloc>maxroom && curloc!=1000 && it_open(curloc)) {
   int tmploc;
   tmploc=it_loc(curloc);
   if (tmploc==curloc || ++cnt>=40000L) {
     /* writeln("GAME ERROR: Loop in the object tree."); */
     return 0;
   }
   else curloc=tmploc;
 }
 if (curloc==1 || curloc==1000 || curloc==loc+first_room) return 1;
 else return 0;
}


static int good_light(int obj,int roomlight,rbool active)
     /* obj is a noun number */
     /* If active is false, we don't care if the light is actually turned
	on is the valid light */
{
  if (roomlight==1 && !noun[obj].light)
    return 0; /* obj is not a light source */
  if (roomlight>1) {
    if (!matchclass(first_noun+obj,roomlight))
      return 0; /* Not the correct light */    
    else return 1; /* The correct light _always_ illuminates the room */
  }
  if (!active) return 1;
 /* Now need to determine if obj is actually providing light */
  if (!noun[obj].on)
    return 0;  /* Light source is off or extinguished */
  return 1;
}

int lightcheck(int parent,int roomlight,rbool active)
     /* This checks to see if anything contained in parent is a valid 
	room light */
     /* active=1 means that we only want active lights; 
	active=0 indicates that extinguished light sources are okay. */
{
  int i;

  contloop(i,parent) {
    if (tnoun(i) && good_light(i-first_noun,roomlight,active)) return 1;
    if (it_open(i) && lightcheck(i,roomlight,active)) 
      return 1; /* Check children */
  }
  return 0;
/*
    nounloop(i)
      if (good_light(i,room[loc].light) && in_scope(i+first_noun))
	return 1;
  return 0;*/
}


rbool islit(void)
{
  if (room[loc].light==0) return 1;
  if (lightcheck(loc+first_room,room[loc].light,1)) return 1;
  if (lightcheck(1,room[loc].light,1)) return 1; /* Player carried light */
  if (lightcheck(1000,room[loc].light,1)) return 1; /* Worn light */
  return 0;
}

/* Is item visible to player? */
/* visible only works for "normal" items; if the object could 
   be a virtual object (i.e. with negative item number), then use
   gen_visible() below */
rbool visible(int item)
{
  assert(item>=0);
  if (islit())
    return in_scope(item);
  else 
    return player_has(item);
}

rbool genvisible(parse_rec *dobj)
{
  int i;

  if (dobj->obj>0) return visible(dobj->obj);
  
  if (dobj->info==D_INTERN) {
    if (dobj->obj!=-ext_code[wdoor]) return 1;
    return islit(); /* If item is a is a door */
  }
  if (dobj->info==D_GLOBAL || dobj->info==D_NUM) return 1;
  if (dobj->info==D_FLAG) {
    for(i=0;i<MAX_FLAG_NOUN;i++)  /* Flag nouns */
      if (flag_noun[i]!=0 && dobj->obj==-flag_noun[i]
	  && (room[loc].flag_noun_bits & (1L<<i))!=0)
	 return 1;
    return 0;
  }
  if (dobj->info==D_PIX) {
    for(i=0;i<MAX_PIX;i++)  /* PIX names */
      if (pix_name[i]!=0 && dobj->obj==-pix_name[i] && 
	  (room[loc].PIX_bits & (1L<<i))!=0)
	return 1;
    return 0;
  }
  fatal("INTERNAL ERROR: Invalid gen_visible type.");
  return 0;
}




/* Need to find a noun related to w */
/* If there is an object with name w in scope, it returns 0
   (since the object will have already been added to the menu).
   if there are none, it returns the first object with name w. */
static integer find_related(word w)
{
  int i;
  int item;

  if (w==0) return 0;
  item=0;
  nounloop(i)
    if (noun[i].name==w) {
      if (visible(i+first_noun)) return i+first_noun;
      else if (item==0) item=i+first_noun;
    }
  creatloop(i)
    if (creature[i].name==w) {
      if (visible(i+first_creat)) return i+first_creat;
      else if (item==0) item=i+first_creat;
    }
  return item;
}


static void add_to_scope(integer item)
{
  integer i;

  if (tnoun(item)) {
    noun[item-first_noun].scope=1;
    i=find_related(noun[item-first_noun].related_name);
    if (i!=0) {
      if (tnoun(i)) noun[i-first_noun].scope=1;
      else if (tcreat(i)) creature[i-first_creat].scope=1;
    }
  }
  else if (tcreat(item)) creature[item-first_creat].scope=1;
  if (item==1 || item==1000 || troom(item) || it_open(item))
    contloop(i,item) 
      add_to_scope(i);
}


void compute_scope(void)
{
  int i;
  uint32 rflag;

  nounloop(i) noun[i].scope=0;
  creatloop(i) creature[i].scope=0;
  add_to_scope(1);
  add_to_scope(1000);
  add_to_scope(loc+first_room);
  rflag=room[loc].flag_noun_bits;
  nounloop(i)
    if (noun[i].isglobal ||
	(noun[i].flagnum && (rflag & (1L<<(noun[i].flagnum-1)))))      
      add_to_scope(i+first_noun);
  creatloop(i)
    if (creature[i].isglobal ||
	(creature[i].flagnum && (rflag & (1L<<(creature[i].flagnum-1)))))
      add_to_scope(i+first_creat);
}

void compute_seen(void)
{
  int i;

  compute_scope();
  nounloop(i)
    noun[i].seen=noun[i].seen || noun[i].scope;
  creatloop(i)
    creature[i].seen=creature[i].seen || creature[i].scope;
}


/*---------------------------------------------------------------------*/
/*  Routines to compute the score */

void recompute_score(void)
{
  int obj;

  tscore-=objscore;
  objscore=0;
  nounloop(obj)
    if (noun[obj].points && !noun[obj].unused &&
	( visible(obj+first_noun) 
	  || is_within(obj+first_noun,treas_room,0) )   )
      objscore+=noun[obj].points;
  creatloop(obj)
    if (!creature[obj].unused && creature[obj].points 
	&& visible(obj+first_creat))
      objscore+=creature[obj].points;
  tscore+=objscore;
}



/*---------------------------------------------------------------------*/
/*  Menu Noun section: routines to get a list of 'relevant' nouns for  */
/*    the menuing system. They're here because they belong next to the */
/*    scope routines, above   */ 

static int *nlist, nleng; /* These are really local variables */

static void add_mnoun(int n) 
{
  nlist=rrealloc(nlist,(nleng+2)*sizeof(int));
  nlist[nleng]=n;
  nlist[++nleng]=0;
}


/* This adds mitem and everything it contains */
static void add_mitem(int item)
{
  integer i;

  if (tnoun(item) || tcreat(item)) add_mnoun(item);
  if (item==1 || item==1000 || troom(item) || it_open(item))
    contloop(i,item) 
      add_mitem(i);
  /* Need to check related nouns */
  if (tnoun(item)) {
    i=find_related(noun[item-first_noun].related_name);
    if (i!=0) add_mnoun(i);
  }
}


static word getword(int item,int n)
/* Gets nth word associated with item */
{
  if (n==1) {
    if (item<0) return -item;
    else if (tnoun(item)) return noun[item-first_noun].adj;
    else if (tcreat(item)) return creature[item-first_creat].adj;
  }
  if (n==2)
    if (tnoun(item) || tcreat(item)) return it_name(item);
  return 0;
}

static int cmp_nouns(const void *a,const void *b)
/*   *a, *b are object numbers; need alphabetic sort.*/
{
  word wa,wb;
  int cmp;

  wa=getword(*((const int*)a),1);
  wb=getword(*((const int*)b),1);
  cmp=strcmp(dict[wa],dict[wb]);
  if (cmp!=0) return cmp;
  wa=getword(*(const int*)a,2);
  wb=getword(*(const int*)b,2);
  return strcmp(dict[wa],dict[wb]);
}

int *get_nouns(void)
/* This returns the list of all objects that should show up on the menu */
/* The list should be 0 terminated and needs to be sorted */
{
  int i;
  uint32 rflag;

  nlist=rmalloc(sizeof(int));
  nlist[0]=0;
  nleng=0;
  
  for(i=0;i<numglobal;i++) 
    add_mnoun(-globalnoun[i]);
  for(i=0;i<MAX_FLAG_NOUN;i++)
    if (room[loc].flag_noun_bits & (1L<<i)) 
      add_mnoun(-flag_noun[i]);
  add_mitem(1);
  add_mitem(1000);
  add_mitem(loc+first_room);
  rflag=room[loc].flag_noun_bits;
  nounloop(i)
    if (noun[i].isglobal ||
	(noun[i].flagnum && (rflag & (1L<<(noun[i].flagnum-1)))))      
      add_mitem(i+first_noun);
  creatloop(i)
    if (creature[i].isglobal ||
	(creature[i].flagnum && (rflag & (1L<<(creature[i].flagnum-1)))))
      add_mitem(i+first_creat);
  qsort(nlist,nleng,sizeof(int),cmp_nouns);
  return nlist;
}



/*---------------------------------------------------------------------*/
/* goto_room, the basic primitive used to move the player around       */

void goto_room(int newroom)
{
  int i, j;
  
/* Move group members in old room to new room */
  safecontloop(i,j,loc+first_room)
    if (it_group(i)) 
      it_move(i,newroom+first_room);

#if 0  /* -- this has been moved to v_go*/
  if (loc!=newroom)
    oldloc=loc;  /* Save old location for NO_BLOCK_HOSTILE purposes */
#endif
  loc=newroom;  
  if (loc!=newroom) oldloc=loc; /* No backtracking unless v_go allows it */
  if (!room[loc].seen) {
    room[loc].seen=1;
    tscore+=room[loc].points;
    first_visit_flag=1;
    room_firstdesc=1;
    v_look();
  } else {
    first_visit_flag=0;
    if (verboseflag)
      v_look(); /* But see v_go() for a special case involving SPECIAL */
    room_firstdesc=0;
  }
  if (room[loc].end) endflag=1;
  if (room[loc].win) winflag=1;
  if (room[loc].killplayer) deadflag=1;
  do_autoverb=1;  
  set_statline();
}


static void rundesc(int i, descr_ptr dp[],const char *shortdesc, int msgid)
{
  if (dp[i].size>0) 
    print_descr(dp[i],1);
  else if (!invischeck(shortdesc)) 
    raw_lineout(shortdesc,1,MSG_DESC,NULL);
  else sysmsg(msgid,"$You$ see nothing unexpected.");
} 

void it_describe(int dobj)
{
  if (troom(dobj)) 
    print_descr(room_ptr[dobj-first_room],1);
  else if (tnoun(dobj))
    rundesc(dobj-first_noun,noun_ptr,noun[dobj-first_noun].shortdesc,194);
  else if (tcreat(dobj))
    rundesc(dobj-first_creat,creat_ptr,
	   creature[dobj-first_creat].shortdesc,195);
  else if (dobj==-ext_code[wdoor])  /* i.e. DOOR */
    {
      if (room[loc].locked_door) 
	sysmsg(21,"$You$ see a locked door.");
      else sysmsg(22,"$You$ see a perfectly normal doorway.");
    }
  else sysmsg(194,"$You$ see nothing unexpected.");
  if (tnoun(dobj) && 
      (noun[dobj-first_noun].open || !noun[dobj-first_noun].closable) && 
      !it_appears_empty(dobj)) {
    sysmsg(228,"Which contains:");
    print_contents(dobj,1);
  }
}



static char *build_position(word prep,word name)
/* Return the malloc'd string '$prep$ the $name$' */
{
  int leng;
  char *s;

  leng=strlen(dict[prep])+strlen(dict[name])+6; /* includes final '\0' */
  s=rmalloc(leng*sizeof(char));
  
  strcpy(s,dict[prep]);
  strcat(s," the ");
  strcat(s,dict[name]);
  assert(strlen(s)+1==leng);
  return s;
}






static int print_obj(int obj,int ind_lev)
/* Prints out s on a line of its own if obj isn't INVISIBLE */
/* parent_descr is true if the parent has been described, false
   otherwise (say if the parent is invisible). */
/* ind_lev=indentation level */
/* Return 1 if we actually printed something, 0 if obj is invisible */
{
  int sdesc_flag; /* True if should print out as sdesc rather than
		as adjective-noun */
  int i, retval, parent;
  const char *s;
  char *t, *s0,  *posstr;

  if (tcreat(obj) && creature[obj-first_creat].initdesc!=0)
    return 0; /* Don't print normal description if printing initdesc */

  s0=NULL;
  sdesc_flag=!player_has(obj); /* This should be tested. */
  sdesc_flag=sdesc_flag||(ind_lev>1);  /* It seems that AGT uses the
					    sdesc for describing items
					    contained in other items */
  /* Some code below relies on this, as well */

  if (sdesc_flag) 
    s=it_sdesc(obj);
  else if (it_name(obj)==0 && it_adj(obj)==0)  /* Invisible */
    return 0;
  else {
    s0=objname(obj); /* Must remember to rfree s before exiting */
    if (aver>=AGTME10) {
      for(t=s0;isspace(*t);t++); /* Skip over initial whitespace... */
      *t=toupper(*t);  /* ...and upcase the first non-space character */
    }
    s=s0;
  }

  retval=0;
  if (sdesc_flag && tnoun(obj) && noun[obj-first_noun].initdesc!=0) {
    retval=1;
    msgout(noun[obj-first_noun].initdesc,1);
    noun[obj-first_noun].initdesc=0; /* Only show it once */
  }
  else if (!invischeck(s)) {
    retval=1; /* We're actually going to print something */
    for(i=0;i<ind_lev;i++) writestr("   ");
    raw_lineout(s,sdesc_flag,MSG_DESC,NULL);
        /* Do $word$ formatting if sdesc */
    /* Need to output container */
    parent=it_loc(obj);
    if (tnoun(obj) && noun[obj-first_noun].pos_prep!=0)
      {
	writestr(" (");
	if (noun[obj-first_noun].pos_prep==-1)
	  writestr(noun[obj-first_noun].position);
	else  {
	  posstr=build_position(noun[obj-first_noun].pos_prep,
				noun[obj-first_noun].pos_name);
	  writestr(posstr);
	  rfree(posstr);
	}
	writestr(")");
      }
    else if (parent>=first_noun && it_invisible(parent,sdesc_flag) 
	     && (it_name(parent)!=0 || it_adj(parent)!=0) ) {
      /* If the parent object *isn't* invisible, we will already have
	 printed it out */
      /* This also relies on sdesc_flag being the same for parent 
	 and child objects */
      
      if (parent>=first_creat && parent<=maxcreat)
	sysmsg(221,"(Carried by");
      else 
	sysmsg(222," (Inside");      
      t=objname(parent);
      writestr(t);rfree(t);
      sysmsg(223,")");
    }
    if (tnoun(obj) && noun[obj-first_noun].light && noun[obj-first_noun].on
	&& PURE_OBJ_DESC)
      sysmsg(220," (Providing light)");
    writeln("");
  }
  if (!sdesc_flag)
    rfree(s0);
  return retval;
}


int print_contents(int obj,int ind_lev)
/* obj=object to list contents of; ind_lev=indentation level */
/* Returns number of objects contained in obj that were listed */
{
  int i, cnt;

  cnt=0;

  contloop(i,obj) {
      if (print_obj(i,ind_lev)) cnt++;
      if (it_open(i)) print_contents(i,ind_lev+1);
    }
  return cnt;
}


/* ------------------------------------------------------------------- */
/*  Routines for directly getting and setting object properties and   */
/*   attributes. */


#define NUM_WPROP 6
#define NUM_WATTR 6


static void *compute_addr(int obj, int prop, const prop_struct *ptable)
{
  int ofs;
  void *base;

  if (DIAG)
    rprintf("(Accessing %s->%s)\n",dict[it_name(obj)],ptable[prop].name);
  if (troom(obj)) {
    base=(void*)(&room[obj-first_room]);
    ofs=ptable[prop].room;
  }
  else if (tnoun(obj)) {
    base=(void*)(&noun[obj-first_noun]);
    ofs=ptable[prop].noun;
  }
  else if (tcreat(obj)) {
    base=(void*)(&creature[obj-first_creat]);
    ofs=ptable[prop].creature;
  }
  else return NULL;
  
  if (ofs==-1) /* Field doesn't exist in this type of object */
    return NULL;

  return (void*) ( ((char*)base) + ofs );
}


long getprop(int obj, int prop) 
{
  integer *paddr;

  if (prop>=NUM_PROP) return 0;
  paddr=(integer*)compute_addr(obj,prop,proplist);
  if (paddr==NULL) return 0;
  return *paddr;
}

void setprop(int obj, int prop, long val)
{
  integer *paddr;

  if (prop>=NUM_WPROP) {
    writeln("GAME ERROR: Read-only or non-existant property.");
    return;
  }

  paddr=(integer*)compute_addr(obj,prop,proplist);
  if (paddr==NULL) {
    writeln("GAME ERROR: Property-object mismatch.");
    return;
  }
  *paddr=val;
}

rbool getattr(int obj, int prop) 
{
  rbool *paddr;

  if (prop>=NUM_ATTR) return 0;
  paddr=(rbool*)compute_addr(obj,prop,attrlist);
  if (paddr==NULL) return 0;
  return *paddr;
}

void setattr(int obj, int prop, rbool val)
{
  rbool *paddr;

  if (prop>=NUM_WATTR && prop!=24) {
    writeln("GAME ERROR: Read-only or non-existant attribute.");
    return;
  }

  paddr=(rbool*)compute_addr(obj,prop,attrlist);
  if (paddr==NULL) {
    writeln("GAME ERROR: Property-object mismatch.");
    return;
  }
  *paddr=val;
}



/* ------------------------------------------------------------------- */
/*  This sets up the creat_fix[] array, which is used to determine the */
/*  scan ranges for addressed creatures in cases where there is more */
/*  than one creature of the same name */

void init_creat_fix(void)
{
  int i,j;

  creat_fix=rmalloc(rangefix(maxcreat-first_creat+1)*sizeof(integer));
  for(i=0;i<maxcreat-first_creat+1;i++)
    creat_fix[i]=i+first_creat;  
  for(i=0;i<maxcreat-first_creat+1;i++)
    if (creat_fix[i]==i+first_creat)  /* That is, it hasn't changed. */
      for(j=i+1;j<maxcreat-first_creat+1;j++)
	if (creature[i].name==creature[j].name &&
	    creature[i].adj==creature[j].adj)
	  creat_fix[j]=i+first_creat;  /* That is, j --> i */  
}

void free_creat_fix(void)
{
  rfree(creat_fix);
}

/* ------------------------------------------------------------------- */

#ifndef IT_MACRO
int it_contents(integer obj)
{
  if (tnoun(obj)) return noun[obj-first_noun].contents;
  else if (troom(obj)) return room[obj-first_room].contents;
  else if (tcreat(obj)) return creature[obj-first_creat].contents;
  else if (obj==1) return player_contents;
  else if (obj==1000) return player_worn;
  else return 0;
}

rbool it_lockable(integer obj, word nword)
{
  if (tnoun(obj)) return noun[obj-first_noun].lockable;
  else if (it_door(obj,nword)) return 1;
  else return 0;
}

rbool it_locked(integer obj, word nword)
{
  if (tnoun(obj)) return noun[obj-first_noun].locked;
  else if (it_door(obj,nword) && room[loc].locked_door) return 1;
  else return 0;
}

#endif






