AwakeMUD-0.8.18B/
AwakeMUD-0.8.18B/doc/
AwakeMUD-0.8.18B/lib/
AwakeMUD-0.8.18B/lib/etc/
AwakeMUD-0.8.18B/lib/etc/pfiles/
AwakeMUD-0.8.18B/lib/misc/
AwakeMUD-0.8.18B/lib/text/
AwakeMUD-0.8.18B/lib/text/help/
AwakeMUD-0.8.18B/lib/text/wizhelp/
AwakeMUD-0.8.18B/lib/veh/
AwakeMUD-0.8.18B/lib/world/
AwakeMUD-0.8.18B/lib/world/mob/
AwakeMUD-0.8.18B/lib/world/mtx/
AwakeMUD-0.8.18B/lib/world/qst/
AwakeMUD-0.8.18B/lib/world/shp/
AwakeMUD-0.8.18B/lib/world/veh/
// file: newdb.cpp
// author: Andrew Hynek
// contents: implementation of the index classes


#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>

#include "structs.h"
#include "newdb.h"
#include "db.h"
#include "comm.h"       // for shutdown()
#include "file.h"
#include "utils.h"
#include "memory.h"
#include "handler.h"
#include "vtable.h"
#include "constants.h"
#include "interpreter.h" // for alias

extern void kill_ems(char *);
static const char *const INDEX_FILENAME = "etc/pfiles/index";
extern char *cleanup(char *dest, const char *src);

// ____________________________________________________________________________
//
// global variables
// ____________________________________________________________________________

PCIndex playerDB;

// ____________________________________________________________________________
//
// PCIndex -- the PC database class: implementation
// ____________________________________________________________________________

int entry_compare(const void *one, const void *two)
{
  PCIndex::entry *ptr1 = (PCIndex::entry *)one;
  PCIndex::entry *ptr2 = (PCIndex::entry *)two;

  if (ptr1->id < ptr2->id)
    return -1;
  else if (ptr1->id > ptr2->id)
    return 1;

  return 0;
}

static void init_char(struct char_data * ch)
{
  int i;

  /* create a player_special structure */
  if (ch->player_specials == NULL)
  {
    ch->player_specials = new player_special_data;
  }

  ch->player_specials->saved.freeze_level = 0;
  ch->player_specials->saved.invis_level  = 0;
  ch->player_specials->saved.incog_level  = 0;
  ch->player_specials->saved.wimp_level   = 0;
  ch->player_specials->saved.bad_pws      = 0;

  set_title(ch, NULL);

  GET_LOADROOM(ch) = NOWHERE;
  GET_WAS_IN(ch) = NOWHERE;

  ch->player.time.birth = time(0);
  ch->player.time.played = 0;
  ch->player.time.logon = time(0);
  ch->player.time.lastdisc = time(0);

  ch->player.weight = (int)gen_size(GET_RACE(ch), 0, 3, GET_SEX(ch));
  ch->player.height = (int)gen_size(GET_RACE(ch), 1, 3, GET_SEX(ch));

  ch->points.max_mental = 1000;
  ch->points.max_physical = 1000;
  ch->points.mental = GET_MAX_MENTAL(ch);
  ch->points.physical = GET_MAX_PHYSICAL(ch);
  GET_BALLISTIC(ch) = GET_TOTALBAL(ch) = GET_IMPACT(ch) = GET_TOTALIMP(ch) = 0;
  ch->points.sustained[0] = 0;
  ch->points.sustained[1] = 0;
  ch->points.grade = 0;

  if (access_level(ch, LVL_VICEPRES))
  {
    GET_COND(ch, FULL) = -1;
    GET_COND(ch, THIRST) = -1;
    GET_COND(ch, DRUNK) = -1;
  } else
  {
    GET_COND(ch,FULL) = 24;
    GET_COND(ch, THIRST) = 24;
    GET_COND(ch, DRUNK) = 0;
  }

  if (!access_level(ch, LVL_VICEPRES))
    for (i = 1; i <= MAX_SKILLS; i++)
    {
      SET_SKILL(ch, i, 0)
    }
  else
    for (i = 1; i <= MAX_SKILLS; i++)
    {
      SET_SKILL(ch, i, 100);
    }

  ch->char_specials.saved.affected_by.Clear();
}

static void init_char_strings(char_data *ch)
{
  if (ch->player.physical_text.keywords)
    delete [] ch->player.physical_text.keywords;

  size_t len = strlen(GET_CHAR_NAME(ch)) + 1; // + strlen(race) + 2;
  ch->player.physical_text.keywords = new char[len];

  strcpy(ch->player.physical_text.keywords, GET_CHAR_NAME(ch));
  *(ch->player.physical_text.keywords) =
    LOWER(*ch->player.physical_text.keywords);

  if (ch->player.physical_text.name)
    delete [] ch->player.physical_text.name;

  if (ch->player.physical_text.room_desc)
    delete [] ch->player.physical_text.room_desc;

  if (ch->player.background)
    delete [] ch->player.background;

  if (ch->player.physical_text.look_desc)
    delete [] ch->player.physical_text.look_desc;

  {
    char temp[256];

    sprintf(temp, "A %s %s", genders[(int)GET_SEX(ch)], pc_race_types[(int)GET_RACE(ch)]);
    ch->player.physical_text.name = str_dup(temp);

    sprintf(temp, "A %s %s voice.", genders[(int)GET_SEX(ch)], pc_race_types[(int)GET_RACE(ch)]);
    ch->player.physical_text.room_desc = str_dup(temp);

    ch->player.physical_text.look_desc = str_dup("A fairly nondescript thing.\n");    
    ch->player.background = str_dup("A boring character.\n");
  }

  set_title(ch, "^y(Newbie)^n");
  set_pretitle(ch, NULL);
  set_whotitle(ch, " New ");

  switch(GET_RACE(ch)) {
  case RACE_HUMAN:
    set_whotitle(ch, "Human");
    break;
  case RACE_DWARF:
    set_whotitle(ch, "Dwarf");
    break;
  case RACE_ELF:
    set_whotitle(ch, "Elf");
    break;
  case RACE_ORK:
    set_whotitle(ch, "Ork");
    break;
  case RACE_TROLL:
    set_whotitle(ch, "Troll");
    break;
  case RACE_CYCLOPS:
    set_whotitle(ch, "Cyclops");
    break;
  case RACE_KOBOROKURU:
    set_whotitle(ch, "Koborokuru");
    break;
  case RACE_FOMORI:
    set_whotitle(ch, "Fomori");
    break;
  case RACE_MENEHUNE:
    set_whotitle(ch, "Menehune");
    break;
  case RACE_HOBGOBLIN:
    set_whotitle(ch, "Hobgoblin");
    break;
  case RACE_GIANT:
    set_whotitle(ch, "Giant");
    break;
  case RACE_GNOME:
    set_whotitle(ch, "Gnome");
    break;
  case RACE_ONI:
    set_whotitle(ch, "Oni");
    break;
  case RACE_WAKYAMBI:
    set_whotitle(ch, "Wakyambi");
    break;
  case RACE_OGRE:
    set_whotitle(ch, "Ogre");
    break;
  case RACE_MINOTAUR:
    set_whotitle(ch, " Minotaur");
    break;
  case RACE_SATYR:
    set_whotitle(ch, "Satyr");
    break;
  case RACE_NIGHTONE:
    set_whotitle(ch, "Night-One");
    break;
  case RACE_DRYAD:
    set_whotitle(ch, "Dryad");
    break;
  case RACE_DRAGON:
    set_whotitle(ch, "Dragon");
    break;
  default:
    mudlog("No race found at set_whotitle in class.cc", NULL, LOG_SYSLOG, TRUE);
    set_whotitle(ch, "CHKLG"); /* Will set incase the players */
  }        /* race is undeterminable      */

  GET_PROMPT(ch) = str_dup("< @pP @mM > ");
  ch->player.matrixprompt = str_dup("< @pP @mM > ");
}

/* Some initializations for characters, including initial skills */
void do_start(struct char_data * ch)
{
  void advance_level(struct char_data * ch);

  GET_LEVEL(ch) = 1;
  GET_KARMA(ch) = 0;
  GET_REP(ch) = 0;
  GET_NOT(ch) = 0;
  GET_TKE(ch) = 0;

  ch->points.max_physical = 1000;
  ch->points.max_mental = 1000;

  ch->char_specials.saved.left_handed = (!number(0, 9) ? 1 : 0);
  GET_WIELDED(ch, 0) = 0;
  GET_WIELDED(ch, 1) = 0;

  advance_level(ch);

  GET_PHYSICAL(ch) = GET_MAX_PHYSICAL(ch);
  GET_MENTAL(ch) = GET_MAX_MENTAL(ch);

  GET_COND(ch, THIRST) = 24;
  GET_COND(ch, FULL) = 24;
  GET_COND(ch, DRUNK) = 0;
  GET_LOADROOM(ch) = 8039;

  PLR_FLAGS(ch).SetBit(PLR_NEWBIE);
  PRF_FLAGS(ch).SetBits(PRF_AUTOEXIT, PRF_LONGEXITS, ENDBIT);
  PLR_FLAGS(ch).SetBit(PLR_AUTH);
  ch->player.time.played = 0;
  ch->player.time.logon = time(0);
  ch->player.time.lastdisc = time(0);
}

/* Gain maximum in various points */
void advance_level(struct char_data * ch)
{
  int i;

  if (IS_SENATOR(ch))
  {
    for (i = 0; i < 3; i++)
      GET_COND(ch, i) = (char) -1;
    GET_KARMA(ch) = 0;
    GET_REP(ch) = 0;
    GET_TKE(ch) = 0;
    GET_NOT(ch) = 0;
    if (PRF_FLAGGED(ch, PRF_PKER))
      PRF_FLAGS(ch).RemoveBit(PRF_PKER);
    PRF_FLAGS(ch).SetBits(PRF_HOLYLIGHT, PRF_ROOMFLAGS,
                          PRF_NOHASSLE, ENDBIT);
  }

  playerDB.SaveChar(ch);
  playerDB.Update(ch);

  sprintf(buf, "%s advanced to %s.",
          GET_CHAR_NAME(ch), status_ratings[(int)GET_LEVEL(ch)]);
  mudlog(buf, ch, LOG_MISCLOG, TRUE);
}

