/
Crimson2/alias/
Crimson2/area.tmp/
Crimson2/area.tmp/AnomalySpaceDock/
Crimson2/area.tmp/AnomalyStation/
Crimson2/area.tmp/AntHill/
Crimson2/area.tmp/ArcticTerrarium/
Crimson2/area.tmp/BuilderCity/
Crimson2/area.tmp/Dungeon/
Crimson2/area.tmp/MiningDock/
Crimson2/area.tmp/PipeSystem/
Crimson2/area.tmp/RattArea/
Crimson2/area.tmp/RobotFactory/
Crimson2/area.tmp/SilverDale/
Crimson2/area.tmp/StarshipFearless/
Crimson2/area.tmp/StationConduits/
Crimson2/area.tmp/TerrariumAlpha/
Crimson2/area.tmp/TerrariumBeta/
Crimson2/area.tmp/TestArea/
Crimson2/area.tmp/Void/
Crimson2/area/
Crimson2/area/AnomalySpaceDock/
Crimson2/area/AnomalyStation/
Crimson2/area/MiningDock/
Crimson2/area/PipeSystem/
Crimson2/area/SilverDale/
Crimson2/area/StationConduits/
Crimson2/area/Void/
Crimson2/board/
Crimson2/clone/
Crimson2/lib/
Crimson2/mole/
Crimson2/mole/mole_src/HELP/
Crimson2/player/
Crimson2/util/
Crimson2/wldedit/
Crimson2/wldedit/res/
/*
 * Written by B. Cameron Lesiuk, 1995
 * Written for use with Crimson2 MUD (written/copyright Ryan Haksi 1995).
 * This source is proprietary. Use of this code without permission from 
 * Ryan Haksi or Cam Lesiuk is strictly prohibited. 
 * 
 * (clesiuk@engr.uvic.ca)
 */  

/* Function.c
 * This file contains all functions available in the c-script language.
 */ 

/* HOW TO ADD A FUNCTION 
 * ---------------------
 *
 * Step 1:   Decide on a function name and the parameters.
 * Step 2:   Write a function using FNPROC(function_name) similar to 
 *           the ones already there.
 * Step 3:   a return code or return information is passed back via
 *           the "Return" parameter. See existing functions and interp.h.
 * Step 4:   Enter into fTable (at the end of this file) a table entry
 *           for your newly-created function. The table's entries are:
 *             "" enclosed string - this is the name of the function as 
 *                called within the C4 script language
 *             Procedure pointer (see existing entries for examples)
 *             Return data type - see codestuf.h for data types
 *             Parameter data types - see examples for format. REMEMBER to end
 *               your parameter list with a CDT_NULL!!!!!!!!!!!!!!!!!!!
 *           That's all. It's pretty simple once you get the hang of it.
 *           NOTE: you don't have to put a reference to your function in 
 *           function.h because all references are through fTable (which is 
 *           already in function.h) Cool, eh.
 *
 * WARNING!  If your function allocates or frees a data object (memory)
 *           make sure you call the appropriate pointer registry function.
 *           (FnAddToRegistry() or FnRemoveFromRegistry())!!!
 *           NOTE: This doesn't apply to Property objects. Why? Because
 *           the way we've got it, the C4 coder can never get their
 *           hands on a pointer to a property - there aren't any functions
 *           to support it. Therefore, they can't use an invalid pointer
 *           through them. Prrrrrrfect. 
 *
 * WARNING!  Don't modify this file if you have any of your area files
 *           saved in binary format. IE: all your obj, mob, rst, and 
 *           wld files should be DECOMPILED and in source format!
 *           Otherwise, modifying this file may cause your area files
 *           to become SCRAMBLED AND UNUSABLE. Read the huge warning
 *           in codestuf.c for an expanded explanation.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "crimson2.h"
#include "macro.h"
#include "log.h"
#include "mem.h"
#include "str.h"
#include "ini.h"
#include "queue.h"
#include "send.h"
#include "extra.h"
#include "property.h"
#include "thing.h"
#include "index.h"
#include "world.h"
#include "base.h"
#include "object.h"
#include "char.h"
#include "mobile.h"
#include "player.h"
#include "area.h"
#include "code.h"
#include "codestuf.h"
#include "compile.h"
#include "interp.h"
#include "decomp.h"
#include "function.h"
#include "parse.h"
#include "exit.h"
#include "skill.h"
#include "fight.h"
#include "cmd_move.h"

/***********************************************************************
 *
 * function.c Pointer Registry Functions
 *
 ***********************************************************************
 * There's a small problem with C4 and pointers. Specifically, it is
 * possible for the coder to:
 *   a) assign a variable to a data object 
 *   b) free the object
 *   c) try to use the variable which points to the just-deleted object! 
 *     WHOOOOOPS!
 * The problem had 2 solutions. One, don't let users anywhere near pointers.
 * This sucks as it severely limits flexibility of the C4 code.
 * Two, somehow have a way of verifying a pointer before using it. This
 * registry was set up towards this end. We only have a problem within
 * a single program; after the program ends, all variables are flushed
 * and there's no way to get them back if they have been free'ed. 
 * Therefore, this registry gets flushed each time Interp does it's stuff.
 * 
 * What we do is maintain a log of all pointers which have been free'ed.
 * If an attempt to use one of these pointers is made, block it. Of course,
 * Creating data objects has to also access this log; if we free something, 
 * then allocate a new one and the new one get's the old one's pointer,
 * we need to not-block the pointer anymore.
 *
 * SO, what we do is each call to a function.c function must call this
 * registry to validate it's pointers. If an invalid pointer is detected,
 * this registry sets that pointer to NULL. */
 /* Note: what we SHOULD do is, upon a FREE of a pointer, search all our
 * stack and data variables for that pointer and set any references to it
 * to NULL. I didn't do this originally and now I can't remember why. Hmmm.. */
/* registry - global declaration */
#define FN_DEF_REGISTRY_SIZE (1<<5)
void **FnPointerRegistry;
LWORD FnPointerRegistrySizeByte=0; /* registry starts empty with no memory */
LWORD FnPointerRegistryPos=0;      /* points to first empty (unused) entry */

void FunctionCheckRegistry(INTERPVARTYPE *Param) {
  WORD i,j;
  BYTE datatype;
  void *ptr;

  /* Little bit of optimization here */
  /* I don't really expect the majority of C4 routines to be free-ing */
  if (!FnPointerRegistryPos) return;

  for (i=0;(datatype=Param[i].iDataType)!=CDT_NULL;i++) {
    if (datatype!=CDT_INT) {
      /* pointer type - we gotta check it. */
      ptr=Param[i].iPtr;
      for(j=0; j<FnPointerRegistryPos; j++) {
        if (FnPointerRegistry[j]==ptr) {
          Param[i].iPtr=NULL;
          break;
        }
      }
    }
  }
}

void FnAddToRegistry(void *ptr) {
  REALLOC("WARNING! function.c pointer registry overflow... resizing\n",
    FnPointerRegistry,void*,FnPointerRegistryPos+2,FnPointerRegistrySizeByte);
  FnPointerRegistry[FnPointerRegistryPos]=ptr;
  FnPointerRegistryPos++;
}

void FnRemoveFromRegistry(void *ptr) {
  WORD i;
  for(i=0; i<FnPointerRegistryPos; i++) {
    if (FnPointerRegistry[i]==ptr) {
      FnPointerRegistryPos--;
      FnPointerRegistry[i]=FnPointerRegistry[FnPointerRegistryPos];
      return;
    }
  }
}

void FunctionFlushRegistry() {
  if (FnPointerRegistrySizeByte) {
    FnPointerRegistryPos=0;
  } else {
    /* Initialize registry */
    FnPointerRegistrySizeByte=FN_DEF_REGISTRY_SIZE*sizeof(void*);
    MALLOC((void*)FnPointerRegistry,void*,FnPointerRegistrySizeByte);
  }
}

/***********************************************************************
 *
 * function.c support functions (not called directly by C4 code)
 *
 ***********************************************************************/

/* FnProcessStrSubstitute 
 * This procedure takes a string, such as "%s says hi to %i people", 
 * and the parameters passed to a function, and substitutes the 
 * parameters into the string, copying the final string over to *dest.
 * The size of dest MUST be set. */
BYTE *FnProcessStrSubstitute(BYTE *buf,int size,BYTE *src,INTERPVARTYPE *Param) {
  BYTE buf2[20],*dest,*end;
  BYTE *c1;
  int CurParam;

  CurParam=0; 
  end=&(buf[size-1]); /* find end of buffer */
  dest=buf;
  /* note: we don't use the buffer as an array - instead we just shimmy
   * a pointer along. We do this becuase shimmying it along requires
   * ADDITION, whereas array functions require MULTIPLICATION - slower! */
  if (buf&&src) {
    while((*src)&&(dest!=end)) {
      if (*src=='%') { /* string substitution! */
        src++;
        if (*src) { /* if it exists, LEAVE it incremented - we need src+=2 ! */
          if (*src=='s') { /* str substitution */
            if ((Param[CurParam].iDataType==CDT_STR)&&
                (Param[CurParam].iPtr)) {
              for (c1=Str(Param[CurParam].iPtr)->sText;
                   (*c1)&&(dest!=end);dest++,c1++)
                *dest=*c1;
              dest--;
            }
          }
          else if (*src == 'i') { /* int substitution */
            if (Param[CurParam].iDataType==CDT_INT) {
              sprintf(buf2,"%li",Param[CurParam].iInt);
              for (c1=buf2;(*c1)&&(dest!=end);dest++,c1++)
                *dest=*c1;
              dest--;
            }
          }
          else if (*src == 'c') { /* character substitution */
            if (Param[CurParam].iDataType==CDT_INT)
              *dest=Param[CurParam].iInt;
          }
          /* else ignore character - unknown type */
          if (Param[CurParam].iDataType!=CDT_NULL)
            CurParam++; /* increment our parameter list! */
        }
        else
          src--;
      }
      else if (*src=='\\') { /* override */
        src++;
        if (*(src)) /* notice how we leave the incrementation of src  */
          *dest=*src;  
        else
          src--;
      }
      else {
        *dest=*src;
      }
      src++;
      dest++;
    } /* while */
    *dest=0; /* mark our end of string */
  }
  else
    *dest=0;
  return buf;
}

/* This procedure looks at "command_string" and determines if the
 * command string is calling the command "cmd", checking for 
 * appropriate abreviations, etc. This proc returns the number
 * of letters matched if successful, or 0 if no valid match is found*/
LWORD FnIsCommand(BYTE *command_string,BYTE *cmd) {
  LWORD len;
  BYTE word[FBUF_COMMON_SIZE];
  if (cmd && command_string) {
    StrOneWord(command_string,word);
    len=strlen(word);
    if ((len)&&(strlen(cmd))) {
      if (!(STRNICMP(word,cmd,len))) {
        return len;
      }
    }
  }
  return 0;
}

#define OS_INVENTORY (1<<0)
#define OS_EQUIPPED  (1<<1)
/* ObjectStripRaw :
 *   THING - thing containing other things which you want to remove items from.
 *   INT   - low virtual number
 *   INT   - high virtual number
 *   THING - MOVE flag. If NULL, items are destroyed, else moved to THIS TTYPE_WLD
 *   INT   - Operation flags:
 *            OS_INVENTORY - strips all stuff in inventory (not equipped)
 *            OS_EQUIPPED  - strips all stuff equipped 
 * This proc will remove all objects from THING with virtual numbers
 * greater than or equal to one INT, and lower or equal to other INT.
 * This is used to remove a range of objects, all at the same time.
 * You can use this to remove, say, from a players inventory all objects
 * from a specific area. 
 */
LWORD FnObjectStripRaw(THING *tfrom,ULWORD low,ULWORD high,THING *tto,WORD flags) {
  THING *t,*tnext;
  ULWORD i,count;

  if (!tfrom) {
    return 0;
  }

  if (tto) {
/*    if (tto->tType!=TTYPE_WLD)
      return;*/
    if (tfrom==tto) /* if we'd be moving them right back here */
      return 0;
  }

  if (low>high) {
    i=low;
    low=high;
    high=i;
  }
  for (t=tfrom->tContain,count=0;t;t=tnext) {
    tnext=t->tNext;
    if ( (t->tType == TTYPE_OBJ) && 
         (Obj(t)->oTemplate->oVirtual>=low) &&
         (Obj(t)->oTemplate->oVirtual<=high) &&
         ( ((flags&OS_INVENTORY)&&(!Obj(t)->oEquip))||
           ((flags&OS_EQUIPPED)&&(Obj(t)->oEquip)) ) ) {
      if (tto) {
        ThingTo(t,tto);
      } else {
        ThingFree(t);
        FnAddToRegistry(t);
      }
      count++;
    } /* if object */
  } /* For --next */
  return count;
} /* FnObjectStripRaw */

/***********************************************************************
 *
 * System functions
 *
 ***********************************************************************/

FNPROC(FnVersion) { /* Returns version information for Crimson II */
  Return->iInt=((CRIMSON_MAJOR_VERSION<<8)+CRIMSON_MINOR_VERSION);
}

FNPROC(FnC4Version) { /* Returns version information for code */
  Return->iInt=((CODE_MAJOR_VERSION<<8)+CODE_MINOR_VERSION);
}

/* generate a number between min and max */
FNPROC(FnNumber) {
  Return->iInt=Number(Param[0].iInt, Param[1].iInt);
}

/* Dice1,6 is the same as 1D6 ie roll a 6 sided die once */
FNPROC(FnDice) {
  Return->iInt=Dice(Param[0].iInt, Param[1].iInt);
}

/***********************************************************************
 *
 * String functions
 *
 ***********************************************************************/

FNPROC(FnStrcmp) {
  Return->iInt=1;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    Return->iInt=strcmp(Str(Param[0].iPtr)->sText,Str(Param[1].iPtr)->sText);
}

/* Should be StrExact & should call StrExact */
FNPROC(FnStricmp) {
  Return->iInt=1;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    Return->iInt=STRICMP(Str(Param[0].iPtr)->sText,Str(Param[1].iPtr)->sText);
}

FNPROC(FnStrncmp) {
  Return->iInt=1;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    Return->iInt=strncmp(Str(Param[0].iPtr)->sText,
                       Str(Param[1].iPtr)->sText,
                       Param[2].iInt);
}

FNPROC(FnStrnicmp) {
  Return->iInt=1;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    Return->iInt=STRNICMP(Str(Param[0].iPtr)->sText,
                       Str(Param[1].iPtr)->sText,
                       Param[2].iInt);
}

