/***************************************************************************
 *  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.doc' as well the Merc       *
 *  license in 'license.txt'.  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 01/02/2007
 \version 1.5
 \remarks
  This source code copyright (C) 2005, 2006, 2007 by Jon A. Lambert
  All rights reserved.

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

/*-----------------------------------------------------------------------*/
/* OS DEPENDENT INCLUDES AND DEFINITIONS                                 */
/*-----------------------------------------------------------------------*/
#include "os.hpp"

/*-----------------------------------------------------------------------*/
/* MurkMUD++ BEGINS HERE                                                 */
/*-----------------------------------------------------------------------*/

#include "config.hpp"
#include "utils.hpp"
#include "io.hpp"
#include "database.hpp"
#include "symbols.hpp"
#include "world.hpp"

/*
 * Globals.
 */
World *g_world = NULL;
Database *g_db = NULL;
Area *area_last = NULL;
std::list<Ban *> ban_list;
std::list<Character *> char_list;
std::list<Descriptor *> descriptor_list;       /* All open descriptors     */
std::list<Note *> note_list;
std::list<Object *> object_list;
std::list<Shop *> shop_list;

// These iterators used on loops where the next iterator can be invalidated
// because of a nested method that erases an object in the list.
CharIter deepchnext, deeprmnext;
ObjIter deepobnext;
DescIter deepdenext;
//bool character_invalidated = false;  // This is set in Mprogs if we

std::map<int, MobPrototype *> mob_table;
std::map<int, ObjectPrototype *> obj_table;
std::map<int, Room *> room_table;

struct kill_data kill_table[MAX_LEVEL];

/*
 * Global variables.
 */
bool merc_down;                 /* Shutdown         */
bool wizlock;                   /* Game is wizlocked        */
std::string str_boot_time;
std::string help_greeting;
short g_port;
SOCKET g_listen;
bool extract_chars;

/*
 * The kludgy global is for spells who want more stuff from command line.
 */
std::string target_name;

std::ifstream * fpArea;
std::string strArea;

/*
 * Array of containers read for proper re-nesting of objects.
 */
Object *rgObjNest[MAX_NEST];

bool MOBtrigger;

std::string dir_name[] = {
  "north", "east", "south", "west", "up", "down"
};

sh_int rev_dir[] = {
  2, 3, 0, 1, 5, 4
};

std::string where_name[] = {
  "<used as light>     ",
  "<worn on finger>    ",
  "<worn on finger>    ",
  "<worn around neck>  ",
  "<worn around neck>  ",
  "<worn on body>      ",
  "<worn on head>      ",
  "<worn on legs>      ",
  "<worn on feet>      ",
  "<worn on hands>     ",
  "<worn on arms>      ",
  "<worn as shield>    ",
  "<worn about body>   ",
  "<worn about waist>  ",
  "<worn around wrist> ",
  "<worn around wrist> ",
  "<wielded>           ",
  "<held>              "
};

/*
 * Class table.
 */
struct class_type class_table[] = {
  { "Mag", APPLY_INT, OBJ_VNUM_SCHOOL_DAGGER,
    3018, 95, 18, 10, 6, 8, true},
  { "Cle", APPLY_WIS, OBJ_VNUM_SCHOOL_MACE,
    3003, 95, 18, 12, 7, 10, true},
  { "Thi", APPLY_DEX, OBJ_VNUM_SCHOOL_DAGGER,
    3028, 85, 18, 8, 8, 13, false},
  { "War", APPLY_STR, OBJ_VNUM_SCHOOL_SWORD,
    3022, 85, 18, 6, 11, 15, false}
};

/*
 * Attribute bonus structures.
 */
struct str_app_type str_app[26] = {
  {-5, -4, 0, 0}, {-5, -4, 3, 1}, {-3, -2, 3, 2}, {-3, -1, 10, 3},
  {-2, -1, 25, 4}, {-2, -1, 55, 5}, {-1, 0, 80, 6}, {-1, 0, 90, 7},
  {0, 0, 100, 8}, {0, 0, 100, 9}, {0, 0, 115, 10},  {0, 0, 115, 11},
  {0, 0, 140, 12}, {0, 0, 140, 13}, {0, 1, 170, 14}, {1, 1, 170, 15}, /* 15  */
  {1, 2, 195, 16}, {2, 3, 220, 22}, {2, 4, 250, 25}, {3, 5, 400, 30},
  {3, 6, 500, 35}, {4, 7, 600, 40}, {5, 7, 700, 45}, {6, 8, 800, 50},
  {8, 10, 900, 55}, {10, 12, 999, 60}      /* 25   */
};

struct int_app_type int_app[26] = {
  {3}, {5}, {7}, {8}, {9}, {10},      /*  5 */
  {11}, {12}, {13}, {15}, {17},       /* 10 */
  {19}, {22}, {25}, {28}, {31},       /* 15 */
  {34}, {37}, {40}, {44}, {49},       /* 20 */
  {55}, {60}, {70}, {85}, {99}        /* 25 */
};

struct wis_app_type wis_app[26] = {
  {0}, {0}, {0}, {0}, {0}, {1},       /*  5 */
  {1}, {1}, {1}, {2}, {2},            /* 10 */
  {2}, {2}, {2}, {2}, {3},            /* 15 */
  {3}, {4}, {4}, {5}, {5},            /* 20 */
  {6}, {7}, {7}, {7}, {8}             /* 25 */
};

struct dex_app_type dex_app[26] = {
  {60}, {50}, {50}, {40}, {30}, {20}, /* 5 */
  {10}, {0}, {0}, {0}, {0},           /* 10 */
  {0}, {0}, {0}, {0}, {-10},          /* 15 */
  {-15}, {-20}, {-30}, {-40}, {-50},  /* 20 */
  {-65}, {-75}, {-90}, {-105}, {-120} /* 25 */
};

struct con_app_type con_app[26] = {
  {-4, 20}, {-3, 25}, {-2, 30}, {-2, 35}, {-1, 40}, {-1, 45}, /*  5 */
  {-1, 50}, {0, 55}, {0, 60}, {0, 65}, {0, 70},               /* 10 */
  {0, 75}, {0, 80}, {0, 85}, {0, 88}, {1, 90},                /* 15 */
  {2, 95}, {2, 97}, {3, 99}, {3, 99}, {4, 99},                /* 20 */
  {4, 99}, {5, 99}, {6, 99}, {7, 99}, {8, 99}                 /* 25 */
};

/*
 * Liquid properties.
 */
struct liq_type liq_table[LIQ_MAX] = {
  {"water", "clear", {0, 1, 10}},       /*  0 */
  {"beer", "amber", {3, 2, 5}},
  {"wine", "rose", {5, 2, 5}},
  {"ale", "brown", {2, 2, 5}},
  {"dark ale", "dark", {1, 2, 5}},

  {"whisky", "golden", {6, 1, 4}},      /*  5 */
  {"lemonade", "pink", {0, 1, 8}},
  {"firebreather", "boiling", {10, 0, 0}},
  {"local specialty", "everclear", {3, 3, 3}},
  {"slime mold juice", "green", {0, 4, -8}},

  {"milk", "white", {0, 3, 6}}, /* 10 */
  {"tea", "tan", {0, 1, 6}},
  {"coffee", "black", {0, 1, 6}},
  {"blood", "red", {0, 2, -1}},
  {"salt water", "clear", {0, 1, -2}},

  {"cola", "cherry", {0, 1, 5}} /* 15 */
};

/* prototypes */
void multi_hit(Character *ch, Character *victim, int dt);
void damage(Character *ch, Character *victim, int dam, int dt);

bool dragon(Character *ch, const char *spell_name);

void mprog_act_trigger(const std::string & buf, Character *mob, Character *ch, Object *obj, void *vo);
void mprog_bribe_trigger(Character *mob, Character *ch, int amount);
void mprog_death_trigger(Character *mob);
void mprog_entry_trigger(Character *mob);
void mprog_fight_trigger(Character *mob, Character *ch);
void mprog_give_trigger(Character *mob, Character *ch, Object *obj);
void mprog_greet_trigger(Character *mob);
void mprog_hitprcnt_trigger(Character *mob, Character *ch);
void mprog_random_trigger(Character *mob);
void mprog_speech_trigger(char *txt, Character *mob);

bool spec_breath_any(Character *ch);
bool spec_breath_acid(Character *ch);
bool spec_breath_fire(Character *ch);
bool spec_breath_frost(Character *ch);
bool spec_breath_gas(Character *ch);
bool spec_breath_lightning(Character *ch);
bool spec_cast_adept(Character *ch);
bool spec_cast_cleric(Character *ch);
bool spec_cast_judge(Character *ch);
bool spec_cast_mage(Character *ch);
bool spec_cast_undead(Character *ch);
bool spec_executioner(Character *ch);
bool spec_fido(Character *ch);
bool spec_guard(Character *ch);
bool spec_janitor(Character *ch);
bool spec_mayor(Character *ch);
bool spec_poison(Character *ch);
bool spec_thief(Character *ch);

/* Structs */
/*
 * MOBprogram block
*/
class MobProgramActList {
public:
  MobProgramActList *next;
  std::string buf;
  Character *ch;
  Object *obj;
  void *vo;

  MobProgramActList() :
    next(NULL), ch(NULL), obj(NULL), vo(NULL) {
  }

};

class MobProgram {
public:
  MobProgram *next;
  int type;
  std::string arglist;
  std::string comlist;

  MobProgram() :
    next(NULL), type(0) {
  }

};

#include "shop.hpp"
#include "note.hpp"
#include "affect.hpp"
#include "ban.hpp"
#include "exit.hpp"
#include "reset.hpp"
#include "area.hpp"
#include "room.hpp"
#include "objproto.hpp"
#include "object.hpp"
#include "mobproto.hpp"
#include "descriptor.hpp"
#include "pcdata.hpp"
#include "extra.hpp"

struct cmd_type cmd_table[] = {
  /*
   * Common movement commands.
   */
  {"north", &Character::do_north, POS_STANDING, 0},
  {"east", &Character::do_east, POS_STANDING, 0},
  {"south", &Character::do_south, POS_STANDING, 0},
  {"west", &Character::do_west, POS_STANDING, 0},
  {"up", &Character::do_up, POS_STANDING, 0},
  {"down", &Character::do_down, POS_STANDING, 0},

  /*
   * Common other commands.
   * Placed here so one and two letter abbreviations work.
   */
  {"buy", &Character::do_buy, POS_RESTING, 0},
  {"cast", &Character::do_cast, POS_FIGHTING, 0},
  {"exits", &Character::do_exits, POS_RESTING, 0},
  {"get", &Character::do_get, POS_RESTING, 0},
  {"inventory", &Character::do_inventory, POS_DEAD, 0},
  {"kill", &Character::do_kill, POS_FIGHTING, 0},
  {"look", &Character::do_look, POS_RESTING, 0},
  {"order", &Character::do_order, POS_RESTING, 0},
  {"rest", &Character::do_rest, POS_RESTING, 0},
  {"sleep", &Character::do_sleep, POS_SLEEPING, 0},
  {"stand", &Character::do_stand, POS_SLEEPING, 0},
  {"tell", &Character::do_tell, POS_RESTING, 0},
  {"wield", &Character::do_wear, POS_RESTING, 0},
  {"wizhelp", &Character::do_wizhelp, POS_DEAD, L_HER},

  /*
   * Informational commands.
   */
  {"areas", &Character::do_areas, POS_DEAD, 0},
  {"bug", &Character::do_bug, POS_DEAD, 0},
  {"commands", &Character::do_commands, POS_DEAD, 0},
  {"compare", &Character::do_compare, POS_RESTING, 0},
  {"consider", &Character::do_consider, POS_RESTING, 0},
  {"credits", &Character::do_credits, POS_DEAD, 0},
  {"equipment", &Character::do_equipment, POS_DEAD, 0},
  {"examine", &Character::do_examine, POS_RESTING, 0},
  {"help", &Character::do_help, POS_DEAD, 0},
  {"idea", &Character::do_idea, POS_DEAD, 0},
  {"report", &Character::do_report, POS_DEAD, 0},
  {"pagelength", &Character::do_pagelen, POS_DEAD, 0},
  {"score", &Character::do_score, POS_DEAD, 0},
  {"slist", &Character::do_slist, POS_DEAD, 0},
  {"socials", &Character::do_socials, POS_DEAD, 0},
  {"time", &Character::do_time, POS_DEAD, 0},
  {"typo", &Character::do_typo, POS_DEAD, 0},
  {"weather", &Character::do_weather, POS_RESTING, 0},
  {"who", &Character::do_who, POS_DEAD, 0},
  {"wizlist", &Character::do_wizlist, POS_DEAD, 0},

  /*
   * Configuration commands.
   */
  {"auto", &Character::do_auto, POS_DEAD, 0},
  {"autoexit", &Character::do_autoexit, POS_DEAD, 0},
  {"autoloot", &Character::do_autoloot, POS_DEAD, 0},
  {"autosac", &Character::do_autosac, POS_DEAD, 0},
  {"blank", &Character::do_blank, POS_DEAD, 0},
  {"brief", &Character::do_brief, POS_DEAD, 0},
  {"channels", &Character::do_channels, POS_DEAD, 0},
  {"combine", &Character::do_combine, POS_DEAD, 0},
  {"config", &Character::do_config, POS_DEAD, 0},
  {"description", &Character::do_description, POS_DEAD, 0},
  {"password", &Character::do_password, POS_DEAD, 0},
  {"prompt", &Character::do_prompt, POS_DEAD, 0},
  {"title", &Character::do_title, POS_DEAD, 0},
  {"wimpy", &Character::do_wimpy, POS_DEAD, 0},

  /*
   * Communication commands.
   */
  {"answer", &Character::do_answer, POS_SLEEPING, 0},
  {"auction", &Character::do_auction, POS_SLEEPING, 0},
  {"chat", &Character::do_chat, POS_SLEEPING, 0},
  {".", &Character::do_chat, POS_SLEEPING, 0},
  {"emote", &Character::do_emote, POS_RESTING, 0},
  {",", &Character::do_emote, POS_RESTING, 0},
  {"gtell", &Character::do_gtell, POS_DEAD, 0},
  {";", &Character::do_gtell, POS_DEAD, 0},
  {"music", &Character::do_music, POS_SLEEPING, 0},
  {"note", &Character::do_note, POS_SLEEPING, 0},
  {"question", &Character::do_question, POS_SLEEPING, 0},
  {"reply", &Character::do_reply, POS_RESTING, 0},
  {"say", &Character::do_say, POS_RESTING, 0},
  {"'", &Character::do_say, POS_RESTING, 0},
  {"shout", &Character::do_shout, POS_RESTING, 3},
  {"yell", &Character::do_yell, POS_RESTING, 0},
  {"gag", &Character::do_gag, POS_DEAD, 0},
  {"ignore", &Character::do_gag, POS_DEAD, 0},

  /*
   * Object manipulation commands.
   */
  {"brandish", &Character::do_brandish, POS_RESTING, 0},
  {"close", &Character::do_close, POS_RESTING, 0},
  {"drink", &Character::do_drink, POS_RESTING, 0},
  {"drop", &Character::do_drop, POS_RESTING, 0},
  {"eat", &Character::do_eat, POS_RESTING, 0},
  {"fill", &Character::do_fill, POS_RESTING, 0},
  {"give", &Character::do_give, POS_RESTING, 0},
  {"hold", &Character::do_wear, POS_RESTING, 0},
  {"list", &Character::do_list, POS_RESTING, 0},
  {"lock", &Character::do_lock, POS_RESTING, 0},
  {"open", &Character::do_open, POS_RESTING, 0},
  {"pick", &Character::do_pick, POS_RESTING, 0},
  {"put", &Character::do_put, POS_RESTING, 0},
  {"quaff", &Character::do_quaff, POS_RESTING, 0},
  {"recite", &Character::do_recite, POS_RESTING, 0},
  {"remove", &Character::do_remove, POS_RESTING, 0},
  {"sell", &Character::do_sell, POS_RESTING, 0},
  {"take", &Character::do_get, POS_RESTING, 0},
  {"sacrifice", &Character::do_sacrifice, POS_RESTING, 0},
  {"unlock", &Character::do_unlock, POS_RESTING, 0},
  {"value", &Character::do_value, POS_RESTING, 0},
  {"wear", &Character::do_wear, POS_RESTING, 0},
  {"zap", &Character::do_zap, POS_RESTING, 0},

  /*
   * Combat commands.
   */
  {"backstab", &Character::do_backstab, POS_STANDING, 0},
  {"bs", &Character::do_backstab, POS_STANDING, 0},
  {"disarm", &Character::do_disarm, POS_FIGHTING, 0},
  {"flee", &Character::do_flee, POS_FIGHTING, 0},
  {"kick", &Character::do_kick, POS_FIGHTING, 0},
  {"murde", &Character::do_murde, POS_FIGHTING, 5},
  {"murder", &Character::do_murder, POS_FIGHTING, 5},
  {"rescue", &Character::do_rescue, POS_FIGHTING, 0},

  /*
   * Miscellaneous commands.
   */
  {"follow", &Character::do_follow, POS_RESTING, 0},
  {"group", &Character::do_group, POS_SLEEPING, 0},
  {"hide", &Character::do_hide, POS_RESTING, 0},
  {"practice", &Character::do_practice, POS_SLEEPING, 0},
  {"qui", &Character::do_qui, POS_DEAD, 0},
  {"quit", &Character::do_quit, POS_DEAD, 0},
  {"recall", &Character::do_recall, POS_FIGHTING, 0},
  {"/", &Character::do_recall, POS_FIGHTING, 0},
  {"rent", &Character::do_rent, POS_DEAD, 0},
  {"save", &Character::do_save, POS_DEAD, 0},
  {"sleep", &Character::do_sleep, POS_SLEEPING, 0},
  {"sneak", &Character::do_sneak, POS_STANDING, 0},
  {"spells", &Character::do_spells, POS_SLEEPING, 0},
  {"split", &Character::do_split, POS_RESTING, 0},
  {"steal", &Character::do_steal, POS_STANDING, 0},
  {"train", &Character::do_train, POS_RESTING, 0},
  {"visible", &Character::do_visible, POS_SLEEPING, 0},
  {"wake", &Character::do_wake, POS_SLEEPING, 0},
  {"where", &Character::do_where, POS_RESTING, 0},

  /*
   * Immortal commands.
   */
  {"advance", &Character::do_advance, POS_DEAD, L_GOD},
  {"trust", &Character::do_trust, POS_DEAD, L_GOD},

  {"allow", &Character::do_allow, POS_DEAD, L_SUP},
  {"ban", &Character::do_ban, POS_DEAD, L_SUP},
  {"deny", &Character::do_deny, POS_DEAD, L_SUP},
  {"disconnect", &Character::do_disconnect, POS_DEAD, L_SUP},
  {"freeze", &Character::do_freeze, POS_DEAD, L_SUP},
  {"hotboo", &Character::do_hotboo, POS_DEAD, L_SUP},
  {"hotboot", &Character::do_hotboot, POS_DEAD, L_SUP},
  {"shutdow", &Character::do_shutdow, POS_DEAD, L_SUP},
  {"shutdown", &Character::do_shutdown, POS_DEAD, L_SUP},
  {"users", &Character::do_users, POS_DEAD, L_SUP},
  {"wizify", &Character::do_wizify, POS_DEAD, L_SUP},
  {"wizlock", &Character::do_wizlock, POS_DEAD, L_SUP},

  {"force", &Character::do_force, POS_DEAD, L_DEI},
  {"mload", &Character::do_mload, POS_DEAD, L_DEI},
  {"mset", &Character::do_mset, POS_DEAD, L_DEI},
  {"noemote", &Character::do_noemote, POS_DEAD, L_DEI},
  {"notell", &Character::do_notell, POS_DEAD, L_DEI},
  {"oload", &Character::do_oload, POS_DEAD, L_DEI},
  {"oset", &Character::do_oset, POS_DEAD, L_DEI},
  {"owhere", &Character::do_owhere, POS_DEAD, L_DEI},
  {"pardon", &Character::do_pardon, POS_DEAD, L_DEI},
  {"peace", &Character::do_peace, POS_DEAD, L_DEI},
  {"purge", &Character::do_purge, POS_DEAD, L_DEI},
  {"restore", &Character::do_restore, POS_DEAD, L_DEI},
  {"rset", &Character::do_rset, POS_DEAD, L_DEI},
  {"silence", &Character::do_silence, POS_DEAD, L_DEI},
  {"sla", &Character::do_sla, POS_DEAD, L_DEI},
  {"slay", &Character::do_slay, POS_DEAD, L_DEI},
  {"sset", &Character::do_sset, POS_DEAD, L_DEI},
  {"transfer", &Character::do_transfer, POS_DEAD, L_DEI},
  {"mpstat", &Character::do_mpstat, POS_DEAD, L_DEI},

  {"at", &Character::do_at, POS_DEAD, L_ANG},
  {"bamfin", &Character::do_bamfin, POS_DEAD, L_ANG},
  {"bamfout", &Character::do_bamfout, POS_DEAD, L_ANG},
  {"echo", &Character::do_echo, POS_DEAD, L_ANG},
  {"goto", &Character::do_goto, POS_DEAD, L_ANG},
  {"holylight", &Character::do_holylight, POS_DEAD, L_ANG},
  {"memory", &Character::do_memory, POS_DEAD, L_ANG},
  {"mfind", &Character::do_mfind, POS_DEAD, L_ANG},
  {"mstat", &Character::do_mstat, POS_DEAD, L_ANG},
  {"mwhere", &Character::do_mwhere, POS_DEAD, L_ANG},
  {"ofind", &Character::do_ofind, POS_DEAD, L_ANG},
  {"ostat", &Character::do_ostat, POS_DEAD, L_ANG},
  {"recho", &Character::do_recho, POS_DEAD, L_ANG},
  {"return", &Character::do_return, POS_DEAD, L_ANG},
  {"rstat", &Character::do_rstat, POS_DEAD, L_ANG},
  {"slookup", &Character::do_slookup, POS_DEAD, L_ANG},
  {"switch", &Character::do_switch, POS_DEAD, L_ANG},

  {"immtalk", &Character::do_immtalk, POS_DEAD, L_ANG},
  {":", &Character::do_immtalk, POS_DEAD, L_ANG},

  /*
   * MOBprogram commands.
   */
  {"mpasound", &Character::do_mpasound, POS_DEAD, 41},
  {"mpjunk", &Character::do_mpjunk, POS_DEAD, 41},
  {"mpecho", &Character::do_mpecho, POS_DEAD, 41},
  {"mpechoat", &Character::do_mpechoat, POS_DEAD, 41},
  {"mpechoaround", &Character::do_mpechoaround, POS_DEAD, 41},
  {"mpkill", &Character::do_mpkill, POS_DEAD, 41},
  {"mpmload", &Character::do_mpmload, POS_DEAD, 41},
  {"mpoload", &Character::do_mpoload, POS_DEAD, 41},
  {"mppurge", &Character::do_mppurge, POS_DEAD, 41},
  {"mpgoto", &Character::do_mpgoto, POS_DEAD, 41},
  {"mpat", &Character::do_mpat, POS_DEAD, 41},
  {"mptransfer", &Character::do_mptransfer, POS_DEAD, 41},
  {"mpforce", &Character::do_mpforce, POS_DEAD, 41},

