/*  exec.c-- player command execution utilities           */
/* 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 file contains the wrapper for running player commands,
   routines run at the end of the turn, and various other functions
   needed by runverb.c and token.c. */

#define global

#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <limits.h>

#include "agility.h"
#include "interp.h"
#include "exec.h"

static rbool pronoun_mode;

word realverb=0; /* Name of current verb. (normally ~= input[vp])*/


/* ------------------------------------------------------------------- */ 
/* High level output functions, used for printing messages, error      */
/* messages, and everything else. They call the direct output functions */
/* in interface.c The reason they're in runverb.c is that they need to */
/* access item info in order to fill in the blanks */


/* This updates the contents of compass_rose, which can be used by the 
   OS layer to print out some sort of representation of which way the
   player can go. */

static void set_compass_rose(void)
{
  int i, bit;
  
  compass_rose=0;
  if (!islit()) return;  /* No compass in darkness */
  for(i=0,bit=1;i<12;i++,bit<<=1)
    if (troom(room[loc].path[i])) compass_rose|=bit;
}

static void time_out(char *s)
{
  int hr,min;
  
  hr=curr_time/100;
  min=curr_time%100;

  if (milltime_mode)
    sprintf(s,"%02d:%02d",hr,min);
  else {
    if (hr>12) hr=hr-12;
    if (hr==0) hr=12;
    sprintf(s,"%2d:%02d %s",hr,min,(curr_time>=1200)?"pm":"am");
  }
}





void set_statline()
{
  char timestr[20];

  recompute_score();
  set_compass_rose();

  rstrncpy(l_stat,room[loc].name,81);

  time_out(timestr);

  switch(statusmode) {
     case 0:sprintf(r_stat,"Score: %ld  Moves: %d",tscore,turncnt);break;
     case 1:sprintf(r_stat,"Score: %ld   %s",tscore,timestr);break;
     case 2:sprintf(r_stat,"Moves: %d",turncnt);break;
     case 3:sprintf(r_stat,"%s",timestr);break;
     case 4:r_stat[0]='\0';break;  /* 'Trinity style' status line */
     case 5:sprintf(r_stat,"Score: %ld",tscore);break;
     }
}


/* --------------------------------------------------------------------	*/
/*  Message printing / $ substitution Routines                          */
/* --------------------------------------------------------------------	*/

#define FILL_SIZE 100


/* Tries to convert *pstr to a number, which it returns.
  If it fails, or if the number is not in the range 0..maxval,
  it returns -1. 
  It advances *pstr to point after the number and after the
  terminating character, if relevant.
  <term_char> is the terminating character; if this is 0, then
     the calling routine will worry about the terminating character.
  <maxval> of 0 indicates no upper bound
*/

static int extract_number(const char **pstr, int maxval, 
			  char term_char)
{
  const char *s;
  long n;  /* n holds the value to be returned; i holds 
	      the number of characters parsed. */
  n=0;
  s=*pstr;
  while(*s==' '||*s=='\t') s++;
  for(;*s!=0;s++) {
    if (*s<'0' || *s>'9') break;
    n=10*n + (*s-'0');
    if (maxval && n>maxval) return -1;
  }
  if (term_char) {
    if (*s==term_char) s++;
    else return -1;
  }
  *pstr=s;
  return n;
}

#define BAD_PROP (-1000)

/* This is used by #PROP[obj].[prop]# and $ATTR[obj].[attr]$, etc. */
/* isprop: Are we looking for a property (as opposed to an attribute)? */
static void extract_prop_val(const char **pstr, 
			     int *id, int *val, 
			     rbool isprop, const char term_char)
{
  const char *s;
  int v; /* object number / final value */
  int i; /* Attribute id */
  rbool builtin; /* Expect builtin property or attribute? */

  *id=i=BAD_PROP; *val=0; /* Failure case by default */
  builtin=0;
  s=*pstr; 
  if (match_str(&s,"NOUN")) v=dobj;
  else if (match_str(&s,"OBJECT")) v=iobj;
  else v=extract_number(&s,maxcreat,0); /* Must be object number */
  while(*s=='.') {
    s++;
    if (*s=='-') {
      builtin=1; s++;
    } else 
      builtin=0;
    i=extract_number(&s,0,0);
    if (!troom(v) && !tnoun(v) && !tcreat(v)) {i=-1;continue;}
    if (isprop || *s=='.')  /* Treat as property */
      v=builtin ? getprop(v,i) : op_objprop(2,v,i,0);
    else  /* Treat as attribute */
      v=builtin ? getattr(v,i) : op_objflag(2,v,i);
  }
  if ( *s!=term_char) return;
  *pstr=s+1;
  if (i<0) return;
  *id=builtin ? -1 : i;
  *val=v;
}




static word it_pronoun(int item, rbool ind_form)
/* Return the correct pronoun to go with item; 
   ind_form is 1 if we want the object form, 0 if we want the
   subject form */
{
  if (it_plur(item)) 
    return (ind_form ? ext_code[wthem] : ext_code[wthey]);
  if (tcreat(item)) 
    switch (creature[item-first_creat].gender) {
      case 0:return ext_code[wit];
      case 1:return (ind_form ? ext_code[wher] : ext_code[wshe]);
      case 2:return (ind_form ? ext_code[whim] : ext_code[whe]);
      }
  return ext_code[wit];
}

/* This sets the value of "The" for the given noun. */
/* (In particular, proper nouns shouldn't have a "the") */
static void theset(char *buff, int item)
{		   
  if (it_proper(item))
    strcpy(buff,"");
  else 
    strcpy(buff,"the ");
}


