/*  Runverb.c-- handles built-in verbs                    */
/* Copyright 1996,97    Robert Masenten                   */
/*                                                        */
/* This is part of the source for AGiliTy, the (Mostly) Universal  */
/*       AGT Interpreter                                           */


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

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



/* ------------------------------------------------------------------- */
/* VERBS: Functions that implement the predefined verbs.   */
/* ------------------------------------------------------------------- */

void v_look()
{
  do_look=1;
}


static void v_go(int dir)
/* 1=N, etc. */
{
  int newloc, tmploc;
  int i;
/*  bool has_seen;*/

  dir--;

  tmploc=loc;
  newloc=room[loc].path[dir];
  if (newloc<first_room)
    {sysmsg(13,"$You$ can't go that way.");return;}
  if (newloc>exitmsg_base)   /* Customized error messages */
    { msgout(newloc-exitmsg_base,1);
      return;}
  if (newloc>maxroom)
    {writeln("GAME FILE ERROR: Invalid room number.");
     return;}
 /* Then need to check for hostile creatures */
  /* (If we are going back to the room we came from and not PURE_HOSTILE
     is set, then we don't need to check this) */
  if (dir!=12 && (PURE_HOSTILE || newloc!=oldloc+first_room))
    creatloop(i)
      if (creature[i].location==loc+first_room &&
	  creature[i].hostile) {
	parse_rec tmpcreat;
	tmpobj(&tmpcreat);
	curr_creat_rec=&tmpcreat;
	curr_creat_rec->obj=i+first_creat;
	sysmsg(14,"The $c_name$ blocks $your$ way.");
	curr_creat_rec=NULL;
	return; }

/*  has_seen=room[newloc-first_room].has_seen;*/
  goto_room(newloc-first_room);
  if (dir!=12 && newloc!=tmploc+first_room)  /* SPECIAL */
    oldloc=tmploc;  /* Can backtrack as long as not from special */
  if (dir==12 && special_ptr[loc].size>0)
    /* need to print special of NEW room */
    runptr(loc,special_ptr,"INTERNAL ERROR: Invalid special ptr",0);

  if (tmploc==loc && dir==12) /* SPECIAL that sends us nowhere */
    do_look=0;
}




static void v_noun(int vc,int dobj)
/* PUSH, PULL, TURN, PLAY, CHANGE_LOCATIONS */
{
  if (vc==0 && !it_pushable(dobj)) 
    {sysmsg(172,"$You$ can't push the $noun$.");return;}
  if (vc==1 && !it_pullable(dobj))
    {sysmsg(175,"$You$ can't pull the $noun$.");return;}
  if (vc==2 && !it_turnable(dobj))
    {sysmsg(166,"$You$ can't turn the $noun$.");return;}  
  if (vc==3 && !it_playable(dobj))
    {sysmsg(178,"$You$ can't play the $noun$.");return;}
  if (dobj==room[loc].key) { /* SPECIAL triggered */    
    v_go(13);
    return;
  }
  if (vc==4) 
    {sysmsg(181,"Nothing happens.");return;}
 /* otherwise, print out relevent description. */ 
  if (vc==0)
    runptr(dobj-first_noun,push_ptr,
	   "$You$ push the $noun$ for a while, but nothing happens.",172);
  if (vc==1)
    runptr(dobj-first_noun,pull_ptr,
	   "$You$ pull the $noun$ a bit, but nothing happens.",174);
  if (vc==2)
    runptr(dobj-first_noun,turn_ptr,
	   "$You$ turn the $noun$, but nothing happens.",165);
  if (vc==3)
    runptr(dobj-first_noun,play_ptr,
	   "$You$ play the $noun$ for a bit, but nothing happens.",177);  
}

static void v_talk(int vc,int dobj)
{
  if (!visible(dobj))
    {sysmsg(3,"Who $are$ $you$ addressing?");return;}
  if (!tcreat(dobj))
    {sysmsg(156,"That isn't animate.");return;}
  if (vc==0)
    runptr(dobj-first_creat,
	   talk_ptr,"$Your$ conversational gambit is ignored.",159);
  if (vc==1)
    runptr(dobj-first_creat,ask_ptr,"$You$ get no answer.",163);
}

static void v_examine(int dobj)
{
  it_describe(dobj);
}

static void v_view(int dobj)  /* VIEW a picture */
{
  int i;

  if (tnoun(dobj) && noun[dobj-first_noun].pict!=0) 
    pictcmd(1,pictable[noun[dobj-first_noun].pict-1]);
  else if (tcreat(dobj) && creature[dobj-first_creat].pict!=0)
    pictcmd(1,pictable[creature[dobj-first_creat].pict-1]);
  else if (dobj==-ext_code[wscene] && room[loc].pict!=0)
    /* View the room picture */
    pictcmd(1,pictable[room[loc].pict-1]);
  else {  /* room.PIX_bits */
    if (dobj<0)
      for(i=0;i<maxpix;i++)  /* Check them all */
	if (dobj==-pix_name[i] && 
	    (room[loc].PIX_bits & (1<<i) ) ) 
	  {pictcmd(2,i);return;}
    writeln("That can't be VIEWed here.");
  }
}


