SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 * ------------------------------------------------------------------------ *
 * 			Gorog's Revenge on Unruly Bastards							    *
 ***************************************************************************/

#include	"stdafx.h"
#include	"smaug.h"
#include	"area.h"
#include	"mobiles.h"
#include	"objects.h"
#include	"rooms.h"
#include	"descriptor.h"
#include	"character.h"

#define  MAX_DISPLAY_LINES  14000      /* Size of Sort Array             */
#define  MAX_NAME_LENGTH       13
#define  MAX_SITE_LENGTH       16
#define  MAX_FIELD_LENGTH      20
#define  MAX_NUM_OPS           32
#define  GR_NUM_FIELDS         12
#define  GO_NUM_FIELDS         24
#define  UMIN(a,b)            ((a) < (b) ? (a) : (b))

enum OTypes {
	OCOUNT, OVNUM, OTYPE, OLEVEL, OWEAR, OAVG, OHR, ODR, OHP, OMP, OAC,
	OSTR, ODEX, OCON, OWIS, OINT, OLUCK, OSAV0, OSAV1, OSAV2, OSAV3, OSAV4
};  

typedef  struct gr_struct       GR_STRUCT;


struct field_struct         /* field table - info re each field          */
{
   char    nam [MAX_FIELD_LENGTH];
   BOOL    num;             /* is field numeric or char string?          */
}  gr_fd [GR_NUM_FIELDS], go_fd [GO_NUM_FIELDS];

struct                      /* operand table - info about each operand   */
{
   int             field;
   int             op;
   long            nval;        /* value for numeric operands            */
   char            sval [MAX_FIELD_LENGTH];
   BOOL            num;		/* is field numeric or char string?      */
}  gr_op [MAX_NUM_OPS];         /* the above field is stored here as     */
				/* well as in "fields" for readability   */
struct                          /* operand table - info about each op    */
{
   int             field;
   int             op;
   short           nval;        /* value for numeric operands            */
   char            sval [MAX_FIELD_LENGTH];
   BOOL            num;		/* is field numeric or char string?      */
}  go_op [MAX_NUM_OPS];

enum gr_field_type          /* enumerates the fields in the input record */
   {name, sex, Class, race, level, room, gold, clan, council,
    site, last, pkill};

struct  gr_struct               /* input record containing pfile info    */
{
   char    name [MAX_NAME_LENGTH];
   char    sex;
   char    Class;
   char    race;
   char    level;
   short   room;
   long    gold;
   char    clan;
   char    council;
   char    site [MAX_SITE_LENGTH];
   long    last;
   char    pkill;
};


class CGoData {		// input record containing object data
public:
	void	Clear () { ZeroMemory (this, sizeof (CGoData)); }
	BOOL	ReadNames (CCharacter *ch, CObjData &Obj, BOOL np_sw, 
				BOOL nm_sw, BOOL ng_sw);
	void	ReadObject (CObjIndexData& Idx, CObjData& Obj, int& tot_match);
	void	AccumAff (int loc, int mod);

	short	n [22];
	char	*s [2];
};


short go_wear_ext (long arg)    /* extract bit set in arg ignoring pos 1 */
{
  short cou;
  if (arg <= 1) return (short) arg;
  for (cou=1; cou<=31; cou++)
     if (arg & ((unsigned long) 1 << cou)) return cou + 1;
  return -1;
}

int go_str_cmp (const char *astr, const char *bstr)
{
    int i;
    for (; *astr || *bstr; astr++, bstr++)
    {
        i=LOWER (*astr)-LOWER (*bstr);
        if (i) return i;
    }
    return 0;
}

void go_init (void)
{
  int cou;

  for (cou=0; cou<GO_NUM_FIELDS; cou++)
      go_fd[cou].num=TRUE;
  go_fd[22].num=FALSE;
  go_fd[23].num=FALSE;

  strcpy (go_fd[ 0].nam, "count");
  strcpy (go_fd[ 1].nam, "vnum");
  strcpy (go_fd[ 2].nam, "type");
  strcpy (go_fd[ 3].nam, "level");
  strcpy (go_fd[ 4].nam, "wear");
  strcpy (go_fd[ 5].nam, "avg" );
  strcpy (go_fd[ 6].nam, "hr"  );
  strcpy (go_fd[ 7].nam, "dr"  );
  strcpy (go_fd[ 8].nam, "hp"  );
  strcpy (go_fd[ 9].nam, "mp"  );
  strcpy (go_fd[10].nam, "ac"  );
  strcpy (go_fd[11].nam, "str" );
  strcpy (go_fd[12].nam, "dex" );
  strcpy (go_fd[13].nam, "con" );
  strcpy (go_fd[14].nam, "wis" );
  strcpy (go_fd[15].nam, "int" );
  strcpy (go_fd[16].nam, "luck");
  strcpy (go_fd[17].nam, "sav0");
  strcpy (go_fd[18].nam, "sav1");
  strcpy (go_fd[19].nam, "sav2");
  strcpy (go_fd[20].nam, "sav3");
  strcpy (go_fd[21].nam, "sav4");
  strcpy (go_fd[22].nam, "cname");
  strcpy (go_fd[23].nam, "name");
}

char *go_otype_to_disp (int arg)
{
  if (arg==ITEM_LIGHT    ) return "lt";
  if (arg==ITEM_SCROLL   ) return "sc";
  if (arg==ITEM_WAND     ) return "wa";
  if (arg==ITEM_STAFF    ) return "st";
  if (arg==ITEM_WEAPON   ) return "wp";
  if (arg==ITEM_ARMOR    ) return "ar";
  if (arg==ITEM_POTION   ) return "po";
  if (arg==ITEM_CONTAINER) return "cn";
  if (arg==ITEM_POTION   ) return "po";
  if (arg==ITEM_NOTE     ) return "no";
  if (arg==ITEM_KEY      ) return "ky";
  if (arg==ITEM_FOOD     ) return "fo";
  if (arg==ITEM_CORPSE_PC) return "pc";
  if (arg==ITEM_CORPSE_NPC) return "mc";
  if (arg==ITEM_PILL     ) return "pi";
  if (arg==ITEM_BOOK     ) return "bk";
  return NULL;
}

char *owear_to_disp (short arg)
{
  static char owear_disp[20][3] =
     { "??", "ta", "fi", "ne", "bo", "he", "le", "fe", "ha", "ar",
       "sh", "ab", "wa", "wr", "wi", "ho", "du", "ea", "ey", "mi" };

  arg = (arg<0 || arg>20) ? 0 : arg;
  return owear_disp[ arg ];
}

