/
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/
/* Crimson2 Mud Server
 * All source written/copyright Ryan Haksi 1995 *
 * This source code is proprietary. Use in whole or in part without
 * explicity permission by the author is strictly prohibited
 *
 * Current email address(es): cryogen@infoserve.net
 * Phone number: (604) 591-5295
 *
 * C4 Script Language written/copyright Cam Lesiuk 1995
 * Email: clesiuk@engr.uvic.ca
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#ifndef WIN32 
  #include <unistd.h>
  #include <dirent.h>
#else
  #include <io.h>
  #include <direct.h>
#endif
#include <ctype.h>

#include "crimson2.h"
#include "macro.h"
#include "queue.h"
#include "log.h"
#include "mem.h"
#include "str.h"
#include "ini.h"
#include "send.h"
#include "extra.h"
#include "property.h"
#include "code.h"
#include "file.h"
#include "thing.h"
#include "index.h"
#include "world.h"
#include "area.h"
#include "reset.h"
#include "edit.h"
#include "history.h"
#include "socket.h"
#include "base.h"
#include "object.h"
#include "char.h"
#include "affect.h"
#include "effect.h"
#include "skill.h"
#include "player.h"
#include "parse.h"
#include "cmd_inv.h"
#include "cmd_misc.h"

#ifdef WIN32
  /* including winsock.h etc auto includes the windows types */
  /* turn off warning: benign type redefinition */
  #pragma warning( disable : 4142 )
  #include <winsock.h>
  #pragma warning( default : 4142 )
#endif

#define PLAYER_ALLOC_SIZE 4096
#define PLAYER_INDEX_SIZE 1024

INDEX playerIndex;

BYTE     playerInitLog;
LWORD    playerStartRoom;
LWORD    playerIdleRoom;
LWORD    playerIdleTick;
LWORD    playerDropTick;
LWORD    playerAFKDropTick;

BYTE    *pSystemList[] = {
  "LOG",
  "KILLER",
  "HELPER",
  "CRASHSAVED",
  "NOHASSLE",
  ""
};

BYTE    *pAutoList[] = {
  "A-LOOK",
  "A-EXIT",
  "A-LOOT",
  "A-ASSIST",
  "A-AGGRESSIVE",
  "A-JUNK",
  "A-FLEE",
  "A-EAT",
  "A-DRINK",
  "NOEMAIL",
  "HINT",
  "EXPERT",
  "CONSIDER",
  "A-RESCUE",
  "AFK",
  ""
};

BYTE *pSpeechColor[] = {
  "rA",
  "gA",
  "pA",
  "cA",
  "CA",
  "wA",
  "WA",
  "WR",
  "gR",
  "GR",
  "yR", 
  "pR", 
  "cR", 
  "CR", 
  "WR", 
  "AG", 
  "RG", 
  "yG", 
  "BG", 
  "PG", 
  "cG", 
  "AY", 
  "rY", 
  "RY", 
  "gY", 
  "BY", 
  "PY", 
  "cY", 
  "wY", 
  "rB", 
  "gB", 
  "yB", 
  "pB", 
  "cB", 
  "wB", 
  "WB", 
  "aP", 
  "gP", 
  "yP", 
  "YP", 
  "cP", 
  "CP", 
  "WP", 
  "AC", 
  "rC", 
  "RC", 
  "gC", 
  "yC", 
  "bC", 
  "BC", 
  "PC", 
  "wC", 
  "rW", 
  "AW", 
  "RW", 
  "bW", 
  "BW", 
  "pW", 
  "PW", 
  "GW",
  ""
};

LWORD pSpeechColorNum = 0;