  /*
   * End of list.
   */
  {"", 0, POS_DEAD, 0}
};

/*
 * The skill and spell table.
 * Slot numbers must never be changed as they appear in #OBJECTS sections.
 * Skills include spells as a particular case.
 */
struct skill_type skill_table[MAX_SKILL] = {

/*
 * Magic spells.
 */

  {   "reserved", {99, 99, 99, 99},
      NULL, TAR_IGNORE, POS_STANDING,
      0, 0, "", ""},
  {   "acid blast", {20, 37, 37, 37},
      &Character::spell_acid_blast, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      20, 12, "acid blast", "!Acid Blast!"},
  {   "armor", {5, 1, 37, 37},
      &Character::spell_armor, TAR_CHAR_DEFENSIVE, POS_STANDING,
      5, 12, "", "You feel less protected."},
  {   "bless", {37, 5, 37, 37},
      &Character::spell_bless, TAR_CHAR_DEFENSIVE, POS_STANDING,
      5, 12, "", "You feel less righteous."},
  {   "blindness", {8, 5, 37, 37},
      &Character::spell_blindness, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      5, 12, "", "You can see again."},
  {   "burning hands", {5, 37, 37, 37},
      &Character::spell_burning_hands, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "burning hands", "!Burning Hands!"},
  {   "call lightning", {37, 12, 37, 37},
      &Character::spell_call_lightning, TAR_IGNORE, POS_FIGHTING,
      15, 12, "lightning bolt", "!Call Lightning!"},
  {   "cause critical", {37, 9, 37, 37},
      &Character::spell_cause_critical, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      20, 12, "spell", "!Cause Critical!"},
  {   "cause light", {37, 1, 37, 37},
      &Character::spell_cause_light, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "spell", "!Cause Light!"},
  {   "cause serious", {37, 5, 37, 37},
      &Character::spell_cause_serious, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      17, 12, "spell", "!Cause Serious!"},
  {   "change sex", {37, 37, 37, 37},
      &Character::spell_change_sex, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      15, 12, "", "Your body feels familiar again."},
  {   "charm person", {14, 37, 37, 37},
      &Character::spell_charm_person, TAR_CHAR_OFFENSIVE, POS_STANDING,
      5, 12, "", "You feel more self-confident."},
  {   "chill touch", {3, 37, 37, 37},
      &Character::spell_chill_touch, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "chilling touch", "You feel less cold."},
  {   "colour spray", {11, 37, 37, 37},
      &Character::spell_colour_spray, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "colour spray", "!Colour Spray!"},
  {   "continual light", {4, 2, 37, 37},
      &Character::spell_continual_light, TAR_IGNORE, POS_STANDING,
      7, 12, "", "!Continual Light!"},
  {   "control weather", {10, 13, 37, 37},
      &Character::spell_control_weather, TAR_IGNORE, POS_STANDING,
      25, 12, "", "!Control Weather!"},
  {   "create food", {37, 3, 37, 37},
      &Character::spell_create_food, TAR_IGNORE, POS_STANDING,
      5, 12, "", "!Create Food!"},
  {   "create spring", {10, 37, 37, 37},
      &Character::spell_create_spring, TAR_IGNORE, POS_STANDING,
      20, 12, "", "!Create Spring!"},
  {   "create water", {37, 2, 37, 37},
      &Character::spell_create_water, TAR_OBJ_INV, POS_STANDING,
      5, 12, "", "!Create Water!"},
  {   "cure blindness", {37, 4, 37, 37},
      &Character::spell_cure_blindness, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      5, 12, "", "!Cure Blindness!"},
  {   "cure critical", {37, 9, 37, 37},
      &Character::spell_cure_critical, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      20, 12, "", "!Cure Critical!"},
  {   "cure light", {37, 1, 37, 37},
      &Character::spell_cure_light, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      10, 12, "", "!Cure Light!"},
  {   "cure poison", {37, 9, 37, 37},
      &Character::spell_cure_poison, TAR_CHAR_DEFENSIVE, POS_STANDING,
      5, 12, "", "!Cure Poison!"},
  {   "cure serious", {37, 5, 37, 37},
      &Character::spell_cure_serious, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      15, 12, "", "!Cure Serious!"},
  {   "curse", {12, 12, 37, 37},
      &Character::spell_curse, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      20, 12, "curse", "The curse wears off."},
  {   "detect evil", {37, 4, 37, 37},
      &Character::spell_detect_evil, TAR_CHAR_SELF, POS_STANDING,
      5, 12, "", "The red in your vision disappears."},
  {   "detect hidden", {37, 7, 37, 37},
      &Character::spell_detect_hidden, TAR_CHAR_SELF, POS_STANDING,
      5, 12, "", "You feel less aware of your suroundings."},
  {   "detect invis", {2, 5, 37, 37},
      &Character::spell_detect_invis, TAR_CHAR_SELF, POS_STANDING,
      5, 12, "", "You no longer see invisible objects."},
  {   "detect magic", {2, 3, 37, 37},
      &Character::spell_detect_magic, TAR_CHAR_SELF, POS_STANDING,
      5, 12, "", "The detect magic wears off."},
  {   "detect poison", {37, 5, 37, 37},
      &Character::spell_detect_poison, TAR_OBJ_INV, POS_STANDING,
      5, 12, "", "!Detect Poison!"},
  {   "dispel evil", {37, 10, 37, 37},
      &Character::spell_dispel_evil, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "dispel evil", "!Dispel Evil!"},
  {   "dispel magic", {26, 31, 37, 37},
      &Character::spell_dispel_magic, TAR_CHAR_OFFENSIVE, POS_STANDING,
      15, 12, "", "!Dispel Magic!"},
  {   "earthquake", {37, 7, 37, 37},
      &Character::spell_earthquake, TAR_IGNORE, POS_FIGHTING,
      15, 12, "earthquake", "!Earthquake!"},
  {   "enchant weapon", {12, 37, 37, 37},
      &Character::spell_enchant_weapon, TAR_OBJ_INV, POS_STANDING,
      100, 24, "", "!Enchant Weapon!"},
  {   "energy drain", {13, 37, 37, 37},
      &Character::spell_energy_drain, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      35, 12, "energy drain", "!Energy Drain!"},
  {   "faerie fire", {4, 2, 37, 37},
      &Character::spell_faerie_fire, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      5, 12, "faerie fire", "The pink aura around you fades away."},
  {   "faerie fog", {10, 14, 37, 37},
      &Character::spell_faerie_fog, TAR_IGNORE, POS_STANDING,
      12, 12, "faerie fog", "!Faerie Fog!"},
  {   "fireball", {15, 37, 37, 37},
      &Character::spell_fireball, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "fireball", "!Fireball!"},
  {   "flamestrike", {37, 13, 37, 37},
      &Character::spell_flamestrike, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      20, 12, "flamestrike", "!Flamestrike!"},
  {   "fly", {7, 12, 37, 37},
      &Character::spell_fly, TAR_CHAR_DEFENSIVE, POS_STANDING,
      10, 18, "", "You slowly float to the ground."},
  {   "gate", {37, 37, 37, 37},
      &Character::spell_gate, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      50, 12, "", "!Gate!"},
  {   "giant strength", {7, 37, 37, 37},
      &Character::spell_giant_strength, TAR_CHAR_DEFENSIVE, POS_STANDING,
      20, 12, "", "You feel weaker."},
  {   "harm", {37, 15, 37, 37},
      &Character::spell_harm, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      35, 12, "harm spell", "!Harm!"},
  {   "heal", {37, 14, 37, 37},
      &Character::spell_heal, TAR_CHAR_DEFENSIVE, POS_FIGHTING,
      50, 12, "", "!Heal!"},
  {   "identify", {10, 10, 37, 37},
      &Character::spell_identify, TAR_OBJ_INV, POS_STANDING,
      12, 24, "", "!Identify!"},
  {   "infravision", {6, 9, 37, 37},
      &Character::spell_infravision, TAR_CHAR_DEFENSIVE, POS_STANDING,
      5, 18, "", "You no longer see in the dark."},
  {   "invis", {4, 37, 37, 37},
      &Character::spell_invis, TAR_CHAR_DEFENSIVE, POS_STANDING,
      5, 12, "", "You are no longer invisible."},
  {   "know alignment", {8, 5, 37, 37},
      &Character::spell_know_alignment, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      9, 12, "", "!Know Alignment!"},
  {   "lightning bolt", {9, 37, 37, 37},
      &Character::spell_lightning_bolt, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "lightning bolt", "!Lightning Bolt!"},
  {   "locate object", {6, 10, 37, 37},
      &Character::spell_locate_object, TAR_IGNORE, POS_STANDING,
      20, 18, "", "!Locate Object!"},
  {   "magic missile", {1, 37, 37, 37},
      &Character::spell_magic_missile, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "magic missile", "!Magic Missile!"},
  {   "mass invis", {15, 17, 37, 37},
      &Character::spell_mass_invis, TAR_IGNORE, POS_STANDING,
      20, 24, "", "!Mass Invis!"},
  {   "pass door", {18, 37, 37, 37},
      &Character::spell_pass_door, TAR_CHAR_SELF, POS_STANDING,
      20, 12, "", "You feel solid again."},
  {   "poison", {37, 8, 37, 37},
      &Character::spell_poison, TAR_CHAR_OFFENSIVE, POS_STANDING,
      10, 12, "poison", "You feel less sick."},
  {   "protection", {37, 6, 37, 37},
      &Character::spell_protection, TAR_CHAR_SELF, POS_STANDING,
      5, 12, "", "You feel less protected."},
  {   "refresh", {5, 3, 37, 37},
      &Character::spell_refresh, TAR_CHAR_DEFENSIVE, POS_STANDING,
      12, 18, "refresh", "!Refresh!"},
  {   "remove curse", {37, 12, 37, 37},
      &Character::spell_remove_curse, TAR_CHAR_DEFENSIVE, POS_STANDING,
      5, 12, "", "!Remove Curse!"},
  {   "sanctuary", {37, 13, 37, 37},
      &Character::spell_sanctuary, TAR_CHAR_DEFENSIVE, POS_STANDING,
      75, 12, "", "The white aura around your body fades."},
  {   "shield", {13, 37, 37, 37},
      &Character::spell_shield, TAR_CHAR_DEFENSIVE, POS_STANDING,
      12, 18, "", "Your force shield shimmers then fades away."},
  {   "shocking grasp", {7, 37, 37, 37},
      &Character::spell_shocking_grasp, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      15, 12, "shocking grasp", "!Shocking Grasp!"},
  {   "sleep", {14, 37, 37, 37},
      &Character::spell_sleep, TAR_CHAR_OFFENSIVE, POS_STANDING,
      15, 12, "", "You feel less tired."},
  {   "stone skin", {17, 37, 37, 37},
      &Character::spell_stone_skin, TAR_CHAR_SELF, POS_STANDING,
      12, 18, "", "Your skin feels soft again."},
  {   "summon", {37, 8, 37, 37},
      &Character::spell_summon, TAR_IGNORE, POS_STANDING,
      50, 12, "", "!Summon!"},
  {   "teleport", {8, 37, 37, 37},
      &Character::spell_teleport, TAR_CHAR_SELF, POS_FIGHTING,
      35, 12, "", "!Teleport!"},
  {   "ventriloquate", {1, 37, 37, 37},
      &Character::spell_ventriloquate, TAR_IGNORE, POS_STANDING,
      5, 12, "", "!Ventriloquate!"},
  {   "weaken", {7, 37, 37, 37},
      &Character::spell_weaken, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      20, 12, "spell", "You feel stronger."},
  {   "word of recall", {37, 37, 37, 37},
      &Character::spell_word_of_recall, TAR_CHAR_SELF, POS_RESTING,
      5, 12, "", "!Word of Recall!"},
/*
 * Dragon breath
 */
  {   "acid breath", {33, 37, 37, 37},
      &Character::spell_acid_breath, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 4, "blast of acid", "!Acid Breath!"},
  {   "fire breath", {34, 37, 37, 37},
      &Character::spell_fire_breath, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 4, "blast of flame", "!Fire Breath!"},
  {   "frost breath", {31, 37, 37, 37},
      &Character::spell_frost_breath, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 4, "blast of frost", "!Frost Breath!"},
  {   "gas breath", {35, 37, 37, 37},
      &Character::spell_gas_breath, TAR_IGNORE, POS_FIGHTING,
      0, 4, "blast of gas", "!Gas Breath!"},
  {   "lightning breath", {32, 37, 37, 37},
      &Character::spell_lightning_breath, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 4, "blast of lightning", "!Lightning Breath!"},
/*
 * Fighter and thief skills.
 */
  {   "backstab", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_STANDING,
      0, 24, "backstab", "!Backstab!"},
  {   "disarm", {37, 37, 10, 37},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 24, "", "!Disarm!"},
  {   "dodge", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 0, "", "!Dodge!"},
  {   "enhanced damage", {37, 37, 37, 1},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 0, "", "!Enhanced Damage!"},
  {   "hide", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_RESTING,
      0, 12, "", "!Hide!"},
  {   "kick", {37, 37, 37, 1},
      &Character::spell_null, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 8, "kick", "!Kick!"},
  {   "parry", {37, 37, 37, 1},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 0, "", "!Parry!"},
  {   "peek", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_STANDING,
      0, 0, "", "!Peek!"},
  {   "pick lock", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_STANDING,
      0, 12, "", "!Pick!"},
  {   "rescue", {37, 37, 37, 1},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 12, "", "!Rescue!"},
  {   "second attack", {37, 37, 1, 1},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 0, "", "!Second Attack!"},
  {   "sneak", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_STANDING,
      0, 12, "", NULL},
  {   "steal", {37, 37, 1, 37},
      &Character::spell_null, TAR_IGNORE, POS_STANDING,
      0, 24, "", "!Steal!"},
  {   "third attack", {37, 37, 37, 1},
      &Character::spell_null, TAR_IGNORE, POS_FIGHTING,
      0, 0, "", "!Third Attack!"},
/*
 *  Spells for mega1.are from Glop/Erkenbrand.
*/
  {   "general purpose", {37, 37, 37, 37},
      &Character::spell_general_purpose, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 12, "general purpose ammo", "!General Purpose Ammo!"},
  {   "high explosive", {37, 37, 37, 37},
      &Character::spell_high_explosive, TAR_CHAR_OFFENSIVE, POS_FIGHTING,
      0, 12, "high explosive ammo", "!High Explosive Ammo!"}
};

///////////////////
// start of code //
///////////////////

/*
 * Lowest level output function.
 * Write a block of text to the file descriptor.
 * If this gives errors on very long blocks (like 'ofind all'),
 *   try lowering the max block size.
 */
bool write_to_descriptor (SOCKET desc, const char *txt, int length)
{
  int iStart;
  int nWrite;
  int nBlock;

  if (length <= 0)
    length = strlen (txt);

  for (iStart = 0; iStart < length; iStart += nWrite) {
    nBlock = std::min (length - iStart, 4096);
    if ((nWrite = send (desc, txt + iStart, nBlock, 0)) == SOCKET_ERROR) {
      std::perror ("Write_to_descriptor");
      return false;
    }
  }

  return true;
}

/*
 * Return ascii name of an affect location.
 */
std::string affect_loc_name (int location)
{
  switch (location) {
  case APPLY_NONE:
    return "none";
  case APPLY_STR:
    return "strength";
  case APPLY_DEX:
    return "dexterity";
  case APPLY_INT:
    return "intelligence";
  case APPLY_WIS:
    return "wisdom";
  case APPLY_CON:
    return "constitution";
  case APPLY_SEX:
    return "sex";
  case APPLY_CLASS:
    return "class";
  case APPLY_LEVEL:
    return "level";
  case APPLY_AGE:
    return "age";
  case APPLY_MANA:
    return "mana";
  case APPLY_HIT:
    return "hp";
  case APPLY_MOVE:
    return "moves";
  case APPLY_GOLD:
    return "gold";
  case APPLY_EXP:
    return "experience";
  case APPLY_AC:
    return "armor class";
  case APPLY_HITROLL:
    return "hit roll";
  case APPLY_DAMROLL:
    return "damage roll";
  case APPLY_SAVING_PARA:
    return "save vs paralysis";
  case APPLY_SAVING_ROD:
    return "save vs rod";
  case APPLY_SAVING_PETRI:
    return "save vs petrification";
  case APPLY_SAVING_BREATH:
    return "save vs breath";
  case APPLY_SAVING_SPELL:
    return "save vs spell";
  }

  bug_printf ("Affect_location_name: unknown location %d.", location);
  return "(unknown)";
}

/*
 * Return ascii name of an affect bit vector.
 */
std::string affect_bit_name (int vector)
{
  std::string buf;

  if (vector & AFF_BLIND)
    buf.append(" blind");
  if (vector & AFF_INVISIBLE)
    buf.append(" invisible");
  if (vector & AFF_DETECT_EVIL)
    buf.append(" detect_evil");
  if (vector & AFF_DETECT_INVIS)
    buf.append(" detect_invis");
  if (vector & AFF_DETECT_MAGIC)
    buf.append(" detect_magic");
  if (vector & AFF_DETECT_HIDDEN)
    buf.append(" detect_hidden");
  if (vector & AFF_SANCTUARY)
    buf.append(" sanctuary");
  if (vector & AFF_FAERIE_FIRE)
    buf.append(" faerie_fire");
  if (vector & AFF_INFRARED)
    buf.append(" infrared");
  if (vector & AFF_CURSE)
    buf.append(" curse");
  if (vector & AFF_POISON)
    buf.append(" poison");
  if (vector & AFF_PROTECT)
    buf.append(" protect");
  if (vector & AFF_SLEEP)
    buf.append(" sleep");
  if (vector & AFF_SNEAK)
    buf.append(" sneak");
  if (vector & AFF_HIDE)
    buf.append(" hide");
  if (vector & AFF_CHARM)
    buf.append(" charm");
  if (vector & AFF_FLYING)
    buf.append(" flying");
  if (vector & AFF_PASS_DOOR)
    buf.append(" pass_door");
  if (buf.empty())
    buf.append("none");
  else
    buf.erase(0,1);
  return buf;
}

/*
 * Return ascii name of extra flags vector.
 */
std::string extra_bit_name (int extra_flags)
{
  std::string buf;

  if (extra_flags & ITEM_GLOW)
    buf.append(" glow");
  if (extra_flags & ITEM_HUM)
    buf.append(" hum");
  if (extra_flags & ITEM_DARK)
    buf.append(" dark");
  if (extra_flags & ITEM_LOCK)
    buf.append(" lock");
  if (extra_flags & ITEM_EVIL)
    buf.append(" evil");
  if (extra_flags & ITEM_INVIS)
    buf.append(" invis");
  if (extra_flags & ITEM_MAGIC)
    buf.append(" magic");
  if (extra_flags & ITEM_NODROP)
    buf.append(" nodrop");
  if (extra_flags & ITEM_BLESS)
    buf.append(" bless");
  if (extra_flags & ITEM_ANTI_GOOD)
    buf.append(" anti-good");
  if (extra_flags & ITEM_ANTI_EVIL)
    buf.append(" anti-evil");
  if (extra_flags & ITEM_ANTI_NEUTRAL)
    buf.append(" anti-neutral");
  if (extra_flags & ITEM_NOREMOVE)
    buf.append(" noremove");
  if (extra_flags & ITEM_INVENTORY)
    buf.append(" inventory");
  if (buf.empty())
    buf.append("none");
  else
    buf.erase(0,1);
  return buf;
}

/*
 * Lookup a skill by name.
 */
int skill_lookup (const std::string & name)
{
  int sn;

  for (sn = 0; sn < MAX_SKILL; sn++) {
    if (skill_table[sn].name == NULL)
      break;
    if (tolower(name[0]) == tolower(skill_table[sn].name[0])
      && !str_prefix (name, skill_table[sn].name))
      return sn;
  }

  return -1;
}

/*
 * Translates mob virtual number to its mob index struct.
 * Hash table lookup.
 */
MobPrototype *get_mob_index (int vnum)
{
  std::map<int,MobPrototype*>::iterator pMobIndex;

  pMobIndex = mob_table.find(vnum);

  if (pMobIndex != mob_table.end())
      return (*pMobIndex).second;

  if (g_db->fBootDb) {
    fatal_printf ("Get_mob_index: bad vnum %d.", vnum);
  }

  return NULL;
}

/*
 * Translates mob virtual number to its obj index struct.
 * Hash table lookup.
 */
ObjectPrototype *get_obj_index (int vnum)
{
  std::map<int,ObjectPrototype*>::iterator pObjIndex;

  pObjIndex = obj_table.find(vnum);

  if (pObjIndex != obj_table.end())
      return (*pObjIndex).second;

  if (g_db->fBootDb) {
    fatal_printf ("Get_obj_index: bad vnum %d.", vnum);
  }

  return NULL;
}

/*
 * Translates mob virtual number to its room index struct.
 * Hash table lookup.
 */
Room *get_room_index (int vnum)
{
  std::map<int,Room*>::iterator pRoomIndex;

  pRoomIndex = room_table.find(vnum);

  if (pRoomIndex != room_table.end())
      return (*pRoomIndex).second;

  if (g_db->fBootDb) {
    fatal_printf ("Get_room_index: bad vnum %d.", vnum);
  }

  return NULL;
}

std::string get_title (int klass, int level, int sex)
{
  sqlite3_stmt *stmt = NULL;
  char *sql = sqlite3_mprintf(
    "SELECT title FROM titles WHERE class = %d AND level = %d and sex = %d",
    klass, level, sex == SEX_FEMALE ? 1 : 0);
  std::string str("");
  Database* db = Database::instance();

  if (sqlite3_prepare(db->database, sql, -1, &stmt, 0) != SQLITE_OK) {
    bug_printf("Could not prepare statement: %s", sqlite3_errmsg(db->database));
    sqlite3_free(sql);
    return str;
  }

  if (sqlite3_step(stmt) == SQLITE_ROW) {
    str.assign((const char*)sqlite3_column_text( stmt, 0 ));
  }

  sqlite3_finalize(stmt);
  sqlite3_free(sql);
  return str;
}

/*
 * Get an extra description from a list.
 */
std::string get_extra_descr (const std::string & name, std::list<ExtraDescription *> & ed)
{
  std::list<ExtraDescription *>::iterator e;
  for (e = ed.begin(); e != ed.end(); e++) {
    if (is_name (name, (*e)->keyword))
      return (*e)->description;
  }
  return "";
}

/*
 * Given a name, return the appropriate spec fun.
 */