int owear_to_num (char *arg)
{
  if (!strcmp (arg, "take"   )) return  1;
  if (!strcmp (arg, "finger" )) return  2;
  if (!strcmp (arg, "neck"   )) return  3;
  if (!strcmp (arg, "body"   )) return  4;
  if (!strcmp (arg, "head"   )) return  5;
  if (!strcmp (arg, "legs"   )) return  6;
  if (!strcmp (arg, "feet"   )) return  7;
  if (!strcmp (arg, "hands"  )) return  8;
  if (!strcmp (arg, "arms"   )) return  9;
  if (!strcmp (arg, "shield" )) return 10;
  if (!strcmp (arg, "about"  )) return 11;
  if (!strcmp (arg, "waist"  )) return 12;
  if (!strcmp (arg, "wrist"  )) return 13;
  if (!strcmp (arg, "wield"  )) return 14;
  if (!strcmp (arg, "hold"   )) return 15;
  if (!strcmp (arg, "dual"   )) return 16;
  if (!strcmp (arg, "ears"   )) return 17;
  if (!strcmp (arg, "eyes"   )) return 18;
  if (!strcmp (arg, "missile")) return 19;
  return 0;
}
	
int go_fnam_to_num (char *arg)
{
  int cou;
  
  for (cou=0; cou<GO_NUM_FIELDS; cou++)
      if (!strcmp (arg, go_fd[cou].nam))
         return cou;
  return -1;
}

/*
 * Generalized sort function.
 * Sorts either ascending or descending.
 * Sorts an array containing either numbers or strings.
 * 1st parm is a pointer to an array of structures.
 * 2nd parm indicates the starting position within the record
 * 3rd parm indicates the first record in the sort range
 * 4th parm indicates the last  record in the sort range
 *     e.g. the array may contain 100 records but we may wish to sort
 *     only the first fifty.
 * 5th parm is n_s - number/string - TRUE is number - FALSE is string
 * 6th parm is direction - TRUE is ascending - FALSE is descending
 */
void go_sort (CCharacter *ch, CGoData **p,
        int ind, int left, int right, BOOL n_s, BOOL sor_dir)
{
  CGoData *swap;
  int i=left, j=right, testn;
  static char tests[ MAX_STRING_LENGTH ];

  if (left < 0 || left >= right) return;
  right = UMIN (right, MAX_DISPLAY_LINES - 1);

  if (n_s)
     testn = p[left]->n[ind];
  else
     strcpy (tests, p[left]->s[ind]);

  do
  {
     if (n_s)
     {
        if (sor_dir)
           while (p[i]->n[ind] < testn) i++;
        else
           while (p[i]->n[ind] > testn) i++;
        if (sor_dir)
           while (testn < p[j]->n[ind]) j--;
        else
           while (testn > p[j]->n[ind]) j--;
     }
     else
     {
        if (sor_dir)
           while (strcmp (p[i]->s[ind], tests) < 0) i++;
        else
           while (strcmp (p[i]->s[ind], tests) > 0) i++;
        if (sor_dir)
           while (strcmp (tests, p[j]->s[ind]) < 0) j--;
        else
           while (strcmp (tests, p[j]->s[ind]) > 0) j--;
     }

     if (i <= j) { swap=p[i]; p[i] = p[j]; p[j] = swap; i++; j--; }
  } while (i <= j);

if (left < j)  go_sort (ch, p, ind, left,  j, n_s, sor_dir);
if (i < right) go_sort (ch, p, ind, i, right, n_s, sor_dir);
}


void CGoData::AccumAff (int loc, int mod)
{
	switch (loc) {
		case APPLY_HITROLL       : n [OHR]  += mod; break;
		case APPLY_DAMROLL       : n [ODR]  += mod; break;
		case APPLY_HIT           : n [OHP]  += mod; break;
		case APPLY_MANA          : n [OMP]  += mod; break;
		case APPLY_AC            : n [OAC]  += mod; break;
		case APPLY_STR           : n [OSTR] += mod; break;
		case APPLY_DEX           : n [ODEX] += mod; break;
		case APPLY_CON           : n [OCON] += mod; break;
		case APPLY_WIS           : n [OWIS] += mod; break;
		case APPLY_INT           : n [OINT] += mod; break;
		case APPLY_LCK           : n [OLUCK]+= mod; break;
		case APPLY_SAVING_POISON : n [OSAV0]+= mod; break;
		case APPLY_SAVING_ROD    : n [OSAV1]+= mod; break;
		case APPLY_SAVING_PARA   : n [OSAV2]+= mod; break;
		case APPLY_SAVING_BREATH : n [OSAV3]+= mod; break;
		case APPLY_SAVING_SPELL  : n [OSAV4]+= mod; break;
	}
}


void display_operand_table (CCharacter *ch, int op_num)
{
  int cou;
  char opn[7][3] = {"eq", "ne", "su", "ge", "gt", "le", "lt"};

  pager_printf (ch, "OPERAND TABLE\n\r");
  for (cou=0; cou < op_num; cou++)
     if (go_op[cou].num)
        pager_printf (ch,
        "%2d %-7s %2s %10ld\n\r", cou+1, go_fd[go_op[cou].field].nam,
           opn[go_op[cou].op], go_op[cou].nval);
     else
        pager_printf (ch, "%2d %-7s %2s %s\n\r",
        cou+1, go_fd[go_op[cou].field].nam,
           opn[go_op[cou].op], go_op[cou].sval);
}

/*
 *  Store operand's operator and value in operand table.
 */
BOOL go_parse_operator (CCharacter *ch, char *pch, int *op_num)
{
  enum op_type {EQ, NE, SU, GE, GT, LE, LT};
  int  cou;
  char opstr [7][3] = { "=", "!=", "<>", ">=", ">", "<=", "<" };

  go_op[*op_num].op = -1;
  for (cou=0; cou<7; cou++)
      if (!str_prefix (opstr[cou], pch))
         {
         go_op[*op_num].op = cou;
         break;
         }
  if (go_op[*op_num].op < 0)
     {pager_printf (ch, "Invalid operator: %s\n\r", pch); return FALSE;}
  if (go_op[*op_num].op==EQ || go_op[*op_num].op==GT
  ||   go_op[*op_num].op==LT)
     pch++;
  else pch+=2;                              /* advance to operand value */
  if (*pch=='\0')
     {pager_printf (ch, "Value is missing from operand.\n\r"); return FALSE;}

  if (go_fd[ go_op[ *op_num ].field ].num)
  {
     go_op[*op_num].num  = TRUE;
     if (isdigit (*pch))                        /* user entered number */
        go_op[*op_num].nval = atoi (pch);
     else
     if (go_op[*op_num].field == OTYPE)
          go_op[*op_num].nval = get_otype (pch); /* user entered token */
     else
     if (go_op[*op_num].field == OWEAR)
          go_op[*op_num].nval = owear_to_num (pch); /* user entered token */
  }
  else
  {
     go_op[*op_num].num  = FALSE;

     if (strlen (pch) > MAX_FIELD_LENGTH)
     {
        pager_printf (ch, "Char string is too long:%s\n\r", pch);
        return FALSE;
     }
     strcpy (go_op[*op_num].sval, pch);      /* store str value in table */
  }
  (*op_num)++;                            /* operand now stored in table */
  return TRUE;
}