void PlayerInit(void) {
  BYTE           buf[256];
  #ifndef WIN32
    DIR                *playerDir;
    struct dirent      *playerEnt;
  #else
    struct _finddata_t  playerFind;
    LWORD               findError;
  #endif
  LWORD          sLen;
  BYTE           playerName[256];
  BYTE           playerFile[256];
  LWORD          playerNum;
  LWORD          playerDelete = FALSE;
  LWORD          deleteThreshold;
  LWORD          deleteNum;
  THING         *player;

  IndexInit(&playerIndex, PLAYER_INDEX_SIZE, "playerIndex", 0);

  playerInitLog   = INILWordRead("crimson2.ini", "playerInitLog", 0);
  playerStartRoom = INILWordRead("crimson2.ini", "pStartRoom",    3001);
  playerIdleRoom  = INILWordRead("crimson2.ini", "pIdleRoom",     0);
  playerIdleTick  = INILWordRead("crimson2.ini", "pIdleTick",     12);
  playerDropTick  = INILWordRead("crimson2.ini", "pDropTick",     48);
  playerAFKDropTick=INILWordRead("crimson2.ini", "pAFKTick",      360);

  if (PLAYER_MAX_SKILL < skillNum) {
    sprintf(buf, "Increase PLAYER_MAX_SKILL to at least %hd\n", skillNum);
    Log(LOG_ERROR, buf);
    EXIT(ERROR_BADFILE);
  }
  
  for (pSpeechColorNum=0; *pSpeechColor[pSpeechColorNum]; pSpeechColorNum++);


  /* Scan Crash directory - Count objects as online */
  playerNum = 0;
  Log(LOG_BOOT, "Scanning crash/ for active files\n");
#ifndef WIN32
  playerDir = opendir("crash");
  if (!playerDir) {
    Log(LOG_ERROR, "Directory crash/ doesnt exist... create it!\n");
    EXIT(ERROR_BADFILE);
  }
  while( (playerEnt=readdir(playerDir)) ) {
    sLen = strlen(playerEnt->d_name);
    strcpy(playerName, playerEnt->d_name);
#else
  findError = _findfirst( "crash\\*.plr", &playerFind );
  while( findError != -1 ) {
    strcpy(playerName, playerFind.name);
    sLen = strlen(playerName);
#endif
    if (sLen > 4 && !strcmp(playerName+sLen-4, ".plr")) {
      playerName[sLen-4] = '\0';
      /* see if the crash file is stale, if it is, move it! */
      player = PlayerRead(playerName, PREAD_SCAN);
      /* its stale when its >48 hours old */
      if (time(0) - Plr(player)->pTimeLastOn > 48*3600) {
        #ifdef WIN32
          sprintf(buf, "move crash\\%s.plr player\\%s.plr", playerName, playerName);
        #else
          sprintf(buf, "mv crash/%s.plr player/%s.plr", playerName, playerName);
        #endif
        system(buf);
      } else {
#ifndef WIN32
        PlayerCountObjects("crash", playerEnt->d_name, PCO_ONLINE);
#else
        PlayerCountObjects("crash", playerFind.name, PCO_ONLINE);
#endif
        if (playerInitLog) {
          if (playerNum%5 == 4) {
            sprintf(buf, " %-14s\n", playerName);
          } else {
            sprintf(buf, " %-14s", playerName);
          }
          if (playerNum%5==0)
            Log(LOG_BOOT, buf);
          else
            LogPrintf(LOG_BOOT, buf);
        }
      }
      THINGFREE(player);
      playerNum++;
    }
#ifdef WIN32
    if ( _findnext( findError, &playerFind ) !=0) {
      _findclose(findError);
      break;
    }
#endif
  }  
  if (playerNum>0 && playerNum%5!=0)
    LogPrintf(LOG_BOOT, "\n");
#ifndef WIN32
  closedir(playerDir);
#endif
  sprintf(buf, "%ld crashed players found.\n\n", playerNum);
  Log(LOG_BOOT, buf);



  /* Scan Players directory - Count offline objects */
  playerNum = 0;
  deleteNum = 0;
  Log(LOG_BOOT, "Scanning players/ for active files\n");
#ifndef WIN32
  playerDir = opendir("player");
  if (!playerDir) {
    Log(LOG_ERROR, "Directory player/ doesnt exist... create it!\n");
    EXIT(ERROR_BADFILE);
  }
  while( (playerEnt=readdir(playerDir)) ) {
    sLen = strlen(playerEnt->d_name);
    strcpy(playerName, playerEnt->d_name);
    strcpy(playerFile, playerEnt->d_name);
#else
  findError = _findfirst( "player\\*.plr", &playerFind );
  while( findError != -1 ) {
    strcpy(playerName, playerFind.name);
    strcpy(playerFile, playerFind.name);
    sLen = strlen(playerName);
#endif

    if (sLen > 4 && !strcmp(playerName+sLen-4, ".plr")) {
      playerName[sLen-4] = '\0';

      /* Check for auto-delete its stale when its > ?? days */
      player = PlayerRead(playerName, PREAD_SCAN);
      if (Character(player)->cExp <= 0 && Character(player)->cLevel <= 1)
        deleteThreshold = 10 * 24 * 3600;
      else if (Character(player)->cLevel <= 3)
        deleteThreshold = 30 * 24 * 3600;
      else if (Character(player)->cLevel <= 10)
        deleteThreshold = 60 * 24 * 3600;
      else if (Character(player)->cLevel <= 20)
        deleteThreshold = 120 * 24 * 3600;
      else if (Character(player)->cLevel <= 30)
        deleteThreshold = 150 * 24 * 3600;
      else
        deleteThreshold = 365 * 24 * 3600;
      playerDelete = FALSE;
      if (Plr(player)->pTimeLastOn && (time(NULL) - Plr(player)->pTimeLastOn) > deleteThreshold) {
        playerDelete = TRUE;
        deleteNum += 1;
      }
      THINGFREE(player);

      if (playerDelete) {
        /* They're history */
        #ifdef WIN32
          sprintf(buf, "move player\\%s.plr player\\%s.plr.del", playerName, playerName);
        #else
  	/* THERE IS AS BUG deleting player files at random, so I took this out.  -- CAM TAG TAG */
        /*  sprintf(buf, "mv player/%s.plr player/%s.plr.del", playerName, playerName);
*/
        #endif
        /*system(buf);
*/
      } else {
        /* Still with us, collect some stats on them */
        PlayerCountObjects("player", playerFile, PCO_OFFLINE);

        if (playerInitLog) {
          if (playerNum%5 == 4) {
            sprintf(buf, " %-14s\n", playerName);
          } else {
            sprintf(buf, " %-14s", playerName);
          }
          if (playerNum%5==0)
            Log(LOG_BOOT, buf);
          else
            LogPrintf(LOG_BOOT, buf);
        }
        playerNum++;
      }
    }
#ifdef WIN32
    if ( _findnext( findError, &playerFind ) !=0) {
      _findclose(findError);
      break;
    }
#endif
  }  
  if (playerNum>0 && playerNum%5!=0)
    LogPrintf(LOG_BOOT, "\n");
#ifndef WIN32
  closedir(playerDir);
#endif
  sprintf(buf, "%ld offline players found.\n", playerNum);
  Log(LOG_BOOT, buf);
  sprintf(buf, "%ld old players turfed.\n\n", deleteNum);
  Log(LOG_BOOT, buf);
}

/* Count all the Objects in a player file - call before boot-time reset */
void PlayerCountObjects(BYTE *directory, BYTE *playerFileName, LWORD online) {
  FILE        *playerFile;
  BYTE         playerFileBuf[128];
  BYTE         buf[256];
  OBJTEMPLATE *objTemplate;
  LWORD        oVirtual;


  sprintf(playerFileBuf, "%s/%s", directory, playerFileName);
  playerFile = fopen(playerFileBuf, "rb");
  if (!playerFile) {
    Log(LOG_ERROR, "PlayerCountObjects: Unable to open: ");
    LogPrintf(LOG_ERROR, playerFileBuf);
    LogPrintf(LOG_ERROR, "\n");
    return;
  }

  if (PlayerReadHeader(playerFile, "[Objects]")){
    while(!feof(playerFile)) {
      fscanf(playerFile, " ");
      buf[0]=0;
      fgets(buf, sizeof(buf), playerFile);
      if (*buf=='[') break;
      switch(buf[0]) {
      case '#':
        sscanf(buf, "# %ld", &oVirtual);
        if (oVirtual != -1)
          objTemplate = ObjectOf(oVirtual);
        else
          objTemplate = NULL;
        if (objTemplate) {
          if (online) {
            objTemplate->oOnline++;
          } else {
            objTemplate->oOffline++;
          }
        }
        break;
      }
    }
  }
  fclose(playerFile);
}


THING *PlayerFind(BYTE *playerName) {
  LWORD  i;

  for (i=0; i<playerIndex.iNum; i++) {
    if (!STRICMP(Thing(playerIndex.iThing[i])->tSDesc->sText, playerName))
      return playerIndex.iThing[i]; /* allready playing */
  }
  return NULL;
}

/* reformat a player name to correct case, guard against caps lock morons */
/* returns true/false name string is valid */
BYTE PlayerName(BYTE *playerName) {
  BYTE   allUpper = TRUE;
  LWORD  i;

  if (!playerName) return PNAME_INVALID; /* null */
  if (strlen(playerName)<3) return PNAME_INVALID; /* too short */
  if (strlen(playerName)>15) return PNAME_INVALID; /* too long */

  if (islower(playerName[0]))
    playerName[0] = toupper(playerName[0]);

  /* ensure no punctuation in name */
  for (i=0; playerName[i] != '\0'; i++) {
    if (islower(playerName[i]))
      allUpper = FALSE;
    if (!isalpha(playerName[i]))
      return PNAME_INVALID;
  }

  /* HAHA upper case bozo's are in for a shock */
  if (allUpper) /* lowercase everything but first char */
    for (i=1; playerName[i] != '\0'; i++)
      playerName[i] = tolower(playerName[i]);

  return PNAME_VALID;
}


THING *PlayerAlloc(BYTE *playerName) {
  PLR   *player;
  LWORD  i;

  MEMALLOC(player, PLR, PLAYER_ALLOC_SIZE);
  memset(player, 0, sizeof(PLR));
  IndexAppend(&playerIndex, Thing(player));

  /* Complete set of defaults */
  Thing(player)->tType       = TTYPE_PLR;
  Thing(player)->tSDesc      = STRCREATE(playerName); /* name */
  Thing(player)->tDesc       = STRCREATE("You see nothing special.\n"); /* paragraph description */

  Base(player)->bKey         = STRCREATE(playerName); /* keywords that you can use to access player */
  Base(player)->bLDesc       = STRCREATE("$n the Titleless"); /* name & title */
  Base(player)->bWeight      = 150;

  Character(player)->cLevel  = 1;
  Character(player)->cPos    = POS_STANDING;

  Plr(player)->pPassword     = STRCREATE(""); /* default password */
  Plr(player)->pEnter        = STRCREATE("$n appears from a cloud of blue sparkles\n"); /* default password */
  Plr(player)->pExit         = STRCREATE("$n dissolves into a cloud of blue sparkles\n"); /* default password */
  Plr(player)->pPrompt       = STRCREATE(""); /* default password */
  Plr(player)->pLastLogin    = STRCREATE(""); /* default password */
  Plr(player)->pEmail        = STRCREATE(""); /* default password */
  Plr(player)->pPlan         = STRCREATE(""); /* default password */
  Plr(player)->pHunger       = -1;
  Plr(player)->pThirst       = -1;
  Plr(player)->pAuto         = PA_AUTOLOOK | PA_AUTOEXIT | PA_AUTOEAT | PA_AUTOFLEE
                               | PA_NOEMAIL | PA_AUTODRINK | PA_HINT | PA_CONSIDER;
  Plr(player)->pSockPref     = sockDefFlag;
  Plr(player)->pScreenLines  = 23;

  Plr(player)->pStartRoom    = -1;
  Plr(player)->pRecallRoom   = -1;
  Plr(player)->pDeathRoom    = 100;

  memcpy(Plr(player)->pColorPref, defaultPref, sizeof(COLORPREF)*COLORPREF_MAX);
  /* assign a random speech color */
  i = Number(0, pSpeechColorNum-1);
  Plr(player)->pColorPref[6].cFG = *pSpeechColor[i];
  Plr(player)->pColorPref[6].cBG = *(pSpeechColor[i]+1);

  return Thing(player);
}

BYTE PlayerCrashExists(THING *thing) {
  FILE *file;
  BYTE  playerFileBuf[128];
  BYTE  playerName[128];

  if (!thing) return FALSE;

  /* obtain filename version of players name */
  strcpy(playerName, thing->tSDesc->sText);
  StrToLower(playerName);

  /* plr file */
  sprintf(playerFileBuf, "crash/%s.plr", playerName);
  file = fopen(playerFileBuf, "rb");
  if (file) {
    fclose(file);
    return TRUE;
  } else
    return FALSE;
}

BYTE PlayerCloneExists(THING *thing) {
  FILE *file;
  BYTE  playerFileBuf[128];
  BYTE  playerName[128];

  if (!thing) return FALSE;

  /* obtain filename version of players name */
  strcpy(playerName, thing->tSDesc->sText);
  StrToLower(playerName);

  /* plr file */
  sprintf(playerFileBuf, "clone/%s.plr", playerName);
  file = fopen(playerFileBuf, "rb");
  if (file) {
    fclose(file);
    return TRUE;
  } else
    return FALSE;
}

/* Since the online totals of objects are adjusted when a crash player file
   is read in, in the event that the password check fails (after the file
   has allready been read) the online totals to be re-adjusted.
 */
void PlayerAddOnline(THING *thing) {
  if (!thing) return;
  if (thing->tType == TTYPE_OBJ) {
    Obj(thing)->oTemplate->oOnline++;
  }
  PlayerAddOnline(thing->tContain);
  PlayerAddOnline(thing->tNext);
}

/* the ftell/fseek business prevents a missing setting from preventing reads
   of later ones due to zipping down to the end of the file */
BYTE PlayerReadHeader(FILE *playerFile, BYTE *header) {
  BYTE  buf[512];
  LWORD playerPos;

  playerPos = ftell(playerFile);
  while (!feof(playerFile)) {
    fscanf(playerFile, " %s", buf);
    if (!STRICMP(header, buf)) return TRUE;
    fgets(buf, 511, playerFile); /* must be some other line */
  }
  if (feof(playerFile))
    fseek(playerFile, playerPos, SEEK_SET);
  return FALSE;
}


/* Misplaced {'s and }'s will really screw this thing up, the return value
 * is used so that during recursion it can tell itself not to overrun the
 * end of file or the end of the group!
 */
BYTE PlayerReadObject(THING *player, FILE *playerFile, THING *thing, BYTE mode) {
  THING       *object;
  OBJTEMPLATE *objTemplate;
  LWORD        oVirtual;
  LWORD        apply;
  BYTE         aType[256];
  LWORD        aValue;
  BYTE         buf[256];
  LWORD        i;
  LWORD        typeCheck = -1;

  fgets(buf, sizeof(buf), playerFile);
  for (i=0;buf[i]==' ';i++);
  object = NULL;
  while(!feof(playerFile)) {
  
    if (buf[i]=='{') /* objects within objects */
      if (!PlayerReadObject(player, playerFile, object, mode))
        return FALSE;
      
    if (buf[i]=='}') return TRUE;  /* end of nested objects level */
    if (buf[i]=='[') return FALSE; /* End of section */
    switch(buf[i]) {
    case '#':
      apply=0;
      sscanf(buf+i, "# %ld", &oVirtual);
      if (oVirtual != -1)
        objTemplate = ObjectOf(oVirtual);
      else
        objTemplate = NULL;
      if (objTemplate) {
        object = ObjectCreate(objTemplate, thing);
        typeCheck = -1;
        /* trailing comment of object name is ignored */
        sscanf(buf+i, 
              "# %*d %ld %ld %ld %ld %ld %ld %ld",
               &Obj(object)->oDetail.lValue[0],
               &Obj(object)->oDetail.lValue[1],
               &Obj(object)->oDetail.lValue[2],
               &Obj(object)->oDetail.lValue[3],
               &Obj(object)->oEquip,
               &Obj(object)->oAct,
               &typeCheck);
        if (Obj(object)->oEquip && objTemplate->oType == OTYPE_WEAPON)
          Character(player)->cWeapon = object;
          
        BITSET(Character(player)->cEquip, Obj(object)->oEquip);
          
        /* Now that the object is really loaded remove extra online entry */
        if (mode == PREAD_CRASH)
          objTemplate->oOnline--; 
        /* In the game now, no longer offline */
        else if (mode == PREAD_PLAYER)
          objTemplate->oOffline--;
      } else {
        object = NULL;
      }
      break;
        
    case 'A':
      if (object) {
        sscanf(buf+i, "A %s %ld", aType, &aValue);
        Obj(object)->oApply[apply].aType = TYPESSCANF(aType, applyList);
        TYPECHECK(Obj(object)->oApply[apply].aType, applyList);
        Obj(object)->oApply[apply].aValue = aValue;
        apply++;
      }
      break;
    }
    
    fgets(buf, sizeof(buf), playerFile);
    for (i=0;buf[i]==' ';i++);

    /* if we've finished reading this object see if its been obsoleted */
    switch(buf[i]) {
    case '{':
    case '}':
    case '[':
    case '#':
      if (object && typeCheck!=-1 && typeCheck!=objTemplate->oType) {
        SendThing("^wRemoving out of play object\n", thing);
        THINGFREE(object);
      }
    }
    
  }
  
  /* End of file */
  return FALSE;
}

/* will return NULL if player does not exist, it should be noted
  that due to internal structure lines greater than 511 characters
  are not supported.
  BTW: in case you are wondering why the settings are all named,
   experience has shown that the player file structure is frequently
   changed and that requests to change level, abilities etc are *VERY*
   common.
  */
THING *PlayerRead(BYTE *playerName, BYTE mode) {
  THING       *player;
  FILE        *playerFile;
  BYTE         playerFileBuf[128];
  BYTE         pathBuf[64] = "crash";
  BYTE         buf[256];
  BYTE        *scan;
  LWORD        i;
  STR         *sKey;
  STR         *sDesc;
  AFFECT      *affect;
  

  /* check to see if a crash recovery file exists first */
  switch (mode) {
  case PREAD_NORMAL:
  case PREAD_SCAN:
    sprintf(playerFileBuf, "crash/%s.plr", playerName);
    StrPath(playerFileBuf);
    StrToLower(playerFileBuf);
    playerFile = fopen(playerFileBuf, "rb");
    if (!playerFile) {
      /* check to see if a normal player save file exists */
      strcpy(pathBuf, "player");
      sprintf(playerFileBuf, "player/%s.plr", playerName);
      StrPath(playerFileBuf);
      StrToLower(playerFileBuf);
      playerFile = fopen(playerFileBuf, "rb");
      if (mode == PREAD_NORMAL)
        mode = PREAD_PLAYER;
    } else 
      if (mode == PREAD_NORMAL)
        mode = PREAD_CRASH;
    break;

  case PREAD_CLONE:
    sprintf(playerFileBuf, "clone/%s.plr", playerName);
    StrPath(playerFileBuf);
    StrToLower(playerFileBuf);
    playerFile = fopen(playerFileBuf, "rb");
    break;
  }

  if (playerFile) {
    /* the basics */
    player = PlayerAlloc(playerName);
    FILESTRREAD(playerFile, Thing(player)->tSDesc);
    if (fileError) {
      sprintf(buf, "Error reading SDesc for player %s\n", playerName);
      Log(LOG_ERROR, buf);
    }
    FILESTRREAD(playerFile, Plr(player)->pPassword);
    if (fileError) {
      sprintf(buf, "Error reading Password for player %s\n", playerName);
      Log(LOG_ERROR, buf);
    }
    FILESTRREAD(playerFile, Base(player)->bLDesc);
    if (fileError) {
      sprintf(buf, "Error reading LDesc for player %s\n", playerName);
      Log(LOG_ERROR, buf);
    }
    FILESTRREAD(playerFile, Thing(player)->tDesc);
    if (fileError) {
      sprintf(buf, "Error reading Desc for player %s\n", playerName);
      Log(LOG_ERROR, buf);
    }
    if (PlayerReadHeader(playerFile, "Messages:")) {
      fgets(buf, sizeof(buf), playerFile); 
      FILESTRREAD(playerFile, Plr(player)->pEnter);
      if (fileError) {
        sprintf(buf, "Error reading EnterMsg for player %s\n", playerName);
        Log(LOG_ERROR, buf);
      }
      FILESTRREAD(playerFile, Plr(player)->pExit);
      if (fileError) {
        sprintf(buf, "Error reading ExitMsg for player %s\n", playerName);
        Log(LOG_ERROR, buf);
      }
      FILESTRREAD(playerFile, Plr(player)->pEmail);
      if (fileError) {
        sprintf(buf, "Error reading Email for player %s\n", playerName);
        Log(LOG_ERROR, buf);
      }
      FILESTRREAD(playerFile, Plr(player)->pPlan);
      if (fileError) {
        sprintf(buf, "Error reading Plan for player %s\n", playerName);
        Log(LOG_ERROR, buf);
      }
    }

    if (PlayerReadHeader(playerFile, "Prompt:")) {
      fscanf(playerFile, " ");
      FILESTRREAD(playerFile, Plr(player)->pPrompt);
      if (fileError) {
        sprintf(buf, "Error reading Prompt for player %s\n", playerName);
        Log(LOG_ERROR, buf);
      }
    }
    if (PlayerReadHeader(playerFile, "LastLogin:")) {
      fscanf(playerFile, " ");
      FILESTRREAD(playerFile, Plr(player)->pLastLogin);
      if (fileError) {
        sprintf(buf, "Error reading LastLogin for player %s\n", playerName);
        Log(LOG_ERROR, buf);
      }
    }
    if (PlayerReadHeader(playerFile, "Level:"))
      Character(player)->cLevel = FileByteRead(playerFile);
    if (PlayerReadHeader(playerFile, "Race:"))
      Plr(player)->pRace = FILETYPEREAD(playerFile, raceList);
    if (PlayerReadHeader(playerFile, "Class:"))
      Plr(player)->pClass = FILETYPEREAD(playerFile, classList);
    if (PlayerReadHeader(playerFile, "Position:"))
      Character(player)->cPos = FILETYPEREAD(playerFile, posList);
    if (Character(player)->cPos >= POS_STUNNED)
      Character(player)->cPos = POS_STANDING;
    if (PlayerReadHeader(playerFile, "Sex:"))
      Character(player)->cSex = FILETYPEREAD(playerFile, sexList);

    if (PlayerReadHeader(playerFile, "AutoAction:"))
      Plr(player)->pAuto = FileFlagRead(playerFile, pAutoList);
    if (PlayerReadHeader(playerFile, "System:"))
      Plr(player)->pSystem = FileFlagRead(playerFile, pSystemList);
    if (PlayerReadHeader(playerFile, "SockPref:"))
      Plr(player)->pSockPref = FileFlagRead(playerFile, sfPrefList);
    if (PlayerReadHeader(playerFile, "ScreenLines:"))
      Plr(player)->pScreenLines = FileByteRead(playerFile);
    if (PlayerReadHeader(playerFile, "ColorPref:")) {
      fscanf(playerFile, " ");
      fgets(buf, sizeof(buf), playerFile);
      scan = buf;
      for (i=0; i<COLORPREF_MAX; i++) {
        if (!*scan) break;
        Plr(player)->pColorPref[i].cFG = scan[0];
        Plr(player)->pColorPref[i].cBG = scan[1];
        scan = StrOneWord(scan, NULL);
      }
    }
/*
    if (PlayerReadHeader(playerFile, "Equip:"))
      fscanf(playerFile, " %ld", &Character(player)->cEquip);
*/
    if (PlayerReadHeader(playerFile, "Practice:"))
      fscanf(playerFile, " %ld", &Plr(player)->pPractice);
    if (PlayerReadHeader(playerFile, "HitP:"))
      fscanf(playerFile, " %ld", &Character(player)->cHitP);
    if (PlayerReadHeader(playerFile, "MoveP:"))
      fscanf(playerFile, " %ld", &Character(player)->cMoveP);
    if (PlayerReadHeader(playerFile, "PowerP:"))
      fscanf(playerFile, " %ld", &Character(player)->cPowerP);

    if (PlayerReadHeader(playerFile, "GainPractice:"))
      fscanf(playerFile, " %hd", &Plr(player)->pGainPractice);
    if (PlayerReadHeader(playerFile, "GainHit:"))
      fscanf(playerFile, " %hd", &Plr(player)->pGainHit);
    if (PlayerReadHeader(playerFile, "GainMove:"))
      fscanf(playerFile, " %hd", &Plr(player)->pGainMove);
    if (PlayerReadHeader(playerFile, "GainPower:"))
      fscanf(playerFile, " %hd", &Plr(player)->pGainPower);

    if (PlayerReadHeader(playerFile, "Fame:"))
      fscanf(playerFile, " %hd", &Plr(player)->pFame);
    if (PlayerReadHeader(playerFile, "Infamy:"))
      fscanf(playerFile, " %hd", &Plr(player)->pInfamy);

    if (PlayerReadHeader(playerFile, "HitPMax:"))
      fscanf(playerFile, " %ld", &Character(player)->cHitPMax);
    if (PlayerReadHeader(playerFile, "MovePMax:"))
      fscanf(playerFile, " %ld", &Plr(player)->pMovePMax);
    if (PlayerReadHeader(playerFile, "PowerPMax:"))
      fscanf(playerFile, " %ld", &Plr(player)->pPowerPMax);

    if (PlayerReadHeader(playerFile, "Hunger:"))
      fscanf(playerFile, " %hd", &Plr(player)->pHunger);
    if (PlayerReadHeader(playerFile, "Thirst:"))
      fscanf(playerFile, " %hd", &Plr(player)->pThirst);
    if (PlayerReadHeader(playerFile, "Intox:"))
      fscanf(playerFile, " %hd", &Plr(player)->pIntox);

    if (PlayerReadHeader(playerFile, "Str:"))
      fscanf(playerFile, " %hd", &Plr(player)->pStr);
    /* temporary fix */
    /*MAXSET(Plr(player)->pStr, raceList[Plr(player)->pRace].rMaxStat[SA_STR]);*/
    if (PlayerReadHeader(playerFile, "Dex:"))
      fscanf(playerFile, " %hd", &Plr(player)->pDex);
    if (PlayerReadHeader(playerFile, "Con:"))
      fscanf(playerFile, " %hd", &Plr(player)->pCon);
    if (PlayerReadHeader(playerFile, "Wis:"))
      fscanf(playerFile, " %hd", &Plr(player)->pWis);
    if (PlayerReadHeader(playerFile, "Int:"))
      fscanf(playerFile, " %hd", &Plr(player)->pInt);
/*
    if (PlayerReadHeader(playerFile, "HitBonus:"))
      fscanf(playerFile, " %hd", &Character(player)->cHitBonus);
    if (PlayerReadHeader(playerFile, "DamBonus:"))
      fscanf(playerFile, " %hd", &Character(player)->cDamBonus);
   
    if (PlayerReadHeader(playerFile, "Armor:"))
      fscanf(playerFile, " %hd", &Character(player)->cArmor);
    for (i=0; i<CHAR_MAX_RESIST; i++) {
      sprintf(buf, "Resist%ld:", i);
      if (PlayerReadHeader(playerFile, buf))
        fscanf(playerFile, " %hd", &Character(player)->cResist[i]);
    }
*/
    if (mode!=PREAD_CLONE && PlayerReadHeader(playerFile, "Money:"))
      fscanf(playerFile, " %ld", &Character(player)->cMoney);
    if (mode!=PREAD_CLONE && PlayerReadHeader(playerFile, "Bank:"))
      fscanf(playerFile, " %ld", &Plr(player)->pBank);
    if (PlayerReadHeader(playerFile, "Exp:"))
      fscanf(playerFile, " %ld", &Character(player)->cExp);

    if (PlayerReadHeader(playerFile, "TimeTotal:"))
      fscanf(playerFile, " %lu", &Plr(player)->pTimeTotal);
    if (PlayerReadHeader(playerFile, "TimeLastOn:"))
      fscanf(playerFile, " %lu", &Plr(player)->pTimeLastOn);
    if (PlayerReadHeader(playerFile, "Attempts:"))
      fscanf(playerFile, " %hd", &Plr(player)->pAttempts);
    if (PlayerReadHeader(playerFile, "StartRoom:"))
      fscanf(playerFile, " %ld", &Plr(player)->pStartRoom);
    if (PlayerReadHeader(playerFile, "RecallRoom:"))
      fscanf(playerFile, " %ld", &Plr(player)->pRecallRoom);
    if (PlayerReadHeader(playerFile, "DeathRoom:"))
      fscanf(playerFile, " %ld", &Plr(player)->pDeathRoom);

    /* Read in the Skills */
    if (PlayerReadHeader(playerFile, "[Skills]")){
      while(fscanf(playerFile, " %s", buf)==1) {
        if (buf[0]=='[') break;
        i = TYPEFIND(buf, skillList);
        if (i!=-1) {
          fscanf(playerFile, " %hd ", &Plr(player)->pSkill[i]);
        }
      }
    }

    /* Read all the Objects */
    if (PlayerReadHeader(playerFile, "[Objects]") && mode != PREAD_SCAN){
      fscanf(playerFile, " ");
      PlayerReadObject(player, playerFile, Thing(player), mode);
    }
    /* apply the affects of equipment, saved in virgin state */
    AffectApplyAll(player);

    /* Read all the Affects - these are applied as they load */
    if (PlayerReadHeader(playerFile, "[Affects]") && mode != PREAD_SCAN){
      while(fscanf(playerFile, " %s", buf)==1) {
        if (buf[0]=='[') break;
        i = TYPEFIND(buf, effectList);
        if (i!=-1) {
          affect = AffectCreate(player, AFFECT_NONE, 0, 0, i);
          AffectRead(player, affect, playerFile);
        } else
          fgets(buf, sizeof(buf), playerFile);
      }
    }

    /* Read all the Properties */
    if (PlayerReadHeader(playerFile, "[Properties]")){
      fscanf(playerFile, " ");
      fgets(buf, sizeof(buf), playerFile);
      while(!feof(playerFile)) {
        if (buf[0]!='P') break;
        sKey =  FileStrRead(playerFile);
        sDesc = FileStrRead(playerFile);
        Thing(player)->tProperty = PropertyCreate(Thing(player)->tProperty, sKey, sDesc);
        /* if this is code - compile it */
        if ( sKey->sText[0]=='@' && !CodeIsCompiled(Thing(player)->tProperty) )
          if (!CodeCompileProperty(Thing(player)->tProperty, NULL))
            CodeSetFlag(Thing(player), Thing(player)->tProperty);
        fgets(buf, sizeof(buf), playerFile);
      }
    }

    /* pack it in */
    fclose(playerFile);

  } else
    player = NULL;

  return Thing(player);
}

void PlayerWriteObject(FILE *playerFile, THING *object, BYTE indent, BYTE mode) {
  LWORD  apply;

  if (!object) return;
  if (object->tType != TTYPE_OBJ || Obj(object)->oTemplate->oVirtual<0) {
    /* cant save the system objects - save any contents though */
    PlayerWriteObject(playerFile, object->tContain, indent, mode);
    PlayerWriteObject(playerFile, object->tNext, indent, mode);
    return;
  }

  {
    LWORD i; /* this way this isnt tying up stack frame space during recursion */

    for(i=0;i<indent;i++) fprintf(playerFile, "  ");
    fprintf(playerFile, 
            "#%-5ld %ld %ld %ld %ld %ld %ld ",
            Obj(object)->oTemplate->oVirtual,
            Obj(object)->oDetail.lValue[0],
            Obj(object)->oDetail.lValue[1],
            Obj(object)->oDetail.lValue[2],
            Obj(object)->oDetail.lValue[3],
            Obj(object)->oEquip,
            Obj(object)->oAct);
    FileByteWrite(playerFile, Obj(object)->oTemplate->oType, ' ');
    fprintf(playerFile, "%s\n", object->tSDesc->sText);
    if (mode == PWRITE_PLAYER)
      Obj(object)->oTemplate->oOffline++;
    for (apply=0; apply<OBJECT_MAX_APPLY; apply++) {
      if (Obj(object)->oApply[apply].aType) {
        for(i=0;i<indent;i++) fprintf(playerFile, "  ");
        fprintf(playerFile, "A ");
        FILETYPEWRITE(playerFile, Obj(object)->oApply[apply].aType, applyList, ' ');
        FileByteWrite(playerFile, Obj(object)->oApply[apply].aValue, '\n');
      }
    }
  }
  
  if (object->tContain) {
    {
      LWORD i; /* this way this isnt tying up stack frame space during recursion */
      for(i=0;i<indent;i++) fprintf(playerFile, "  ");
    }
    fprintf(playerFile, "  {\n");
    PlayerWriteObject(playerFile, object->tContain, indent+1, mode);
    {
      LWORD i; /* this way this isnt tying up stack frame space during recursion */
      for(i=0;i<indent;i++) fprintf(playerFile, "  ");
    }
    fprintf(playerFile, "  }\n");
  }
  
  PlayerWriteObject(playerFile, object->tNext, indent, mode);
}

void PlayerUpdateTime(THING *player) {
  ULWORD    timeNow;

  /* Update timeOnline etc */
  timeNow = time(0);
  Plr(player)->pTimeTotal += timeNow-Plr(player)->pTimeLastOn;
  Plr(player)->pTimeLastOn = timeNow;
}

/* path should be either "crash" or "player", nothing else */
void PlayerWrite(THING *player, BYTE mode) {
  FILE     *playerFile;
  BYTE      playerFileBuf[128];
  BYTE      buf[256];
  LWORD     i;
  PROPERTY *property;
  AFFECT   *affect;

  if (!player) return;

  AffectUnapplyAll(player);

  switch(mode) {
  case PWRITE_PLAYER:
    sprintf(playerFileBuf, "crash/%s.plr", Thing(player)->tSDesc->sText);
    StrToLower(playerFileBuf);
    unlink(playerFileBuf);
    sprintf(playerFileBuf, "player/%s.plr", Thing(player)->tSDesc->sText);
    StrToLower(playerFileBuf);
    break;

  case PWRITE_CRASH:
    sprintf(playerFileBuf, "player/%s.plr", Thing(player)->tSDesc->sText);
    StrToLower(playerFileBuf);
    unlink(playerFileBuf);
    sprintf(playerFileBuf, "crash/%s.plr", Thing(player)->tSDesc->sText);
    StrToLower(playerFileBuf);
    break;

  case PWRITE_CLONE:
    sprintf(playerFileBuf, "clone/%s.plr", Thing(player)->tSDesc->sText);
    StrToLower(playerFileBuf);
    break;
  }

  playerFile = fopen(playerFileBuf, "wb");
  if (!playerFile) {
    sprintf(buf, "Unable to write %s\n", playerFileBuf);
    Log(LOG_ERROR, buf);
    PERROR("PlayerWrite");
    return;
  }
  FileStrWrite(playerFile, Thing(player)->tSDesc);
  FileStrWrite(playerFile, Plr(player)->pPassword);
  FileStrWrite(playerFile, Base(player)->bLDesc);
  FileStrWrite(playerFile, Thing(player)->tDesc);
  fprintf(playerFile, "Messages: \n");
  FileStrWrite(playerFile, Plr(player)->pEnter);
  FileStrWrite(playerFile, Plr(player)->pExit);
  FileStrWrite(playerFile, Plr(player)->pEmail);
  FileStrWrite(playerFile, Plr(player)->pPlan);

  fprintf(playerFile, "%-20s", "Prompt:");
  FileStrWrite(playerFile, Plr(player)->pPrompt);

  fprintf(playerFile, "%-20s", "LastLogin:");
  FileStrWrite(playerFile, Plr(player)->pLastLogin);

  fprintf(playerFile, "%-20s", "Level:");
  FileByteWrite(playerFile, Character(player)->cLevel, '\n');

  fprintf(playerFile, "%-20s", "Race:");
  FILETYPEWRITE(playerFile, Plr(player)->pRace, raceList, '\n');

  fprintf(playerFile, "%-20s", "Class:");
  FILETYPEWRITE(playerFile, Plr(player)->pClass, classList, '\n');

  fprintf(playerFile, "%-20s", "Position:");
  FILETYPEWRITE(playerFile, Character(player)->cPos, posList, '\n');
  fprintf(playerFile, "%-20s", "Sex:");
  FILETYPEWRITE(playerFile, Character(player)->cSex, sexList, '\n');

  fprintf(playerFile, "%-20s", "AutoAction:");
  FileFlagWrite(playerFile, Plr(player)->pAuto, pAutoList, '\n');

  fprintf(playerFile, "%-20s", "System:");
  FileFlagWrite(playerFile, Plr(player)->pSystem, pSystemList, '\n');

  fprintf(playerFile, "%-20s", "SockPref:");
  {
    SOCK *sock;
    sock = BaseControlFind(player);
    if (sock && sock->sMode==MODE_PLAY) 
      Plr(player)->pSockPref = sock->sPref;
  }
  FileFlagWrite(playerFile, Plr(player)->pSockPref, sfPrefList, '\n');

  fprintf(playerFile, "%-20s", "ScreenLines:");
  FileByteWrite(playerFile, Plr(player)->pScreenLines, '\n');

  fprintf(playerFile, "%-20s", "ColorPref:");
  for (i=0; i<COLORPREF_MAX; i++) {
    if (!*colorPrefList[i].cName) break;
    fprintf(playerFile, "%c%c ", Plr(player)->pColorPref[i].cFG, Plr(player)->pColorPref[i].cBG);
  }
  fprintf(playerFile, "\n");
/*
  fprintf(playerFile, "%-20s%ld\n", "Equip:",      Character(player)->cEquip);
*/
  fprintf(playerFile, "%-20s%ld\n", "Practice:",   Plr(player)->pPractice);
  fprintf(playerFile, "%-20s%ld\n", "HitP:",       Character(player)->cHitP);
  fprintf(playerFile, "%-20s%ld\n", "MoveP:",      Character(player)->cMoveP);
  fprintf(playerFile, "%-20s%ld\n", "PowerP:",     Character(player)->cPowerP);
  fprintf(playerFile, "%-20s%hd\n", "GainPractice:",Plr(player)->pGainPractice);
  fprintf(playerFile, "%-20s%hd\n", "GainHit:",    Plr(player)->pGainHit);
  fprintf(playerFile, "%-20s%hd\n", "GainMove:",   Plr(player)->pGainMove);
  fprintf(playerFile, "%-20s%hd\n", "GainPower:",  Plr(player)->pGainPower);
  fprintf(playerFile, "%-20s%hd\n", "Fame:",       Plr(player)->pFame);
  fprintf(playerFile, "%-20s%hd\n", "Infamy:",     Plr(player)->pInfamy);
  fprintf(playerFile, "%-20s%ld\n", "HitPMax:",    Character(player)->cHitPMax);
  fprintf(playerFile, "%-20s%ld\n", "MovePMax:",   Plr(player)->pMovePMax);
  fprintf(playerFile, "%-20s%ld\n", "PowerPMax:",  Plr(player)->pPowerPMax);
  fprintf(playerFile, "%-20s%hd\n", "Hunger:",     Plr(player)->pHunger);
  fprintf(playerFile, "%-20s%hd\n", "Thirst:",     Plr(player)->pThirst);
  fprintf(playerFile, "%-20s%hd\n", "Intox:",      Plr(player)->pIntox);
  fprintf(playerFile, "%-20s%hd\n", "Str:",        Plr(player)->pStr);
  fprintf(playerFile, "%-20s%hd\n", "Dex:",        Plr(player)->pDex);
  fprintf(playerFile, "%-20s%hd\n", "Con:",        Plr(player)->pCon);
  fprintf(playerFile, "%-20s%hd\n", "Wis:",        Plr(player)->pWis);
  fprintf(playerFile, "%-20s%hd\n", "Int:",        Plr(player)->pInt);
/*
  fprintf(playerFile, "%-20s%hd\n", "HitBonus:",   Character(player)->cHitBonus);
  fprintf(playerFile, "%-20s%hd\n", "DamBonus:",   Character(player)->cDamBonus);
  fprintf(playerFile, "%-20s%hd\n", "Armor:",      Character(player)->cArmor);
  for (i=0; i<CHAR_MAX_RESIST; i++) {
    sprintf(buf, "Resist%ld:", i);
    fprintf(playerFile, "%-20s%hd\n", buf,           Character(player)->cResist[i]);
  }
*/
  if (mode != PWRITE_CLONE) {
    fprintf(playerFile, "%-20s%ld\n", "Money:", Character(player)->cMoney);
    fprintf(playerFile, "%-20s%ld\n", "Bank:",  Plr(player)->pBank);
  } else {
    fprintf(playerFile, "%-20s%d\n", "Money:", 0);
    fprintf(playerFile, "%-20s%d\n", "Bank:",  0);
  }
  fprintf(playerFile, "%-20s%ld\n", "Exp:",        Character(player)->cExp);

  fprintf(playerFile, "%-20s%lu\n", "TimeTotal:",  Plr(player)->pTimeTotal);
  fprintf(playerFile, "%-20s%lu\n", "TimeLastOn:", Plr(player)->pTimeLastOn);
  fprintf(playerFile, "%-20s%hd\n", "Attempts:",   Plr(player)->pAttempts);
  fprintf(playerFile, "%-20s%ld\n", "StartRoom:",  Plr(player)->pStartRoom);
  fprintf(playerFile, "%-20s%ld\n", "RecallRoom:", Plr(player)->pRecallRoom);
  fprintf(playerFile, "%-20s%ld\n", "DeathRoom:",  Plr(player)->pDeathRoom);

  /* Write out all the Skills */
  fprintf(playerFile, "\n[Skills]\n");
  for(i=0; i<skillNum; i++) {
    if (Plr(player)->pSkill[i] != 0) {
      fprintf(playerFile, "%-20s%hd\n", skillList[i].sName, Plr(player)->pSkill[i]);
    }
  }
  fprintf(playerFile, "[End of Skills]\n");

  if (mode != PWRITE_CLONE) {
    /* Write out all the Objects */
    fprintf(playerFile, "\n[Objects]\n");
    PlayerWriteObject(playerFile, Thing(player)->tContain, 0, mode);
    fprintf(playerFile, "[End of Objects]\n");

    /* Write out affects */
    fprintf(playerFile, "\n[Affects]\n");
    for(affect = Character(player)->cAffect; affect; affect=affect->aNext)  
      AffectWrite(player, affect, playerFile);
    fprintf(playerFile, "[End of Affects]\n");
  }
  
  /* Properties */
  fprintf(playerFile, "\n[Properties]\n");
  for (property=player->tProperty; property; property=property->pNext) {
    fprintf(playerFile, "P\n");
    FileStrWrite(playerFile, property->pKey);
    if ( CodeIsCompiled(property) ){
      CodeDecompProperty(property, NULL);
      /* warn if it didnt decompile */
      if (CodeIsCompiled(property)) {
        sprintf(buf, "PlayerWrite: Property %s failed to decompile for player %s\n",
          property->pKey->sText,
          player->tSDesc->sText);
        Log(LOG_ERROR, buf);
        fprintf(playerFile, "/* <Decompile Error> */\n~\n");
      } else {
        FileStrWrite(playerFile, property->pDesc);
      }
    } else
      FileStrWrite(playerFile, property->pDesc);
  }
  fprintf(playerFile, "[End of Properties]\n");

  AffectApplyAll(player);

  /* all done close the file */
  fclose(playerFile);
}



/* the following routine is called by ThingFree, dont call it directly */
void PlayerFree(THING *thing) {

  STRFREE(Plr(thing)->pPassword);
  STRFREE(Plr(thing)->pEnter);
  STRFREE(Plr(thing)->pExit);
  STRFREE(Plr(thing)->pPrompt);
  STRFREE(Plr(thing)->pLastLogin);
  STRFREE(Plr(thing)->pEmail);
  STRFREE(Plr(thing)->pPlan);
  /* turf the index entry for this player */
  IndexDelete(&playerIndex, thing, NULL);
  /* give up the memory */
  MEMFREE(Plr(thing), PLR);
}

/* Eat the first thing we find whatever it is, 
  poison not withstanding */
LWORD PlayerAutoEat(THING *thing, THING *search) {
  if (!thing) return FALSE;
  if (Character(thing)->cPos < POS_SITTING) return FALSE;
  if (!search) return FALSE;
  if (search->tType != TTYPE_OBJ) {
    return PlayerAutoEat(thing, search->tNext);
  }
  if (Obj(search)->oTemplate->oType == OTYPE_CONTAINER) {
    if (!PlayerAutoEat(thing, search->tContain))
      return PlayerAutoEat(thing, search->tNext);
    return TRUE;
  }
  if (Obj(search)->oTemplate->oType != OTYPE_FOOD
    ||!InvUnEquip(thing, search, IUE_BLOCKABLE) ) {
    return PlayerAutoEat(thing, search->tNext);
  }
  InvEat(thing, search);
  return TRUE;
}

/* drink from the first drinkcon we find, whatever its
   contents */
LWORD PlayerAutoDrink(THING *thing, THING *search) {
  if (!thing) return FALSE;
  if (Character(thing)->cPos < POS_SITTING) return FALSE;
  if (!search) return FALSE;
  if (search->tType != TTYPE_OBJ) {
    return PlayerAutoDrink(thing, search->tNext);
  }
  if (Obj(search)->oTemplate->oType == OTYPE_CONTAINER) {
    if (!PlayerAutoDrink(thing, search->tContain))
      return PlayerAutoDrink(thing, search->tNext);
    return TRUE;
  }
  if (Obj(search)->oTemplate->oType != OTYPE_DRINKCON
    ||OBJECTGETFIELD(search, OF_DRINKCON_CONTAIN)==0 ) {
    return PlayerAutoDrink(thing, search->tNext);
  }
  InvDrink(thing, search);
  return TRUE;
}

void PlayerIdle() {
  BYTE   crashSave = 2; /* save this many players per beat */
  LWORD  i;
  THING  *thing;
  SOCK   *sock;

  if (playerIndex.iNum==0) return;
  for (i=0; i<playerIndex.iNum && crashSave>0; i++) {
    thing = playerIndex.iThing[i];
    /* Crash save 'em as necessary */
    sock = BaseControlFind(thing);
    if ( (!sock || sock->sMode == MODE_PLAY)
     && !BIT(Plr(thing)->pSystem, PS_CRASHSAVED)) {
      crashSave--;
      if (Base(playerIndex.iThing[i])->bInside 
       && Base(playerIndex.iThing[i])->bInside->tType == TTYPE_WLD)
        Plr(playerIndex.iThing[i])->pStartRoom = Wld(Base(playerIndex.iThing[i])->bInside)->wVirtual;
      PlayerWrite(thing, PWRITE_CRASH);
      BITSET(Plr(playerIndex.iThing[i])->pSystem, PS_CRASHSAVED);
      printf("AS_: Autosaving %s\n", thing->tSDesc->sText);
    }
  }
  
  /* if everyone has been crash saved, start over */
  if (crashSave>0) {
    for(i=0;i<playerIndex.iNum;i++)
      BITCLR(Plr(playerIndex.iThing[i])->pSystem, PS_CRASHSAVED);
  }
}

void PlayerTick() {
  LWORD  i=0;
  THING *thing=NULL;
  THING *obj=NULL;
  LWORD  gain;
  SOCK  *sock;
  BYTE   buf[256];

  if (playerIndex.iNum==0) return;
  while(1) {
    /* I know this while thing is a bit of a strange way to do it
       however at some point in the future we need to be able to 
       set thing to null and then kill/drop link to the thing in
       question and still continue through the loop */
    if (thing == playerIndex.iThing[i]) i++;
    if (i>=playerIndex.iNum) break;
    thing = playerIndex.iThing[i];

    /* Update the the amount of time they've played */
    PlayerUpdateTime(thing);

    /* Check for Affect timeouts ie spells expiring */
    if (CharTick(thing)) continue;

    /* Check for equipped items for stuff */
    for (obj = Base(thing)->bInside; obj; obj=obj->tNext) {
      if (obj->tType == TTYPE_OBJ && Obj(obj)->oEquip) {
        /* Check for Good items */
        if (BIT(Obj(obj)->oAct, OA_GOOD)) {
          if (Character(thing)->cAura < 400)
            Character(thing)->cAura +=1;
        }
        /* Check for evil items */
        if (BIT(Obj(obj)->oAct, OA_EVIL)) {
          if (Character(thing)->cAura > -400)
            Character(thing)->cAura -=1;
        }
      }
    }

    sock = BaseControlFind(thing);
    if (sock) {
      PlayerUpdateTime(thing);
    }

    if (Base(thing)->bInside) {
      if (!BIT(Plr(thing)->pAuto, PA_AFK)) {
        /* gain hit points */
        gain = classList[Plr(thing)->pClass].cGainHitP;
        gain = gain*raceList[Plr(thing)->pRace].rGainHitP/100;
        gain += Character(thing)->cLevel/20;
        gain += Plr(thing)->pCon/20;
        gain = CharGainAdjust(thing, GAIN_HITP, gain);
        Character(thing)->cHitP += gain;
        MAXSET(Character(thing)->cHitP, CharGetHitPMax(thing));
        if (sock && BIT(sock->sPref, SP_CHANTICK)) {
          sprintf(buf, "^pYou gain: ^w%ld ^ghp", gain);
          SendThing(buf, thing);
        }

        /* gain move points */
        gain = classList[Plr(thing)->pClass].cGainMoveP;
        gain = gain*raceList[Plr(thing)->pRace].rGainMoveP/100;
        gain += Character(thing)->cLevel/20;
        gain += Plr(thing)->pDex/20;
        gain = CharGainAdjust(thing, GAIN_MOVEP, gain);
        Character(thing)->cMoveP += gain;
        MAXSET(Character(thing)->cMoveP, CharGetMovePMax(thing));
        if (sock && BIT(sock->sPref, SP_CHANTICK)) {
          sprintf(buf, ", ^w%ld ^gmv", gain);
          SendThing(buf, thing);
        }
      
        /* gain power points */
        gain = classList[Plr(thing)->pClass].cGainPowerP;
        gain = gain*raceList[Plr(thing)->pRace].rGainPowerP/100;
        gain += Character(thing)->cLevel/20;
        gain += Plr(thing)->pInt/20;
        gain = CharGainAdjust(thing, GAIN_POWERP, gain);
        Character(thing)->cPowerP += gain;
        MAXSET(Character(thing)->cPowerP, CharGetPowerPMax(thing));
        if (sock && BIT(sock->sPref, SP_CHANTICK)) {
          sprintf(buf, ", and ^w%ld ^gpp\n", gain);
          SendThing(buf, thing);
        }
      } else {
        if (sock && BIT(sock->sPref, SP_CHANTICK)) {
          SendThing("^yWhile AFK you do not heal/rest or get hungry/thirsty!\n", thing);
        }
      }
      
      if (   Character(thing)->cPowerP == CharGetPowerPMax(thing)
          && Character(thing)->cMoveP == CharGetMovePMax(thing)
          && Character(thing)->cHitP == CharGetHitPMax(thing)
          && Character(thing)->cPos >= POS_SLEEPING
          )
        if ( Character(thing)->cPos == POS_SLEEPING && !BIT(Plr(thing)->pAuto, PA_EXPERT))
          SendHint("^;HINT: You're fully rested! You should ^<WAKE^; up now and carry on!\n", thing);
        else if ( Character(thing)->cPos <= POS_SITTING  && !BIT(Plr(thing)->pAuto, PA_EXPERT))
          SendHint("^;HINT: You're fully rested! You should ^<STAND^; up now and carry on!\n", thing);

      if (!ParseCommandCheck(PARSE_COMMAND_WCREATE, sock, "") 
       && Character(thing)->cLevel >= LEVEL_HUNGER
       && !BIT(Plr(thing)->pAuto, PA_AFK)) {
        /* Gain Hunger */
        /* if (Plr(thing)->pHunger>=(-1*raceList[Plr(thing)->pRace].rMaxHunger)){ */
        gain = raceList[Plr(thing)->pRace].rGainHunger;
        gain = CharGainAdjust(thing, GAIN_HUNGER, gain);
        MINSET(gain, 1);
        Plr(thing)->pHunger += gain;
        MAXSET(Plr(thing)->pHunger, raceList[Plr(thing)->pRace].rMaxHunger);
        if (Plr(thing)->pHunger>0) {
          SendThing("You are hungry!\n", thing);
          if (BIT(Plr(thing)->pAuto, PA_AUTOEAT)) {
            /* search your inventory for something to eat */
            PlayerAutoEat(thing, thing->tContain);
          }
        }
      
        /* Gain Thirst */
        /* if (Plr(thing)->pThirst>=(-1*raceList[Plr(thing)->pRace].rMaxThirst)){ */
        gain = raceList[Plr(thing)->pRace].rGainThirst;
        gain = CharGainAdjust(thing, GAIN_THIRST, gain);
        MINSET(gain, 1);
        Plr(thing)->pThirst += gain;
        MAXSET(Plr(thing)->pThirst, raceList[Plr(thing)->pRace].rMaxThirst);
        if (Plr(thing)->pThirst>0) {
          SendThing("You are thirsty!\n", thing);
          if (BIT(Plr(thing)->pAuto, PA_AUTODRINK)) {
            /* search your inventory for something to drink */
            PlayerAutoDrink(thing, thing->tContain);
          }
        }
      } 
      
      /* Lose Intox */
      /* if (Plr(thing)->pIntox>=(-1*raceList[Plr(thing)->pRace].rMaxIntox)){ */
      Plr(thing)->pIntox -= raceList[Plr(thing)->pRace].rGainIntox;
      MAXSET(Plr(thing)->pIntox, raceList[Plr(thing)->pRace].rMaxIntox);
      MINSET(Plr(thing)->pIntox, -raceList[Plr(thing)->pRace].rMaxIntox);
      if (Plr(thing)->pIntox>0) {
        SendThing("You are intoxicated!\n", thing);
      }

      if (Character(thing)->cLevel >= LEVEL_GOD ) {
        /* gods dont get hungry etc */
        Plr(thing)->pHunger = -1;
        Plr(thing)->pThirst = -1;
        Plr(thing)->pIntox  = -1;
      }
      
      /* Check to see if they have been idle too long */
      Plr(thing)->pIdleTick++;
      if ((!Character(thing)->cFight)&&(Character(thing)->cLevel<LEVEL_GOD)){
        if (Plr(thing)->pIdleTick > playerIdleTick && !Plr(thing)->pIdleRoom) {
          /* Suck them into the void */
          SendThing("^wYou have been idle and are pulled into nothingness\n", thing);
          SendAction("$n disappears into the void\n",
            thing, NULL, SEND_ROOM|SEND_VISIBLE|SEND_AUDIBLE|SEND_CAPFIRST);
          Plr(thing)->pIdleRoom = Base(thing)->bInside;
          ThingTo(thing, WorldOf(playerIdleRoom));
        } else if ( (Plr(thing)->pIdleTick > playerDropTick
                    &&!BIT(Plr(thing)->pAuto, PA_AFK)
                    &&sock) 
                  ||(Plr(thing)->pIdleTick > playerAFKDropTick)) {
          if (sock)
            sock->sMode = MODE_KILLSOCKET;
            
          /* 
           * rent out char here (if they have the money) 
           */
          
          /* Update where they start */
          if (thing->tType == TTYPE_PLR && Base(thing)->bInside && Base(thing)->bInside->tType==TTYPE_WLD)
            Plr(thing)->pStartRoom = Wld(Base(thing)->bInside)->wVirtual;
          else
            Plr(thing)->pStartRoom = -1;
          PlayerWrite(thing, PWRITE_PLAYER);
        }
      }
    }
  }
}

/* Turns out that normalize is way too generous */
#define NORMALIZE 0
void PlayerRollAbilities(THING *thing, LWORD reroll) {
  LWORD min;
  LWORD max;
  LWORD bonus;
  LWORD str = -1;
  LWORD dex = -1;
  LWORD con = -1;
  LWORD wis = -1;
  LWORD intel = -1; /* int is of course reserved.... */
  LWORD newStr;
  LWORD newDex;
  LWORD newCon;
  LWORD newWis;
  LWORD newIntel;
  LWORD i;
#if (NORMALIZE)
  float normal;
  float actual;
#endif

  /* Roll Abilities */
  for (i=0; i<reroll; i++) {
    min =   raceList[Plr(thing)->pRace].rMinStat[SA_STR];
    max =   raceList[Plr(thing)->pRace].rMaxStat[SA_STR];
    bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_STR];
    bonus += classList[Plr(thing)->pClass].cBonusStat[SA_STR];
    newStr = Number(min, max) + bonus;
    min =   raceList[Plr(thing)->pRace].rMinStat[SA_DEX];
    max =   raceList[Plr(thing)->pRace].rMaxStat[SA_DEX];
    bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_DEX];
    bonus += classList[Plr(thing)->pClass].cBonusStat[SA_DEX];
    newDex = Number(min, max) + bonus;
    min =   raceList[Plr(thing)->pRace].rMinStat[SA_CON];
    max =   raceList[Plr(thing)->pRace].rMaxStat[SA_CON];
    bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_CON];
    bonus += classList[Plr(thing)->pClass].cBonusStat[SA_CON];
    newCon = Number(min, max) + bonus;
    min =   raceList[Plr(thing)->pRace].rMinStat[SA_WIS];
    max =   raceList[Plr(thing)->pRace].rMaxStat[SA_WIS];
    bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_WIS];
    bonus += classList[Plr(thing)->pClass].cBonusStat[SA_WIS];
    newWis = Number(min, max) + bonus;
    min =   raceList[Plr(thing)->pRace].rMinStat[SA_INT];
    max =   raceList[Plr(thing)->pRace].rMaxStat[SA_INT];
    bonus = raceList[Plr(thing)->pRace].rBonusStat[SA_INT];
    bonus += classList[Plr(thing)->pClass].cBonusStat[SA_INT];
    newIntel = Number(min, max) + bonus;

    /* Keep best average */
    if (newStr+newDex+newCon+newWis+newIntel > str+dex+con+wis+intel) {
      str = newStr;
      dex = newDex;
      con = newCon;
      wis = newWis;
      intel = newIntel;
    }
  }