FNPROC(FnHello) {
  BYTE word[FBUF_COMMON_SIZE],*ptr;

  Return->iInt=0;
  if (Param[0].iPtr) {
    if (FnIsCommand(Str(Param[0].iPtr)->sText,"say")) {
      ptr=Str(Param[0].iPtr)->sText;
      ptr=StrOneWord(ptr,NULL); /* get rid of "say" */
      ptr=StrOneWord(ptr,word); /* get our first word */
      if ((!(STRICMP(word,"hi")))||(!(STRICMP(word,"hello")))) {
        /* our first word is hi or hello - exactly */
        /* now make sure there's no extra bagage. */
        Return->iInt=1;
        for (;*ptr;ptr++) {
          if (*ptr!=' ') {
            Return->iInt=0;
            return;
          }
        }
      }
    }
  }
}

FNPROC(FnYesNo) {
  BYTE word[FBUF_COMMON_SIZE],*ptr;

  Return->iInt=0;
  if (Param[0].iPtr) {
    if (FnIsCommand(Str(Param[0].iPtr)->sText,"say")) {
      ptr=Str(Param[0].iPtr)->sText;
      ptr=StrOneWord(ptr,NULL); /* get rid of "say" */
      ptr=StrOneWord(ptr,word); /* get our first word */
      if (!(STRICMP(word,"yes"))) {
        Return->iInt=1;
      } else if (!(STRICMP(word,"no"))) {
        Return->iInt=-1;
      }

      if (Return->iInt) {
        /* our first word is yes or no - exactly */
        /* now make sure there's no extra bagage. */
        for (;*ptr;ptr++) {
          if (*ptr!=' ') {
            Return->iInt=0;
            return;
          }
        }
      }
    }
  }
}


FNPROC(FnStrlen) {
  Return->iInt=0;
  if (Param[0].iPtr)
    Return->iInt=strlen(Str(Param[0].iPtr)->sText);
}

/* Should use StrFind */
FNPROC(FnStrIn) { /* checks if s1 is in s0, anywhere. Case insensitive. */
  INTERPVARTYPE *str;

  if (Param[0].iPtr) {
    str=&Param[1];
    while(str->iDataType==CDT_STR && str->iPtr) {
      if (StrFind(Str(Param[0].iPtr)->sText, Str(str->iPtr)->sText)) {
        Return->iInt=1;
        return;
      }
      str++;
    }
  }
  Return->iInt=0;
}

FNPROC(FnStrAt) { /* checks if s1 is in s0 at the specified position */
  BYTE *p0,*p1;
  WORD p0len,word;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    p0=Str(Param[0].iPtr)->sText;
    p1=Str(Param[1].iPtr)->sText;
    p0len=strlen(p0);
    while(*p1==' ') p1++;
    for (word=0;(word<Param[2].iInt)&&(*p1);) {
      if (*p1==' ') {
        word++;
        while(*p1==' ') p1++;
      } else p1++;
    }
    if (!STRNICMP(p1,p0,p0len)) Return->iInt=1;
    else Return->iInt=0; 
  } else Return->iInt=0;
}

FNPROC(FnStrAtExact) { /* checks if s0 is in s1 at the specified position -exactly */
  BYTE *p0,*p1;
  WORD p0len,word;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    p0=Str(Param[0].iPtr)->sText;
    p1=Str(Param[1].iPtr)->sText;
    p0len=strlen(p0);
    while(*p1==' ') p1++;
    for (word=0;(word<Param[2].iInt)&&(*p1);) {
      if (*p1==' ') {
        word++;
        while(*p1==' ') p1++;
      } else p1++;
    }
    if (strlen(p1)<p0len) Return->iInt=0;
    else {
      if (!STRNICMP(p1,p0,p0len)) Return->iInt=1;
      else Return->iInt=0; 
      if ((p1[p0len]!=' ')&&(p1[p0len])) /* check for exact - end of word */
        Return->iInt=0;
    }
  } else Return->iInt=0;
}

FNPROC(FnStrIsCmd) { /* checks if s0 is a call for cmd spec'd by s1 */
  INTERPVARTYPE *str;

  Return->iInt=0;
  if (Param[0].iPtr) {
    str = &Param[1];
    while(str->iDataType==CDT_STR && str->iPtr) {
      Return->iInt=FnIsCommand(Str(Param[0].iPtr)->sText, Str(str->iPtr)->sText);
      if (Return->iInt) return;
      str++;
    }
  }
}

FNPROC(FnStrExact) { 
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    Return->iInt=StrExact(Str(Param[0].iPtr)->sText,
      Str(Param[1].iPtr)->sText);
  }
}
/***********************************************************************
 *
 * Send functions
 *
 ***********************************************************************/

/* SendThing */
FNPROC(FnSendThing) { 
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    SendThing(Str(Param[1].iPtr)->sText,Param[0].iPtr);
}

FNPROC(FnSendThingStr) { 
  BYTE buf[FBUF_COMMON_SIZE];
  if (Param[0].iPtr&&Param[1].iPtr) {
    SendThing(FnProcessStrSubstitute(buf,FBUF_COMMON_SIZE,
      Str(Param[1].iPtr)->sText, &(Param[2])),Param[0].iPtr);
  }
}

FNPROC(FnSendAction) { 
  if (Param[3].iPtr) {
    SendAction(Str(Param[3].iPtr)->sText,Param[0].iPtr,
      Param[1].iPtr,Param[2].iInt);
  }
}

FNPROC(FnSendActionStr) { 
  BYTE buf[FBUF_COMMON_SIZE];
  if (Param[3].iPtr) {
    FnProcessStrSubstitute(buf,FBUF_COMMON_SIZE,
      Str(Param[3].iPtr)->sText, &(Param[4]));
    SendAction(buf,Param[0].iPtr, Param[1].iPtr,Param[2].iInt);
  }
}

/***********************************************************************
 *
 * Extra functions
 *
 ***********************************************************************/

FNPROC(FnExtraGetNext) {
  if (Param[0].iPtr) 
    Return->iPtr=((EXTRA *)(Param[0].iPtr))->eNext;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnExtraGetKey) {
  if (Param[0].iPtr) 
    Return->iPtr=((EXTRA *)(Param[0].iPtr))->eKey;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnExtraGetDesc) {
  if (Param[0].iPtr) 
    Return->iPtr=((EXTRA *)(Param[0].iPtr))->eDesc;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnExtraFind) {
  Return->iPtr=NULL;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    Return->iPtr=ExtraFind(Param[0].iPtr,Str(Param[1].iPtr)->sText);
  }
}
  
FNPROC(FnExtraFree) {
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    FnAddToRegistry(Thing(Param[0].iPtr)->tExtra);
    Thing(Param[0].iPtr)->tExtra=ExtraFree(Thing(Param[0].iPtr)->tExtra,
      Param[1].iPtr);
    Return->iInt=1;
  }
}

FNPROC(FnExtraCreate) {
  Return->iPtr=NULL;

  if ((Param[0].iPtr)&&(Param[1].iPtr)&&(Param[2].iPtr)) {
    /* I use ExtraAlloc here because we already have STRs. */
    Thing(Param[0].iPtr)->tExtra=ExtraAlloc(Thing(Param[0].iPtr)->tExtra,
      Param[1].iPtr,Param[2].iPtr);
    FnRemoveFromRegistry(Thing(Param[0].iPtr)->tExtra);
    Return->iPtr=Thing(Param[0].iPtr)->tExtra;
  }
}


FNPROC(FnExtraSetKey) {
  if ((Param[0].iPtr)&&(Param[1].iPtr)) { 
    if (((EXTRA*)(Param[0].iPtr))->eKey) {
      StrFree(Extra(Param[0].iPtr)->eKey);
      FnAddToRegistry(Extra(Param[0].iPtr)->eKey);
    }
    ((EXTRA*)(Param[0].iPtr))->eKey=StrAlloc(Str(Param[1].iPtr));
    FnRemoveFromRegistry(Extra(Param[0].iPtr)->eKey);
    Return->iPtr=Param[0].iPtr;
  }
  else 
    Return->iPtr=NULL;
}

FNPROC(FnExtraSetDesc) {
  if ((Param[0].iPtr)&&(Param[1].iPtr)) { 
    if (((EXTRA*)(Param[0].iPtr))->eDesc) {
      StrFree(((EXTRA*)(Param[0].iPtr))->eDesc);
      FnAddToRegistry(Extra(Param[0].iPtr)->eKey);
    }
    ((EXTRA*)(Param[0].iPtr))->eDesc=StrAlloc(Str(Param[1].iPtr));
    FnRemoveFromRegistry(Extra(Param[0].iPtr)->eKey);
    Return->iPtr=Param[0].iPtr;
  }
  else 
    Return->iPtr=NULL;
}

/***********************************************************************
 *
 * Thing functions
 *
 ***********************************************************************/

FNPROC(FnThingGetType) {
  if (Param[0].iPtr) 
    Return->iInt=(Thing(Param[0].iPtr)->tType);
  else
    Return->iInt=TTYPE_UNDEF;
}

FNPROC(FnThingGetName) {
  if (Param[0].iPtr) 
    Return->iPtr=((THING *)(Param[0].iPtr))->tSDesc;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnThingGetDesc) {
  if (Param[0].iPtr) 
    Return->iPtr=((THING *)(Param[0].iPtr))->tDesc;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnThingGetExtra) {
  if (Param[0].iPtr) 
    Return->iPtr=((THING *)(Param[0].iPtr))->tExtra;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnThingGetNext) {
  if (Param[0].iPtr) 
    Return->iPtr=((THING *)(Param[0].iPtr))->tNext;
  else 
    Return->iPtr=NULL;
}

FNPROC(FnThingGetContain) {
  if (Param[0].iPtr) 
    Return->iPtr=((THING *)(Param[0].iPtr))->tContain;
  else 
    Return->iPtr=NULL;
}

/* Moves PLR, MOB or OBJ from one THING to another THING. Stops FIGHTING, 
   etc. if required */
FNPROC(FnThingTo) {
  WORD srcType,dstType;

  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    srcType=Thing(Param[0].iPtr)->tType;
    dstType=Thing(Param[1].iPtr)->tType;

    if (((srcType==TTYPE_MOB)||(srcType==TTYPE_PLR))&&
        (dstType==TTYPE_WLD)) {
      /* now, let's take care of a few other things before our move */
      /* stop fighting */
      FightStop(Param[0].iPtr);

      /* ok, let's move. */
      ThingTo(Param[0].iPtr,Param[1].iPtr);
      Return->iInt=1;
    } else if (srcType==TTYPE_OBJ) {
      ThingTo(Param[0].iPtr,Param[1].iPtr);
      Return->iInt=1;
    }
  }
}

FNPROC(FnThingFind) {
  BYTE *key;

  if (Param[0].iPtr)
    key=Str(Param[0].iPtr)->sText;   /* key string   */
  else
    key=NULL;

  Return->iPtr=ThingFind(
    key,                                /* key string   */
    Param[1].iInt,                      /* virtual #    */
    Param[2].iPtr,                      /* search thing */
    Param[3].iInt,                      /* search flag  */
    &(Param[4].iInt));                  /* offset (ie Nth item) */
}

FNPROC(FnThingGetWait) {
  if (Param[0].iPtr) 
    Return->iInt=(Thing(Param[0].iPtr)->tWait);
  else
    Return->iInt=0;
}

FNPROC(FnThingSetWait) {
  if (Param[0].iPtr) {
    Return->iInt=(Thing(Param[0].iPtr)->tWait);
    Thing(Param[0].iPtr)->tWait = Param[1].iInt;
  }
  else
    Return->iInt=0;
}

FNPROC(FnThingGetIdleWait) {
  if (Param[0].iPtr) 
    Return->iInt=(Thing(Param[0].iPtr)->tIdleWait);
  else
    Return->iInt=0;
}

FNPROC(FnThingSetIdleWait) {
  if (Param[0].iPtr) {
    Return->iInt=(Thing(Param[0].iPtr)->tIdleWait);
    Thing(Param[0].iPtr)->tIdleWait = Param[1].iInt;
  }
  else
    Return->iInt=0;
}



/***********************************************************************
 *
 * World functions
 *
 ***********************************************************************/

/* Should be WorldOf */
FNPROC(FnWorldOf) {         
  Return->iPtr=WorldOf(Param[0].iInt);
}

FNPROC(FnWorldGetVirtual) {
  Return->iInt=0;  
  if (Param[0].iPtr)
    Return->iInt=Wld(Param[0].iPtr)->wVirtual;
}

FNPROC(FnWorldGetFlag) {
  Return->iInt=0;  
  if (Param[0].iPtr)
    Return->iInt=Wld(Param[0].iPtr)->wFlag;
}

FNPROC(FnWorldSetFlag) {
  Return->iInt=0;  
  if (Param[0].iPtr) {
    Return->iInt=Wld(Param[0].iPtr)->wFlag;
  Wld(Param[0].iPtr)->wFlag=Param[1].iInt;
  }  
}

FNPROC(FnWorldGetFlagBit) {
  Return->iInt=0;  
  if (Param[0].iPtr)
    Return->iInt=(Wld(Param[0].iPtr)->wFlag)&Param[1].iInt;
}

FNPROC(FnWorldSetFlagBit) {
  Return->iInt=0;  
  if (Param[0].iPtr) {
    Return->iInt=(Wld(Param[0].iPtr)->wFlag)&Param[1].iInt;
  BITSET(Wld(Param[0].iPtr)->wFlag,Param[1].iInt);
  }
}

FNPROC(FnWorldClearFlagBit) {
  Return->iInt=0;  
  if (Param[0].iPtr) {
    Return->iInt=(Wld(Param[0].iPtr)->wFlag)&Param[1].iInt;
  BITCLR(Wld(Param[0].iPtr)->wFlag,Param[1].iInt);
  }
}

FNPROC(FnWorldGetType) {
  Return->iInt=0;  
  if (Param[0].iPtr)
    Return->iInt=Wld(Param[0].iPtr)->wType;
}

FNPROC(FnWorldSetType) {
  Return->iInt=0;  
  if (Param[0].iPtr) {
    Return->iInt=Wld(Param[0].iPtr)->wType;
  Wld(Param[0].iPtr)->wType=Param[1].iInt;
  }
}



/***********************************************************************
 *
 * Base functions
 *
 ***********************************************************************/

FNPROC(FnBaseGetKey) {
  Return->iPtr=NULL;
  if (Param[0].iPtr) 
    if ((Thing(Param[0].iPtr)->tType==TTYPE_OBJ) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_PLR))
      Return->iPtr=(Base(Param[0].iPtr)->bKey);
}

FNPROC(FnBaseGetLDesc) {
  Return->iPtr=NULL;
  if (Param[0].iPtr) 
    if ((Thing(Param[0].iPtr)->tType==TTYPE_OBJ) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_PLR))
      Return->iPtr=(Base(Param[0].iPtr)->bLDesc);
}