/*
 * Store operand's field name in the operand table.
 */
BOOL go_parse_operand (CCharacter *ch, char *arg, int *op_num, int *sor_ind,
        BOOL *sor_dir, BOOL *or_sw, BOOL *np_sw, BOOL *nm_sw, BOOL *ng_sw,
        BOOL *do_sw, BOOL *d2_sw)
{
  int  cou;
  char *pch;

  if (!strcmp (arg, "or"   )) return *or_sw    = TRUE;
  if (!strcmp (arg, "np"   )) return *np_sw    = TRUE;
  if (!strcmp (arg, "nm"   )) return *nm_sw    = TRUE;
  if (!strcmp (arg, "ng"   )) return *ng_sw    = TRUE;
  if (!strcmp (arg, "do"   )) return *do_sw    = TRUE;
  if (!strcmp (arg, "d2"   )) return *d2_sw = TRUE;

  if (arg[0]=='+' || arg[0]=='-')
  {
     *sor_dir = (arg[0]=='+') ? TRUE : FALSE;
     pch = arg + 1;
     if (pch[0] == '\0')
        {
        pager_printf (ch, "Sorry. Missing sort field: %s\n\r", arg);
        return FALSE;
        }

     if ((*sor_ind = go_fnam_to_num (pch)) == -1)
        {
        pager_printf (ch, "Sorry. Invalid sort field: %s\n\r", arg);
        return FALSE;
        }
     return TRUE;
  }
                                                 
  for (cou=0; cou<GO_NUM_FIELDS; cou++)           /* check field name    */
      if (!str_prefix (go_fd[cou].nam, arg))
      {
         arg += strlen (go_fd[cou].nam);         /* advance to operator */
         go_op[ *op_num ].field = cou;
						 /* store field enum */
         if (!go_parse_operator (ch, arg, op_num))
            return FALSE;
         return TRUE;
      }
  pager_printf (ch, "Sorry. Invalid field name: %s\n\r", arg);
  return FALSE;
}

/*
 * Evaluate one string criteria
 */
BOOL go_eval_str (char *lval, int op, char *rval)
{
  enum op_type {EQ, NE, SU, GE, GT, LE, LT};
  switch (op)
  {
     case EQ: if (!str_cmp (lval, rval)) return TRUE;
              else return FALSE;
     case NE: if ( str_cmp (lval, rval)) return TRUE;
              else return FALSE;
     case GT: if ( go_str_cmp (lval, rval) >  0) return TRUE;
              else return FALSE;
     case GE: if ( go_str_cmp (lval, rval) >= 0) return TRUE;
              else return FALSE;
     case LT: if ( go_str_cmp (lval, rval) <  0) return TRUE;
              else return FALSE;
     case LE: if ( go_str_cmp (lval, rval) <= 0) return TRUE;
              else return FALSE;
     case SU: if (strstr (lval, rval)) return TRUE;
              else return FALSE;
  }
  return FALSE;
}

/*
 * Evaluate one numeric criteria
 */
BOOL go_eval_num (long lval, int op, long rval)
{
  enum op_type {EQ, NE, SU, GE, GT, LE, LT};
  switch (op)
  {
     case EQ: return lval == rval;
     case NE: return lval != rval;
     case GE: return lval >= rval;
     case GT: return lval >  rval;
     case LE: return lval <= rval;
     case LT: return lval <  rval;
     default: return FALSE;
  }
}

/*
 * Evaluate one input record to see if it matches all search criteria
 */
BOOL go_eval_and (CCharacter *ch, CGoData *r, int op_num)
{
 int  cou;

  for (cou=0; cou<op_num; cou++)
  {
      if (go_op[cou].field <= OSAV4)
      {
         if (!go_eval_num
               (r->n[go_op[cou].field], go_op[cou].op, go_op[cou].nval))
            return FALSE;
         else continue;
      }
      else
      {
         if (!go_eval_str (
                 r->s[go_op[cou].field-OSAV4-1], go_op[cou].op, 
                      go_op[cou].sval))
           return FALSE;
        else continue;
     }
  }
  return TRUE;
}

/*
 * Evaluate one input record to see if it matches any search criteria
 */
BOOL go_eval_or (CCharacter *ch, CGoData *r, int op_num)
{
  int  cou;
  for (cou=0; cou<op_num; cou++)
  {
      if (go_op[cou].field <= OSAV4)
      {
         if (go_eval_num (r->n[ go_op[cou].field ], go_op[cou].op,
              go_op[cou].nval))
            return TRUE;
         else continue;
     }
     else
     {
        if (go_eval_str (r->s[go_op[cou].field-OSAV4-1], go_op[cou].op, 
                          go_op[cou].sval))
           return TRUE;
        else continue;
     }
  }
  return FALSE;
}

void go_display (CCharacter *ch, int dis_num, int tot_match, BOOL d2_sw,
                 CGoData **p)
{
  enum {CNAME, ONAME};

  CGoData r;
  int cou, lim;
  char pri_cname[MAX_NAME_LENGTH];
  char pri_oname[MAX_NAME_LENGTH];

  if (tot_match > 0 && dis_num > 0)          /* print title if app  */
  {
  if (!d2_sw)
      pager_printf (ch,
         "\n\r%-12s%3s %5s %2s %-12s %2s %2s %2s %2s %2s %3s %3s %3s "
         "%11s\n\r",
         "Character", "Cou", "OVnum", "Lv", "OName", "Ty", "We",
         "Av", "Hr", "Dr", "Hp", "Mp", "AC", "S D C W I L");
  else
      pager_printf (ch,
         "\n\r%-12s%3s %5s %2s %-12s %2s %2s %2s %2s %2s %3s %3s %2s "
         "%2s %2s %2s %2s\n\r",
         "Character", "Cou", "OVnum", "Lv", "OName", "Ty", "We",
         "Av", "Hr", "Dr", "Hp", "Mp", "S0", "S1", "S2", "S3", "S4");
   }
   lim = UMIN (tot_match, dis_num);

   for (cou=0; cou<lim; cou++)
   {
      r = *p[cou];
      strncpy (pri_cname, r.s[CNAME], MAX_NAME_LENGTH - 1);
      pri_cname[ MAX_NAME_LENGTH - 1] = '\0';
      strncpy (pri_oname, r.s[ONAME], MAX_NAME_LENGTH - 1);
      pri_oname[ MAX_NAME_LENGTH - 1] = '\0';
      if (!d2_sw)
         pager_printf (ch,
            "%-12s%3d %5d%3d %-12s %2s %2s%3d%3d%3d%4d%4d%4d"
            "%2d%2d%2d%2d%2d%2d\n\r", 
            pri_cname, r.n[OCOUNT], r.n[OVNUM], r.n[OLEVEL],
            pri_oname, go_otype_to_disp (r.n[OTYPE]),
            owear_to_disp (r.n[OWEAR]),
            r.n[OAVG], r.n[OHR], r.n[ODR],
            r.n[OHP], r.n[OMP], r.n[OAC], r.n[OSTR], r.n[ODEX],
            r.n[OCON], r.n[OWIS], r.n[OINT], r.n[OLUCK]);
      else
         pager_printf (ch,
            "%-12s%3d %5d%3d %-12s %2s %2s%3d%3d%3d%4d%4d%3d"
            "%3d%3d%3d%3d\n\r", 
            pri_cname, r.n[OCOUNT], r.n[OVNUM], r.n[OLEVEL],
            pri_oname, go_otype_to_disp (r.n[OTYPE]),
            owear_to_disp (r.n[OWEAR]),
            r.n[OAVG], r.n[OHR], r.n[ODR],
            r.n[OHP], r.n[OMP], r.n[OSAV0], r.n[OSAV1], r.n[OSAV2],
            r.n[OSAV3], r.n[OSAV4]);
  }
  if (tot_match == 0)
     pager_printf (ch, "Zero matches were found.\n\r");
  else pager_printf (ch,
    "%5d matches in total.\n\r", tot_match);
}