SPEC_FUN *spec_lookup (const std::string & name)
{
  if (!str_cmp (name, "spec_breath_any"))
    return spec_breath_any;
  if (!str_cmp (name, "spec_breath_acid"))
    return spec_breath_acid;
  if (!str_cmp (name, "spec_breath_fire"))
    return spec_breath_fire;
  if (!str_cmp (name, "spec_breath_frost"))
    return spec_breath_frost;
  if (!str_cmp (name, "spec_breath_gas"))
    return spec_breath_gas;
  if (!str_cmp (name, "spec_breath_lightning"))
    return spec_breath_lightning;
  if (!str_cmp (name, "spec_cast_adept"))
    return spec_cast_adept;
  if (!str_cmp (name, "spec_cast_cleric"))
    return spec_cast_cleric;
  if (!str_cmp (name, "spec_cast_judge"))
    return spec_cast_judge;
  if (!str_cmp (name, "spec_cast_mage"))
    return spec_cast_mage;
  if (!str_cmp (name, "spec_cast_undead"))
    return spec_cast_undead;
  if (!str_cmp (name, "spec_executioner"))
    return spec_executioner;
  if (!str_cmp (name, "spec_fido"))
    return spec_fido;
  if (!str_cmp (name, "spec_guard"))
    return spec_guard;
  if (!str_cmp (name, "spec_janitor"))
    return spec_janitor;
  if (!str_cmp (name, "spec_mayor"))
    return spec_mayor;
  if (!str_cmp (name, "spec_poison"))
    return spec_poison;
  if (!str_cmp (name, "spec_thief"))
    return spec_thief;
  return 0;
}

/*
 * MOBprogram code block
*/
/* the functions */

/* This routine transfers between alpha and numeric forms of the
 *  mob_prog bitvector types. This allows the use of the words in the
 *  mob/script files.
 */
int mprog_name_to_type (const std::string & name)
{
  if (!str_cmp (name, "in_file_prog"))
    return IN_FILE_PROG;
  if (!str_cmp (name, "act_prog"))
    return ACT_PROG;
  if (!str_cmp (name, "speech_prog"))
    return SPEECH_PROG;
  if (!str_cmp (name, "rand_prog"))
    return RAND_PROG;
  if (!str_cmp (name, "fight_prog"))
    return FIGHT_PROG;
  if (!str_cmp (name, "hitprcnt_prog"))
    return HITPRCNT_PROG;
  if (!str_cmp (name, "death_prog"))
    return DEATH_PROG;
  if (!str_cmp (name, "entry_prog"))
    return ENTRY_PROG;
  if (!str_cmp (name, "greet_prog"))
    return GREET_PROG;
  if (!str_cmp (name, "all_greet_prog"))
    return ALL_GREET_PROG;
  if (!str_cmp (name, "give_prog"))
    return GIVE_PROG;
  if (!str_cmp (name, "bribe_prog"))
    return BRIBE_PROG;
  return (ERROR_PROG);
}

/* This routine reads in scripts of MOBprograms from a file */
MobProgram *mprog_file_read (const std::string & f, MobProgram *mprg, MobPrototype *pMobIndex)
{
  MobProgram *mprg2;
  std::ifstream progfile;
  bool done = false;
  char MOBProgfile[MAX_INPUT_LENGTH];

  snprintf (MOBProgfile, sizeof MOBProgfile, "%s%s", MOB_DIR, f.c_str());
  progfile.open (MOBProgfile, std::ifstream::in | std::ifstream::binary);
  if (!progfile.is_open()) {
    fatal_printf ("Mob:%d couldnt open mobprog file", pMobIndex->vnum);
  }
  mprg2 = mprg;
  switch (fread_letter (progfile)) {
  case '>':
    break;
  case '|':
    fatal_printf ("empty mobprog file.");
    break;
  default:
    fatal_printf ("in mobprog file syntax error.");
    break;
  }
  while (!done) {
    mprg2->type = mprog_name_to_type (fread_word (progfile));
    switch (mprg2->type) {
    case ERROR_PROG:
      fatal_printf ("mobprog file type error");
      break;
    case IN_FILE_PROG:
      fatal_printf ("mprog file contains a call to file.");
      break;
    default:
      pMobIndex->progtypes = pMobIndex->progtypes | mprg2->type;
      mprg2->arglist = fread_string (progfile);
      mprg2->comlist = fread_string (progfile);
      switch (fread_letter (progfile)) {
      case '>':
        mprg2->next = new MobProgram();
        mprg2 = mprg2->next;
        mprg2->next = NULL;
        break;
      case '|':
        done = true;
        break;
      default:
        fatal_printf ("in mobprog file syntax error.");
        break;
      }
      break;
    }
  }
  progfile.close();
  return mprg2;
}

/* This procedure is responsible for reading any in_file MOBprograms.
 */
void mprog_read_programs (std::ifstream & fp, MobPrototype *pMobIndex)
{
  MobProgram *mprg;
  bool done = false;

  if ((fread_letter (fp)) != '>') {
    fatal_printf ("Load_mobiles: vnum %d MOBPROG char", pMobIndex->vnum);
  }
  pMobIndex->mobprogs = new MobProgram();
  mprg = pMobIndex->mobprogs;
  while (!done) {
    mprg->type = mprog_name_to_type (fread_word (fp));
    switch (mprg->type) {
    case ERROR_PROG:
      fatal_printf ("Load_mobiles: vnum %d MOBPROG type.", pMobIndex->vnum);
      break;
    case IN_FILE_PROG:
      mprg = mprog_file_read (fread_string (fp), mprg, pMobIndex);
      fread_to_eol (fp);
      switch (fread_letter (fp)) {
      case '>':
        mprg->next = new MobProgram();
        mprg = mprg->next;
        mprg->next = NULL;
        break;
      case '|':
        mprg->next = NULL;
        fread_to_eol (fp);
        done = true;
        break;
      default:
        fatal_printf ("Load_mobiles: vnum %d bad MOBPROG.", pMobIndex->vnum);
        break;
      }
      break;
    default:
      pMobIndex->progtypes = pMobIndex->progtypes | mprg->type;
      mprg->arglist = fread_string (fp);
      fread_to_eol (fp);
      mprg->comlist = fread_string (fp);
      fread_to_eol (fp);
      switch (fread_letter (fp)) {
      case '>':
        mprg->next = new MobProgram();
        mprg = mprg->next;
        mprg->next = NULL;
        break;
      case '|':
        mprg->next = NULL;
        fread_to_eol (fp);
        done = true;
        break;
      default:
        fatal_printf ("Load_mobiles: vnum %d bad MOBPROG.", pMobIndex->vnum);
        break;
      }
      break;
    }
  }
}

/*
 * Create a 'money' obj.
 */
Object *create_money (int amount)
{
  char buf[MAX_STRING_LENGTH];
  Object *obj;

  if (amount <= 0) {
    bug_printf ("Create_money: zero or negative money %d.", amount);
    amount = 1;
  }

  if (amount == 1) {
    obj = get_obj_index(OBJ_VNUM_MONEY_ONE)->create_object(0);
  } else {
    obj = get_obj_index(OBJ_VNUM_MONEY_SOME)->create_object(0);
    snprintf (buf, sizeof buf, obj->short_descr.c_str(), amount);
    obj->short_descr = buf;
    obj->value[0] = amount;
  }

  return obj;
}

/*
 * It is very important that this be an equivalence relation:
 * (1) A ~ A
 * (2) if A ~ B then B ~ A
 * (3) if A ~ B  and B ~ C, then A ~ C
 */
bool is_same_group (Character * ach, Character * bch)
{
  if (ach->leader != NULL)
    ach = ach->leader;
  if (bch->leader != NULL)
    bch = bch->leader;
  return ach == bch;
}

bool Note::is_note_to (Character * ch)
{
  if (!str_cmp (ch->name, sender))
    return true;

  if (is_name ("all", to_list))
    return true;

  if (ch->is_hero() && is_name ("immortal", to_list))
    return true;

  if (is_name (ch->name, to_list))
    return true;

  return false;
}

void note_attach (Character * ch)
{
  Note *pnote;

  if (ch->pnote != NULL)
    return;

  pnote = new Note();

  pnote->sender = ch->name;
  ch->pnote = pnote;
  return;
}

void note_remove (Character * ch, Note * pnote)
{
  std::string to_new, to_one, to_list;

  /*
   * Build a new to_list.
   * Strip out this recipient.
   */
  to_list = pnote->to_list;
  while (!to_list.empty()) {
    to_list = one_argument (to_list, to_one);
    if (!to_list.empty() && str_cmp (ch->name, to_one)) {
      to_new.append(" ");
      to_new.append(to_one);
    }
  }

  /*
   * Just a simple recipient removal?
   */
  if (str_cmp (ch->name, pnote->sender) && !to_new.empty()) {
    pnote->to_list = to_new.substr(1);
    return;
  }

  /*
   * Remove note from linked list.
   */
  note_list.erase(std::find(note_list.begin(),note_list.end(),pnote));
  delete pnote;

  /*
   * Rewrite entire list.
   */
  std::ofstream notefile;

  notefile.open (NOTE_FILE, std::ofstream::out | std::ofstream::binary);
  if (!notefile.is_open()) {
    std::perror (NOTE_FILE);
  } else {
    for (std::list<Note*>::iterator p = note_list.begin();
      p != note_list.end(); p++) {
      notefile << "Sender  " << (*p)->sender << "~\n";
      notefile << "Date    " << (*p)->date << "~\n";
      notefile << "Stamp   " << (*p)->date_stamp << "\n";
      notefile << "To      " << (*p)->to_list << "~\n";
      notefile << "Subject " << (*p)->subject << "~\n";
      notefile << "Text\n" << (*p)->text << "~\n\n";
    }
    notefile.close();
  }
  return;
}

/* This routine handles the variables for command expansion.
 * If you want to add any go right ahead, it should be fairly
 * clear how it is done and they are quite easy to do, so you
 * can be as creative as you want. The only catch is to check
 * that your variables exist before you use them. At the moment,
 * using $t when the secondary target refers to an object
 * i.e. >prog_act drops~<nl>if ispc($t)<nl>sigh<nl>endif<nl>~<nl>
 * probably makes the mud crash (vice versa as well) The cure
 * would be to change act() so that vo becomes vict & v_obj.
 * but this would require a lot of small changes all over the code.
 */
void mprog_translate (char ch, std::string & t, Character * mob, Character * actor,
  Object * obj, void *vo, Character * rndm)
{
  static const char *he_she[] = { "it", "he", "she" };
  static const char *him_her[] = { "it", "him", "her" };
  static const char *his_her[] = { "its", "his", "her" };
  Character *vict = (Character *) vo;
  Object *v_obj = (Object *) vo;

  t.erase();
  switch (ch) {
  case 'i':
    one_argument (mob->name, t);
    break;

  case 'I':
    t = mob->short_descr;
    break;

  case 'n':
    if (actor && mob->can_see(actor)) {
      one_argument (actor->name, t);
      if (!actor->is_npc ())
        t[0] = toupper(t[0]);
    }
    break;

  case 'N':
    if (actor) {
      if (mob->can_see(actor)) {
        if (actor->is_npc ())
          t = actor->short_descr;
        else
          t = actor->name + " " + actor->pcdata->title;
      } else {
        t = "someone";
      }
    }
    break;

  case 't':
    if (vict && mob->can_see(vict)) {
      one_argument (vict->name, t);
      if (!vict->is_npc ())
        t[0] = toupper(t[0]);
    }
    break;

  case 'T':
    if (vict) {
      if (mob->can_see(vict)) {
        if (vict->is_npc ())
          t = vict->short_descr;
        else
          t = vict->name + " " + vict->pcdata->title;
      } else {
        t = "someone";
      }
    }
    break;

  case 'r':
    if (rndm && mob->can_see(rndm)) {
      one_argument (rndm->name, t);
      if (!rndm->is_npc ())
        t[0] = toupper(t[0]);
    }
    break;

  case 'R':
    if (rndm) {
      if (mob->can_see(rndm)) {
        if (rndm->is_npc ())
          t = rndm->short_descr;
        else
          t = rndm->name + " " + rndm->pcdata->title;
      } else {
        t = "someone";
      }
    }
    break;

  case 'e':
    if (actor) {
      if (mob->can_see(actor))
        t = he_she[actor->sex];
      else
        t = "someone";
    }
    break;

  case 'm':
    if (actor) {
      if (mob->can_see(actor))
        t = him_her[actor->sex];
      else
        t = "someone";
    }
    break;

  case 's':
    if (actor) {
      if (mob->can_see(actor))
        t = his_her[actor->sex];
      else
        t = "someone's";
    }
    break;

  case 'E':
    if (vict) {
      if (mob->can_see(vict))
        t = he_she[vict->sex];
      else
        t = "someone";
    }
    break;

  case 'M':
    if (vict) {
      if (mob->can_see(vict))
        t = him_her[vict->sex];
      else
        t = "someone";
    }
    break;

  case 'S':
    if (vict) {
      if (mob->can_see(vict))
        t = his_her[vict->sex];
      else
        t = "someone's";
    }
    break;

  case 'j':
    t = he_she[mob->sex];
    break;

  case 'k':
    t = him_her[mob->sex];
    break;

  case 'l':
    t = his_her[mob->sex];
    break;

  case 'J':
    if (rndm) {
      if (mob->can_see(rndm))
        t = he_she[rndm->sex];
      else
        t = "someone";
    }
    break;

  case 'K':
    if (rndm) {
      if (mob->can_see(rndm))
        t = him_her[rndm->sex];
      else
        t = "someone";
    }
    break;

  case 'L':
    if (rndm) {
      if (mob->can_see(rndm))
        t = his_her[rndm->sex];
      else
        t = "someone's";
    }
    break;

  case 'o':
    if (obj) {
      if (mob->can_see_obj(obj))
        one_argument (obj->name, t);
      else
        t = "something";
    }
    break;

  case 'O':
    if (obj) {
      if (mob->can_see_obj(obj))
        t = obj->short_descr;
      else
        t = "something";
    }
    break;

  case 'p':
    if (v_obj) {
      if (mob->can_see_obj(v_obj))
        one_argument (v_obj->name, t);
      else
        t = "something";
    }
    break;

  case 'P':
    if (v_obj) {
      if (mob->can_see_obj(v_obj))
        t = v_obj->short_descr;
      else
        t = "something";
    }
    break;

  case 'a':
    if (obj)
      switch (obj->name[0]) {
      case 'a':
      case 'e':
      case 'i':
      case 'o':
      case 'u':
        t = "an";
        break;
      default:
        t = "a";
      }
    break;

  case 'A':
    if (v_obj)
      switch (v_obj->name[0]) {
      case 'a':
      case 'e':
      case 'i':
      case 'o':
      case 'u':
        t = "an";
        break;
      default:
        t = "a";
      }
    break;

  case '$':
    t = "$";
    break;

  default:
    bug_printf ("Mob: %d bad $var", mob->pIndexData->vnum);
    break;
  }

  return;

}

/* This procedure simply copies the cmnd to a buffer while expanding
 * any variables by calling the translate procedure.  The observant
 * code scrutinizer will notice that this is taken from act()
 */
void mprog_process_cmnd (const std::string & cmnd, Character * mob,
  Character * actor, Object * obj, void *vo, Character * rndm)
{
  std::string buf;
  std::string tmp;
  std::string::const_iterator str;
  str = cmnd.begin();

  while (str != cmnd.end()) {
    if (*str != '$') {
      buf.append(1, *str);
      str++;
      continue;
    }
    str++;
    mprog_translate (*str, tmp, mob, actor, obj, vo, rndm);
    buf.append(tmp);
    str++;
  }
  mob->interpret (buf);

  return;

}

/* Used to get sequential lines of a multi line string (separated by "\r\n")
 * Thus its like one_argument(), but a trifle different. It is destructive
 * to the multi line string argument, and thus clist must not be shared.
 */
std::string mprog_next_command (std::string & clist, std::string & cmd)
{
  std::string::iterator pointer = clist.begin();

  while (*pointer != '\n' && *pointer != '\r' && pointer != clist.end())
    pointer++;
  cmd.assign(clist.begin(), pointer);
  while ((*pointer == '\n' || *pointer == '\r') && pointer != clist.end())
    pointer++;
  return std::string(pointer, clist.end());
}

/* These two functions do the basic evaluation of ifcheck operators.
 *  It is important to note that the string operations are not what
 *  you probably expect.  Equality is exact and division is substring.
 *  remember that lhs has been stripped of leading space, but can
 *  still have trailing spaces so be careful when editing since:
 *  "guard" and "guard " are not equal.
 */
bool mprog_seval (const std::string & lhs, const std::string & opr, const std::string & rhs)
{

  if (!str_cmp (opr, "=="))
    return (bool) (!str_cmp (lhs, rhs));
  if (!str_cmp (opr, "!="))
    return (bool) (str_cmp (lhs, rhs));
  if (!str_cmp (opr, "/"))
    return (bool) (!str_infix (rhs, lhs));
  if (!str_cmp (opr, "!/"))
    return (bool) (str_infix (rhs, lhs));

  bug_printf ("Improper MOBprog operator");
  return 0;

}

bool mprog_veval (int lhs, const std::string & opr, int rhs)
{

  if (!str_cmp (opr, "=="))
    return (lhs == rhs);
  if (!str_cmp (opr, "!="))
    return (lhs != rhs);
  if (!str_cmp (opr, ">"))
    return (lhs > rhs);
  if (!str_cmp (opr, "<"))
    return (lhs < rhs);
  if (!str_cmp (opr, ">="))
    return (lhs <= rhs);
  if (!str_cmp (opr, ">="))
    return (lhs >= rhs);
  if (!str_cmp (opr, "&"))
    return (lhs & rhs);
  if (!str_cmp (opr, "|"))
    return (lhs | rhs);

  bug_printf ("Improper MOBprog operator\r\n");
  return 0;

}

/* This function performs the evaluation of the if checks.  It is
 * here that you can add any ifchecks which you so desire. Hopefully
 * it is clear from what follows how one would go about adding your
 * own. The syntax for an if check is: ifchck ( arg ) [opr val]
 * where the parenthesis are required and the opr and val fields are
 * optional but if one is there then both must be. The spaces are all
 * optional. The evaluation of the opr expressions is farmed out
 * to reduce the redundancy of the mammoth if statement list.
 * If there are errors, then return -1 otherwise return boolean 1,0
 */
