/***************************************************************************
 *  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.                                                  *
 ***************************************************************************/

#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include "merc.h"
extern	int	_filbuf		args( (FILE *) );



/*
 * Max sizes.
 * Increase if you add more areas.
 * Use 'memory' command in game to see current sizes.
 * Note: MAX_HASH takes no permanent memory.
 */
#define MAX_AREA	     45
#define MAX_EXIT	   6000
#define	MAX_HELP	    160
#define MAX_MOBILE	    760
#define MAX_OBJECT	    900
#define	MAX_RESET	   4200
#define MAX_ROOM	   2600
#define	MAX_SHOP	     36
#define	MAX_SHARE	 840000
#define MAX_HASH	  12000



/*
 * Reset commands:
 *   '*': comment
 *   'M': read a mobile 
 *   'O': read an object
 *   'P': put object in object
 *   'G': give object to mobile
 *   'E': equip object to mobile
 *   'D': set state of door
 *   'R': randomize room exits
 *   'S': stop (end of list)
 */
struct	reset_com
{
    char	command;
    sh_int	arg1;
    sh_int	arg2;
    sh_int	arg3;
};



/*
 * Area definition.
 */
struct	area_data
{
    char *	name;
    sh_int	age;
    sh_int	cmd;
};



/*
 * String hashing stuff.
 * Saves memory by re-using duplicate strings.
 */
typedef struct	hash_data		HASH_DATA;

struct	hash_data
{
    HASH_DATA *	next;
    char *	string;
};

#define	MAX_KEY_HASH	512



/*
 * Globals.
 */
struct	exit_type	exit_index	[MAX_EXIT];
struct	help_index_type	help_index	[MAX_HELP];
struct	mob_index_type	mob_index	[MAX_MOBILE];
struct	obj_index_type	obj_index	[MAX_OBJECT];
struct	room_index_type	room_index	[MAX_ROOM];
struct	shop_type	shop_index	[MAX_SHOP];

CHAR_DATA *		char_free;
NOTE_DATA *		note_free;
OBJ_DATA *		obj_free;

char			bug_buf		[2*MAX_INPUT_LENGTH];
CHAR_DATA *		char_list;
long			current_time;
char			log_buf		[2*MAX_INPUT_LENGTH];
KILL_DATA		kill_table	[MAX_LEVEL];
NOTE_DATA *		note_list;
OBJ_DATA *		object_list;
TIME_INFO_DATA		time_info;
WEATHER_DATA		weather_info;

sh_int			gsn_backstab;
sh_int			gsn_dodge;
sh_int			gsn_hide;
sh_int			gsn_peek;
sh_int			gsn_pick_lock;
sh_int			gsn_sneak;
sh_int			gsn_steal;

sh_int			gsn_disarm;
sh_int			gsn_enhanced_damage;
sh_int			gsn_kick;
sh_int			gsn_parry;
sh_int			gsn_rescue;
sh_int			gsn_second_attack;
sh_int			gsn_third_attack;

sh_int			gsn_blindness;
sh_int			gsn_charm_person;
sh_int			gsn_curse;
sh_int			gsn_invis;
sh_int			gsn_mass_invis;
sh_int			gsn_poison;
sh_int			gsn_sleep;





/*
 * Locals.
 */
struct	reset_com	reset_table	[MAX_RESET];
struct	area_data	area_table	[MAX_AREA];

char			share_space	[MAX_SHARE];
char *			top_share;

HASH_DATA **		hash_table;
HASH_DATA *		hash_block;
int			top_hash;

int			top_area;
int			top_exit;
int			top_help;
int			top_mob;
int			top_obj;
int			top_reset;
int			top_room;
int			top_shop;

int			nAlloc;
int			sAlloc;



/*
 * Semi-locals
 */
bool			abort_bad_vnum;
FILE *			fpArea;
char			strArea[MAX_INPUT_LENGTH];



/*
 * Local booting procedures.
 */
void *	alloc_share	args( ( int size ) );

void	load_area	args( ( FILE *fp ) );
void	load_helps	args( ( FILE *fp ) );
void	load_mobiles	args( ( FILE *fp ) );
void	load_objects	args( ( FILE *fp ) );
void	load_resets	args( ( FILE *fp ) );
void	load_rooms	args( ( FILE *fp ) );
void	load_shops	args( ( FILE *fp ) );
void	load_specials	args( ( FILE *fp ) );
void	load_notes	args( ( void ) );

char	fread_letter	args( ( FILE *fp ) );
int	fread_number	args( ( FILE *fp ) );
char *	fread_string	args( ( FILE *fp ) );
void	fread_to_eol	args( ( FILE *fp ) );
char *	fread_word	args( ( FILE *fp ) );

void	fix_exits	args( ( void ) );

void	reset_area	args( ( int area, bool fplayer ) );



/*
 * Big mama top level function.
 */
#define SECS_PER_MUD_HOUR	(PULSE_TICK/PULSE_PER_SECOND)
#define SECS_PER_MUD_DAY	(24*SECS_PER_MUD_HOUR)
#define SECS_PER_MUD_MONTH	(35*SECS_PER_MUD_DAY)
#define SECS_PER_MUD_YEAR	(17*SECS_PER_MUD_MONTH)