// Find the name of the character and object and place in record
// The name of the object is easy ... but the name of the character
// proved to be one of the most difficult and frustrating aspects ot
// this function. F.Y.I - if an object is not "carried_by" someone,
// it could be on the ground ... but ... growl ... it could also be
// in a container carried by someone - or in a container on the ground.
BOOL CGoData::ReadNames (CCharacter *ch, CObjData &Obj, BOOL np_sw, 
						 BOOL nm_sw, BOOL ng_sw)
{
	enum		{CNAME, ONAME};
	CObjData	*pt;
	char		*ground = "(none)";
	char		*ack    = "(error in data structure)";

	Clear ();
	s [ONAME] = Obj.GetName () ? NCCP Obj.GetName () : ack;  // set object name

	if (Obj.carried_by) {				// it's being carried by a char
		if (ch->GetTrustLevel () < Obj.carried_by->GetLevel ())
			return FALSE;
		if (nm_sw &&  Obj.carried_by->IsNpc ())
			return FALSE;
		if (np_sw && ! Obj.carried_by->IsNpc ())
			return FALSE;
		s [CNAME] = NCCP Obj.carried_by->GetName ();
	}
	else if (Obj.in_obj) {				// it's in a container
		pt = &Obj;
		while (pt->in_obj)
			pt = pt->in_obj;
		if (pt->carried_by && ch->GetTrustLevel () < pt->carried_by->GetLevel ())
			return FALSE;
		if (pt->carried_by && nm_sw &&  pt->carried_by->IsNpc ())
			return FALSE;
		if (pt->carried_by && np_sw && !pt->carried_by->IsNpc ())
			return FALSE;
		if (pt->carried_by)
			s [CNAME] = NCCP pt->carried_by->GetName ();
		else {
			if (ng_sw)
				return FALSE;
			s [CNAME] = ground;
		}
	}
	else if (! Obj.in_obj) {				// it's on the ground
		if (ng_sw)
			return FALSE;
		s [CNAME] = ground;
	}
	return TRUE;
}


BOOL go_read (CCharacter *ch, int dis_num, int op_num, int sor_ind,
            BOOL sor_dir, BOOL or_sw, BOOL np_sw, BOOL nm_sw, BOOL ng_sw,
            BOOL d2_sw)
{
	CGoData		r;						// input (physical record)
	CGoData		a [MAX_DISPLAY_LINES];	// array of records
	CGoData		*p [MAX_DISPLAY_LINES];	// array of pointers to records
	BOOL		ok_otype [255];			// we want to process these otypes
	int			tot_match = 0;			// total records matched
	int			ind;					// indicates the sort field

	memset (ok_otype, 0, sizeof ok_otype);
	ok_otype [ITEM_LIGHT] = ok_otype [ITEM_WAND] = ok_otype [ITEM_KEY] = 
	ok_otype [ITEM_STAFF] = ok_otype [ITEM_WEAPON] = ok_otype [ITEM_ARMOR] =
	ok_otype [ITEM_CONTAINER] = TRUE; 


	POSITION	Apos = AreaList.GetHeadPosition ();
	while (Apos) {
		CAreaData	&Area = *AreaList.GetNext (Apos);
		POSITION	Ipos = Area.m_ObjIdxList.GetHeadPosition ();

		while (Ipos) {
			CObjIndexData	&Idx =
				*(CObjIndexData*) Area.m_ObjIdxList.GetNext (Ipos);
			if (! ok_otype [Idx.item_type])
				continue;

			POSITION	pos = Idx.m_ObjList.GetHeadPosition ();
			while (pos) {						// Loop through all objects
				CObjData	&Obj = *Idx.m_ObjList.GetNext (pos);

				if (r.ReadNames (ch, Obj, np_sw, nm_sw, ng_sw)) {
					r.ReadObject (Idx, Obj, tot_match);

					BOOL bEval = or_sw ?
						go_eval_or (ch, &r, op_num) :
						go_eval_and (ch, &r, op_num);

					if (bEval) {				// record is a match
						if (dis_num > 0 && tot_match < MAX_DISPLAY_LINES) {
							a [tot_match] = r;
							p [tot_match] = &a [tot_match];
						}
						++tot_match;
					}
				}
			}
		}
	}
	ind = (sor_ind <= OSAV4) ? sor_ind : sor_ind - OSAV4 - 1;

	if (tot_match > 1 && dis_num > 0)
		go_sort (ch, p, ind, 0, UMIN ((tot_match - 1),
			MAX_DISPLAY_LINES - 1), (sor_ind <= OSAV4), sor_dir);

	go_display (ch, dis_num, tot_match, d2_sw, p);
	return TRUE;
}


void CGoData::ReadObject (CObjIndexData& Idx, CObjData& Obj, int& tot_match)
{
	n [OCOUNT] = Obj.count;
	n [OVNUM]  = Idx.vnum;
	n [OTYPE]  = Obj.item_type;
	n [OLEVEL] = Obj.level;
	n [OWEAR]  = go_wear_ext (Obj.wear_flags);
	n [OAVG]   = (Obj.item_type == ITEM_WEAPON) ?
		(Obj.value [1] + Obj.value [2])/2 : 0;

	CAffectList		&IList = Idx.AffList;
	POSITION		apos = IList.GetHeadPosition ();
	while (apos) {
		CAffectData	&Aff = *IList.GetNext (apos);
		AccumAff (Aff.location, Aff.modifier);
	}

	CAffectList		&AList = Obj.AffList;
	apos = AList.GetHeadPosition ();
	while (apos) {
		CAffectData	&Aff = *AList.GetNext (apos);
		AccumAff (Aff.location, Aff.modifier);
	}
}