int mprog_do_ifchck (const std::string & ifchck, Character * mob,
  Character * actor, Object * obj, void *vo, Character * rndm)
{

  char buf[MAX_INPUT_LENGTH];
  char arg[MAX_INPUT_LENGTH];
  char opr[MAX_INPUT_LENGTH];
  char val[MAX_INPUT_LENGTH];
  Character *vict = (Character *) vo;
  Object *v_obj = (Object *) vo;
  char *bufpt = buf;
  char *argpt = arg;
  char *oprpt = opr;
  char *valpt = val;
  std::string::const_iterator point = ifchck.begin();
  int lhsvl;
  int rhsvl;

  if (ifchck.empty()) {
    bug_printf ("Mob: %d null ifchck", mob->pIndexData->vnum);
    return -1;
  }
  /* skip leading spaces */
  while (*point == ' ')
    point++;

  /* get whatever comes before the left paren.. ignore spaces */
  while (*point != '(')
    if (point == ifchck.end()) {
      bug_printf ("Mob: %d ifchck syntax error", mob->pIndexData->vnum);
      return -1;
    } else if (*point == ' ')
      point++;
    else
      *bufpt++ = *point++;

  *bufpt = '\0';
  point++;

  /* get whatever is in between the parens.. ignore spaces */
  while (*point != ')')
    if (point == ifchck.end()) {
      bug_printf ("Mob: %d ifchck syntax error", mob->pIndexData->vnum);
      return -1;
    } else if (*point == ' ')
      point++;
    else
      *argpt++ = *point++;

  *argpt = '\0';
  point++;

  /* check to see if there is an operator */
  while (*point == ' ')
    point++;
  if (point == ifchck.end()) {
    *opr = '\0';
    *val = '\0';
  } else {                      /* there should be an operator and value, so get them */

    while ((*point != ' ') && (!isalnum (*point)))
      if (point == ifchck.end()) {
        bug_printf ("Mob: %d ifchck operator without value", mob->pIndexData->vnum);
        return -1;
      } else
        *oprpt++ = *point++;

    *oprpt = '\0';

    /* finished with operator, skip spaces and then get the value */
    while (*point == ' ')
      point++;
    for (;;) {
      if ((*point != ' ') && (point == ifchck.end()))
        break;
      else
        *valpt++ = *point++;
    }

    *valpt = '\0';
  }
//  bufpt = buf;
//  argpt = arg;
//  oprpt = opr;
//  valpt = val;

  /* Ok... now buf contains the ifchck, arg contains the inside of the
   *  parentheses, opr contains an operator if one is present, and val
   *  has the value if an operator was present.
   *  So.. basically use if statements and run over all known ifchecks
   *  Once inside, use the argument and expand the lhs. Then if need be
   *  send the lhs,opr,rhs off to be evaluated.
   */

  if (!str_cmp (buf, "rand")) {
    return (number_percent () <= std::atoi (arg));
  }

  if (!str_cmp (buf, "ispc")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return 0;
    case 'n':
      if (actor)
        return (!actor->is_npc ());
      else
        return -1;
    case 't':
      if (vict)
        return (!vict->is_npc ());
      else
        return -1;
    case 'r':
      if (rndm)
        return (!rndm->is_npc ());
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'ispc'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "isnpc")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return 1;
    case 'n':
      if (actor)
        return actor->is_npc ();
      else
        return -1;
    case 't':
      if (vict)
        return vict->is_npc ();
      else
        return -1;
    case 'r':
      if (rndm)
        return rndm->is_npc ();
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'isnpc'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "isgood")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return mob->is_good ();
    case 'n':
      if (actor)
        return actor->is_good ();
      else
        return -1;
    case 't':
      if (vict)
        return vict->is_good ();
      else
        return -1;
    case 'r':
      if (rndm)
        return rndm->is_good ();
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'isgood'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "isfight")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return (mob->fighting) ? 1 : 0;
    case 'n':
      if (actor)
        return (actor->fighting) ? 1 : 0;
      else
        return -1;
    case 't':
      if (vict)
        return (vict->fighting) ? 1 : 0;
      else
        return -1;
    case 'r':
      if (rndm)
        return (rndm->fighting) ? 1 : 0;
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'isfight'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "isimmort")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return (mob->get_trust () > LEVEL_IMMORTAL);
    case 'n':
      if (actor)
        return (actor->get_trust () > LEVEL_IMMORTAL);
      else
        return -1;
    case 't':
      if (vict)
        return (vict->get_trust () > LEVEL_IMMORTAL);
      else
        return -1;
    case 'r':
      if (rndm)
        return (rndm->get_trust () > LEVEL_IMMORTAL);
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'isimmort'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "ischarmed")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return mob->is_affected (AFF_CHARM);
    case 'n':
      if (actor)
        return actor->is_affected (AFF_CHARM);
      else
        return -1;
    case 't':
      if (vict)
        return vict->is_affected (AFF_CHARM);
      else
        return -1;
    case 'r':
      if (rndm)
        return rndm->is_affected (AFF_CHARM);
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'ischarmed'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "isfollow")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return (mob->master != NULL && mob->master->in_room == mob->in_room);
    case 'n':
      if (actor)
        return (actor->master != NULL
          && actor->master->in_room == actor->in_room);
      else
        return -1;
    case 't':
      if (vict)
        return (vict->master != NULL
          && vict->master->in_room == vict->in_room);
      else
        return -1;
    case 'r':
      if (rndm)
        return (rndm->master != NULL
          && rndm->master->in_room == rndm->in_room);
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'isfollow'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "isaffected")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return (mob->affected_by & std::atoi (arg));
    case 'n':
      if (actor)
        return (actor->affected_by & std::atoi (arg));
      else
        return -1;
    case 't':
      if (vict)
        return (vict->affected_by & std::atoi (arg));
      else
        return -1;
    case 'r':
      if (rndm)
        return (rndm->affected_by & std::atoi (arg));
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'isaffected'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "hitprcnt")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->hit / mob->max_hit;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->hit / actor->max_hit;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->hit / vict->max_hit;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->hit / rndm->max_hit;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'hitprcnt'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "inroom")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->in_room->vnum;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->in_room->vnum;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->in_room->vnum;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->in_room->vnum;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'inroom'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "sex")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->sex;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->sex;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->sex;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->sex;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'sex'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "position")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->position;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->position;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->position;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->position;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'position'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "level")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->get_trust ();
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->get_trust ();
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->get_trust ();
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->get_trust ();
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'level'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "class")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->klass;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->klass;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->klass;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->klass;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'class'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "goldamt")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->gold;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        lhsvl = actor->gold;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 't':
      if (vict) {
        lhsvl = vict->gold;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'r':
      if (rndm) {
        lhsvl = rndm->gold;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'goldamt'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "objtype")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'o':
      if (obj) {
        lhsvl = obj->item_type;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'p':
      if (v_obj) {
        lhsvl = v_obj->item_type;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'objtype'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "objval0")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'o':
      if (obj) {
        lhsvl = obj->value[0];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'p':
      if (v_obj) {
        lhsvl = v_obj->value[0];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'objval0'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "objval1")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'o':
      if (obj) {
        lhsvl = obj->value[1];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'p':
      if (v_obj) {
        lhsvl = v_obj->value[1];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'objval1'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "objval2")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'o':
      if (obj) {
        lhsvl = obj->value[2];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'p':
      if (v_obj) {
        lhsvl = v_obj->value[2];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'objval2'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "objval3")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'o':
      if (obj) {
        lhsvl = obj->value[3];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'p':
      if (v_obj) {
        lhsvl = v_obj->value[3];
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'objval3'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "number")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      lhsvl = mob->gold;
      rhsvl = std::atoi (val);
      return mprog_veval (lhsvl, opr, rhsvl);
    case 'n':
      if (actor) {
        if (actor->is_npc ()) {
          lhsvl = actor->pIndexData->vnum;
          rhsvl = std::atoi (val);
          return mprog_veval (lhsvl, opr, rhsvl);
          }
      } else
        return -1;
    case 't':
      if (vict) {
        if (actor->is_npc ()) {
          lhsvl = vict->pIndexData->vnum;
          rhsvl = std::atoi (val);
          return mprog_veval (lhsvl, opr, rhsvl);
          }
      } else
        return -1;
    case 'r':
      if (rndm) {
        if (actor->is_npc ()) {
          lhsvl = rndm->pIndexData->vnum;
          rhsvl = std::atoi (val);
          return mprog_veval (lhsvl, opr, rhsvl);
          }
      } else
        return -1;
    case 'o':
      if (obj) {
        lhsvl = obj->pIndexData->vnum;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    case 'p':
      if (v_obj) {
        lhsvl = v_obj->pIndexData->vnum;
        rhsvl = std::atoi (val);
        return mprog_veval (lhsvl, opr, rhsvl);
      } else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'number'", mob->pIndexData->vnum);
      return -1;
    }
  }

  if (!str_cmp (buf, "name")) {
    switch (arg[1]) {           /* arg should be "$*" so just get the letter */
    case 'i':
      return mprog_seval (mob->name, opr, val);
    case 'n':
      if (actor)
        return mprog_seval (actor->name, opr, val);
      else
        return -1;
    case 't':
      if (vict)
        return mprog_seval (vict->name, opr, val);
      else
        return -1;
    case 'r':
      if (rndm)
        return mprog_seval (rndm->name, opr, val);
      else
        return -1;
    case 'o':
      if (obj)
        return mprog_seval (obj->name, opr, val);
      else
        return -1;
    case 'p':
      if (v_obj)
        return mprog_seval (v_obj->name, opr, val);
      else
        return -1;
    default:
      bug_printf ("Mob: %d bad argument to 'name'", mob->pIndexData->vnum);
      return -1;
    }
  }

  /* Ok... all the ifchcks are done, so if we didnt find ours then something
   * odd happened.  So report the bug and abort the MOBprogram (return error)
   */
  bug_printf ("Mob: %d unknown ifchck", mob->pIndexData->vnum);
  return -1;

}

/* Quite a long and arduous function, this guy handles the control
 * flow part of MOBprograms.  Basicially once the driver sees an
 * 'if' attention shifts to here.  While many syntax errors are
 * caught, some will still get through due to the handling of break
 * and errors in the same fashion.  The desire to break out of the
 * recursion without catastrophe in the event of a mis-parse was
 * believed to be high. Thus, if an error is found, it is bugged and
 * the parser acts as though a break were issued and just bails out
 * at that point. I havent tested all the possibilites, so I'm speaking
 * in theory, but it is 'guaranteed' to work on syntactically correct
 * MOBprograms, so if the mud crashes here, check the mob carefully!
 */
std::string mprog_process_if (const std::string & ifchck, std::string & com_list,
  Character * mob, Character * actor, Object * obj, void *vo, Character * rndm)
{
  std::string buf;
  std::string morebuf;
  std::string cmnd;
  bool loopdone = false;
  bool flag = false;
  int legal;

  /* check for trueness of the ifcheck */
  if ((legal = mprog_do_ifchck (ifchck, mob, actor, obj, vo, rndm))) {
    if (legal == 1)
      flag = true;
    else
      return "";
  }

  while (loopdone == false) {   /*scan over any existing or statements */
    com_list = mprog_next_command (com_list, cmnd);
    cmnd.erase(0, cmnd.find_first_not_of(" "));
    if (cmnd.empty()) {
      bug_printf ("Mob: %d no commands after IF/OR", mob->pIndexData->vnum);
      return "";
    }
    morebuf = one_argument (cmnd, buf);
    if (!str_cmp (buf, "or")) {
      if ((legal = mprog_do_ifchck (morebuf, mob, actor, obj, vo, rndm))) {
        if (legal == 1)
          flag = true;
        else
          return "";
      }
    } else
      loopdone = true;
  }

  if (flag)
    for (;;) {                  /*ifcheck was true, do commands but ignore else to endif */
      if (!str_cmp (buf, "if")) {
        com_list =
          mprog_process_if (morebuf, com_list, mob, actor, obj, vo, rndm);
        cmnd.erase(0, cmnd.find_first_not_of(" "));
        if (com_list.empty())
          return "";
        com_list = mprog_next_command (com_list, cmnd);
        morebuf = one_argument (cmnd, buf);
        continue;
      }
      if (!str_cmp (buf, "break"))
        return "";
      if (!str_cmp (buf, "endif"))
        return com_list;
      if (!str_cmp (buf, "else")) {
        while (str_cmp (buf, "endif")) {
          com_list = mprog_next_command (com_list, cmnd);
          cmnd.erase(0, cmnd.find_first_not_of(" "));
          if (cmnd.empty()) {
            bug_printf ("Mob: %d missing endif after else", mob->pIndexData->vnum);
            return "";
          }
          morebuf = one_argument (cmnd, buf);
        }
        return com_list;
      }
      mprog_process_cmnd (cmnd, mob, actor, obj, vo, rndm);
      com_list = mprog_next_command (com_list, cmnd);
      cmnd.erase(0, cmnd.find_first_not_of(" "));
      if (cmnd.empty()) {
        bug_printf ("Mob: %d missing else or endif", mob->pIndexData->vnum);
        return "";
      }
      morebuf = one_argument (cmnd, buf);
  } else {                      /*false ifcheck, find else and do existing commands or quit at endif */

    while ((str_cmp (buf, "else")) && (str_cmp (buf, "endif"))) {
      com_list = mprog_next_command (com_list, cmnd);
      cmnd.erase(0, cmnd.find_first_not_of(" "));
      if (cmnd.empty()) {
        bug_printf ("Mob: %d missing an else or endif", mob->pIndexData->vnum);
        return "";
      }
      morebuf = one_argument (cmnd, buf);
    }

    /* found either an else or an endif.. act accordingly */
    if (!str_cmp (buf, "endif"))
      return com_list;
    com_list = mprog_next_command (com_list, cmnd);
    cmnd.erase(0, cmnd.find_first_not_of(" "));
    if (cmnd.empty()) {
      bug_printf ("Mob: %d missing endif", mob->pIndexData->vnum);
      return "";
    }
    morebuf = one_argument (cmnd, buf);

    for (;;) {                  /*process the post-else commands until an endif is found. */
      if (!str_cmp (buf, "if")) {
        com_list = mprog_process_if (morebuf, com_list, mob, actor,
          obj, vo, rndm);
        cmnd.erase(0, cmnd.find_first_not_of(" "));
        if (com_list.empty())
          return "";
        com_list = mprog_next_command (com_list, cmnd);
        morebuf = one_argument (cmnd, buf);
        continue;
      }
      if (!str_cmp (buf, "else")) {
        bug_printf ("Mob: %d found else in an else section", mob->pIndexData->vnum);
        return "";
      }
      if (!str_cmp (buf, "break"))
        return "";
      if (!str_cmp (buf, "endif"))
        return com_list;
      mprog_process_cmnd (cmnd, mob, actor, obj, vo, rndm);
      com_list = mprog_next_command (com_list, cmnd);
      cmnd.erase(0, cmnd.find_first_not_of(" "));
      if (cmnd.empty()) {
        bug_printf ("Mob:%d missing endif in else section", mob->pIndexData->vnum);
        return "";
      }
      morebuf = one_argument (cmnd, buf);
    }
  }
}

/* The main focus of the MOBprograms.  This routine is called
 *  whenever a trigger is successful.  It is responsible for parsing
 *  the command list and figuring out what to do. However, like all
 *  complex procedures, everything is farmed out to the other guys.
 */
void mprog_driver (const std::string & com_list, Character * mob,
  Character * actor, Object * obj, void *vo)
{

  std::string tmpcmndlst;
  std::string buf;
  std::string morebuf;
  std::string command_list;
  std::string cmnd;
  Character *rndm = NULL;
  int count = 0;

  if (mob->is_affected (AFF_CHARM))
      return;

  /* get a random visable mortal player who is in the room with the mob */
  CharIter vch;
  for (vch = mob->in_room->people.begin(); vch != mob->in_room->people.end(); vch++)
    if (!(*vch)->is_npc ()
      && (*vch)->level < LEVEL_IMMORTAL && mob->can_see(*vch)) {
      if (number_range (0, count) == 0)
        rndm = *vch;
      count++;
    }

  tmpcmndlst = com_list;
  command_list = tmpcmndlst;
  command_list = mprog_next_command (command_list, cmnd);
  while (!cmnd.empty()) {
    morebuf = one_argument (cmnd, buf);
    if (!str_cmp (buf, "if"))
      command_list = mprog_process_if (morebuf, command_list, mob,
        actor, obj, vo, rndm);
    else
      mprog_process_cmnd (cmnd, mob, actor, obj, vo, rndm);
    command_list = mprog_next_command (command_list, cmnd);
  }

  return;

}

/* The next two routines are the basic trigger types. Either trigger
 *  on a certain percent, or trigger on a keyword or word phrase.
 *  To see how this works, look at the various trigger routines..
 */
void mprog_wordlist_check (const std::string & arg, Character * mob,
  Character * actor, Object * obj, void *vo, int type)
{
  std::string list;
  std::string dupl;
  std::string word;
  MobProgram *mprg;

  std::string::size_type start;
  std::string::size_type end;

  unsigned int i;

  for (mprg = mob->pIndexData->mobprogs; mprg != NULL; mprg = mprg->next)
    if (mprg->type & type) {
      list = mprg->arglist;
      for (i = 0; i < list.size(); i++)
        list[i] = tolower (list[i]);
      dupl = arg;
      for (i = 0; i < dupl.size(); i++)
        dupl[i] = tolower (dupl[i]);

      if (list.substr(0,2) == "p ") {
        list = list.substr(2);
        while ((start = dupl.find(list)) != std::string::npos)
          if ((start == 0 || dupl[start - 1] == ' ')
            && (dupl[end = start + list.size()] == ' '
              || dupl[end] == '\n' || dupl[end] == '\r' || dupl[end] == '\0')) {
            mprog_driver (mprg->comlist, mob, actor, obj, vo);
            break;
          } else
            dupl = dupl.substr(start + 1);
      } else {
        list = one_argument (list, word);
        for (; !word.empty(); list = one_argument (list, word))
          while ((start = dupl.find(word)) != std::string::npos)
            if ((start == 0 || dupl[start - 1] == ' ')
              && (dupl[end = start + word.size()] == ' '
              || dupl[end] == '\n' || dupl[end] == '\r' || dupl[end] == '\0')) {
              mprog_driver (mprg->comlist, mob, actor, obj, vo);
              break;
            } else
              dupl = dupl.substr(start + 1);
      }
    }

  return;
}

/*
 * Mob autonomous action.
 * This function takes 25% to 35% of ALL Merc cpu time.
 * -- Furey
 */
void mobile_update (void)
{
  Character *ch;
  Exit *pexit;
  int door;
try {
  /* Examine all mobs. */
  CharIter c;
  for (c = char_list.begin(); c != char_list.end(); c = deepchnext) {
    ch = *c;
    deepchnext = ++c;

    if (!ch->is_npc () || ch->in_room == NULL || ch->is_affected (AFF_CHARM))
      continue;

    /* Examine call for special procedure */
    if (ch->spec_fun != 0) {
      if ((*ch->spec_fun) (ch))
        continue;
    }

    /* That's all for sleeping / busy monster */
    if (ch->position < POS_STANDING)
      continue;

    /* MOBprogram random trigger */
    if (ch->in_room->area->nplayer > 0) {
      mprog_random_trigger (ch);
      /* If ch dies or changes
         position due to it's random
         trigger continue - Kahn */
      if (ch->position < POS_STANDING)
        continue;
    }

    /* Scavenge */
    if (IS_SET (ch->actflags, ACT_SCAVENGER)
      && !ch->in_room->contents.empty() && number_percent() <= 25) {
      Object *obj_best;
      int max;

      max = 1;
      obj_best = 0;
      ObjIter obj;
      for (obj = ch->in_room->contents.begin(); obj != ch->in_room->contents.end(); obj++) {
        if ((*obj)->can_wear(ITEM_TAKE) && (*obj)->cost > max) {
          obj_best = *obj;
          max = (*obj)->cost;
        }
      }

      if (obj_best) {
        obj_best->obj_from_room ();
        obj_best->obj_to_char (ch);
        ch->act ("$n gets $p.", obj_best, NULL, TO_ROOM);
      }
    }

    /* Wander */
    if (!IS_SET (ch->actflags, ACT_SENTINEL)
      && (door = number_range (0, 31)) <= 5
      && (pexit = ch->in_room->exit[door]) != NULL
      && pexit->to_room != NULL && !IS_SET (pexit->exit_info, EX_CLOSED)
      && !IS_SET (pexit->to_room->room_flags, ROOM_NO_MOB)
      && (!IS_SET (ch->actflags, ACT_STAY_AREA)
        || pexit->to_room->area == ch->in_room->area)) {
      ch->move_char (door);
      /* If ch changes position due
         to it's or someother mob's
         movement via MOBProgs,
         continue - Kahn */
      if (ch->position < POS_STANDING)
        continue;
    }

    /* Flee */
    if (ch->hit < (ch->max_hit / 2)
      && (door = number_range (0, 7)) <= 5
      && (pexit = ch->in_room->exit[door]) != NULL
      && pexit->to_room != NULL && !IS_SET (pexit->exit_info, EX_CLOSED)
      && !IS_SET (pexit->to_room->room_flags, ROOM_NO_MOB)) {
      bool found;

      found = false;
      CharIter rch;
      for (rch = pexit->to_room->people.begin(); rch != pexit->to_room->people.end(); rch++) {
        if (!(*rch)->is_npc ()) {
          found = true;
          break;
        }
      }
      if (!found)
        ch->move_char (door);
    }

  }

} catch (...) {
  fatal_printf("mobile_update() exception");
}
  return;
}

/*
 * Update the weather.
 */
void weather_update (void)
{
  std::string buf = g_world->weather_update();

  if (!buf.empty()) {
    for (DescIter d = descriptor_list.begin();
      d != descriptor_list.end(); d++) {
      if ((*d)->connected == CON_PLAYING && (*d)->character->is_outside()
        && (*d)->character->is_awake ())
        (*d)->character->send_to_char (buf);
    }
  }

  return;
}

/*
 * Update all chars, including mobs.
 * This function is performance sensitive.
 */
void char_update (void)
{
  Character *ch;
  Character *ch_save;
  Character *ch_quit;
  time_t save_time;

try {
  save_time = g_world->get_current_time();
  ch_save = NULL;
  ch_quit = NULL;
  CharIter c;
  for (c = char_list.begin(); c != char_list.end(); c = deepchnext) {
    Affect *paf;
    ch = *c;
    deepchnext = ++c;

    /*
     * Find dude with oldest save time.
     */
    if (!ch->is_npc ()
      && (ch->desc == NULL || ch->desc->connected == CON_PLAYING)
      && ch->level >= 2 && ch->save_time < save_time) {
      ch_save = ch;
      save_time = ch->save_time;
    }

    if (ch->position >= POS_STUNNED) {
      if (ch->hit < ch->max_hit)
        ch->hit += ch->hit_gain();

      if (ch->mana < ch->max_mana)
        ch->mana += ch->mana_gain ();

      if (ch->move < ch->max_move)
        ch->move += ch->move_gain();
    }

    if (ch->position == POS_STUNNED)
      ch->update_pos();

    if (!ch->is_npc () && ch->level < LEVEL_IMMORTAL) {
      Object *obj;

      if ((obj = ch->get_eq_char (WEAR_LIGHT)) != NULL
        && obj->item_type == ITEM_LIGHT && obj->value[2] > 0) {
        if (--obj->value[2] == 0 && ch->in_room != NULL) {
          --ch->in_room->light;
          ch->act ("$p goes out.", obj, NULL, TO_ROOM);
          ch->act ("$p goes out.", obj, NULL, TO_CHAR);
          obj->extract_obj ();
        }
      }

      if ((obj = ch->get_eq_char (WEAR_LIGHT)) != NULL
        && obj->item_type == ITEM_DARKNESS && obj->value[2] > 0) {
        if (--obj->value[2] == 0 && ch->in_room != NULL) {
          ++ch->in_room->light;
          ch->act ("$p disappears.", obj, NULL, TO_ROOM);
          ch->act ("$p disappears.", obj, NULL, TO_CHAR);
          obj->extract_obj ();
        }
      }

      if (++ch->timer >= 12) {
        if (ch->was_in_room == NULL && ch->in_room != NULL) {
          ch->was_in_room = ch->in_room;
          if (ch->fighting != NULL)
            ch->stop_fighting (true);
          ch->act ("$n disappears into the void.", NULL, NULL, TO_ROOM);
          ch->send_to_char ("You disappear into the void.\r\n");
          ch->save_char_obj();
          ch->char_from_room();
          ch->char_to_room(get_room_index (ROOM_VNUM_LIMBO));
        }
      }

      if (ch->timer > 30)
        ch_quit = ch;

      ch->gain_condition (COND_DRUNK, -1);
      ch->gain_condition (COND_FULL, -1);
      ch->gain_condition (COND_THIRST, -1);
    }

    AffIter af, next;
    for (af = ch->affected.begin(); af != ch->affected.end(); af = next) {
      paf = *af;
      next = ++af;
      if (paf->duration > 0)
        paf->duration--;
      else if (paf->duration < 0);
      else {
        if (next == ch->affected.end()
          || (*next)->type != paf->type || (*next)->duration > 0) {
          if (paf->type > 0 && skill_table[paf->type].msg_off[0] != '\0') {
            ch->send_to_char (skill_table[paf->type].msg_off);
            ch->send_to_char ("\r\n");
          }
        }

        ch->affect_remove (paf);
      }
    }

    /*
     * Careful with the damages here,
     *   MUST NOT refer to ch after damage taken,
     *   as it may be lethal damage (on NPC).
     */
    if (ch->is_affected (AFF_POISON)) {
      ch->act ("$n shivers and suffers.", NULL, NULL, TO_ROOM);
      ch->send_to_char ("You shiver and suffer.\r\n");
      damage (ch, ch, 2, skill_lookup("poison"));
    } else if (ch->position == POS_INCAP) {
      damage (ch, ch, 1, TYPE_UNDEFINED);
    } else if (ch->position == POS_MORTAL) {
      damage (ch, ch, 2, TYPE_UNDEFINED);
    }
  }

  /*
   * Autosave and autoquit.
   * Check that these chars still exist.
   */
  if (ch_save != NULL || ch_quit != NULL) {
    CharIter cnext;
    for (c = char_list.begin(); c != char_list.end(); c = cnext) {
      ch = *c;
      cnext = ++c;
      if (ch == ch_save)
        ch->save_char_obj();
      if (ch == ch_quit)
        ch->do_quit ("");
    }
  }
} catch (...) {
  fatal_printf("char_update() exception");
}

  return;
}

/*
 * Update all objs.
 * This function is performance sensitive.
 */
void obj_update (void)
{
  Object *obj;
  ObjIter o;

try {
  for (o = object_list.begin(); o != object_list.end(); o = deepobnext) {
    const char *message;
    obj = *o;
    deepobnext = ++o;

    if (obj->timer <= 0 || --obj->timer > 0)
      continue;

    switch (obj->item_type) {
    default:
      message = "$p vanishes.";
      break;
    case ITEM_FOUNTAIN:
      message = "$p dries up.";
      break;
    case ITEM_CORPSE_NPC:
      message = "$p decays into dust.";
      break;
    case ITEM_CORPSE_PC:
      message = "$p decays into dust.";
      break;
    case ITEM_FOOD:
      message = "$p decomposes.";
      break;
    }

    if (obj->carried_by != NULL) {
      obj->carried_by->act (message, obj, NULL, TO_CHAR);
    } else if (obj->in_room != NULL && !obj->in_room->people.empty()) {
      Character *rch = obj->in_room->people.front();
      rch->act (message, obj, NULL, TO_ROOM);
      rch->act (message, obj, NULL, TO_CHAR);
    }

    obj->extract_obj ();
  }
} catch (...) {
  fatal_printf("obj_update() exception");
}

  return;
}

/*
 * Aggress.
 *
 * for each mortal PC
 *     for each mob in room
 *         aggress on some random PC
 *
 * This function takes 25% to 35% of ALL Merc cpu time.
 * Unfortunately, checking on each PC move is too tricky,
 *   because we don't the mob to just attack the first PC
 *   who leads the party into the room.
 *
 * -- Furey
 */
void aggr_update (void)
{
  Character *wch;
  Character *ch;
  Character *victim;

try {
  CharIter c;
  for (c = char_list.begin(); c != char_list.end(); c = deepchnext) {
    wch = *c;
    deepchnext = ++c;

    /* MOBProgram ACT_PROG trigger */
    if (wch->is_npc () && wch->mpactnum > 0 && wch->in_room->area->nplayer > 0) {
      MobProgramActList *tmp_act, *tmp2_act;
      for (tmp_act = wch->mpact; tmp_act != NULL; tmp_act = tmp_act->next) {
        mprog_wordlist_check (tmp_act->buf, wch, tmp_act->ch,
          tmp_act->obj, tmp_act->vo, ACT_PROG);
      }
      for (tmp_act = wch->mpact; tmp_act != NULL; tmp_act = tmp2_act) {
        tmp2_act = tmp_act->next;
        delete tmp_act;
      }
      wch->mpactnum = 0;
      wch->mpact = NULL;
    }

    if (wch->is_npc ()
      || wch->level >= LEVEL_IMMORTAL || wch->in_room == NULL)
      continue;

    Room* rlist = wch->in_room;

    CharIter rch, rcnext;
    for (rch = rlist->people.begin(); rch != rlist->people.end(); rch = deeprmnext) {
      ch = *rch;
      deeprmnext = ++rch;
      int count;

      if (!ch->is_npc ()
        || !IS_SET (ch->actflags, ACT_AGGRESSIVE)
        || ch->fighting != NULL || ch->is_affected (AFF_CHARM)
        || !ch->is_awake ()
        || (IS_SET (ch->actflags, ACT_WIMPY) && wch->is_awake ())
        || !ch->can_see(wch))
        continue;

      /*
       * Ok we have a 'wch' player character and a 'ch' npc aggressor.
       * Now make the aggressor fight a RANDOM pc victim in the room,
       *   giving each 'vch' an equal chance of selection.
       */
      count = 0;
      victim = NULL;
      for (CharIter vc = rlist->people.begin(); vc != rlist->people.end(); vc++) {
        if (!(*vc)->is_npc () && (*vc)->level < LEVEL_IMMORTAL
          && (!IS_SET (ch->actflags, ACT_WIMPY) || !(*vc)->is_awake ())
          && ch->can_see(*vc)) {
          if (number_range (0, count) == 0)
            victim = *vc;
          count++;
        }
      }

      if (victim == NULL) {
        bug_printf ("Aggr_update: null victim.", count);
        continue;
      }

      multi_hit (ch, victim, TYPE_UNDEFINED);
    }
  }

} catch (...) {
  fatal_printf("aggr_update() exception");
}

  return;
}

/*
 * Control the fights going on.
 * Called periodically by update_handler.
 */
void violence_update (void)
{
  Character *ch;
  Character *victim;
  Character *rch;

try {

  CharIter c;
  for (c = char_list.begin(); c != char_list.end(); c = deepchnext) {
    ch = *c;
    deepchnext = ++c;

    victim = ch->fighting;
    if (victim == NULL || ch->in_room == NULL)
      continue;

    if (ch->is_awake () && ch->in_room == victim->in_room)
      multi_hit (ch, victim, TYPE_UNDEFINED);
    else
      ch->stop_fighting (false);

    victim = ch->fighting;
    if (victim == NULL)
      continue;

    mprog_hitprcnt_trigger (ch, victim);
    mprog_fight_trigger (ch, victim);

    /*
     * Fun for the whole family!
     */

    if (ch == NULL || ch->in_room == NULL)
      continue;
    victim = ch->fighting;
    if (victim == NULL)
      continue;
    Room * rlist = ch->in_room;

    CharIter rc, rnext;
    for (rc = rlist->people.begin(); rc != rlist->people.end(); rc = deeprmnext) {
      rch = *rc;
      deeprmnext = ++rc;

      if (rch->fighting == NULL && rch->is_awake ()) {
        /*
         * PC's auto-assist others in their group.
         */
        if (!ch->is_npc () || ch->is_affected (AFF_CHARM)) {
          if ((!rch->is_npc () || rch->is_affected (AFF_CHARM))
            && is_same_group (ch, rch))
            multi_hit (rch, victim, TYPE_UNDEFINED);
          continue;
        }

        /*
         * NPC's assist NPC's of same type or 12.5% chance regardless.
         */
        if (rch->is_npc () && !rch->is_affected (AFF_CHARM)) {
          if (rch->pIndexData == ch->pIndexData || number_range (0, 7) == 0) {
            Character *target;
            int number;

            target = NULL;
            number = 0;
            CharIter vch;
            for (vch = rlist->people.begin(); vch != rlist->people.end(); vch++) {
              if (rch->can_see(*vch)
                && is_same_group (*vch, victim)
                && number_range (0, number) == 0) {
                target = *vch;
                number++;
              }
            }

            if (target != NULL) {
              if ((((target->level - rch->level <= 4)
                    && (target->level - rch->level >= -4))
                  && !(rch->is_good () && target->is_good ()))
                || (rch->is_evil () || target->is_evil ()))
                multi_hit (rch, target, TYPE_UNDEFINED);
            }
          }
        }
      }
    }
  }

} catch (...) {
  fatal_printf("violence_update() exception");
}

  return;
}


void extract_dead_characters()
{
  Character *curr;
  CharIter c, next;
  for (c = char_list.begin(); c != char_list.end(); c = next) {
	curr = *c;
    next = ++c;
    if (curr->is_npc()) {
      if (IS_SET(curr->actflags, ACT_EXTRACT)) {
		  REMOVE_BIT(curr->actflags, ACT_EXTRACT);
		  curr->extract_char_old(true);
	  }
    } else {
      if (IS_SET(curr->actflags, PLR_EXTRACT)) {
		  REMOVE_BIT(curr->actflags, PLR_EXTRACT);
		  curr->extract_char_old(false);
 	  }
	}
  }
}


/*
 * Handle all kinds of updates.
 * Called once per pulse from game loop.
 * Random times to defeat tick-timing clients and players.
 */
void update_handler (void)
{
  static int pulse_area;
  static int pulse_mobile;
  static int pulse_violence;
  static int pulse_point;

try {
  if (--pulse_area <= 0) {
    pulse_area = number_range (PULSE_AREA / 2, 3 * PULSE_AREA / 2);
    g_world->area_update ();
  }

  if (--pulse_violence <= 0) {
    pulse_violence = PULSE_VIOLENCE;
    violence_update ();
  }

  if (--pulse_mobile <= 0) {
    pulse_mobile = PULSE_MOBILE;
    mobile_update ();
  }

  if (--pulse_point <= 0) {
    pulse_point = number_range (PULSE_TICK / 2, 3 * PULSE_TICK / 2);
    weather_update ();
    char_update ();
    obj_update ();
  }

  aggr_update ();
  if (extract_chars) {
	  extract_dead_characters();
	  extract_chars = false;
  }

} catch (...) {
  fatal_printf("update_handler() exception");
}
  return;
}

/*
 * Shopping commands.
 */
Character *find_keeper (Character * ch)
{
  char buf[MAX_STRING_LENGTH];
  Shop *pShop;

  pShop = NULL;
  CharIter keeper;
  for (keeper = ch->in_room->people.begin(); keeper != ch->in_room->people.end(); keeper++) {
    if ((*keeper)->is_npc () && (pShop = (*keeper)->pIndexData->pShop) != NULL)
      break;
  }

  if (pShop == NULL) {
    ch->send_to_char ("You can't do that here.\r\n");
    return NULL;
  }

  /*
   * Undesirables.
   */
  if (!ch->is_npc () && IS_SET (ch->actflags, PLR_KILLER)) {
    (*keeper)->do_say ("Killers are not welcome!");
    snprintf (buf, sizeof buf, "%s the KILLER is over here!\r\n", ch->name.c_str());
    (*keeper)->do_shout (buf);
    return NULL;
  }

  if (!ch->is_npc () && IS_SET (ch->actflags, PLR_THIEF)) {
    (*keeper)->do_say ("Thieves are not welcome!");
    snprintf (buf, sizeof buf, "%s the THIEF is over here!\r\n", ch->name.c_str());
    (*keeper)->do_shout (buf);
    return NULL;
  }

  /*
   * Shop hours.
   */
  if (g_world->hour() < pShop->open_hour) {
    (*keeper)->do_say ("Sorry, come back later.");
    return NULL;
  }

  if (g_world->hour() > pShop->close_hour) {
    (*keeper)->do_say ("Sorry, come back tomorrow.");
    return NULL;
  }

  /*
   * Invisible or hidden people.
   */
  if (!(*keeper)->can_see(ch)) {
    (*keeper)->do_say ("I don't trade with folks I can't see.");
    return NULL;
  }

  return *keeper;
}

int get_cost (Character * keeper, Object * obj, bool fBuy)
{
  Shop *pShop;
  int cost;

  if (obj == NULL || (pShop = keeper->pIndexData->pShop) == NULL)
    return 0;

  if (fBuy) {
    cost = obj->cost * pShop->profit_buy / 100;
  } else {
    int itype;

    cost = 0;
    for (itype = 0; itype < MAX_TRADE; itype++) {
      if (obj->item_type == pShop->buy_type[itype]) {
        cost = obj->cost * pShop->profit_sell / 100;
        break;
      }
    }

    ObjIter o;
    for (o = keeper->carrying.begin(); o != keeper->carrying.end(); o++) {
      if (obj->pIndexData == (*o)->pIndexData)
        cost /= 2;
    }
  }

  if (obj->item_type == ITEM_STAFF || obj->item_type == ITEM_WAND)
    cost = cost * obj->value[2] / obj->value[1];

  return cost;
}

/*
 * Generic channel function.
 */
void talk_channel (Character * ch, const std::string & argument, int channel,
  const char *verb)
{
  char buf[MAX_STRING_LENGTH];
  int position;

  if (argument.empty()) {
    snprintf (buf, sizeof buf, "%s what?\r\n", verb);
    buf[0] = toupper (buf[0]);
    return;
  }

  if (!ch->is_npc () && IS_SET (ch->actflags, PLR_SILENCE)) {
    snprintf (buf, sizeof buf, "You can't %s.\r\n", verb);
    ch->send_to_char (buf);
    return;
  }

  REMOVE_BIT (ch->deaf, channel);

  switch (channel) {
  default:
    snprintf (buf, sizeof buf, "You %s '%s'.\r\n", verb, argument.c_str());
    ch->send_to_char (buf);
    snprintf (buf, sizeof buf, "$n %ss '$t'.", verb);
    break;

  case CHANNEL_IMMTALK:
    snprintf (buf, sizeof buf, "$n: $t.");
    position = ch->position;
    ch->position = POS_STANDING;
    ch->act (buf, argument.c_str(), NULL, TO_CHAR);
    ch->position = position;
    break;
  }

  for (DescIter d = descriptor_list.begin();
    d != descriptor_list.end(); d++) {
    Character *och;
    Character *vch;

    och = (*d)->original ? (*d)->original : (*d)->character;
    vch = (*d)->character;

    if ((*d)->connected == CON_PLAYING
      && vch != ch && !IS_SET (och->deaf, channel)) {
      if (channel == CHANNEL_IMMTALK && !och->is_hero())
        continue;
      if (channel == CHANNEL_YELL && vch->in_room->area != ch->in_room->area)
        continue;

      position = vch->position;
      if (channel != CHANNEL_SHOUT && channel != CHANNEL_YELL)
        vch->position = POS_STANDING;
      ch->act (buf, argument.c_str(), vch, TO_VICT);
      vch->position = position;
    }
  }

  return;
}

Room *find_location (Character * ch, const std::string & arg)
{
  Character *victim;
  Object *obj;

  if (is_number (arg))
    return get_room_index (std::atoi (arg.c_str()));

  if ((victim = ch->get_char_world (arg)) != NULL)
    return victim->in_room;

  if ((obj = ch->get_obj_world (arg)) != NULL)
    return obj->in_room;

  return NULL;
}

bool is_safe (Character * ch, Character * victim)
{
  if (ch->is_npc () || victim->is_npc ())
    return false;

  if (ch->get_age() < 21) {
    ch->send_to_char ("You aren't old enough.\r\n");
    return true;
  }

  if (IS_SET (victim->actflags, PLR_KILLER))
    return false;

  if (ch->level >= victim->level) {
    ch->send_to_char ("You may not attack a lower level player.\r\n");
    return true;
  }

  return false;
}

/*
 * See if an attack justifies a KILLER flag.
 */
void check_killer (Character * ch, Character * victim)
{
  /*
   * Follow charm thread to responsible character.
   * Attacking someone's charmed char is hostile!
   */
  while (victim->is_affected (AFF_CHARM) && victim->master != NULL)
    victim = victim->master;

  /*
   * NPC's are fair game.
   * So are killers and thieves.
   */
  if (victim->is_npc ()
    || IS_SET (victim->actflags, PLR_KILLER)
    || IS_SET (victim->actflags, PLR_THIEF))
    return;

  /*
   * Charm-o-rama.
   */
  if (IS_SET (ch->affected_by, AFF_CHARM)) {
    if (ch->master == NULL) {
      bug_printf ("Check_killer: %s bad AFF_CHARM",
        ch->is_npc () ? ch->short_descr.c_str() : ch->name.c_str());
      ch->affect_strip (skill_lookup("charm person"));
      REMOVE_BIT (ch->affected_by, AFF_CHARM);
      return;
    }

    ch->master->send_to_char ("*** You are now a KILLER!! ***\r\n");
    SET_BIT (ch->master->actflags, PLR_KILLER);
    ch->stop_follower();
    return;
  }

  /*
   * NPC's are cool of course (as long as not charmed).
   * Hitting yourself is cool too (bleeding).
   * So is being immortal (Alander's idea).
   * And current killers stay as they are.
   */
  if (ch->is_npc ()
    || ch == victim
    || ch->level >= LEVEL_IMMORTAL || IS_SET (ch->actflags, PLR_KILLER))
    return;

  ch->send_to_char ("*** You are now a KILLER!! ***\r\n");
  SET_BIT (ch->actflags, PLR_KILLER);
  ch->save_char_obj();
  return;
}

/*
 * Check for parry.
 */
bool check_parry (Character * ch, Character * victim)
{
  int chance;

  if (!victim->is_awake ())
    return false;

  if (victim->is_npc ()) {
    /* Tuan was here.  :) */
    chance = std::min (60, 2 * victim->level);
  } else {
    if (victim->get_eq_char (WEAR_WIELD) == NULL)
      return false;
    chance = victim->pcdata->learned[skill_lookup("parry")] / 2;
  }

  if (number_percent () >= chance + victim->level - ch->level)
    return false;

  ch->act ("You parry $n's attack.", NULL, victim, TO_VICT);
  ch->act ("$N parries your attack.", NULL, victim, TO_CHAR);
  return true;
}

/*
 * Check for dodge.
 */
bool check_dodge (Character * ch, Character * victim)
{
  int chance;

  if (!victim->is_awake ())
    return false;

  if (victim->is_npc ())
    /* Tuan was here.  :) */
    chance = std::min (60, 2 * victim->level);
  else
    chance = victim->pcdata->learned[skill_lookup("dodge")] / 2;

  if (number_percent () >= chance + victim->level - ch->level)
    return false;

  ch->act ("You dodge $n's attack.", NULL, victim, TO_VICT);
  ch->act ("$N dodges your attack.", NULL, victim, TO_CHAR);
  return true;
}

/*
 * Make a corpse out of a character.
 */
void make_corpse (Character * ch)
{
  char buf[MAX_STRING_LENGTH];
  Object *corpse;
  Object *obj;
  std::string name;

  if (ch->is_npc ()) {
    name = ch->short_descr;
    corpse = get_obj_index(OBJ_VNUM_CORPSE_NPC)->create_object(0);
    corpse->timer = number_range (2, 4);
    if (ch->gold > 0) {
      create_money(ch->gold)->obj_to_obj(corpse);
      ch->gold = 0;
    }
  } else {
    name = ch->name;
    corpse = get_obj_index (OBJ_VNUM_CORPSE_PC)->create_object(0);
    corpse->timer = number_range (25, 40);
  }

  snprintf (buf, sizeof buf, corpse->short_descr.c_str(), name.c_str());
  corpse->short_descr = buf;

  snprintf (buf, sizeof buf, corpse->description.c_str(), name.c_str());
  corpse->description = buf;

  ObjIter o, onext;
  for (o = ch->carrying.begin(); o != ch->carrying.end(); o = onext) {
    obj = *o;
    onext = ++o;
    obj->obj_from_char();
    if (IS_SET (obj->extra_flags, ITEM_INVENTORY))
      obj->extract_obj ();
    else
      obj->obj_to_obj(corpse);
  }

  corpse->obj_to_room (ch->in_room);
  return;
}

/*
 * Improved Death_cry contributed by Diavolo.
 */
void death_cry (Character * ch)
{
  Room *was_in_room;
  const char *msg;
  int door;
  int vnum;

  vnum = 0;
  switch (number_range (0, 15)) {
  default:
    msg = "You hear $n's death cry.";
    break;
  case 0:
    msg = "$n hits the ground ... DEAD.";
    break;
  case 1:
    msg = "$n splatters blood on your armor.";
    break;
  case 2:
    msg = "You smell $n's sphincter releasing in death.";
    vnum = OBJ_VNUM_FINAL_TURD;
    break;
  case 3:
    msg = "$n's severed head plops on the ground.";
    vnum = OBJ_VNUM_SEVERED_HEAD;
    break;
  case 4:
    msg = "$n's heart is torn from $s chest.";
    vnum = OBJ_VNUM_TORN_HEART;
    break;
  case 5:
    msg = "$n's arm is sliced from $s dead body.";
    vnum = OBJ_VNUM_SLICED_ARM;
    break;
  case 6:
    msg = "$n's leg is sliced from $s dead body.";
    vnum = OBJ_VNUM_SLICED_LEG;
    break;
  }

  ch->act (msg, NULL, NULL, TO_ROOM);

  if (vnum != 0) {
    char buf[MAX_STRING_LENGTH];
    Object *obj;
    std::string name;

    name = ch->is_npc () ? ch->short_descr : ch->name;
    obj = get_obj_index(vnum)->create_object(0);
    obj->timer = number_range (4, 7);

    snprintf (buf, sizeof buf, obj->short_descr.c_str(), name.c_str());
    obj->short_descr = buf;

    snprintf (buf, sizeof buf, obj->description.c_str(), name.c_str());
    obj->description = buf;

    obj->obj_to_room (ch->in_room);
  }

  if (ch->is_npc ())
    msg = "You hear something's death cry.";
  else
    msg = "You hear someone's death cry.";

  was_in_room = ch->in_room;
  for (door = 0; door <= 5; door++) {
    Exit *pexit;

    if ((pexit = was_in_room->exit[door]) != NULL
      && pexit->to_room != NULL && pexit->to_room != was_in_room) {
      ch->in_room = pexit->to_room;
      ch->act (msg, NULL, NULL, TO_ROOM);
    }
  }
  ch->in_room = was_in_room;

  return;
}

void raw_kill (Character * victim)
{
  victim->stop_fighting(true);
  mprog_death_trigger (victim);
  make_corpse (victim);

  if (victim->is_npc ()) {
    victim->pIndexData->killed++;
    kill_table[URANGE (0, victim->level, MAX_LEVEL - 1)].killed++;
    victim->extract_char (true);
    return;
  }

  victim->extract_char (false);
  while (victim->affected.begin() != victim->affected.end())
    victim->affect_remove (*victim->affected.begin());
  victim->affected_by = 0;
  victim->armor = 100;
  victim->position = POS_RESTING;
  victim->hit = std::max (1, victim->hit);
  victim->mana = std::max (1, victim->mana);
  victim->move = std::max (1, victim->move);
  victim->save_char_obj();
  return;
}

/*
 * Compute xp for a kill.
 * Also adjust alignment of killer.
 * Edit this function to change xp computations.
 */
int xp_compute (Character * gch, Character * victim)
{
  int align;
  int xp;
  int extra;
  int level;
  int number;

  xp = 300 - URANGE (-3, gch->level - victim->level, 6) * 50;
  align = gch->alignment - victim->alignment;

  if (align > 500) {
    gch->alignment = std::min (gch->alignment + (align - 500) / 4, 1000);
    xp = 5 * xp / 4;
  } else if (align < -500) {
    gch->alignment = std::max (gch->alignment + (align + 500) / 4, -1000);
  } else {
    gch->alignment -= gch->alignment / 4;
    xp = 3 * xp / 4;
  }

  /*
   * Adjust for popularity of target:
   *   -1/8 for each target over  'par' (down to -100%)
   *   +1/8 for each target under 'par' (  up to + 25%)
   */
  level = URANGE (0, victim->level, MAX_LEVEL - 1);
  number = std::max (1, kill_table[level].number);
  extra = victim->pIndexData->killed - kill_table[level].killed / number;
  xp -= xp * URANGE (-2, extra, 8) / 8;

  xp = number_range (xp * 3 / 4, xp * 5 / 4);
  xp = std::max (0, xp);

  return xp;
}

void group_gain (Character * ch, Character * victim)
{
  char buf[MAX_STRING_LENGTH];
  Character *lch;
  int xp;
  int members;

  /*
   * Monsters don't get kill xp's or alignment changes.
   * P-killing doesn't help either.
   * Dying of mortal wounds or poison doesn't give xp to anyone!
   */
  if (ch->is_npc () || !victim->is_npc () || victim == ch)
    return;

  members = 0;
  CharIter gch;
  for (gch = ch->in_room->people.begin(); gch != ch->in_room->people.end(); gch++) {
    if (is_same_group (*gch, ch))
      members++;
  }

  if (members == 0) {
    bug_printf ("Group_gain: members.", members);
    members = 1;
  }

  lch = (ch->leader != NULL) ? ch->leader : ch;

  for (gch = ch->in_room->people.begin(); gch != ch->in_room->people.end(); gch++) {
    Object *obj;

    if (!is_same_group (*gch, ch))
      continue;

    if ((*gch)->level - lch->level >= 6) {
      (*gch)->send_to_char ("You are too high for this group.\r\n");
      continue;
    }

    if ((*gch)->level - lch->level <= -6) {
      (*gch)->send_to_char ("You are too low for this group.\r\n");
      continue;
    }

    xp = xp_compute (*gch, victim) / members;
    snprintf (buf, sizeof buf, "You receive %d experience points.\r\n", xp);
    (*gch)->send_to_char (buf);
    (*gch)->gain_exp(xp);

    ObjIter o, onext;
    for (o = ch->carrying.begin(); o != ch->carrying.end(); o = onext) {
      obj = *o;
      onext = ++o;
      if (obj->wear_loc == WEAR_NONE)
        continue;

      if ((obj->is_obj_stat(ITEM_ANTI_EVIL) && ch->is_evil ())
        || (obj->is_obj_stat(ITEM_ANTI_GOOD) && ch->is_good ())
        || (obj->is_obj_stat(ITEM_ANTI_NEUTRAL) && ch->is_neutral ())) {
        ch->act ("You are zapped by $p.", obj, NULL, TO_CHAR);
        ch->act ("$n is zapped by $p.", obj, NULL, TO_ROOM);
        obj->obj_from_char();
        obj->obj_to_room(ch->in_room);
      }
    }
  }

  return;
}

void dam_message (Character * ch, Character * victim, int dam, int dt)
{
  static const char * attack_table[] = {
    "hit",
    "slice", "stab", "slash", "whip", "claw",
    "blast", "pound", "crush", "grep", "bite",
    "pierce", "suction"
  };

  char buf1[256], buf2[256], buf3[256];
  const char *vs;
  const char *vp;
  std::string attack;
  char punct;

  if (dam == 0) {
    vs = "miss";
    vp = "misses";
  } else if (dam <= 4) {
    vs = "scratch";
    vp = "scratches";
  } else if (dam <= 8) {
    vs = "graze";
    vp = "grazes";
  } else if (dam <= 12) {
    vs = "hit";
    vp = "hits";
  } else if (dam <= 16) {
    vs = "injure";
    vp = "injures";
  } else if (dam <= 20) {
    vs = "wound";
    vp = "wounds";
  } else if (dam <= 24) {
    vs = "maul";
    vp = "mauls";
  } else if (dam <= 28) {
    vs = "decimate";
    vp = "decimates";
  } else if (dam <= 32) {
    vs = "devastate";
    vp = "devastates";
  } else if (dam <= 36) {
    vs = "maim";
    vp = "maims";
  } else if (dam <= 40) {
    vs = "MUTILATE";
    vp = "MUTILATES";
  } else if (dam <= 44) {
    vs = "DISEMBOWEL";
    vp = "DISEMBOWELS";
  } else if (dam <= 48) {
    vs = "EVISCERATE";
    vp = "EVISCERATES";
  } else if (dam <= 52) {
    vs = "MASSACRE";
    vp = "MASSACRES";
  } else if (dam <= 100) {
    vs = "*** DEMOLISH ***";
    vp = "*** DEMOLISHES ***";
  } else {
    vs = "*** ANNIHILATE ***";
    vp = "*** ANNIHILATES ***";
  }

  punct = (dam <= 24) ? '.' : '!';

  if (dt == TYPE_HIT) {
    snprintf (buf1, sizeof buf1, "$n %s $N%c", vp, punct);
    snprintf (buf2, sizeof buf2, "You %s $N%c", vs, punct);
    snprintf (buf3, sizeof buf3, "$n %s you%c", vp, punct);
  } else {
    if (dt >= 0 && dt < MAX_SKILL)
      attack = skill_table[dt].noun_damage;
    else if (dt >= TYPE_HIT
      && dt < (int) (TYPE_HIT + sizeof (attack_table) / sizeof (attack_table[0])))
      attack = attack_table[dt - TYPE_HIT];
    else {
      bug_printf ("Dam_message: bad dt %d.", dt);
      attack = attack_table[0];
    }

    snprintf (buf1, sizeof buf1, "$n's %s %s $N%c", attack.c_str(), vp, punct);
    snprintf (buf2, sizeof buf2, "Your %s %s $N%c", attack.c_str(), vp, punct);
    snprintf (buf3, sizeof buf3, "$n's %s %s you%c", attack.c_str(), vp, punct);
  }

  ch->act (buf1, NULL, victim, TO_NOTVICT);
  ch->act (buf2, NULL, victim, TO_CHAR);
  ch->act (buf3, NULL, victim, TO_VICT);

  return;
}

/*
 * Disarm a creature.
 * Caller must check for successful attack.
 */
void disarm (Character * ch, Character * victim)
{
  Object *obj;

  if ((obj = victim->get_eq_char (WEAR_WIELD)) == NULL)
    return;

  if (ch->get_eq_char (WEAR_WIELD) == NULL && number_percent() <= 50)
    return;

  ch->act ("$n DISARMS you!", NULL, victim, TO_VICT);
  ch->act ("You disarm $N!", NULL, victim, TO_CHAR);
  ch->act ("$n DISARMS $N!", NULL, victim, TO_NOTVICT);

  obj->obj_from_char();
  if (victim->is_npc ())
    obj->obj_to_char (victim);
  else
    obj->obj_to_room (victim->in_room);

  return;
}

/*
 * Trip a creature.
 * Caller must check for successful attack.
 */
void trip (Character * ch, Character * victim)
{
  if (victim->wait == 0) {
    ch->act ("$n trips you and you go down!", NULL, victim, TO_VICT);
    ch->act ("You trip $N and $N goes down!", NULL, victim, TO_CHAR);
    ch->act ("$n trips $N and $N goes down!", NULL, victim, TO_NOTVICT);

    ch->wait_state (2 * PULSE_VIOLENCE);
    victim->wait_state (2 * PULSE_VIOLENCE);
    victim->position = POS_RESTING;
  }

  return;
}

/*
 * Inflict damage from a hit.
 */
void damage (Character * ch, Character * victim, int dam, int dt)
{
  if (victim->position == POS_DEAD)
    return;

  /*
   * Stop up any residual loopholes.
   */
  if (dam > 1000) {
    bug_printf ("Damage: %d: more than 1000 points!", dam);
    dam = 1000;
  }

  if (victim != ch) {
    /*
     * Certain attacks are forbidden.
     * Most other attacks are returned.
     */
    if (is_safe (ch, victim))
      return;
    check_killer (ch, victim);

    if (victim->position > POS_STUNNED) {
      if (victim->fighting == NULL)
        victim->set_fighting(ch);
      victim->position = POS_FIGHTING;
    }

    if (victim->position > POS_STUNNED) {
      if (ch->fighting == NULL)
        ch->set_fighting(victim);

      /*
       * If victim is charmed, ch might attack victim's master.
       */
      if (ch->is_npc ()
        && victim->is_npc ()
        && victim->is_affected (AFF_CHARM)
        && victim->master != NULL
        && victim->master->in_room == ch->in_room && number_range(0, 7) == 0) {
        ch->stop_fighting(false);
        multi_hit (ch, victim->master, TYPE_UNDEFINED);
        return;
      }
    }

    /*
     * More charm stuff.
     */
    if (victim->master == ch)
      victim->stop_follower();

    /*
     * Inviso attacks ... not.
     */
    if (ch->is_affected (AFF_INVISIBLE)) {
      ch->affect_strip (skill_lookup("invis"));
      ch->affect_strip (skill_lookup("mass invis"));
      REMOVE_BIT (ch->affected_by, AFF_INVISIBLE);
      ch->act ("$n fades into existence.", NULL, NULL, TO_ROOM);
    }

    /*
     * Damage modifiers.
     */
    if (victim->is_affected (AFF_SANCTUARY))
      dam /= 2;

    if (victim->is_affected (AFF_PROTECT) && ch->is_evil ())
      dam -= dam / 4;

    if (dam < 0)
      dam = 0;

    /*
     * Check for disarm, trip, parry, and dodge.
     */
    if (dt >= TYPE_HIT) {
      if (ch->is_npc () && number_percent () < ch->level / 2)
        disarm (ch, victim);
      if (ch->is_npc () && number_percent () < ch->level / 2)
        trip (ch, victim);
      if (check_parry (ch, victim))
        return;
      if (check_dodge (ch, victim))
        return;
    }

    dam_message (ch, victim, dam, dt);
  }

  /*
   * Hurt the victim.
   * Inform the victim of his new state.
   */
  victim->hit -= dam;
  if (!victim->is_npc ()
    && victim->level >= LEVEL_IMMORTAL && victim->hit < 1)
    victim->hit = 1;
  victim->update_pos();

  switch (victim->position) {
  case POS_MORTAL:
    victim->act ("$n is mortally wounded, and will die soon, if not aided.",
      NULL, NULL, TO_ROOM);
    victim->send_to_char
      ("You are mortally wounded, and will die soon, if not aided.\r\n");
    break;

  case POS_INCAP:
    victim->act ("$n is incapacitated and will slowly die, if not aided.",
      NULL, NULL, TO_ROOM);
    victim->send_to_char
      ("You are incapacitated and will slowly die, if not aided.\r\n");
    break;

  case POS_STUNNED:
    victim->act ("$n is stunned, but will probably recover.",
      NULL, NULL, TO_ROOM);
    victim->send_to_char ("You are stunned, but will probably recover.\r\n");
    break;

  case POS_DEAD:
    victim->act ("$n is DEAD!!", 0, 0, TO_ROOM);
    victim->send_to_char ("You have been KILLED!!\r\n\r\n");
    break;

  default:
    if (dam > victim->max_hit / 4)
      victim->send_to_char ("That really did HURT!\r\n");
    if (victim->hit < victim->max_hit / 4)
      victim->send_to_char ("You sure are BLEEDING!\r\n");
    break;
  }

  /*
   * Sleep spells and extremely wounded folks.
   */
  if (!victim->is_awake ())
    victim->stop_fighting(false);

  /*
   * Payoff for killing things.
   */
  if (victim->position == POS_DEAD) {
    group_gain (ch, victim);

    if (!victim->is_npc ()) {
      log_printf ("%s killed by %s at %d", victim->name.c_str(),
        (ch->is_npc () ? ch->short_descr.c_str() : ch->name.c_str()), victim->in_room->vnum);

      /*
       * Dying penalty:
       * 1/2 way back to previous level.
       */
      if (victim->exp > 1000 * victim->level)
        victim->gain_exp((1000 * victim->level - victim->exp) / 2);
    }

    raw_kill (victim);

    if (!ch->is_npc () && victim->is_npc ()) {
      if (IS_SET (ch->actflags, PLR_AUTOLOOT))
        ch->do_get ("all corpse");
      else
        ch->do_look ("in corpse");

      if (IS_SET (ch->actflags, PLR_AUTOSAC))
        ch->do_sacrifice ("corpse");
    }

    return;
  }

  if (victim == ch)
    return;

  /*
   * Take care of link dead people.
   */
  if (!victim->is_npc () && victim->desc == NULL) {
    if (number_range (0, victim->wait) == 0) {
      victim->do_recall ("");
      return;
    }
  }

  /*
   * Wimp out?
   */
  if (victim->is_npc () && dam > 0) {
    if ((IS_SET (victim->actflags, ACT_WIMPY) && number_percent() <= 50
        && victim->hit < victim->max_hit / 2)
      || (victim->is_affected (AFF_CHARM) && victim->master != NULL
        && victim->master->in_room != victim->in_room))
      victim->do_flee ("");
  }

  if (!victim->is_npc ()
    && victim->hit > 0 && victim->hit <= victim->wimpy && victim->wait == 0)
    victim->do_flee ("");

  return;
}

/*
 * Hit one guy once.
 */
void one_hit (Character * ch, Character * victim, int dt)
{
  Object *wield;
  int victim_ac;
  int thac0;
  int thac0_00;
  int thac0_32;
  int dam;

  /*
   * Can't beat a dead char!
   * Guard against weird room-leavings.
   */
  if (victim->position == POS_DEAD || ch->in_room != victim->in_room)
    return;

  /*
   * Figure out the type of damage message.
   */
  wield = ch->get_eq_char (WEAR_WIELD);
  if (dt == TYPE_UNDEFINED) {
    dt = TYPE_HIT;
    if (wield != NULL && wield->item_type == ITEM_WEAPON)
      dt += wield->value[3];
  }

  /*
   * Calculate to-hit-armor-class-0 versus armor.
   */
  if (ch->is_npc ()) {
    thac0_00 = 20;
    thac0_32 = 0;
  } else {
    thac0_00 = class_table[ch->klass].thac0_00;
    thac0_32 = class_table[ch->klass].thac0_32;
  }
  thac0 = interpolate (ch->level, thac0_00, thac0_32) - ch->get_hitroll();
  victim_ac = std::max (-15, victim->get_ac() / 10);
  if (!ch->can_see(victim))
    victim_ac -= 4;

  /*
   * The moment of excitement!
   */
  int diceroll = number_range(0, 19);
  if (diceroll == 0 || (diceroll != 19 && diceroll < thac0 - victim_ac)) {
    /* Miss. */
    damage (ch, victim, 0, dt);
    return;
  }

  /*
   * Hit.
   * Calc damage.
   */
  if (ch->is_npc ()) {
    dam = number_range (ch->level / 2, ch->level * 3 / 2);
    if (wield != NULL)
      dam += dam / 2;
  } else {
    if (wield != NULL)
      dam = number_range (wield->value[1], wield->value[2]);
    else
      dam = number_range (1, 4);
  }

  /*
   * Bonuses.
   */
  dam += ch->get_damroll();
  int enh = skill_lookup("enhanced damage");
  if (!ch->is_npc () && ch->pcdata->learned[enh] > 0)
    dam += dam * ch->pcdata->learned[enh] / 150;
  if (!victim->is_awake ())
    dam *= 2;
  if (dt == skill_lookup("backstab"))
    dam *= 2 + ch->level / 8;

  if (dam <= 0)
    dam = 1;

  damage (ch, victim, dam, dt);
  return;
}

/*
 * Do one group of attacks.
 */
void multi_hit (Character * ch, Character * victim, int dt)
{
  int chance;

  one_hit (ch, victim, dt);
  if (ch->fighting != victim || dt == skill_lookup("backstab"))
    return;

  chance =
    ch->is_npc () ? ch->level : ch->pcdata->learned[skill_lookup("second attack")] / 2;
  if (number_percent () < chance) {
    one_hit (ch, victim, dt);
    if (ch->fighting != victim)
      return;
  }

  chance =
    ch->is_npc () ? ch->level : ch->pcdata->learned[skill_lookup("third attack")] / 4;
  if (number_percent () < chance) {
    one_hit (ch, victim, dt);
    if (ch->fighting != victim)
      return;
  }

  chance = ch->is_npc () ? ch->level / 2 : 0;
  if (number_percent () < chance)
    one_hit (ch, victim, dt);

  return;
}

/*
 * Utter mystical words for an sn.
 */
void say_spell (Character * ch, int sn)
{
  std::string mwords, buf, buf2;
  const char *pName;
  int iSyl;
  int length;

  struct syl_type {
    const char *old;
    const char *newsyl;
  };

  static const struct syl_type syl_table[] = {
    {" ", " "},
    {"ar", "abra"},
    {"au", "kada"},
    {"bless", "fido"},
    {"blind", "nose"},
    {"bur", "mosa"},
    {"cu", "judi"},
    {"de", "oculo"},
    {"en", "unso"},
    {"light", "dies"},
    {"lo", "hi"},
    {"mor", "zak"},
    {"move", "sido"},
    {"ness", "lacri"},
    {"ning", "illa"},
    {"per", "duda"},
    {"ra", "gru"},
    {"re", "candus"},
    {"son", "sabru"},
    {"tect", "infra"},
    {"tri", "cula"},
    {"ven", "nofo"},
    {"a", "a"}, {"b", "b"}, {"c", "q"}, {"d", "e"},
    {"e", "z"}, {"f", "y"}, {"g", "o"}, {"h", "p"},
    {"i", "u"}, {"j", "y"}, {"k", "t"}, {"l", "r"},
    {"m", "w"}, {"n", "i"}, {"o", "a"}, {"p", "s"},
    {"q", "d"}, {"r", "f"}, {"s", "g"}, {"t", "h"},
    {"u", "j"}, {"v", "z"}, {"w", "x"}, {"x", "n"},
    {"y", "l"}, {"z", "k"},
    {"", ""}
  };

  for (pName = skill_table[sn].name; *pName != '\0'; pName += length) {
    for (iSyl = 0; (length = strlen (syl_table[iSyl].old)) != 0; iSyl++) {
      if (!str_prefix (syl_table[iSyl].old, pName)) {
        mwords.append(syl_table[iSyl].newsyl);
        break;
      }
    }

    if (length == 0)
      length = 1;
  }

  buf = "$n utters the words, '";
  buf.append(skill_table[sn].name);
  buf.append("'.");
  buf2 = "$n utters the words, '";
  buf2.append(mwords);
  buf2.append("'.");

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if (*rch != ch)
      ch->act ( (ch->klass == (*rch)->klass ? buf : buf2).c_str(), NULL, *rch, TO_VICT);
  }

  return;
}

/*
 * Cast spells at targets using a magical object.
 */
void obj_cast_spell (int sn, int level, Character * ch, Character * victim,
  Object * obj)
{
  void *vo;

  if (sn <= 0)
    return;

  if (sn >= MAX_SKILL || skill_table[sn].spell_fun == 0) {
    bug_printf ("Obj_cast_spell: bad sn %d.", sn);
    return;
  }

  switch (skill_table[sn].target) {
  default:
    bug_printf ("Obj_cast_spell: bad target for sn %d.", sn);
    return;

  case TAR_IGNORE:
    vo = NULL;
    break;

  case TAR_CHAR_OFFENSIVE:
    if (victim == NULL)
      victim = ch->fighting;
    if (victim == NULL || !victim->is_npc ()) {
      ch->send_to_char ("You can't do that.\r\n");
      return;
    }
    vo = (void *) victim;
    break;

  case TAR_CHAR_DEFENSIVE:
    if (victim == NULL)
      victim = ch;
    vo = (void *) victim;
    break;

  case TAR_CHAR_SELF:
    vo = (void *) ch;
    break;

  case TAR_OBJ_INV:
    if (obj == NULL) {
      ch->send_to_char ("You can't do that.\r\n");
      return;
    }
    vo = (void *) obj;
    break;
  }

  target_name = "";
  (ch->*(skill_table[sn].spell_fun)) (sn, level, vo);

  if (skill_table[sn].target == TAR_CHAR_OFFENSIVE && victim->master != ch) {
    Character *vch;

    CharIter rch, next;
    for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch = next) {
      vch = *rch;
      next = ++rch;
      if (victim == vch && victim->fighting == NULL) {
        multi_hit (victim, ch, TYPE_UNDEFINED);
        break;
      }
    }
  }

  return;
}

/* Snarf a MOBprogram section from the area file.
 */
void load_mobprogs (std::ifstream & fp)
{
  char letter;
  MobPrototype *iMob;
  int value;
  MobProgram *original;
  MobProgram *working;

  for (;;)
    switch (letter = fread_letter (fp)) {
    default:
      fatal_printf ("Load_mobprogs: bad command '%c'.", letter);
      break;
    case 'S':
    case 's':
      fread_to_eol (fp);
      return;
    case '*':
      fread_to_eol (fp);
      break;
    case 'M':
    case 'm':
      value = fread_number (fp);
      if ((iMob = get_mob_index (value)) == NULL) {
        fatal_printf ("Load_mobprogs: vnum %d doesnt exist", value);
      }

      if ((original = iMob->mobprogs) != NULL)
        for ( ; original->next != NULL; original = original->next) ;
      working = new MobProgram();
      if (original)
        original->next = working;
      else
        iMob->mobprogs = working;
      working = mprog_file_read (fread_word (fp), working, iMob);
      working->next = NULL;
      fread_to_eol (fp);
      break;
    }
}

void mprog_percent_check (Character * mob, Character * actor, Object * obj,
  void *vo, int type)
{
  MobProgram *mprg;

  for (mprg = mob->pIndexData->mobprogs; mprg != NULL; mprg = mprg->next)
    if ((mprg->type & type)
      && (number_percent () < std::atoi (mprg->arglist.c_str()))) {
      mprog_driver (mprg->comlist, mob, actor, obj, vo);
      if (type != GREET_PROG && type != ALL_GREET_PROG)
        break;
    }

  return;

}

/* The triggers.. These are really basic, and since most appear only
 * once in the code (hmm. i think they all do) it would be more efficient
 * to substitute the code in and make the mprog_xxx_check routines global.
 * However, they are all here in one nice place at the moment to make it
 * easier to see what they look like. If you do substitute them back in,
 * make sure you remember to modify the variable names to the ones in the
 * trigger calls.
 */
void mprog_act_trigger (const std::string & buf, Character * mob, Character * ch,
  Object * obj, void *vo)
{
  if (mob == NULL || ch == NULL)
    return;

  MobProgramActList *tmp_act;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & ACT_PROG)) {
    tmp_act = new MobProgramActList();
    if (mob->mpactnum > 0)
      tmp_act->next = mob->mpact->next;
    else
      tmp_act->next = NULL;

    mob->mpact = tmp_act;
    mob->mpact->buf = buf;
    mob->mpact->ch = ch;
    mob->mpact->obj = obj;
    mob->mpact->vo = vo;
    mob->mpactnum++;

  }
  return;

}