static void num_name_func(parse_rec *obj_rec,char *fill_buff, word prev_adj)
/* This is a subroutine to wordcode_match. */
/* It gives either a noun name or a number, depending. */
/* prev_adj is a word if this was preceded by its associated $adjective$;
   the goal is to avoid having $adjective$ $noun$ expand to (e.g.)
   'silver silver' when the player types in "get silver" to pick up
   a magic charm with synonym 'silver'. */
{
  word w;

  if (obj_rec==NULL) {
    strcpy(fill_buff,"");
    return;
  }

  w=0;
  if (obj_rec->noun!=0) w=obj_rec->noun;
  if ( (w==0 || w==prev_adj) && obj_rec->obj!=0)  
    w=it_name(obj_rec->obj);

  if (w==0) {
    if (obj_rec->info==D_NUM) sprintf(fill_buff,"%ld",(long)obj_rec->num);
    else strcpy(fill_buff,"");
#if 0
    strcpy(fill_buff,"that"); /* We can try and hope */
#endif
    return;
  }

  if (w==prev_adj) /* ... and prev_adj!=0 but we don't need to explicity
		      test that since w!=0 */
    fill_buff[0]=0; /* i.e. an empty string */
  else {
    rstrncpy(fill_buff,dict[w],FILL_SIZE);
    if (it_proper(obj_rec->obj)) fill_buff[0]=toupper(fill_buff[0]);
  }
}

static word get_adj( parse_rec *obj_rec, char *buff)
{
  word w;

  if (obj_rec->adj!=0) w=obj_rec->adj;
  else w=it_adj(obj_rec->obj);

  if (w==0) strcpy(buff,"");
  else {
    rstrncpy(buff,dict[w],FILL_SIZE);
    if (it_proper(obj_rec->obj)) buff[0]=toupper(buff[0]);
  }
  
  return w;
}



#define d2buff(i) {rstrncpy(fill_buff,dict[i],FILL_SIZE);return 1;}
#define num_name(obj_rec,jsa)  {num_name_func(obj_rec,fill_buff,jsa);return 1;}
/* jsa= Just seen adj */
#define youme(mestr,youstr) {strcpy(fill_buff,irun_mode?mestr:youstr);\
			       return 1;}

word just_seen_adj;  /* This determines if we just saw $adjective$; if so,
		      this is set to it, otherwise it is zero. See 
		      num_name_func above. */

static int wordcode_match(const char **pvarname,char *fill_buff,
			  int context,const char *pword) 
/* Check <*p*pvarname> for a match; put subs text in fill_buf
   <context> indicates who called us; this determines
      what substitutions are valid. See interp.h for possible
      values.  Move *p*pvarname after whatever is matched.
   <pword> contains the parse word when context is MSG_PARSE. */
/* $ forms: 
   $verb$, $noun$, $adjective$, $prep$, $object$, $name$, 
   $n_pro$, $o_pro$, $n_indir$, $o_indir$,
      $name_pro$, $name_indir$
   $n_is$, $o_is$, $name_is$ 
   $c_name$ 
   $n_was$, $o_was$, $name_was$
   $the_n$, $the_o$, $the_name$
   */