static bool load_char(const char *name, char_data *ch, bool logon)
{
  File fl;
  int i;
  char filename[MAX_STRING_LENGTH];
  char buf[MAX_STRING_LENGTH], bits[MAX_STRING_LENGTH];

  for (i = 0;(*(bits + i) = LOWER(*(name + i))); i++)
    ;

  sprintf(filename, "%s%s%c%s%s%s", PLR_PREFIX, SLASH, *bits,
          SLASH, bits, PLR_SUFFIX);

  fl.Open(filename, "r");

  if (!fl.IsOpen()) {
    sprintf(buf, "SYSERR: Couldn't open player file %s", filename);
    mudlog(buf, NULL, LOG_SYSLOG, TRUE);
    return false;
  }

  VTable data;
  data.Parse(&fl);

  fl.Close();

  /* character initializations */
  /* initializations necessary to keep some things straight */
  init_char(ch);

  for(i = 1; i <= MAX_SKILLS; i++)
    GET_SKILL(ch, i) = 0;

  ch->char_specials.carry_weight = 0;
  ch->char_specials.carry_items = 0;
  GET_BALLISTIC(ch) = GET_TOTALBAL(ch) = GET_IMPACT(ch) = GET_TOTALIMP(ch) = 0;
  ch->points.init_dice = 0;
  ch->points.init_roll = 0;
  ch->points.sustained[1] = 0;
  ch->points.sustained[0] = 0;

  GET_LAST_TELL(ch) = NOBODY;

  if (!data.GetString("Character", NULL)) {
    sprintf(buf, "Error: no character name in %s", filename);
    mudlog(buf, NULL, LOG_SYSLOG, true);
    return false;
  } else if (!data.GetString("Password", NULL)) {
    sprintf(buf, "Error: no password in %s", filename);
    mudlog(buf, NULL, LOG_SYSLOG, true);
    return false;
  }

  ch->player.char_name = str_dup(data.GetString("Character", NULL));

  memset(GET_PASSWD(ch), 0, MAX_PWD_LENGTH);
  strncpy(GET_PASSWD(ch), data.GetString("Password", NULL), MAX_PWD_LENGTH);

  GET_RACE(ch) = data.LookupInt("Race", pc_race_types, RACE_HUMAN);
  GET_SEX(ch) = data.LookupInt("Gender", genders, SEX_NEUTRAL);

  GET_LEVEL(ch) = data.LookupInt("Rank", status_ratings, 0);

  AFF_FLAGS(ch).FromString(data.GetString("AffFlags", "0"));
  PLR_FLAGS(ch).FromString(data.GetString("PlrFlags", "0"));
  PRF_FLAGS(ch).FromString(data.GetString("PrfFlags", "0"));

  { // player text
    text_data *text_tab[3] = {
                               &ch->player.physical_text,
                               &ch->player.astral_text,
                               &ch->player.matrix_text
                             };

    const char *section_tab[3] = {
                                   "PHYSICAL TEXT",
                                   "ASTRAL TEXT",
                                   "MATRIX TEXT"

                                 };
    for (int i = 0; i < 3; i++) {
      if (data.DoesSectionExist(section_tab[i])) {
        text_data *ptr = text_tab[i];
        char field[128], defawlt[128];

        sprintf(field, "%s/Keywords", section_tab[i]);
        ptr->keywords = str_dup(data.GetString(field, GET_CHAR_NAME(ch)));

        sprintf(field, "%s/Name", section_tab[i]);
        ptr->name = str_dup(data.GetString(field, GET_CHAR_NAME(ch)));

        sprintf(defawlt, "An internal error occurred, but %s is here.\n",
                GET_CHAR_NAME(ch));

        sprintf(field, "%s/RoomDesc", section_tab[i]);
        ptr->room_desc = str_dup(data.GetString(field, defawlt));

        sprintf(field, "%s/LookDesc", section_tab[i]);
        ptr->look_desc = str_dup(data.GetString(field, defawlt));
      }
    }
  }
  
  { // miscellaneous text
    POOFIN(ch)  = str_dup(data.GetString("MISC TEXT/PoofIn", NULL));
    POOFOUT(ch) = str_dup(data.GetString("MISC TEXT/PoofOut", NULL));

    GET_TITLE(ch) = str_dup(data.GetString("MISC TEXT/Title", NULL));
    GET_PRETITLE(ch) = str_dup(data.GetString("MISC TEXT/Pretitle", NULL));
    GET_WHOTITLE(ch) = str_dup(data.GetString("MISC TEXT/Whotitle", NULL));

    ch->player.prompt = str_dup(data.GetString("MISC TEXT/Prompt", NULL));
    ch->player.matrixprompt = str_dup(data.GetString("MISC TEXT/MatrixPrompt", NULL));
    ch->player.host = str_dup(data.GetString("MISC TEXT/Host", NULL));
    ch->player.background = str_dup(data.GetString("MISC TEXT/Background", "A boring character.\n"));
  }

  { // attributes
    GET_REAL_BOD(ch) = data.GetInt("ATTRIBUTES/Bod", 1);
    GET_REAL_QUI(ch) = data.GetInt("ATTRIBUTES/Qui", 1);
    GET_REAL_STR(ch) = data.GetInt("ATTRIBUTES/Str", 1);
    GET_REAL_CHA(ch) = data.GetInt("ATTRIBUTES/Cha", 1);
    GET_REAL_INT(ch) = data.GetInt("ATTRIBUTES/Int", 1);
    GET_REAL_WIL(ch) = data.GetInt("ATTRIBUTES/Wil", 1);
    ch->real_abils.mag = data.GetInt("ATTRIBUTES/Mag", 0);
    GET_PP(ch) = data.GetInt("ATTRIBUTES/PowerPoints", ch->real_abils.mag);
    ch->real_abils.ess = data.GetInt("ATTRIBUTES/EssenceTotal", 600);
    ch->real_abils.esshole = data.GetInt("ATTRIBUTES/EssenceHole", 0);
    ch->real_abils.bod_index = data.GetInt("ATTRIBUTES/BiowarIndex", 0);
    ch->real_abils.highestindex = data.GetInt("ATTRIBUTES/HighestIndex", 0);
    GET_REAL_REA(ch) = (GET_REAL_QUI(ch) + GET_REAL_INT(ch)) / 2;
  }
  { 
    ch->real_abils.defense_pool = data.GetInt("POOLS/Dodge", 0);
    ch->real_abils.body_pool = data.GetInt("POOLS/Body", 0);
    ch->real_abils.hacking_pool_max = data.GetInt("POOLS/MaxHacking", 0);
    ch->real_abils.casting_pool = data.GetInt("POOLS/Casting", 0);
    ch->real_abils.drain_pool = data.GetInt("POOLS/Drain", 0);
    ch->real_abils.spell_defense_pool = data.GetInt("POOLS/SpellDefense", 0);
    ch->real_abils.reflection_pool = data.GetInt("POOLS/Reflecting", 0);
         
  }
  ch->aff_abils = ch->real_abils;


  { // points
    GET_HEIGHT(ch) = data.GetInt("POINTS/Height", 150);
    GET_WEIGHT(ch) = data.GetInt("POINTS/Weight", 70);

    GET_TRADITION(ch) = data.GetInt("POINTS/Tradition", TRAD_MUNDANE);
    GET_ASPECT(ch) = data.GetInt("POINTS/Aspect", ASPECT_FULL);
    GET_TOTEM(ch) = data.GetInt("POINTS/Totem", TOTEM_UNDEFINED);
    GET_TOTEMSPIRIT(ch) = data.GetInt("POINTS/TotemSpirit", 0);
    GET_NUYEN(ch) = data.GetInt("POINTS/Cash", 0);
    GET_BANK(ch) = data.GetInt("POINTS/Bank", 0);

    GET_KARMA(ch) = data.GetInt("POINTS/Karma", 0);
    GET_REP(ch) = data.GetInt("POINTS/Rep", 0);
    GET_TKE(ch) = data.GetInt("POINTS/TKE", 0);
    GET_NOT(ch) = data.GetInt("Points/Notoriety", 0);

    GET_SIG(ch) = data.GetInt("POINTS/Sig", 0);
    GET_GRADE(ch) = data.GetInt("POINTS/UsedGrade", 0);
    ch->points.extrapp = data.GetInt("POINTS/ExtraPower", 0);
    GET_PHYSICAL(ch) = data.GetInt("POINTS/Physical", 1000);
    GET_PHYSICAL_LOSS(ch) = data.GetInt("POINTS/PhysicalLoss", 0);
    GET_MENTAL(ch) = data.GetInt("POINTS/Mental", 1000);
    GET_MENTAL_LOSS(ch) = data.GetInt("POINTS/MentalLoss", 0);
    GET_PERM_BOD_LOSS(ch) = data.GetInt("POINTS/PermBodLoss", 0);
    GET_CENTERINGSKILL(ch) = data.GetInt("POINTS/Centering", 0);
    if (PLR_FLAGGED(ch, PLR_AUTH)) {
      GET_ATT_POINTS(ch) = data.GetInt("POINTS/AttPoints", 0);
      GET_SKILL_POINTS(ch) = data.GetInt("POINTS/SkillPoints", 0);
      GET_FORCE_POINTS(ch) = data.GetInt("POINTS/ForcePoints", 0);
      GET_RESTRING_POINTS(ch) = data.GetInt("POINTS/RestringPoints", 0);
    }
    ch->player_specials->saved.zonenum = data.GetInt("POINTS/ZoneNumber", 0);
    ch->player_specials->saved.wimp_level = data.GetInt("POINTS/WimpLevel", 0);
    ch->player_specials->saved.freeze_level = data.GetInt("POINTS/FreezeLevel", 0);
    ch->player_specials->saved.invis_level = data.GetInt("POINTS/InvisLevel", 0);
    ch->player_specials->saved.incog_level = data.GetInt("POINTS/IncogLevel", 0);
    ch->player_specials->saved.bad_pws = data.GetInt("POINTS/BadPWs", 0);
    ch->player_specials->saved.load_room =
      data.GetLong("POINTS/LoadRoom", NOWHERE);
    ch->player_specials->saved.last_in =
      data.GetLong("POINTS/LastRoom", NOWHERE);
    GET_LASTROOM(ch) = data.GetLong("POINTS/Home", NOWHERE);

    ch->char_specials.saved.left_handed = data.GetInt("POINTS/LeftHanded", 0);
    ch->char_specials.arrive = str_dup(data.GetString("POINTS/ArriveText", NULL));
    ch->char_specials.leave = str_dup(data.GetString("POINTS/LeaveText", NULL));

    GET_LANGUAGE(ch) = data.GetInt("POINTS/CurLang", SKILL_ENGLISH);
    GET_MASKING(ch) = data.GetInt("POINTS/Masking", 0);

    if (GET_LANGUAGE(ch) == 0) {
      GET_SKILL(ch, SKILL_ENGLISH) = 10;
      GET_LANGUAGE(ch) = SKILL_ENGLISH;
    }

    if (GET_LANGUAGE(ch) < SKILL_ENGLISH)
      GET_LANGUAGE(ch) = SKILL_ENGLISH;
    ch->player.time.lastdisc = data.GetLong("POINTS/LastD", time(NULL));
    ch->player.time.logon = data.GetLong("POINTS/Last", time(NULL));
    ch->player.time.birth = data.GetLong("POINTS/Born", time(NULL));
    ch->player.time.played = data.GetInt("POINTS/Played", 0);
  }

  { // conditions
    GET_COND(ch, FULL) = data.GetInt("CONDITIONS/Hunger", 0);
    GET_COND(ch, THIRST) = data.GetInt("CONDITIONS/Thirst", 0);
    GET_COND(ch, DRUNK) = data.GetInt("CONDITIONS/Drunk", 0);
  }

  if (GET_LEVEL(ch) == LVL_MORTAL) { // Drugs
    GET_DRUG_STAGE(ch) = data.GetInt("DRUGS/Stage", 0);
    GET_DRUG_DURATION(ch) = data.GetInt("DRUGS/Duration", 0);
    GET_DRUG_AFFECT(ch) = data.GetInt("DRUGS/Affect", 0);
    GET_DRUG_DOSE(ch) = data.GetInt("DRUGS/Dose", 0);
    for (int i = 0; i < NUM_DRUGS - 1;) {
      const char *sect_name = data.GetIndexSection("DRUGS", i);
      sprintf(buf, "%s/Addict", sect_name);
      GET_DRUG_ADDICT(ch, ++i) = data.GetInt(buf, 0);
      sprintf(buf, "%s/Doses", sect_name);
      GET_DRUG_DOSES(ch, i) = data.GetInt(buf, 0);
      sprintf(buf, "%s/Edge", sect_name);
      GET_DRUG_EDGE(ch, i) = data.GetInt(buf, 0);
      sprintf(buf, "%s/LastFix", sect_name);
      GET_DRUG_LASTFIX(ch, i) = data.GetInt(buf, 0);
      sprintf(buf, "%s/Addtime", sect_name);
      GET_DRUG_ADDTIME(ch, i) = data.GetInt(buf, 0);
      sprintf(buf, "%s/Tolerant", sect_name);
      GET_DRUG_TOLERANT(ch, i) = data.GetInt(buf, 0);
      sprintf(buf, "%s/LastWith", sect_name);
      GET_DRUG_LASTWITH(ch, i) = data.GetInt(buf, 0);
    }
  }

  { // skills
    const int num_skills = data.NumFields("SKILLS");

    for (int j = 0; j < num_skills; j++) {
      const char *skill_name = data.GetIndexField("SKILLS", j);
      int idx;

      for (idx = 0; idx <= MAX_SKILLS; idx++)
        if (!str_cmp(skills[idx].name, skill_name))
          break;

      if (idx > 0 && idx <= MAX_SKILLS)
        GET_SKILL(ch, idx) = data.GetIndexInt("SKILLS", j, 0);
    }
  }
  {
    const int num_skills = data.NumFields("POWERS");

    for (int j = 0; j < num_skills; j++) {
      const char *skill_name = data.GetIndexField("POWERS", j);
      int idx;
      for (idx = 1; idx <= ADEPT_NUMPOWER; idx++)
        if (!str_cmp(adept_powers[idx], skill_name))
          break;
      GET_POWER_TOTAL(ch, idx) = data.GetIndexInt("POWERS", j, 0);
    }
  }
  { 
    const int num_skills = data.NumFields("METAMAGIC");

    for (int j = 0; j < num_skills; j++) {
      const char *skill_name = data.GetIndexField("METAMAGIC", j);
      int idx;
      for (idx = 0; idx <= META_MAX; idx++)
        if (!str_cmp(metamagic[idx], skill_name))
          break;
      GET_METAMAGIC(ch, idx) = data.GetIndexInt("METAMAGIC", j, 0);
    }
  }
  { // spells
    const int num_spells = data.NumSubsections("SPELL");

    for (int j = 0; j < num_spells; j++) {
      const char *sect_name = data.GetIndexSection("SPELL", j);
      char field[128];

      spell_data *spell = new spell_data;
      sprintf(field, "%s/Name", sect_name);
      spell->name = str_dup(data.GetString(field, "An unnamed spell"));

      sprintf(field, "%s/Type", sect_name);
      spell->type = data.GetInt(field, 0);

      sprintf(field, "%s/SubType", sect_name);
      spell->subtype = data.GetInt(field, 0);

      sprintf(field, "%s/Force", sect_name);
      spell->force = data.GetInt(field, 1);

      spell->next = ch->spells;
      ch->spells = spell;
    }
  }
  {
    const int num_spirits = data.NumSubsections("SPIRITS");
    struct spirit_data *last = NULL;
    for (int j = 0; j < num_spirits; j++) {
      const char *sect_name = data.GetIndexSection("SPIRITS", j);
      char field[128];
      spirit_data *spirit = new spirit_data;
      sprintf(field, "%s/Type", sect_name);
      spirit->type = data.GetInt(field, 0);
      sprintf(field, "%s/Services", sect_name);
      spirit->services = data.GetInt(field, 0);
      sprintf(field, "%s/Idnum", sect_name);
      spirit->id = data.GetInt(field, 0);
      sprintf(field, "%s/Force", sect_name);
      spirit->force = data.GetInt(field, 0);
      if (last)
        last->next = spirit;
      else
        GET_SPIRIT(ch) = spirit;
      GET_NUM_SPIRITS(ch)++;
      last = spirit;
    }
  }
  STOP_WORKING(ch);
  AFF_FLAGS(ch).RemoveBits(AFF_MANNING, AFF_RIG, AFF_PILOT, AFF_BANISH, AFF_FEAR, AFF_STABILIZE, AFF_SPELLINVIS, AFF_SPELLIMPINVIS, AFF_DETOX, AFF_RESISTPAIN, AFF_TRACKING, AFF_TRACKED, ENDBIT);
  PLR_FLAGS(ch).RemoveBits(PLR_REMOTE, PLR_SWITCHED, PLR_MATRIX, PLR_PROJECT, PLR_EDITING, PLR_WRITING, PLR_PERCEIVE, PLR_VISA, ENDBIT);

  { // aliases
    const int num_aliaii = data.NumFields("ALIASES");

    for (int j = 0; j < num_aliaii; j++) {
      alias *a = new alias;

      a->command = str_dup(data.GetIndexField("ALIASES", j));
      a->replacement = str_dup(data.GetIndexString("ALIASES", j, NULL));

      if (strchr(a->replacement, ALIAS_SEP_CHAR) ||
          strchr(a->replacement, ALIAS_VAR_CHAR))
        a->type = ALIAS_COMPLEX;
      else
        a->type = ALIAS_SIMPLE;

      a->next = GET_ALIASES(ch);
      GET_ALIASES(ch) = a;
    }
  }

  {
    const int num_mem = data.NumFields("MEMORY");

    for (int j = 0; j < num_mem; j++) {
      remem *a = new remem;

      a->mem = str_dup(data.GetIndexField("MEMORY", j));
      a->idnum = atol(data.GetIndexString("MEMORY", j, NULL));
      a->next = GET_MEMORY(ch);
      GET_MEMORY(ch) = a;
    }
  }

  {
    char field[128];
    for (int j = 0; j <= QUEST_TIMER - 1; j++) {
      sprintf(field, "QUESTS/Quest %d", j);
      GET_LQUEST(ch, j) = data.GetInt(field, 0);
    }
  }

  {
    struct obj_data *obj = NULL, *last_obj = NULL;
    struct phone_data *k;
    long vnum;
    int inside = 0, last_in = 0;
    int num_objs = data.NumSubsections("WORN");
    for (int i = 0; i < num_objs; i++) {
      const char *sect_name = data.GetIndexSection("WORN", i);
      sprintf(buf, "%s/Vnum", sect_name);
      vnum = data.GetLong(buf, 0);
      if (vnum > 0 && (obj = read_object(vnum, VIRTUAL))) {
        sprintf(buf, "%s/Name", sect_name);
        obj->restring = str_dup(data.GetString(buf, NULL));
        sprintf(buf, "%s/Photo", sect_name);
        obj->photo = str_dup(data.GetString(buf, NULL));
        for (int x = 0; x < 10; x++) {
          sprintf(buf, "%s/Value %d", sect_name, x);
          GET_OBJ_VAL(obj, x) = data.GetInt(buf, GET_OBJ_VAL(obj, x));
        }
        if (GET_OBJ_TYPE(obj) == ITEM_PHONE && GET_OBJ_VAL(obj, 2) && !logon) {
          sprintf(buf, "%04d%04d", GET_OBJ_VAL(obj, 0), GET_OBJ_VAL(obj, 1));
          k = new phone_data;
          k->number = atoi(buf);
          k->phone = obj;
          k->rtg = GET_OBJ_VAL(obj, 8);
          if (phone_list)
            k->next = phone_list;
          phone_list = k;
        } else if (GET_OBJ_TYPE(obj) == ITEM_PHONE && GET_OBJ_VAL(obj, 2))
          GET_OBJ_VAL(obj, 9) = 1;
        if (GET_OBJ_TYPE(obj) == ITEM_FOCUS && GET_OBJ_VAL(obj, 0) == FOCI_SUSTAINED)
          GET_OBJ_VAL(obj, 4) = 0;
        if (GET_OBJ_TYPE(obj) == ITEM_FOCUS && GET_OBJ_VAL(obj, 4))
          GET_FOCI(ch)++;
        sprintf(buf, "%s/Condition", sect_name);
        GET_OBJ_CONDITION(obj) = data.GetInt(buf, GET_OBJ_CONDITION(obj));
        sprintf(buf, "%s/Cost", sect_name);
        GET_OBJ_COST(obj) = data.GetInt(buf, 1);
        sprintf(buf, "%s/Timer", sect_name);
        GET_OBJ_TIMER(obj) = data.GetInt(buf, 0);
        sprintf(buf, "%s/Attempt", sect_name);
        GET_OBJ_ATTEMPT(obj) = data.GetInt(buf, 0);
//        sprintf(buf, "%s/ExtraFlags", sect_name);
//        GET_OBJ_EXTRA(obj).FromString(data.GetString(buf, "0"));
        sprintf(buf, "%s/Inside", sect_name);
        inside = data.GetInt(buf, 0);
        if (inside > 0) {
          if (inside == last_in)
            last_obj = last_obj->in_obj;
          else if (inside < last_in)
            while (inside <= last_in) {
              last_obj = last_obj->in_obj;
              last_in--;
            }
          if (last_obj)
            obj_to_obj(obj, last_obj);
        } else {
          sprintf(buf, "%s/Worn", sect_name);
          equip_char(ch, obj, data.GetInt(buf, 0));
        }
        last_in = inside;
        last_obj = obj;
      }
    }
  }

  {
    struct obj_data *last_obj = NULL, *obj;
    struct phone_data *k;
    int vnum = 0;
    int inside = 0, last_in = 0;
    int num_objs = data.NumSubsections("INV");
    for (int j = 0; j < num_objs; j++) {
      const char *sect_name = data.GetIndexSection("INV", j);
      sprintf(buf, "%s/Vnum", sect_name);
      vnum = data.GetLong(buf, 0);
      if (vnum > 0 && (obj = read_object(vnum, VIRTUAL))) {
        sprintf(buf, "%s/Name", sect_name);
        obj->restring = str_dup(data.GetString(buf, NULL));
        sprintf(buf, "%s/Photo", sect_name);
        obj->photo = str_dup(data.GetString(buf, NULL));
        for (int x = 0; x < 10; x++) {
          sprintf(buf, "%s/Value %d", sect_name, x);
          GET_OBJ_VAL(obj, x) = data.GetInt(buf, GET_OBJ_VAL(obj, x));
        }
        if (GET_OBJ_TYPE(obj) == ITEM_PHONE && GET_OBJ_VAL(obj, 2) && !logon) {
          sprintf(buf, "%04d%04d", GET_OBJ_VAL(obj, 0), GET_OBJ_VAL(obj, 1));
          k = new phone_data;
          k->number = atoi(buf);
          k->phone = obj;
          k->rtg = GET_OBJ_VAL(obj, 8);
          if (phone_list)
            k->next = phone_list;
          phone_list = k;
        } else if (GET_OBJ_TYPE(obj) == ITEM_PHONE && GET_OBJ_VAL(obj, 2))
          GET_OBJ_VAL(obj, 9) = 1;
        if (GET_OBJ_TYPE(obj) == ITEM_FOCUS && GET_OBJ_VAL(obj, 0) == FOCI_SUSTAINED)
          GET_OBJ_VAL(obj, 4) = 0;
        if (GET_OBJ_TYPE(obj) == ITEM_FOCUS && GET_OBJ_VAL(obj, 4))
          GET_FOCI(ch)++;
        sprintf(buf, "%s/Condition", sect_name);
        GET_OBJ_CONDITION(obj) = data.GetInt(buf, GET_OBJ_CONDITION(obj));
        sprintf(buf, "%s/Cost", sect_name);
        GET_OBJ_COST(obj) = data.GetInt(buf, 1);
        sprintf(buf, "%s/Timer", sect_name);
        GET_OBJ_TIMER(obj) = data.GetInt(buf, 0);
        sprintf(buf, "%s/Attempt", sect_name);
        GET_OBJ_ATTEMPT(obj) = data.GetInt(buf, 0);
//        sprintf(buf, "%s/ExtraFlags", sect_name);
//        GET_OBJ_EXTRA(obj).FromString(data.GetString(buf, "0"));
        sprintf(buf, "%s/Inside", sect_name);
        inside = data.GetInt(buf, 0);
        if (inside > 0) {
          if (inside == last_in)
            last_obj = last_obj->in_obj;
          else if (inside < last_in)
            while (inside <= last_in) {
              last_obj = last_obj->in_obj;
              last_in--;
            }
          if (last_obj)
            obj_to_obj(obj, last_obj);
        } else
          obj_to_char(obj, ch);
        last_in = inside;
        last_obj = obj;
      }
    }
  }

  {
    struct obj_data *obj, *last_obj;
    struct phone_data *k;
    int vnum = 0, last_in = 0, inside = 0;
    int num_objs = data.NumSubsections("CYBERWAR");
    for (int j = 0; j < num_objs; j++) {
      const char *sect_name = data.GetIndexSection("CYBERWAR", j);
      sprintf(buf, "%s/Vnum", sect_name);
      vnum = data.GetLong(buf, 0);
      if (vnum > 0 && (obj = read_object(vnum, VIRTUAL))) {
        sprintf(buf, "%s/Name", sect_name);
        obj->restring = str_dup(data.GetString(buf, NULL));
        sprintf(buf, "%s/Photo", sect_name);
        obj->photo = str_dup(data.GetString(buf, NULL));
        for (int x = 0; x < 10; x++) {
          sprintf(buf, "%s/Value %d", sect_name, x);
          GET_OBJ_VAL(obj, x) = data.GetInt(buf, GET_OBJ_VAL(obj, x));
        }
        if (GET_OBJ_VAL(obj, 0) == CYB_PHONE && GET_OBJ_VAL(obj, 7) && !logon) {
          sprintf(buf, "%04d%04d", GET_OBJ_VAL(obj, 3), GET_OBJ_VAL(obj, 6));
          k = new phone_data;
          k->number = atoi(buf);
          k->phone = obj;
          k->rtg = GET_OBJ_VAL(obj, 8);
          if (phone_list)
            k->next = phone_list;
          phone_list = k;
        } else if (GET_OBJ_VAL(obj, 2) == 4 && GET_OBJ_VAL(obj, 7))
          GET_OBJ_VAL(obj, 9) = 1;
        sprintf(buf, "%s/Cost", sect_name);
        GET_OBJ_COST(obj) = data.GetInt(buf, 1);
        sprintf(buf, "%s/Level", sect_name);
        inside = data.GetInt(buf, 0);
        if (inside > 0) {
          if (inside == last_in)
            last_obj = last_obj->in_obj;
          else if (inside < last_in)
            while (inside <= last_in) {
              last_obj = last_obj->in_obj;
              last_in--;
            }
          if (last_obj)
            obj_to_obj(obj, last_obj);
        } else
        obj_to_cyberware(obj, ch);
        last_in = inside;
        last_obj = obj;
      }
    }
  }

  {
    struct obj_data *obj;
    int vnum = 0;
    int num_objs = data.NumSubsections("BIOWAR");
    for (int j = 0; j < num_objs; j++) {
      const char *sect_name = data.GetIndexSection("BIOWAR", j);
      sprintf(buf, "%s/Vnum", sect_name);
      vnum = data.GetLong(buf, 0);
      if (vnum > 0 && (obj = read_object(vnum, VIRTUAL))) {
        for (int x = 0; x < 10; x++) {
          sprintf(buf, "%s/Value %d", sect_name, x);
          GET_OBJ_VAL(obj, x) = data.GetInt(buf, GET_OBJ_VAL(obj, x));
        }
        sprintf(buf, "%s/Cost", sect_name);
        GET_OBJ_COST(obj) = data.GetInt(buf, 1);
        obj_to_bioware(obj, ch);
      }
    }
  }

  for (struct obj_data *jack = ch->cyberware; jack; jack = jack->next_content)
    if (GET_OBJ_VAL(jack, 0) == CYB_CHIPJACK) {
      int max = 0;
      for (struct obj_data *wire = ch->cyberware; wire; wire = wire->next_content) 
        if (GET_OBJ_VAL(wire, 0) == CYB_SKILLWIRE) 
          max = GET_OBJ_VAL(wire, 1);
      for (struct obj_data *chip = jack->contains; chip; chip = chip->next_content)
        ch->char_specials.saved.skills[GET_OBJ_VAL(chip, 0)][1] = skills[GET_OBJ_VAL(chip, 0)].type ? GET_OBJ_VAL(chip, 1) 
                                                                                         : MIN(max, GET_OBJ_VAL(chip, 1));
      break;
    } else if (GET_OBJ_VAL(jack, 0) == CYB_MEMORY) {
      int max = 0;
      for (struct obj_data *wire = ch->cyberware; wire; wire = wire->next_content) 
        if (GET_OBJ_VAL(wire, 0) == CYB_SKILLWIRE) 
          max = GET_OBJ_VAL(wire, 1);
      for (struct obj_data *chip = jack->contains; chip; chip = chip->next_content)
        ch->char_specials.saved.skills[GET_OBJ_VAL(chip, 0)][1] = skills[GET_OBJ_VAL(chip, 0)].type ? GET_OBJ_VAL(chip, 1)
                                                                                         : MIN(max, GET_OBJ_VAL(chip, 1));
      break;
    }

  affect_total(ch);

  if ((((long) (time(0) - ch->player.time.lastdisc)) >= SECS_PER_REAL_HOUR)) {
    GET_PHYSICAL(ch) = GET_MAX_PHYSICAL(ch);
    GET_MENTAL(ch) = GET_MAX_MENTAL(ch);
    if (AFF_FLAGS(ch).IsSet(AFF_HEALED))
      AFF_FLAGS(ch).SetBit(AFF_HEALED);
  }
  if ( !IS_SENATOR(ch) )
    PRF_FLAGS(ch).RemoveBit(PRF_ROLLS);

  if (((long) (time(0) - ch->player.time.lastdisc) >= SECS_PER_REAL_HOUR * 2) ||
      (GET_LAST_IN(ch) > 599 && GET_LAST_IN(ch) < 700)) {
    GET_LAST_IN(ch) = GET_LOADROOM(ch);
    GET_PHYSICAL(ch) = GET_MAX_PHYSICAL(ch);
    GET_MENTAL(ch) = GET_MAX_MENTAL(ch);
  }
  if (!logon)
    ch->player.time.logon = time(0);
  // initialization for imms
  if(IS_SENATOR(ch)) {
    GET_COND(ch, FULL) = -1;
    GET_COND(ch, THIRST) = -1;
    GET_COND(ch, DRUNK) = -1;
  }

  switch (GET_RACE(ch)) {
  case RACE_HUMAN:
  case RACE_OGRE:
    NATURAL_VISION(ch) = NORMAL;
    break;
  case RACE_DWARF:
  case RACE_GNOME:
  case RACE_MENEHUNE:
  case RACE_KOBOROKURU:
  case RACE_TROLL:
  case RACE_CYCLOPS:
  case RACE_FOMORI:
  case RACE_GIANT:
  case RACE_MINOTAUR:
    NATURAL_VISION(ch) = THERMOGRAPHIC;
    break;
  case RACE_ORK:
  case RACE_HOBGOBLIN:
  case RACE_SATYR:
  case RACE_ONI:
  case RACE_ELF:
  case RACE_WAKYAMBI:
  case RACE_NIGHTONE:
  case RACE_DRYAD:
    NATURAL_VISION(ch) = LOWLIGHT;
    break;
  }

  return true;
}