static void v_read(int dobj)
{
  if (!tnoun(dobj) || !noun[dobj-first_noun].readable) {
    sysmsg(134,
	   "$You$ can't read the $noun$, so instead $you$ just examine it.");
    it_describe(dobj);
    return;}
  if (text_ptr[dobj-first_noun].size>0)
    runptr(dobj-first_noun,text_ptr,"INTERNAL ERROR: Invalid read pointer",0);
  else 
    runptr(dobj-first_noun,noun_ptr,"$You$ learn nothing new.",193);
}


static void v_eat(int vc,int dobj)
{
  if (!tnoun(dobj))
    {sysmsg(124,"That can't be consumed.");return;}
  if (vc==0 && !noun[dobj-first_noun].edible)
    {sysmsg(124,"$You$ can't eat that.");return;}
  if (vc==1 && !noun[dobj-first_noun].drinkable)
    {sysmsg(127,"$You$ can't drink that.");return;}

  sysmsg(128,"$You$ $verb$ the $adjective$ $noun$.");

  if (noun[dobj-first_noun].movable) it_destroy(dobj);
  if (noun[dobj-first_noun].poisonous) {
    sysmsg(129,"Unfortunatly, it was poisonous.");
    deadflag=1;
  }
}

static int can_wear(parse_rec *objrec)
/* assumes objrec is in the noun range */
{
  if (noun[objrec->obj-first_noun].weight>100)
    {sysmsgd(30,"The $noun$ $n_is$ far too heavy to wear.",objrec);
     return 0;}
  else if (totwt+noun[objrec->obj-first_noun].weight>100)
    {sysmsgd(31,"$You$ $are$ already loaded down with too much weight as"
	     " it is.", objrec);
     return 0;}
  return 1;
}

static int can_carry(parse_rec *objrec)
/* assumes objrec is in the noun range */
{
  if (noun[objrec->obj-first_noun].weight>100)
    {sysmsgd(30,"The $noun$ $n_is$ far too heavy to carry.",objrec);
     return 0;}
  else if (totwt+noun[objrec->obj-first_noun].weight>100)
    {sysmsgd(31,"$You$ $are$ already carrying too much weight as it is.",
	     objrec);
     return 0;}
  if (noun[objrec->obj-first_noun].size>100) {
    sysmsgd(32,"The $noun$ $n_is$ to big and bulky to pick up.",
	    objrec);
    return 0;}
  if (totsize+noun[objrec->obj-first_noun].size>100) {
    sysmsgd(33,"$You$ $are$ carrying too much to also carry the $noun$.",
	    objrec);
    return 0;}
  return 1;
}

static int v_get(parse_rec *objrec)
{
  int cnt,i;
  int obj;
  parse_rec tmp;

  obj=objrec->obj;

  /* If there is a hostile creature in the room and PURE_GETHOSTILE isn't
     set, then don't let the player pick up anything */
  if (!PURE_GETHOSTILE) 
    creatloop(i)
      if (creature[i].location==loc+first_room &&
	  creature[i].hostile)
	{sysmsg(14,"The creature blocks $your$ way.");
	  return 0; }  

  if (objrec->info==D_ALL) {
    cnt=0;
    tmpobj(&tmp);
    nounloop(i) 
      if (noun[i].location==loc+first_room && noun[i].movable) {
   /* Need to add weight/size check */
	tmp.obj=i+first_noun;
	if (can_carry(&tmp)) {
	  get_obj(i+first_noun);
	  sysmsgd(8,"$You$ pick up the $adjective$ $noun$",&tmp);
	}
	cnt++;
	}
    if (cnt==0) {
      sysmsg(24,"There doesn't seem to be anything here to take.");
      return 0;
    } else return 1;
  }
  if (!tnoun(obj) || !noun[obj-first_noun].movable)
    {sysmsgd(29,"$You$ can't pick the $noun$ up.",objrec);return 0;}
  if (it_loc(obj)==1)
    {sysmsgd(27,"$You$ already have the $noun$.",objrec);return 1;}
  if (!can_carry(objrec)) return 0;
  get_obj(obj);
  sysmsg(8,"$You$ pick up the $adjective$ $noun$.");
  return 1;
}

static int v_remove(parse_rec *objrec)
{
  int i,j;
  integer obj;
  parse_rec tmp_obj;

  obj=objrec->obj;
  if (objrec->info==D_ALL) {
    if (player_worn==0) {
      sysmsg(46,"$You$ $are$ not wearing anything.");
      return 0;}
    tmpobj(&tmp_obj);
    safecontloop(i,j,1000) {
	if (PURE_WEAR) drop_obj(i);
	else it_move(i,1);  /* Really need to check to make sure
				  we haven't exceeded weight requirement 
				  here */
	tmp_obj.obj=i;
	sysmsgd(9,"$You$ take off the $noun$",&tmp_obj);
      }
    return 1;
  }  
  if (it_loc(obj)!=1000) 
    {sysmsg(46,"$You$ $are$ not wearing that.");return 0;}
  sysmsg(9,"$You$ take off the $noun$.");
  if (PURE_WEAR) drop_obj(obj);  /* Required to be consistent w/ AGT */
  else v_get(objrec);  /* (trap can_carry problems) */
  return 1;
}