void boot_db( void )
{
    /*
     * Init some data space stuff.
     */
    {
	int iHash;

	abort_bad_vnum	= TRUE;
	top_exit	= 1;
	share_space[0]	= '\0';
	top_share	= &share_space[1];

	hash_table	= alloc_mem( MAX_KEY_HASH * sizeof(*hash_table) );
	hash_block	= alloc_mem( MAX_HASH * sizeof(*hash_block) );
	for ( iHash = 0; iHash < MAX_KEY_HASH; iHash++ )
	    hash_table[iHash]	= NULL;
    }

    /*
     * Set time and weather.
     */
    {
	long secs;

	secs		 = current_time - 650336715;
	time_info.year	 = secs / SECS_PER_MUD_YEAR;
	secs		-= time_info.year * SECS_PER_MUD_YEAR;
	time_info.month	 = secs / SECS_PER_MUD_MONTH;
	secs		-= time_info.month * SECS_PER_MUD_MONTH;
	time_info.day	 = secs / SECS_PER_MUD_DAY;
	secs		-= time_info.day * SECS_PER_MUD_DAY;
	time_info.hours	 = secs / SECS_PER_MUD_HOUR;

	     if ( time_info.hours <  5 ) weather_info.sunlight = SUN_DARK;
	else if ( time_info.hours <  6 ) weather_info.sunlight = SUN_RISE;
	else if ( time_info.hours < 19 ) weather_info.sunlight = SUN_LIGHT;
	else if ( time_info.hours < 20 ) weather_info.sunlight = SUN_SET;
	else                             weather_info.sunlight = SUN_DARK;

	weather_info.change	= 0;
	weather_info.mmhg	= 960;
	if ( time_info.month >= 7 && time_info.month <=12 )
	    weather_info.mmhg += number_range( 1, 50 );
	else
	    weather_info.mmhg += number_range( 1, 80 );

	     if ( weather_info.mmhg <=  980 ) weather_info.sky = SKY_LIGHTNING;
	else if ( weather_info.mmhg <= 1000 ) weather_info.sky = SKY_RAINING;
	else if ( weather_info.mmhg <= 1020 ) weather_info.sky = SKY_CLOUDY;
	else                                  weather_info.sky = SKY_CLOUDLESS;

    }

    /*
     * Assign gsn's for skills which have them.
     */
    {
	int sn;

	for ( sn = 0; sn < MAX_SKILL; sn++ )
	{
	    if ( skill_table[sn].pgsn != NULL )
		*skill_table[sn].pgsn = sn;
	}
    }

    /*
     * Read in all the area files.
     */
    {
	FILE *fpList;

	if ( ( fpList = fopen( AREA_LIST, "r" ) ) == NULL )
	{
	    perror( AREA_LIST );
	    exit( 1 );
	}

	for ( ; ; )
	{
	    strcpy( strArea, fread_word( fpList ) );
	    if ( strArea[0] == '$' )
		break;

	    if ( strArea[0] == '-' )
	    {
		fpArea = stdin;
	    }
	    else
	    {
		if ( ( fpArea = fopen( strArea, "r" ) ) == NULL )
		{
		    perror( strArea );
		    exit( 1 );
		}
	    }

	    for ( ; ; )
	    {
		char *word;

		if ( fread_letter( fpArea ) != '#' )
		{
		    bug( "Boot_db: # not found.", 0 );
		    exit( 1 );
		}

		word = fread_word( fpArea );

		     if ( word[0] == '$'               )                 break;
		else if ( !str_cmp( word, "AREA"     ) ) load_area    (fpArea);
		else if ( !str_cmp( word, "HELPS"    ) ) load_helps   (fpArea);
		else if ( !str_cmp( word, "MOBILES"  ) ) load_mobiles (fpArea);
		else if ( !str_cmp( word, "OBJECTS"  ) ) load_objects (fpArea);
		else if ( !str_cmp( word, "RESETS"   ) ) load_resets  (fpArea);
		else if ( !str_cmp( word, "ROOMS"    ) ) load_rooms   (fpArea);
		else if ( !str_cmp( word, "SHOPS"    ) ) load_shops   (fpArea);
		else if ( !str_cmp( word, "SPECIALS" ) ) load_specials(fpArea);
		else
		{
		    bug( "Boot_db: bad section name.", 0 );
		    exit( 1 );
		}
	    }

	    if ( fpArea != stdin )
		fclose( fpArea );
	    fpArea = NULL;
	}
	fclose( fpList );
    }

    /*
     * Fix up exits.
     * Reset all areas once.
     * Load up the notes file.
     * Declare db booting over.
     */
    {
	fix_exits( );
	area_update( );
	load_notes( );
	free_mem( hash_table );
	free_mem( hash_block );
	abort_bad_vnum	= FALSE;
    }

    return;
}



/*
 * Snarf an 'area' header line.
 */
void load_area( FILE *fp )
{
    int area;

    if ( ( area = top_area ) >= MAX_AREA )
    {
	bug( "Load_area: MAX_AREA: more than %d areas.", MAX_AREA );
	exit( 1 );
    }
	
    area_table[area].name	= fread_string( fp );
    area_table[area].age	= 15;
    area_table[area].cmd	= top_reset;
    top_area			= area + 1;
    return;
}



/*
 * Snarf a help section.
 */
void load_helps( FILE *fp )
{
    int iHelp;

    for ( iHelp = top_help; iHelp < MAX_HELP; iHelp++ )
    {
	help_index[iHelp].level		= fread_number( fp );
	help_index[iHelp].keyword	= fread_string( fp );
	if ( help_index[iHelp].keyword[0] == '$' )
	    { top_help = iHelp; return; }
	help_index[iHelp].text		= fread_string( fp );
	/*
	 * Leading period (to allow initial space).
	 */
	if ( help_index[iHelp].text[0] == '.' )
	    help_index[iHelp].text++;
    }

    bug( "Load_helps: MAX_HELP: more than %d helps.", MAX_HELP );
    exit( 1 );
    return;
}



/*
 * Snarf a mob section.
 */
void load_mobiles( FILE *fp )
{
    int iMob;

    for ( iMob = top_mob; iMob < MAX_MOBILE; iMob++ )
    {
	char letter;

	letter				= fread_letter( fp );
	if ( letter != '#' )
	{
	    bug( "Load_mobiles: # not found after vnum %d.",
		iMob == 0 ? 0 : mob_index[iMob-1].vnum );
	    exit( 1 );
	}

	mob_index[iMob].vnum		= fread_number( fp );
	if ( mob_index[iMob].vnum == 0 )
	    { top_mob = iMob; return; }
	if ( iMob > 0 && mob_index[iMob].vnum <= mob_index[iMob-1].vnum )
	{
	    bug( "Load_mobiles: vnum %d not in increasing order.",
		mob_index[iMob].vnum );
	    exit( 1 );
	}

	mob_index[iMob].player_name	= fread_string( fp );
	mob_index[iMob].short_descr	= fread_string( fp );
	mob_index[iMob].long_descr	= fread_string( fp );
	mob_index[iMob].description	= fread_string( fp );

	mob_index[iMob].long_descr[0]	= UPPER(mob_index[iMob].long_descr[0]);
	mob_index[iMob].description[0]	= UPPER(mob_index[iMob].description[0]);

	mob_index[iMob].act		= fread_number( fp ) | ACT_IS_NPC;
	mob_index[iMob].affected_by	= fread_number( fp );
	mob_index[iMob].shop		= SHOP_NONE;
	mob_index[iMob].alignment	= fread_number( fp );
	letter				= fread_letter( fp );
	mob_index[iMob].level		= number_fuzzy( fread_number( fp ) );

	/*
	 * The unused stuff is for imps who want to use the old-style
	 * stats-in-files method.
	 */
	mob_index[iMob].hitroll		= fread_number( fp );	/* Unused */
	mob_index[iMob].ac		= fread_number( fp );	/* Unused */
	mob_index[iMob].hitnodice	= fread_number( fp );	/* Unused */
	/* 'd'		*/		  fread_letter( fp );	/* Unused */
	mob_index[iMob].hitsizedice	= fread_number( fp );	/* Unused */
	/* '+'		*/		  fread_letter( fp );	/* Unused */
	mob_index[iMob].hitplus		= fread_number( fp );	/* Unused */
	mob_index[iMob].damnodice	= fread_number( fp );	/* Unused */
	/* 'd'		*/		  fread_letter( fp );	/* Unused */
	mob_index[iMob].damsizedice	= fread_number( fp );	/* Unused */
	/* '+'		*/		  fread_letter( fp );	/* Unused */
	mob_index[iMob].damplus		= fread_number( fp );	/* Unused */
	mob_index[iMob].gold		= fread_number( fp );	/* Unused */
	/* xp, can't be used! */	  fread_number( fp );	/* Unused */
	/* position	*/		  fread_number( fp );	/* Unused */
	/* start pos	*/		  fread_number( fp );	/* Unused */

	/*
	 * Back to meaningful values.
	 */
	mob_index[iMob].sex		= fread_number( fp );

	if ( letter != 'S' )
	{
	    bug( "Load_mobiles: vnum %d non-S.", mob_index[iMob].vnum );
	    exit( 1 );
	}

	kill_table[MAX(0, MIN(MAX_LEVEL-1, mob_index[iMob].level))].number++;
    }

    bug( "Load_mobiles: MAX_MOBILE: more than %d mobs.", MAX_MOBILE );
    exit( 1 );
    return;
}