static bool save_char(char_data *player, DBIndex::vnum_t loadroom)
{
  FILE *fl;
  char outname[40], buf[MAX_STRING_LENGTH];
  char bits[128];
  int i;
  struct obj_data *char_eq[NUM_WEARS];
  int wield[2];
  struct obj_data *temp, *next_obj;

  if (IS_NPC(player))
    return false;

  for (i = 0;(*(bits + i) = LOWER(*(GET_CHAR_NAME(player) + i))); i++)
    ;
  sprintf(outname, "%s%s%c%s%s%s", PLR_PREFIX, SLASH, *bits, SLASH, bits, PLR_SUFFIX);

  if (!(fl = fopen(outname, "w"))) {
    sprintf(buf, "SYSERR: Couldn't open player file (%s) for write", outname);
    mudlog(buf, NULL, LOG_SYSLOG, TRUE);
    return false;
  }

  /**************************************************/
  /** Do In-Memory Copy **/
  wield[0] = GET_WIELDED(player, 0);
  wield[1] = GET_WIELDED(player, 1);

  /* Unaffect everything a character can be affected by */

  /* worn eq */
  for (i = 0; i < NUM_WEARS; i++) {
    if (player->equipment[i])
      char_eq[i] = unequip_char(player, i, FALSE);
    else
      char_eq[i] = NULL;
  }

  GET_WIELDED(player, 0) = wield[0];
  GET_WIELDED(player, 1) = wield[1];

  /* cyberware */
  for (temp = player->cyberware; temp; temp = next_obj) {
    next_obj = temp->next_content;
    obj_from_cyberware(temp);
    obj_to_char(temp, player);
  }

  /* bioware */
  for (temp = player->bioware; temp; temp = next_obj) {
    next_obj = temp->next_content;
    obj_from_bioware(temp);
    obj_to_char(temp, player);
  }

  for (struct sustain_data *sust = GET_SUSTAINED(player); sust; sust = sust->next)
    if (!sust->caster)
      spell_modify(player, sust, FALSE);

  /**************************************************/
  if (loadroom == NOWHERE)
    loadroom = GET_LOADROOM(player);

  if (player->in_room != NOWHERE)
    if ( world[player->in_room].number <= 1 ) {
      GET_LAST_IN(player) = world[player->was_in_room].number;
    } else {
      GET_LAST_IN(player) = world[player->in_room].number;
    }


  /***************************************/
  /* Code Here for the actual Save Washu */
  /***************************************/

  fprintf(fl, "Character:\t%s\n", player->player.char_name);
  fprintf(fl, "Password:\t%s\n", GET_PASSWD(player));
  fprintf(fl, "Race:\t%s\n", pc_race_types[(int)GET_RACE(player)]);
  fprintf(fl, "Gender:\t%s\n", genders[(int)GET_SEX(player)]);
  fprintf(fl, "Rank:\t%s\n", status_ratings[(int)GET_LEVEL(player)]);

  fprintf(fl,
          "AffFlags:\t%s\n"
          "PlrFlags:\t%s\n"
          "PrfFlags:\t%s\n",
          AFF_FLAGS(player).ToString(),
          PLR_FLAGS(player).ToString(),
          PRF_FLAGS(player).ToString());

  { // player text
    text_data *text_tab[3] = {
                               &player->player.physical_text,
                               &player->player.astral_text,
                               &player->player.matrix_text
                             };

    const char *section_tab[3] = {
                                   "PHYSICAL TEXT",
                                   "ASTRAL TEXT",
                                   "MATRIX TEXT"
                                 };

    for (int i = 0; i < 3; i++) {
      text_data *ptr = text_tab[i];

      fprintf(fl, "[%s]\n", section_tab[i]);

      fprintf(fl,
              "\tKeywords:\t%s\n"
              "\tName:\t%s\n",
              ptr->keywords? ptr->keywords : player->player.char_name,
              ptr->name? ptr->name : player->player.char_name);

      if (ptr->room_desc) {
        strcpy(buf, ptr->room_desc);
        kill_ems(buf);
        fprintf(fl, "\tRoomDesc:$\n%s~\n", buf);
      } else {
        char race[32];

        strcpy(race, pc_race_types[(int)GET_RACE(player)]);
        *race = tolower(*race);

        fprintf(fl, "\tRoomDesc:$\nA %s %s voice~\n",
                genders[(int)GET_SEX(player)], race);
      }

      if (ptr->look_desc) {
        strcpy(buf, ptr->look_desc);
        kill_ems(buf);
        fprintf(fl, "\tLookDesc:$\n%s~\n", buf);
      } else {
        char race[32];

        strcpy(race, pc_race_types[(int)GET_RACE(player)]);
        *race = tolower(*race);

        fprintf(fl, "\tLookDesc:$\nYou see an unimaginative %s.~\n", race);
      }
    }
  }

  { // miscellaneous text
    fprintf(fl, "[MISC TEXT]\n");

    if(POOFIN(player))
      fprintf(fl, "\tPoofIn:\t%s\n", POOFIN(player));

    if(POOFOUT(player))
      fprintf(fl, "\tPoofOut:\t%s\n", POOFOUT(player));

    if(GET_TITLE(player))
      fprintf(fl, "\tTitle:\t%s\n", GET_TITLE(player));

    if(GET_PRETITLE(player))
      fprintf(fl, "\tPretitle:\t%s\n", GET_PRETITLE(player));

    if(GET_WHOTITLE(player))
      fprintf(fl, "\tWhotitle:\t%s\n", GET_WHOTITLE(player));
    else
      fprintf(fl, "\tWhotitle:\t%s\n", pc_race_types[(int)GET_RACE(player)]);

    if (player->player.prompt)
      fprintf(fl, "\tPrompt:\t%s\n", player->player.prompt);
    if (player->player.matrixprompt)
      fprintf(fl, "\tMatrixPrompt:\t%s\n", player->player.matrixprompt);

    if (player->player.host)
      fprintf(fl, "\tHost:\t%s\n", player->player.host);
    if (player->player.background) {
      strcpy(buf, player->player.background);
      kill_ems(buf);
      fprintf(fl, "\tBackground:$\n%s~\n", buf);
    }
  }

  { // attributes
    fprintf(fl, "[ATTRIBUTES]\n");

    fprintf(fl,
            "\tBod:\t%d\n"
            "\tQui:\t%d\n"
            "\tStr:\t%d\n"
            "\tCha:\t%d\n"
            "\tInt:\t%d\n"
            "\tWil:\t%d\n"
            "\tMag:\t%d\n"
            "\tEssenceTotal:\t%d\n"
            "\tEssenceHole:\t%d\n"
            "\tBiowarIndex:\t%d\n"
            "\tHighestIndex:\t%d\n"
            "\tPowerPoints:\t%d\n",
            GET_REAL_BOD(player), GET_REAL_QUI(player), GET_REAL_STR(player),
            GET_REAL_CHA(player), GET_REAL_INT(player), GET_REAL_WIL(player),
            GET_REAL_MAG(player), GET_REAL_ESS(player),
            player->real_abils.esshole, GET_INDEX(player), player->real_abils.highestindex, GET_PP(player));
  }
  {
    fprintf(fl, "[POOLS]\n");
    fprintf(fl, "\tCasting:\t%d\n"
                "\tSpellDefense:\t%d\n"
                "\tDrain:\t%d\n"
                "\tReflecting:\t%d\n"
                "\tMaxHacking:\t%d\n"
                "\tBody:\t%d\n"
                "\tDodge:\t%d\n",
                GET_CASTING(player), GET_SDEFENSE(player), GET_DRAIN(player), GET_REFLECT(player), 
                GET_MAX_HACKING(player), GET_BODY(player), GET_DEFENSE(player));
                 
  }
  { // points
    fprintf(fl, "[POINTS]\n");

    fprintf(fl, "\tHeight:\t%d\n", GET_HEIGHT(player));
    fprintf(fl, "\tWeight:\t%d\n", GET_WEIGHT(player));

    if (player->player.tradition != TRAD_MUNDANE) {
      fprintf(fl, "\tTradition:\t%d\n", GET_TRADITION(player));
    }
    if (GET_TOTEM(player)) {
      fprintf(fl, "\tTotem:\t%d\n", GET_TOTEM(player));
      fprintf(fl, "\tTotemSpirit:\t%d\n", GET_TOTEMSPIRIT(player));
    }
    if (GET_ASPECT(player))
      fprintf(fl, "\tAspect:\t%d\n", GET_ASPECT(player));
    fprintf(fl, "\tCash:\t%d\n", GET_NUYEN(player));
    fprintf(fl, "\tBank:\t%d\n", GET_BANK(player));
    fprintf(fl, "\tKarma:\t%d\n", GET_KARMA(player));
    fprintf(fl, "\tRep:\t%d\n", GET_REP(player));
    fprintf(fl, "\tNotoriety:\t%d\n", GET_NOT(player));
    fprintf(fl, "\tTKE:\t%d\n", GET_TKE(player));
    fprintf(fl, "\tSig:\t%d\n", GET_SIG(player));
    if (GET_TRADITION(player) != TRAD_MUNDANE) {
      fprintf(fl, "\tUsedGrade:\t%d\n", GET_GRADE(player));
      fprintf(fl, "\tExtraPower:\t%d\n", player->points.extrapp);
      if (GET_METAMAGIC(player, META_CENTERING))
        fprintf(fl, "\tCentering:\t%d\n", GET_CENTERINGSKILL(player));
    }
    fprintf(fl, "\tPhysical:\t%d\n", GET_PHYSICAL(player));
    fprintf(fl, "\tPhysicalLoss:\t%d\n", GET_PHYSICAL_LOSS(player));
    fprintf(fl, "\tMental:\t%d\n", GET_MENTAL(player));
    fprintf(fl, "\tMentalLoss:\t%d\n", GET_MENTAL_LOSS(player));
    fprintf(fl, "\tPermBodLoss:\t%d\n", GET_PERM_BOD_LOSS(player));

    if (PLR_FLAGGED(player, PLR_AUTH)) {
      fprintf(fl, "\tAttPoints:\t%d\n", GET_ATT_POINTS(player));
      fprintf(fl, "\tSkillPoints:\t%d\n", GET_SKILL_POINTS(player));
      fprintf(fl, "\tForcePoints:\t%d\n", GET_FORCE_POINTS(player));
      fprintf(fl, "\tRestringPoints:\t%d\n", GET_RESTRING_POINTS(player));
    }
    if (GET_LEVEL(player) >= LVL_BUILDER)
      fprintf(fl, "\tZoneNumber:\t%d\n", player->player_specials->saved.zonenum);

    fprintf(fl, "\tLeftHanded:\t%d\n", player->char_specials.saved.left_handed);
    if (player->char_specials.arrive)
      fprintf(fl, "\tArriveText:\t%s\n", player->char_specials.arrive);
    if (player->char_specials.leave)
      fprintf(fl, "\tLeaveText:\t%s\n", player->char_specials.leave);

    fprintf(fl, "\tCurLang:\t%d\n", GET_LANGUAGE(player));
    fprintf(fl, "\tMasking:\t%d\n", GET_MASKING(player));
    fprintf(fl, "\tWimpLevel:\t%d\n", GET_WIMP_LEV(player));

    if (GET_FREEZE_LEV(player))
      fprintf(fl, "\tFreezeLevel:\t%d\n", GET_FREEZE_LEV(player));

    if (GET_INVIS_LEV(player))
      fprintf(fl, "\tInvisLevel:\t%d\n", GET_INVIS_LEV(player));

    if (GET_INCOG_LEV(player))
      fprintf(fl, "\tIncogLevel:\t%d\n", GET_INCOG_LEV(player));

    if (GET_BAD_PWS(player))
      fprintf(fl, "\tBadPWs:\t%d\n", GET_BAD_PWS(player));

    fprintf(fl, "\tLoadRoom:\t%ld\n", GET_LOADROOM(player));
    fprintf(fl, "\tLastRoom:\t%ld\n", GET_LAST_IN(player));
    fprintf(fl, "\tLastD:\t%ld\n", time(0));
    fprintf(fl, "\tLast:\t%ld\n", player->player.time.logon);
    fprintf(fl, "\tBorn:\t%ld\n", player->player.time.birth);
    fprintf(fl, "\tPlayed:\t%d\n", player->player.time.played);
  }

  { // conditions
    fprintf(fl,
            "[CONDITIONS]\n"
            "\tHunger:\t%d\n"
            "\tThirst:\t%d\n"
            "\tDrunk:\t%d\n",
            GET_COND(player, FULL),
            GET_COND(player, THIRST),
            GET_COND(player, DRUNK));
  }

  if (GET_LEVEL(player) == LVL_MORTAL) {
    fprintf(fl, "[DRUGS]\n"
            "\tAffect:\t%d\n"
            "\tStage:\t%d\n"
            "\tDuration:\t%d\n"
            "\tDose:\t%d\n", GET_DRUG_AFFECT(player), GET_DRUG_STAGE(player), GET_DRUG_DURATION(player), GET_DRUG_DOSE(player));
    for (int i = 1; i < NUM_DRUGS; i++)
      fprintf(fl, "\t[DRUG %d]\n"
              "\t\tAddict:\t%d\n"
              "\t\tDoses:\t%d\n"
              "\t\tEdge:\t%d\n"
              "\t\tLastFix:\t%d\n"
              "\t\tAddtime:\t%d\n"
              "\t\tTolerant:\t%d\n"
              "\t\tLastWith:\t%d\n",
              i, GET_DRUG_ADDICT(player, i), GET_DRUG_DOSES(player, i), GET_DRUG_EDGE(player, i), GET_DRUG_LASTFIX(player, i),
              GET_DRUG_ADDTIME(player, i), GET_DRUG_TOLERANT(player, i), GET_DRUG_LASTWITH(player, i));
  }

  { // skills
    fprintf(fl, "[SKILLS]\n");

    for (int i = 0; i <= MAX_SKILLS; i++)
      if (GET_SKILL(player, i))
        fprintf(fl, "\t%s:\t%d\n",
                skills[i].name, player->char_specials.saved.skills[i][0]);
  }
  { // adept powers
    if (GET_TRADITION(player) == TRAD_ADEPT) {
      fprintf(fl, "[POWERS]\n");
      for (int i = 1; i < ADEPT_NUMPOWER; i++)
        if (GET_POWER_TOTAL(player, i))
          fprintf(fl, "\t%s:\t%d\n", adept_powers[i], GET_POWER_TOTAL(player, i));
    }
  }
  {
    if (GET_TRADITION(player) != TRAD_MUNDANE) {
      fprintf(fl, "[METAMAGIC]\n");
      for (int i = 0; i < META_MAX; i++)
        if (GET_METAMAGIC(player, i))
          fprintf(fl, "\t%s:\t%d\n", metamagic[i], GET_METAMAGIC(player, i));
    }
  }
  /* add spell and eq affections back in now */
  for (temp = player->carrying; temp; temp = next_obj) {
    next_obj = temp->next_content;
    if ((GET_OBJ_TYPE(temp) == ITEM_CYBERWARE)) {
      obj_from_char(temp);
      obj_to_cyberware(temp, player);
    }
    /* bioware next */
    if (GET_OBJ_TYPE(temp) == ITEM_BIOWARE) {
      obj_from_char(temp);
      obj_to_bioware(temp, player);
    }
  }

  for (struct sustain_data *sust = GET_SUSTAINED(player); sust; sust = sust->next)
    if (!sust->caster)
      spell_modify(player, sust, TRUE);

  // then worn eq
  for (i = 0; i < NUM_WEARS; i++) {
    if (char_eq[i])
      equip_char(player, char_eq[i], i);
  }

  affect_total(player);

  if (player->spells) {
    fprintf(fl, "[SPELL]\n");
    int i = 0;
    for (struct spell_data *temp = player->spells; temp; temp = temp->next, i++) {
      fprintf(fl,
              "\t[SPELL %d]\n"
              "\t\tName:\t%s\n"
              "\t\tType:\t%d\n"
              "\t\tSubType:\t%d\n"
              "\t\tForce:\t%d\n",
              i, temp->name, temp->type, temp->subtype, temp->force);
    }
  }

  if (GET_SPIRIT(player) && GET_TRADITION(player) == TRAD_HERMETIC) {
    fprintf(fl, "[SPIRITS]\n");
    int i = 0;
    for (struct spirit_data *spirit = GET_SPIRIT(player); spirit; spirit = spirit->next, i++) {
      fprintf(fl, "\t[SPIRIT %d]\n"
              "\t\tType:\t%d\n"
              "\t\tForce:\t%d\n"
              "\t\tServices:\t%d\n"
              "\t\tIdnum:\t%d\n",
              i, spirit->type, spirit->force, spirit->services, spirit->id);
    }
  }
  fprintf(fl, "[ALIASES]\n");
  for (struct alias *a = GET_ALIASES(player); a; a = a->next) {
    fprintf(fl, "\t%s:\t%s\n",
            a->command, a->replacement);
  }

  fprintf(fl, "[MEMORY]\n");
  for (struct remem *b = GET_MEMORY(player); b; b = b->next)
    if (b->idnum)
      fprintf(fl, "\t%s:\t%ld\n", b->mem, b->idnum);

  fprintf(fl, "[QUESTS]\n");
  for (int i = 0; i <= QUEST_TIMER - 1; i++)
    fprintf(fl, "\tQuest %d:\t%d\n", i, GET_LQUEST(player, i));

  struct obj_data *obj = NULL, *last_obj = NULL;
  int o = 0, level = 0;
  fprintf(fl, "[WORN]\n");
  for (i = 0; i < NUM_WEARS; i++)
    if ((obj = GET_EQ(player, i)) && !IS_OBJ_STAT(obj, ITEM_NORENT))
      break;

  while (obj && i < NUM_WEARS) {
    if (!IS_OBJ_STAT(obj, ITEM_NORENT)) {
      fprintf(fl, "\t[WORN %d]\n", o);
      fprintf(fl, "\t\tVnum:\t%ld\n", GET_OBJ_VNUM(obj));
      if (level)
        fprintf(fl, "\t\tInside:\t%d\n", level);
      fprintf(fl, "\t\tWorn:\t%d\n", i);
      if (GET_OBJ_TYPE(obj) == ITEM_PHONE)
        for (int x = 0; x < 9; x++)
          fprintf(fl, "\t\tValue %d:\t%d\n", x, GET_OBJ_VAL(obj, x));
      else
        for (int x = 0; x < 10; x++)
          fprintf(fl, "\t\tValue %d:\t%d\n", x, GET_OBJ_VAL(obj, x));
      fprintf(fl, "\t\tCondition:\t%d\n", GET_OBJ_CONDITION(obj));
      fprintf(fl, "\t\tCost:\t%d\n", GET_OBJ_COST(obj));
      fprintf(fl, "\t\tTimer:\t%d\n", GET_OBJ_TIMER(obj));
      fprintf(fl, "\t\tAttempt:\t%d\n", GET_OBJ_ATTEMPT(obj));
      fprintf(fl, "\t\tExtraFlags:\t%s\n", GET_OBJ_EXTRA(obj).ToString());
      if (obj->restring)
        fprintf(fl, "\t\tName:\t%s\n", obj->restring);
      if (obj->photo)
        fprintf(fl, "\t\tPhoto:$\n%s~\n", cleanup(buf2, obj->photo));
      last_obj = GET_EQ(player, i);
      o++;
    }

    if (obj->contains && !IS_OBJ_STAT(obj, ITEM_NORENT) && GET_OBJ_TYPE(obj) != ITEM_PART) {
      obj = obj->contains;
      level++;
      continue;
    } else if (!obj->next_content && obj->in_obj)
      while (obj && !obj->next_content && level >= 0) {
        obj = obj->in_obj;
        level--;
      }

    if (!obj || !obj->next_content)
      while (i < NUM_WEARS) {
        i++;
        if ((obj = GET_EQ(player, i)) && !IS_OBJ_STAT(obj, ITEM_NORENT)) {
          level = 0;
          break;
        }
      }
    else
      obj = obj->next_content;
  }

  level = 0;
  o = 0;
  fprintf(fl, "[INV]\n");
  for (obj = player->carrying; obj;) {
    if (!IS_OBJ_STAT(obj, ITEM_NORENT)) {
      fprintf(fl, "\t[Object %d]\n", o);
      o++;
      fprintf(fl, "\t\tVnum:\t%ld\n", GET_OBJ_VNUM(obj));
      if (level)
        fprintf(fl, "\t\tInside:\t%d\n", level);
      if (GET_OBJ_TYPE(obj) == ITEM_PHONE)
        for (int x = 0; x < 9; x++)
          fprintf(fl, "\t\tValue %d:\t%d\n", x, GET_OBJ_VAL(obj, x));
      else
        for (int x = 0; x < 10; x++)
          fprintf(fl, "\t\tValue %d:\t%d\n", x, GET_OBJ_VAL(obj, x));
      fprintf(fl, "\t\tCondition:\t%d\n", GET_OBJ_CONDITION(obj));
      fprintf(fl, "\t\tCost:\t%d\n", GET_OBJ_COST(obj));
      fprintf(fl, "\t\tTimer:\t%d\n", GET_OBJ_TIMER(obj));
      fprintf(fl, "\t\tAttempt:\t%d\n", GET_OBJ_ATTEMPT(obj));
      fprintf(fl, "\t\tExtraFlags:\t%d\n", GET_OBJ_EXTRA(obj).ToString());      
      if (obj->restring)
        fprintf(fl, "\t\tName:\t%s\n", obj->restring);
      if (obj->photo)
        fprintf(fl, "\t\tPhoto:$\n%s\n~\n", cleanup(buf2, obj->photo));
    }

    if (obj->contains && !IS_OBJ_STAT(obj, ITEM_NORENT) && GET_OBJ_TYPE(obj) != ITEM_PART) {
      obj = obj->contains;
      level++;
      continue;
    } else if (!obj->next_content && obj->in_obj)
      while (obj && !obj->next_content && level >= 0) {
        obj = obj->in_obj;
        level--;
      }

    if (obj)
      obj = obj->next_content;
  }

  o = 0;
  fprintf(fl, "[BIOWAR]\n");
  for (obj = player->bioware; obj; obj = obj->next_content) {
    if (!IS_OBJ_STAT(obj, ITEM_NORENT)) {
      fprintf(fl, "\t[BIOWAR %d]\n", o);
      o++;
      fprintf(fl, "\t\tVnum:\t%ld\n", GET_OBJ_VNUM(obj));
      for (int x = 0; x < 10; x++)
        fprintf(fl, "\t\tValue %d:\t%d\n", x, GET_OBJ_VAL(obj, x));
      fprintf(fl, "\t\tCost:\t%d\n", GET_OBJ_COST(obj));
    }
  }

  o = level = 0;
  fprintf(fl, "[CYBERWAR]\n");
  for (obj = player->cyberware; obj;) {
      fprintf(fl, "\t[CYBER %d]\n", o);
      o++;
      fprintf(fl, "\t\tVnum:\t%ld\n", GET_OBJ_VNUM(obj));
      if (obj->restring)
        fprintf(fl, "\t\tName:\t%s\n", obj->restring);
      if (obj->photo)
        fprintf(fl, "\t\tPhoto:$\n%s\n~\n", cleanup(buf2, obj->photo));
      if (GET_OBJ_VAL(obj, 2) == 4) {
        fprintf(fl, "\t\tValue 3:\t%d\n", GET_OBJ_VAL(obj, 3));
        fprintf(fl, "\t\tValue 6:\t%d\n", GET_OBJ_VAL(obj, 6));
        fprintf(fl, "\t\tValue 7:\t%d\n", GET_OBJ_VAL(obj, 7));
        fprintf(fl, "\t\tValue 8:\t%d\n", GET_OBJ_VAL(obj, 8));
      } else
        for (int x = 0; x < 10; x++)
          fprintf(fl, "\t\tValue %d:\t%d\n", x, GET_OBJ_VAL(obj, x));
      fprintf(fl, "\t\tCost:\t%d\n", GET_OBJ_COST(obj));
      if (level)
        fprintf(fl, "\t\tLevel:\t%d\n", level);
    if (obj->contains) {
      obj = obj->contains;
      level++;
      continue;
    } else if (!obj->next_content && obj->in_obj)
      while (obj && !obj->next_content && level >= 0) {
        obj = obj->in_obj;
        level--;
      }

    if (obj)
      obj = obj->next_content;
  }

  fclose(fl);

  return true;
}