static void v_drop(int dobj)
{
  int i, j;
  parse_rec tmp;

  if (dobj==ALL_MARK) {
    tmpobj(&tmp);
    if (player_contents==0) 
      sysmsg(45,"$You$ don't have anything to drop.");
    else safecontloop(i,j,1) {
	tmp.obj=i;      
	drop_obj(i);
	sysmsgd(9,"$You$ drop the $noun$",&tmp);
    }
    return;
  }
  if (!it_possess(dobj))
    {sysmsg(47,"$You$ don't have that.");return;}
  if (it_loc(dobj)==1000) {
    writeln("(Taking it off first)");
  }
  sysmsg(9,"$You$ drop the $noun$.");  
  drop_obj(dobj);
}

static void v_wear(parse_rec *objrec)
{
  int i, cnt;
  parse_rec tmp;
  int obj;

  obj=objrec->obj;
  if (objrec->info==D_ALL) {
    cnt=0;
    tmpobj(&tmp);
    nounloop(i) 
      if (noun[i].location!=1000 && visible(i+first_noun) && 
	  noun[i].wearable) {
	tmp.obj=i+first_noun;
   /* Need to add weight/size check */
	if (can_wear(&tmp)) {
	  it_move(i+first_noun,1000);
	  sysmsgd(8,"$You$ wear the $adjective$ $noun$",&tmp);
	}
	cnt++;
	}
    if (cnt==0) 
      sysmsg(24,"There doesn't seem to be anything $you$ can wear here");
    return;
  }
  if (!tnoun(obj) || !noun[obj-first_noun].wearable)
    {sysmsg(10,"$You$ can't wear that.");return;}
  if (it_loc(obj)==1000)
    {sysmsgd(27,"$You$ already have the $noun$.",objrec);return;}
  if (!can_wear(objrec)) return;
  sysmsg(42,"$You$ put on the $noun$.");
  it_move(obj,1000);
}

static int do_lock(uchar l_or_u,int dobj,int iobj)
{
  int dnoun;

  if (!it_lockable(dobj))
    { sysmsg(108,"The $noun$ can't be $verb$ed.");return 0;}
  if (it_locked(dobj)!=l_or_u) 
    { sysmsg( (l_or_u ? 109 : 119),"The $noun$ is already $verb$ed"); 
      return 0;}
  if (dobj<0) { /* i.e. a door */
    sysmsg( (l_or_u ? 104 :115),"$You$ try to $verb$ the $noun$, but fail.");
    return 0;}
  dnoun=dobj-first_noun;
  if (iobj==0)
    { sysmsg( (l_or_u ? 106 : 116), 
	       "$You$ will need to use something to do that."); return 0;}
  if (iobj!=noun[dnoun].key) 
    { sysmsg( (l_or_u ? 111 : 121), "The $object$ doesn't fit."); return 0;}
  noun[dnoun].locked=!l_or_u;   
  return 1;
}

static void v_lock(uchar l_or_u,int dobj,int iobj)
/* First argument indicates lock or unlock-- 0=lock, 1=unlock */
{
  if (!do_lock(l_or_u,dobj,iobj)) return;
 /* Need to fix these messages: */
  sysmsg( (l_or_u ? 112 : 122), "$You$ $verb$ the $noun$ with the $object$.");
}

static void v_open(int dobj,int iobj)
/* OPEN ... WITH ... */
{
  int dnoun;

  dnoun=dobj-first_noun;
  if (it_open(dobj)) 
    { sysmsg(78,"The $noun$ is already open."); return;}
  if (!tnoun(dobj) || !noun[dnoun].closable)
    { sysmsg(77,"$You$ can't open the $noun$.");return;}
  if (iobj!=0)  /* Need to do unlock action */
    {
      if (!do_lock(1,dobj,iobj)) return;
      /* If something goes wrong, return */
    }
  if (noun[dnoun].lockable && noun[dnoun].locked)
    { sysmsg(79,"It is locked.");return;}
  noun[dnoun].open=1;
  if (iobj!=0)   /* Obviously these messages need improvement */
    sysmsg(81,"$You$ have opened the $noun$ with the $object$.");
  else sysmsg(82,"$You$ have opened the $noun$.");
  if (noun[dnoun].contents!=0)
    sysmsg(187,"Inside, $you$ see the following:");
  print_contents(dobj,1);
}

static void v_close(int dobj)
{
  int dnoun;

  dnoun=dobj-first_noun;
  if (!it_open(dobj)) {
    sysmsg(88,"The $noun$ is already closed.");return;}
  if (!tnoun(dobj) || !noun[dnoun].closable) {
    sysmsg(87,"$You$ can't close the $noun$."); return;}
  noun[dnoun].open=0;
  sysmsg(89,"$You$ have closed the $noun$.");
}



static void v_light(int newstate,int dobj)
{
  if (!tnoun(dobj) || !noun[dobj-first_noun].light) 
    {sysmsg(newstate?135:140,"$You$ can't $verb$ the $noun$.");return;}
  dobj-=first_noun;
  if (noun[dobj].on==newstate) {
    if (newstate)
      sysmsg(136,"The $noun$ $n_is$ aleady lit.");
    else sysmsg(141,
	   "The $noun$ $n_is$n't lit, so you can't extinguish $n_indir$");
    return;}
  noun[dobj].on=newstate;
  if (newstate) 
    sysmsg(138,"The $noun$ $n_is$ now lit.");
  else sysmsg(143,"The $noun$ $n_is$ no longer lit.");
}

