/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael          *
 *  Chastain, Michael Quan, and Mitchell Tse.                              *
 *                                                                         *
 *  In order to use any part of this Merc Diku Mud, you must comply with   *
 *  both the original Diku license in 'license.diku' as well the Merc      *
 *  license in 'license.merc'.  In particular, you may not remove either   *
 *  of these copyright notices.                                            *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

/*
 MurkMUD++ - A Windows compatible, C++ compatible Merc 2.2 Mud.

 \author Jon A. Lambert
 \date 08/30/2006
 \version 1.4
 \remarks
  This source code copyright (C) 2005, 2006 by Jon A. Lambert
  All rights reserved.

  Use governed by the MurkMUD++ public license found in license.murk++
*/

#include "os.hpp"
#include "config.hpp"
#include "utils.hpp"
#include "globals.hpp"
#include "io.hpp"

#include "descriptor.hpp"
#include "pcdata.hpp"
#include "object.hpp"
#include "room.hpp"
#include "area.hpp"
#include "note.hpp"
#include "objproto.hpp"
#include "mobproto.hpp"

/*
 * constants for Telnet.
 */
#define IAC 255     /* interpret as command: */
#define WONT    252     /* I won't use option */
#define WILL    251     /* I will use option */
#define GA  249     /* you may reverse the line */
#define TELOPT_ECHO 1   /* echo */

const unsigned char echo_off_str[] = { IAC, WILL, TELOPT_ECHO, '\0' };
const unsigned char echo_on_str[] = { IAC, WONT, TELOPT_ECHO, '\0' };
const unsigned char go_ahead_str[] = { IAC, GA, '\0' };


// Temporary externs
extern bool write_to_descriptor (SOCKET desc, const char *txt, int length);
extern std::string get_title (int klass, int level, int sex);
extern bool check_parse_name (const std::string & name);

/*
 * Bust a prompt (player settable prompt)
 * coded by Morgenes for Aldara Mud
 */
static void bust_a_prompt (Character * ch)
{
  std::string buf;
  char buf2[MAX_STRING_LENGTH];
  std::string::iterator str;

  if (ch->prompt.empty()) {
    ch->send_to_char ("\r\n\r\n");
    return;
  }

  str = ch->prompt.begin();
  while (str != ch->prompt.end()) {
    if (*str != '%') {
      buf.append(1,*str);
      str++;
      continue;
    }
    ++str;
    switch (*str) {
    default:
      buf.append(" ");
      break;
    case 'h':
      snprintf (buf2, sizeof buf2, "%d", ch->hit);
      buf.append(buf2);
      break;
    case 'H':
      snprintf (buf2, sizeof buf2, "%d", ch->max_hit);
      buf.append(buf2);
      break;
    case 'm':
      snprintf (buf2, sizeof buf2, "%d", ch->mana);
      buf.append(buf2);
      break;
    case 'M':
      snprintf (buf2, sizeof buf2, "%d", ch->max_mana);
      buf.append(buf2);
      break;
    case 'v':
      snprintf (buf2, sizeof buf2, "%d", ch->move);
      buf.append(buf2);
      break;
    case 'V':
      snprintf (buf2, sizeof buf2, "%d", ch->max_move);
      buf.append(buf2);
      break;
    case 'x':
      snprintf (buf2, sizeof buf2, "%d", ch->exp);
      buf.append(buf2);
      break;
    case 'g':
      snprintf (buf2, sizeof buf2, "%d", ch->gold);
      buf.append(buf2);
      break;
    case 'a':
      if (ch->level < 5)
        snprintf (buf2, sizeof buf2, "%d", ch->alignment);
      else
        snprintf (buf2, sizeof buf2, "%s", ch->is_good () ? "good" : ch->is_evil () ?
          "evil" : "neutral");
      buf.append(buf2);
      break;
    case 'r':
      if (ch->in_room != NULL)
        buf.append(ch->in_room->name);
      else
        buf.append(" ");
      break;
    case 'R':
      if (ch->is_immortal() && ch->in_room != NULL)
        snprintf (buf2, sizeof buf2, "%d", ch->in_room->vnum);
      else
        snprintf (buf2, sizeof buf2, " ");
      buf.append(buf2);
      break;
    case 'z':
      if (ch->is_immortal() && ch->in_room != NULL)
        buf.append(ch->in_room->area->name);
      else
        buf.append(" ");
      break;
    case '%':
      buf.append("%%");
      break;
    }
    ++str;
  }
  ch->desc->write_to_buffer (buf);
  return;
}