PCIndex::PCIndex()
{
  tab = NULL;
  entry_cnt = entry_size = 0;
  needs_save = false;
}

PCIndex::~PCIndex()
{
  reset();
}

bool PCIndex::Save()
{
  if (!needs_save)
    return true;

  FILE *index = fopen(INDEX_FILENAME, "w");

  if (!tab) {
    if (entry_cnt > 0)
      log("--Error: no table when there should be?!  That's fucked up, man.");
    return false;
  }

  if (!index) {
    log("--Error: Could not open player index file %s\n",
        INDEX_FILENAME);
    return false;
  }

  fprintf(index,
          "* Player index file\n"
          "* Generated automatically by PCIndex::Save\n\n");

  for (int i = 0; i < entry_cnt; i++) {
    entry *ptr = tab+i;

    fprintf(index,
            "%ld %d %u %lu %s\n",
            ptr->id, ptr->level, ptr->flags, ptr->last, ptr->name);
  }

  fprintf(index, "END\n");
  fclose(index);

  needs_save = false;

  return true;
}

char_data *PCIndex::CreateChar(char_data *ch)
{
  if (entry_cnt >= entry_size)
    resize_table();

  entry info;

  if (strlen(GET_CHAR_NAME(ch)) >= MAX_NAME_LENGTH) {
    log("--Fatal error: Could not fit name into player index..\n"
        "             : Inrease MAX_NAME_LENGTH");
    shutdown();
  }

  // if this is the first character, make him president
  if (entry_cnt < 1) {
    log("%s promoted to president, by virtue of first-come, first-serve.",
        GET_CHAR_NAME(ch));

    for (int i = 0; i < 3; i++)
      GET_COND(ch, i) = (char) -1;

    GET_KARMA(ch) = 0;
    GET_LEVEL(ch) = LVL_PRESIDENT;
    GET_REP(ch) = 0;
    GET_NOT(ch) = 0;
    GET_TKE(ch) = 0;

    PLR_FLAGS(ch).RemoveBit(PLR_NEWBIE);
    PLR_FLAGS(ch).RemoveBit(PLR_AUTH);
    PRF_FLAGS(ch).SetBits(PRF_HOLYLIGHT, PRF_CONNLOG, PRF_ROOMFLAGS,
                          PRF_NOHASSLE, PRF_AUTOINVIS, PRF_AUTOEXIT, ENDBIT);
  } else {
    PLR_FLAGS(ch).SetBit(PLR_AUTH);
  }
  init_char(ch);
  init_char_strings(ch);

  strcpy(info.name, GET_CHAR_NAME(ch));
  info.id = find_open_id();
  info.level = GET_LEVEL(ch);
  info.flags = (PLR_FLAGGED(ch, PLR_NODELETE) ? NODELETE : 0);
  info.active_data = ch;
  info.instance_cnt = 1;

  // sync IDs
  GET_IDNUM(ch) = info.id;

  // update the logon time
  time(&info.last);

  // insert the new info in the correct place
  sorted_insert(&info);

  if (!needs_save)
    needs_save = true;

  Save();

  return ch;
}