static void v_turn(word prep, int dobj)
{
  int newstate;  /* 1=on, 0=off */

  newstate=(prep==ext_code[won]); /* ON or OFF ? */
  if (!it_turnable(dobj) && !nounattr(dobj,light)) 
    {sysmsg(newstate?135:140,
	    "$You$ can't turn the $noun$ $prep$.");return;}
  if (!tnoun(dobj))  /* This should be redundant */
    {writeln("INTERNAL ERROR: Non-noun turn on/off not supported");
     return;}
  dobj-=first_noun;
  if (noun[dobj].on==newstate)
    {sysmsg(newstate?137:142,"The $noun$ is already $prep$.");return;}
  noun[dobj].on=newstate;
  sysmsg(newstate?139:144,"The $noun$ is now $prep$.");
}






static void v_attack(uchar missile,parse_rec *targrec,parse_rec *weprec)
/* Missile=1 if actually firing a weapon. */
{
  int targ, wep;
  targ=targrec->obj;
  wep=weprec->obj;

  /* The following fix really belongs in the parser, but it might
     break some games to do this translation before running metacommands */
  if (missile && targ==0 && tcreat(wep) )  /* SHOOT <creature> */
    {targ=wep; targrec=weprec; wep=0; }

  curr_creat_rec=targrec;  /* So error messages will print properly */
  if (wep>0 && !player_has(wep)) {
    sysmsgi(98,"(Getting the $object$ first)",weprec);
    if (!v_get(weprec)) return;
  }
  if ((targ>0 && !tcreat(targ)) || targ<0)
    { sysmsg(missile ? 90 : 93,
	     "It only makes sense to attack living things.");
    return;}
  if (missile) 
    if (wep==0) 
      { sysmsg(94,"It's not clear what $you$ want to shoot with.");return;}
    else if (!tnoun(wep) || !noun[wep-first_noun].shootable) {     
      sysmsgi(96,"The $object$ doesn't seem to be able to fire.",weprec);
      return;}
    else if (noun[wep-first_noun].num_shots<=0) { 
      sysmsgi(97,"The $object$ is out of ammunition.",weprec);
      return;}
    else noun[wep-first_noun].num_shots--;

  if (targ==0)
    if (!missile) {sysmsg(90,"Attack what???");return;}
    else {sysmsg(188,"$You$ fire a shot into the air.");return;}

  if (wep==0) {
    sysmsg(creature[targ-first_creat].hostile?91:92,
	   "$You$ attack the $noun$ with $your$ bare hands, but it "
	   "evades $your$ attack and fights its way free.");
    return;
  }
 
  if (creature[targ-first_creat].weapon==wep)
    {
      sysmsgd(creature[targ-first_creat].hostile?99:101,
	      "$You$ kill the $noun$; it vanishes in a cloud of red smoke.",
	      targrec);
      it_destroy(targ);
      if (!missile) drop_obj(wep);
      return; 
    }
  else {
    if (!missile)
      sysmsg(creature[targ-first_creat].hostile?51:54,
	     "$You$ strike at the $noun$ with the $object$, but $your$ "
	     "weapon bounces off it harmlessly");
    else 
      sysmsg(creature[targ-first_creat].hostile?100:102 ,
	     "$You$ fire at the $noun$ with the $object$, but $your$ "
	     "shots don't seem to have any effect.");
    if (missile) drop_obj(wep);      
    if (creature[targ-first_creat].hostile &&
	++creature[targ-first_creat].counter>=
	    creature[targ-first_creat].threshold)
      {
	sysmsg(16,"The $noun$ counterattacks! It fights viciously and $you$ "
	       "$are$ unable to defend $your$self against it.");
	deadflag=1;
      }
  }
}   

/* child_proc is true if v_put is being called by v_put, and so
   shouldn't print success messages */
static bool v_put(int dobj,word prep,int iobj,bool child_proc)
{
  int i, szsum;
  bool in_prep;

  in_prep=(prep==ext_code[win] || prep==ext_code[winto] 
	    || prep==ext_code[winside]);
  
  if (prep==0 || iobj==0) 
    { v_drop(dobj); return 1;}
  if (!it_possess(dobj))
    {sysmsg(47,"$You$ don't have the $noun$.");return 0;}
  if (!tnoun(dobj)) 
    {sysmsg(10,"$You$ can't do that with the $noun$.");return 0;}
  if (tcreat(iobj))
    {sysmsg(189,"The $object$ doesn't want it.");return 0;}
  if (!tnoun(iobj))
    {sysmsg(64,"$You$ can't put something $prep$ the $object$.");return 0;}
  if (dobj==iobj) 
    {sysmsg(62,"$You$ can't put an object $prep$ itself.");return 0;}
  if (!it_open(iobj) && in_prep) 
    {sysmsg(65,"The $object$ isn't open.");return 0;}
  if (in_prep) { /* PUT IN */
    if (it_loc(dobj)==1000) {
      writeln("(Taking it off first)");}
    szsum=0; /* Make sure iobj has room for dobj */
    contloop(i,iobj)
      if (tnoun(i)) szsum+=noun[i].size;
    szsum+=noun[dobj-first_noun].size;
    if (szsum>noun[iobj-first_noun].size)
      {sysmsg(66,"$You$ can't fit the $noun$ into the $object$.");return 0;}
    it_move(dobj,iobj);
    }
  else {
    int parent;

    parent=it_loc(iobj);
    if (!troom(parent)) {
      if (!v_put(dobj,ext_code[win],parent,1)) return 0;
    }
    else { 
      if (it_loc(dobj)==1000) 
	writeln("(Taking it off first)");
      drop_obj(dobj);
    }
    dobj-=first_noun;
    assert(noun[dobj].pos_prep==0);  /* v_put should have ensured this */
    noun[dobj].pos_prep=prep;
    noun[dobj].pos_name=it_name(iobj);
    if (iobj>0) noun[dobj].nearby_noun=iobj;
  }
  if (!child_proc)
    sysmsg(67,"You place the $noun$ $prep$ the $object$.");
  return 1;
}