/*
 * Snarf an obj section.
 */
void load_objects( FILE *fp )
{
    int iObj;

    for ( iObj = top_obj; iObj < MAX_OBJECT; iObj++ )
    {
	char letter;

	letter				= fread_letter( fp );
	if ( letter != '#' )
	{
	    bug( "Load_objects: # not found after vnum %d.",
		iObj == 0 ? 0 : obj_index[iObj-1].vnum );
	    exit( 1 );
	}

	obj_index[iObj].vnum		= fread_number( fp );
	if ( obj_index[iObj].vnum == 0 )
	    { top_obj = iObj; return; }
	if ( iObj > 0 && obj_index[iObj].vnum <= obj_index[iObj-1].vnum )
	{
	    bug( "Load_objects: vnum %d not in increasing order.",
		obj_index[iObj].vnum );
	    exit( 1 );
	}

	obj_index[iObj].name		= fread_string( fp );
	obj_index[iObj].short_descr	= fread_string( fp );
	obj_index[iObj].description	= fread_string( fp );
	/* Action description */	  fread_string( fp );

	obj_index[iObj].short_descr[0]	= LOWER(obj_index[iObj].short_descr[0]);
	obj_index[iObj].description[0]	= UPPER(obj_index[iObj].description[0]);

	obj_index[iObj].item_type	= fread_number( fp );
	obj_index[iObj].extra_flags	= fread_number( fp );
	obj_index[iObj].wear_flags	= fread_number( fp );
	obj_index[iObj].value[0]	= fread_number( fp );
	obj_index[iObj].value[1]	= fread_number( fp );
	obj_index[iObj].value[2]	= fread_number( fp );
	obj_index[iObj].value[3]	= fread_number( fp );
	obj_index[iObj].weight		= fread_number( fp );
	obj_index[iObj].cost		= fread_number( fp );	/* Unused */
	/* Cost per day */		  fread_number( fp );

	if ( obj_index[iObj].item_type == ITEM_POTION )
	    SET_BIT(obj_index[iObj].extra_flags, ITEM_NODROP);

	for ( ; ; )
	{
	    char letter;

	    letter = fread_letter( fp );

	    if ( letter == 'A' )
	    {
		AFFECT_DATA *paf;

		paf			= alloc_share( sizeof(*paf) );
		paf->type		= -1;
		paf->duration		= -1;
		paf->location		= fread_number( fp );
		paf->modifier		= fread_number( fp );
		paf->bitvector		= 0;
		paf->next		= obj_index[iObj].affected;
		obj_index[iObj].affected	= paf;
	    }

	    else if ( letter == 'E' )
	    {
		EXTRA_DESCR_DATA *ed;

		ed			= alloc_share( sizeof(*ed) );
		ed->keyword		= fread_string( fp );
		ed->description		= fread_string( fp );
		ed->next		= obj_index[iObj].ex_description;
		obj_index[iObj].ex_description	= ed;
	    }

	    else
	    {
		ungetc( letter, fp );
		break;
	    }
	}

	/*
	 * Translate spell "slot numbers" to internal "skill numbers."
	 */
	switch ( obj_index[iObj].item_type )
	{
	case ITEM_PILL:
	case ITEM_POTION:
	case ITEM_SCROLL:
	    obj_index[iObj].value[1] = slot_lookup( obj_index[iObj].value[1] );
	    obj_index[iObj].value[2] = slot_lookup( obj_index[iObj].value[2] );
	    obj_index[iObj].value[3] = slot_lookup( obj_index[iObj].value[3] );
	    break;

	case ITEM_STAFF:
	case ITEM_WAND:
	    obj_index[iObj].value[3] = slot_lookup( obj_index[iObj].value[3] );
	    break;
	}
    }

    bug( "Load_objects: MAX_OBJECT: more than %d objects.", MAX_OBJECT );
    exit( 1 );
    return;
}



/*
 * Snarf a reset section.
 */
void load_resets( FILE *fp )
{
    int iReset;
    int iexit;

    for ( iReset = top_reset; iReset < MAX_RESET; iReset++ )
    {
	switch ( reset_table[iReset].command = fread_letter( fp ) )
	{
	default:
	    bug( "Load_resets: bad command '%c'.",
		reset_table[iReset].command );
	    exit( 1 );
	    break;

	case 'S':
	    fread_to_eol( fp );
	    top_reset = iReset + 1;
	    return;

	case '*':
	    break;

	case 'M':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_mobile ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;
	    reset_table[iReset].arg3	= real_room   ( fread_number( fp ) ) ;
	    break;

	case 'O':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_object ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;
	    reset_table[iReset].arg3	= real_room   ( fread_number( fp ) ) ;
	    break;

	case 'P':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_object ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;
	    reset_table[iReset].arg3	= real_object ( fread_number( fp ) ) ;
	    break;

	case 'G':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_object ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;
	    reset_table[iReset].arg3	= 0;
	    break;

	case 'E':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_object ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;
	    reset_table[iReset].arg3	=		fread_number( fp )   ;
	    break;

	case 'D':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_room   ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;
	    reset_table[iReset].arg3	=		fread_number( fp )   ;

	    if ( reset_table[iReset].arg2 < 0
	    ||   reset_table[iReset].arg2 > 5
	    || ( iexit = room_index[reset_table[iReset].arg1].
			 exit[reset_table[iReset].arg2] ) == 0
	    || !IS_SET( exit_index[iexit].exit_info, EX_ISDOOR ) )
	    {
		bug( "Load_resets: 'D': exit %d not door.",
		    reset_table[iReset].arg2 );
		exit( 1 );
	    }

	    if ( reset_table[iReset].arg3 < 0 || reset_table[iReset].arg3 > 2 )
	    {
		bug( "Load_resets: 'D': bad 'locks': %d.",
		    reset_table[iReset].arg3 );
		exit( 1 );
	    }

	    break;

	case 'R':
	    /* if_flag */				fread_number( fp )   ;
	    reset_table[iReset].arg1	= real_room   ( fread_number( fp ) ) ;
	    reset_table[iReset].arg2	=		fread_number( fp )   ;

	    if ( reset_table[iReset].arg2 < 0 || reset_table[iReset].arg2 > 6 )
	    {
		bug( "Load_resets: 'R': bad exit %d.",
		    reset_table[iReset].arg2 );
		exit( 1 );
	    }

	    break;

	}
	fread_to_eol( fp );
    }

    bug( "Load_resets: MAX_RESET: more than %d resets.", MAX_RESET );
    exit( 1 );
    return;
}



/*
 * Snarf a room section.
 */