Descriptor::Descriptor(SOCKET desc) :
  character(NULL), original(NULL),
  descriptor(desc), connected(CON_GET_NAME), fcommand(false),
  repeat(0), showstr_head(NULL), showstr_point(NULL) {
  memset(inbuf, 0, sizeof inbuf);
}



/*
 * Append onto an output buffer.
 */
void Descriptor::write_to_buffer (const std::string & txt) {
  /*
   * Initial \r\n if needed.
   */
  if (outbuf.empty() && !fcommand)
    outbuf = "\r\n";

  /*
   * Copy.
   */
  outbuf.append(txt);
  return;
}


/* The heart of the pager.  Thanks to N'Atas-Ha, ThePrincedom
   for porting this SillyMud code for MERC 2.0 and laying down the groundwork.
   Thanks to Blackstar, hopper.cs.uiowa.edu 4000 for which
   the improvements to the pager was modeled from.  - Kahn */
void Descriptor::show_string (const std::string & input) {
  char buffer[MAX_STRING_LENGTH];
  std::string buf;
  register char *scan, *chk;
  int lines = 0, toggle = 1;

  one_argument (input, buf);
  incomm.erase();

  if (!buf.empty()) {
    switch (toupper (buf[0])) {
    case 'C':                    /* show next page of text */
      lines = 0;
      break;

    case 'R':                    /* refresh current page of text */
      lines = -1 - (character->pcdata->pagelen);
      break;

    case 'B':                    /* scroll back a page of text */
      lines = -(2 * character->pcdata->pagelen);
      break;

    case 'H':                    /* Show some help */
      write_to_buffer ("C, or Return = continue, R = redraw this page,\r\n");
      write_to_buffer (
        "B = back one page, H = this help, Q or other keys = exit.\r\n\r\n");
      lines = -1 - (character->pcdata->pagelen);
      break;

    default:                     /*otherwise, stop the text viewing */
      if (showstr_head) {
        std::free(showstr_head);
        showstr_head = NULL;
      }
      showstr_point = 0;
      return;

    }
  }

  /* do any backing up necessary */
  if (lines < 0) {
    for (scan = showstr_point; scan > showstr_head; scan--)
      if ((*scan == '\n') || (*scan == '\r')) {
        toggle = -toggle;
        if (toggle < 0)
          if (!(++lines))
            break;
      }
    showstr_point = scan;
  }

  /* show a chunk */
  lines = 0;
  toggle = 1;
  for (scan = buffer;; scan++, showstr_point++) {
    *scan = *showstr_point;
    if ((*scan == '\n' || *scan == '\r') && (toggle = -toggle) < 0) {
      lines++;
    } else if (!*scan || (character && !character->is_npc ()
        && lines >= character->pcdata->pagelen)) {

      *scan = '\0';
      write_to_buffer (buffer);

      /* See if this is the end (or near the end) of the string */
      for (chk = showstr_point; isspace (*chk); chk++) ;
      if (!*chk) {
        if (showstr_head) {
          std::free(showstr_head);
          showstr_head = NULL;
        }
        showstr_point = 0;
      }
      return;
    }
  }
}

/*
 * Look for link-dead player to reconnect.
 */
bool Descriptor::check_reconnect (const std::string & name, bool fConn)
{
  Object *obj;

  CharIter c;
  for (c = char_list.begin(); c != char_list.end(); c++) {
    if (!(*c)->is_npc ()
      && (!fConn || (*c)->desc == NULL)
      && !str_cmp (character->name, (*c)->name)) {
      if (fConn == false) {
        character->pcdata->pwd = (*c)->pcdata->pwd;
      } else {
        delete character;
        character = *c;
        (*c)->desc = this;
        (*c)->timer = 0;
        (*c)->send_to_char ("Reconnecting.\r\n");
        (*c)->act ("$n has reconnected.", NULL, NULL, TO_ROOM);
        log_printf ("%s@%s reconnected.", (*c)->name.c_str(), host.c_str());
        connected = CON_PLAYING;

        /*
         * Contributed by Gene Choi
         */
        if ((obj = (*c)->get_eq_char (WEAR_LIGHT)) != NULL
          && obj->item_type == ITEM_LIGHT
          && obj->value[2] != 0 && (*c)->in_room != NULL)
          ++(*c)->in_room->light;

        if ((obj = (*c)->get_eq_char (WEAR_LIGHT)) != NULL
          && obj->item_type == ITEM_DARKNESS
          && obj->value[2] != 0 && (*c)->in_room != NULL)
          --(*c)->in_room->light;
      }
      return true;
    }
  }

  return false;
}