FNPROC(FnBaseGetInside) {
  Return->iPtr=NULL;
  if (Param[0].iPtr) 
    if ((Thing(Param[0].iPtr)->tType==TTYPE_OBJ) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_PLR))
      Return->iPtr=(Base(Param[0].iPtr)->bInside);
}

FNPROC(FnBaseGetConWeight) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    if ((Thing(Param[0].iPtr)->tType==TTYPE_OBJ) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_PLR))
      Return->iInt=(Base(Param[0].iPtr)->bConWeight);
}

FNPROC(FnBaseGetWeight) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    if ((Thing(Param[0].iPtr)->tType==TTYPE_OBJ) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_PLR))
      Return->iInt=(Base(Param[0].iPtr)->bWeight);
}



/***********************************************************************
 *
 * Object functions
 *
 ***********************************************************************/


FNPROC(FnObjectCreate) {
  Return->iPtr=NULL;
  if (Param[0].iPtr) {
    Return->iPtr=ObjectCreate(ObjectOf(Param[1].iInt),Param[0].iPtr);
    FnRemoveFromRegistry(Return->iPtr);
  }
}

FNPROC(FnObjectCreateNum) {
  ULWORD i;
 
  Return->iPtr=NULL;
  if (Param[2].iInt<1)
    return;
  if (Param[2].iInt>200)
    Param[2].iInt=200;
  if (!Param[0].iPtr)
    return;

  for (i=0;i<Param[2].iInt;i++) {
    Return->iPtr=ObjectCreate(ObjectOf(Param[1].iInt),Param[0].iPtr);
    FnRemoveFromRegistry(Return->iPtr);
  }
}

FNPROC(FnObjectRemove) {
  THING *t;
  OBJTEMPLATE *o;

  Return->iInt=0;
  o=ObjectOf(Param[1].iInt);
  if ((Param[0].iPtr) && (o)) {
    for (t=Thing(Param[0].iPtr)->tContain;t;t=t->tNext) {
      if ( (t->tType == TTYPE_OBJ) && (Obj(t)->oTemplate==o) ) {
        ThingFree(t);
        FnAddToRegistry(t);
        Return->iInt=1;
        return;
      }
    }
  }
}


/* ObjectStrip :
 *   THING - thing containing other things which you want to remove items from.
 *   INT   - low virtual number
 *   INT   - high virtual number
 *   THING - MOVE flag. If NULL, items are destroyed, else moved to THIS TTYPE_WLD
 * This proc will remove all objects from THING with virtual numbers
 * greater than or equal to one INT, and lower or equal to other INT.
 * This is used to remove a range of objects, all at the same time.
 * You can use this to remove, say, from a players inventory all objects
 * from a specific area. 
 */
FNPROC(FnObjectStrip) {
  Return->iInt=FnObjectStripRaw(Param[0].iPtr,Param[1].iInt,
    Param[2].iInt,Param[3].iPtr,OS_INVENTORY|OS_EQUIPPED);
} /* FnObjectStrip */

FNPROC(FnObjectStripInv) {
  Return->iInt=FnObjectStripRaw(Param[0].iPtr,Param[1].iInt,
    Param[2].iInt,Param[3].iPtr,OS_INVENTORY);
} /* FnObjectStripInv */

FNPROC(FnObjectStripEqu) {
  Return->iInt=FnObjectStripRaw(Param[0].iPtr,Param[1].iInt,
    Param[2].iInt,Param[3].iPtr,OS_EQUIPPED);
} /* FnObjectStripEqu */

FNPROC(FnObjectFree) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    if (Thing(Param[0].iPtr)->tType==TTYPE_OBJ) {
      ThingFree(Thing(Param[0].iPtr));
      FnAddToRegistry(Param[0].iPtr);
      Return->iInt=1;
    }
  }
}

FNPROC(FnObjectContain) {
  THING *t;
  OBJTEMPLATE *o;

  Return->iPtr=NULL;
  o=ObjectOf(Param[1].iInt);
  if ((Param[0].iPtr) && (o)) {
    for (t=Thing(Param[0].iPtr)->tContain;t;t=t->tNext) {
      if ( (t->tType == TTYPE_OBJ) && (Obj(t)->oTemplate==o) ) {
        Return->iPtr=t;
        return;
      }
    }
  }
}

FNPROC(FnObjectCount) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    Return->iInt=ObjectPresent(ObjectOf(Param[1].iInt),Param[0].iPtr,OP_ALL);
}

FNPROC(FnObjectCountInv) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    Return->iInt=ObjectPresent(ObjectOf(Param[1].iInt),Param[0].iPtr,OP_INVENTORY);
}

FNPROC(FnObjectCountEqu) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    Return->iInt=ObjectPresent(ObjectOf(Param[1].iInt),Param[0].iPtr,OP_EQUIPPED);
}
                          
/* This proc processes the raw command line of a CHR to uniformly handle the 
 * the process of trading an item from a MOB to a CHR in exchange for the 
 * CHR's money. The params are as follows:
 *   CDT_THING thing1   - merchant MOB
 *   CDT_THING thing2   - customer CHR
 *   CDT_STR   cmd      - CHR's command line
 *   CDT_INT   mark up  - mark up on items (must be 100 or better) 100=list price
 *   CDT_ETC   obj1,obj2... - virtual object #'s of items merchant has unlimited stock.
 *             NOTE: if you use a negative virtual number, the effect is that
 *             the merchant will NOT sell any of that type of object which may
 *             be in the inventory (but will still sell them if they are
 *             marked elsewhere as being an unlimited supply item).
 *             Also, the items will not be removed from inventory in the event
 *             that they are unlimited supply items!!!
 * If this proc does NOT process the command, the return value is 0. If this
 * proc processes the command but no purchases were made, -1 is returned. 
 * If this proc processes the command and purchases WERE made, the number
 * of purchases made is returned */