/* Also $STRn$, $FLAGn$, $ONn$, $OPENn$, $LOCKEDn$ */
/*  Support for FLAG, ON, OPEN, and LOCKED added by Mitch Mlinar */
/* Return 0 if no match, 1 if there is  */
{
  int hold_val, hold_id;

  fill_buff[0]=0; /* By default, return "\0" string */
  if (match_str(pvarname,"STR")) { /* String variable */
    hold_id=extract_number(pvarname,MAX_USTR,'$');
    if (hold_id<1) return 0;    
    rstrncpy(fill_buff,userstr[hold_id-1],FILL_SIZE);
    return 1;
  }
  else if (match_str(pvarname,"VAR")) {
    hold_id=extract_number(pvarname,VAR_NUM,'$');
    if (hold_id<0) return 0;
    hold_val=agt_var[hold_id];
    rstrncpy(fill_buff,
	     get_objattr_str(AGT_VAR,hold_id,hold_val),FILL_SIZE);
    return 1;
  } else if (match_str(pvarname,"FLAG")) {
    hold_id=extract_number(pvarname,FLAG_NUM,'$');
    if (hold_id<0) return 0;
    rstrncpy(fill_buff,
	     get_objattr_str(AGT_FLAG,hold_id,flag[hold_id]),FILL_SIZE);
    return 1;
  } else if (match_str(pvarname,"ATTR")) {
    extract_prop_val(pvarname,&hold_id,&hold_val,0,'$');
    if (hold_id==BAD_PROP) return 1;
    rstrncpy(fill_buff,
	     get_objattr_str(AGT_OBJFLAG,hold_id,hold_val),FILL_SIZE);
    return 1;
  } else if (match_str(pvarname,"PROP")) {
      extract_prop_val(pvarname,&hold_id,&hold_val,1,'$');
      if (hold_id==BAD_PROP) return 1;
      rstrncpy(fill_buff,
	       get_objattr_str(AGT_OBJPROP,hold_id,hold_val),FILL_SIZE);
      return 1;
  } else if (match_str(pvarname,"OPEN")) {
    hold_val = extract_number(pvarname,maxnoun,'$');
    strcpy(fill_buff,it_open(hold_val)? "open" : "closed");
    return 1;
  } 
  else if (match_str(pvarname,"ON")) {
    hold_val = extract_number(pvarname,maxnoun,'$');
    strcpy(fill_buff,it_on(hold_val)? "on" : "off");
    return 1;
  }
  else if (match_str(pvarname,"LOCKED")) {
    hold_val = extract_number(pvarname,maxnoun,'$');
    strcpy(fill_buff,it_locked(hold_val,0) ? "locked" : "unlocked");
    return 1;
  }

  if (context==MSG_MAIN) return 0; 

  if (context==MSG_PARSE) {
    /* The only special subsitution allowed is $word$. */
    if (match_str(pvarname,"WORD$")) {
      if (pword==NULL) fill_buff[0]=0;
      else rstrncpy(fill_buff,pword,FILL_SIZE);
      return 1;
    } else return 0;
  }

/* d2buff is a macro that returns 1 */
  if (match_str(pvarname,"NOUN$"))
    num_name(dobj_rec,just_seen_adj);
  just_seen_adj=0; /* It doesn't matter. */
  if (match_str(pvarname,"VERB$")) 
    d2buff(realverb);  /* auxsyn[vb][0] */ 
  if (match_str(pvarname,"OBJECT$"))
    num_name(iobj_rec,0);
  if (match_str(pvarname,"NAME$"))
    num_name(actor_rec,0);
  if (match_str(pvarname,"ADJECTIVE$")) {
    just_seen_adj=get_adj(dobj_rec,fill_buff);
    return 1;
  }
  if (match_str(pvarname,"PREP$"))
    d2buff(prep);
  if (match_str(pvarname,"N_PRO$"))
    d2buff(it_pronoun(dobj,0));
  if (match_str(pvarname,"O_PRO$"))
    d2buff(it_pronoun(iobj,0));
  if (match_str(pvarname,"NAME_PRO$"))
    d2buff(it_pronoun(actor,0));
  if (match_str(pvarname,"N_INDIR$"))
    d2buff(it_pronoun(dobj,1));
  if (match_str(pvarname,"O_INDIR$"))
    d2buff(it_pronoun(iobj,1));
  if (match_str(pvarname,"NAME_INDIR$"))
    d2buff(it_pronoun(actor,1));
  if (match_str(pvarname,"N_IS$")) {
    if (!it_plur(dobj)) d2buff(ext_code[wis])
    else d2buff(ext_code[ware]);
  }
  if (match_str(pvarname,"O_IS$")) {
    if (!it_plur(iobj)) d2buff(ext_code[wis])
    else d2buff(ext_code[ware]);
  }
  if (match_str(pvarname,"NAME_IS$")) {
    if (!it_plur(actor)) d2buff(ext_code[wis])
    else d2buff(ext_code[ware]);
  }

  if (match_str(pvarname,"N_WAS$")) {
    if (!it_plur(dobj)) d2buff(ext_code[wwas])
    else d2buff(ext_code[wwere]);
  }
  if (match_str(pvarname,"O_WAS$")) {
    if (!it_plur(iobj)) d2buff(ext_code[wwas])
    else d2buff(ext_code[wwere]);
  }
  if (match_str(pvarname,"NAME_WAS$")) {
    if (!it_plur(actor)) d2buff(ext_code[wwas])
    else d2buff(ext_code[wwere]);
  }
  if (match_str(pvarname,"THE_N$"))
    {theset(fill_buff,dobj);return 1;}
  if (match_str(pvarname,"THE_O$"))
    {theset(fill_buff,iobj);return 1;}
  if (match_str(pvarname,"THE_NAME$"))
    {theset(fill_buff,actor);return 1;}
  if (match_str(pvarname,"THE_C$"))
    {theset(fill_buff,curr_creat_rec->obj); return 1;}
  if (match_str(pvarname,"C_NAME$")) 
    num_name(curr_creat_rec,0); 
  if (match_str(pvarname,"TIME$")) 
    {time_out(fill_buff);return 1;}

  if (pronoun_mode && match_str(pvarname,"YOU$")) 
    youme("I","you");
  if (pronoun_mode && match_str(pvarname,"ARE$")) 
    youme("am","are");
  if (pronoun_mode && match_str(pvarname,"YOU_OBJ$"))
    youme("me","you");
  if (pronoun_mode && match_str(pvarname,"YOUR$"))
    youme("my","your");
  if (pronoun_mode && match_str(pvarname,"YOU'RE$"))
    youme("i'm","you're");
  return 0;  /* Don't recognize $word$ */
}



static int capstate(const char *varname)
{
  if (islower(varname[0])) return 0; /* $word$ */
  if (islower(varname[1])) return 2; /* $Word$ */
  if (!isalpha(varname[1]) && varname[1]!=0 
      && islower(varname[2])) return 2;
  else return 1; /* $WORD$ */
}

static char fill_buff[FILL_SIZE]; /* Buffer to hold returned string */