char_data *PCIndex::LoadChar(const char *name, bool logon)
{
  rnum_t idx = get_idx_by_name(name);

  if (idx < 0 || idx >= entry_cnt)
    return NULL;

  entry *ptr = tab+idx;

  // load the character data
  char_data *ch = Mem->GetCh();

  ch->player_specials = new player_special_data;
  memset(ch->player_specials, 0, sizeof(player_special_data));

  load_char(name, ch, logon);

  // sync IDs
  GET_IDNUM(ch) = ptr->id;

  ptr->instance_cnt++;

  if (logon) {
    ptr->active_data = NULL;
    // set the active data thing
    // ptr->active_data = ch;

    // update the last logon time
    time(&ptr->last);
  }

  return ch;
}

bool PCIndex::SaveChar(char_data *ch, vnum_t loadroom)
{
  if (IS_NPC(ch))
    return false;

  bool ret = save_char(ch, loadroom);

  Update(ch);

  return ret;
}

bool PCIndex::StoreChar(char_data *ch, bool save)
{
  bool result = true;

  if (IS_NPC(ch))
    return false;

  if (PLR_FLAGGED(ch, PLR_DELETED)) {
    playerDB.DeleteChar(GET_IDNUM(ch));
    return true;
  }
  /*  if (save && !SaveChar(ch, GET_LOADROOM(ch))) {
      char msg[256];
      sprintf(msg, "Error: could not save PC %s before storing",
              GET_CHAR_NAME(ch));
      mudlog(msg, ch, LOG_SYSLOG, true);
   
      result = false;
    }
  */
  rnum_t idx = get_idx_by_name(GET_CHAR_NAME(ch));

  update_by_idx(idx, ch);

  entry *ptr = tab+idx;

  ptr->instance_cnt--;

  //if (!ptr->instance_cnt || ptr->instance_cnt < 0)
  ptr->active_data = NULL;

  if (ch->desc)
    free_editing_structs(ch->desc);

  extract_char(ch);

  return result;
}