/*
  The following code is intended to replace the static arrays "a" and "p"
  with dynamically allocated ones. But I'm confused as to why. If the
  user asks to display 10 lines, but selects 10,000 ... then the arrays
  must be allocated to hold 10,000 so that they may be sorted. Once they
  are sorted, we display only 10 - but - that don't change the fact that
  we have to sort all 10,000 - sigh - arg! - Gorog

  CGoData        *a;
  CGoData       **p;
  a = (CGoData  *) calloc (UMIN (dis_num, MAX_DISPLAY_LINES), sizeof *a);
  if (!a)
  {
     pager_printf (ch, "Sorry. There is currently insufficient memory avail"
     " to service your request. Try later or speak to a coder.\n\r");
     return FALSE;
  }
  p = (CGoData **) calloc (UMIN (dis_num, MAX_DISPLAY_LINES), sizeof *p);
  if (!p)
  {
     pager_printf (ch, "Sorry. There is currently insufficient memory avail"
     " to service your request. Try later or speak to a coder.\n\r");
     return FALSE;
  }
  free (p); free (a);
*/

void do_ogrub (CCharacter *ch, char *argument)
{
  char arg1[MAX_STRING_LENGTH];
  int  dis_num;                            /* display lines requested     */
  int  op_num = 0;                         /* num of operands on cmd line */
  int  sor_ind  = OVNUM;                   /* sort indicator              */
  BOOL or_sw    = FALSE;                   /* or search criteria          */
  BOOL sor_dir  = 1;                       /* sort indicator              */
  BOOL np_sw    = FALSE;                   /* no players                  */
  BOOL nm_sw    = FALSE;                   /* no mobs                     */
  BOOL ng_sw    = FALSE;                   /* no ground objs              */
  BOOL do_sw    = FALSE;                   /* display operand table       */
  BOOL d2_sw    = FALSE;                   /* alternate display format    */

  go_init ();                              /* initialize data structures  */
  argument = one_argument (argument, arg1);
  if (!*arg1)
  {
     pager_printf (ch, "Please type HELP OGRUB for info on OGRUB.\n\r");
     return;
  }
  if (isdigit (*arg1))        /* first arg is number of display lines   */
     dis_num = atoi (arg1);
  else
  {
     pager_printf (ch, "You did not specify the number of display lines.\n\r");
     return;
  }
  if (dis_num > MAX_DISPLAY_LINES)
  {
     pager_printf (ch, "Sorry. You have requested more than %d display " 
                      "lines.\n\r", MAX_DISPLAY_LINES);
     return;
  }

  argument = one_argument (argument, arg1);
  while (*arg1)                      /* build the operand table        */
  {
     if (op_num >= MAX_NUM_OPS)
     {
        pager_printf (ch, "Sorry. You have entered more than %d operands.\n\r",
           MAX_NUM_OPS, MAX_NUM_OPS);
        return;
     }
     if (!go_parse_operand (ch, arg1, &op_num, &sor_ind, &sor_dir,
        &or_sw, &np_sw, &nm_sw, &ng_sw, &do_sw, &d2_sw))
        return;
     argument = one_argument (argument, arg1);
  }
  if (op_num <= 0)
  {
     pager_printf (ch, "Sorry. You did not include any valid operands.\n\r");
     return;
  }
  if (do_sw) 
    display_operand_table (ch, op_num);
  if (!go_read (ch, dis_num, op_num, sor_ind,          /* future expansion*/
        sor_dir, or_sw, np_sw, nm_sw, ng_sw, d2_sw))
     return;
}

char *gr_strc (char c)           /* convert a char to a str */
{
  static char s[2] = "s";
  s[0]=c;
  return s;
}

/*
 * Evaluate one input record to see if it matches all search criteria
 */