static char *wordvar_match(const char **pvarname,char match_type,
			   int context, const char *pword)
     /* Match_type=='#' for variables, '$' for parsed words */
     /* Possible # forms: #VARn#, #CNTn# */ 
     /* See above for $ forms */ 
     /* Moves *pvarname to point after matched object */
{
  int i, hold_val, hold_prop;
  const char *start;

  start=*pvarname;
  if (match_type=='$') {
    i=wordcode_match(pvarname,fill_buff,context,pword);
    if (i==0) return NULL;
    /* Now need to fix capitalization */
    switch (capstate(start)) {
      case 0:break; /* $word$ */
      case 1:  /* $WORD$ */
	for(i=0;fill_buff[i]!='\0';i++) 
	  fill_buff[i]=toupper(fill_buff[i]);
	break;
      case 2: /* $Word$ */
	fill_buff[0]=toupper(fill_buff[0]);
	break;
      }
  } else {  /* So match type is '#' */
    if (match_str(pvarname,"VAR")) {
      hold_val=extract_number(pvarname,VAR_NUM,'#');
      if (hold_val<0) return NULL;
      hold_val=agt_var[hold_val];
    } 
    else if (match_str(pvarname,"CNT")|| 
	     match_str(pvarname,"CTR")) {
      hold_val=extract_number(pvarname,CNT_NUM,'#');
      if (hold_val<0) return NULL;
      hold_val=cnt_val(agt_counter[hold_val]);
    }
    else if (match_str(pvarname,"PROP")) {
      extract_prop_val(pvarname,&hold_prop,&hold_val,1,'#');
      if (hold_prop==BAD_PROP) hold_val=0;
    }
    else
      return NULL;
    
   /* Now to convert hold_val into a string */ 
    sprintf(fill_buff,"%d",hold_val);

  }
  return fill_buff;
}

static char  *format_line(const char *s,int context,const char *pword) 
/* Do $word$ substituations; return the result */
{
  char *t; /* The new string after all the substitutions. */
  int t_size; /* How much space has been allocated for it. */
  const char *p, *oldp;  /* Pointer to the original string */
  int i;
  char *fill_word, *q; /* Word used to fill in the blanks, and a pointer
			used to iterate through it*/
  char fill_type; /* '#'=#variable#, '$'=$word$ */

 /* Need to do subsitutions and also correct for tabs */
  t_size=200;
  t=rmalloc(t_size+FILL_SIZE+10);
  just_seen_adj=0;

 /* Note that I leave some margin here: t is 310 characters, but i will never 
    be allowed above around 200. This is to avoid having to put special 
    checking code throughout the following to make sure t isn't overrun */ 
  for(p=s,i=0;*p!='\0';p++) {
    if (i>=t_size) {
      t_size=i+100;
      t=rrealloc(t,t_size+FILL_SIZE+10);
    }
    if (!rspace(*p) && *p!='$') 
      just_seen_adj=0;
    if (*p=='$' || *p=='#')  {  
      /* Read in $word$ or #var# and do substitution */
      fill_type=*p;
      oldp=p++;  /* Save old value in case we are wrong and then 
		  increment p */
      fill_word=wordvar_match(&p,fill_type,context,pword);
      if (fill_word==NULL) {
        /*i.e. no match-- so just copy it verbatim */ 
	t[i++]=fill_type;
	just_seen_adj=0;
	p=oldp;   /* Go back and try again... */
      } else { /* Fill in word */
	p--;
	if (fill_word[0]=='\0') { /* Empty string */
	  /* We need to eliminate a 'double space' in this case */
	  if ( ( oldp==s || rspace(*(oldp-1)) ) && rspace(*(p+1))) 
	    p++;
	} else  /* Normal case */
	  for(q=fill_word;*q!='\0';)
	    t[i++]=*q++;
      }
    } /* End $/# matcher */
    else
      t[i++]=*p;
  } /* End scanning loop */

  if (aver<AGX00 && i>0 && t[i-1]==' ') {
    /* For pre-Magx, delete trailing spaces */
    do 
      i--;
    while(i>0 && t[i]==' ');
    i++;    
  }
  t[i]=0;
  t=rrealloc(t,i+1);
  return t;
}

void raw_lineout(const char *s, rbool do_repl, int context, const char *pword)
{
  char *outstr;
  
  if (do_repl) {
    outstr=format_line(s,context,pword);
    writestr(outstr);
    rfree(outstr);
  } else
    writestr(s);
}


static void lineout(const char *s, rbool nl,int context,const char *pword)
{
  raw_lineout(s,1,context,pword);
  if (nl) writeln("");
  else writestr(" ");
}

static void gen_print_descr(descr_ptr dp,rbool nl,
			    int context,const char *pword)
{
  int j;
  descr_line *txt;

  agt_textcolor(7);
  textbold=0;
  agt_par(1);
  txt=read_descr(dp.start,dp.size);
  if (txt!=NULL) 
    for(j=0;txt[j]!=NULL;j++)  
      lineout(txt[j], nl || (txt[j+1]!=NULL), context,pword);
  free_descr(txt);
  agt_par(0);
  agt_textcolor(7);
  textbold=0;
}

void print_descr(descr_ptr dp,rbool nl)
{
  gen_print_descr(dp,nl,MSG_DESC,NULL);
}

void quote(int msgnum)
{
  char **qptr;
  descr_line *txt;
  int i;
  int len;

  txt=read_descr(msg_ptr[msgnum-1].start, msg_ptr[msgnum-1].size);
  if (txt!=NULL) {
    for(len=0;txt[len]!=NULL;len++);
    qptr=rmalloc(len*sizeof(char*));
    for(i=0;i<len;i++)     
      qptr[i]=format_line(txt[i],MSG_DESC,NULL); 
    free_descr(txt);
    textbox(qptr,len,TB_BORDER|TB_CENTER);
    rfree(qptr);
  }
}


void msgout(int msgnum,rbool add_nl)
{
  print_descr(msg_ptr[msgnum-1],add_nl);
}


#define MAX_NUM_ERR 240      /* Highest numbered STANDARD message */
#define OLD_MAX_STD_MSG 185

