/*  exec.c-- player command execution utilities           */
/* Copyright 1996,97    Robert Masenten                   */
/*                                                        */
/* This is part of the source for AGiliTy                 */

#define global

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

#include "agtread.h"
#include "uagt.h"
#include "exec.h"

static bool pronoun_mode;



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

  strcpy(l_stat,room[loc].name);

  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';  /* 'Trinity style' status line */
     case 5:sprintf(r_stat,"Score: %ld",tscore);break;
     }
}



static word it_pronoun(int item, bool 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];
}



static int extract_number(char *s, int maxval)
/* Tries to convert s to a number, which it returns.
  If it fails, or if the number is not in the range 0..maxval,
  it returns -1. */
{
  int i;
  char *tail;

  i=strtol(s,&tail,10);
  if (tail!=NULL && *tail!='\0') return -1;
  if (i<0 || i>maxval) return -1;
  return i;
}



static void num_name_func(parse_rec *obj_rec,char *buff)
/* This is a subroutine to wordcode_match. */
/* It gives either a noun name or a number, depending. */
{
  if (obj_rec==NULL) strcpy(buff,"that");
  if (obj_rec->noun!=0)
    strcpy(buff,dict[obj_rec->noun]);
  else if (obj_rec->obj!=0)
    strcpy(buff,dict[it_name(obj_rec->obj)]);
  else if (obj_rec->info==D_NUM)
    sprintf(buff,"%ld",(long)obj_rec->num);
  else 
   strcpy(buff,"that"); /* We can try and hope */
}

static void get_adj( parse_rec *obj_rec, char *buff)
{
  if (obj_rec->adj!=0)
    strcpy(buff,dict[obj_rec->adj]);
  else 
    strcpy(buff,dict[it_adj(obj_rec->obj)]);
}

#define d2buff(i) {strcpy(fill_buff,dict[i]);return 1;}
#define num_name(obj_rec)  {num_name_func(obj_rec,fill_buff);return 1;}
#define youme(mestr,youstr) {strcpy(fill_buff,irun_mode?mestr:youstr);\
			       return 1;}

static int wordcode_match(char *varname,char *fill_buff) 
/* $ forms: 
   $verb$, $noun$, $adjective$, $prep$, $object$, $name$, 
   $n_pro$, $o_pro$, $n_indir$, $o_indir$, 
   $n_is$, $o_is$, $c_name$ 
   */
/* Return 0 if no match, 1 if there is  */
{
  int hold_val;

  if (strncasecmp(varname,"STR",3)==0) { /* String variable */
	hold_val=extract_number(varname+3,MAX_USTR);
	if (hold_val<1) return 0;    
	if (hold_val>MAX_USTR) {
	  writeln("GAME ERROR: Invalid string number");
	  return 0;}
	strcpy(fill_buff,userstr[hold_val-1]);
	return 1;
  }

/* d2buff is a macro that returns 1 */
  if (strcasecmp(varname,"verb")==0) 
    d2buff(input[vp]);  /* auxsyn[vb][0] */
  if (strcasecmp(varname,"noun")==0)
    num_name(dobj_rec);
  if (strcasecmp(varname,"object")==0)
    num_name(iobj_rec);
  if (strcasecmp(varname,"adjective")==0)
    {get_adj(dobj_rec,fill_buff);return 1;}
  if (strcasecmp(varname,"prep")==0)
    d2buff(prep);
  if (strcasecmp(varname,"name")==0)
    d2buff(it_name(actor));
  if (strcasecmp(varname,"n_pro")==0)
    d2buff(it_pronoun(dobj,0));
  if (strcasecmp(varname,"o_pro")==0)
    d2buff(it_pronoun(iobj,0));
  if (strcasecmp(varname,"n_indir")==0)
    d2buff(it_pronoun(dobj,1));
  if (strcasecmp(varname,"o_indir")==0)
    d2buff(it_pronoun(iobj,1));
  if (strcasecmp(varname,"n_is")==0)
    if (!it_plur(dobj)) d2buff(ext_code[wis])
    else d2buff(ext_code[ware]);
  if (strcasecmp(varname,"o_is")==0)
    if (!it_plur(iobj)) d2buff(ext_code[wis])
    else d2buff(ext_code[ware]);
  if (strcasecmp(varname,"c_name")==0) 
    num_name(curr_creat_rec); 
  if (strcasecmp(varname,"time")==0) 
    {time_out(fill_buff);return 1;}

  if (strcasecmp(varname,"you")==0 && pronoun_mode) 
    youme("I","you");
  if (strcasecmp(varname,"are")==0 && pronoun_mode) 
    youme("am","are");
  if (strcasecmp(varname,"you_obj")==0 && pronoun_mode)
    youme("me","you");
  if (strcasecmp(varname,"your")==0 && pronoun_mode)
    youme("my","your");
  return 0;  /* Don't recognize $word$ */
}


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