void load_rooms( FILE *fp )
{
    int iRoom;

    for ( iRoom = top_room; iRoom < MAX_ROOM; iRoom++ )
    {
	char letter;

	letter				= fread_letter( fp );
	if ( letter != '#' )
	{
	    bug( "Load_rooms: error at vnum %d.",
		iRoom == 0 ? 0 : room_index[iRoom-1].vnum );
	    exit( 1 );
	}

	room_index[iRoom].area		= top_area - 1;
	room_index[iRoom].vnum		= fread_number( fp );
	if ( room_index[iRoom].vnum == 0 )
	    { top_room = iRoom; return; }

	if ( iRoom > 0 && room_index[iRoom].vnum <= room_index[iRoom-1].vnum )
	{
	    bug( "Load_rooms: vnum %d not in increasing order.",
		room_index[iRoom].vnum );
	    exit( 1 );
	}
	room_index[iRoom].name		= fread_string( fp );
	room_index[iRoom].description	= fread_string( fp );
	/* Area number */		  fread_number( fp );
	room_index[iRoom].room_flags	= fread_number( fp );
	room_index[iRoom].sector_type	= fread_number( fp );

	for ( ; ; )
	{
	    letter = fread_letter( fp );

	    if ( letter == 'S' )
		break;

	    if ( letter == 'D' )
	    {
		int door;
		int locks;

		door = fread_number( fp );
		if ( door < 0 || door > 5 )
		{
		    bug( "Fread_rooms: vnum %d has bad door number.",
			room_index[iRoom].vnum );
		    exit( 1 );
		}

		if ( top_exit >= MAX_EXIT )
		{
		    bug( "Load_rooms: MAX_EXIT: more than %d exits.",
			MAX_EXIT );
		    exit( 1 );
		}

		room_index[iRoom].exit[door]		= top_exit;
		exit_index[top_exit].description	= fread_string( fp );
		exit_index[top_exit].keyword		= fread_string( fp );
		exit_index[top_exit].exit_info		= 0;
		locks					= fread_number( fp );
		exit_index[top_exit].key		= fread_number( fp );
		exit_index[top_exit].to_room		= fread_number( fp );

		if ( locks == 1 )
		    exit_index[top_exit].exit_info = EX_ISDOOR;
		if ( locks == 2 )
		    exit_index[top_exit].exit_info = EX_ISDOOR | EX_PICKPROOF;
		top_exit++;
	    }
	    else if ( letter == 'E' )
	    {
		EXTRA_DESCR_DATA *ed;

		ed			= alloc_share( sizeof(*ed) );
		ed->keyword		= fread_string( fp );
		ed->description		= fread_string( fp );
		ed->next		= room_index[iRoom].ex_description;
		room_index[iRoom].ex_description	= ed;
	    }
	    else
	    {
		bug( "Load_rooms: vnum %d has flag not 'DES'.",
		    room_index[iRoom].vnum );
		exit( 1 );
	    }
	}
    }

    bug( "Load_rooms: MAX_ROOM: more than %d rooms.", MAX_ROOM );
    exit( 1 );
    return;
}



/*
 * Snarf a shop section.
 */
void load_shops( FILE *fp )
{
    int iShop;

    for ( iShop = top_shop; iShop < MAX_SHOP; iShop++ )
    {
	int iTrade;

	shop_index[iShop].keeper	= fread_number( fp );
	if ( shop_index[iShop].keeper == 0 )
	    { top_shop = iShop; return; }

	for ( iTrade = 0; iTrade < MAX_TRADE; iTrade++ )
	    shop_index[iShop].buy_type[iTrade]	= fread_number( fp );

	shop_index[iShop].profit_buy	= fread_number( fp );
	shop_index[iShop].profit_sell	= fread_number( fp );
	shop_index[iShop].open_hour	= fread_number( fp );
	shop_index[iShop].close_hour	= fread_number( fp );
					  fread_to_eol( fp );
	mob_index[real_mobile(shop_index[iShop].keeper)].shop	= iShop;
    }

    bug( "Load_shops: MAX_SHOP: more than %d shops.", MAX_SHOP );
    exit( 1 );
    return;
}



/*
 * Snarf spec proc declarations.
 */
void load_specials( FILE *fp )
{
    for ( ; ; )
    {
	char *name;
	char letter;
	int vnum;

	switch ( letter = fread_letter( fp ) )
	{
	default:
	    bug( "Load_specials: letter '%c' not *MS.", letter );
	    return;

	case 'S':
	    return;

	case '*':
	    break;

	case 'M':
	    vnum = fread_number( fp );
	    name = fread_word  ( fp );
	    mob_index[real_mobile(vnum)].spec_fun = spec_lookup( vnum, name );
	    break;
	}

	fread_to_eol( fp );
    }
}



/*
 * Snarf notes file.
 */
void load_notes( void )
{
    FILE *fp;
    NOTE_DATA *pnotelast;

    if ( ( fp = fopen( NOTE_FILE, "r" ) ) == NULL )
	return;

    pnotelast = NULL;
    for ( ; ; )
    {
	NOTE_DATA *pnote;
	char letter;

	do
	{
	    letter = getc( fp );
	    if ( feof(fp) )
	    {
		fclose( fp );
		return;
	    }
	}
	while ( isspace(letter) );
	ungetc( letter, fp );

	pnote		= alloc_share( sizeof(*pnote) );

	if ( str_cmp( fread_word( fp ), "sender" ) )
	    break;
	pnote->sender	= fread_string( fp );

	if ( str_cmp( fread_word( fp ), "date" ) )
	    break;
	pnote->date	= fread_string( fp );

	if ( str_cmp( fread_word( fp ), "to" ) )
	    break;
	pnote->to_list	= fread_string( fp );

	if ( str_cmp( fread_word( fp ), "subject" ) )
	    break;
	pnote->subject	= fread_string( fp );

	if ( str_cmp( fread_word( fp ), "text" ) )
	    break;
	pnote->text	= fread_string( fp );

	if ( note_list == NULL )
	    note_list		= pnote;
	else
	    pnotelast->next	= pnote;

	pnotelast	= pnote;
    }

    strcpy( strArea, NOTE_FILE );
    fpArea = fp;
    bug( "Load_notes: bad key word.", 0 );
    exit( 1 );
    return;
}



/*
 * Translate all room exits from virtual to real.
 * Has to be done after all rooms are read in.
 * Check for bad reverse exits.
 */
void fix_exits( void )
{
    extern const sh_int rev_dir [];
    char buf[MAX_STRING_LENGTH];
    int iexit;
    int iexit_rev;
    int room;
    int to_room;
    int door;

    for ( room = 0; room < top_room; room++ )
    {
	for ( door = 0; door <= 5; door++ )
	{
	    if ( ( iexit = room_index[room].exit[door] ) == 0
	    ||   exit_index[iexit].to_room == NOWHERE )
		continue;

	    exit_index[iexit].to_room = real_room( exit_index[iexit].to_room );
	}
    }

    for ( room = 0; room < top_room; room++ )
    {
	for ( door = 0; door <= 5; door++ )
	{
	    if ( ( iexit   = room_index[room].exit[door] ) == 0
	    ||   ( to_room = exit_index[iexit].to_room   ) == NOWHERE )
		continue;

	    iexit_rev = room_index[to_room].exit[rev_dir[door]];
	    if ( iexit_rev == 0 || exit_index[iexit_rev].to_room == room )
		continue;

	    sprintf( buf, "Fix_exits: %d:%d -> %d:%d -> %d.",
		room_index[room].vnum, door,
		room_index[to_room].vnum, rev_dir[door],
		exit_index[iexit_rev].to_room != NOWHERE
		    ? room_index[exit_index[iexit_rev].to_room].vnum
		    : NOWHERE );
	    bug( buf, 0 );
	}
    }
}