void mprog_bribe_trigger (Character * mob, Character * ch, int amount)
{
  if (mob == NULL || ch == NULL)
    return;

  char buf[MAX_STRING_LENGTH];
  MobProgram *mprg;
  Object *obj;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & BRIBE_PROG)) {
    obj = get_obj_index (OBJ_VNUM_MONEY_SOME)->create_object(0);
    snprintf (buf, sizeof buf, obj->short_descr.c_str(), amount);
    obj->short_descr = buf;
    obj->value[0] = amount;
    obj->obj_to_char (mob);
    mob->gold -= amount;

    for (mprg = mob->pIndexData->mobprogs; mprg != NULL; mprg = mprg->next)
      if ((mprg->type & BRIBE_PROG)
        && (amount >= std::atoi (mprg->arglist.c_str()))) {
        mprog_driver (mprg->comlist, mob, ch, obj, NULL);
        break;
      }
  }

  return;

}

void mprog_death_trigger (Character * mob)
{
  if (mob == NULL)
    return;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & DEATH_PROG)) {
    mprog_percent_check (mob, NULL, NULL, NULL, DEATH_PROG);
  }

  death_cry (mob);
  return;

}

void mprog_entry_trigger (Character * mob)
{
  if (mob == NULL)
    return;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & ENTRY_PROG))
    mprog_percent_check (mob, NULL, NULL, NULL, ENTRY_PROG);

  return;

}