void PCIndex::Update(char_data *ch)
{
  rnum_t idx = get_idx_by_name(GET_CHAR_NAME(ch));
  update_by_idx(idx, ch);
}

void PCIndex::reset()
{
  if (tab != NULL) {
    delete [] tab;
    tab = NULL;
  }

  entry_cnt = entry_size = 0;
  needs_save = false;
}

bool PCIndex::load()
{
  File index(INDEX_FILENAME, "r");

  if (!index.IsOpen()) {
    log("--WARNING: Could not open player index file %s...\n"
        "         : Starting game with NO PLAYERS!!!\n",
        INDEX_FILENAME);

    FILE *test = fopen(INDEX_FILENAME, "r");
    if (test) {
      log("_could_ open it normally, tho");
      fclose(test);
    }

    return false;
  }

  reset();

  char line[512];
  int line_num = 0;
  int idx = 0;

  entry_cnt = count_entries(&index);
  resize_table();

  line_num += index.GetLine(line, 512, FALSE);
  while (!index.EoF() && strcmp(line, "END")) {
    entry *ptr = tab+idx;
    char temp[512];           // for paranoia

    if (sscanf(line, " %ld %d %u %lu %s ",
               &ptr->id, &ptr->level, &ptr->flags, &ptr->last, temp) != 5) {
      log("--Fatal error: syntax error in player index file, line %d",
          line_num);
      shutdown();
    }

    strncpy(ptr->name, temp, MAX_NAME_LENGTH);
    *(ptr->name+MAX_NAME_LENGTH-1) = '\0';

    ptr->active_data = NULL;

    line_num += index.GetLine(line, 512, FALSE);
    idx++;
  }

  if (entry_cnt != idx)
    entry_cnt = idx;

  log("--Successfully loaded %d entries from the player index file.",
      entry_cnt);

  sort_by_id();
  clear_by_time();
  return true;
}