#if (NORMALIZE)
  /* Normalize */
  normal = raceList[Plr(thing)->pRace].rNormalize;
  if (normal > 0) { /* -1 or 0 in race table disables normalization */
    actual = (float) 
            (str
           + dex
           + con
           + wis
           + intel);
    actual = actual / 5.0;

    /* okay changed my mind - ensure actual average is at least the normal one */
    if (actual < normal) {
      str   = (LWORD) ( (float)str   * normal / actual );
      dex   = (LWORD) ( (float)dex   * normal / actual );
      con   = (LWORD) ( (float)con   * normal / actual );
      wis   = (LWORD) ( (float)wis   * normal / actual );
      intel = (LWORD) ( (float)intel * normal / actual );
    }
  }
#endif

  /* watch the maximums */
  MAXSET(str, raceList[Plr(thing)->pRace].rMaxStat[SA_STR]);
  MAXSET(dex, raceList[Plr(thing)->pRace].rMaxStat[SA_DEX]);
  MAXSET(con, raceList[Plr(thing)->pRace].rMaxStat[SA_CON]);
  MAXSET(wis, raceList[Plr(thing)->pRace].rMaxStat[SA_WIS]);
  MAXSET(intel, raceList[Plr(thing)->pRace].rMaxStat[SA_INT]);
 
  /* watch the minimums */ 
  MINSET(str, raceList[Plr(thing)->pRace].rMinStat[SA_STR]);
  MINSET(dex, raceList[Plr(thing)->pRace].rMinStat[SA_DEX]);
  MINSET(con, raceList[Plr(thing)->pRace].rMinStat[SA_CON]);
  MINSET(wis, raceList[Plr(thing)->pRace].rMinStat[SA_WIS]);
  MINSET(intel, raceList[Plr(thing)->pRace].rMinStat[SA_INT]);

  Plr(thing)->pStr = str;
  Plr(thing)->pDex = dex;
  Plr(thing)->pCon = con;
  Plr(thing)->pWis = wis;
  Plr(thing)->pInt = intel;

  /* Set starting hit points etc if they are 1st level */
  if (Character(thing)->cLevel == 1) {
    /* Set Practices */
    Plr(thing)->pPractice = Plr(thing)->pWis/30+5;
    if (Plr(thing)->pInt<60) Plr(thing)->pPractice+=1; /* Dumber people get more pracs */
    if (Plr(thing)->pInt<30) Plr(thing)->pPractice+=2; /* Just starting out - they'll need 'em */
    /* Set Hit Points */
    max = classList[Plr(thing)->pClass].cStartHitP;
    max = max*raceList[Plr(thing)->pRace].rMaxHitP/100;
    Character(thing)->cHitPMax = max;
    Character(thing)->cHitP = CharGetHitPMax(thing);
    /* gain move points */
    max = classList[Plr(thing)->pClass].cStartMoveP;
    max = max*raceList[Plr(thing)->pRace].rMaxMoveP/100;
    Plr(thing)->pMovePMax = max;
    Character(thing)->cMoveP = CharGetMovePMax(thing);
    /* gain power points */
    max = classList[Plr(thing)->pClass].cStartPowerP;
    max = max*raceList[Plr(thing)->pRace].rMaxPowerP/100;
    Plr(thing)->pPowerPMax = max;
    Character(thing)->cPowerP = CharGetPowerPMax(thing);
  }
}