/*
 * Repopulate areas periodically.
 */
void area_update( void )
{
    int area;

    for ( area = 0; area < top_area; area++ )
    {
	CHAR_DATA *pch;
	bool fplayer;

	area_table[area].age++;
	if ( area_table[area].age < 3 )
	    continue;

	/*
	 * Check for PC's.
	 */
	fplayer = FALSE;
	for ( pch = char_list; pch != NULL; pch = pch->next )
	{
	    if ( !IS_NPC(pch)
	    &&   IS_AWAKE(pch)
	    &&   pch->in_room != NOWHERE
	    &&   room_index[pch->in_room].area == area )
	    {
		fplayer = TRUE;
		if ( area_table[area].age == 15 - 1 )
		{
		    send_to_char(
			"You hear the patter of little feet.\n\r", pch );
		}
		break;
	    }
	}

	/*
	 * Check age.
	 */
	if ( fplayer && area_table[area].age < 15 )
	    continue;

	/*
	 * Ok let's reset!
	 */
	reset_area( area, fplayer );
	area_table[area].age	= 0;
	if ( area == room_index[real_room(ROOM_VNUM_SCHOOL)].area )
	    area_table[area].age = 15-3;
    }

    return;
}



/*
 * Reset one area.
 */
void reset_area( int area, bool fplayer )
{
    CHAR_DATA *mob;
    bool last;
    int cmd;
    int level;

    if ( area < 0 || area >= top_area )
    {
	bug( "Reset_area: bad area %d.", area );
	return;
    }

    mob 	= NULL;
    last	= TRUE;
    level	= 0;

    for ( cmd = area_table[area].cmd; reset_table[cmd].command != 'S'; cmd++ )
    {
	OBJ_DATA *obj;
	OBJ_DATA *obj_to;
	int arg1;
	int arg2;
	int arg3;
	int iexit;

	arg1	= reset_table[cmd].arg1;
	arg2	= reset_table[cmd].arg2;
	arg3	= reset_table[cmd].arg3;

	switch ( reset_table[cmd].command )
	{
	case 'M':
	    level = MAX( 0, MIN( MAX_LEVEL-5, mob_index[arg1].level-2 ) );
	    if ( mob_index[arg1].count >= arg2 )
	    {
		last = FALSE;
		break;
	    }

	    mob = create_mobile( arg1 );
	    if ( arg3 > 0
	    &&   IS_SET(room_index[arg3-1].room_flags, ROOM_PET_SHOP) )
		SET_BIT(mob->act, ACT_PET);
	    if ( room_is_dark( arg3 ) )
		SET_BIT(mob->affected_by, AFF_INFRARED);
	    char_to_room( mob, arg3 );
	    level = MAX( 0, MIN( MAX_LEVEL-5, mob->level-2 ) );
	    last  = TRUE;
	    break;

	case 'O':
	    if ( fplayer
	    ||   count_obj_list( arg1, room_index[arg3].contents ) > 0 )
	    {
		last = FALSE;
		break;
	    }

	    obj = create_object( arg1, level );
	    obj_to_room( obj, arg3 );
	    last = TRUE;
	    break;

	case 'P':
	    if ( fplayer
	    || ( obj_to = get_obj_num( arg3 ) ) == NULL
	    ||   obj_to->in_room == NOWHERE
	    ||   count_obj_list( arg1, obj_to->contains ) > 0 )
	    {
		last = FALSE;
		break;
	    }
	    
	    obj    = create_object( arg1, obj_to->level );
	    obj_to_obj( obj, obj_to );
	    last = TRUE;
	    break;

	case 'G':
	case 'E':
	    if ( !last )
		break;

	    if ( mob == NULL )
	    {
		bug( "Reset_area: 'E' or 'G': null mob.", 0 );
		last = FALSE;
		break;
	    }

	    if ( mob_index[mob->rnum].shop != SHOP_NONE )
	    {
		obj = create_object( arg1, 6 );
		SET_BIT( obj->extra_flags, ITEM_INVENTORY );
	    }
	    else
	    {
		obj = create_object( arg1, level );
	    }
	    obj_to_char( obj, mob );
	    if ( reset_table[cmd].command == 'E' )
		equip_char( mob, obj, arg3 );
	    last = TRUE;
	    break;

	case 'D':
	    if ( ( iexit = room_index[arg1].exit[arg2] ) == 0 )
		break;

	    switch ( arg3 )
	    {
	    case 0:
		REMOVE_BIT( exit_index[iexit].exit_info, EX_CLOSED );
		REMOVE_BIT( exit_index[iexit].exit_info, EX_LOCKED );
		break;

	    case 1:
		SET_BIT(    exit_index[iexit].exit_info, EX_CLOSED );
		REMOVE_BIT( exit_index[iexit].exit_info, EX_LOCKED );
		break;

	    case 2:
		SET_BIT(    exit_index[iexit].exit_info, EX_CLOSED );
		SET_BIT(    exit_index[iexit].exit_info, EX_LOCKED );
		break;
	    }

	    last = TRUE;
	    break;

	case 'R':
	    {
		int d0;
		int d1;

		for ( d0 = 0; d0 < arg2 - 1; d0++ )
		{
		    d1                        = number_range( d0, arg2-1 );
		    iexit                     = room_index[arg1].exit[d0];
		    room_index[arg1].exit[d0] = room_index[arg1].exit[d1];
		    room_index[arg1].exit[d1] = iexit;
		}
	    }
	    break;
	}
    }

    return;
}



/*
 * Create an instance of a mobile.
 */
CHAR_DATA *create_mobile( int rnum )
{
    CHAR_DATA *mob;

    if ( rnum < 0 || rnum >= top_mob )
    {
	bug( "Create_mobile: %d out of range.", rnum );
	exit( 1 );
    }

    if ( char_free == NULL )
    {
	mob		= alloc_mem( sizeof(*mob) );
    }
    else
    {
	mob		= char_free;
	char_free	= char_free->next;
    }

    clear_char( mob );
    mob->rnum		= rnum;

    mob->name		= mob_index[rnum].player_name;
    mob->short_descr	= mob_index[rnum].short_descr;
    mob->long_descr	= mob_index[rnum].long_descr;
    mob->description	= mob_index[rnum].description;

    mob->level		= number_fuzzy( mob_index[rnum].level );
    mob->act		= mob_index[rnum].act;
    mob->affected_by	= mob_index[rnum].affected_by;
    mob->alignment	= mob_index[rnum].alignment;
    mob->sex		= mob_index[rnum].sex;

    mob->armor		= interpolate( mob->level, 100, -100 );

    mob->max_hit	= mob->level * 8 + number_range(
				mob->level * mob->level / 4,
				mob->level * mob->level );
    mob->hit		= mob->max_hit;
	    
    /*
     * Insert in list.
     */
    mob->next		= char_list;
    char_list		= mob;
    mob_index[rnum].count++;
    return mob;
}