/*
 * Check if already playing.
 */
bool Descriptor::check_playing (const std::string & name)
{
  DescIter dold;

  for (dold = descriptor_list.begin(); dold != descriptor_list.end(); dold++) {
    if (*dold != this
      && (*dold)->character != NULL
      && (*dold)->connected != CON_GET_NAME
      && (*dold)->connected != CON_GET_OLD_PASSWORD
      && !str_cmp (name, (*dold)->original
        ? (*dold)->original->name : (*dold)->character->name)) {
      write_to_buffer ("Already playing.\r\nName: ");
      connected = CON_GET_NAME;
      if (character != NULL) {
        delete character;
        character = NULL;
      }
      return true;
    }
  }

  return false;
}


/*
 * Transfer one line from input buffer to input line.
 */
void Descriptor::read_from_buffer ()
{
  int i, j, k;

  /*
   * Hold horses if pending command already.
   */
  if (!incomm.empty())
    return;

  /*
   * Look for at least one new line.
   */
  for (i = 0; inbuf[i] != '\n' && inbuf[i] != '\r'; i++) {
    if (inbuf[i] == '\0')
      return;
  }

  /*
   * Canonical input processing.
   */
  for (i = 0, k = 0; inbuf[i] != '\n' && inbuf[i] != '\r'; i++) {
    if (k >= MAX_INPUT_LENGTH - 2) {
      write_to_descriptor (descriptor, "Line too long.\r\n", 0);

      /* skip the rest of the line */
      for (; inbuf[i] != '\0'; i++) {
        if (inbuf[i] == '\n' || inbuf[i] == '\r')
          break;
      }
      inbuf[i] = '\n';
      inbuf[i + 1] = '\0';
      break;
    }

    if (inbuf[i] == '\b' && k > 0) {
      --k;
    } else if ( ((unsigned)inbuf[i] <= 0177)
      && isprint (inbuf[i])) {
      incomm.append(1, inbuf[i]);
      k++;
    }
  }

  /*
   * Finish off the line.
   */
  if (k == 0) {
    incomm.append(" ");
    k++;
  }

  /*
   * Deal with bozos with #repeat 1000 ...
   */
  if (k > 1 || incomm[0] == '!') {
    if (incomm[0] != '!' && incomm != inlast) {
      repeat = 0;
    } else {
      if (++repeat >= 20) {
        log_printf ("%s input spamming!", host.c_str());
        write_to_descriptor (descriptor,
          "\r\n*** PUT A LID ON IT!!! ***\r\n", 0);
        incomm = "quit";
      }
    }
  }

  /*
   * Do '!' substitution.
   */
  if (incomm[0] == '!')
    incomm = inlast;
  else
    inlast = incomm;

  /*
   * Shift the input buffer.
   */
  while (inbuf[i] == '\n' || inbuf[i] == '\r')
    i++;
  for (j = 0; (inbuf[j] = inbuf[i + j]) != '\0'; j++) ;
  return;
}

/*
 * Low level output function.
 */
bool Descriptor::process_output (bool fPrompt)
{
  /*
   * Bust a prompt.
   */
  if (fPrompt && !merc_down && connected == CON_PLAYING) {
    if (showstr_point)
      write_to_buffer (
        "[Please type (c)ontinue, (r)efresh, (b)ack, (h)elp, (q)uit, or RETURN]:  ");
    else {
      Character *ch;

      ch = original ? original : character;
      if (IS_SET (ch->actflags, PLR_BLANK))
        write_to_buffer ("\r\n");

      if (IS_SET (ch->actflags, PLR_PROMPT))
        bust_a_prompt (ch);

      if (IS_SET (ch->actflags, PLR_TELNET_GA))
        write_to_buffer (reinterpret_cast<const char*>(go_ahead_str));
    }
  }

  /*
   * Short-circuit if nothing to write.
   */
  if (outbuf.empty())
    return true;

  /*
   * OS-dependent output.
   */
  if (!write_to_descriptor (descriptor, outbuf.c_str(), outbuf.size())) {
    outbuf.erase();
    return false;
  } else {
    outbuf.erase();
    return true;
  }
}