/* Fallback messages should always have msgid less than the original */
int stdmsg_fallback[MAX_NUM_ERR-OLD_MAX_STD_MSG]={
  0,0,0,12,0,   /* 186 - 190 */
  0,0,0,0,0,    /* 191 - 195 */
  0,13,13,5,10,        /* 196 - 200 */
  10,61,10,16,59,      /* 201 - 205 */
  90,107,116,135,140,  /* 206 - 210 */
  184,3,47,185,61,      /* 211 - 215 */
  0,0,0,0,0,             /* 216 - 220 */
  0,0,0,0,0,0,0,0,0,0,   /* 221 - 230 */
  0,0,0,0,0,0,0,0,0,0    /* 231 - 240 */
};

void gen_sysmsg(int msgid,char *s,int context,const char *pword)
/* Prints either STANDARD message number <msgid> or default msg <s>;
   A <msgid> of 0 means there is no standard message counterpart.
   <context> determines what $$ substitutions are meaningful
   <parseword> gives the $pword$ substitution for MSG_PARSE messages 
   msgid 3 should probably *not* be redirected to avoid giving hints to
   the player as to what nouns exist in the game.
*/
{
  /* Use gamefile's redefined version of message? */
  rbool use_game_msg; 
  rbool nl; /* Should it be followed by a newline? */

  nl=1; /* By default, follow message with newline */

  /* The following msgids shouldn't be followed by newlines: */
  if (msgid==1 || msgid==145 || (msgid>=218 && msgid<=223)  
      || msgid==225) 
    nl=0;

  if (DEBUG_SMSG) rprintf("\nSTD %d",msgid);
  
  use_game_msg=( (PURE_SYSMSG || s==NULL) 
		 && msgid!=0 && msgid<=NUM_ERR 
		 && err_ptr!=NULL );

  if (use_game_msg) {
    /* Check for fall-back messages */
    if (err_ptr[msgid-1].size<=0 
	&& msgid>OLD_MAX_STD_MSG && msgid<=MAX_NUM_ERR) {
      msgid=stdmsg_fallback[msgid-OLD_MAX_STD_MSG-1];
      if (DEBUG_SMSG) rprintf("==> %3d");
    }
    if (msgid!=0 && err_ptr[msgid-1].size>0) {
      if (DEBUG_SMSG) rprintf(" : From gamefile\n");
      gen_print_descr(err_ptr[msgid-1],nl,context,pword);
    } else use_game_msg=0;
  }
  
  if (DEBUG_SMSG && !use_game_msg) rprintf(" : Default\n");

  if (!use_game_msg) {
    /* Either the game doesn't redefine the message, or we're ignoring
       redefinitions */
    if (s==NULL) return;
    pronoun_mode=1;
    lineout(s,nl,context,pword);  
    pronoun_mode=!PURE_PROSUB;
  }
}


void sysmsg(int msgid,char *s)
{
  gen_sysmsg(msgid,s,MSG_RUN,NULL);
}


void alt_sysmsg(int msgid,char *s,
		parse_rec *new_dobjrec,parse_rec *new_iobjrec)
{
  parse_rec *save_dobjrec, *save_iobjrec;
  integer save_dobj, save_iobj;

  save_dobj=dobj; save_dobjrec=dobj_rec;  
  dobj=p_obj(new_dobjrec); dobj_rec=new_dobjrec;

  save_iobj=iobj; save_iobjrec=iobj_rec;
  iobj=p_obj(new_iobjrec); iobj_rec=new_iobjrec;

  gen_sysmsg(msgid,s,MSG_RUN,NULL);

  dobj=save_dobj; dobj_rec=save_dobjrec;
  iobj=save_iobj; iobj_rec=save_iobjrec;
}


void sysmsgd(int msgid,char *s,parse_rec *new_dobjrec)
/* Front end for sysmsg w/alternative direct object */
{
  alt_sysmsg(msgid,s,new_dobjrec,NULL);
}


/* --------------------------------------------------------------------	*/
/*  QUESTION and ANSWER processing                                      */
/* --------------------------------------------------------------------	*/


static char *match_string(char *ans,char *corr_ans,int n)
/* Searches for s (w/ surrounding whitespace removed) inside ans */
/* looking at only n characters of s */
{
  char *s;
  char *corr;
  int i;

  s=rstrdup(corr_ans);
  for(i=n-1;i>0 && isspace(s[i]);i--); /* Kill trailing whitespace */
  s[i+1]=0;
  for(i=0;s[i]!=0;i++) s[i]=tolower(s[i]);
  for(i=0;isspace(s[i]);i++); /* Kill leading whitespace */
  corr=strstr(ans,s+i);
  rfree(s);
  return corr;
}