BOOL gr_eval_and (GR_STRUCT r, int op_num)
{
  int  cou;
  for (cou=0; cou<op_num; cou++)
  {
     switch (gr_op[cou].field)
     {
     case name:
        if (!go_eval_str (r.name, gr_op[cou].op, gr_op[cou].sval))
           return FALSE;
        else break;
     case sex:
        if (!go_eval_num (r.sex, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case Class:
        if (!go_eval_num (r.Class, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case race:
        if (!go_eval_num (r.race, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case level:
        if (!go_eval_num (r.level, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case room:
        if (!go_eval_num (r.room, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case gold:
        if (!go_eval_num (r.gold, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case clan:
        if (!go_eval_num (r.clan, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case council:
        if (!go_eval_num (r.council, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case site:
        if (!go_eval_str (r.site, gr_op[cou].op, gr_op[cou].sval))
           return FALSE;
        else break;
     case last:
        if (!go_eval_num (r.last, gr_op[cou].op, gr_op[cou].nval))
           return FALSE;
        else break;
     case pkill:
        if (!go_eval_str (gr_strc (r.pkill), gr_op[cou].op, gr_op[cou].sval))
           return FALSE;
        else break;
     }
  }
  return TRUE;
}

/*
 * Evaluate one input record to see if it matches any search criteria
 */
BOOL gr_eval_or (GR_STRUCT r, int op_num)
{
  int cou;
  for (cou=0; cou<op_num; cou++)
  {
     switch (gr_op[cou].field)
     {
     case name:
        if (go_eval_str (r.name, gr_op[cou].op, gr_op[cou].sval))
           return TRUE;
        else break;
     case sex:
        if (go_eval_num (r.sex, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case Class:
        if (go_eval_num (r.Class, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case race:
        if (go_eval_num (r.race, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case level:
        if (go_eval_num (r.level, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case room:
        if (go_eval_num (r.room, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case gold:
        if (go_eval_num (r.gold, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case clan:
        if (go_eval_num (r.clan, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case council:
        if (go_eval_num (r.council, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case site:
        if (go_eval_str (r.site, gr_op[cou].op, gr_op[cou].sval))
           return TRUE;
        else break;
     case last:
        if (go_eval_num (r.last, gr_op[cou].op, gr_op[cou].nval))
           return TRUE;
        else break;
     case pkill:
        if (go_eval_str (gr_strc (r.pkill), gr_op[cou].op, gr_op[cou].sval))
           return TRUE;
        else break;

     }
  }
  return FALSE;
}

void gr_init (void)
{
  strcpy (gr_fd[ 0].nam, "name"  ); gr_fd[0].num=FALSE;
  strcpy (gr_fd[ 1].nam, "sex"   ); gr_fd[ 1].num=TRUE;
  strcpy (gr_fd[ 2].nam, "class" ); gr_fd[ 2].num=TRUE;
  strcpy (gr_fd[ 3].nam, "race"  ); gr_fd[ 3].num=TRUE;
  strcpy (gr_fd[ 4].nam, "level" ); gr_fd[ 4].num=TRUE;
  strcpy (gr_fd[ 5].nam, "room"  ); gr_fd[ 5].num=TRUE;
  strcpy (gr_fd[ 6].nam, "gold"  ); gr_fd[ 6].num=TRUE;
  strcpy (gr_fd[ 7].nam, "clan"  ); gr_fd[ 7].num=TRUE;
  strcpy (gr_fd[ 8].nam, "council"); gr_fd[ 8].num=TRUE;
  strcpy (gr_fd[ 9].nam, "site"  ); gr_fd[ 9].num=FALSE;
  strcpy (gr_fd[10].nam, "last"  ); gr_fd[10].num=TRUE;
  strcpy (gr_fd[11].nam, "pkill" ); gr_fd[11].num=FALSE;
}

/*
 *  Store operand's operator and value in operand table.
 */
BOOL gr_parse_operator (CCharacter *ch, char *pch, int *op_num)
{
  enum op_type {EQ, NE, SU, GE, GT, LE, LT};
  int  cou;
  char opstr [7][3] = { "=", "!=", "<>", ">=", ">", "<=", "<" };

  gr_op[*op_num].op = -1;
  for (cou=0; cou<7; cou++)
      if (!str_prefix (opstr[cou], pch))
         {
         gr_op[*op_num].op = cou;
         break;
         }

  if (gr_op[*op_num].op < 0)
   {printf ("Invalid operator: %s\n\r", pch); return FALSE;}

  if (gr_op[*op_num].op==EQ || gr_op[*op_num].op==LT
  || gr_op[*op_num].op==GT)
     pch++;
  else pch+=2;                               /* advance to operand value */

  if (*pch=='\0')
     {ch->SendTextf ("Value is missing from operand.\n\r"); return FALSE;}

  if (gr_fd[gr_op[*op_num].field].num)
  {
     gr_op[*op_num].num  = TRUE;
     gr_op[*op_num].nval = atol (pch);   /* store num operand value in table */
  }
  else
  {
     if (strlen (pch) > MAX_FIELD_LENGTH)
        {ch->SendTextf ("Char string is too long:%s\n\r", pch); return FALSE;}
     gr_op[*op_num].num  = FALSE;
     strcpy (gr_op[*op_num].sval, pch);  /* store str operand value in table */
  }
  (*op_num)++;                         /* operand now stored in table      */
  return TRUE;
}

/*
 * Store operand's field name in the operand table.
 */
BOOL gr_parse_operand (CCharacter *ch, char *arg, BOOL *or_sw, int *op_num)
{
  int  cou;

  if (!strcmp (arg, "or"))
     return *or_sw = TRUE;
                                                 
  for (cou=1; cou<=GR_NUM_FIELDS; cou++)          /* check field name    */
      if (!str_prefix (gr_fd[cou-1].nam, arg))
      {
         arg += strlen (gr_fd[ cou-1 ].nam);     /* advance to operator */
         gr_op[ *op_num ].field = cou-1;          /* store field name    */
         if (!gr_parse_operator (ch, arg, op_num))
            return FALSE;
         return TRUE;
      }
  ch->SendTextf ("Sorry. Invalid field name: %s\n\r", arg);
  return FALSE;
}

/*
 * Read the input file to select records matching the search criteria
 */
void gr_read (
     CCharacter *ch, int op_num, BOOL or_sw, int dis_num)
{
  FILE *fp;
  BOOL res;                                 /* result of a BOOLean exp   */
  BOOL title_sw = FALSE;                    /* only print title once     */
  int  tot_match = 0;                       /* total records matched     */
  GR_STRUCT r;                              /* input (physical record)   */
  char sex[]   = "NMF";                     /* convert sex to text       */
  char Class[] = "MCTWVDRA";                /* convert class to text     */
  char race[][3] =                          /* convert race to text      */
  {"Hu", "El", "Dw", "Ha", "Px", "Va", "Og", "HO", "HT", "HE", "Gi"};
  char clan[][4] = {
  "   ", "Gui", "DS ", "MS ", "RB ", "AR ", "Bru", "Las","Nos", "Tre", "Ven"};
  char council[][4] =
  {"   ", "CoE", "MC ", "NC ", "Pro", "PK ", "QC ", "Neo", "Cod", "AC "};

  if ((fp = fopen ("/home/mud/grub.new", "r")) == NULL)
     return;
  fread (&r, sizeof (r), 1, fp);
  while (!feof (fp))                       /* read each input record    */
  {
     if (or_sw)                           /* is this an "or" search?   */
        res = gr_eval_or (r, op_num);
     else res = gr_eval_and (r, op_num);
     if (res)                             /* record is a match         */
     {
        tot_match++;
        if (!title_sw && dis_num > 0)     /* print title if applicable */
        {
           ch->SendTextf (
           "\n\r%-12s %-2s %1s %-2s %1s %3s %3s %5s %11s %-15s %-6s %s\n\r",
           "Name", "Lv", "S", "R", "C", "Cln", "Cou", "Room", "Gold",
           "Site", "Last", "Pk");
           title_sw = TRUE;
        }
        if (tot_match <= dis_num)         /* print record if applicable */
           ch->SendTextf (
  "%-12s %2hd %c %2s %c %3s %3s %5hd %11ld %-15s %6lu %c\n\r", 
              r.name, r.level, sex[ (unsigned char) r.sex],
              race[ (unsigned char) r.race], Class[ (unsigned char) r.Class],
              clan[ (unsigned char) r.clan],
              council[ (unsigned char) r.council],
              r.room, r.gold, r.site, r.last, r.pkill);
     }
     fread (&r, sizeof (r), 1, fp);
  }
  fclose (fp);
  if (tot_match == 0)
     ch->SendTextf ("Zero matches were found.\n\r");
  else ch->SendTextf ("%5d matches in total\n\r", tot_match);
}

/*
 * GRUB (Gorog's Revenge on Unruly Bastards)
 *
 * This command is used by mud administrators to obtain info from the
 * player files - often to deal with unruly problem players.
 *
 * It works in two phases. The first phase processes the command line
 * entered by the user and stores each operand in an operand table.
 * Each entry in this table contains the field to be compared, the
 * the operation to be performed and the value to be compared to the
 * record's value.
 *
 * The second phase reads each input record - one record for each player.
 * It then compares the appropriate values from the input record to
 * each operand in the operand table.
 */
void do_grub (CCharacter *ch, char *argument)
{
  char arg1[MAX_STRING_LENGTH];
  BOOL or_sw = FALSE;                       /* or search criteria           */
  int  dis_num;                             /* display lines requested      */
  int  op_num = 0;                          /* num of operands on cmd line  */

  gr_init ();				    /* initialize data structures   */
  argument = one_argument (argument, arg1);
  if (!*arg1)
  {
     ch->SendTextf ("Please type HELP GRUB for info on how to use GRUB.\n\r");
     return;
  }
  if (isdigit (*arg1))        /* first argument is number of display lines */
     dis_num = atoi (arg1);
  else
  {
     ch->SendTextf ("You did not specify the number of display lines.\n\r");
     return;
  }

  argument = one_argument (argument, arg1);
  while (*arg1)
  {					        /* build the operand table */
     if (!gr_parse_operand (ch, arg1, &or_sw, &op_num))
        return;
     argument = one_argument (argument, arg1);
  }
  /*display_operand_table (op_num);*/
  gr_read (ch, op_num, or_sw, dis_num);      /* read the input file     */
}

/*
 * Sorts the arrays "vnums" and "count" based on the order in "count"
 */
void zero_sort (int *vnums, int *count, int left, int right)
{
int i=left, j=right, swap, test;
test = count[ (left + right) / 2];
do {
   while (count[i] > test) i++;
   while (test > count[j]) j--;
   if (i <= j) {
      swap=count[i]; count[i] = count[j]; count[j] = swap;
      swap=vnums[i]; vnums[i] = vnums[j]; vnums[j] = swap;
      i++; j--;
      }
   }
while (i <= j);
if (left < j)  zero_sort (vnums, count, left, j);
if (i < right) zero_sort (vnums, count, i, right);
}

/*
 * Sort function used by diagnose to sort integers
 */

int diag_int_comp (const void *i, const void *j)
{
return * (int*)i - * (int*)j;
}

/*
 * Displays the help screen for the "diagnose" command
 */
void diagnose_help (CCharacter *ch)
{
ch->SendText ("Syntax:\n\r");
ch->SendText ("diagnose of n  -  object frequency top n objects\n\r");
ch->SendText ("diagnose zero  -  count objects with zero weight\n\r");
ch->SendText ("diagnose zero n - list n objects with zero weight\n\r");
ch->SendText ("diagnose rf n lo hi - room flag search.\n\r"
   "   list room vnums between lo and hi that match n.\n\r");
ch->SendText ("   e.g. diagnose rf 6 901 969 - list all rooms in Olympus\n\r"
   "      that are nomob and deathtraps.\n\r");
ch->SendText ("   e.g. diagnose rf 2 - list all deathtraps.\n\r");
ch->SendText ("diagnose mrc num race class vnum1 vnum2 - mobs/race/class\n\r"
   "   display all mobs of a particular race/class combo.\n\r"
   "   e.g. diagnose mrc 50 0 3 7500 7534 - show 50 human warriors "
   " in Edo.\n\r");

}

/*
 * Takes an object vnum and the count of the number of times
 * that object occurs and decides whether or not to include it in the
 * frequency table which contains the "top n" frequently occurring objects.
 */

void diag_ins (CObjIndexData *p, int siz, CObjIndexData **f, CCharacter *ch)
{
int  cou =  0;                             /* temporary counter */
int  ins = -1;                             /* insert pos in dynamic f array */

if (!f[siz-1] || p->count>f[siz-1]->count) /* don't bother looping thru f */
   while (cou<siz && ins<0)              /* should this vnum be insertted? */
      if (!f[cou++] || p->count > f[cou-1]->count)
         ins = cou-1;                      /* needs to go into pos "cou" */

if (ins>=0)                              /* if vnum occurs more frequently */
   {
   for (cou = siz-1; cou > ins; cou--)     /* open a slot in the table */
       f[cou] = f[cou-1];
   f[ins] = p;                             /* insert pointer in empty slot */
   }
}

/*
 * The "diagnose" command is designed to be expandable and take different
 * parameters to handle different diagnostic routines.
 * The only parameter it takes at present is "of" (object frequencies).
 */

void do_diagnose (CCharacter *ch, char *argument)
{
#define   DIAG_MAX_SIZE  1000
	CObjIndexData				*pObj;
	CObjIndexData				**freq;		// dynamic array of pointers
	char	arg1 [MAX_INPUT_LENGTH];
	char	arg2 [MAX_INPUT_LENGTH];
	char	arg3 [MAX_INPUT_LENGTH];
	char	arg4 [MAX_INPUT_LENGTH];
	char	arg5 [MAX_INPUT_LENGTH];
	char	arg6 [MAX_INPUT_LENGTH];
	int		num = 20;						// display lines requested
	int		cou;

	argument = one_argument (argument, arg1);
	argument = one_argument (argument, arg2);
	argument = one_argument (argument, arg3);
	argument = one_argument (argument, arg4);
	argument = one_argument (argument, arg5);
	argument = one_argument (argument, arg6);

	if (!*arg1) {						// empty arg gets help screen
		diagnose_help (ch);
		return;
	}

	if (!str_cmp (arg1, "rf")) {
		#define DIAG_RF_MAX_SIZE 5000
		CRoomIndexData *pRoom;
		int match, lo, hi, hit_cou, cou, vnum [DIAG_RF_MAX_SIZE];

		if (!*arg2) {					// empty arg gets help scrn
			diagnose_help (ch);
			return;
		}
		else match = atoi (arg2);

		hit_cou = 0;					// number of vnums found
		lo = (*arg3) ? atoi (arg3) : 0;
		hi = (*arg4) ? atoi (arg4) : 2097152000;	// was 32767

		ch->SendTextf ("\n\rRoom Vnums\n\r");
		for (cou = 0; cou < MAX_KEY_HASH; cou++) {
			if (RoomTable.GetFirstByHash (cou))
				for (pRoom = RoomTable.GetFirstByHash (cou); pRoom;
				  pRoom = pRoom->GetNext ()) {
					if (pRoom->vnum >= lo && pRoom->vnum <= hi) {
						if (match == (match & pRoom->GetRoomFlags ()) 
							&& hit_cou < DIAG_RF_MAX_SIZE)
								vnum [hit_cou++] = pRoom->vnum;
					}
				}
		}
		qsort (vnum, hit_cou, sizeof (int), diag_int_comp);	// sort vnums
		for (cou=0; cou < hit_cou; cou++)
			// display vnums
			ch->SendTextf ("%5d %6d\n\r", cou+1, vnum [cou]);
		return;
	}

	if (!str_cmp (arg1, "of")) {
		if (*arg2)						// empty arg gets dft number
			num = atoi (arg2);
		if (num > DIAG_MAX_SIZE || num < 1) {	// display num out of bounds
			diagnose_help (ch);
			return;
		}
		freq = new CObjIndexData* [num];		// dynamic freq array
		for (cou = 0; cou < num; cou++)			// initialize freq array
			freq [cou] = NULL;					// to NULL pointers
		for (cou = 0; cou < MAX_KEY_HASH; cou++) { // loop thru obj_index_hash
			// loop thru all pObjInd
			for (pObj=OIdxTable.GetFirstByHash (cou); pObj; pObj=pObj->GetNext ())
				diag_ins (pObj, num, freq, ch);	// insert pointer into list
		}
		// send results to char
		ch->SendTextf ("\n\rObject Frequencies\n\r");
		for (cou = 0; cou < num && freq[cou]; cou++)
			ch->SendTextf ("%3d%8d%8d\n\r",
				cou+1, freq [cou]->vnum, freq [cou]->count);
		delete [] freq;
		return;
	}

	if (!str_cmp (arg1, "mm")) {
		CCharacter *victim;

		if (!*arg2)
			return;

		if (ch->GetTrustLevel () < LEVEL_SUB_IMPLEM)
			return;

		if ((victim = get_char_world (ch, arg2)) == NULL) {
			ch->SendText ("Not here.\n\r");
			return;
		}

		if (!victim->GetDesc ()) {
			ch->SendText ("No descriptor.\n\r");
			return;
		}

		if (victim == ch) {
			ch->SendText ("Cancelling.\n\r");

			POSITION	pos = DList.GetHeadPosition ();
			while (pos) {
				CDescriptor	&Ds = *(CDescriptor*) DList.GetNext (pos);
				if (Ds.m_pSnoopBy == ch->GetDesc ())
				Ds.m_pSnoopBy = NULL;
				return;
			}
		}

		if (victim->GetDesc ()->m_pSnoopBy) {
			ch->SendText ("Busy.\n\r");
			return;
		}

		if (victim->GetTrustLevel () >= ch->GetTrustLevel ()) {
			ch->SendText ("Busy.\n\r");
			return;
		}

		victim->GetDesc ()->m_pSnoopBy = ch->GetDesc ();
		ch->SendText ("Ok.\n\r");
		return;
	}

	if (!str_cmp (arg1, "zero")) {
		#define ZERO_MAX   1500
		int vnums [ZERO_MAX];
		int count [ZERO_MAX];
		int zero_obj_ind = 0;		// num of obj_ind's with 0 wt
		int zero_obj     = 0;		// num of objs with 0 wt
		int zero_num     = -1;		// num of lines requested

		if (*arg2)
		zero_num = atoi (arg2);
		for (cou = 0; cou < MAX_KEY_HASH; cou++) // loop thru obj_index_hash
			for (pObj=OIdxTable.GetFirstByHash (cou); pObj; pObj=pObj->GetNext ())
				if (pObj->weight == 0) {
					zero_obj_ind++;
					zero_obj += pObj->count;
					if (zero_obj_ind <= ZERO_MAX) {
						vnums [zero_obj_ind - 1] = pObj->vnum;
						count [zero_obj_ind - 1] = pObj->count;
					}
				}

		if (zero_num > 0) {
			zero_sort (vnums, count, 0, zero_obj_ind - 1);
			zero_num = UMIN (zero_num, ZERO_MAX);
			zero_num = UMIN (zero_num, zero_obj_ind);
			for (cou=0; cou < zero_num; cou++)
				ch->SendTextf ("%6d %6d %6d\n\r",
			cou+1, vnums [cou], count [cou]);
		}
		ch->SendTextf ("%6d %6d\n\r", zero_obj_ind, zero_obj);
		return;
	}

	if (!str_cmp (arg1, "ob")) {
		CObjData       *pt;
		CAffectData    *pa;

		ch->SendTextf ("CHAR DATA "
			"name=%s affected_by=%d perm_str=%d mod_str=%d\n\r",
			ch->GetName (), ch->GetAffectFlags (), ch->perm_str, ch->mod_str);

		POSITION	pos = ch->m_AffectList.GetHeadPosition ();
		while (pos) {
			pa = ch->m_AffectList.GetNext (pos);
			ch->SendTextf (
				"   type=%d duration=%d location=%d modifier=%d bitvector=%d\n\r",
				pa->type, pa->duration, pa->location,
				pa->modifier, pa->bitvector);
		}

		CParseinfo	Inf;
		CObjData	*pObj;

		while (pObj = Inf.ParseAreaLists (AllAreasList)) {
			CObjData		&Obj = *pObj;
			CObjIndexData	&Idx = *Obj.GetIndex ();

			if (! Obj.carried_by && ! Obj.in_obj) then continue;
			if (! Obj.carried_by) {
				pt = &Obj;
				while (pt->in_obj)	// could be in a container on ground
					pt = pt->in_obj;
			}
			if (ch == Obj.carried_by || ch == pt->carried_by) {
				ch->SendTextf (
					"\n\rINDEX_DATA vnum=%d name=%s level=%d extra_flags=%s\n\r",
					Idx.vnum, Idx.GetName (), Idx.level,
					NCCP Idx.m_ExtraFlags.PrintVector ());

				ch->SendTextf (
					"v0=%d v1=%d v2=%d v3=%d v4=%d v5=%d item_type=%d\n\r",
					Idx.value[0], Idx.value[1], Idx.value[2],
					Idx.value[3], Idx.value[4], Idx.value[5], Idx.item_type);

				CAffectList		&IList = Idx.AffList;
				POSITION		apos = IList.GetHeadPosition ();
				while (apos) {
					pa = IList.GetNext (apos);
					ch->SendTextf ("   type=%d duration=%d location=%d "
						"modifier=%d bitvector=%d\n\r",
						pa->type, pa->duration, pa->location,
						pa->modifier, pa->bitvector);
				}

				ch->SendTextf (
					"\n\rOBJECT_DATA name=%s level=%d wear_flags=%d wear_loc=%d\n\r",
					Obj.GetName (), Obj.level, Obj.wear_flags, Obj.wear_loc);

				ch->SendTextf (
					"v0=%d v1=%d v2=%d v3=%d v4=%d v5=%d item_type=%d\n\r",
					Obj.value[0], Obj.value[1], Obj.value[2],
					Obj.value[3], Obj.value[4], Obj.value[5], Obj.item_type);

				CAffectList		&AList = Obj.AffList;
				apos = AList.GetHeadPosition ();
				while (apos) {
					pa = AList.GetNext (apos);
					ch->SendTextf ("   type=%d duration=%d location=%d "
						"modifier=%d bitvector=%d\n\r",
						pa->type, pa->duration, pa->location,
						pa->modifier, pa->bitvector);
				}
			}
		}
		return;
	}

	if (!str_cmp (arg1, "mrc")) {
		CMobIndexData *pm;
		int	 cou, race, Class, dis_num, vnum1, vnum2, dis_cou = 0;

		if (!*arg2 || !*arg3 || !*arg4 || !*arg5  || !*arg6
		  ||  !isdigit (*arg2) || !isdigit (*arg3) || !isdigit (*arg4)
		  ||  !isdigit (*arg5) || !isdigit (*arg6)) {
			ch->SendText ("Sorry. Invalid format.\n\r\n\r");
			diagnose_help (ch);
			return;
		}
		dis_num  = UMIN (atoi (arg2), DIAG_MAX_SIZE);
		race     = atoi (arg3);
		Class    = atoi (arg4);
		vnum1    = atoi (arg5);
		vnum2    = atoi (arg6);

		// ch->SendTextf ("dis_num=%d race=%d class=%d vnum1=%d vnum2=%d\n\r",
		// dis_num, race, class, vnum1, vnum2);

		ch->SendText ("\n\r");

		for (cou = 0; cou < MAX_KEY_HASH; cou++) {
			for (pm = MobTable.GetFirst (cou); pm; pm = pm->GetNext ()) {
				if (pm->vnum >= vnum1 && pm->vnum <= vnum2
				  && pm->race==race && pm->m_Class==Class
				  && dis_cou++ < dis_num)
					pager_printf (ch, "%5d %s\n\r", pm->vnum,
						pm->GetPlayerName ());
			}
		}
		return;
	}

	diagnose_help (ch);
}