int  PCIndex::count_entries(File *index)
{
  char line[512];
  int count = 0;

  index->GetLine(line, 512, FALSE);
  while (!index->EoF() && strcmp(line, "END")) {
    count++;
    index->GetLine(line, 512, FALSE);
  }

  index->Rewind();

  return count;
}

void PCIndex::sort_by_id()
{
  qsort(tab, entry_cnt, sizeof(entry), entry_compare);
}

void PCIndex::resize_table(int empty_slots)
{
  entry_size = entry_cnt + empty_slots;
  entry *new_tab = new entry[entry_size];

  if (tab) {
    for (int i = 0; i < entry_cnt; i++)
      new_tab[i] = tab[i];

    // fill empty slots with zeroes
    memset(new_tab+entry_cnt, 0, sizeof(entry)*(entry_size-entry_cnt));

    // delete the old table
    delete [] tab;
  } else {
    // fill entire table with zeros
    memset(new_tab, 0, sizeof(entry)*entry_size);
  }

  // finally, update the pointer
  tab = new_tab;
}

void PCIndex::sorted_insert(const entry *info)
{
  int i;

  // whee!! linear!

  // first, find the correct index
  for (i = 0; i < entry_cnt; i++)
    if (tab[i].id > info->id)
      break;

  // create an empty space for the new entry
  for (int j = entry_cnt; j > i; j--)
    tab[j] = tab[j-1];

  tab[i] = *info;

  log("--Inserting %s's (id#%d) entry into position %d",
      info->name, info->id, i);

  // update entry_cnt, and we're done
  entry_cnt++;
  needs_save = true;
}