void Descriptor::close_socket ()
{
  if (!outbuf.empty())
    process_output(false);

  if (character != NULL) {
    log_printf ("Closing link to %s.", character->name.c_str());
    if (connected == CON_PLAYING) {
      character->act ("$n has lost $s link.", NULL, NULL, TO_ROOM);
      character->desc = NULL;
    } else {
      delete character;
    }
  }

  deepdenext = descriptor_list.erase(
     std::find(descriptor_list.begin(),descriptor_list.end(),this));
  closesocket (descriptor);
  delete this;
  return;
}

bool Descriptor::read_from_descriptor ()
{
  unsigned int iStart;

  /* Hold horses if pending command already. */
  if (!incomm.empty())
    return true;

  /* Check for overflow. */
  iStart = strlen (inbuf);
  if (iStart >= sizeof (inbuf) - 10) {
    log_printf ("%s input overflow!", host.c_str());
    write_to_descriptor (descriptor,
      "\r\n*** PUT A LID ON IT!!! ***\r\n", 0);
    return false;
  }

  /* Snarf input. */
  for (;;) {
    int nRead;

    nRead = recv (descriptor, inbuf + iStart,
      sizeof (inbuf) - 10 - iStart, 0);
    if (nRead > 0) {
      iStart += nRead;
      if (inbuf[iStart - 1] == '\n' || inbuf[iStart - 1] == '\r')
        break;
    } else if (nRead == 0) {
      log_printf ("EOF encountered on read.");
      return false;
    } else if (GETERROR == EWOULDBLOCK)
      break;
    else {
      std::perror ("Read_from_descriptor");
      return false;
    }
  }

  inbuf[iStart] = '\0';
  return true;
}

/*
 * Deal with sockets that haven't logged in yet.
 */