/*
 * Create an instance of an object.
 */
OBJ_DATA *create_object( int rnum, int level )
{
    static OBJ_DATA obj_zero;
    OBJ_DATA *obj;

    if ( rnum < 0 || rnum >= top_obj )
    {
	bug( "Create_object: rnum %d out of range.", rnum );
	exit( 1 );
    }

    if ( obj_free == NULL )
    {
	obj		= alloc_mem( sizeof(*obj) );
    }
    else
    {
	obj		= obj_free;
	obj_free	= obj_free->next;
    }

    *obj		= obj_zero;
    obj->rnum		= rnum;  
    obj->in_room	= NOWHERE;
    obj->level		= level;
    obj->wear_loc	= -1;

    obj->name		= obj_index[rnum].name;
    obj->short_descr	= obj_index[rnum].short_descr;
    obj->description	= obj_index[rnum].description;
    obj->item_type	= obj_index[rnum].item_type;
    obj->extra_flags	= obj_index[rnum].extra_flags;
    obj->wear_flags	= obj_index[rnum].wear_flags;
    obj->value[0]	= obj_index[rnum].value[0];
    obj->value[1]	= obj_index[rnum].value[1];
    obj->value[2]	= obj_index[rnum].value[2];
    obj->value[3]	= obj_index[rnum].value[3];
    obj->weight		= obj_index[rnum].weight;
    obj->cost		= number_fuzzy( 10 )
			* number_fuzzy( level ) * number_fuzzy( level );

    /*
     * The ED's and affects reside completely in shared space,
     *   so we just copy by reference.
     */
    obj->affected	= obj_index[rnum].affected;
    obj->ex_description	= obj_index[rnum].ex_description;

    /*
     * Mess with object properties.
     */
    switch ( obj->item_type )
    {
    default:
	bug( "Read_object: vnum %d bad type.", obj_index[rnum].vnum );
	break;

    case ITEM_LIGHT:
    case ITEM_TREASURE:
    case ITEM_FURNITURE:
    case ITEM_TRASH:
    case ITEM_CONTAINER:
    case ITEM_DRINK_CON:
    case ITEM_KEY:
    case ITEM_FOOD:
    case ITEM_BOAT:
    case ITEM_CORPSE_NPC:
    case ITEM_CORPSE_PC:
    case ITEM_FOUNTAIN:
	break;

    case ITEM_SCROLL:
	obj->value[0]	= number_fuzzy( obj->value[0] );
	break;

    case ITEM_WAND:
    case ITEM_STAFF:
	obj->value[0]	= number_fuzzy( obj->value[0] );
	obj->value[1]	= number_fuzzy( obj->value[1] );
	obj->value[2]	= obj->value[1];
	break;

    case ITEM_WEAPON:
	obj->value[1]	= number_fuzzy( number_fuzzy( 1 * level / 4 + 2 ) );
	obj->value[2]	= number_fuzzy( number_fuzzy( 3 * level / 4 + 6 ) );
	break;

    case ITEM_ARMOR:
	obj->value[0]	= number_fuzzy( level / 4 + 2 );
	break;

    case ITEM_POTION:
    case ITEM_PILL:
	obj->value[0]	= number_fuzzy( number_fuzzy( obj->value[0] ) );
	break;

    case ITEM_MONEY:
	obj->value[0]	= obj->cost;
	break;
    }

    obj->next		= object_list;
    object_list		= obj;
    obj_index[rnum].count++;

    return obj;
}



/*
 * Clear a new character.
 */
void clear_char( CHAR_DATA *ch )
{
    static CHAR_DATA ch_zero;

    *ch				= ch_zero;
    ch->name			= &share_space[0];
    ch->pwd			= &share_space[0];
    ch->short_descr		= &share_space[0];
    ch->long_descr		= &share_space[0];
    ch->description		= &share_space[0];
    ch->title			= &share_space[0];
    ch->poofin			= &share_space[0];
    ch->poofout			= &share_space[0];
    ch->in_room			= NOWHERE;
    ch->logon			= current_time;
    ch->armor			= 100;
    ch->was_in_room		= NOWHERE;
    ch->position		= POS_STANDING;
    ch->practice		= 21;
    ch->hit			= 20;
    ch->max_hit			= 20;
    ch->mana			= 100;
    ch->max_mana		= 100;
    ch->move			= 100;
    ch->max_move		= 100;
    return;
}



/*
 * Free a character.
 */
void free_char( CHAR_DATA *ch )
{
    OBJ_DATA *obj;
    OBJ_DATA *obj_next;
    AFFECT_DATA *paf;
    AFFECT_DATA *paf_next;

    for ( obj = ch->carrying; obj != NULL; obj = obj_next )
    {
	obj_next = obj->next_content;
	extract_obj( obj );
    }

    for ( paf = ch->affected; paf != NULL; paf = paf_next )
    {
	paf_next = paf->next;
	affect_remove( ch, paf );
    }

    free_mem( ch->name		);
    free_mem( ch->pwd		);
    free_mem( ch->short_descr	);
    free_mem( ch->long_descr	);
    free_mem( ch->description	);
    free_mem( ch->title		);
    free_mem( ch->poofin	);
    free_mem( ch->poofout	);
    free_mem( ch->pcdata	);
    ch->next	= char_free;
    char_free	= ch;
    return;
}



/*
 * Translates virtual room number to real room number.
 * Binary search.
 */
int real_room( int vnum )
{
    int	bot;
    int top;
    int mid;

    bot = 0;
    top = top_room - 1;

    for ( ; ; )
    {
	mid = (bot + top) / 2;
	if ( room_index[mid].vnum == vnum )
	    return mid;
	if ( bot >= top )
	    break;
	if ( room_index[mid].vnum  > vnum )
	    top = mid - 1;
	else
	    bot = mid + 1;
    }

    if ( abort_bad_vnum )
    {
	bug( "Real_room: bad vnum %d.", vnum );
	exit( 1 );
    }

    return -1;
}



/*
 * Translates virtual mob number to real mob number.
 * Binary search.
 */
int real_mobile( int vnum )
{
    int	bot;
    int top;
    int mid;

    bot = 0;
    top = top_mob - 1;

    for ( ; ; )
    {
	mid = (bot + top) / 2;
	if ( mob_index[mid].vnum == vnum )
	    return mid;
	if ( bot >= top )
	    break;
	if ( mob_index[mid].vnum  > vnum )
	    top = mid - 1;
	else
	    bot = mid + 1;
    }

    if ( abort_bad_vnum )
    {
	bug( "Real_mobile: bad vnum %d.", vnum );
	exit( 1 );
    }

    return -1;
}



/*
 * Translates virtual obj number to real obj number.
 * Binary search.
 */