static void v_throw(parse_rec *nounrec,word prep,parse_rec *objrec)
/* at, to, in, into, across, inside */
{
  /* Need to check to see what the preposition is-- if it is AT
     then we should send it to attack routine regardless. */
  if (tcreat(objrec->obj) || prep==ext_code[wat])
    v_attack(0,objrec,nounrec);
  else 
    v_put(nounrec->obj,prep,objrec->obj,0);
}



void v_inventory(void) 
{
  if (player_contents!=0) {
    sysmsg(130,"$You$ $are$ carrying:");
    print_contents(1,1);   /* obj=1=self, ind_lev=1 */
  } else sysmsg(131,"$You$ $are$ empty-handed.");    
  if (player_worn!=0) {
    sysmsg(132,"$You$ $are$ wearing:");
    print_contents(1000,1);
  }
}	





static void v_quit(void)
{
  if (yesno("Are you sure you want to quit?"))
    quitflag=1;
}

const char dirname[12][10]={"north","south","east","west",
			      "northeast","northwest","southeast","southwest",
			      "up","down","in","out"};

void v_listexit(void)
{
  int i, j,k;

  if (!islit()) {
    sysmsg(23,"It is too dark to see anything.");return;}
  j=k=0;
  for(i=0;i<12;i++)
    if (room[loc].path[i]!=0) k++;
  if (k==0) 
    writeln("There are no immediatly visible exits.");
  else {
    writestr("There are exits to ");
    for(i=0;i<12;i++)
      if (room[loc].path[i]!=0) {
	j++;
	if (j>1) writestr(", ");
	if (j>1 && j==k) writestr("or ");
	if (i<8) writestr("the ");
	writestr(dirname[i]);
      }
    writeln(".");
  }
}


static void v_yell(void)
{
  sysmsg(150,"YAAAAEEEEEEEEOOOOOOUUUUUAAAAHHHHHH!!!!!");
}



/* ------------------------------------------------------------------- */
/*  VERB EXECUTION AND GRAMMER CHECKING */







static int checkgram(int vb,int dobj,word prep, int iobj)
{
  int i;
 
 /* First of all, no constraints on dummy_verb grammer */
  if (vb>=BASE_VERB && vb<TOTAL_VERB) return 0;
     
  if (!(verbflag[vb]&VERB_TAKEOBJ) 
      && (dobj!=0 || iobj!=0 || prep>0) 
      && vb!=OLD_VERB+11) 
    {sysmsg(190,"$Verb$ doesn't take an object.");
     return -1;}
  if (prep>0) {
    for(i=preplist[vb];syntbl[i]!=0 && syntbl[i]!=prep;i++);
    if (syntbl[i]!=prep) { 
      sysmsg(190,"$Verb$ doesn't take $prep$ as a preposition.");
      return -1;}
  }
  if (iobj==ALL_MARK) {
    sysmsg(5,"You can't use ALL as an indirect object");return -1;}
  if (dobj==ALL_MARK && vb!=33 && vb!=41 && vb!=51 && vb!=52) {
    /* i.e. verb is not GET,DROP,WEAR,REMOVE */
    sysmsg(5,"You can't use ALL with '$verb$'.");return -1;}
  return 0;
}


static bool verify_scope(int vb,int dobj,word prep, int iobj)
     /* This checks to make sure that all of the objects are present */
{
  if (!(verbflag[vb]&VERB_TAKEOBJ)) return 1;
      /* No objects (and we've already checked the grammar in 
	 a previous routine) */

  if (vb==31 || vb==34)  /* TELL, ASK */
    return 1;  /* These verbs handle this themselves */

  if (dobj==0) {
    sysmsg(184,"What do $you$ want to $verb$?");
    return 0;
  }
  if (dobj!=ALL_MARK && !visible(dobj)) {
    sysmsg(3,"$You$ don't see any $noun$ here.");
    return 0;}

  if (prep!=0 && vb!=35) { /* verb 35 is TURN e.g. ON|OFF */
    if (iobj==0) {
      sysmsg(185,"What do $you$ want to $verb$ the $object$ $prep$?");
      return 0;
    }
    if (iobj!=ALL_MARK && !visible(iobj)) {
      sysmsg(4,"$You$ don't see any $object$ here.");
      return 0;
    }
  }
  return 1;
}