/* How much do we need for next level */
LWORD PlayerExpNeeded(THING *thing) {
  LWORD needed;

  needed = (1+Character(thing)->cLevel);
  needed *= 50;
  needed *= Character(thing)->cLevel;
  needed *= Character(thing)->cLevel;

  /* create plateau right before Hero level */
  if (Character(thing)->cLevel >= LEVEL_LEGEND-1) {
    needed += 20000000;
    needed += (Character(thing)->cLevel-( LEVEL_LEGEND-2 ))*2000000;
  }
  needed = needed * classList[Plr(thing)->pClass].cGainLevel / 100;
  needed = needed * raceList[Plr(thing)->pRace].rGainLevel / 100;
  return MAXV(needed,0);
}

LWORD PlayerExpUntilLevel(THING *thing) {
  LWORD expUntilLevel;

  if (thing->tType!=TTYPE_PLR) return -1;
  expUntilLevel = PlayerExpNeeded(thing)-Character(thing)->cExp;
  MINSET(expUntilLevel, 0);
  return expUntilLevel;
}

void PlayerGainLevel(THING *thing, LWORD message) {
  BYTE  buf[256];
  LWORD gain;
  SOCK *sock;
  BYTE  expert = FALSE;

  sock = BaseControlFind(thing);
  if (sock && BIT(Plr(sock->sHomeThing)->pAuto, PA_EXPERT)) expert = TRUE;
  Character(thing)->cLevel += 1;
  MAXSET(Character(thing)->cExp, PlayerExpNeeded(thing)-1);
  if (message) SendThing("^wGRATZ! You just gained a level!\n", thing);

  /* gain practices */
  if (!Plr(thing)->pGainPractice) {
    gain = Plr(thing)->pWis/20;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain += Number(1, 10);
    else
      gain += Number(1, 8);
  } else {
    gain = Plr(thing)->pGainPractice;
  }
  Plr(thing)->pGainPractice = 0;
  Plr(thing)->pPractice += gain;
  sprintf(buf, "^wYou gain %ld practices (You now have %ld available)\n", gain, Plr(thing)->pPractice);
  SendThing(buf, thing);

  /* gain hit points */
  if (!Plr(thing)->pGainHit) {
    gain = classList[Plr(thing)->pClass].cMaxHitP;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain = Number(1, gain);
  } else {
    gain = Plr(thing)->pGainHit;
  }
  Plr(thing)->pGainHit = 0;
  gain = gain*raceList[Plr(thing)->pRace].rMaxHitP/100;
  Character(thing)->cHitPMax += gain;
  if (message) {
    sprintf(buf, "^wYou gain %ld hit points (Your maximum is now %ld)\n", gain, CharGetHitPMax(thing));
    SendThing(buf, thing);
  }

  /* gain move points */
  if (!Plr(thing)->pGainMove) {
    gain = classList[Plr(thing)->pClass].cMaxMoveP;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain = Number(1, gain);
  } else {
    gain = Plr(thing)->pGainMove;
  }
  Plr(thing)->pGainMove = 0;
  gain = gain*raceList[Plr(thing)->pRace].rMaxMoveP/100;
  Plr(thing)->pMovePMax += gain;
  if (message) {
    if (expert || Character(thing)->cLevel >= 5) {
      sprintf(buf, "^wYou gain %ld move points (Your maximum is now %ld)\n", gain, CharGetMovePMax(thing));
      SendThing(buf, thing);
    }
  }

  /* gain power points */
  if (!Plr(thing)->pGainPower) {
    gain = classList[Plr(thing)->pClass].cMaxPowerP;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain = Number(1, gain);
  } else {
    gain = Plr(thing)->pGainPower;
  }
  Plr(thing)->pGainPower = 0;
  gain = gain*raceList[Plr(thing)->pRace].rMaxPowerP/100;
  Plr(thing)->pPowerPMax += gain;
  if (message) {
    if (expert || Character(thing)->cLevel >= 10) {
      sprintf(buf, "^wYou gain %ld power points (Your maximum is now %ld)\n", gain, CharGetPowerPMax(thing));
      SendThing(buf, thing);
    }
  }
  
  if (Character(thing)->cLevel == LEVEL_MOVETIRING) {
    if (message) SendThing("NEW: Moving will now tire you, and you will have to rest periodically\n", thing);
  } else if (Character(thing)->cLevel == LEVEL_HUNGER) {
    if (message) SendThing("NEW: You will now need food and drink periodically\n", thing);
    Plr(thing)->pThirst = -1*raceList[Plr(thing)->pRace].rMaxThirst;
    Plr(thing)->pHunger = -1*raceList[Plr(thing)->pRace].rMaxHunger;
    
  } else if (Character(thing)->cLevel == LEVEL_FIGHTTIRING) {
    if (message) SendThing("NEW: Fighting will now tire you, and you will have to rest periodically\n", thing);
  }

  if (!Character(thing)->cLevel < 5) {
    if (message) SendHint("^;HINT: You have practices! Locate Duah and improve your skills\n", thing);
  }
  if (message) SendHint("^;HINT: Save your progress! Visit Club Med and have a brain dump done!\n", thing);
}