FNPROC(FnObjectBuy) {
  BYTE          *cmd;
  BYTE           buyKey[FBUF_COMMON_SIZE];
  LWORD          buyOffset;
  LWORD          buyNum;
  LWORD          showVirtual;
  LWORD          lastVirtual;
  BYTE           bufPlr[FBUF_COMMON_SIZE];
  BYTE           bufRoom[FBUF_COMMON_SIZE];
  BYTE           bufObj[FBUF_COMMON_SIZE];
  INTERPVARTYPE *stock;
  INTERPVARTYPE *checkInv;
  THING         *thing;
  THING         *next;
  OBJTEMPLATE   *obj;
  LWORD          i;
  LWORD          foundObj;
  LWORD          actionType;
  LWORD          salesTotal;
  LWORD          markup;
  THING         *tempHolder;
  BYTE           buyAction; /* 0 if "buy", 1 if "list" */

  Return->iInt=0;

  if (!((Param[0].iPtr) && (Param[1].iPtr) && (Param[2].iPtr) )) {
    return;
  } /* non-NULL pointers */

  markup=Param[3].iInt;
  if (markup<100)
    markup=100;

  /* first thing, check to make sure we've been given the correct
   * input */
  cmd=StrOneWord(Str(Param[2].iPtr)->sText,buyKey);
  if (!((Thing(Param[0].iPtr)->tType==TTYPE_MOB) &&
      ((Thing(Param[1].iPtr)->tType==TTYPE_PLR)||
       (Thing(Param[1].iPtr)->tType==TTYPE_MOB)))) {
    return;
  } /* valid THINGs */

  /* must type at least 2 letters of buy,purchase,list */
  if (strlen(buyKey)<2) return;

  /* ok, we've got a MOB, a CHR, and a command line to parse. */
  /* Let's take a look at the command line and see if the CHR */
  /* wants to BUY something */
  if (FnIsCommand(buyKey,"buy") || FnIsCommand(buyKey,"purchase")) {
    buyAction=0;
  } else if (FnIsCommand(buyKey,"list")) {
    buyAction=1;
  } else if (FnIsCommand(buyKey,"show")||FnIsCommand(buyKey,"inspect")||FnIsCommand(buyKey,"examine")) {
    buyAction=2;
  } else {
    return;
  } /* command line is "buy" or "list" */

  /* it's a BUY command all righty! */
  /* mark return code as "yes, we processed this cmd" 
   * however, mark as a "-1": ie: we haven't bought 
   * any YET 
   */
  Return->iInt=-1;  

  /* Note: there's a small quirk in things here. What we need to do is
   * somehow separate what BELONGS TO THE VENDOR, and what is in the
   * INVENTORY and can thus be sold. What we do is look at the parameter
   * list and take out anything which is marked with a minus (-) sign.
   * This separates what the vendor has as its store inventory, and what the
   * vendor has as it's own equipment and personal inventory... so it 
   * (for example) doesn't sell all its clips for its gun. */
  tempHolder=ObjectCreate(corpseTemplate,NULL);
  /* load up our corpse, er I mean temp holder */
  for (checkInv=&Param[4];checkInv->iDataType==CDT_INT;checkInv++) {
    if (checkInv->iInt<0) {
      /* This is an item which we NEED to keep. */
      foundObj=FALSE; /* set to 1 if we found and moved it */
      /* Check inventory for it first. */
      for (thing=Thing(Param[0].iPtr)->tContain;thing;thing=next) {
        next=thing->tNext;
        if ( (thing->tType==TTYPE_OBJ)
          && (Obj(thing)->oTemplate->oVirtual == -checkInv->iInt)
        ) {
          ThingTo(thing,tempHolder);
          foundObj=TRUE;
          break;
        }
      }
      /* What the hell is Cam doing here!?!???? */
      /* Why would we create objects to add to the tempHolder? */
      /* In fact this will create enormous amounts of objects to add back
        into the mobs inventory since they transfer back at the end of this
        routine */
      /* Ahhhh wait I get it this means that this mob will replenish its
         supply of these items for its own personal use */
      if (!foundObj) { /* ie if NOT FOUND IN INVENTORY, check infinite list and
                    * take out of "infinite" store inventory */
        FnRemoveFromRegistry(
          ObjectCreate(ObjectOf(-(checkInv->iInt)), tempHolder)
        );
      }
    }
  }

  /* no parameters given - do inventory listing */
  if ((!*cmd)||(buyAction==1)) {
    SendAction("^b$n has the following items for sale:\n\n"
      ,Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
    /* first do stock items */
    stock=&Param[4];
    while(stock->iDataType==CDT_INT) {
      if ((stock->iInt>=0)&&((obj=ObjectOf(stock->iInt)))) {
        sprintf(bufRoom,"^c     %s  ^b",
          StrTruncate(bufObj,obj->oSDesc->sText,40));
        for (i=strlen(bufRoom);i<54;i++)
          strcat(bufRoom,".");
        sprintf(bufPlr,"%s ^c%8li^Cc\n",bufRoom,
          ((obj->oValue)*markup)/100);
        SendAction(bufPlr,Param[0].iPtr,Param[1].iPtr,
          SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      }
      stock++;
    }

    /* We want to check here to make sure the inventory doesnt contain
     * any unlimited items.*/
    for (thing=Thing(Param[0].iPtr)->tContain;thing;thing=next) {
      next = thing->tNext;
      stock=&Param[4];
      while(stock->iDataType==CDT_INT) {
        if (stock->iDataType==CDT_INT
         && thing
         && thing->tType == TTYPE_OBJ
         && Obj(thing)->oTemplate->oVirtual==stock->iInt
         && !Obj(thing)->oEquip
        ) {
          FnAddToRegistry(thing);
          THINGFREE(thing);
        }
        stock++;
      }
    }

    /* next do inventory items */
    showVirtual=-1;
    /* we use the showVirtual to track our virtual obj # */
    /* so that we only print 1 of each object type  */
    while(1) {
      lastVirtual=showVirtual; /* save last showVirtual */
      /* first, find our smallest (un-displayed) object */
      for (thing=Thing(Param[0].iPtr)->tContain;thing;thing=next) {
        next = thing->tNext;
        if ((thing->tType==TTYPE_OBJ)&&(!Obj(thing)->oEquip)) {
          if ((Obj(thing)->oTemplate->oVirtual>lastVirtual)&&
              ((Obj(thing)->oTemplate->oVirtual<showVirtual)  ||
               (lastVirtual==showVirtual)))
            showVirtual=Obj(thing)->oTemplate->oVirtual;
        }
      }

      /* Display this object. */
      if (showVirtual==lastVirtual) {
        /* we are done - move everything back from our tempHolder */
        while(tempHolder->tContain)
          ThingTo(tempHolder->tContain,Thing(Param[0].iPtr));
        THINGFREE(tempHolder);
        return; /* we're done */
      }
      obj=ObjectOf(showVirtual);
      sprintf(bufRoom,"^c     %s  ^b",
        StrTruncate(bufObj,obj->oSDesc->sText,40));
      for (i=strlen(bufRoom);i<54;i++)
        strcat(bufRoom,".");
      sprintf(bufPlr,"%s ^c%8li^Cc ^b(%li in stock)\n",bufRoom,
        ((obj->oValue)*markup)/100,
        ObjectPresent(obj,Param[0].iPtr,OP_ALL));
      SendAction(bufPlr,Param[0].iPtr,Param[1].iPtr,
        SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
    } /* while (1) inventory loop */
  } /* if... no parameters, show inventory */

  /* See if user specified HOW MANY to buy */
  ParseFind(cmd, buyKey, &buyOffset, &buyNum, NULL, NULL);
  /* buyNum==-1 means buy all */
  if (buyNum == TF_ALLMATCH) {
    SendAction("^p$n raises an eyebrow, and says ^C\"Perhaps a more reasonable number\"\n",Param[0].iPtr,Param[1].iPtr,
      SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
    /* we are done - move everything back from our tempHolder */
    while(tempHolder->tContain)
      ThingTo(tempHolder->tContain,Thing(Param[0].iPtr));
    THINGFREE(tempHolder);
    return;
  }

  /* let's see if it's in our regular stock... */
  stock=&Param[4];
  while(stock->iDataType==CDT_INT) {
    if (stock->iInt>=0 && (obj=ObjectOf(stock->iInt))) {
      /* this object exists... check it's name */
      if (StrIsKey(buyKey,obj->oKey)) {
        /* Hey hey hey! We've found it! */
        buyOffset--;
        if (buyOffset>0) { stock++; continue; }

        /* just show it to them */
        if (buyAction==2) {
          thing = ObjectCreate(obj, NULL);
          ThingShowDetail(thing, Param[1].iPtr, TRUE);
          THINGFREE(thing);
          Return->iInt=0;
          return;
        }

        salesTotal=buyNum*obj->oValue*markup/100;

        /* check value of item, and CHR's money situation */
        if ((Character(Param[1].iPtr)->cMoney>=salesTotal)) {
          /* they have enough money, but can they carry it all? */
          if (1) { /* check their weight here */
            /* purchase is made! */

            /* exchange cash */
            Character(Param[1].iPtr)->cMoney-=salesTotal;
            Character(Param[0].iPtr)->cMoney+=salesTotal;

            /* and now create "buyNum" obj's in chr's inventory */
            for (i=0;i<buyNum;i++) 
              FnRemoveFromRegistry(ObjectCreate(obj,Param[1].iPtr));

            /* tell Plr how much they spent, etc. */
            sprintf(bufPlr,"^bYou buy %li %s%s for %li credit%s.\n",
              buyNum, obj->oSDesc->sText, 
              (buyNum>1)?"s":"", 
              salesTotal,
              (salesTotal>1)?"s":"");
            sprintf(bufRoom,"^b$N buys %li %s%s.\n",
              buyNum, obj->oSDesc->sText,
              (buyNum>1)?"s":"");
            actionType=SEND_VISIBLE;

            /* and lastly, return the number of items purchased */
            Return->iInt=buyNum;
          } else {
            /* can't carry it all! Too much weight! */
            sprintf(bufPlr,"^bYou cannot carry all that weight!\n");
            bufRoom[0]=0;
            actionType=0;
          }
        } else {
          /* not enough cash!!! */
          sprintf(bufPlr,"^bYou don't have %li credit%s to pay for %li %s%s!\n",
            salesTotal,(salesTotal>1)?"s":"",
            buyNum,obj->oSDesc->sText,
            (buyNum>1)?"s":"");
          bufRoom[0]=0;
          actionType=0;
        }
        SendAction(bufPlr,Param[0].iPtr,Param[1].iPtr,
          SEND_DST|actionType|SEND_CAPFIRST);
        if (bufRoom[0])
          SendAction(bufRoom,Param[0].iPtr,Param[1].iPtr,
            SEND_ROOM|actionType|SEND_CAPFIRST);
        /* we are done - move everything back from our tempHolder */
        while(tempHolder->tContain)
          ThingTo(tempHolder->tContain,Thing(Param[0].iPtr));
        THINGFREE(tempHolder);
        return;
      } /* we've found object they want in stock */
    } /* virtual numbered object exists */
    stock++;
  } /* while... check to see if item is in stock */

  /* it's not in stock... let's see if it's in MOB's inventory... */
  /* note: we only check MOB's INVENTORY, not EQUIPMENT! This way,
   * the mob can use/keep personal items, and have an inventory
   * of sellable items. */
  /* Look for the first Object */
  thing = CharThingFind(Thing(Param[0].iPtr), buyKey, -1, Thing(Param[0].iPtr), TF_OBJ, &buyOffset);

  if (!thing) {
  /* Hmmmm... not in stock or inventory... sorry! */

    sprintf(bufPlr,"^p$n says \"Sorry, I haven't got any of those in stock.\"\n");
    SendAction(bufPlr,Param[0].iPtr,Param[1].iPtr,
      SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);

  } else {
    /* we have it in our inventory! */
    /* just show it to them */
    if (buyAction==2) {
      ThingShowDetail(thing, Param[1].iPtr, TRUE);
      Return->iInt=0;
      return;
    }

    /* first, count how many we have to see if we have enough */
    if (buyNum<=ObjectPresent(
      Obj(thing)->oTemplate,Param[0].iPtr,OP_ALL)) {
      /* we have enough! Does the CHR have the cash? */
      salesTotal=buyNum*Obj(thing)->oTemplate->oValue*markup/100;
      if ((Character(Param[1].iPtr)->cMoney>=salesTotal)) {
        obj=Obj(thing)->oTemplate;
        /* they have enough money, but can they carry it all? */
        if ( (obj->oWeight*buyNum)
            +(Base(Param[1].iPtr)->bConWeight)
            <= CharGetCarryMax(Param[1].iPtr)
        ) { /* check their weight here */
          /* purchase is made! */

          /* exchange cash */
          Character(Param[1].iPtr)->cMoney-=salesTotal;
          Character(Param[0].iPtr)->cMoney+=salesTotal;

          /* transfer items from merchant to customer */
          for (i=0;i<buyNum; ) {
            for (thing=Thing(Param[0].iPtr)->tContain; thing; thing=next) {
              next = thing->tNext;
              if ((thing->tType==TTYPE_OBJ)&&(!Obj(thing)->oEquip)) {
                if (Obj(thing)->oTemplate->oVirtual==obj->oVirtual) {
                  ThingTo(thing,Param[1].iPtr);
                  i++;
                  break;
                } /* matching Virtual #'s */
              } /* if Obj */
            } /* loop through inventory */
            if (!thing) { /* double check - if we run out of items, exit */
              break;
            }
          } /* loop for # items desired */

          /* tell Plr how much they spent, etc. */
          sprintf(bufPlr,"^bYou buy %li %s%s for %li credit%s.\n",
            buyNum , obj->oSDesc->sText, 
            (buyNum>1)?"s":"", 
            salesTotal,
            (salesTotal>1)?"s":"");
          sprintf(bufRoom,"^b$N buys %li %s%s.\n",
            buyNum, obj->oSDesc->sText,
            (buyNum>1)?"s":"");
          actionType=SEND_VISIBLE;

          /* and lastly, return the number of items purchased */
          Return->iInt=buyNum;
        } else {
          /* can't carry it all! Too much weight! */
          sprintf(bufPlr,"^bYou cannot carry all that weight!\n");
          bufRoom[0]=0;
          actionType=0;
        }
      } else {
        /* not enough cash!!! */
        sprintf(bufPlr,"^bYou don't have %li credit%s to pay for %li %s%s!\n",
          salesTotal,(salesTotal>1)?"s":"",
          buyNum,Obj(thing)->oTemplate->oSDesc->sText,
          (buyNum>1)?"s":"");
        bufRoom[0]=0;
        actionType=0;
      } /* not enough cash */
    } else {
      /* haven't got enough of those items! */
      sprintf(bufPlr,"^p$n says \"I only have %li of those in stock.\"\n",
        ObjectPresent(Obj(thing)->oTemplate,
        Param[0].iPtr,OP_ALL));
      bufRoom[0]=0;
      actionType=SEND_AUDIBLE;
    } /* not enough in inventory */

    SendAction(bufPlr,Param[0].iPtr,Param[1].iPtr,
      SEND_DST|actionType|SEND_CAPFIRST);
    if (bufRoom[0])
      SendAction(bufRoom,Param[0].iPtr,Param[1].iPtr,
        SEND_ROOM|actionType|SEND_CAPFIRST);
  } 

  /* we are done - move everything back from our tempHolder */
  while(tempHolder->tContain)
    ThingTo(tempHolder->tContain,Thing(Param[0].iPtr));
  THINGFREE(tempHolder);
} /* FnObjectBuy */

/* This proc processes the raw command line of a CHR to uniformly handle the 
 * the process of trading an item from a CHR to a MOB in exchange for the 
 * MOB's money. The params are as follows:
 *   CDT_THING thing1   - merchant MOB
 *   CDT_THING thing2   - customer CHR (person with the item)
 *   CDT_STR   cmd      - CHR's command line
 *   CDT_INT   markdown - markdown of item from list price for purchase (0-100)
 *                        Item is purchased for ((list price)*markdown)/100
 *                        NOTE: if you include a negative virtual number, 
 *                        the effect is that the THING bought will be free'd,
 *                        instead of being added to the inventory of the
 *                        vendor. Now, you don't need to put in every object
 *                        you have unlimited supply of because ObjectBuy will
 *                        take care of that. However, if you have something,
 *                        say some ammo clips with which the MOB protects
 *                        itself, which you don't want added to or sold off, 
 *                        this give you a way to make those few items an
 *                        exception.
 *   CDT_ETC   type1,type2... - objects & types in which merchant will buy.
 * This proc returns TRUE if SELL command was dealt with, including if the
 * customer tried to SELL an item which the merchant does not carry (ie deal
 * in),  else FALSE if command was not a SELL command. */
FNPROC(FnObjectSell) {
  BYTE          *cmd;
  BYTE           sellKey[FBUF_COMMON_SIZE];
  LWORD          sellOffset;
  LWORD          sellNum;
  LWORD          markdown;
  LWORD          sellOperation; /* = 0 if "sell", =1 if "value" */
  LWORD          sellPrice;
  BYTE           bufPlr[FBUF_COMMON_SIZE];
  THING         *thing;
  INTERPVARTYPE *stock;
  LWORD          sellThis;

  Return->iInt=0;

  if (!((Param[0].iPtr) && (Param[1].iPtr) && (Param[2].iPtr) )) {
    return;
  } /* non-NULL pointers*/

  markdown=Param[3].iInt;
  if (markdown>100)
    markdown=100;

  /* first thing, check to make sure we've been given the correct
   * input */
  cmd=StrOneWord(Str(Param[2].iPtr)->sText,sellKey);
  if (!((Thing(Param[0].iPtr)->tType==TTYPE_MOB) &&
      ((Thing(Param[1].iPtr)->tType==TTYPE_PLR)||
       (Thing(Param[1].iPtr)->tType==TTYPE_MOB)))) {
    return;
  } /* valid THINGs */

  /* ok, we've got a MOB, a CHR, and a command line to parse. */
  /* Let's take a look at the command line and see if the CHR */
  /* wants to SELL something */
  if (FnIsCommand(sellKey,"sell")&&(strlen(sellKey)>1)) {
    /* command line is "sell"  */
    sellOperation=0;
  } else if (FnIsCommand(sellKey,"value")) {
    sellOperation=1;
  } else {
    return;
  }
  /* it's a SELL/VALUE command all righty! */
  /* mark return code as "yes, we processed this cmd" 
   * however, mark as a "-1": ie: we haven't done anything yet.
   */
  Return->iInt=-1;  

  /* no objects to sell / value */ 
  if (!*cmd) {
    if (sellOperation) { /* empty value command */
      SendAction("^aYou try to get $n to value NOTHING.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to get $n to value NOTHING.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n says, \"Nothing would be worth, hm, let me see... NOTHING!\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n says to $N, \"Nothing would be worth, hm, let me see... NOTHING!\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    } else { /* empty sell command */
      SendAction("^aYou try to sell NOTHING to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to sell NOTHING to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n asks, \"Ah, yes, but what would you like to sell?\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n asks $N, \"Ah, yes, but what would you like to sell?\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    }
    return;
  } /* no params to sell/value command */

  /* Parse player's command line */
  ParseFind(cmd, sellKey, &sellOffset, &sellNum, NULL, NULL);
  /* sellNum==-1 means buy all */

  /* now loop through the player's inventory - use ThingFind to process
   * our command line appropriately */
  /* Look for the first Object */
  thing = CharThingFind(Param[1].iPtr, sellKey, -1, Thing(Param[1].iPtr), TF_OBJINV, &sellOffset);
  if (!thing) {
    /* Hmmmm... player doesn't have that item... sorry! */
    if (sellOperation) { /* value command */
      SendAction("^aYou want to value what?!\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to have $n value a non-existent item.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n says, \"That particular imaginary object is worth 24884727c.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, \"That particular imaginary object is worth 23883727c.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    } else {
      SendAction("^aYou try to sell what?! to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to sell a non-existent item to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n says, \"Hmmm, sorry, I don't deal in imaginary objects.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, \"Hmmm, sorry, I don't deal in imaginary objects.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    }
    return;
  } 
  while (thing && sellNum != 0) {
    /* plr has it in their inventory! */

    /* precalc sellPrice */
    sellPrice=Obj(thing)->oTemplate->oValue;
    sellPrice=(sellPrice*markdown)/100; /* mark down item cost */

    /* initial action report to plr/room */
    if (sellOperation) { /*  value command */
      SendAction("^a$N asks $n to value an item.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    } else {
      SendAction("^a$N tries to sell an object to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    }

    /* First, check to see if we deal in this item...
     * Check parameter list for objects and object types. 
     */
    sellThis = FALSE;
    stock = &Param[4];
    while (stock->iDataType==CDT_INT || stock->iDataType==CDT_STR) {
      if (stock->iDataType==CDT_INT) {
        if (Obj(thing)->oTemplate->oVirtual == stock->iInt) {
          sellThis = TRUE;
          break;
        }
      } else if (stock->iDataType == CDT_STR) {
        if (Obj(thing)->oTemplate->oType == TYPEFIND(Str(stock->iPtr)->sText, oTypeList)) {
          sellThis = TRUE;
          break;
        }
      }
      stock++;
    }

    if (!sellThis) {
      SendAction("^p$n says, ",Param[0].iPtr,Param[1].iPtr,
        SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, ",Param[0].iPtr,Param[1].iPtr,
        SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
      sprintf(bufPlr,"\"I dont deal in things like $n.\"\n");
      SendAction(bufPlr,thing,Param[1].iPtr,
        SEND_DST|SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);

    /* Next, check if we have the cash (and are willing to spend it)
     * for this item. Note: MOBS will not buy an object if the purchase
     * price is more than 50% of their remaining cash. This prevents the
     * MOB from buying one thing, and being stuck with it, basically 
     * disabled because it can't buy any more, and nobody can buy the item.
     */
    } else if (sellOperation) { /*  value command */
      SendAction("^p$n says, ",Param[0].iPtr,Param[1].iPtr,
        SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, ",Param[0].iPtr,Param[1].iPtr,
        SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
      sprintf(bufPlr,"\"That $n is worth %ldc.\"\n",sellPrice);
      SendAction(bufPlr,thing,Param[1].iPtr,
        SEND_DST|SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    } else {
      /* check money situation */

      /* not enough cash to cover it */
      if (sellPrice>(Character(Param[0].iPtr)->cMoney>>1)) {
        SendAction("^p$n says, ",Param[0].iPtr,Param[1].iPtr,
          SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
        SendAction("^p$n tells $N, ",Param[0].iPtr,Param[1].iPtr,
          SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
        sprintf(bufPlr,"\"I don't have enough cash to buy your $n.\"\n");
        SendAction(bufPlr,thing,Param[1].iPtr,
          SEND_DST|SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);

      /* It's a deal! transfer item to MOB and exchange cash */
      } else {
        /* exchange cash */
        Character(Param[1].iPtr)->cMoney+=sellPrice;
        Character(Param[0].iPtr)->cMoney-=sellPrice;
        if (Return->iInt==-1)
          Return->iInt=0;  
        Return->iInt+=sellPrice;
        ThingTo(thing,Param[0].iPtr);

        /* announce what happened */
        SendAction("^pYou sell $n",Param[0].iPtr,Param[1].iPtr,
          SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
        SendAction("^p$N sells",Param[0].iPtr,Param[1].iPtr,
          SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
        sprintf(bufPlr," $n.\n");
        SendAction(bufPlr,thing,Param[1].iPtr,
          SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
        sprintf(bufPlr," $n for %ldc.\n",sellPrice);
        SendAction(bufPlr,thing,Param[1].iPtr,
          SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      }
    }

    /* setup for next object to sell/value */
    thing = CharThingFind(Thing(Param[1].iPtr), sellKey, -1, Thing(Param[1].iPtr), TF_OBJINV|TF_CONTINUE, &sellOffset);
    if (sellNum>0) sellNum--;
  } /* while Thing Find Loop */
} /* FnObjectSell */

FNPROC(FnObjectGetVirtual) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    if (Thing(Param[0].iPtr)->tType==TTYPE_OBJ) {
      Return->iInt=Obj(Param[0].iPtr)->oTemplate->oVirtual;
    }
}

/* FnObjectRedeem - redeem scanned-in electronic or bio technology
 *   CDT_THING thing1   - merchant MOB
 *   CDT_THING thing2   - customer CHR (person with the item)
 *   CDT_STR   cmd      - CHR's command line
 *   CDT_INT   markdown - markdown of item from list price for purchase (0-100)
 *   CDT_INT   flag     - flags 
 *      
 * Flags: OR_BIO  - MOB will buy biological scans
 *        OR_CHIP - MOB will buy electrical scans
 *        OR_RICH - money will just be GENERATED. It will not come from the 
 *                  MOB. Without this flag, the MOB will only redeem 
 *                  scanner information if it has cash to cover the cost.
 */
FNPROC(FnObjectRedeem) {
  LWORD          markdown;
  LWORD          flag;
  FLAG           sFlag;
  LWORD          bioScanned;
  LWORD          chipScanned;
  BYTE          *cmd;
  BYTE           redeemKey[FBUF_COMMON_SIZE];
  LWORD          redeemOffset;
  LWORD          redeemNum;
  LWORD          redeemOperation;
  THING         *thing;
  LWORD          redeemPrice;
  BYTE           bufPlr[FBUF_COMMON_SIZE];

  Return->iInt=0;
  if (!(Param[0].iPtr && Param[1].iPtr && Param[2].iPtr))
    return;

  markdown=Param[3].iInt;
  if (markdown>100)
    markdown=100;
  if (markdown<10)
    markdown=10;

  flag=Param[4].iInt;

  /* first thing, check to make sure we've been given the correct
   * input */
  if (!((Thing(Param[0].iPtr)->tType==TTYPE_MOB) &&
      ((Thing(Param[1].iPtr)->tType==TTYPE_PLR)||
       (Thing(Param[1].iPtr)->tType==TTYPE_MOB)))) {
    return;
  } /* valid THINGs */
  cmd=StrOneWord(Str(Param[2].iPtr)->sText,redeemKey);

  /* ok, we've got a MOB, a CHR, and a command line to parse. */
  /* Let's take a look at the command line and see if the CHR */
  /* wants to SELL something */
  if (FnIsCommand(redeemKey,"redeem")) {
    /* command line is "redeem"  */
    redeemOperation=0;
  } else if (FnIsCommand(redeemKey,"examine")) {
    redeemOperation=1;
  } else {
    return;
  }
  /* it's a REDEEM/VALUE command all righty! */
  /* mark return code as "yes, we processed this cmd" 
   * however, mark as a "-1": ie: we haven't done anything yet.
   */
  Return->iInt=-1;  

  /* no objects to sell / examine */ 
  if (!*cmd) {
    if (redeemOperation) { /* empty examine command */
      SendAction("^aYou try to get $n to examine NOTHING.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to get $n to examine NOTHING.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n says, \"Nothing would be worth, hm, let me see... NOTHING!\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n says to $N, \"Nothing would be worth, hm, let me see... NOTHING!\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    } else { /* empty sell command */
      SendAction("^aYou try to redeem NOTHING to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to redeem NOTHING to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n asks, \"Ah, yes, but what would you like to redeem?\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n asks $N, \"Ah, yes, but what would you like to redeem?\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    }
    return;
  } /* no params to redeem/examine command */

  /* Parse player's command line */
  ParseFind(cmd, redeemKey, &redeemOffset, &redeemNum, NULL, NULL);
  /* redeemNum==-1 means redeem all */

  /* now loop through the player's inventory - use ThingFind to process
   * our command line appropriately */
  /* Look for the first Object */
  thing = CharThingFind(Thing(Param[1].iPtr), redeemKey, -1, Thing(Param[1].iPtr), TF_OBJINV, &redeemOffset);
  if (!thing) {
    /* Hmmmm... player doesn't have that item... sorry! */
    if (redeemOperation) { /* examine command */
      SendAction("^aYou want to examine what?!\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to have $n examine a non-existent item.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n says, \"That particular imaginary merchandise is worth 24884727c.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, \"That particular imaginary merchandise is worth 23883727c.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    } else {
      SendAction("^aYou try to redeem what?! to $n.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$N tries to get $n redeem a non-existent item.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^p$n says, \"Hmmm, sorry, I don't deal in imaginary merchandise.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, \"Hmmm, sorry, I don't deal in imaginary merchandise.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    }
    return;
  } 
  while (thing && redeemNum != 0) {
    /* plr has it in their inventory! */

    SendAction("^a$n checks out $A $N...\n",
      Param[0].iPtr,thing,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    /* First, check if object IS a scanner, and if it is, check that */
    /* it scans material we are interested in.                       */
    if (Obj(thing)->oTemplate->oType!=OTYPE_SCANNER) {
      SendAction("^p$n says, \"Hmmm, this isn't a scanner.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
      SendAction("^p$n tells $N, \"Hmmm, this isn't a scanner.\"\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
    } else {
      sFlag       = OBJECTGETFIELD(thing, OF_SCANNER_SFLAG);
      bioScanned  = OBJECTGETFIELD(thing, OF_SCANNER_BIO);
      chipScanned = OBJECTGETFIELD(thing, OF_SCANNER_CHIP);
      if ( ((BIT(flag,OR_CHIP)) && (BIT(sFlag,OSF_SCANCHIP)) )
       ||  ((BIT(flag,OR_BIO )) && (BIT(sFlag,OSF_SCANBIO )) )
      ) {
        /* OK! We have a scanner, and we deal in it's type. */
        /* Let's do the redeem/examine!                       */

        /* precalc redeemPrice */
        redeemPrice=0;
        if (BIT(flag,OR_BIO))
          redeemPrice+=bioScanned;
        if (BIT(flag,OR_CHIP))
          redeemPrice+=chipScanned;
          
        /* We have our redeem/examine price */
        if (redeemOperation) { /* examine command */
          SendAction("^p$n says, ",Param[0].iPtr,Param[1].iPtr,
            SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
          SendAction("^p$n tells $N, ",Param[0].iPtr,Param[1].iPtr,
            SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
          sprintf(bufPlr,"\"The scans in this $N is worth %ldc.\"\n",redeemPrice);
          SendAction(bufPlr,Param[1].iPtr,thing,
            SEND_SRC|SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
        } else { /* redeem command */
          /* Next, check if we have the cash (and are willing to spend it)
           * for this item. Note: MOBS will not buy scans if the purchase
           * price is more than 50% of their remaining cash. This prevents the
           * MOB from buying one thing, and being stuck with it, basically 
           * disabled because it can't buy any more, and nobody can buy the item.
           */
          /* check money situation */
          /* not enough cash to cover it */
          if ((redeemPrice>(Character(Param[0].iPtr)->cMoney>>1))
           &&(!(BIT(flag,OR_RICH)))) {
            SendAction("^p$n says, ",Param[0].iPtr,Param[1].iPtr,
              SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
            SendAction("^p$n tells $N, ",Param[0].iPtr,Param[1].iPtr,
              SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
            SendAction("\"I don't have enough cash to buy these scans.\"\n",
              Param[1].iPtr,thing,
              SEND_SRC|SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
          } else {
            /* exchange cash */
            /* exchange cash */
            Character(Param[1].iPtr)->cMoney+=redeemPrice;
            if (!(BIT(flag,OR_RICH)))
              Character(Param[0].iPtr)->cMoney-=redeemPrice;
            if (Return->iInt==-1)
              Return->iInt=0;  
            Return->iInt+=redeemPrice;
            /* Empty scanner */
            if (BIT(flag,OR_BIO))
              OBJECTSETFIELD(thing,OF_SCANNER_BIO, 0);
            if (BIT(flag,OR_CHIP))
              OBJECTSETFIELD(thing,OF_SCANNER_CHIP,0);

            /* announce what happened */
            SendAction("^pYou redeem $N",Param[1].iPtr,thing,
              SEND_SRC|SEND_AUDIBLE|SEND_CAPFIRST);
            SendAction("^p$n redeems $N",Param[1].iPtr,thing,
              SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
            sprintf(bufPlr," for %ldc.\n",redeemPrice);
            SendAction(bufPlr,Param[1].iPtr,thing,
              SEND_SRC|SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
          }
        }
        /* in case no transaction actually occurred, make sure we mark as
         * parsing this command */
        if (!Return->iInt)
          Return->iInt=-1;
      } else {
        SendAction("^p$n says, \"I don't deal in this type of scanner.\"\n",
          Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_AUDIBLE|SEND_CAPFIRST);
        SendAction("^p$n tells $N, \"I don't deal in this type of scanner.\"\n",
          Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_AUDIBLE|SEND_CAPFIRST);
      }
    }
    /* setup for next object to sell/examine */
    thing = CharThingFind(Thing(Param[1].iPtr), redeemKey, -1, Thing(Param[1].iPtr), TF_OBJINV|TF_CONTINUE, &redeemOffset);
    if (redeemNum>0) redeemNum--;
  } /* while Thing Find Loop */
} /* FnObjectRedeem */

/***********************************************************************
 *
 * Character functions
 *
 ***********************************************************************/

FNPROC(FnCharThingFind) {
  BYTE *key;

  if (Param[1].iPtr)
    key=Str(Param[1].iPtr)->sText;   /* key string   */
  else
    key=NULL;

  Return->iPtr=CharThingFind(
    Param[0].iPtr,
    key,                                /* key string   */
    Param[2].iInt,                      /* virtual #    */
    Param[3].iPtr,                      /* search thing */
    Param[4].iInt,                      /* search flag  */
    &(Param[5].iInt));                  /* offset (ie Nth item) */
}

FNPROC(FnCharAction) { /* send a command by someone */
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if ( (((THING*)(Param[0].iPtr))->tType==TTYPE_PLR)||
         (((THING*)(Param[0].iPtr))->tType==TTYPE_MOB) ) {
      Return->iInt=ParseCommand(Param[0].iPtr,Str(Param[1].iPtr)->sText);
    } else Return->iInt=0;
  } else Return->iInt=0;
}

FNPROC(FnCharActionStrip) { /* send a command by someone - strip first word */
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if ( (((THING*)(Param[0].iPtr))->tType==TTYPE_PLR)||
         (((THING*)(Param[0].iPtr))->tType==TTYPE_MOB) ) {
      Return->iInt=ParseCommand(Param[0].iPtr,StrOneWord(Str(Param[1].iPtr)->sText,NULL));
    } else Return->iInt=0;
  } else Return->iInt=0;
}

FNPROC(FnCharActionStr) { /* send a command by someone */
  BYTE cmd[FBUF_COMMON_SIZE];
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if ((((THING*)(Param[0].iPtr))->tType==TTYPE_PLR)||
        (((THING*)(Param[0].iPtr))->tType==TTYPE_MOB)) {
      FnProcessStrSubstitute(cmd,FBUF_COMMON_SIZE,
        Str(Param[1].iPtr)->sText, &(Param[2]));
      Return->iInt=ParseCommand(Param[0].iPtr,cmd);
    } else Return->iInt=0;
  } else Return->iInt=0;
}


/* gets Char's current level */
FNPROC(FnCharGetLevel) {
  Return->iInt=0;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      Return->iInt=Character(Param[0].iPtr)->cLevel;
    }
}

/* gets Char's current Hit Points */
FNPROC(FnCharGetHitP) {
  Return->iInt=0;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      Return->iInt=Character(Param[0].iPtr)->cHitP;
    }
}

/* sets Char's current Hit Points */
FNPROC(FnCharSetHitP) {
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      if (Param[1].iInt>CharGetHitPMax(Param[0].iPtr))
        Character(Param[0].iPtr)->cHitP = CharGetHitPMax(Param[0].iPtr);
      else
        Character(Param[0].iPtr)->cHitP = Param[1].iInt;
      Return->iInt = Character(Param[0].iPtr)->cHitP;
    }
}

/* Gets the Characters current Hit Point Max */
FNPROC(FnCharGetHitPMax) {
  Return->iInt=0;
  if (Param[0].iPtr) /* errorchecking by CharGetHitP */
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      Return->iInt=CharGetHitPMax(Param[0].iPtr);
}

FNPROC(FnCharGetMovePMax) {
  Return->iInt=0;
  if (Param[0].iPtr) /* errorchecking by CharGetHitP */
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      Return->iInt=CharGetMovePMax(Param[0].iPtr);
}

FNPROC(FnCharGetPowerPMax) {
  Return->iInt=0;
  if (Param[0].iPtr) /* errorchecking by CharGetHitP */
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      Return->iInt=CharGetPowerPMax(Param[0].iPtr);
}

FNPROC(FnCharGetMoney) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      Return->iInt=Character(Param[0].iPtr)->cMoney;
    }
  }
}

/* Set Money to a parm determined amount */
FNPROC(FnCharSetMoney) {
  if (Param[0].iPtr) {
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      Return->iInt = Character(Param[0].iPtr)->cMoney;
      Character(Param[0].iPtr)->cMoney=Param[1].iInt;
    }
  }
}
FNPROC(FnCharGetExp) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      Return->iInt=Character(Param[0].iPtr)->cExp;
    }
  }
}


/* this player gets experience */
FNPROC(FnCharGainExp) {
  Return->iInt = 0;
  if (Param[0].iPtr) {
    if (Thing(Param[0].iPtr)->tType==TTYPE_PLR) {
      Return->iInt = Character(Param[0].iPtr)->cExp;
      PlayerGainExp(Param[0].iPtr, Param[1].iInt);
    }
  }
}

/* group splits experience */
FNPROC(FnCharGainExpGroup) {
  Return->iInt = 0;
  if (Param[0].iPtr) {
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      CharGainExpFollow(Param[0].iPtr, Param[1].iInt);
    }
  }
}


FNPROC(FnCharPractice) {
  Return->iInt = 0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR)||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      if (FnIsCommand(Str(Param[1].iPtr)->sText,"practice")) {
        /* this is indeed a valid practice call */
        Return->iInt = 1;
        SkillPractice(Param[0].iPtr,Str(Param[1].iPtr)->sText,
          Param[2].iInt,Param[3].iInt,Param[4].iInt);
      }
    }
  }
}

FNPROC(FnCharGetFighting) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      Return->iPtr=Character(Param[0].iPtr)->cFight;
}

FNPROC(FnCharIsLeader) {
  Return->iInt=0;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      if ((Character(Param[0].iPtr)->cLead)==Param[0].iPtr)
        Return->iInt=1;
}

FNPROC(FnCharGetLeader) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      Return->iPtr=Character(Param[0].iPtr)->cLead;
}

FNPROC(FnCharGetFollower) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      Return->iPtr=Character(Param[0].iPtr)->cFollow;
}

FNPROC(FnCharAddFollower) {
  Return->iPtr=NULL;
  if (Param[0].iPtr && Param[0].iPtr)
    if ( (Thing(Param[0].iPtr)->tType==TTYPE_PLR ||
          Thing(Param[0].iPtr)->tType==TTYPE_MOB)
       &&(Thing(Param[1].iPtr)->tType==TTYPE_PLR ||
          Thing(Param[1].iPtr)->tType==TTYPE_MOB)
       )
      CharAddFollow(Param[0].iPtr, Param[1].iPtr);
}

FNPROC(FnCharRemoveFollower) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      CharRemoveFollow(Param[0].iPtr);
}

/***********************************************************************
 *
 * Player functions
 *
 ***********************************************************************/
FNPROC(FnPlayerWrite) {
  THING *inside;
  THING *player;

  player = Param[0].iPtr;
  if (player && player->tType==TTYPE_PLR) {
    inside = Base(player)->bInside;
    if (inside && inside->tType==TTYPE_WLD)
      Plr(player)->pDeathRoom = Wld(inside)->wVirtual;
    PlayerWrite(player,PWRITE_CLONE);
  }
}

FNPROC(FnPlayerGetBank) {
  Return->iInt=0;
  if (Param[0].iPtr)
    if (Thing(Param[0].iPtr)->tType==TTYPE_PLR) {
      Return->iInt=Plr(Param[0].iPtr)->pBank;
    }
}

FNPROC(FnPlayerSetBank) {
  Return->iInt=0;
  if (Param[0].iPtr)
    if (Thing(Param[0].iPtr)->tType==TTYPE_PLR) {
      Return->iInt=Plr(Param[0].iPtr)->pBank;
      Plr(Param[0].iPtr)->pBank=Param[1].iInt;
    }
}

FNPROC(FnPlayerBank) {
  BYTE          *cmd;
  WORD           bankOperation;
  BYTE           bankKey[FBUF_COMMON_SIZE];
  LWORD          bankOffset;
  LWORD          bankNum;
  BYTE           bankBuf[FBUF_COMMON_SIZE];

  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)&&(Param[2].iPtr))
    if (Thing(Param[1].iPtr)->tType==TTYPE_PLR) {
      cmd=StrOneWord(Str(Param[2].iPtr)->sText,bankKey);
    } else {
      return;
    }

  /* ok, we've got a PLR, and a command line to parse. */
  /* Let's take a look at the command line and see if the PLR */
  /* wants to DEPOSIT something */
  if (strlen(bankKey)<2) 
    return;

  if (FnIsCommand(bankKey,"deposit")) {
    bankOperation=0;
  } else if (FnIsCommand(bankKey,"withdraw")) {
    bankOperation=1;
  } else if (FnIsCommand(bankKey,"balance")) {
    bankOperation=2;
  } else {
    return;
  }
  /* it's a bank command all righty! */
  /* mark return code as "yes, we processed this cmd" */
  Return->iInt=1;

  /* Parse player's command line */
  ParseFind(cmd, bankKey, &bankOffset, &bankNum, NULL, NULL);
  /* bankNum==-1 means buy all */

  if (bankOperation==0) { /* deposit */
    if (bankNum==-1)
      bankNum=Character(Param[1].iPtr)->cMoney;

    if (bankNum<0) 
      bankNum=0;

    if (Thing(Param[0].iPtr)->tType==TTYPE_OBJ) {
      if (bankNum>Character(Param[1].iPtr)->cMoney) {
        sprintf(bankBuf,"^aThe $n shows \"Insufficient funds provided for deposit.\"\n"); 
        SendAction(bankBuf,
          Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
        SendAction("^a$n shows something to $N.\n",
          Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
        return;
      }

      sprintf(bankBuf,"^aThe $n shows \"Deposit for account '$N' of %lic accepted. Thank you.\"\n",
        bankNum); 
      SendAction(bankBuf,
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$n shows something to $N.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    } else {
      if (bankNum>Character(Param[1].iPtr)->cMoney) {
        sprintf(bankBuf,"^p$n says \"It doesn't seem, $N, that you have that much cash.\"\n"); 
        SendAction(bankBuf,
          Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
        SendAction("^a$n says something to $N.\n",
          Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
        return;
      }
      sprintf(bankBuf,"^p$n says \"Thank you, $N. %lic will be added to your account.\"\n",
        bankNum); 
      SendAction(bankBuf,
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$n says something to $N.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    }
    /* now move money */
    Character(Param[1].iPtr)->cMoney-=bankNum;
    Plr(Param[1].iPtr)->pBank+=bankNum;
  } else if (bankOperation==1) { /* withdraw */
    if (bankNum==-1)
      bankNum=Plr(Param[1].iPtr)->pBank;
    
    if (bankNum<0) 
      bankNum=0;

    if (Thing(Param[0].iPtr)->tType==TTYPE_OBJ) {
      if (bankNum>Plr(Param[1].iPtr)->pBank) {
        sprintf(bankBuf,"^aThe $n shows \"Insufficient funds for withdrawal.\"\n"); 
        SendAction(bankBuf,
          Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
        SendAction("^a$n shows something to $N.\n",
          Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
        return;
      }
      sprintf(bankBuf,"^aThe $n shows \"Withdraw from account '$N' of %lic.\"\n",
        bankNum); 
      SendAction(bankBuf,
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$n shows something to $N.",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    } else {
      if (bankNum>Plr(Param[1].iPtr)->pBank) {
        sprintf(bankBuf,"^p$n says \"You don't have that many funds in your account, $N.\"\n"); 
        SendAction(bankBuf,
          Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
        SendAction("^a$n says something to $N.\n",
          Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
        return;
      }
      sprintf(bankBuf,"^p$n says \"Here you go, $N, %lic. Have a nice day.\"\n",bankNum); 
      SendAction(bankBuf,
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$n says something to $N.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    }
    /* now move money */
    Plr(Param[1].iPtr)->pBank-=bankNum;
    Character(Param[1].iPtr)->cMoney+=bankNum;

  } else { /* Balance */
    if (Thing(Param[0].iPtr)->tType==TTYPE_OBJ) {
      sprintf(bankBuf,"^aThe $n shows \"Account Balance for $N: %lic\"\n",
        Plr(Param[1].iPtr)->pBank); 
      SendAction(bankBuf,
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$n shows something to $N.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    } else {
      sprintf(bankBuf,"^p$n says \"$N, your account balance is %lic.\"\n",
        Plr(Param[1].iPtr)->pBank); 
      SendAction(bankBuf,
        Param[0].iPtr,Param[1].iPtr,SEND_DST|SEND_VISIBLE|SEND_CAPFIRST);
      SendAction("^a$n says something to $N.\n",
        Param[0].iPtr,Param[1].iPtr,SEND_ROOM|SEND_VISIBLE|SEND_CAPFIRST);
    }
  }
}

FNPROC(FnPlayerGainPractice) {
  Return->iInt = 0;
  if (Param[0].iPtr) {
    if (Thing(Param[0].iPtr)->tType==TTYPE_PLR) {
      BYTE buf[256];

      Return->iInt = Plr(Param[0].iPtr)->pPractice;
      Plr(Param[0].iPtr)->pPractice += Param[1].iInt;
      sprintf(buf, "^wYou gain %ld practices (You now have %ld available)\n", 
        Param[1].iInt,
        Plr(Param[0].iPtr)->pPractice
      );
      SendThing(buf, Param[0].iPtr);
    }
  }
}

/* this player gets fame */
FNPROC(FnPlayerGainFame) {
  THING * player;

  Return->iInt = 0;
  player = Param[0].iPtr;
  if (player) {
    if (player->tType==TTYPE_PLR) {
      LWORD fame = Param[1].iInt;

      MINSET( fame, Plr(player)->pFame*-1 );
      MAXSET( fame, 3 );
      PlayerGainFame(player, fame);
      Return->iInt = Plr(player)->pFame;
    }
  }
}

/* this player gets infamy */
FNPROC(FnPlayerGainInfamy) {
  THING * player;

  Return->iInt = 0;
  player = Param[0].iPtr;
  if (player) {
    if (player->tType==TTYPE_PLR) {
      LWORD infamy = Param[1].iInt;

      MINSET( infamy, Plr(player)->pInfamy*-1 );
      MAXSET( infamy, 3 );
      PlayerGainInfamy(player, infamy, NULL);
      Return->iInt = Plr(player)->pInfamy;
    }
  }
}

/* player, message, exp, fame, property, value */
FNPROC(FnPlayerSolvedQuest) {
  THING *player;
  LWORD  fame = Param[3].iInt;

  Return->iInt = 0;
  player = Param[0].iPtr;
  if (!player || player->tType!=TTYPE_PLR) return;
  if (!Param[4].iPtr) return;
  if (PropertyGetLWord(player, Str(Param[4].iPtr)->sText, 0) >= Param[5].iInt) return;
  if (Param[1].iPtr)
    SendThing(Str(Param[1].iPtr)->sText,Param[0].iPtr);
  PropertySetLWord(player, Str(Param[4].iPtr)->sText, Param[5].iInt);
  PlayerGainExp(Param[0].iPtr, Param[2].iInt);

  MINSET( fame, Plr(player)->pFame*-1 );
  MAXSET( fame, 3 );
  PlayerGainFame(player, fame);
  Return->iInt = Param[5].iInt;
}

FNPROC(FnPlayerGetRace) {
  Return->iInt=0;
  if (Param[0].iPtr)
    if (Thing(Param[0].iPtr)->tType==TTYPE_PLR) {
      Return->iInt=Plr(Param[0].iPtr)->pRace;
    }
}


/***********************************************************************
 *
 * Fight functions
 *
 ***********************************************************************/
FNPROC(FnFightStop) {
  if (Param[0].iPtr)
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB))
      FightStop(Param[0].iPtr);
}

FNPROC(FnFightStart) {
  if (Param[0].iPtr&&Param[1].iPtr)
    if (((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) &&
        ((Thing(Param[1].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[1].iPtr)->tType==TTYPE_MOB))) 
      FightStart(Param[0].iPtr,Param[1].iPtr,1,NULL);
}

FNPROC(FnFightDamage) {
  Return->iInt=0;
  if (Param[0].iPtr&&Param[5].iPtr) {
    if ((Thing(Param[0].iPtr)->tType==TTYPE_PLR) ||
        (Thing(Param[0].iPtr)->tType==TTYPE_MOB)) {
      Return->iInt=FightDamage(Param[0].iPtr,Param[1].iInt,Param[2].iInt,Param[3].iInt,
        Param[4].iInt,Str(Param[5].iPtr)->sText);
    } 
  }
}

FNPROC(FnFightDamageRaw) {
  Return->iInt=0;
  if ((Param[0].iPtr) && (Thing(Param[0].iPtr)->tType<TTYPE_CHARACTER))
    return;
  if ((!Param[1].iPtr) ||(Thing(Param[1].iPtr)->tType<TTYPE_CHARACTER))
    return;

  /* Thing, Target, Damage to do - thing is optional (ie for crediting xp for a kill */
  Return->iInt=FightDamagePrimitive(Param[0].iPtr,Param[1].iPtr,Param[2].iInt);
}


FNPROC(FnMobileCreate) {
  Return->iPtr=NULL;
  if (Param[0].iPtr) 
    if (Thing(Param[0].iPtr)->tType==TTYPE_WLD) {
      Return->iPtr=MobileCreate(MobileOf(Param[1].iInt),Param[0].iPtr);
      FnRemoveFromRegistry(Return->iPtr);
    }
}

FNPROC(FnMobileGetVirtual) {
  Return->iInt=0;
  if (Param[0].iPtr) 
    if (Thing(Param[0].iPtr)->tType==TTYPE_MOB) {
      Return->iInt=Mob(Param[0].iPtr)->mTemplate->mVirtual;
    }
}


FNPROC(FnMobileCreateNum) {
  ULWORD i;
 
  Return->iPtr=NULL;
  if (Param[2].iInt<1)
    return;
  if (Param[2].iInt>200)
    Param[2].iInt=200;
  if (!Param[0].iPtr)
    return;

  if (Thing(Param[0].iPtr)->tType==TTYPE_WLD)
    for (i=0;i<Param[2].iInt;i++) {
      Return->iPtr=MobileCreate(MobileOf(Param[1].iInt),Param[0].iPtr);
      FnRemoveFromRegistry(Return->iPtr);
    }
}

FNPROC(FnMobileFree) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    if (Thing(Param[0].iPtr)->tType==TTYPE_MOB) {
      ThingFree(Thing(Param[0].iPtr));
      FnAddToRegistry(Param[0].iPtr);
      Return->iInt=1;
    }
  }
}

/***********************************************************************
 *
 * Exit functions
 *
 ***********************************************************************/

FNPROC(FnExitFind) {
  Return->iPtr=NULL;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    if (Thing(Param[0].iPtr)->tType==TTYPE_WLD) 
      Return->iPtr=ExitFind(Wld(Param[0].iPtr)->wExit,Str(Param[1].iPtr)->sText);
}

FNPROC(FnExitDir) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    if (Thing(Param[0].iPtr)->tType==TTYPE_WLD) 
      Return->iPtr=ExitDir(Wld(Param[0].iPtr)->wExit,Param[1].iInt);
}

FNPROC(FnExitReverse) {
  Return->iPtr=NULL;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    if (Thing(Param[0].iPtr)->tType==TTYPE_WLD) 
      Return->iPtr=ExitReverse(Param[0].iPtr,Param[1].iPtr);
}

FNPROC(FnExitIsCorner) {
  Return->iInt=EDIR_UNDEFINED;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    Return->iInt=ExitIsCorner(Param[0].iPtr,Param[1].iPtr);
}

FNPROC(FnExitFree) {
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    if (Thing(Param[0].iPtr)->tType==TTYPE_WLD) {
      FnAddToRegistry(Wld(Param[0].iPtr)->wExit);
      Wld(Param[0].iPtr)->wExit=ExitFree(Wld(Param[0].iPtr)->wExit,Param[1].iPtr);
      Return->iInt=1;
    }
}

FNPROC(FnExitCreate) {
  Return->iPtr=NULL;
  if ((Param[0].iPtr)&&(Param[6].iPtr))
    if ((Thing(Param[0].iPtr)->tType==TTYPE_WLD) &&
        (Thing(Param[6].iPtr)->tType==TTYPE_WLD)) {
      /* Ok, here we go. Gulp. */
      Wld(Param[0].iPtr)->wExit=Return->iPtr=ExitCreate(
        Wld(Param[0].iPtr)->wExit, /* eList */
        Param[1].iInt,             /* eDir */
        Param[2].iPtr?Str(Param[2].iPtr)->sText:NULL, /* eKey */
        Param[3].iPtr?Str(Param[3].iPtr)->sText:NULL, /* eDesc */
        Param[4].iInt,             /* eFlag */
        Param[5].iInt,             /* eKeyObj */
        Param[6].iPtr);            /* eWorld */
      FnRemoveFromRegistry(Return->iPtr);
    }
}


FNPROC(FnExitGetDir) {
  Return->iInt=EDIR_UNDEFINED;
  if (Param[0].iPtr)
    Return->iInt=Exit(Param[0].iPtr)->eDir;
}

FNPROC(FnExitSetDir) {
  LWORD i;
  Return->iInt=EDIR_UNDEFINED;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    if (Thing(Param[0].iPtr)->tType==TTYPE_WLD) 
      if ((Param[2].iInt>EDIR_MIN)&&(Param[2].iInt<EDIR_UNDEFINED)) {
        /* Check if this direction exists - abort if it does */
        i=Exit(Param[1].iPtr)->eDir;
        Exit(Param[1].iPtr)->eDir=EDIR_UNDEFINED;
        if (ExitDir(Wld(Param[1].iPtr)->wExit,Param[2].iInt)) {
          Exit(Param[1].iPtr)->eDir=i;
          return; 
        }
        Return->iInt=Exit(Param[1].iPtr)->eDir;
        Exit(Param[1].iPtr)->eDir=Param[2].iInt;
      }
}

FNPROC(FnExitGetFlag) {
  Return->iInt=0;
  if (Param[0].iPtr)
    Return->iInt=Exit(Param[0].iPtr)->eFlag;
}

FNPROC(FnExitSetFlag) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    Return->iInt=Exit(Param[0].iPtr)->eFlag;
    Exit(Param[0].iPtr)->eFlag=Param[1].iInt;
  }
}


FNPROC(FnExitGetKeyObj) {
  Return->iInt=0;
  if (Param[0].iPtr)
    Return->iInt=Exit(Param[0].iPtr)->eKeyObj;
}

FNPROC(FnExitSetKeyObj) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    Return->iInt=Exit(Param[0].iPtr)->eKeyObj;
    Exit(Param[0].iPtr)->eKeyObj=Param[1].iInt;
  }
}

FNPROC(FnExitGetKey) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    Return->iPtr=Exit(Param[0].iPtr)->eKey;
}

FNPROC(FnExitSetKey) {
  STR *s;
  Return->iInt=0;
  if (Param[0].iPtr) {
    if (!Param[1].iPtr) {
      if (Exit(Param[0].iPtr)->eKey) {
        FnAddToRegistry(Exit(Param[0].iPtr)->eKey);
        StrFree(Exit(Param[0].iPtr)->eKey);
      }
      Exit(Param[0].iPtr)->eKey=Param[1].iPtr;
      Return->iInt=1;
    } else if ((s=StrAlloc(Param[1].iPtr))) {
      FnRemoveFromRegistry(s);
      if (Exit(Param[0].iPtr)->eKey) {
        FnAddToRegistry(Exit(Param[0].iPtr)->eKey);
        StrFree(Exit(Param[0].iPtr)->eKey);
      }
      Exit(Param[0].iPtr)->eKey=s;
      Return->iInt=1;
    }
  }
}

FNPROC(FnExitGetDesc) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    Return->iPtr=Exit(Param[0].iPtr)->eDesc;
}

FNPROC(FnExitSetDesc) {
  STR *s;
  Return->iInt=0;
  if (Param[0].iPtr) {
    if (!Param[1].iPtr) {
      if (Exit(Param[0].iPtr)->eDesc) {
        FnAddToRegistry(Exit(Param[0].iPtr)->eDesc);
        StrFree(Exit(Param[0].iPtr)->eDesc);
      }
      Exit(Param[0].iPtr)->eDesc=Param[1].iPtr;
      Return->iInt=1;
    } else if ((s=StrAlloc(Param[1].iPtr))) {
      FnRemoveFromRegistry(s);
      if (Exit(Param[0].iPtr)->eDesc) {
        FnAddToRegistry(Exit(Param[0].iPtr)->eDesc);
        StrFree(Exit(Param[0].iPtr)->eDesc);
      }
      Exit(Param[0].iPtr)->eDesc=s;
      Return->iInt=1;
    }
  }
}

FNPROC(FnExitGetWorld) {
  Return->iPtr=NULL;
  if (Param[0].iPtr)
    Return->iPtr=Exit(Param[0].iPtr)->eWorld;
}

FNPROC(FnExitSetWorld) {
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr))
    if (Thing(Param[1].iPtr)->tType==TTYPE_WLD) {
      Return->iInt=1;
      Exit(Param[0].iPtr)->eWorld=Param[1].iPtr;
    }
}

FNPROC(FnExitGetFlagBit) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    Return->iInt=(Exit(Param[0].iPtr)->eFlag)&Param[1].iInt;
  }
}

FNPROC(FnExitSetFlagBit) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    Return->iInt=(Exit(Param[0].iPtr)->eFlag)&Param[1].iInt;
    BITSET(Exit(Param[0].iPtr)->eFlag,Param[1].iInt);
  }
}

FNPROC(FnExitClearFlagBit) {
  Return->iInt=0;
  if (Param[0].iPtr) {
    Return->iInt=(Exit(Param[0].iPtr)->eFlag)&Param[1].iInt;
    BITCLR(Exit(Param[0].iPtr)->eFlag,Param[1].iInt);
  }
}

FNPROC(FnDoorSetFlag) {
  EXIT *e;
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    Return->iInt=Exit(Param[1].iPtr)->eFlag;
    Exit(Param[1].iPtr)->eFlag=Param[2].iInt;
    e=ExitReverse(Param[0].iPtr,Param[1].iPtr);
    if (e) {
      Exit(e)->eFlag=Param[2].iInt;
    }
  }
}

FNPROC(FnDoorSetFlagBit) {
  EXIT *e;
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    Return->iInt=(Exit(Param[1].iPtr)->eFlag)&Param[2].iInt;
    BITSET(Exit(Param[1].iPtr)->eFlag,Param[2].iInt);
    e=ExitReverse(Param[0].iPtr,Param[1].iPtr);
    if (e) {
      BITSET(Exit(e)->eFlag,Param[2].iInt);
    }
  }
}

FNPROC(FnDoorClearFlagBit) {
  EXIT *e;
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    Return->iInt=(Exit(Param[1].iPtr)->eFlag)&Param[2].iInt;
    BITCLR(Exit(Param[1].iPtr)->eFlag,Param[2].iInt);
    e=ExitReverse(Param[0].iPtr,Param[1].iPtr);
    if (e) {
      BITCLR(Exit(e)->eFlag,Param[2].iInt);
    }
  }
}

FNPROC(FnDoorSetKeyObj) {
  EXIT *e;
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    Return->iInt=Exit(Param[1].iPtr)->eKeyObj;
    Exit(Param[1].iPtr)->eKeyObj=Param[2].iInt;
    e=ExitReverse(Param[0].iPtr,Param[1].iPtr);
    if (e) {
      Exit(e)->eKeyObj=Param[2].iInt;
    }
  }
}



/***********************************************************************
 *
 * Property functions
 *
 ***********************************************************************/

/* PropertySetStr - creates a property with the specified Key and Desc */
/* int PropertyCreateStr(thing,key,desc); 
 * returns 1 if successful, 0 if an error occurred. */
FNPROC(FnPropertySetStr) {
  PROPERTY *i;

  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)&&(Param[2].iPtr)) {
    if (*(Str(Param[1].iPtr)->sText)=='@')
      return;

    /* First, find & delete any properties with the same
     * key list as this new property */
    while ( (i=PropertyFind(Thing(Param[0].iPtr)->tProperty, Str(Param[1].iPtr)->sText)) ) {
      Thing(Param[0].iPtr)->tProperty = PropertyFree(Thing(Param[0].iPtr)->tProperty, i);
    }

    /* Now, let's create our "property" */
    Thing(Param[0].iPtr)->tProperty=PropertySet(Thing(Param[0].iPtr)->tProperty,
      Str(Param[1].iPtr)->sText,Str(Param[2].iPtr)->sText);
    Return->iInt=1;
  }
}

/* FnPropertySetInt - creates a property with the specified Key and Int 
 *                       as the Text.
 * int PropertySetStr(thing,key,text); 
 * returns the EXTRA of the newly created property. */
FNPROC(FnPropertySetInt) {

  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if (*(Str(Param[1].iPtr)->sText)=='@')
      return;

    PropertySetLWord(Thing(Param[0].iPtr), Str(Param[1].iPtr)->sText, Param[2].iInt);
    Return->iInt=1;
  }
}

/* PropertyExist - returns 1 (TRUE) if property exists, 0 (FALSE) otherwise.*/
/* NOTES:
  Cant find code properties, ie @ properties 
  Cant find LWORD type properties inherited from mob/obj template, ie % properties
*/
FNPROC(FnPropertyExist) {
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if (*(Str(Param[1].iPtr)->sText)=='@')
      return;
    if (PropertyFind(Thing(Param[0].iPtr)->tProperty, Str(Param[1].iPtr)->sText))
      Return->iInt=1;
  }
}

/* PropertyGetInt - Finds the property with the specified Key, and parses 
 *                  the text as an int, returning this value. Returns 0 if
 *                  the property doesn't exist. */
FNPROC(FnPropertyGetInt) {
  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if (*(Str(Param[1].iPtr)->sText)=='@')
      return;
    Return->iInt = PropertyGetLWord(Thing(Param[0].iPtr), Str(Param[1].iPtr)->sText, 0);
  }
}

/* PropertyGetStr - Finds the property with the specified Key, and returns
 *                  the Text as a STR pointer. Returns NULL if property not 
 *                  found. */
FNPROC(FnPropertyGetStr) {
  PROPERTY *i;

  Return->iPtr=NULL;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if (*(Str(Param[1].iPtr)->sText)=='@')
      return;
    i = PropertyFind(Thing(Param[0].iPtr)->tProperty, Str(Param[1].iPtr)->sText);
    if (i) {
      Return->iPtr=i->pDesc;
    }
  }
}

/* PropertyFree - Finds the specified property and Frees it. 
 *                Returns 1 when successful, 0 if the property doesn't exist */
FNPROC(FnPropertyFree) {
  PROPERTY *i;

  Return->iInt=0;
  if ((Param[0].iPtr)&&(Param[1].iPtr)) {
    if (*(Str(Param[1].iPtr)->sText)=='@')
      return;
    /* Find & delete any properties with the same
     * key list as the specified property */
    i = PropertyFind(Thing(Param[0].iPtr)->tProperty, Str(Param[1].iPtr)->sText);
    if (i) {
      Thing(Param[0].iPtr)->tProperty = PropertyFree(Thing(Param[0].iPtr)->tProperty,i);
      Return->iInt=1;
    }
  }
}

/******************* Table Declarations *****************/
FTABLETYPE fTable[]={
  /* System Functions */
  { "Version",           FnVersion,          CDT_INT,   {0}},
  { "C4Version",         FnC4Version,        CDT_INT,   {0}},
  { "Number",            FnNumber,           CDT_INT,   {CDT_INT,  CDT_INT,  0}},
  { "Dice",              FnDice,             CDT_INT,   {CDT_INT,  CDT_INT,  0}},

  /* string functions */
  { "StrCmp",            FnStrcmp,           CDT_INT,   {CDT_STR,  CDT_STR,  0}},
  { "StrICmp",           FnStricmp,          CDT_INT,   {CDT_STR,  CDT_STR,  0}},
  { "StrNCmp",           FnStrncmp,          CDT_INT,   {CDT_STR,  CDT_STR,  CDT_INT,0}},
  { "StrNICmp",          FnStrnicmp,         CDT_INT,   {CDT_STR,  CDT_STR,  CDT_INT,0}},
  { "StrLen",            FnStrlen,           CDT_INT,   {CDT_STR,  0}},
  { "StrIn",             FnStrIn,            CDT_INT,   {CDT_STR,  CDT_ETC,0}},
  { "StrAt",             FnStrAt,            CDT_INT,   {CDT_STR,  CDT_STR,  CDT_INT,0}},
  { "StrAtExact",        FnStrAtExact,       CDT_INT,   {CDT_STR,  CDT_STR,  CDT_INT,0}},
  { "StrIsCmd",          FnStrIsCmd,         CDT_INT,   {CDT_STR,  CDT_ETC,  0}},
  { "StrExact",          FnStrExact,         CDT_INT,   {CDT_STR,  CDT_STR,  0}},
  { "Hello",             FnHello,            CDT_INT,   {CDT_STR,  0}},
  { "YesNo",             FnYesNo,            CDT_INT,   {CDT_STR,  0}},
/* NOTE NOTE NOTE!!! StrFree or some equivalent function MUST NEVER be implemented! */
/* If you implement StrFree, the onus is on the C4 coder to make sure Str's etc.    */
/* are properly allocated, linked, & freed. IT'S DANGEROUS. Don't do it!            */
/* Example: suppose you did this in C4: StrFree("hello");                                */
/* You now have freed a str which was embedded in code!!! NOW YOU ARE FUCKED.       */
/* Just don't do it. Anyplace like ExitSetKey, the Str allocation and freeing       */
/* is setup by the function.c coder, NOT the C4 coder. That's the way it should be. */

  /* Send Functions */
  { "SendThing",         FnSendThing,        0,         {CDT_THING,CDT_STR,0}},
  { "SendThingStr",      FnSendThingStr,     0,         {CDT_THING,CDT_STR,CDT_ETC,0}},
  { "SendAction",        FnSendAction,       0,         {CDT_THING,CDT_THING,CDT_INT,CDT_STR,0}},
  { "SendActionStr",     FnSendActionStr,    0,         {CDT_THING,CDT_THING,CDT_INT,CDT_STR,CDT_ETC,0}},

  /* Extra functions */
  { "ExtraGetNext",      FnExtraGetNext,     CDT_EXTRA, {CDT_EXTRA,0}},
  { "ExtraGetKey",       FnExtraGetKey,      CDT_STR,   {CDT_EXTRA,0}},
  { "ExtraGetDesc",      FnExtraGetDesc,     CDT_STR,   {CDT_EXTRA,0}},
  { "ExtraFind",         FnExtraFind,        CDT_EXTRA, {CDT_THING,CDT_STR,0}},
  { "ExtraFree",         FnExtraFree,        CDT_INT,   {CDT_THING,CDT_EXTRA,0}},
  { "ExtraCreate",       FnExtraCreate,      CDT_EXTRA, {CDT_THING,CDT_STR,CDT_STR,0}},
  { "ExtraSetKey",       FnExtraSetKey,      CDT_EXTRA, {CDT_EXTRA,CDT_STR,0}},
  { "ExtraSetDesc",      FnExtraSetDesc,     CDT_EXTRA, {CDT_EXTRA,CDT_STR,0}},

  /* Thing Functions */
  { "ThingGetType",      FnThingGetType,     CDT_INT,   {CDT_THING,0}},
  { "ThingGetName",      FnThingGetName,     CDT_STR,   {CDT_THING,0}},
  { "ThingGetDesc",      FnThingGetDesc,     CDT_STR,   {CDT_THING,0}},
  { "ThingGetExtra",     FnThingGetExtra,    CDT_EXTRA, {CDT_THING,0}},
  { "ThingGetNext",      FnThingGetNext,     CDT_THING, {CDT_THING,0}},
  { "ThingGetContain",   FnThingGetContain,  CDT_THING, {CDT_THING,0}},
  { "ThingTo",           FnThingTo,          CDT_INT,   {CDT_THING,CDT_THING, 0}},
  { "ThingFind",         FnThingFind,        CDT_THING, {CDT_STR,CDT_INT,CDT_THING,CDT_INT,CDT_INT,0}},
  { "ThingGetWait",      FnThingGetWait,     CDT_INT,   {CDT_THING,0}},
  { "ThingSetWait",      FnThingSetWait,     CDT_INT,   {CDT_THING,CDT_INT, 0}},
  { "ThingGetIdleWait",  FnThingGetIdleWait, CDT_INT,   {CDT_THING,0}},
  { "ThingSetIdleWait",  FnThingSetIdleWait, CDT_INT,   {CDT_THING,CDT_INT, 0}},

  /* World Functions */
  { "WorldOf",           FnWorldOf,          CDT_THING, {CDT_INT,  0}},
  { "WorldGetVirtual",   FnWorldGetVirtual,  CDT_INT,   {CDT_THING,  0}},
  { "WorldGetFlag",      FnWorldGetFlag,     CDT_INT,   {CDT_THING,  0}},
  { "WorldSetFlag",      FnWorldSetFlag,     CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "WorldGetFlagBit",   FnWorldGetFlagBit,  CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "WorldSetFlagBit",   FnWorldSetFlagBit,  CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "WorldClearFlagBit", FnWorldClearFlagBit,CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "WorldGetType",      FnWorldGetType,     CDT_INT,   {CDT_THING,  0}},
  { "WorldSetType",      FnWorldSetType,     CDT_INT,   {CDT_THING,CDT_INT,  0}},

  /* Base Functions */
  { "BaseGetKey",        FnBaseGetKey,       CDT_STR,   {CDT_THING,0}},
  { "BaseGetLDesc",      FnBaseGetLDesc,     CDT_STR,   {CDT_THING,0}},
  { "BaseGetInside",     FnBaseGetInside,    CDT_THING, {CDT_THING,0}},
  { "BaseGetConWeight",  FnBaseGetConWeight, CDT_INT,   {CDT_THING,0}},
  { "BaseGetWeight",     FnBaseGetWeight,    CDT_INT,   {CDT_THING,0}},

  /* Object Functions */
  { "ObjectCreate",      FnObjectCreate,     CDT_THING, {CDT_THING,CDT_INT,  0}},
  { "ObjectCreateNum",   FnObjectCreateNum,  CDT_THING, {CDT_THING,CDT_INT,CDT_INT,  0}},
  { "ObjectFree",        FnObjectFree,       CDT_INT,   {CDT_THING,  0}},
  { "ObjectRemove",      FnObjectRemove,     CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "ObjectStrip",       FnObjectStrip,      CDT_INT,   {CDT_THING,CDT_INT,CDT_INT,CDT_THING,  0}},
  { "ObjectStripInv",    FnObjectStripInv,   CDT_INT,   {CDT_THING,CDT_INT,CDT_INT,CDT_THING,  0}},
  { "ObjectStripEqu",    FnObjectStripEqu,   CDT_INT,   {CDT_THING,CDT_INT,CDT_INT,CDT_THING,  0}},
  { "ObjectContain",     FnObjectContain,    CDT_THING, {CDT_THING,CDT_INT,  0}},
  { "ObjectCount",       FnObjectCount,      CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "ObjectCountInv",    FnObjectCountInv,   CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "ObjectCountEqu",    FnObjectCountEqu,   CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "ObjectBuy",         FnObjectBuy,        CDT_INT,   {CDT_THING,CDT_THING,CDT_STR,CDT_INT,CDT_ETC,  0}},
  { "ObjectSell",        FnObjectSell,       CDT_INT,   {CDT_THING,CDT_THING,CDT_STR,CDT_INT,CDT_ETC,  0}},
  { "ObjectGetVirtual",  FnObjectGetVirtual, CDT_INT,   {CDT_THING,  0}},
  { "ObjectRedeem",      FnObjectRedeem,     CDT_INT,   {CDT_THING,CDT_THING,CDT_STR,CDT_INT,CDT_INT,  0}},

  /* Character Functions */
  { "CharThingFind",     FnCharThingFind,    CDT_THING, {CDT_THING,CDT_STR,CDT_INT,CDT_THING,CDT_INT,CDT_INT,0}},
  { "CharAction",        FnCharAction,       CDT_INT,   {CDT_THING,CDT_STR,  0}},
  { "CharActionStrip",   FnCharActionStrip,  CDT_INT,   {CDT_THING,CDT_STR,  0}},
  { "CharActionStr",     FnCharActionStr,    CDT_INT,   {CDT_THING,CDT_STR,  CDT_ETC,0}},
  { "CharGetLevel",      FnCharGetLevel,     CDT_INT,   {CDT_THING,0}},
  { "CharGetHitP",       FnCharGetHitP,      CDT_INT,   {CDT_THING,0}},
  { "CharSetHitP",       FnCharSetHitP,      CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "CharGetHitPMax",    FnCharGetHitPMax,   CDT_INT,   {CDT_THING,0}},
  { "CharGetMovePMax",   FnCharGetMovePMax,  CDT_INT,   {CDT_THING,0}},
  { "CharGetPowerPMax",  FnCharGetPowerPMax, CDT_INT,   {CDT_THING,0}},
  { "CharGetMoney",      FnCharGetMoney,     CDT_INT,   {CDT_THING,0}},
  { "CharSetMoney",      FnCharSetMoney,     CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "CharGetExp",        FnCharGetExp,       CDT_INT,   {CDT_THING,0}},
  { "CharGainExp",       FnCharGainExp,      CDT_INT,   {CDT_THING,CDT_INT,  0}},
  { "CharGainExpGroup",  FnCharGainExpGroup, 0,         {CDT_THING,CDT_INT,  0}},
  { "CharPractice",      FnCharPractice,     CDT_INT,   {CDT_THING,CDT_STR,CDT_INT,CDT_INT,CDT_INT,  0}},
  { "CharGetFighting",   FnCharGetFighting,  CDT_THING, {CDT_THING,  0}}, 
  { "CharIsLeader",      FnCharIsLeader,     CDT_INT,   {CDT_THING,  0}},
  { "CharGetLeader",     FnCharGetLeader,    CDT_THING, {CDT_THING,  0}},
  { "CharGetFollower",   FnCharGetFollower,  CDT_THING, {CDT_THING,  0}},
  { "CharAddFollower",   FnCharAddFollower,  CDT_THING, {CDT_THING,CDT_THING, 0}},
  { "CharRemoveFollower",FnCharRemoveFollower,CDT_THING,{CDT_THING,  0}},

  { "PlayerWrite",       FnPlayerWrite,      0,         {CDT_THING,  0}},
  { "PlayerGetBank",     FnPlayerGetBank,    CDT_INT,   {CDT_THING,  0}},
  { "PlayerSetBank",     FnPlayerSetBank,    CDT_INT,   {CDT_THING,CDT_INT, 0}},
  { "PlayerBank",        FnPlayerBank,       CDT_INT,   {CDT_THING,CDT_THING,CDT_STR, 0}},
  /*{ "PlayerGainPractice",FnPlayerGainPractice,CDT_INT,  {CDT_THING,CDT_INT, 0}},*/
  { "PlayerGainFame",    FnPlayerGainFame,   CDT_INT,   {CDT_THING,CDT_INT, 0}},
  { "PlayerGainInfamy",  FnPlayerGainInfamy, CDT_INT,   {CDT_THING,CDT_INT, 0}},
  { "PlayerSolvedQuest", FnPlayerSolvedQuest,CDT_INT,   {CDT_THING,CDT_STR,CDT_INT,CDT_INT,CDT_STR,CDT_INT, 0}},
  { "PlayerGetRace",     FnPlayerGetRace,    CDT_INT,   {CDT_THING,  0}},

  { "FightStop",         FnFightStop,        0,         {CDT_THING,  0}},
  { "FightStart",        FnFightStart,       0,         {CDT_THING, CDT_THING, 0}},
  { "FightDamage",       FnFightDamage,      CDT_INT,   {CDT_THING, CDT_INT, CDT_INT, CDT_INT, CDT_INT, CDT_STR, 0}},
  { "FightDamageRaw",    FnFightDamageRaw,   CDT_INT,   {CDT_THING, CDT_THING, CDT_INT, 0}},
  
  { "MobileCreate",      FnMobileCreate,     CDT_THING, {CDT_THING,CDT_INT,  0}},
  { "MobileCreateNum",   FnMobileCreateNum,  CDT_THING, {CDT_THING,CDT_INT,CDT_INT,  0}},
  { "MobileFree",        FnMobileFree,       CDT_INT,   {CDT_THING,  0}},
  { "MobileGetVirtual",  FnMobileGetVirtual, CDT_INT,   {CDT_THING,  0}},

  { "PropertySetStr",    FnPropertySetStr,   CDT_INT,   {CDT_THING,CDT_STR,CDT_STR,   0}},
  { "PropertySetInt",    FnPropertySetInt,   CDT_INT,   {CDT_THING,CDT_STR,CDT_INT,   0}},
  { "PropertyExist",     FnPropertyExist,    CDT_INT,   {CDT_THING,CDT_STR,   0}},
  { "PropertyGetInt",    FnPropertyGetInt,   CDT_INT,   {CDT_THING,CDT_STR,   0}},
  { "PropertyGetStr",    FnPropertyGetStr,   CDT_STR,   {CDT_THING,CDT_STR,   0}},
  { "PropertyFree",      FnPropertyFree,     CDT_INT,   {CDT_THING,CDT_STR,   0}},

  { "ExitFind",          FnExitFind,         CDT_EXIT,  {CDT_THING,CDT_STR,   0}},
  { "ExitDir",           FnExitDir,          CDT_EXIT,  {CDT_THING,CDT_INT,   0}},
  { "ExitReverse",       FnExitReverse,      CDT_EXIT,  {CDT_THING,CDT_EXIT,  0}},
  { "ExitGetDir",        FnExitGetDir,       CDT_INT,   {CDT_EXIT,  0}},
  { "ExitSetDir",        FnExitSetDir,       CDT_INT,   {CDT_THING,CDT_EXIT,CDT_INT,  0}},
  { "ExitIsCorner",      FnExitIsCorner,     CDT_INT,   {CDT_EXIT, CDT_EXIT,  0}},
  { "ExitCreate",        FnExitCreate,       CDT_EXIT,  {CDT_THING, CDT_INT, CDT_STR, CDT_STR, CDT_INT, CDT_INT, CDT_THING, 0}},
  { "ExitFree",          FnExitFree,         CDT_INT,   {CDT_THING, CDT_EXIT,  0}},
  { "ExitGetFlag",       FnExitGetFlag,      CDT_INT,   {CDT_EXIT,  0}},
  { "ExitSetFlag",       FnExitSetFlag,      CDT_INT,   {CDT_EXIT, CDT_INT,  0}},
  { "ExitGetFlagBit",    FnExitGetFlagBit,   CDT_INT,   {CDT_EXIT, CDT_INT,  0}},
  { "ExitSetFlagBit",    FnExitSetFlagBit,   CDT_INT,   {CDT_EXIT, CDT_INT,  0}},
  { "ExitClearFlagBit",  FnExitClearFlagBit, CDT_INT,   {CDT_EXIT, CDT_INT,  0}},
  { "DoorSetFlag",       FnDoorSetFlag,      CDT_INT,   {CDT_THING, CDT_EXIT, CDT_INT,  0}},
  { "DoorSetFlagBit",    FnDoorSetFlagBit,   CDT_INT,   {CDT_THING, CDT_EXIT, CDT_INT,  0}},
  { "DoorClearFlagBit",  FnDoorClearFlagBit, CDT_INT,   {CDT_THING, CDT_EXIT, CDT_INT,  0}},
  { "ExitGetKeyObj",     FnExitGetKeyObj,    CDT_INT,   {CDT_EXIT,  0}},
  { "ExitSetKeyObj",     FnExitSetKeyObj,    CDT_INT,   {CDT_EXIT, CDT_INT,  0}},
  { "DoorSetKeyObj",     FnDoorSetKeyObj,    CDT_INT,   {CDT_THING, CDT_EXIT, CDT_INT,  0}},
  { "ExitGetKey",        FnExitGetKey,       CDT_STR,   {CDT_EXIT, 0}},
  { "ExitSetKey",        FnExitSetKey,       CDT_INT,   {CDT_EXIT, CDT_STR, 0}},
  { "ExitGetDesc",       FnExitGetDesc,      CDT_STR,   {CDT_EXIT, 0}},
  { "ExitSetDesc",       FnExitSetDesc,      CDT_INT,   {CDT_EXIT, CDT_STR, 0}},
  { "ExitGetWorld",      FnExitGetWorld,     CDT_THING, {CDT_EXIT, 0}},
  { "ExitSetWorld",      FnExitSetWorld,     CDT_INT,   {CDT_EXIT, CDT_THING, 0}},

  {  NULL,               NULL,               0,         {0}}
};