int real_object( int vnum )
{
    int	bot;
    int top;
    int mid;

    bot = 0;
    top = top_obj - 1;

    for ( ; ; )
    {
	mid = (bot + top) / 2;
	if ( obj_index[mid].vnum == vnum )
	    return mid;
	if ( bot >= top )
	    break;
	if ( obj_index[mid].vnum  > vnum )
	    top = mid - 1;
	else
	    bot = mid + 1;
    }

    if ( abort_bad_vnum )
    {
	bug( "Read_object: bad vnum %d.", vnum );
	exit( 1 );
    }

    return -1;
}



/*
 * Read a letter from a file.
 */
char fread_letter( FILE *fp )
{
    char c;

    do
    {
	c = getc( fp );
    }
    while ( isspace(c) );

    return c;
}



/*
 * Read a number from a file.
 */
int fread_number( FILE *fp )
{
    int number;
    bool sign;
    char c;

    do
    {
	c = getc( fp );
    }
    while ( isspace(c) );

    number = 0;

    sign   = FALSE;
    if ( c == '+' )
    {
	c = getc( fp );
    }
    else if ( c == '-' )
    {
	sign = TRUE;
	c = getc( fp );
    }

    while ( isdigit(c) )
    {
	number = number * 10 + c - '0';
	c = getc( fp );
    }

    if ( sign )
	number = 0 - number;

    if ( c == '|' )
	number += fread_number( fp );
    else if ( c != ' ' )
	ungetc( c, fp );

    return number;
}



/*
 * Read and allocate space for a string from a file.
 * These strings are read-only and shared.
 * Note that share_space[0] == '\0', all zero-length strings are there.
 * Char type lookup and funny code for even more speed,
 *   this function takes 40% to 50% of boot-up time.
 */
char *fread_string( FILE *fp )
{
    static bool char_special[256-EOF];
    HASH_DATA *phash;
    int hash_key;
    char *plast;
    char c;

    if ( char_special[EOF-EOF] != TRUE )
    {
	char_special[EOF-EOF]  = TRUE;
	char_special['\n'-EOF] = TRUE;
	char_special['\r'-EOF] = TRUE;
	char_special['~'-EOF]  = TRUE;
    }

    if ( ( plast = top_share ) > &share_space[MAX_SHARE - MAX_STRING_LENGTH] )
    {
	bug( "Fread_string: more than %d shared space (MAX_SHARE).",
	    MAX_SHARE );
	exit( 1 );
    }

    /*
     * Skip blanks.
     * Read first char.
     */
    do
    {
	c = getc( fp );
    }
    while ( isspace(c) );

    if ( ( *plast++ = c ) == '~' )
	return &share_space[0];

    for ( ;; )
    {
	if ( !char_special[ ( *plast++ = getc( fp ) ) - EOF ] )
	    continue;

	switch ( plast[-1] )
	{
	default:
	    break;

	case EOF:
	    bug( "Fread_string: EOF", 0 );
	    exit( 1 );
	    break;

	case '\n':
	    *plast++ = '\r';
	    break;

	case '\r':
	    plast--;
	    break;

	case '~':
	    plast[-1]	= '\0';

	    hash_key	= MIN( MAX_KEY_HASH - 1, plast - 1 - top_share );
	    for ( phash = hash_table[hash_key]; phash; phash = phash->next )
	    {
		if ( phash->string[0] == top_share[0]
		&&  !strcmp( phash->string+1, top_share+1 ) )
		{
		    tail_chain( );	/* For profiling */
		    return phash->string;
		}
	    }

	    if ( top_hash >= MAX_HASH )
	    {
		bug( "Fread_string: more than %d hashed strings (MAX_HASH).",
		    MAX_HASH );
		exit( 1 );
	    }
		
	    phash			= &hash_block[top_hash++];
	    phash->string		= top_share;
	    phash->next			= hash_table[hash_key];
	    hash_table[hash_key]	= phash;
	    top_share			= plast;
	    return phash->string;
	}
    }
}



/*
 * Read to end of line (for comments).
 */
void fread_to_eol( FILE *fp )
{
    char c;

    do
    {
	c = getc( fp );
    }
    while ( c != '\n' && c != '\r' );

    do
    {
	c = getc( fp );
    }
    while ( c == '\n' || c == '\r' );

    ungetc( c, fp );
    return;
}



/*
 * Read one word (into static buffer).
 */
char *fread_word( FILE *fp )
{
    static char word[MAX_INPUT_LENGTH];
    char *pword;
    char cEnd;

    do
    {
	word[0] = getc( fp );
    }
    while ( isspace( word[0] ) );

    cEnd = ( word[0] == '\'' || word[0] == '"' ) ? word[0] : ' ';
    for ( pword = word + 1; pword < word + MAX_INPUT_LENGTH; pword++ )
    {
	*pword = getc( fp );
	if ( ( cEnd == ' ' ) ? isspace( *pword ) : *pword == cEnd )
	{
	    if ( cEnd == ' ' )
		ungetc( *pword, fp );
	    *pword = '\0';
	    return word;
	}
    }

    bug( "Fread_word: word too long.", 0 );
    exit( 1 );
    return NULL;
}



/*
 * Duplicate a string into dynamic memory.
 * Fread_strings are read-only and shared.
 */
char *str_dup( const char *str )
{
    char *str_new;

    if ( str >= share_space && str < top_share )
	return (char *) str;

    if ( str[0] == '\0' )
	return &share_space[0];

    str_new	= alloc_mem( strlen(str) + 1 );
    strcpy( str_new, str );
    return str_new;
}



/*
 * Allocate some (permanent) shared memory.
 * Have to dance with memory alignment.
 */
#define MASK	(sizeof(long) - 1)

void *alloc_share( int size )
{
    void *pMem;

    top_share = (char *) ( ( ( (long) top_share + MASK ) ) & ~ MASK );
    if ( top_share + size >= &share_space[MAX_SHARE] )
    {
	bug( "Alloc_share: more than %d shared space (MAX_SHARE).",
	    MAX_SHARE );
	exit( 1 );
    }
    
    pMem       = top_share;
    top_share += size;
    return pMem;
}



/*
 * Allocate some memory.
 */
void *alloc_mem( int size )
{
    void *pMem;

    nAlloc += 1;
    sAlloc += size;
    if ( ( pMem = malloc( size ) ) == NULL )
    {
	perror( "Alloc_mem" );
	exit( 1 );
    }

    return pMem;
}



/*
 * Free some memory.
 * Null is legal here to simplify callers.
 * Read-only shared strings are not touched.
 */
void free_mem( void *pMem )
{
    if ( pMem == NULL )
	return;

    if ( (char *) pMem >= share_space && (char *) pMem < top_share )
	return;

    free( pMem );
    return;
}



void do_areas( CHAR_DATA *ch, char *argument )
{
    char buf[MAX_STRING_LENGTH];
    int area;
    int half;

    half = (top_area + 1) / 2;
    for ( area = 0; area < half; area++ )
    {
	sprintf( buf, "%-39s%-39s\n\r",
	    area_table[area].name,
	    area+half < top_area ? area_table[area+half].name : "" );
	send_to_char( buf, ch );
    }

    return;
}