void PlayerGainExp(THING *thing, LWORD exp) {
  BYTE   buf[256];
  THING *world;
  LWORD  expUntilLevel;
  LWORD  gain;

  if (thing->tType != TTYPE_PLR) return;

  /* guard against no gain zones etc here */
  world = Base(thing)->bInside;
  if (world->tType==TTYPE_WLD 
   && BIT(areaList[Wld(world)->wArea].aResetFlag, RF_NOGAIN)) {
    sprintf(buf, "^yYou ^rWOULD ^yhave gained %ld experience points! (This zone isnt open yet)\n", exp);
    SendThing(buf, thing);
    return;
  }
  
  /* check if they're a god */
  if (Character(thing)->cLevel>=LEVEL_GOD-1) {
    sprintf(buf, "^yYou ^rWOULD ^yhave gained %ld experience points! (except that you're a god)\n", exp);
    SendThing(buf, thing);
    return;
  }
  
  /* Check if they're a area editor */
  if (ParseCommandCheck(PARSE_COMMAND_WGOTO, BaseControlFind(thing), "")) {
    sprintf(buf, "^yYou ^rWOULD ^yhave gained %ld experience points! (except that you're the area creator)\n", exp);
    SendThing(buf, thing);
    return;
  }

  expUntilLevel = PlayerExpNeeded(thing)-Character(thing)->cExp-exp;
  if (expUntilLevel>0) {
    sprintf(buf, "^yYou just gained %ld experience points! (%ld until next level)\n", exp, expUntilLevel);
    SendThing(buf, thing);
  } else {
    sprintf(buf, "^yYou just gained %ld experience points! (^wLEVEL!!!^y)\n", exp);
    SendThing(buf, thing);
  }

  /* Predetermine Practice roll to prevent kill/relevel "bug" */
  if (!Plr(thing)->pGainPractice) {
    gain = Plr(thing)->pWis/20;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain += Number(1, 10);
    else
      gain += Number(1, 8);
    Plr(thing)->pGainPractice = gain;
  }

  /* Predetermine hit roll to prevent kill/relevel "bug" */
  if (!Plr(thing)->pGainHit && !Number(0,5)) {
    gain = classList[Plr(thing)->pClass].cMaxHitP;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain = Number(1, gain);
    Plr(thing)->pGainHit= gain;
  }

  /* Predetermine move roll to prevent kill/relevel "bug" */
  if (!Plr(thing)->pGainMove && !Number(0,5)) {
    gain = classList[Plr(thing)->pClass].cMaxMoveP;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain = Number(1, gain);
    Plr(thing)->pGainMove= gain;
  }

  /* Predetermine power roll to prevent kill/relevel "bug" */
  if (!Plr(thing)->pGainPower && !Number(0,5)) {
    gain = classList[Plr(thing)->pClass].cMaxPowerP;
    if (Character(thing)->cLevel > LEVEL_GREEN)
      gain = Number(1, gain);
    Plr(thing)->pGainPower = gain;
  }

  Character(thing)->cExp += exp;
  if (Character(thing)->cExp > PlayerExpNeeded(thing)) {
    PlayerGainLevel(thing, TRUE);
  }
}