DBIndex::vnum_t PCIndex::find_open_id()
{
  if (entry_cnt < 1)
    return 1;

  /* this won't work right now since id#s are used to store
     owners of foci, etc.  So once there's a universal id# invalidator,
     this should be put in
  */
  /*
  for (int i = 1; i < entry_cnt; i++)
    if (tab[i].id > (tab[i-1].id+1))
      return (tab[i-1].id+1);
  */

  return (tab[entry_cnt-1].id+1);
}
void PCIndex::clear_by_time()
{
  for (int i = 0; i < entry_cnt; i++) {
    entry *ptr = tab+i;
    if ((time(0) - ptr->last) > (SECS_PER_REAL_DAY*30)) {
      playerDB.DeleteChar(ptr->id);
    }
  }
}
DBIndex::rnum_t PCIndex::get_idx_by_name(const char *name)
{
  for (int i = 0; i < entry_cnt; i++) {
    entry *ptr = tab+i;

    if (!str_cmp(name, ptr->name))
      return i;
  }

  return -1;
}

DBIndex::rnum_t PCIndex::get_idx_by_id(const vnum_t virt)
{
  // binary search
  int low = 0, high = entry_cnt - 1;
  int mid = high / 2;
  while (low <= high) {
    entry *ptr = tab+mid;

    if (ptr->id == virt)
      return mid;

    if (ptr->id < virt) {
      low = mid+1;
    } else {
      high = mid-1;
    }

    mid = (low + high) / 2;
  }
  return -1;
}

DBIndex::vnum_t PCIndex::get_id_by_idx(const rnum_t idx)
{
  if (idx >= 0 && idx < entry_cnt)
    return (tab[idx].id);

  return -1;
}

const char *PCIndex::get_name_by_idx(const rnum_t idx)
{
  if (idx >= 0 && idx < entry_cnt)
    return (tab[idx].name);

  return NULL;
}

char_data *PCIndex::get_char_by_idx(const rnum_t idx)
{
  if (idx >= 0 && idx < entry_cnt)
    return tab[idx].active_data;

  return NULL;
}

bool PCIndex::delete_by_idx(rnum_t idx)
{
  entry *ptr = tab+idx;

  if (ptr->flags & NODELETE)
    return false;

  // delete the character's files
  log("PCLEAN: %s (ID#%d): level=%d, last=%s",
      ptr->name, ptr->id, ptr->level,
      asctime(localtime(&(ptr->last))));

  char bits[128];
  for (int i = 0;(*(bits + i) = LOWER(*(ptr->name + i))); i++)
    ;
  sprintf(buf, "%s%s%c%s%s%s", PLR_PREFIX, SLASH, *bits, SLASH, bits, PLR_SUFFIX);
  unlink(buf);

  for (int i = idx+1; i < entry_cnt; i++)
    tab[i-1] = tab[i];

  entry_cnt--;

  entry *zero = tab+entry_cnt;

  memset(zero->name, 0, MAX_NAME_LENGTH);
  zero->id = -1;
  zero->level = 0;
  zero->last = 0;
  zero->active_data = NULL;
  zero->flags = 0;

  if (!needs_save)
    needs_save = true;

  Save();

  return true;
}

void PCIndex::update_by_idx(rnum_t idx, char_data *ch)
{
  entry *ptr = tab+idx;
  bool changed = false;

  if (ptr->id != GET_IDNUM(ch)) {
    char msg[256];

    sprintf(msg,
            "Warning: %s's cached id didn't match up (%ld != %ld); fixing",
            GET_CHAR_NAME(ch), GET_IDNUM(ch), ptr->id);
    mudlog(msg, ch, LOG_SYSLOG, true);

    GET_IDNUM(ch) = ptr->id;
  }

  if (ptr->level != GET_LEVEL(ch)) {
    ptr->level = GET_LEVEL(ch);
    changed = true;
  }

  if ((PLR_FLAGGED(ch, PLR_NODELETE) && !(ptr->flags & NODELETE)) ||
      (!PLR_FLAGGED(ch, PLR_NODELETE) && (ptr->flags & NODELETE))) {
    ptr->flags = 0;

    if (PLR_FLAGGED(ch, PLR_NODELETE))
      ptr->flags |= NODELETE;

    changed = true;
  }

  if (changed && !needs_save)
    needs_save = true;
}