/*  object.c-- functions to manipulate rooms, nouns, and creatures  */
/* Copyright 1996,97    Robert Masenten                             */
/*                                                                  */
/* 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 <assert.h>
#include <ctype.h>
#include "agtread.h"
#include "uagt.h"
#include "exec.h"

/* This initializes a parse_rec for a given object */
void tmpobj(parse_rec *objrec)
{
  objrec->info=D_NOUN;
  objrec->num=0;
  objrec->noun=objrec->adj=0;
  objrec->obj=0;
}

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


/* Functions for getting bascic information about items */

static 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;
}

bool it_possess(int item)  
{
  int l;

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


static bool it_empty(int item)
{
  if (item<0) return 0;

  if (it_contents(item)!=0) return 0;
  else return 1;
}


/* ------------------------------------------------------------------- */
/* 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,bool 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;
    }
}




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

/* This chases up the object tree */
static bool is_here(int item,int where)
{
 int curloc;
 
 curloc=it_loc(item); /* Should work for nouns or creatures */
 while(curloc>maxroom && curloc!=1000 && curloc!=where)
     curloc=it_loc(curloc);
 if (curloc==1 || curloc==1000)
   if (where==1) return 1;
   else curloc=loc;
 return (curloc==where);
}


bool player_has(int item)
{
  return is_here(item,1);
#if 0
 int curloc;
 
 curloc=it_loc(item); /* Should work for nouns or creatures */
 while(curloc>maxroom && curloc!=1000)
     curloc=it_loc(curloc);
 return (curloc==1 || curloc==1000);
#endif
}


bool 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;
 
 curloc=it_loc(item); /* Should work for nouns or creatures */
 while (curloc>maxroom && curloc!=1000 && it_open(curloc))
     curloc=it_loc(curloc);
 if (curloc==1 || curloc==1000 || curloc==loc+first_room) return 1;
 else return 0;
}


static int good_light(int obj,int roomlight)
/* obj is a noun number */
{
  if (roomlight==1 && !noun[obj].light)
    return 0; /* obj is not a light source */
  if (roomlight>1)
    if (obj+first_noun==roomlight) return 1;
    else return 0; /* Not the correct light */    
 /* Now need to determine if obj is actually providing light */
  if (!noun[obj].on)
    return 0;  /* Light source is off or extinguished */
  return 1;
}

static int lightcheck(int parent,int roomlight)
/* This checks to see if anything contained in parent is a valid room light */
{
  int i;

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


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

/* Is item visible to player? */
bool visible(int item)
{
  if (item<0 && item!=ext_code[wdoor]) return 1;
  if (islit())
    return in_scope(item);
  else 
    return player_has(item);
}



/* 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;

  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);
}


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

void recompute_score(void)
{
  int obj;

  tscore-=objscore;
  objscore=0;
  nounloop(obj)
    if (noun[obj].points &&
	( visible(obj+first_noun) || is_here(obj+first_noun,treas_room) )   )
      objscore+=noun[obj].points;
  creatloop(obj)
    if (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;

  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 & (1<<i)) 
      add_mnoun(-flag_noun[i]);
  add_mitem(1);
  add_mitem(1000);
  add_mitem(loc+first_room);
  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();
}



void it_describe(int dobj)
{
  if (troom(dobj)) 
    print_descr(room_ptr[loc],1);
  else if (tnoun(dobj))
    runptr(dobj-first_noun,noun_ptr,"$You$ see nothing unexpected.",194);
  else if (tcreat(dobj))
    runptr(dobj-first_creat,creat_ptr,"$You$ see nothing unexpected.",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.");
    }
  if (tnoun(dobj) && 
      (noun[dobj-first_noun].open || !noun[dobj-first_noun].closable) && 
      !it_empty(dobj)) {
    writeln("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 bool invischeck(char *s) 
{
  while(isspace(*s)) s++;
  return strncasecmp(s,"INVISIBLE",9)==0; 
}



static int print_obj(int obj,int ind_lev)
/* Prints out s on a line of its own if obj isn't 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;
  char *s, *t, *posstr;

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

  sdesc_flag=!player_has(obj); /* This should be tested. */

  if (sdesc_flag) 
    s=it_sdesc(obj);   
  else 
    s=objname(obj); /* Must remember to rfree s before exiting */

  if (aver>=AGTME10) {
    for(t=s;isspace(*t);t++); /* Skip over initial whitespace... */
    *t=toupper(*t);  /* ...and upcase the first non-space character */
  }

  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("   ");
    writestr(s);
    /* 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(")");
      }
#if 0
    else if (parent==1000) {
       writestr(" (being worn)");
    } else if (parent>=first_noun) {
      if (parent>=first_creat && parent<=maxcreat)
	writestr("(carried by ");
      else 
	writestr(" (inside ");
      t=objname(parent);
      writestr(t);rfree(t);
      writestr(")");
    }
#endif 
    if (tnoun(obj) && noun[obj-first_noun].light && noun[obj-first_noun].on
	&& PURE_OBJ_DESC)
      writestr(" [Providing light]");
    writeln("");
  }
  if (!sdesc_flag)
    rfree(s);
  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;
}