static rbool check_answer(char *ans,long start,long size)
/* qnum has already been fixed to start from 0 */
/*   Master's edition answer checker. Master's edition answers can */
/* be seperate by AND and OR characters. If there is one OR in the */
/* answer, all ANDs will also be treated as ORs */
/*   Furthermore, AND-delimited strings must appear in the correct order */
/* unless PURE_ANSWER is false */
{
  char *corr, *corr2; /* Pointer into answer to match correct answers */
  int match_mode; /* 0=AND mode, 1=OR mode */
  descr_line *astr; /* Holds the answer string */
  int i; /* Index to line of astr we're on. */
  char *p,*q, *r;  /* Used to break astr up into pieces and 
		loop over them */

  astr=read_descr(start,size);
  if (astr==NULL) {
    if (!PURE_ERROR)
      writeln("GAME ERROR: Empty answer field.");
    return 1; }

  match_mode=0;
  for(i=0;astr[i]!=NULL;i++) 
    if (strstr(astr[i],"OR")!=NULL) {match_mode=1;break;}
  
  corr=ans;
  for(i=0;astr[i]!=NULL;i++) {  /* loop over all lines of the answer */
    p=astr[i];
    do {
      q=strstr(p,"OR");
      r=strstr(p,"AND");
      if (q==NULL || (r!=NULL && r<q)) q=r;
      if (q==NULL) q=p+strlen(p); /* i.e. points at the concluding null */
      corr2=match_string(corr,p,q-p);
      if (corr2==NULL && match_mode==0) {free_descr(astr);return 0;}
      if (corr2!=NULL && match_mode==1) {free_descr(astr);return 1;}
      if (PURE_ANSWER && match_mode==0) corr=corr2;
      if (*q=='O') p=q+2;
      else if (*q=='A') p=q+3;
      else assert(*q==0);
    } while (*q!=0);
  }
  free_descr(astr);
  if (match_mode==0) return 1; /* AND: Matched them all */
  else return 0;  /* OR: Didn't find a single match */
}


/* Does the answer in string ans match answer anum? */
/* Warning: this changes and then rfrees ans */
rbool match_answer(char *ans, int anum)
{
  char *corr;
  rbool ans_corr;

  for(corr=ans;*corr!=0;corr++)
    *corr=tolower(*corr);
  if (answer!=NULL) {
    /* corr=strstr(ans,answer[anum]); */
    corr=match_string(ans,answer[anum],strlen(answer[anum]));
    rfree(ans);
    if (corr==NULL) return 0;
  } else if (ans_ptr!=NULL) { 
    ans_corr=check_answer(ans,ans_ptr[anum].start,ans_ptr[anum].size);
    rfree(ans);
    return ans_corr; 
  }
  else writeln("INT ERR: Invalid answer pointer.");
  return 1;

}


rbool ask_question(int qnum)
/* 1=got it right, 0=got it wrong */
{
  char *ans;

  qnum--;

/* Now actually ask the question and compare the answers */
  if (question!=NULL) 
    writeln(question[qnum]);
  else if (quest_ptr!=NULL)
    print_descr(quest_ptr[qnum],1);
  else {writeln("INT ERR: Invalid question pointer");return 1;}
  ans=agt_readline(2);
  return match_answer(ans,qnum);
}


/* --------------------------------------------------------------------	*/
/*  Miscellaneous support routines */
/* --------------------------------------------------------------------	*/

long read_number(void)
{
  char *s, *err;
  long n;
  
  n=1;
  do {
    if (n!=1) gen_sysmsg(218,"Please enter a *number*. ",MSG_MAIN,NULL);
    s=agt_readline(1);
    n=strtol(s,&err,10);
    if (err==s) err=NULL;
    rfree(s);
  } while (err==NULL || n<=INT_MIN || n>=INT_MAX);
  return n;
}




void runptr(int i, descr_ptr dp[], char *msg, int msgid, 
	    parse_rec *nounrec, parse_rec *objrec)
/* Prints out description unless it doesn't exist, in which
   case it prints out either system message #msgid or the message
   contained in msg. */
{
  if (dp[i].size>0) print_descr(dp[i],1);
  else alt_sysmsg(msgid,msg,nounrec,objrec);
}



/* Score modes:
      S:Score, R:Room  +=list '(out of..'), -=don't list at all.
    0-- S+ R+
    1-- S+ R
    2-- S  R+
    3-- S  R
    4-- S+ R-
    5-- S  R-
    6-- S- R+
    7-- S- R
    8-- S- R- and disable SCORE.
    */
 

void print_score(void)
{
  char s[80];
  int i,rmcnt, totroom;

  if (score_mode<5) {
    if (score_mode==0 || score_mode==1 || score_mode==4)
      sprintf(s,"Your score is %ld (out of %ld possible).",tscore,max_score);
    else sprintf(s,"Your score is %ld.",tscore);
    writeln(s);
  }

  if (score_mode<4 || score_mode==6 || score_mode==7) {
    rmcnt=0;
    totroom=0;
    for(i=0;i<=maxroom-first_room;i++)
      if (!room[i].unused) {
	if (room[i].seen) rmcnt++;
	/* Should really compute this once at the beginning, but */
	/* I don't want to add yet another global variable, particulary */
	/* since this is only used here. */
	totroom++; 
      }
    if (score_mode%2==0) 
      sprintf(s,"You have visited %d locations (out of %d in the game)",rmcnt,
	      totroom);
    else sprintf(s,"You have visited %d locations.",rmcnt);
    writeln(s);
  }
}


int normalize_time(int tnum) /* Convert hhmm so mm<60 */
{
 int min,hr;

  min=tnum % 100; /* The minutes */
  hr=tnum / 100; /* The hours */
  hr+=min/60; min=min%60;
  while(hr<0) hr+=24;
  hr=hr%24;
  return hr*100+min;    
}


void add_time(int dt)
{
  int min,hr;

  min=curr_time % 100; /* The minutes */
  hr=curr_time / 100; /* The hours */
  if (aver==AGT183) min+=dt; /* AGT 1.83 version */
  else {  /* Normal version */
    min+=dt%100;
    hr+=dt/100;}
  while(min<0) {min=min+60;hr++;}
  hr+=min/60; min=min%60;
  while(hr<0) hr+=24;
  hr=hr%24;
  curr_time=hr*100+min;    
}