void Descriptor::nanny (std::string argument)
{
  std::string buf;
  char cbuf[MAX_STRING_LENGTH];  // Needed for Windows crypt
  Character *ch;
  char *pwdnew;
  char *p;
  int iClass;
  int lines;
  int notes;
  bool fOld;

  incomm.erase();
  argument.erase(0, argument.find_first_not_of(" "));

  ch = character;

  switch (connected) {

  default:
    bug_printf ("Nanny: bad connected %d.", connected);
    close_socket();
    return;

  case CON_GET_NAME:
    if (argument.empty()) {
      close_socket();
      return;
    }

    argument[0] = toupper(argument[0]);
    if (!check_parse_name (argument)) {
      write_to_buffer ("Illegal name, try another.\r\nName: ");
      return;
    }

    fOld = load_char_obj (argument);
    ch = character;

    if (IS_SET (ch->actflags, PLR_DENY)) {
      log_printf ("Denying access to %s@%s.", argument.c_str(), host.c_str());
      write_to_buffer ("You are denied access.\r\n");
      close_socket();
      return;
    }

    if (check_reconnect (argument, false)) {
      fOld = true;
    } else {
      if (wizlock && !ch->is_hero() && !ch->wizbit) {
        write_to_buffer ("The game is wizlocked.\r\n");
        close_socket();
        return;
      }
    }

    if (fOld) {
      /* Old player */
      write_to_buffer ("Password: ");
      write_to_buffer (reinterpret_cast<const char*>(echo_off_str));
      connected = CON_GET_OLD_PASSWORD;
    } else {
      /* New player */
      /* New characters with same name fix by Salem's Lot */
      if (check_playing (ch->name))
        return;
      buf = "Did I get that right, " + argument + " (Y/N)? ";
      write_to_buffer (buf);
      connected = CON_CONFIRM_NEW_NAME;
    }
    break;

  case CON_GET_OLD_PASSWORD:
    write_to_buffer ("\r\n");

    strncpy(cbuf,argument.c_str(), sizeof cbuf);
    if (strcmp (crypt (cbuf, ch->pcdata->pwd.c_str()), ch->pcdata->pwd.c_str())) {
      write_to_buffer ("Wrong password.\r\n");
      close_socket();
      return;
    }

    write_to_buffer (reinterpret_cast<const char*>(echo_on_str));

    if (check_reconnect (ch->name, true))
      return;

    if (check_playing (ch->name))
      return;

    log_printf ("%s@%s has connected.", ch->name.c_str(), host.c_str());
    lines = ch->pcdata->pagelen;
    ch->pcdata->pagelen = 20;
    if (ch->is_hero())
      ch->do_help ("imotd");
    ch->do_help ("motd");
    ch->pcdata->pagelen = lines;
    connected = CON_READ_MOTD;
    break;

  case CON_CONFIRM_NEW_NAME:
    switch (argument[0]) {
    case 'y':
    case 'Y':
      buf = "New character.\r\nGive me a password for " + ch->name + ": " + reinterpret_cast<const char*>(echo_off_str);
      write_to_buffer (buf);
      connected = CON_GET_NEW_PASSWORD;
      break;

    case 'n':
    case 'N':
      write_to_buffer ("Ok, what IS it, then? ");
      delete character;
      character = NULL;
      connected = CON_GET_NAME;
      break;

    default:
      write_to_buffer ("Please type Yes or No? ");
      break;
    }
    break;

  case CON_GET_NEW_PASSWORD:
    write_to_buffer ("\r\n");

    if (argument.size() < 5) {
      write_to_buffer (
        "Password must be at least five characters long.\r\nPassword: ");
      return;
    }

    strncpy(cbuf,argument.c_str(), sizeof cbuf);
    pwdnew = crypt (cbuf, ch->name.c_str());
    for (p = pwdnew; *p != '\0'; p++) {
      if (*p == '~') {
        write_to_buffer (
          "New password not acceptable, try again.\r\nPassword: ");
        return;
      }
    }

    ch->pcdata->pwd = pwdnew;
    write_to_buffer ("Please retype password: ");
    connected = CON_CONFIRM_NEW_PASSWORD;
    break;

  case CON_CONFIRM_NEW_PASSWORD:
    write_to_buffer ("\r\n");

    strncpy(cbuf,argument.c_str(), sizeof cbuf);
    if (strcmp (crypt (cbuf, ch->pcdata->pwd.c_str()), ch->pcdata->pwd.c_str())) {
      write_to_buffer ("Passwords don't match.\r\nRetype password: ");
      connected = CON_GET_NEW_PASSWORD;
      return;
    }

    write_to_buffer (reinterpret_cast<const char*>(echo_on_str));
    write_to_buffer ("What is your sex (M/F/N)? ");
    connected = CON_GET_NEW_SEX;
    break;

  case CON_GET_NEW_SEX:
    switch (argument[0]) {
    case 'm':
    case 'M':
      ch->sex = SEX_MALE;
      break;
    case 'f':
    case 'F':
      ch->sex = SEX_FEMALE;
      break;
    case 'n':
    case 'N':
      ch->sex = SEX_NEUTRAL;
      break;
    default:
      write_to_buffer ("That's not a sex.\r\nWhat IS your sex? ");
      return;
    }

    buf = "Select a class [";
    for (iClass = 0; iClass < CLASS_MAX; iClass++) {
      if (iClass > 0)
        buf.append(" ");
      buf.append(class_table[iClass].who_name);
    }
    buf.append("]: ");
    write_to_buffer (buf);
    connected = CON_GET_NEW_CLASS;
    break;

  case CON_GET_NEW_CLASS:
    for (iClass = 0; iClass < CLASS_MAX; iClass++) {
      if (!str_cmp (argument, class_table[iClass].who_name)) {
        ch->klass = iClass;
        break;
      }
    }

    if (iClass == CLASS_MAX) {
      write_to_buffer ("That's not a class.\r\nWhat IS your class? ");
      return;
    }

    log_printf ("%s@%s new player.", ch->name.c_str(), host.c_str());
    write_to_buffer ("\r\n");
    ch->pcdata->pagelen = 20;
    ch->prompt = "<%hhp %mm %vmv> ";
    ch->do_help ("motd");
    connected = CON_READ_MOTD;
    break;

  case CON_READ_MOTD:
    char_list.push_back(ch);
    connected = CON_PLAYING;

    ch->send_to_char
      ("\r\nWelcome to Merc Diku Mud.  May your visit here be ... Mercenary.\r\n");

    if (ch->level == 0) {
      Object *obj;

      ch->pcdata->set_prime (class_table[ch->klass].attr_prime);

      ch->level = 1;
      ch->exp = 1000;
      ch->hit = ch->max_hit;
      ch->mana = ch->max_mana;
      ch->move = ch->max_move;
      buf = "the ";
      buf.append(get_title(ch->klass, ch->level, ch->sex));
      ch->set_title(buf);

      obj = get_obj_index(OBJ_VNUM_SCHOOL_BANNER)->create_object(0);
      obj->obj_to_char (ch);
      ch->equip_char (obj, WEAR_LIGHT);

      obj = get_obj_index(OBJ_VNUM_SCHOOL_VEST)->create_object(0);
      obj->obj_to_char (ch);
      ch->equip_char (obj, WEAR_BODY);

      obj = get_obj_index(OBJ_VNUM_SCHOOL_SHIELD)->create_object(0);
      obj->obj_to_char (ch);
      ch->equip_char (obj, WEAR_SHIELD);

      obj = get_obj_index(class_table[ch->klass].weapon)->create_object(0);
      obj->obj_to_char (ch);
      ch->equip_char (obj, WEAR_WIELD);

      ch->char_to_room(get_room_index (ROOM_VNUM_SCHOOL));
    } else if (ch->in_room != NULL) {
      ch->char_to_room(ch->in_room);
    } else if (ch->is_immortal()) {
      ch->char_to_room(get_room_index (ROOM_VNUM_CHAT));
    } else {
      ch->char_to_room(get_room_index (ROOM_VNUM_TEMPLE));
    }

    ch->act ("$n has entered the game.", NULL, NULL, TO_ROOM);
    ch->do_look ("auto");
    /* check for new notes */
    notes = 0;

    for (std::list<Note*>::iterator p = note_list.begin();
      p != note_list.end(); p++)
      if ((*p)->is_note_to (ch) && str_cmp (ch->name, (*p)->sender)
        && (*p)->date_stamp > ch->last_note)
        notes++;

    if (notes == 1)
      ch->send_to_char ("\r\nYou have one new note waiting.\r\n");
    else if (notes > 1) {
      buf = "\r\nYou have " + itoa(notes, 10) + " new notes waiting.\r\n";
      ch->send_to_char (buf);
    }

    break;
  }

  return;
}