void do_memory( CHAR_DATA *ch, char *argument )
{
    char buf[MAX_STRING_LENGTH];

    sprintf( buf,
	"Areas:  %7d of %7d.\n\rExits:  %7d of %7d.\n\rHelps:  %7d of %7d.\n\rMobs:   %7d of %7d.\n\rObjs:   %7d of %7d.\n\rResets: %7d of %7d.\n\rRooms:  %7d of %7d.\n\rShops:  %7d of %7d.\n\rShare:  %7d of %7d.\n\rHash:   %7d of %7d.\n\rMalloc: %7d of %7d.\n\r",

	top_area,			MAX_AREA,
	top_exit,			MAX_EXIT,
	top_help,			MAX_HELP,
	top_mob,			MAX_MOBILE,
	top_obj,			MAX_OBJECT,
	top_reset,			MAX_RESET,
	top_room,			MAX_ROOM,
	top_shop,			MAX_SHOP,
	top_share - share_space,	MAX_SHARE,
	top_hash,			MAX_HASH,
	nAlloc,				sAlloc
	);

    send_to_char( buf, ch );
    return;
}



/*
 * Stick a little fuzz on a number.
 */
int number_fuzzy( int number )
{
    switch ( number_bits( 2 ) )
    {
    case 0:  number -= 1; break;
    case 3:  number += 1; break;
    }

    return MAX( 1, number );
}



/*
 * Generate a random number.
 */
int number_range( int from, int to )
{
    int bits;
    int power;
    int number;

    if ( ( to = to - from + 1 ) <= 1 )
	return from;

    for ( bits = 1, power = 2; power < to; bits++, power <<= 1 )
	;

    while ( ( number = number_bits( bits ) ) >= to )
	;

    return from + number;
}



/*
 * Generate a percentile roll.
 */
int number_percent( void )
{
    int percent;

    while ( ( percent = number_bits( 7 ) ) >= 100 )
	;

    return 1 + percent;
}



/*
 * Generate a random door.
 */
int number_door( void )
{
    int door;

    while ( ( door = number_bits( 3 ) ) > 5 )
	;

    return door;
}



/*
 * Special high-performance random number generator.
 * Note the '32' is slightly machine dependent (32-bit random numbers).
 * We avoid division (expensive on RISC machines) and ration out bits.
 * Highest bit (sign bit) is unused to avoid sign-extension worries.
 * -- Furey
 */
int number_bits( int width )
{
    static long latch_value;
    static int  latch_width;
    int value;

    if ( latch_width < width )
    {
	latch_value = random( );
	latch_width = 32 - 1;
    }

    value         = (int) ( latch_value & ( ( 1 << width ) - 1 ) );
    latch_value >>= width;
    latch_width  -= width;
    return value;
}



/*
 * Roll some dice.
 */
int dice( int number, int size )
{
    int idice;
    int sum;

    switch ( size )
    {
    case 0: return 0;
    case 1: return number;
    }

    for ( idice = 0, sum = 0; idice < number; idice++ )
	sum += number_range( 1, size );

    return sum;
}



/*
 * Simple linear interpolation.
 */
int interpolate( int level, int value_00, int value_32 )
{
    return value_00 + level * (value_32 - value_00) / 32;
}



/*
 * Removes the tildes from a string.
 * Used for player-entered strings that go into disk files.
 */
void smash_tilde( char *str )
{
    for ( ; *str != '\0'; str++ )
    {
	if ( *str == '~' )
	    *str = '-';
    }

    return;
}



/*
 * Compare strings, case insensitive.
 * Return TRUE if different (compatability with historical functions).
 */
bool str_cmp( const char *astr, const char *bstr )
{
    if ( astr == NULL )
    {
	bug( "Str_cmp: null astr.", 0 );
	return TRUE;
    }

    if ( bstr == NULL )
    {
	bug( "Str_cmp: null bstr.", 0 );
	return TRUE;
    }

    for ( ; *astr || *bstr; astr++, bstr++ )
    {
	if ( LOWER(*astr) != LOWER(*bstr) )
	    return TRUE;
    }

    return FALSE;
}



/*
 * Returns an initial-capped string.
 */
char *capitalize( const char *str )
{
    static char *strcap;
    static int caplen;
    int length;
    int i;

    length = strlen( str );
    if ( length + 1 > caplen )
    {
	free_mem( strcap );
	caplen	= length + 1;
	strcap	= alloc_mem( length + 1 );
    }

    for ( i = 0; str[i] != '\0'; i++ )
	strcap[i] = LOWER(str[i]);
    strcap[i] = '\0';
    strcap[0] = UPPER(strcap[0]);
    return strcap;
}



/*
 * Append a string to a file.
 */
void append_file( CHAR_DATA *ch, char *file, char *str )
{
    FILE *fp;

    if ( IS_NPC(ch) || str[0] == '\0' )
	return;

    fclose( fpReserve );
    if ( ( fp = fopen( file, "a" ) ) == NULL )
    {
	perror( file );
	send_to_char( "Could not open the file!\n\r", ch );
    }
    else
    {
	fprintf( fp, "[%5d] %s: %s\n",
	    room_index[ch->in_room].vnum, ch->name, str );
	fclose( fp );
    }

    fpReserve = fopen( NULL_FILE, "r" );
    return;
}



/*
 * Reports a bug.
 */
void bug( const char *str, int param )
{
    char buf[MAX_STRING_LENGTH];
    FILE *fp;

    if ( fpArea != NULL )
    {
	int iLine;
	int iChar;

	if ( fpArea == stdin )
	{
	    iLine = 0;
	}
	else
	{
	    iChar = ftell( fpArea );
	    fseek( fpArea, 0, 0 );
	    for ( iLine = 0; ftell( fpArea ) < iChar; iLine++ )
	    {
		while ( getc( fpArea ) != '\n' )
		    ;
	    }
	    fseek( fpArea, iChar, 0 );
	}

	sprintf( buf, "[*****] FILE: %s LINE: %d", strArea, iLine );
	log_string( buf );

	if ( ( fp = fopen( "shutdown.txt", "a" ) ) != NULL )
	{
	    fprintf( fp, "[*****] %s\n", buf );
	    fclose( fp );
	}
    }

    strcpy( buf, "[*****] BUG: " );
    sprintf( buf + strlen(buf), str, param );
    log_string( buf );

    fclose( fpReserve );
    if ( ( fp = fopen( BUG_FILE, "a" ) ) != NULL )
    {
	fprintf( fp, "%s\n", buf );
	fclose( fp );
    }
    fpReserve = fopen( NULL_FILE, "r" );

    return;
}



/*
 * Writes a string to the log.
 */
void log_string( const char *str )
{
    char *strtime;

    strtime                    = ctime( &current_time );
    strtime[strlen(strtime)-1] = '\0';
    fprintf( stderr, "%s :: %s\n", strtime, str );
    return;
}



/*
 * This function is here to aid in debugging.
 * If the last expression in a function is another function call,
 *   gcc likes to generate a JMP instead of a CALL.
 * This is called "tail chaining."
 * It hoses the debugger call stack for that call.
 * So I make this the last call in certain critical functions,
 *   where I really need the call stack to be right for debugging!
 *
 * If you don't understand this, then LEAVE IT ALONE.
 * Don't remove any calls to tail_chain anywhere.
 *
 * -- Furey
 */
void tail_chain( void )
{
    return;
}