void look_room(void)
{
  compute_seen();
  writeln("");
  if (islit()) {
    if (room[loc].name!=NULL && room[loc].name[0]!=0 && 
	(!PURE_ROOMTITLE) ) 
      {
	agt_textcolor(-1);  /* Emphasized text on */
	writestr(room[loc].name);
	agt_textcolor(-2);
	writeln("");
      }  /* Emphasized text off */
    if (room_firstdesc && room[loc].initdesc!=0) 
      msgout(room[loc].initdesc,1);
    else if (room_ptr[loc].size>0) 
      print_descr(room_ptr[loc],1);
    print_contents(loc+first_room,1);
    if (listexit_flag)
      v_listexit();
  }
  else 
    sysmsg( room[loc].light==1?6:7,
	    "It is dark. $You$ can't see anything.");
  room_firstdesc=0;
  do_look=0;
}


static void run_autoverb(void)
{
  int v0; /* Will hold the verb number of the autoverb */
  int savevb;
  integer saveactor, savedobj, saveiobj;
  parse_rec *save_actor_rec, *save_dobj_rec, *save_iobj_rec;
  word saveprep;


  beforecmd=1;

  /* This is the penalty for vb, actor, etc being global variables. */
  savevb=vb;
  saveactor=actor; savedobj=dobj; saveprep=prep; saveiobj=iobj;
  save_actor_rec=copy_parserec(actor_rec); 
  save_dobj_rec=copy_parserec(dobj_rec); 
  save_iobj_rec=copy_parserec(iobj_rec);

  if (room[loc].autoverb!=0) {
    v0=verb_code(room[loc].autoverb);
    (void)scan_metacommand(0,v0,0,0,0,NULL);
  }
  free_all_parserec(); 
  vb=savevb;
  actor=saveactor; dobj=savedobj; iobj=saveiobj;
  actor_rec=save_actor_rec; dobj_rec=save_dobj_rec; iobj_rec=save_iobj_rec;
  prep=saveprep;
}



/* ------------------------------------------------------------------- */
/* MAIN COMMAND EXECUTION ROUTINES-- */
/*  These routines handle the execution of player commands  */
/*  Then they change the status line, update counters, etc. */
/* ------------------------------------------------------------------- */

static void creat_initdesc(void)
{
  int i;

  creatloop(i) 
    if (creature[i].location==loc+first_room &&
	creature[i].initdesc!=0) {
      msgout(creature[i].initdesc,1);
      creature[i].initdesc=0;
    }
}

/* Print out picture names, remember to put intro before first one. */
/* This should be called with s==NULL before and after:
   before to reset it, after to put the trailing newline on. */
void listpictname(char *s)
{
  static rbool first_pict=1; /* True until we output first picture */

  if (s==NULL) {
    if (!first_pict) writeln(""); /* Trailing newline */
    first_pict=1;
    return;
  }
  if (first_pict) {
    writeln(""); /* Skip a line */
    sysmsg(219,"  Illustrations:");
    first_pict=0;
  }
  writestr("  ");
  writestr(s);
}


void listpict(int obj)
{
  char *s;

  if (it_pict(obj)!=0 ) {
    s=objname(obj);
    listpictname(s);
    rfree(s);
  }
}


void list_viewable(void)
     /* List pictures that can be viewed, if any */
{
  int i;
  
  listpictname(NULL);

  if (room[loc].pict!=0) 
    listpictname("scene");
  contloop(i,1)
    listpict(i);
  contloop(i,1000)
    listpict(i);
  contloop(i,loc+first_room)
    listpict(i);
	   
  for(i=0;i<maxpix;i++)
    if (room[loc].PIX_bits & (1L<<i))
      listpictname(dict[pix_name[i]]);
  listpictname(NULL);
}



void newroom(void)
{
  rbool save_do_look;
  integer prevloc;
  
  do {
    save_do_look=do_look;
    if (do_look==1) look_room();
    creat_initdesc();
    if (save_do_look==1 && aver>=AGTME10) 
      list_viewable();   /* Print out picts that can be viewed here. */
    do_look=0;

    prevloc=loc;
    if (do_autoverb) {
      do_autoverb=0;
      run_autoverb();
    }

    if (!room[loc].seen) { /* This only runs on the first turn */
      room[loc].seen=1;
      tscore+=room[loc].points;
    }
  } while (prevloc!=loc);  /* Autoverb could move player */
}


static int min_delta(void)
{
  return (aver==AGT183) ? 1 : 0 ;
}


void increment_turn(void)
{
  int i;

  compute_seen();

  newlife_flag=0;

  if (quitflag) return;

  newroom();

  if (winflag || deadflag || endflag) return;

  if (was_metaverb) return;  /* No time should pass during a metaverb. */

  turncnt++;
  /* Now increment the time counter */
  if (delta_time>0) {
    if (PURE_TIME) 
      add_time(agt_rand(min_delta(),delta_time)); 
    else /* if !PURE_TIME */
      add_time(delta_time);
  }

  for(i=0;i<=CNT_NUM;i++)
    if (agt_counter[i]>=0) ++agt_counter[i];
  creatloop(i)
    if (creature[i].location==loc+first_room && creature[i].hostile &&
	creature[i].timethresh>0) 
      {       
	parse_rec tmpcreat; /* Used for creature messages */
	make_parserec(i+first_creat,&tmpcreat); 
	curr_creat_rec=&tmpcreat;

	if (++creature[i].timecounter>=creature[i].timethresh)
	  {   /* Creature attacks */
	    sysmsg(16,"$The_c$$c_name$ suddenly attacks $you_obj$!");
	    sysmsg(creature[i].gender==0 ? 17 : 18,
		   "    $You$ try to defend $your$self, but $the_c$$c_name$ "
		   "kills $you_obj$ anyhow.");
	    deadflag=1;
	  }
	else  /* 'Angrier' messages */
	  if (creature[i].timethresh>0 && 
	      creature[i].timecounter>creature[i].timethresh-3)
	    sysmsg(15,"$The_c$$c_name$ seems to be getting angrier.");
      }
}