void Descriptor::assign_hostname (void)
{
  struct sockaddr_in sock;
  char buf[MAX_STRING_LENGTH];
  struct hostent *from;
#ifndef WIN32
  socklen_t size = sizeof (sock);
#else
  int size = sizeof (sock);
#endif

  if (getpeername (descriptor, (struct sockaddr *) &sock, &size) < 0) {
    std::perror ("New_descriptor: getpeername");
    host = "(unknown)";
  } else {
    int addr = ntohl (sock.sin_addr.s_addr);
    snprintf (buf, sizeof buf, "%d.%d.%d.%d", (addr >> 24) & 0xFF, (addr >> 16) & 0xFF,
      (addr >> 8) & 0xFF, (addr) & 0xFF);
    log_printf ("Sock.sinaddr:  %s", buf);
    from = gethostbyaddr ((char *) &sock.sin_addr, sizeof (sock.sin_addr), AF_INET);
    host = from ? from->h_name : buf;
  }

}

/*
 * Load a char and inventory into a new ch structure.
 */
bool Descriptor::load_char_obj (const std::string & name)
{
  Character* ch = new Character();
  ch->pcdata = new PCData();
  character = ch;
  ch->desc = this;
  ch->name = name;
  ch->prompt = "<%hhp %mm %vmv> ";
  ch->last_note = 0;
  ch->actflags = PLR_BLANK | PLR_COMBINE | PLR_PROMPT;
  ch->pcdata->condition[COND_THIRST] = 48;
  ch->pcdata->condition[COND_FULL] = 48;

  bool found = false;

  char strsave[MAX_INPUT_LENGTH];
  std::ifstream fp;

  snprintf (strsave, sizeof strsave, "%s%s", PLAYER_DIR, capitalize (name).c_str());
  fp.open (strsave, std::ifstream::in | std::ifstream::binary);
  if (fp.is_open()) {
    for (int iNest = 0; iNest < MAX_NEST; iNest++)
      rgObjNest[iNest] = NULL;

    found = true;
    for (;;) {
      char letter;
      std::string word;

      letter = fread_letter (fp);
      if (letter == '*') {
        fread_to_eol (fp);
        continue;
      }

      if (letter != '#') {
        bug_printf ("Load_char_obj: # not found.");
        break;
      }

      word = fread_word (fp);
      if (!str_cmp (word, "PLAYER"))
        ch->fread_char (fp);
      else if (!str_cmp (word, "OBJECT")) {
        Object* obj = new Object;
        if (!obj->fread_obj (ch, fp)) {
          delete obj;
          bug_printf ("fread_obj: bad object.");
        }
      } else if (!str_cmp (word, "END"))
        break;
      else {
        bug_printf ("Load_char_obj: bad section.");
        break;
      }
    }
    fp.close();
  }

  return found;
}