static tline fill_buff; /* Buffer to hold returned string */

static char *wordvar_match(char *varname,char match_type)
/* Match_type=='#' for variables, '$' for parsed words */
/* Possible # forms: #VARn#, #CNTn# */
/* See above for $ forms */ 
{
  int i, hold_val;

  if (match_type=='$') {
    i=wordcode_match(varname,fill_buff);
    if (i==0) return NULL;
    for(i=0;fill_buff[i]!='\0';i++)
      fill_buff[i]=tolower(fill_buff[i]);
   /* Now need to fix capitalization */
    switch (capstate(varname)) {
      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 (strncasecmp(varname,"VAR",3)==0)
      {
	hold_val=extract_number(varname+3,VAR_NUM);
	if (hold_val<0) return NULL;
	hold_val=agt_var[hold_val];
      }
    else if (strncasecmp(varname,"CNT",3)==0)
      {
	hold_val=extract_number(varname+3,CNT_NUM);
	if (hold_val<0) return NULL;
	hold_val=cnt_val(agt_counter[hold_val]);
      }
    else return NULL;
   /* Now to convert hold_val into a string */ 
    sprintf(fill_buff,"%d",hold_val);
  }
  return fill_buff;
}

static char  *format_line(char *s) 
/* Do $word$ substituations; return the result */
{
  char *t; /* The new string after all the substitutions. */
  char *p, *oldp;  /* Pointer to the original string */
  char wvar[15]; /* Holds read in text of $/#-variable name */
  int i,j;
  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=rmalloc(290);
 /* Note that I leave some margin here: t is 290 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' && i<200;p++)
    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 */
      for(j=0;*p!='\0' && *p!=fill_type && j<15;)
	wvar[j++]=*p++;
      if (j<15) {
	wvar[j]='\0';
	fill_word=wordvar_match(wvar,fill_type);
	if (fill_word==NULL) 
	  j=15; /*i.e. no match-- so just copy it verbatim */
	else if (fill_word[0]=='\0')  /* Empty string */
	  {  /* We need to eliminate a 'double space' in this case */
	    if ( ( oldp>s || isspace(*(oldp-1)) ) && 
		isspace(*(p+1))) 
	      p++;
	  }
	else
	  for(q=fill_word;*q!='\0';)
	    t[i++]=*q++;
      }
      if (j==15) { /* Don't recognize subsitution: backtrack */
	t[i++]=fill_type;
	p=oldp;   /* Go back and try again... */
      }
    }
    else t[i++]=*p;
  t[i]=0;
  t=rrealloc(t,i+1);
  return t;
}

static void lineout(char *s, bool nl)
{
  char *outstr;
  
  outstr=format_line(s);
  if (nl) writeln(outstr);
  else writestr(outstr);
  free(outstr);
}

void print_descr(descr_ptr dp,bool nl)
{
  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) );
  free_descr(txt);
  agt_par(0);
  agt_textcolor(7);
  textbold=0;
}

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]); 
    free_descr(txt);
    textbox(qptr,len,TB_BORDER|TB_CENTER);
    rfree(qptr);
  }
}


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