void PlayerGainFame(THING *player, LWORD fame) {
  BYTE buf[256];

  if (!fame) return;
  if (!player || player->tType!=TTYPE_PLR) return;
  if (fame>0)
    sprintf(buf, "^yYou gain %ld points of fame!\n", fame);
  else
    sprintf(buf, "^rYou lose %ld points of fame!\n", fame);
  Plr(player)->pFame+=fame;
  SendThing(buf, player);
}

void PlayerGainInfamy(THING *player, LWORD infamy, BYTE *message) {
  BYTE  buf[256];
  LWORD infamous;

  if (!infamy) return;
  if (!player || player->tType!=TTYPE_PLR) return;
  infamous = PlayerIsInfamous(player);
  if (!message) {
    if (infamy>0)
      sprintf(buf, "^rYou gain %ld points of infamy!\n", infamy);
    else
      sprintf(buf, "^yYou lose %ld points of infamy!\n", infamy);
    SendThing(buf, player);
  } else {
    SendThing(message, player);
  }
  Plr(player)->pInfamy+=infamy;

  if (!infamous && PlayerIsInfamous(player))  
    SendThing("^wUh Oh, start running now - you just became infamous\n", player);
}

LWORD PlayerIsInfamous(THING *player) {
  if (player->tType!=TTYPE_PLR) return FALSE;
  if (Plr(player)->pInfamy > Character(player)->cLevel + Plr(player)->pFame)
    return Plr(player)->pInfamy - ( Character(player)->cLevel + Plr(player)->pFame );
  return FALSE;
}