void mprog_fight_trigger (Character * mob, Character * ch)
{
  if (mob == NULL || ch == NULL)
    return;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & FIGHT_PROG))
    mprog_percent_check (mob, ch, NULL, NULL, FIGHT_PROG);

  return;

}

void mprog_give_trigger (Character * mob, Character * ch, Object * obj)
{
  if (mob == NULL || ch == NULL || obj == NULL)
    return;

  std::string buf;
  MobProgram *mprg;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & GIVE_PROG))
    for (mprg = mob->pIndexData->mobprogs; mprg != NULL; mprg = mprg->next) {
      one_argument (mprg->arglist, buf);
      if ((mprg->type & GIVE_PROG)
        && ((!str_cmp (obj->name, mprg->arglist))
          || (!str_cmp ("all", buf)))) {
        mprog_driver (mprg->comlist, mob, ch, obj, NULL);
        break;
      }
    }

  return;

}

void mprog_greet_trigger (Character * mob)
{
  if (mob == NULL)
    return;

  Room* rm = mob->in_room;
  Character* vmob;
  CharIter v;
  for (v = rm->people.begin(); v != rm->people.end(); v++) {
    vmob = *v;
    if (vmob->is_npc ()
      && (vmob->fighting == NULL)
      && vmob->is_awake ()) {
      if (mob != vmob && vmob->can_see(mob)
        && (vmob->pIndexData->progtypes & GREET_PROG)) {
        mprog_percent_check (vmob, mob, NULL, NULL, GREET_PROG);
      } else if (vmob->pIndexData->progtypes & ALL_GREET_PROG) {
        mprog_percent_check (vmob, mob, NULL, NULL, ALL_GREET_PROG);
      }
    }
  }
  return;
}