/* Wrapper for increment_turn used by exec routines below. 
   This just checks to make sure we're not one of the 1.8x versions
   (which call increment turn from elsewhere) */
static void exec_increment_turn(void)
{
  if (PURE_AFTER) increment_turn();
}

static void end_turn(void)
{
  if (textbold) agt_textcolor(-2);
  textbold=0;
  set_statline();

  if (quitflag) return;

  if (notify_flag && !was_metaverb) {
    if (old_score<tscore)
      sysmsg(227,"  [Your score just went up]");
    else if (old_score>tscore)
      sysmsg(228,"  [Your score just went down]");
  }
  old_score=tscore;

}



static void set_pronoun(int item)
{
  if (item==0) return;
  switch (it_gender(item)) {
     case 0: 
         if (it_plur(item)) 
	   last_they=item; 
	 last_it=item;  /* Change: last_it will be set even if the
			   noun is plural */
	 break;
     case 1: last_she=item;break;
     case 2: last_he=item;break;
  }
}


/* True if the current noun is the last one in the list. */
static rbool lastnoun(parse_rec *list)
{
  if (list->info==D_END) return 1;
  list++;
  while(list->info==D_AND) list++;
  return (list->info==D_END);
}




static void runverbs(parse_rec *actor0,int vnum,
		     parse_rec *lnoun,word prep0,parse_rec *iobj0)
/* The zeros are postpended simply to avoid a name conflict */
{
  parse_rec *currnoun;

  textbold=0;
  do_look=0;do_autoverb=0;
  was_metaverb=0;
  actor=actor0->obj;
  actor_rec=copy_parserec(actor0);
  vb=vnum;
  dobj=lnoun[0].obj;
  dobj_rec=copy_parserec(lnoun);
  prep=prep0;
  iobj=iobj0->obj;
  iobj_rec=copy_parserec(iobj0);
  set_pronoun(actor);  /* Basically the last one that isn't 0 will stick */
  set_pronoun(iobj0->obj);
  was_metaverb=0; /* Most verbs are not metaverbs; assume this by default */
  start_of_turn=1; end_of_turn=0;

  if (lnoun[0].info==D_END || lnoun[0].info==D_ALL) {
    end_of_turn=1;
    exec_verb();
    if (doing_restore) {free_all_parserec();return;}
    if (PURE_AND) exec_increment_turn();
  }
  else for(currnoun=lnoun;currnoun->info!=D_END;currnoun++)
    if (currnoun->info!=D_AND) {
      free_all_parserec();
      end_of_turn=lastnoun(currnoun);
      actor=actor0->obj;
      actor_rec=copy_parserec(actor0);
      vb=vnum;
      dobj=currnoun->obj;
      dobj_rec=copy_parserec(currnoun);
      iobj=iobj0->obj;
      iobj_rec=copy_parserec(iobj0);
      set_pronoun(dobj);
      exec_verb();
      if (doing_restore) return;
      if (PURE_AND) 
	exec_increment_turn();
      else 
	start_of_turn=0;
      if (quitflag || winflag || deadflag || endflag) 
	break;
    }
  assert(end_of_turn);
  if (!PURE_AND) exec_increment_turn();
  end_turn();
  free_all_parserec();
}


/* The following store values for use by AGAIN */
/* (so AGAIN can be implemented just by executing runverbs w/ the saved
   values) */
static int save_vnum;
static word save_prep;
static parse_rec save_actor;
static parse_rec save_obj;
parse_rec *save_lnoun=NULL;



void exec(parse_rec *actor, int vnum,
	  parse_rec *lnoun, word prep, parse_rec *iobj)
{

  cmd_saveable=0;
  pronoun_mode=!PURE_PROSUB;

  if (vnum==verb_code(ext_code[wagain]) && lnoun[0].info==D_END
      && iobj->info==D_END && 
      (actor->info==D_END || actor->obj==save_actor.obj) )
    if (save_lnoun==NULL) {
      rfree(lnoun);
      sysmsg(186,
 	"You can't use AGAIN until you've entered at least one command.");
      return;
    } else {
	memcpy(actor,&save_actor,sizeof(parse_rec));
	vnum=save_vnum;
	prep=save_prep;
	memcpy(iobj,&save_obj,sizeof(parse_rec));
	rfree(lnoun);
	lnoun=save_lnoun;save_lnoun=NULL;
      }
  else 
    realverb=input[vp];


  runverbs(actor,vnum,lnoun,prep,iobj);

  if (cmd_saveable) {
    if (save_lnoun!=NULL) rfree(save_lnoun);

    memcpy(&save_actor,actor,sizeof(parse_rec));
    save_vnum=vnum;
    save_lnoun=lnoun; lnoun=NULL;
    save_prep=prep;
    memcpy(&save_obj,iobj,sizeof(parse_rec));
  } 
  else 
    rfree(lnoun);
}