void PlayerDelete(THING *player) {
  SOCK *sock;
  BYTE  buf[256];
  BYTE  playerName[256];

  /* get rid of the playerFile - but keep a backup in case we change our mind */
  PlayerWrite(player, PWRITE_CRASH);

  strcpy(playerName, player->tSDesc->sText);
  StrToLower(playerName);
#ifdef WIN32
  sprintf(buf, "move crash\\%s.plr player\\%s.plr.del", playerName, playerName);
#else
  sprintf(buf, "mv crash/%s.plr player/%s.plr.del", playerName, playerName);
#endif
  system(buf);
  sprintf(buf, "clone/%s.plr", playerName);
  unlink(buf);
  sprintf(buf, "player/%s.plr", playerName);
  unlink(buf);
  sprintf(buf, "alias/%s.als", playerName);
  unlink(buf);

  sock = BaseControlFind(player);
  if (sock) {
    BaseControlFree(sock->sControlThing, sock);
    sock->sMode = MODE_KILLSOCKET; /* they're not long for this world.... */
  }
  /* turf the playerThing */
  
  THINGFREE(player);
}


BYTE *PlayerGetLevelDesc(THING *thing) {
  if (!thing) return "NONE";
  if (Character(thing)->cLevel < LEVEL_GREEN)
    return "NEWBIE";
  if (Character(thing)->cLevel < LEVEL_NOVICE)
    return "GREEN";
  if (Character(thing)->cLevel < LEVEL_EXPERIENCED)
    return "NOVICE";
  if (Character(thing)->cLevel < LEVEL_ELITE)
    return "EXPERIENCED";
  if (Character(thing)->cLevel < LEVEL_ULTRAELITE)
    return "ELITE";
  if (Character(thing)->cLevel < LEVEL_LEGEND)
    return "ULTRAELITE";
  if (Character(thing)->cLevel < LEVEL_GOD)
    return "LEGEND";
  if (Character(thing)->cLevel < LEVEL_ADMIN)
    return "GOD";
  if (Character(thing)->cLevel < LEVEL_CODER)
    return "ADMIN";
  else
    return "CODER";
}