void mprog_hitprcnt_trigger (Character * mob, Character * ch)
{
  if (mob == NULL || ch == NULL)
    return;
  MobProgram *mprg;

  if (mob->is_npc ()
    && (mob->pIndexData->progtypes & HITPRCNT_PROG))
    for (mprg = mob->pIndexData->mobprogs; mprg != NULL; mprg = mprg->next)
      if ((mprg->type & HITPRCNT_PROG)
        && ((100 * mob->hit / mob->max_hit) < std::atoi (mprg->arglist.c_str()))) {
        mprog_driver (mprg->comlist, mob, ch, NULL, NULL);
        break;
      }

  return;

}

void mprog_random_trigger (Character * mob)
{
  if (mob == NULL)
    return;

  if (mob->pIndexData->progtypes & RAND_PROG)
    mprog_percent_check (mob, NULL, NULL, NULL, RAND_PROG);

  return;

}

void mprog_speech_trigger (const std::string & txt, Character * mob)
{
  if (mob == NULL)
    return;

  Room* rm = mob->in_room;
  Character* vmob;
  CharIter v;
  for (v = rm->people.begin(); v != rm->people.end(); v++) {
    vmob = *v;
    if (vmob->is_npc () && (vmob->pIndexData->progtypes & SPEECH_PROG))
      mprog_wordlist_check (txt, vmob, mob, NULL, NULL, SPEECH_PROG);
  }

  return;

}

/*
 * Core procedure for dragons.
 */
bool dragon (Character * ch, const char *spell_name)
{
  Character *victim = NULL;
  int sn;

  if (ch->position != POS_FIGHTING)
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if ((*rch)->fighting == ch && number_percent() <= 25) {
      victim = *rch;
      break;
    }
  }

  if (victim == NULL)
    return false;

  if ((sn = skill_lookup (spell_name)) < 0)
    return false;
  (ch->*(skill_table[sn].spell_fun)) (sn, ch->level, victim);
  return true;
}

/*
 * Special procedures for mobiles.
 */
bool spec_breath_any (Character * ch)
{
  if (ch->position != POS_FIGHTING)
    return false;

  switch (number_range(0, 7)) {
  case 0:
    return spec_breath_fire (ch);
  case 1:
  case 2:
    return spec_breath_lightning (ch);
  case 3:
    return spec_breath_gas (ch);
  case 4:
    return spec_breath_acid (ch);
  case 5:
  case 6:
  case 7:
    return spec_breath_frost (ch);
  }

  return false;
}

bool spec_breath_acid (Character * ch)
{
  return dragon (ch, "acid breath");
}

bool spec_breath_fire (Character * ch)
{
  return dragon (ch, "fire breath");
}

bool spec_breath_frost (Character * ch)
{
  return dragon (ch, "frost breath");
}

bool spec_breath_gas (Character * ch)
{
  int sn;

  if (ch->position != POS_FIGHTING)
    return false;

  if ((sn = skill_lookup ("gas breath")) < 0)
    return false;
  (ch->*(skill_table[sn].spell_fun)) (sn, ch->level, NULL);
  return true;
}

bool spec_breath_lightning (Character * ch)
{
  return dragon (ch, "lightning breath");
}

bool spec_cast_adept (Character * ch)
{
  Character *victim = NULL;

  if (!ch->is_awake ())
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if (*rch != ch && ch->can_see(*rch) && number_percent() <= 50) {
      victim = *rch;
      break;
    }
  }

  if (victim == NULL)
    return false;

  switch (number_range(0, 7)) {
  case 0:
    ch->act ("$n utters the word 'tehctah'.", NULL, NULL, TO_ROOM);
    ch->spell_armor (skill_lookup ("armor"), ch->level, victim);
    return true;

  case 1:
    ch->act ("$n utters the word 'nhak'.", NULL, NULL, TO_ROOM);
    ch->spell_bless (skill_lookup ("bless"), ch->level, victim);
    return true;

  case 2:
    ch->act ("$n utters the word 'yeruf'.", NULL, NULL, TO_ROOM);
    ch->spell_cure_blindness (skill_lookup ("cure blindness"),
      ch->level, victim);
    return true;

  case 3:
    ch->act ("$n utters the word 'garf'.", NULL, NULL, TO_ROOM);
    ch->spell_cure_light (skill_lookup ("cure light"), ch->level, victim);
    return true;

  case 4:
    ch->act ("$n utters the words 'rozar'.", NULL, NULL, TO_ROOM);
    ch->spell_cure_poison (skill_lookup ("cure poison"), ch->level, victim);
    return true;

  case 5:
    ch->act ("$n utters the words 'nadroj'.", NULL, NULL, TO_ROOM);
    ch->spell_refresh (skill_lookup ("refresh"), ch->level, victim);
    return true;

  }

  return false;
}

bool spec_cast_cleric (Character * ch)
{
  Character *victim = NULL;
  const char *spell;
  int sn;

  if (ch->position != POS_FIGHTING)
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if ((*rch)->fighting == ch && number_percent() <= 50) {
      victim = *rch;
      break;
    }
  }

  if (victim == NULL)
    return false;

  for (;;) {
    int min_level;

    switch (number_range(0, 15)) {
    case 0:
      min_level = 0;
      spell = "blindness";
      break;
    case 1:
      min_level = 3;
      spell = "cause serious";
      break;
    case 2:
      min_level = 7;
      spell = "earthquake";
      break;
    case 3:
      min_level = 9;
      spell = "cause critical";
      break;
    case 4:
      min_level = 10;
      spell = "dispel evil";
      break;
    case 5:
      min_level = 12;
      spell = "curse";
      break;
    case 6:
      min_level = 12;
      spell = "change sex";
      break;
    case 7:
      min_level = 13;
      spell = "flamestrike";
      break;
    case 8:
    case 9:
    case 10:
      min_level = 15;
      spell = "harm";
      break;
    default:
      min_level = 16;
      spell = "dispel magic";
      break;
    }

    if (ch->level >= min_level)
      break;
  }

  if ((sn = skill_lookup (spell)) < 0)
    return false;
  (ch->*(skill_table[sn].spell_fun)) (sn, ch->level, victim);
  return true;
}

bool spec_cast_judge (Character * ch)
{
  Character *victim = NULL;
  const char *spell;
  int sn;

  if (ch->position != POS_FIGHTING)
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if ((*rch)->fighting == ch && number_percent() <= 50) {
      victim = *rch;
      break;
    }
  }

  if (victim == NULL)
    return false;

  spell = "high explosive";
  if ((sn = skill_lookup (spell)) < 0)
    return false;
  (ch->*(skill_table[sn].spell_fun)) (sn, ch->level, victim);
  return true;
}

bool spec_cast_mage (Character * ch)
{
  Character *victim = NULL;
  const char *spell;
  int sn;

  if (ch->position != POS_FIGHTING)
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if ((*rch)->fighting == ch && number_percent() <= 50) {
      victim = *rch;
      break;
    }
  }

  if (victim == NULL)
    return false;

  for (;;) {
    int min_level;

    switch (number_range(0, 15)) {
    case 0:
      min_level = 0;
      spell = "blindness";
      break;
    case 1:
      min_level = 3;
      spell = "chill touch";
      break;
    case 2:
      min_level = 7;
      spell = "weaken";
      break;
    case 3:
      min_level = 8;
      spell = "teleport";
      break;
    case 4:
      min_level = 11;
      spell = "colour spray";
      break;
    case 5:
      min_level = 12;
      spell = "change sex";
      break;
    case 6:
      min_level = 13;
      spell = "energy drain";
      break;
    case 7:
    case 8:
    case 9:
      min_level = 15;
      spell = "fireball";
      break;
    default:
      min_level = 20;
      spell = "acid blast";
      break;
    }

    if (ch->level >= min_level)
      break;
  }

  if ((sn = skill_lookup (spell)) < 0)
    return false;
  (ch->*(skill_table[sn].spell_fun)) (sn, ch->level, victim);
  return true;
}

bool spec_cast_undead (Character * ch)
{
  Character *victim = NULL;
  const char *spell;
  int sn;

  if (ch->position != POS_FIGHTING)
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    if ((*rch)->fighting == ch && number_percent() <= 50) {
      victim = *rch;
      break;
    }
  }

  if (victim == NULL)
    return false;

  for (;;) {
    int min_level;

    switch (number_range(0, 15)) {
    case 0:
      min_level = 0;
      spell = "curse";
      break;
    case 1:
      min_level = 3;
      spell = "weaken";
      break;
    case 2:
      min_level = 6;
      spell = "chill touch";
      break;
    case 3:
      min_level = 9;
      spell = "blindness";
      break;
    case 4:
      min_level = 12;
      spell = "poison";
      break;
    case 5:
      min_level = 15;
      spell = "energy drain";
      break;
    case 6:
      min_level = 18;
      spell = "harm";
      break;
    case 7:
      min_level = 21;
      spell = "teleport";
      break;
    default:
      min_level = 24;
      spell = "gate";
      break;
    }

    if (ch->level >= min_level)
      break;
  }

  if ((sn = skill_lookup (spell)) < 0)
    return false;
  (ch->*(skill_table[sn].spell_fun)) (sn, ch->level, victim);
  return true;
}

bool spec_executioner (Character * ch)
{
  if (!ch->is_awake () || ch->fighting != NULL)
    return false;

  Character *victim = NULL;
  const char *crime = "";
  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {

    if (!(*rch)->is_npc () && IS_SET ((*rch)->actflags, PLR_KILLER)) {
      victim = *rch;
      crime = "KILLER";
      break;
    }

    if (!(*rch)->is_npc () && IS_SET ((*rch)->actflags, PLR_THIEF)) {
      victim = *rch;
      crime = "THIEF";
      break;
    }
  }

  if (victim == NULL)
    return false;

  char buf[MAX_STRING_LENGTH];
  snprintf (buf, sizeof buf, "%s is a %s!  PROTECT THE INNOCENT!  MORE BLOOOOD!!!",
    victim->name.c_str(), crime);
  ch->do_shout (buf);
  multi_hit (ch, victim, TYPE_UNDEFINED);
  get_mob_index(MOB_VNUM_CITYGUARD)->create_mobile()->char_to_room(ch->in_room);
  get_mob_index(MOB_VNUM_CITYGUARD)->create_mobile()->char_to_room(ch->in_room);
  return true;
}

bool spec_fido (Character * ch)
{
  if (!ch->is_awake ())
    return false;

  Object *corpse;
  Object *obj;
  ObjIter c, cnext;
  for (c = ch->in_room->contents.begin(); c != ch->in_room->contents.end(); c = cnext) {
    corpse = *c;
    cnext = ++c;
    if (corpse->item_type != ITEM_CORPSE_NPC)
      continue;

    ch->act ("$n savagely devours a corpse.", NULL, NULL, TO_ROOM);
    ObjIter o, onext;
    for (o = corpse->contains.begin(); o != corpse->contains.end(); o = onext) {
      obj = *o;
      onext = ++o;
      obj->obj_from_obj ();
      obj->obj_to_room (ch->in_room);
    }
    corpse->extract_obj ();
    return true;
  }

  return false;
}

bool spec_guard (Character * ch)
{
  if (!ch->is_awake () || ch->fighting != NULL)
    return false;

  Character *victim = NULL;
  int max_evil = 300;
  Character* ech = NULL;
  const char* crime = "";
  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {

    if (!(*rch)->is_npc () && IS_SET ((*rch)->actflags, PLR_KILLER)) {
      victim = *rch;
      crime = "KILLER";
      break;
    }

    if (!(*rch)->is_npc () && IS_SET ((*rch)->actflags, PLR_THIEF)) {
      victim = *rch;
      crime = "THIEF";
      break;
    }

    if ((*rch)->fighting != NULL
      && (*rch)->fighting != ch && (*rch)->alignment < max_evil) {
      max_evil = (*rch)->alignment;
      ech = *rch;
    }
  }

  if (victim != NULL) {
    char buf[MAX_STRING_LENGTH];
    snprintf (buf, sizeof buf, "%s is a %s!  PROTECT THE INNOCENT!!  BANZAI!!",
      victim->name.c_str(), crime);
    ch->do_shout (buf);
    multi_hit (ch, victim, TYPE_UNDEFINED);
    return true;
  }

  if (ech != NULL) {
    ch->act ("$n screams 'PROTECT THE INNOCENT!!  BANZAI!!",
      NULL, NULL, TO_ROOM);
    multi_hit (ch, ech, TYPE_UNDEFINED);
    return true;
  }

  return false;
}

bool spec_janitor (Character * ch)
{
  Object *trash;

  if (!ch->is_awake ())
    return false;

  ObjIter o, onext;
  for (o = ch->in_room->contents.begin(); o != ch->in_room->contents.end(); o = onext) {
    trash = *o;
    onext = ++o;
    if (!IS_SET (trash->wear_flags, ITEM_TAKE))
      continue;
    if (trash->item_type == ITEM_DRINK_CON
      || trash->item_type == ITEM_TRASH || trash->cost < 10) {
      ch->act ("$n picks up some trash.", NULL, NULL, TO_ROOM);
      trash->obj_from_room ();
      trash->obj_to_char (ch);
      return true;
    }
  }

  return false;
}

bool spec_mayor (Character * ch)
{
  static const char open_path[] =
    "W3a3003b33000c111d0d111Oe333333Oe22c222112212111a1S.";

  static const char close_path[] =
    "W3a3003b33000c111d0d111CE333333CE22c222112212111a1S.";

  static const char *path;
  static int pos;
  static bool move;

  if (!move) {
    if (g_world->hour() == 6) {
      path = open_path;
      move = true;
      pos = 0;
    }

    if (g_world->hour() == 20) {
      path = close_path;
      move = true;
      pos = 0;
    }
  }

  if (ch->fighting != NULL)
    return spec_cast_cleric (ch);
  if (!move || ch->position < POS_SLEEPING)
    return false;

  switch (path[pos]) {
  case '0':
  case '1':
  case '2':
  case '3':
    ch->move_char (path[pos] - '0');
    break;

  case 'W':
    ch->position = POS_STANDING;
    ch->act ("$n awakens and groans loudly.", NULL, NULL, TO_ROOM);
    break;

  case 'S':
    ch->position = POS_SLEEPING;
    ch->act ("$n lies down and falls asleep.", NULL, NULL, TO_ROOM);
    break;

  case 'a':
    ch->act ("$n says 'Hello Honey!'", NULL, NULL, TO_ROOM);
    break;

  case 'b':
    ch->act ("$n says 'What a view!  I must do something about that dump!'",
      NULL, NULL, TO_ROOM);
    break;

  case 'c':
    ch->act ("$n says 'Vandals!  Youngsters have no respect for anything!'",
      NULL, NULL, TO_ROOM);
    break;

  case 'd':
    ch->act ("$n says 'Good day, citizens!'", NULL, NULL, TO_ROOM);
    break;

  case 'e':
    ch->act ("$n says 'I hereby declare the city of Midgaard open!'",
      NULL, NULL, TO_ROOM);
    break;

  case 'E':
    ch->act ("$n says 'I hereby declare the city of Midgaard closed!'",
      NULL, NULL, TO_ROOM);
    break;

  case 'O':
    ch->do_unlock ("gate");
    ch->do_open ("gate");
    break;

  case 'C':
    ch->do_close ("gate");
    ch->do_lock ("gate");
    break;

  case '.':
    move = false;
    break;
  }

  pos++;
  return false;
}

bool spec_poison (Character * ch)
{
  Character *victim;

  if (ch->position != POS_FIGHTING
    || (victim = ch->fighting) == NULL || number_percent () > 2 * ch->level)
    return false;

  ch->act ("You bite $N!", NULL, victim, TO_CHAR);
  ch->act ("$n bites $N!", NULL, victim, TO_NOTVICT);
  ch->act ("$n bites you!", NULL, victim, TO_VICT);
  ch->spell_poison (skill_lookup("poison"), ch->level, victim);
  return true;
}

bool spec_thief (Character * ch)
{
  if (ch->position != POS_STANDING)
    return false;

  CharIter rch;
  for (rch = ch->in_room->people.begin(); rch != ch->in_room->people.end(); rch++) {
    Character* victim = *rch;

    if (victim->is_npc() || victim->level >= LEVEL_IMMORTAL ||
      number_percent() <= 75 || !ch->can_see(victim))      /* Thx Glop */
      continue;

    if (victim->is_awake () && number_range (0, ch->level) == 0) {
      ch->act ("You discover $n's hands in your wallet!",
        NULL, victim, TO_VICT);
      ch->act ("$N discovers $n's hands in $S wallet!",
        NULL, victim, TO_NOTVICT);
      return true;
    } else {
      int gold = victim->gold * number_range (1, 20) / 100;
      ch->gold += 7 * gold / 8;
      victim->gold -= gold;
      return true;
    }
  }

  return false;
}

/* This routine transfers between alpha and numeric forms of the
 *  mob_prog bitvector types. It allows the words to show up in mpstat to
 *  make it just a hair bit easier to see what a mob should be doing.
 */
const char *mprog_type_to_name (int type)
{
  switch (type) {
  case IN_FILE_PROG:
    return "in_file_prog";
  case ACT_PROG:
    return "act_prog";
  case SPEECH_PROG:
    return "speech_prog";
  case RAND_PROG:
    return "rand_prog";
  case FIGHT_PROG:
    return "fight_prog";
  case HITPRCNT_PROG:
    return "hitprcnt_prog";
  case DEATH_PROG:
    return "death_prog";
  case ENTRY_PROG:
    return "entry_prog";
  case GREET_PROG:
    return "greet_prog";
  case ALL_GREET_PROG:
    return "all_greet_prog";
  case GIVE_PROG:
    return "give_prog";
  case BRIBE_PROG:
    return "bribe_prog";
  default:
    return "ERROR_PROG";
  }
}

/* A trivial rehack of do_mstat.  This doesnt show all the data, but just
 * enough to identify the mob and give its basic condition.  It does however,
 * show the MOBprograms which are set.
 */
void Character::do_mpstat (std::string argument)
{
  char buf[MAX_STRING_LENGTH];
  std::string arg;
  MobProgram *mprg;
  Character *victim;

  one_argument (argument, arg);

  if (arg.empty()) {
    send_to_char ("MobProg stat whom?\r\n");
    return;
  }

  if ((victim = get_char_world (arg)) == NULL) {
    send_to_char ("They aren't here.\r\n");
    return;
  }

  if (!victim->is_npc ()) {
    send_to_char ("Only Mobiles can have Programs!\r\n");
    return;
  }

  if (!(victim->pIndexData->progtypes)) {
    send_to_char ("That Mobile has no Programs set.\r\n");
    return;
  }

  snprintf (buf, sizeof buf, "Name: %s.  Vnum: %d.\r\n",
    victim->name.c_str(), victim->pIndexData->vnum);
  send_to_char (buf);

  snprintf (buf, sizeof buf, "Short description: %s.\r\nLong  description: %s",
    victim->short_descr.c_str(),
    !victim->long_descr.empty() ? victim->long_descr.c_str() : "(none).\r\n");
  send_to_char (buf);

  snprintf (buf, sizeof buf, "Hp: %d/%d.  Mana: %d/%d.  Move: %d/%d. \r\n",
    victim->hit, victim->max_hit,
    victim->mana, victim->max_mana, victim->move, victim->max_move);
  send_to_char (buf);

  snprintf (buf, sizeof buf,
    "Lv: %d.  Class: %d.  Align: %d.  AC: %d.  Gold: %d.  Exp: %d.\r\n",
    victim->level, victim->klass, victim->alignment,
    victim->get_ac(), victim->gold, victim->exp);
  send_to_char (buf);

  for (mprg = victim->pIndexData->mobprogs; mprg != NULL; mprg = mprg->next) {
    snprintf (buf, sizeof buf, ">%s %s\r\n%s\r\n",
      mprog_type_to_name (mprg->type), mprg->arglist.c_str(), mprg->comlist.c_str());
    send_to_char (buf);
  }

  return;

}

/* prints the argument to all the rooms aroud the mobile */
void Character::do_mpasound (std::string argument)
{

  Room *was_in_rm;
  int door;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  if (argument.empty()) {
    bug_printf ("Mpasound - No argument from vnum %d.", pIndexData->vnum);
    return;
  }

  was_in_rm = in_room;
  for (door = 0; door <= 5; door++) {
    Exit *pexit;

    if ((pexit = was_in_rm->exit[door]) != NULL
      && pexit->to_room != NULL && pexit->to_room != was_in_rm) {
      in_room = pexit->to_room;
      MOBtrigger = false;
      act (argument, NULL, NULL, TO_ROOM);
    }
  }

  in_room = was_in_rm;
  return;

}

/* lets the mobile kill any player or mobile without murder*/
void Character::do_mpkill (std::string argument)
{
  std::string arg;
  Character *victim;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  one_argument (argument, arg);

  if (arg.empty()) {
    bug_printf ("MpKill - No argument from vnum %d.", pIndexData->vnum);
    return;
  }

  if ((victim = get_char_room (arg)) == NULL) {
    bug_printf ("MpKill - Victim not in room from vnum %d.", pIndexData->vnum);
    return;
  }

  if (victim == this) {
    bug_printf ("MpKill - Victim is self from vnum %d.", pIndexData->vnum);
    return;
  }

  if (is_affected (AFF_CHARM) && master == victim) {
    bug_printf ("MpKill - Charmed mob attacking master from vnum %d.",
      pIndexData->vnum);
    return;
  }

  if (position == POS_FIGHTING) {
    bug_printf ("MpKill - Already fighting from vnum %d", pIndexData->vnum);
    return;
  }

  multi_hit (this, victim, TYPE_UNDEFINED);
  return;
}

/* lets the mobile destroy an object in its inventory
   it can also destroy a worn object and it can destroy
   items using all.xxxxx or just plain all of them */
void Character::do_mpjunk (std::string argument)
{
  std::string arg;
  Object *obj;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  one_argument (argument, arg);

  if (arg.empty()) {
    bug_printf ("Mpjunk - No argument from vnum %d.", pIndexData->vnum);
    return;
  }

  if (str_cmp (arg, "all") && str_prefix ("all.", arg)) {
    if ((obj = get_obj_wear (arg)) != NULL) {
      unequip_char(obj);
      obj->extract_obj ();
      return;
    }
    if ((obj = get_obj_carry (arg)) == NULL)
      return;
    obj->extract_obj ();
  } else {
    ObjIter o, onext;
    for (o = carrying.begin(); o != carrying.end(); o = onext) {
      obj = *o;
      onext = ++o;
      if (arg[3] == '\0' || is_name (&arg[4], obj->name)) {
        if (obj->wear_loc != WEAR_NONE)
          unequip_char(obj);
        obj->extract_obj ();
      }
    }
  }

  return;

}