void sysmsg(int msgid,char *s)
/* Used to print out error messages, etc. 
   msgid should be the Master's Edition message number, if the standard
  messages have been redefined. A msgid of 0 means there is no ME counterpart.
  msgid 3 should probably *not* be redirected to avoid giving hints to
  the player as to what nouns exist in the game.
*/
{
  if (msgid!=0 && msgid<=NUM_ERR && err_ptr!=NULL && err_ptr[msgid-1].size>0) 
    print_descr(err_ptr[msgid-1],1); 
  else {
    pronoun_mode=1;
    lineout(s,1);
    pronoun_mode=!PURE_PROSUB;
  }
}

void sysmsgd(int msgid,char *s,parse_rec *new_dobjrec)
/* Front end for sysmsg w/alternative direct object */
{
  parse_rec *save_dobjrec;
  integer save_dobj;

  save_dobj=dobj; save_dobjrec=dobj_rec;
  dobj=new_dobjrec->obj; dobj_rec=new_dobjrec;
  sysmsg(msgid,s);
  dobj=save_dobj; dobj_rec=save_dobjrec;
}

void sysmsgi(int msgid,char *s,parse_rec *new_objrec)
/* Front end for sysmsg w/alternative direct object */
{
  parse_rec *save_objrec;
  integer save_obj;

  save_obj=iobj; save_objrec=iobj_rec;
  iobj=new_objrec->obj; iobj_rec=new_objrec;
  sysmsg(msgid,s);
  iobj=save_obj; iobj_rec=save_objrec;
}


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 bool check_answer(char *ans,int start,int 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) {
    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 */
}



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

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


long read_number(void)
{
  char *s, *err;
  long n;
  
  n=1;
  do {
    if (n!=1) 
      writestr("Please enter a *number*. ");
    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)
/* 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 sysmsg(msgid,msg);
}



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

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

  rmcnt=0;
  for(i=0;i<=maxroom-first_room;i++)
    if (room[i].seen) rmcnt++;
  if (score_mode%2==0) 
    sprintf(s,"You have visited %d locations (out of %d in the game)",rmcnt,
	    maxroom-first_room+1);
  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 || aver==AGT183A) 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)
{
  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(19,"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 */

  if (room[loc].autoverb!=0) {
    v0=verb_code(room[loc].autoverb);
    (void)scan_metacommand(0,v0,0,0,0,0,0);
  }
}




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


void newroom(void)
{
  int prevloc;

  do {
    prevloc=loc;
    if (do_look==1) look_room();
    creat_initdesc();
    if (do_look==1) {
      /* Need to print out pictures that can be viewed here, if any. */

    }
    do_look=0;

    if (do_autoverb) run_autoverb();
    if (!room[loc].seen) { /* This only runs on the first turn */
      room[loc].seen=1;
      tscore+=room[loc].points;
    }
    do_autoverb=0;
  } while (loc!=prevloc); /* an autoexec'd verb might move the player */
}


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

static void increment_turn(void)
{
  int i;
  parse_rec tmpcreat; 

  tmpobj(&tmpcreat); /* Used for creature messages */
  curr_creat_rec=&tmpcreat;

  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) 
      {       
	tmpcreat.obj=i+first_creat;
	if (++creature[i].timecounter>=creature[i].timethresh)
	  {   /* Creature attacks */
	    sysmsg(16,"The $c_name$ suddently attacks $you_obj$!");
	    sysmsg(creature[i].gender!=0 ? 17 : 18,
		   "      $You$ try to defend $your$self, but the $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_name$ seems to be getting angrier.");
      }
}


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)
      writeln("  [Your score just went up]");
    else if (old_score>tscore)
      writeln("  [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;
  }
}

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=actor0;
  vb=vnum;
  dobj=lnoun[0].obj;
  dobj_rec=lnoun;
  prep=prep0;
  iobj=iobj0->obj;
  iobj_rec=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;

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


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

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