static void exec_verb_info(void)
{
  char *a,*b,*c;
  char buff[200];

  a=objname(dobj);
  b=objname(iobj);
  c=objname(actor);
  sprintf(buff,"\t\t]]%s, %s %s(%ld) %s %s(%ld)",c,dict[ syntbl[auxsyn[vb]] ],
	 a, dobj_rec->num, prep==0 ? "->" : dict[prep], b, iobj_rec->num);
  writeln(buff);
  rfree(a);rfree(b);rfree(c);
}

void exec_verb(void)
/* Execute both meta-commands and more normal commands */
/* May need tweaking for AGAIN and UNDO */
{
  int objswap;  /* 1=if iobj has been moved to dobj */
               /* (Done for metacommands when there is an iobj but no dobj) */
  word m_noun; /* Meta-command word values for these. */
  bool turndone;
  parse_rec *save_dobj_rec;
  int save_vb;  /* Remember what the real verb is; this is used
		   to handle QUIT processing */

  save_vb=vb;

  if (DEBUG_EXEC_VERB) exec_verb_info();

  do_disambig=0;  /* We're doing this for real */

  cmd_saveable=1;

  was_metaverb=(verbflag[vb] & VERB_META) 
    && actor==0 && dobj==0 && prep==0 && iobj==0;

  /* The following is purely for metacommands */
  if (dobj==0 && dobj_rec->info!=D_NUM && iobj!=0) {
    dobj=iobj;
    save_dobj_rec=dobj_rec;
    dobj_rec=iobj_rec;
    objswap=1;
  } else objswap=0;
  
  turndone=0;
/* Now check metacommands */
  if (have_meta)  {
    if (DEBUG_AGT_CMD)
      debugout("*** Scanning: ANY metacommands ****\n");
    /* ANY metacommands: */
    supress_debug=!debug_any;
    if ((PURE_METAVERB || !was_metaverb) 
	&& 2==scan_metacommand(0,0,0,0,0,0,0)) 
      turndone=1;
    supress_debug=0;
  }

  actor_in_scope|=visible(actor); /* Set up for ActorWasPresent */
  
  if (have_meta && !turndone) {
    if (DEBUG_AGT_CMD)
      debugout("*** Scanning: VERB metacommands ****\n");
    beforecmd=1; /* This is for 1.8x support */
    /* Normal treatment */
    m_noun=it_name(dobj);
    if (!turndone 
	&& 2==scan_metacommand(actor,vb,m_noun,prep,it_name(iobj),
			       it_adj(dobj),it_adj(iobj)))
      turndone=1;
  }

  /* if (objswap) {dobj=0;dobj_rec=save_dobj_rec;} */

  if (!turndone && DEBUG_AGT_CMD)
    debugout("*** Executing Built-in Verbs ****\n");  
  
  if (actor>0 && !turndone) {     
    if (!actor_in_scope)
      sysmsg(196,"I don't see $you$ $are$ trying to address here.");
    else 
      sysmsg(192,"$Name$ doesn't want to.");
  }
  else if (!turndone) {  
    /* Execute normal verbs: check grammer and then call */
    if (!ignore_grammer) {
      if (!objswap)
	if (checkgram(vb,dobj,prep,iobj)==-1) return;
      else if (checkgram(vb,0,prep,iobj)==-1) return;
    }

    if (!verify_scope(vb,dobj,prep,iobj)) return;

    if (vb<13 && vb>0) v_go(vb);
    else switch(vb) {
	
	case 14: v_throw(dobj_rec,prep,iobj_rec);break;
	case 29: v_put(dobj,prep,iobj,0);break;

       /* _with_ verbs */
	case 15: v_open(dobj,iobj);break;
	case 16: v_close(dobj);break;
	case 17: v_lock(0,dobj,iobj);break; /* LOCK */
	case 18: v_lock(1,dobj,iobj);break; /* UNLOCK */
	case 36: v_noun(0,dobj);break; /* PUSH (WITH);Ignore indir object*/

	case 26: v_attack(0,dobj_rec,iobj_rec);break;
	case 49: 
	  if (prep==ext_code[wwith])
	    v_attack(1,dobj_rec,iobj_rec); /* SHOOT WITH */
	  else 
	    v_attack(1,iobj_rec,dobj_rec); /* SHOOT AT */
	  break;

       /* _about_ verbs */
	case 31: v_talk(0,dobj);break; /* TELL */
	case 34: v_talk(1,dobj);break; /* ASK */	  

	case 28: v_yell();break;
	case 27: 
	  sysmsg(149,"Time passes...");
	  break;            /* wait */
	case 55: v_go(13);break;  /* magic_word */

	case 19: v_look();break;
	case 50: runptr(loc,help_ptr,
			"Sorry, you're on your own here.",2);
	  break;   /* HELP */

	case 32: v_inventory();break;
	case 56: v_view(dobj); break;  /* VIEW */
	case 35:
	  if (prep>0) 
	    v_turn(prep,dobj); /* TURN ON|OFF */
	  else 
	    v_noun(2,dobj); /* TURN */
	  break;
	case 20: v_examine(dobj);break;
	case 22: v_read(dobj);break;
	case 23: v_eat(0,dobj);break;  /* EAT */
	case 24: v_eat(1,dobj);break;  /* DRINK */
	case 37: v_noun(1,dobj);break;   /* PULL  */
	case 38: v_noun(3,dobj);break;  /* PLAY */
	case 47: v_light(1,dobj);break;   /* LIGHT */
	case 48: v_light(0,dobj);break;   /* EXTINGUISH */
	case 21: v_noun(4,dobj);break; /* Change Location */

	case 51: v_wear(dobj_rec);break;
	case 33: v_get(dobj_rec);break;    /* ? */
	case 52: v_remove(dobj_rec);break;
	case 41: v_drop(dobj);break;
	  
	case 25: print_score();break;
	case 30: cmd_saveable=0; v_quit();break;
	  /* case 40:  SHOW --> default message */
        case 39: 
	case 42: v_listexit();break;
	case 43: cmd_saveable=0; verboseflag=0;  /* BRIEF */
          writeln(
	   "[Now in BRIEF mode (room descriptions will only be printed"
		  " when they are entered the first time)]");
	  break;
	case 44:
	  cmd_saveable=0; verboseflag=1;v_look();  /* VERBOSE */
	  writeln("[Now in VERBOSE mode (room descriptions will be"
		  " printed every time you enter a room)]");
	  break;
	case 45: cmd_saveable=0;savegame();break;
	case 46: cmd_saveable=0;doing_restore=1; return;break;
	case 53: cmd_saveable=0;script(1);break;
	case 54: cmd_saveable=0;script(0);break;
	case 58:         /* INSTRUCTIONS */
	  agt_clrscr();
	  print_instructions(hold_game_name); 
	  close_ins_file();
	  break;
	case (OLD_VERB+1): cmd_saveable=0; /* RESTART */
	  if (restart_state==NULL) 
	   writeln("Sorry, too little memory to support RESTART.");
	  else {doing_restore=2;return;}
	case (OLD_VERB+4): cmd_saveable=0;    /* NOTIFY */
	  notify_flag=!notify_flag; 
	  if (notify_flag) writeln("Score notification is now on.");
	 else writeln("Score notification is now off."); break; 
	case (OLD_VERB+5): listexit_flag=1;
	  writeln("[LISTEXIT mode on: room exits will be listed.]");
	  break; /* LISTEXIT ON */
	case (OLD_VERB+6): listexit_flag=0;
	  writeln("[LISTEXIT mode off: room exits will not be listed.]");
	  break; 
	case (OLD_VERB+7):  /* AGILDEBUG */
	  if (debug_mode) get_debugcmd();
	  else writeln("Nice try.");
	  break;
	case (OLD_VERB+8): /* LOG, LOG ON */
	  logon();break;
	case (OLD_VERB+9): /* LOG OFF */
	  if (logflag&2) break; /* We're replaying; ignore. */
	  if (logflag&1) fclose(log_out);
	  logflag=0;break;
	case (OLD_VERB+10): /* REPLAY n */
	  replay(dobj_rec->num);break;
	case (OLD_VERB+11): /* REPLAY STEP */
	  replay(-1);break;
	case (OLD_VERB+12): /* MENU */
	  if (verbmenu==NULL) {
	    writeln("Sorry, but menus are not supported by this game.");
	    menu_mode=0;
	    break;
	  }
	  if (freeze_mode) {
	    writeln("Sorry, but that is not allowed.");
	    break;
	  }
	  menu_mode=!menu_mode;
	  break;
	case 57: /* AFTER ?!? */
	  writeln("INTERNAL ERROR: Invalid execution of AFTER");
	  break;
	case (OLD_VERB+14): /* SOUND ON */
	  musiccmd(8,0);break;
	case (OLD_VERB+15): /* SOUND OFF */
	  musiccmd(9,0);break;
        case (OLD_VERB+16):  /* INTRO */
	  agt_clrscr();
	  print_descr(intro_ptr,1);
	  break;
	default: sysmsg(185,"Don't know how to $verb$ here...");
	  return;
	}
	}
  /* (if (dobj==0 && dobj_rec->info!=D_NUM && iobj!=0) 
    {dobj=iobj;save_dobj_rec=dobj_rec; dobj_rec=iobj_rec;}*/

  if (aver>=AGT15 && !quitflag && !endflag) {
    if (DEBUG_AGT_CMD)
      debugout("*** Scanning: AFTER metacommands ****\n");
    /* AFTER metacommands: */
    supress_debug=!debug_any;
    if ((PURE_METAVERB || !was_metaverb) && 
	2==scan_metacommand(0,57,0,0,0,0,0)) 
      turndone=1;
    supress_debug=0;

    /* In AGT 1.8x, run aftercommand verb metacommands. */
    /* (This is the most serious flaw in 1.82/1.83; it drastically the 
       semantics of metacommand execution from the earlier formats) */
    if (aver>=AGT182 && aver<=AGT18MAX && !quitflag && !turndone) {
      if (DEBUG_AGT_CMD)
	debugout("*** Scanning (after): VERB metacommands ****\n");
      beforecmd=0; 
      /* Normal treatment */
      m_noun=it_name(dobj);
      scan_metacommand(actor,vb,m_noun,prep,it_name(iobj),
			       it_adj(dobj),it_adj(iobj));
    }
  }

  /* If the player really typed 'q' and we generated an "EndGame"
     metacommand, then really quit. (usually it just gives the
     "restart, restore, undo, quit..." message */
  if (save_vb==30 && endflag) quitflag=1;

}