/* prints the message to everyone in the room other than the mob and victim */
void Character::do_mpechoaround (std::string argument)
{
  std::string arg;
  Character *victim;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  argument = one_argument (argument, arg);

  if (arg.empty()) {
    bug_printf ("Mpechoaround - No argument from vnum %d.", pIndexData->vnum);
    return;
  }

  if (!(victim = get_char_room (arg))) {
    bug_printf ("Mpechoaround - Victim does not exist from vnum %d.",
      pIndexData->vnum);
    return;
  }

  act (argument, NULL, victim, TO_NOTVICT);
  return;
}

/* prints the message to only the victim */
void Character::do_mpechoat (std::string argument)
{
  std::string arg;
  Character *victim;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  argument = one_argument (argument, arg);

  if (arg.empty() || argument.empty()) {
    bug_printf ("Mpechoat - No argument from vnum %d.", pIndexData->vnum);
    return;
  }

  if (!(victim = get_char_room (arg))) {
    bug_printf ("Mpechoat - Victim does not exist from vnum %d.",
      pIndexData->vnum);
    return;
  }

  act (argument, NULL, victim, TO_VICT);
  return;
}

/* prints the message to the room at large */
void Character::do_mpecho (std::string argument)
{
  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  if (argument.empty()) {
    bug_printf ("Mpecho - Called w/o argument from vnum %d.", pIndexData->vnum);
    return;
  }

  act (argument, NULL, NULL, TO_ROOM);
  return;

}

/* lets the mobile load an item or mobile.  All items
are loaded into inventory.  you can specify a level with
the load object portion as well. */
void Character::do_mpmload (std::string argument)
{
  std::string arg;
  MobPrototype *pMobIndex;
  Character *victim;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  one_argument (argument, arg);

  if (arg.empty() || !is_number (arg)) {
    bug_printf ("Mpmload - Bad vnum as arg from vnum %d.", pIndexData->vnum);
    return;
  }

  if ((pMobIndex = get_mob_index (std::atoi (arg.c_str()))) == NULL) {
    bug_printf ("Mpmload - Bad mob vnum from vnum %d.", pIndexData->vnum);
    return;
  }

  victim = pMobIndex->create_mobile ();
  victim->char_to_room(in_room);
  return;
}

void Character::do_mpoload (std::string argument)
{
  std::string arg1, arg2;
  ObjectPrototype *pObjIndex;
  Object *obj;
  int lvl;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  argument = one_argument (argument, arg1);
  argument = one_argument (argument, arg2);

  if (arg1.empty() || !is_number (arg1)) {
    bug_printf ("Mpoload - Bad syntax from vnum %d.", pIndexData->vnum);
    return;
  }

  if (arg2.empty()) {
    lvl = get_trust ();
  } else {
    /*
     * New feature from Alander.
     */
    if (!is_number (arg2)) {
      bug_printf ("Mpoload - Bad syntax from vnum %d.", pIndexData->vnum);
      return;
    }
    lvl = std::atoi (arg2.c_str());
    if (lvl < 0 || lvl > get_trust ()) {
      bug_printf ("Mpoload - Bad level from vnum %d.", pIndexData->vnum);
      return;
    }
  }

  if ((pObjIndex = get_obj_index (std::atoi (arg1.c_str()))) == NULL) {
    bug_printf ("Mpoload - Bad vnum arg from vnum %d.", pIndexData->vnum);
    return;
  }

  obj = pObjIndex->create_object (lvl);
  if (obj->can_wear(ITEM_TAKE)) {
    obj->obj_to_char (this);
  } else {
    obj->obj_to_room (in_room);
  }

  return;
}

/* lets the mobile purge all objects and other npcs in the room,
   or purge a specified object or mob in the room.  It can purge
   itself, but this had best be the last command in the MOBprogram
   otherwise ugly stuff will happen */
void Character::do_mppurge (std::string argument)
{
  std::string arg;
  Character *victim;
  Object *obj;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  one_argument (argument, arg);

  if (arg.empty()) {
    /* 'purge' */

    CharIter rch, rnext;
    for (rch = in_room->people.begin(); rch != in_room->people.end(); rch = rnext) {
      victim = *rch;
      rnext = ++rch;
      if (victim->is_npc () && victim != this)
        victim->extract_char (true);
    }

    ObjIter o, onext;
    for (o = in_room->contents.begin(); o != in_room->contents.end(); o = onext) {
      obj = *o;
      onext = ++o;
      obj->extract_obj ();
    }

    return;
  }

  if ((victim = get_char_room (arg)) == NULL) {
    if ((obj = get_obj_here (arg))) {
      obj->extract_obj ();
    } else {
      bug_printf ("Mppurge - Bad argument from vnum %d.", pIndexData->vnum);
    }
    return;
  }

  if (!victim->is_npc ()) {
    bug_printf ("Mppurge - Purging a PC from vnum %d.", pIndexData->vnum);
    return;
  }

  victim->extract_char (true);
  return;
}

/* lets the mobile goto any location it wishes that is not private */
void Character::do_mpgoto (std::string argument)
{
  std::string arg;
  Room *location;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  one_argument (argument, arg);
  if (arg.empty()) {
    bug_printf ("Mpgoto - No argument from vnum %d.", pIndexData->vnum);
    return;
  }

  if ((location = find_location (this, arg)) == NULL) {
    bug_printf ("Mpgoto - No such location from vnum %d.", pIndexData->vnum);
    return;
  }

  if (fighting != NULL)
    stop_fighting (true);

  char_from_room();
  char_to_room(location);

  return;
}

/* lets the mobile do a command at another location. Very useful */
void Character::do_mpat (std::string argument)
{
  std::string arg;
  Room *location;
  Room *original;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  argument = one_argument (argument, arg);

  if (arg.empty() || argument.empty()) {
    bug_printf ("Mpat - Bad argument from vnum %d.", pIndexData->vnum);
    return;
  }

  if ((location = find_location (this, arg)) == NULL) {
    bug_printf ("Mpat - No such location from vnum %d.", pIndexData->vnum);
    return;
  }

  original = in_room;
  char_from_room();
  char_to_room(location);
  interpret (argument);

  /*
   * See if 'this' still exists before continuing!
   * Handles 'at XXXX quit' case.
   */
  for (CharIter c = char_list.begin(); c != char_list.end(); c++) {
    if (*c == this) {
      char_from_room();
      char_to_room(original);
      break;
    }
  }

  return;
}

/* lets the mobile transfer people.  the all argument transfers
   everyone in the current room to the specified location */
void Character::do_mptransfer (std::string argument)
{
  std::string arg1, arg2;
  Room *location;
  Character *victim;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }
  argument = one_argument (argument, arg1);
  argument = one_argument (argument, arg2);

  if (arg1.empty()) {
    bug_printf ("Mptransfer - Bad syntax from vnum %d.", pIndexData->vnum);
    return;
  }

  if (!str_cmp (arg1, "all")) {
    for (DescIter d = descriptor_list.begin(); d != descriptor_list.end(); d++) {
      if ((*d)->connected == CON_PLAYING
        && (*d)->character != this
        && (*d)->character->in_room != NULL && can_see((*d)->character)) {
        char buf[MAX_STRING_LENGTH];
        snprintf (buf, sizeof buf, "%s %s", (*d)->character->name.c_str(), arg2.c_str());
        do_transfer (buf);
      }
    }
    return;
  }

  /*
   * Thanks to Grodyn for the optional location parameter.
   */
  if (arg2.empty()) {
    location = in_room;
  } else {
    if ((location = find_location (this, arg2)) == NULL) {
      bug_printf ("Mptransfer - No such location from vnum %d.",
        pIndexData->vnum);
      return;
    }

    if (location->is_private()) {
      bug_printf ("Mptransfer - Private room from vnum %d.", pIndexData->vnum);
      return;
    }
  }

  if ((victim = get_char_world (arg1)) == NULL) {
    bug_printf ("Mptransfer - No such person from vnum %d.", pIndexData->vnum);
    return;
  }

  if (victim->in_room == NULL) {
    bug_printf ("Mptransfer - Victim in Limbo from vnum %d.", pIndexData->vnum);
    return;
  }

  if (victim->fighting != NULL)
    victim->stop_fighting (true);

  victim->char_from_room();
  victim->char_to_room(location);

  return;
}

/* lets the mobile force someone to do something.  must be mortal level
   and the all argument only affects those in the room with the mobile */
void Character::do_mpforce (std::string argument)
{
  std::string arg;

  if (!is_npc ()) {
    send_to_char ("Huh?\r\n");
    return;
  }

  argument = one_argument (argument, arg);

  if (arg.empty() || argument.empty()) {
    bug_printf ("Mpforce - Bad syntax from vnum %d.", pIndexData->vnum);
    return;
  }

  if (!str_cmp (arg, "all")) {
    Character *vch;

    CharIter c, next;
    for (c = char_list.begin(); c != char_list.end(); c = next) {
      vch = *c;
      next = ++c;
      if (vch->in_room == in_room && vch->get_trust () < get_trust ()
        && can_see(vch)) {
        vch->interpret (argument);
      }
    }
  } else {
    Character *victim;

    if ((victim = get_char_room (arg)) == NULL) {
      bug_printf ("Mpforce - No such victim from vnum %d.", pIndexData->vnum);
      return;
    }

    if (victim == this) {
      bug_printf ("Mpforce - Forcing oneself from vnum %d.", pIndexData->vnum);
      return;
    }

    victim->interpret (argument);
  }

  return;
}

void new_descriptor (void)
{
  Descriptor *dnew;
  struct sockaddr_in sock;
  SOCKET desc;
#ifndef WIN32
  socklen_t size;
#else
  int size;
  unsigned long flags = 1;
#endif

  size = sizeof (sock);
  getsockname (g_listen, (struct sockaddr *) &sock, &size);
  if ((desc = accept (g_listen, (struct sockaddr *) &sock, &size)) == INVALID_SOCKET) {
    std::perror ("New_descriptor: accept");
    return;
  }
#if !defined(FNDELAY)
#define FNDELAY O_NDELAY
#endif

#ifdef WIN32
  if (ioctlsocket (desc, FIONBIO, &flags)) {
#else
  if (fcntl (desc, F_SETFL, FNDELAY) == -1) {
#endif
    std::perror ("New_descriptor: fcntl: FNDELAY");
    return;
  }

  /*
   * Cons a new descriptor.
   */
  dnew = new Descriptor(desc);
  dnew->assign_hostname();

  /*
   * Swiftest: I added the following to ban sites.  I don't
   * endorse banning of sites, but Copper has few descriptors now
   * and some people from certain sites keep abusing access by
   * using automated 'autodialers' and leaving connections hanging.
   *
   * Furey: added suffix check by request of Nickel of HiddenWorlds.
   */
  for (std::list<Ban*>::iterator pban = ban_list.begin();
    pban != ban_list.end(); pban++) {
    if (str_suffix ((*pban)->name, dnew->host)) {
      write_to_descriptor (desc,
        "Your site has been banned from this Mud.\r\n", 0);
      closesocket (desc);
      delete dnew;
      return;
    }
  }

  descriptor_list.push_back(dnew);

  /*
   * Send the greeting.
   */
  dnew->write_to_buffer (help_greeting);
  return;
}

void game_loop (void)
{
  static struct timeval null_time;
  struct timeval last_time;

#ifndef WIN32
  signal (SIGPIPE, SIG_IGN);
#endif
  gettimeofday (&last_time, NULL);
  g_world->set_current_time(last_time.tv_sec);

  /* Main loop */
  while (!merc_down) {
    fd_set in_set;
    fd_set out_set;
    fd_set exc_set;
#ifdef WIN32
    fd_set dummy_set;
#endif
    SOCKET maxdesc;

    /*
     * Poll all active descriptors.
     */
    FD_ZERO (&in_set);
    FD_ZERO (&out_set);
    FD_ZERO (&exc_set);
    FD_SET (g_listen, &in_set);
#ifdef WIN32
    FD_ZERO (&dummy_set);
    FD_SET (g_listen, &dummy_set);
#endif
    maxdesc = g_listen;
    DescIter d;
    for (d = descriptor_list.begin(); d != descriptor_list.end(); d++) {
      maxdesc = std::max (maxdesc, (*d)->descriptor);
      FD_SET ((*d)->descriptor, &in_set);
      FD_SET ((*d)->descriptor, &out_set);
      FD_SET ((*d)->descriptor, &exc_set);
    }

    if (select (maxdesc + 1, &in_set, &out_set, &exc_set, &null_time) == SOCKET_ERROR) {
      fatal_printf ("Game_loop: select: poll");
    }

    /*
     * New connection?
     */
    if (FD_ISSET (g_listen, &in_set))
      new_descriptor ();

    /*
     * Kick out the freaky folks.
     */
    for (d = descriptor_list.begin(); d != descriptor_list.end(); d = deepdenext) {
      Descriptor* d_this = *d;
      deepdenext = ++d;
      if (FD_ISSET (d_this->descriptor, &exc_set)) {
        FD_CLR (d_this->descriptor, &in_set);
        FD_CLR (d_this->descriptor, &out_set);
        if (d_this->character)
          d_this->character->save_char_obj();
        d_this->outbuf.erase();
        d_this->close_socket();
      }
    }

    /*
     * Process input.
     */
    for (d = descriptor_list.begin(); d != descriptor_list.end(); d = deepdenext) {
      Descriptor* d_this = *d;
      deepdenext = ++d;
      d_this->fcommand = false;

      if (FD_ISSET (d_this->descriptor, &in_set)) {
        if (d_this->character != NULL)
          d_this->character->timer = 0;
        if (!d_this->read_from_descriptor()) {
          FD_CLR (d_this->descriptor, &out_set);
          if (d_this->character != NULL)
            d_this->character->save_char_obj();
          d_this->outbuf.erase();
          d_this->close_socket();
          continue;
        }
      }

      if (d_this->character != NULL && d_this->character->wait > 0) {
        --d_this->character->wait;
        continue;
      }

      d_this->read_from_buffer();
      if (!d_this->incomm.empty()) {
        d_this->fcommand = true;
        if (d_this->character)
          d_this->character->stop_idling();

        if (d_this->connected == CON_PLAYING)
          if (d_this->showstr_point)
            d_this->show_string (d_this->incomm);
          else
            d_this->character->interpret (d_this->incomm);
        else
          d_this->nanny (d_this->incomm);

      }
    }

    /*
     * Autonomous game motion.
     */
    update_handler ();

    /*
     * Output.
     */
    for (d = descriptor_list.begin(); d != descriptor_list.end(); d = deepdenext) {
      Descriptor* d_this = *d;
      deepdenext = ++d;
      if ((d_this->fcommand || !d_this->outbuf.empty())
        && FD_ISSET (d_this->descriptor, &out_set)) {
        if (!d_this->process_output(true)) {
          if (d_this->character != NULL)
            d_this->character->save_char_obj();
          d_this->outbuf.erase();
          d_this->close_socket();
        }
      }
    }

    /*
     * Synchronize to a clock.
     * Sleep( last_time + 1/PULSE_PER_SECOND - now ).
     * Careful here of signed versus unsigned arithmetic.
     */
    {
      struct timeval now_time;
      long secDelta;
      long usecDelta;

      gettimeofday (&now_time, NULL);
      usecDelta = ((int) last_time.tv_usec) - ((int) now_time.tv_usec)
        + 1000000 / PULSE_PER_SECOND;
      secDelta = ((int) last_time.tv_sec) - ((int) now_time.tv_sec);
      while (usecDelta < 0) {
        usecDelta += 1000000;
        secDelta -= 1;
      }

      while (usecDelta >= 1000000) {
        usecDelta -= 1000000;
        secDelta += 1;
      }

      if (secDelta > 0 || (secDelta == 0 && usecDelta > 0)) {
        struct timeval stall_time;

        stall_time.tv_usec = usecDelta;
        stall_time.tv_sec = secDelta;
#ifdef WIN32   /* windows select demands a valid fd_set */
        if (select (0, NULL, NULL, &dummy_set, &stall_time) == SOCKET_ERROR) {
#else
        if (select (0, NULL, NULL, NULL, &stall_time) == SOCKET_ERROR) {
#endif
          fatal_printf ("Game_loop: select: stall");
        }
      }
    }

    gettimeofday (&last_time, NULL);
    g_world->set_current_time(last_time.tv_sec);
  }

  return;
}

/*
 * Parse a name for acceptability.
 */
bool check_parse_name (const std::string & name)
{
  /*
   * Reserved words.
   */
  if (is_name (name, "all auto immortal self someone"))
    return false;

  /*
   * Length restrictions.
   */
  if (name.size() < 3 || name.size() > 12)
    return false;

  /*
   * Alphanumerics only.
   * Lock out IllIll twits.
   */
  std::string::const_iterator pc;
  bool fIll = true;
  for (pc = name.begin(); pc != name.end(); pc++) {
    if (!isalpha (*pc))
      return false;
    if (tolower (*pc) != 'i' && tolower (*pc) != 'l')
      fIll = false;
  }

  if (fIll)
    return false;

  /*
   * Prevent players from naming themselves after mobs.
   */
  std::map<int,MobPrototype*>::iterator pmob;
  for (pmob = mob_table.begin(); pmob != mob_table.end(); pmob++) {
    if (is_name (name, (*pmob).second->name))
      return false;
  }

  return true;
}

int init_server_socket (void)
{
  SOCKET fd;

  if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == INVALID_SOCKET) {
    fatal_printf("Init_socket: socket");
  }

  int x = 1;
  if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (char *) &x, sizeof (x)) == SOCKET_ERROR) {
    closesocket (fd);
    fatal_printf ("Init_socket: SO_REUSEADDR");
  }

#ifdef SO_DONTLINGER
  struct linger ld;
  ld.l_onoff = 1;
  ld.l_linger = 1000;
  if (setsockopt (fd, SOL_SOCKET, SO_DONTLINGER, (char *) &ld, sizeof (ld)) == SOCKET_ERROR) {
    closesocket (fd);
    fatal_printf ("Init_socket: SO_DONTLINGER");
  }
#endif

  static struct sockaddr_in sa_zero;
  struct sockaddr_in sa;
  sa = sa_zero;
  sa.sin_family = AF_INET;
  sa.sin_port = htons (g_port);

  if (bind (fd, (struct sockaddr *) &sa, sizeof (sa)) < 0) {
    closesocket (fd);
    fatal_printf ("Init_socket: bind");
  }

  if (listen (fd, 3) < 0) {
    closesocket (fd);
    fatal_printf ("Init_socket: listen");
  }

  return fd;
}

#if defined(WIN32) && !defined(__DMC__)
void hotboot(void) {
  WSAPROTOCOL_INFO proto_info;
  int count_users = 0;
  std::FILE* fp;

  // Open events created by parent server
  void * file_event = OpenEvent(SYNCHRONIZE, FALSE, "file_created");
  if (file_event == NULL) {
    win_errprint("Error opening event, file_created");
    return;
  }

  void * shutdown_event = OpenEvent(EVENT_MODIFY_STATE, FALSE, "ok_to_shutdown");
  if (shutdown_event == NULL) {
    win_errprint("Error opening event, ok_to_shutdown");
    CloseHandle(file_event);
    return;
  }

  // Wait for parent server to build copyover file
  if (WaitForSingleObject(file_event, INFINITE) == WAIT_FAILED) {
    win_errprint("Error waiting on file_event");
    CloseHandle(file_event);
    CloseHandle(shutdown_event);
    return;
  }

  if ((fp = std::fopen ("hotboot.$$$", "r+b")) == NULL) {
    win_errprint("Error opening hotboot file");
    CloseHandle(file_event);
    CloseHandle(shutdown_event);
    return;
  }

  fread(&count_users, sizeof(int),1,fp); // how many users?
  // read in info about listening socket and build it
  fread(&proto_info,sizeof(WSAPROTOCOL_INFO),1,fp);
  g_listen = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
    &proto_info, 0, 0);
  if (g_listen == INVALID_SOCKET) {
    bug_printf ("Error opening listening socket : %d.", WSAGetLastError());
  }

  // read in info about users and build sockets for them
  for (int i = 0; i < count_users; i++) {
    char chname[25];
    fread(&proto_info,sizeof(WSAPROTOCOL_INFO),1,fp);
    fread(chname,sizeof(chname),1,fp);

    SOCKET sock = WSASocket(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO,
      &proto_info, 0, 0);
    if (sock == INVALID_SOCKET) {
      bug_printf ("Error opening user socket : %d.", WSAGetLastError());
      continue;
    }
    if (!write_to_descriptor (sock, "Returning from hotboot.\r\n", 0)) {
      bug_printf ("Error writing user socket");
      closesocket(sock);
      continue;
    }

    Descriptor * d = new Descriptor(sock);
    d->assign_hostname();
    descriptor_list.push_back(d);
    if (!d->load_char_obj (chname)) {
      write_to_descriptor (sock, "Your character vanished during hotboot. Bye.\r\n", 0);
      bug_printf ("Error loading character %s after hotboot", chname);
      d->close_socket();
      continue;
    }
    d->connected = CON_PLAYING;
    char_list.push_back(d->character);

    write_to_descriptor (sock, "Hotboot recovery complete.\r\n", 0);

    if (!d->character->in_room)
      d->character->in_room = get_room_index (ROOM_VNUM_TEMPLE);

    d->character->char_to_room (d->character->in_room);
    d->character->do_look("");
    d->character->act ("$n materializes!", NULL, NULL, TO_ROOM);
  }
  fclose(fp);
//  if (remove("hotboot.$$$")) {
//    win_errprint("Error removing hotboot file");
//  }

  // tell parent its ok to go away
  if (SetEvent(shutdown_event) == NULL) {
    win_errprint("Error setting ok_to_shutdown event");
  }

  // cleanup event handles
  CloseHandle(file_event);
  CloseHandle(shutdown_event);
}
#endif

int main (int argc, char **argv)
{
  g_world = World::instance();
  g_db = Database::instance();

  str_boot_time = g_world->get_time_text();

  g_db->initialize("murk.db");

  // Get the port number.
  g_port = 1234;
  if (argc > 1) {
    if (!is_number (argv[1])) {
      fatal_printf ("Usage: %s [port #]");
    } else if ((g_port = std::atoi (argv[1])) <= 1024) {
      fatal_printf("Port number must be above 1024.");
    }
  }

  WIN32STARTUP

  // Run the game.
#if defined(WIN32) && !defined(__DMC__)
  if (argc < 3) g_listen = init_server_socket();
  g_db->boot();
  if (argc > 2) hotboot();
#else
  g_listen = init_server_socket();
  g_db->boot();
#endif

  g_world->area_update();

  log_printf ("Merc is ready to rock on port %d.", g_port);
  game_loop ();

  // Normal exit
  closesocket (g_listen);
  log_printf ("Normal termination of game.");
  WIN32CLEANUP
  g_db->shutdown();
  return 0;
}