/* We need to be able to handle both NOUN and OBJECT searches */
/* If obj==0, then we are doing a noun search, otherwise we are doing
  an object search */
/* Return the disambiguation score;
     0 if the object doesn't trigger anything
     1000 if it runs an action token or built in verb.
     Other values may be returned if an ErrMessage token is encountered.
     500 is the cutoff for ALL expansion.
  */

int check_obj(parse_rec *act, int verbid, 
	       parse_rec *dorec, word prep, parse_rec *iorec)
{
  int result;
  parse_rec tmp;
 
  if (iobj==0) 
    do_disambig=1; /* Disambiguating dobj */
  else  
    do_disambig=2; /* Disambiguating iobj */

  actor=act->obj; actor_rec=act;
  /* The xobj_rec don't really matter */
  dobj=dorec->obj; dobj_rec=dorec; 
  if (iorec==NULL) {
    iobj=0;
    tmpobj(&tmp);
    tmp.obj=0;
    iobj_rec=&tmp;
  } else {
    iobj=iorec->obj; iobj_rec=iorec;
  }

  disambig_score=0;
  if (have_meta) {
    beforecmd=1;
    supress_debug=!debug_disambig;    
    result=scan_metacommand(actor,verbid,it_name(dobj),prep,it_name(iobj),
			    it_adj(dobj),it_adj(iobj));
    supress_debug=0;   
    switch (result)
      {
      case -2:return DISAMBIG_SUCC; /* We matched with something */
      case 0:
      case 1:break;  /* Nothing matched, but we still need to check
			built-in verbs */
      case 2:return disambig_score; /* End of turn, no match */
      }
  }

  if (verbid>=BASE_VERB) return disambig_score;

  /* Check built-in verbs here */
  switch(verbid) {
    case 14: /* THROW dobj prep iobj */
    case 29: /* PUT dobj prep iobj */ 
      if (do_disambig==2 && visible(iobj)) return DISAMBIG_SUCC;
      /* ... fall through to next case ... */ 
    case 41: /* DROP */
      if (do_disambig==1 && it_possess(dobj)) return DISAMBIG_SUCC;
      break;

    case 49: /* SHOOT ... AT or WITH ... */
      if (prep==ext_code[wwith]) {
	if (do_disambig==1 && tcreat(dobj) ) return DISAMBIG_SUCC;
	else if (do_disambig==2 && it_possess(iobj) && tnoun(iobj) 
	    && noun[iobj-first_noun].shootable )
	  return DISAMBIG_SUCC;
      } else {         /* prep!=wwith */
	if (do_disambig==2 && tcreat(iobj)) return DISAMBIG_SUCC;
	else if (do_disambig==1 && it_possess(dobj) && tnoun(dobj) 
		 && noun[dobj-first_noun].shootable) 
	  return DISAMBIG_SUCC;
      }
      break;

    case 26: /* ATTACK ... WITH ... */
      if (do_disambig==2 && it_possess(iobj)) return DISAMBIG_SUCC;
      if (do_disambig==1 && tcreat(dobj)) return DISAMBIG_SUCC;
      break;

    case 51:  /* WEAR */
      if (do_disambig==1)
	if (visible(dobj) && tnoun(dobj) && noun[dobj-first_noun].wearable
	    && it_loc(dobj)!=1000)
	  return DISAMBIG_SUCC;
      break;
    case 33: /* GET */
      if (do_disambig==1 && tnoun(dobj) 
	  && noun[dobj-first_noun].location==loc+first_room 
	  && noun[dobj-first_noun].movable) return DISAMBIG_SUCC;
      break;
    case 52:  /* REMOVE */
      if (do_disambig==1 && it_loc(dobj)==1000) return DISAMBIG_SUCC;
      break;
      
      /* The following could be better, but I don't want to give
	 away puzzles by accident */
    case 15: /* OPEN */
    case 17: /* LOCK */
    case 18: /* UNLOCK */
      if (do_disambig==2 && it_possess(iobj)) return DISAMBIG_SUCC;
      /* ... fall through ... */
    default: /* All other verbs just use visibility check */
      if (do_disambig==1 && visible(dobj)) return DISAMBIG_SUCC;
      if (do_disambig==2 && visible(iobj)) return DISAMBIG_SUCC;
    }
 
  if (have_meta && aver>=AGT182 && aver<=AGT18MAX) {
    beforecmd=0;
    supress_debug=!debug_disambig;
    if (scan_metacommand(actor,verbid,it_name(dobj),
			 prep,it_name(iobj),it_adj(dobj),it_adj(iobj))==-2) 
      {    
	supress_debug=0;
	return DISAMBIG_SUCC;
      }
    supress_debug=0;
  }

  return disambig_score; /* Failed to find a match */
}
