daleken/
daleken/data/notes/
daleken/data/player/
daleken/data/system/poses/
daleken/doc/Homepage/images/
daleken/log/
/*___________________________________________________________________________*
   )()(			  DalekenMUD 1.12 (C) 2000			)()(
   `]['		       by Martin Thomson, Lee Brooks,			`]['
    ||		       Ken Herbert and David Jacques			 ||
    || ----------------------------------------------------------------- ||
    || Envy Diku Mud improvements copyright (C) 1994 by Michael Quan,	 ||
    || David Love, Guilherme 'Willie' Arnold, and Mitchell Tse.		 ||
    || Merc Diku Mud improvments copyright (C) 1992, 1993 by Michael	 ||
    || Chastain, Michael Quan, and Mitchell Tse.			 ||
    || Original Diku Mud copyright (C) 1990, 1991			 ||
    || by Sebastian Hammer, Michael Seifert, Hans Henrik St{rfeldt,	 ||
    || Tom Madsen, and Katja Nyboe.					 ||
    || ----------------------------------------------------------------- ||
    || Any use of this software must follow the licenses of the		 ||
    || creators.  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.					 ||
    || ----------------------------------------------------------------- ||
    ||                                db.c                               ||
    || Main database management/loading code.                            ||
 *_/<>\_________________________________________________________________/<>\_*/


#include <math.h>
#include <stdarg.h>
#if defined( unix )
# include <unistd.h>
#endif
#include "mud.h"
#include "olc.h"
#include "event.h"
#include "db.h"

/*
 * Globals.
 */
HELP_DATA	*help_first;
HELP_DATA	*help_last;

HIGHEST_DATA	*highest_first;
HIGHEST_DATA	*highest_last;

SOCIAL_DATA	*social_table[27];

SHOP_DATA	*shop_first;
SHOP_DATA	*shop_last;

RELIGION_DATA	*religion_first;
RELIGION_DATA	*religion_last;

EXIT_DATA	*exit_list;
CHAR_DATA	*char_free;
AFFECT_DATA	*affect_free;
OBJ_DATA	*obj_free;
PC_DATA		*pcdata_free;

CHAR_DATA	*char_list;
char		*help_greeting;
char		log_buf[MAX_INPUT_LENGTH * 2];
KILL_DATA	kill_table[LEVEL_HERO * 2];
NOTE_DATA	*note_list;
OBJ_DATA	*object_list;
BAN_DATA	*badname_list;
PLANE_DATA	*plane_list;

/*
 * SysInfo struct.
 */
struct sysinfo_type *SysInfo;


/*
 * Global Skill Numbers.
 */
int	gsn_atemi;		/* Daleken */
int	gsn_avoidance;		/* Daleken */
int	gsn_awen;		/* Daleken */
int	gsn_backstab;
int	gsn_backhand;		/* Daleken */
int	gsn_barehand;		/* Daleken */
int	gsn_bash;
int	gsn_berserk;		/* by Thelonius */
int	gsn_blindness;
int	gsn_blink;		/* Daleken */
int	gsn_breathing;		/* by Thelonius */
int	gsn_brew;
int	gsn_burn;
int	gsn_burning_hands;	/* Daleken */
int	gsn_bury_item;		/* Daleken */
int	gsn_carve;		/* Daleken */
int	gsn_catfall;		/* Daleken */
int	gsn_chameleon;		/* by Thelonius */
int	gsn_channel;		/* Daleken */
int	gsn_charge;		/* Daleken */
int	gsn_charm_person;
int	gsn_chill_touch;	/* Daleken */
int	gsn_clan_power;		/* Daleken */
int	gsn_continuous_effect;	/* by Incubus */
int	gsn_circle;		/* by Thelonius */
int	gsn_dark_claws;		/* Daleken */
int	gsn_delayed_effect;	/* by Incubus */
int	gsn_detect_traps;	/* Daleken */
int	gsn_dirty_fighting;	/* Daleken */
int	gsn_disable_traps;	/* Daleken */
int	gsn_disarm;
int	gsn_dodge;
int	gsn_domination;		/* by Thelonius */
int	gsn_dual;		/* by Thelonius */
int	gsn_elbow;		/* Daleken */
int	gsn_engrave;		/* Daleken */
int	gsn_enhanced_damage;
int	gsn_explosive;		/* needed, Symposium */
int	gsn_field_dressing;	/* Daleken */
int	gsn_fifth_attack;	/* Daleken */
int	gsn_first_trap;		/* Daleken */
int	gsn_flashing_blades;	/* Daleken */
int	gsn_foci;		/* Daleken */
int	gsn_forage;		/* Daleken */
int	gsn_fortitudes;		/* Daleken */
int	gsn_fourth_attack;
int	gsn_golden_touch;	/* Daleken */
int	gsn_haggle;		/* Daleken */
int	gsn_hand_of_kaz;	/* Daleken */
int	gsn_headbutt;		/* Daleken */
int	gsn_heighten;		/* by Thelonius */
int	gsn_herbalism;		/* Daleken */
int	gsn_hex;
int	gsn_hide;
int	gsn_impale;		/* Daleken */
int	gsn_invis;
int	gsn_jab;		/* Daleken */
int	gsn_juggle;		/* Daleken */
int	gsn_kick;
int	gsn_knee;		/* Daleken */
int	gsn_last_trap;		/* Daleken */
int	gsn_lighten;		/* Daleken */
int	gsn_lucky_blow;		/* Daleken */
int	gsn_magic_lore;		/* Daleken */
int	gsn_mass_invis;
int	gsn_mass_plague;	/* Daleken */
int	gsn_meditation;		/* Daleken */
int	gsn_mob_lore;		/* Daleken */
int	gsn_modify_armour;	/* Daleken */
int	gsn_move_hidden;	/* Daleken */
int	gsn_mute;		/* by Thelonius */
int	gsn_plague;		/* Daleken */
int	gsn_poison;
int	gsn_parry;
int	gsn_peek;
int	gsn_perm_spell;		/* Daleken */
int	gsn_pick_lock;
int	gsn_poison_weapon;	/* by Thelonius */
int	gsn_quicken;		/* Daleken */
int	gsn_race_tail;		/* Daleken */
int	gsn_ram;		/* Daleken */
int	gsn_racial_fatigue;	/* Daleken */
int	gsn_religious;		/* Daleken */
int	gsn_rescue;
int	gsn_repair;		/* Daleken */
int	gsn_retaliate;		/* Daleken */
int	gsn_roundhouse;		/* Daleken */
int	gsn_scribe;
int	gsn_scrolls;		/* by Binky / Thelonius */
int	gsn_second_attack;
int	gsn_sharpen_weapon;	/* Daleken */
int	gsn_shield_block;
int	gsn_shocking_grasp;	/* Daleken */
int	gsn_sleep;
int	gsn_slit_throat;	/* Daleken */
int	gsn_smash;		/* Daleken */
int	gsn_snapkick;		/* Daleken */
int	gsn_snare;		/* by Binky / Thelonius */
int	gsn_sneak;
int	gsn_stake;
int	gsn_stamina;		/* Daleken */
int	gsn_staves;		/* by Binky / Thelonius */
int	gsn_steal;
int	gsn_stomp;		/* Daleken */
int	gsn_strangle;		/* Daleken */
int	gsn_study;		/* Daleken */
int	gsn_surge;		/* Daleken */
int	gsn_swan_song;		/* Daleken */
int	gsn_taunt;		/* Daleken */
int	gsn_third_attack;
int	gsn_throw;		/* Daleken */
int	gsn_throw_weapon;	/* Daleken */
int	gsn_track;		/* Daleken */
int	gsn_trip;		/* Daleken */
int	gsn_turn_undead;
int	gsn_two_handed;		/* Daleken */
int	gsn_untangle;		/* by Thelonius */
int	gsn_uppercut;		/* Daleken */
int	gsn_vampiric_bite;	/* by Kahn */
int	gsn_vanish;		/* Daleken */
int	gsn_wands;		/* by Binky / Thelonius */
int	gsn_weapon_skill;	/* Daleken */
int	gsn_weapon_spell;	/* Daleken */
int	gsn_web;		/* Daleken */
int	gsn_whirlwind;		/* Daleken */


/*
 * Locals.
 */
MOB_INDEX_DATA *mob_index_hash[MAX_KEY_HASH];
OBJ_INDEX_DATA *obj_index_hash[MAX_KEY_HASH];
ROOM_INDEX_DATA *room_index_hash[MAX_KEY_HASH];

AREA_DATA *area_first;
AREA_DATA *area_last;

int	top_affect;
int	top_area;
int	top_ed;
int	top_exit;
int	top_help;
int	top_mob_index;
int	top_mprog;
int	top_obj_index;
int	top_reset;
int	top_room;
int	top_shop;
int	top_social;
int	top_mprog_global;
int	top_vnum_room;		/* OLC */
int	top_vnum_mob;		/* OLC */
int	top_vnum_obj;		/* OLC */
extern int top_pose;		/* interp.c */
int	top_text_block;
int	size_text_block;


/*
 * Memory management.
 * Increase MAX_STRING if you have too, but look to ssm.c
 * Tune the others only if you understand what you're doing.
 * MAX_STRING is now in ssm.c
 */
extern int MAX_STRING;

#define			MAX_PERM_BLOCK	131072
#define			MAX_MEM_LIST	14

void *			rgFreeList		[ MAX_MEM_LIST	     ];
const int		rgSizeList		[ MAX_MEM_LIST	     ]	=
{
    4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768-64
};


extern int nAllocString;
extern int sAllocString;
extern int nOverFlowString;
extern int sOverFlowString;
extern bool Full;
int nAllocPerm;
int sAllocPerm;
static char *pMemPerm;
static int iMemPerm;



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

void init_string_space( void );
void boot_done( void );
char *daPrompt;

/*
 * Local booting procedures.
 */
void init_mm		args( ( void ) );

void load_ban		args( ( void ) );
void load_unique_key	args( ( void ) );	/* Daleken */
void load_badnames	args( ( void ) );	/* Daleken */

void save_badnames	args( ( void ) );	/* Daleken */
void save_socials	args( ( void ) );

void fix_exits		args( ( void ) );
void init_auction	args( ( void ) );
void reset_area		args( ( AREA_DATA *pArea ) );
void init_economy	args( ( void ) );		/* SMAUG */
void economize_mobgold	args( ( CHAR_DATA *mob ) );	/* handler.c */
void rand_colour	args( ( char **to, char *from, int col ) );

/*
 * Big mama top level function.
 */
void boot_db( void )
{
    /*
     * Init some data space stuff.
     */
    fprintf( stderr, "-------------------------------[ Boot Log ]--------------------------------\n" );

    {
	init_string_space( );
	fBootDb = TRUE;
    }

    /*
     * Init random number generator.
     */
    {
	init_mm( );
    }

    /*
     * Assign gsn's for skills which have them.
     */
    log_string( "Assigning global skill numbers." );
    {
	int sn;

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

	for( sn = 0; event_table[sn].type; sn++ )
	    *event_table[sn].type = sn;
    }


    /*
     * Initialise the database loading/saving code.
     */
    init_data_desc_tables( );

    log_string( "Loading system global information." );
    load_file( SYSINFO_FILE );
    load_unique_key( );
    load_file( SYSTEM_DIR RELIGION_FILE );
    load_file( SYSTEM_DIR CLAN_FILE );
    load_file( SYSTEM_DIR PLANE_FILE );
    load_file( SYSTEM_DIR SOCIAL_FILE );

    /*
     * Read in all the area files.
     */
    fprintf( stderr, "%s :: Reading in area files",
	     myctime( &current_time ) );
    if( IS_SET( SysInfo->flags, SYSINFO_VERBOSE_LOG ) )
	fprintf( stderr, ":\n" );
    load_file_list( AREA_DIR, AREA_LIST );
    if( !IS_SET( SysInfo->flags, SYSINFO_VERBOSE_LOG ) )
	fprintf( stderr, "\n" );

    /*
     * Fix up exits.
     * Declare db booting over.
     * Reset all areas once.
     * Load up the notes file.
     */
    {
	log_string( "Fixing exits" );
	fix_exits( );
	add_religion_tokens( );
	fBootDb = FALSE;
	daPrompt = str_dup( "<%h/%Hhp %m/%Mma %v/%Vmv %xtnl> " );
	boot_done( );
	log_string( "System Boot finished: Reseting areas" );
	init_economy( );
	init_areas( );
	init_supermob( );
	load_notes( );
	save_notes( );
	load_ban( );
	init_auction( );
	load_file( SYSTEM_DIR HIGHEST_FILE );
	load_file( SYSTEM_DIR BURIED_FILE );
	load_badnames( );
	load_poses( );
#if defined( HAVE_ISPELL )
	ispell_init( );
#endif

	battle_min = 0;
	battle_max = 0;
    }

    return;
}


/*
 * Save everything.
 */
void db_dump( )
{
    do_asave( NULL, "" );
    save_socials( );
    save_buried( );
    save_highest( );
    save_unique_key( );
    save_religions( );
    save_clans( );
    save_badnames( );
    save_poses( );
    save_planes( );
}


/*
 * Special data reading and interpretation for area files.
 * Possibly can be used for pfiles as well.
 * --Symposium
 */
bool special_read( FILE *fp, struct file_read_type *read_table, void *vo )
{
    int stat;
    int letter;
    char buf[ MAX_INPUT_LENGTH * 2 ];
    char *word;
    int i, num_keys, last_key = 0;

    for( num_keys = 0; *read_table[num_keys].name; )
	 num_keys++;

    while( !feof( fp ) )
    {
	letter = fread_letter( fp );
	if( letter == EOF )
	{
	    bug( "special_read: EOF reached." );
	    return 1;
	}
	if( letter == '.' )
	{
	    fread_to_eol( fp );
	    break;
	}
	if( letter == '!' || letter == '?' )
	{
	    ungetc( letter, fp );
	    break;
	}
	if( letter == '*' )
	{
	    fread_to_eol( fp );
	    continue;
	}

	word = fread_word( fp, &stat );
	for( i = last_key; i < last_key + num_keys; ++i )
	    if( !str_cmp( read_table[i % num_keys].name, word ) )
	    {
		i %= num_keys;
		last_key = i;
		break;
	    }

	/*
	 * OHNO! not known to me!
	 * This also accounts for a duplicate line, only if they are
	 * subsequent lines.
	 * It isn't really necessary to detect this but it is a side effect of
	 * the simplistic check, which isn't all that bad really.
	 */
	if( last_key != i )
	{
	    last_key = 0;
	    bug( "Character %c encountered, word %s.", letter, word );
	    switch( letter )
	    {
	    default:
		fread_to_eol( fp );
		break;
	    case '#':
		fread_number( fp, &stat );
		break;
	    case '%':
	    case '$':		/* fread_string has more room than temp_. */
		free_string( fread_string( fp, &stat ) );
		break;
	    case '>':
		do {
		    fread_to_eol( fp );
		    letter = fread_letter( fp );
		} while( letter != '.' && !feof( fp ) );
		break;
	    }
	    continue;
	}

	read_table[i].standard = SPECIFIED;
	if( read_table[i].read_fun )
	{
	    if( !(*read_table[i].read_fun)( fp, vo ) )
	    {
		bug( "Bad %s section encountered.", word );
		return FALSE;
	    }
	}
	else switch( letter )
	{
	case '@':
	case '!':
	default:
	    bug( "Unusual character found in file." );
	    fread_to_eol( fp );
	    break;
	case '$':
	    *(char **)read_table[i].target = fread_string( fp, &stat );
	    break;
	case '#':
	    *(int *)read_table[i].target = fread_number( fp, &stat );
	    break;
	case '%':
	    word = fread_word( fp, &stat );
	    temp_fread_string( fp, buf );
	    get_lookup( (int *)read_table[i].target, word, buf );
	    break;
	case '>':
	    bug( "No function for section >%s.", word );
	    do {
		fread_to_eol( fp );
		letter = fread_letter( fp );
	    } while( letter != '.' && !feof( fp ) );
	    break;
	}
    }

    for( i = 0; read_table[i].target; i++ )
    {
	switch( read_table[i].standard )
	{
	case MAND:
	    bug( "Problem in area section, MAND field %s missing.",
		       read_table[i].name );
	    return FALSE;
	case SPECIFIED:
	    break;
	case REALLY_STRING:
	    *(char**)read_table[i].target = &str_empty[0];
	    break;
	default:
	    *(int*)read_table[i].target = read_table[i].standard;
	    break;
	}
    }
    return TRUE;
}

/*
 * Sets vnum range for area using OLC protection features.
 */
void assign_area_vnum( int vnum )
{
    if( area_last->lvnum == 0 || area_last->uvnum == 0 )
	area_last->lvnum = area_last->uvnum = vnum;

    if( vnum < area_last->lvnum )
	area_last->lvnum = vnum;
    else if( vnum > area_last->uvnum )
	area_last->uvnum = vnum;
    return;
}


/*
 * Update the ban file upon call.
 * Written by Tre of EnvyMud and modified by Kahn
 */
void ban_update( void )
{
    FILE *fp;
    BAN_DATA *pban;

    if( !( fp = open_file( SYSTEM_DIR BAN_FILE, "w", FALSE ) ) )
    {
	bug( "Ban_update: open_file of BAN_FILE failed" );
	return;
    }

    for( pban = ban_list; pban; pban = pban->next )
    {
	fprintf( fp, "Ban\t" );
	fwrite_quoted_string( fp, pban->name );
	fputc( ';', fp );
	fputc( '\n', fp );
    }

    close_file( fp );

    return;
}


/*
 * Load up the ban file
 */
void load_ban( void )
{
    BAN_DATA *pban;
    FILE *fp;

    if( !( fp = open_file( SYSTEM_DIR BAN_FILE, "r", FALSE ) ) )
	return;

    for( ;; )
    {
	char *word;
	int stat;

	word = fread_word( fp, &stat );
	if( word[0] == '#' )
	{
	    fread_to_eol( fp );
	    continue;
	}
	if( !str_cmp( word, "eof" ) )
	    break;
	if( str_cmp( word, "ban" ) )
	{
	    bug( "Unusual entry in ban file: '%s'", word );
	    break;
	}

	if( ban_free == NULL )
	{
	    pban = alloc_perm( sizeof( *pban ) );
	}
	else
	{
	    pban = ban_free;
	    ban_free = ban_free->next;
	}

	pban->name = fread_string( fp, &stat );
	pban->next = ban_list;
	ban_list = pban;

	if( fread_letter( fp ) != ';' )
	{
	    bug( "Missing semi-colon in ban file." );
	    break;
	}
    }
    close_file( fp );
}


void save_highest( void )
{
    FILE *fp;
    HIGHEST_DATA *high;

    fp = open_file( SYSTEM_DIR HIGHEST_FILE, "w", TRUE );
    if( !fp )
    {
	bug( "Couldn't open highest file." );
	return;
    }

    for( high = highest_first; high; high = high->next )
    {
	write_next_item( fp, "High", high );
	fputc( '\n', fp );
    }
    fprintf( fp, "\neof\n\n" );

    close_file( fp );
    return;
}


/*
 * saves buried items and player corpses in rooms.
 */
void save_buried( void )
{
    OBJ_DATA *obj;
    RESET_DATA *rst;
    FILE *fp;

    fp = open_file( SYSTEM_DIR BURIED_FILE, "w", TRUE );
    if( !fp )
    {
	log_string( "Couldn't open buried items file." );
	return;
    }

    for( obj = object_list; obj; obj = obj->next )
    {
	if( obj->deleted || ( !IS_SET( obj->extra_flags, ITEM_BURIED )
			      && obj->pIndexData->vnum != OBJ_VNUM_CORPSE_PC )
	    || !obj->in_room )
	    continue;

	/* check if resets load the object.
	 * otherwise there is a neverending increase in buried items,
	 * Known bug: you cant bury an item in the same room
	 *	      as one that repops as the same type
	 * --Symposium
	 */
	for( rst = obj->in_room->reset_first; rst; rst = rst->next )
	{
	    if( rst->command == 'O' && rst->arg1 == obj->pIndexData->vnum
		&& IS_SET( obj->pIndexData->extra_flags, ITEM_BURIED ) )
		break;
	}
	if( rst )
	    continue;

	/* write object including objects inside it */
	write_next_item( fp, "Obj", obj );
    }
    fprintf( fp, "\neof\n\n" );

    close_file( fp );
    return;
}


void load_unique_key( )
{
    int stat;
    FILE *fp;

    fp = open_file( SYSTEM_DIR UNIQUE_KEY_FILE, "r", FALSE );
    if( !fp )
    {
	bug( "No unique key file." );
	return;
    }

    SysInfo->object_key = fread_number( fp, &stat );

    close_file( fp );
    return;
}


void save_unique_key( )
{
    FILE *fp;

    fp = open_file( SYSTEM_DIR UNIQUE_KEY_FILE, "w", FALSE );
    if( !fp )
    {
	bug( "No unique key file." );
	return;
    }

    fprintf( fp, "%d\n", SysInfo->object_key );

    close_file( fp );
    return;
}


void load_badnames( )
{
    BAN_DATA *pbad;
    FILE *fp;

    if( !( fp = open_file( SYSTEM_DIR BADNAME_FILE, "r", FALSE ) ) )
    {
	bug( "No badname file." );
	return;
    }

    for( ;; )
    {
	char letter;
	int stat;

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

	if( ban_free == NULL )
	{
	    pbad = alloc_perm( sizeof( *pbad ) );
	}
	else
	{
	    pbad = ban_free;
	    ban_free = ban_free->next;
	}

	pbad->name = fread_string( fp, &stat );

	pbad->next = badname_list;
	badname_list = pbad;
    }
    close_file( fp );
    return;
}


void save_badnames( )
{
    FILE *fp;
    BAN_DATA *pbad;

    fp = open_file( SYSTEM_DIR BADNAME_FILE, "w", FALSE );
    if( !fp )
    {
	bug( "No bad name file." );
	return;
    }
    for( pbad = badname_list; pbad; pbad = pbad->next )
    {
	fwrite_quoted_string( fp, pbad->name );
	fputc( '\n', fp );
    }

    close_file( fp );
    return;
}


/*
 * Translate all room exits from virtual to real.
 * Has to be done after all rooms are read in.
 * Check for bad or suspicious reverse exits.
 */
void fix_exits( void )
{
    EXIT_DATA *pexit;
    ROOM_INDEX_DATA *pRoomIndex;
    int iHash;
    int door;

    for( iHash = 0; iHash < MAX_KEY_HASH; iHash++ )
    {
	for( pRoomIndex = room_index_hash[iHash];
	     pRoomIndex;
	     pRoomIndex = pRoomIndex->next )
	{
	    bool fexit;

	    fexit = FALSE;
	    for( door = 0; door <= 5; door++ )
	    {
		if( ( pexit = pRoomIndex->exit[door] ) )
		{
		    fexit = TRUE;
		    if( pexit->vnum <= 0 )
			pexit->to_room = NULL;
		    else
			pexit->to_room = get_room_index( pexit->vnum );
		    /*
		     * added so exits can have a reference to the
		     * room they are in, --Symposium
		     */
		    pexit->next = exit_list;
		    exit_list = pexit;
		    pexit->in_room = pRoomIndex;
		}
	    }

	    if( !fexit )
		SET_BIT( pRoomIndex->room_flags, ROOM_NO_MOB );
	    /*
	     * While we are cycling all rooms,
	     * may as well add mudprog triggers.
	     * -- Symposium.
	     */
	    add_room_triggers( pRoomIndex );
	}
    }

    return;
}


void init_areas( void )
{
    AREA_DATA *area;
    int time;

    for( area = area_first; area; area = area->next )
    {
	reset_area( area );

	/* refer to ecb_area_reset for the proper version */
	time = area->age * PULSE_PER_SECOND * 60;
	time = number_range( time * 2 / 3, time * 4 / 3 );
	create_area_event( area, evn_area_reset, time );
    }
}


void bad_reset( ROOM_INDEX_DATA *pRoom, RESET_DATA *pReset )
{
    bug( "Bad reset in room %d: %c %d %d.",
	 pRoom->vnum, pReset->command, pReset->arg1, pReset->arg2 );
    switch( pReset->command )
    {
    case 'M':
	bug( "Mobile vnum non-existant." );
	break;
    case 'O':	case 'P':
    case 'E':	case 'G':
	bug( "Object vnum non-existant." );
	break;
    default:
	break;
    }

    if( pReset == pRoom->reset_first )
    {
	pRoom->reset_first = pReset->next;
	if( pRoom->reset_last == pReset )
	    pRoom->reset_last = NULL;
    }
    else
    {
	RESET_DATA *tmp;

	for( tmp = pRoom->reset_first; tmp; tmp = tmp->next )
	{
	    if( tmp->next == pReset )
		break;
	}
	if( !tmp )
	{
	    bug( "Can't free reset, not on list." );
	    return;
	}
	tmp->next = pReset->next;
	if( pRoom->reset_last == pReset )
	    pRoom->reset_last = tmp;
    }
    free_reset_data( pReset );
}


/* OLC
 * Reset one room.  Called by reset_area and olc.
 */
void reset_room( ROOM_INDEX_DATA *pRoom )
{
    RESET_DATA *pReset;
    CHAR_DATA *pMob;
    OBJ_DATA *pObj;
    CHAR_DATA *LastMob = NULL;
    OBJ_DATA *LastObj = NULL;
    int iExit;
    int level = 0;

    if( !pRoom )
	return;

    if( IS_SET( pRoom->room_flags, ROOM_FLOODED ) )
	send_to_room( "The water slowly flows away.\n\r", pRoom );
    REMOVE_BIT( pRoom->room_flags, ROOM_FLOODED );

    for( iExit = 0; iExit < MAX_DIR; iExit++ )
    {
	EXIT_DATA *pExit;

	if( ( pExit = pRoom->exit[iExit] )
	    && !IS_SET( pExit->exit_info, EX_BASHED ) )	/* Skip Bashed. */
	{
	    pExit->exit_info = pExit->rs_flags;
	    if( ( pExit->to_room != NULL )
		&& ( ( pExit = pExit->to_room->exit[rev_dir[iExit]] ) ) )
	    {
		/* nail the other side */
		pExit->exit_info = pExit->rs_flags;
	    }
	}
    }

    for( pReset = pRoom->reset_first; pReset != NULL; pReset = pReset->next )
    {
	MOB_INDEX_DATA *pMobIndex;
	OBJ_INDEX_DATA *pObjIndex;
	OBJ_INDEX_DATA *pObjToIndex;
	EXIT_DATA *pExit;
	int d0, d1;

	switch( pReset->command )
	{
	default:
	    bad_reset( pRoom, pReset );
	    break;

	case 'M':
	    LastObj = NULL;
	    if( !( pMobIndex = get_mob_index( pReset->arg1 ) ) )
	    {
		bad_reset( pRoom, pReset );
		break;
	    }

	    if( SysInfo->total_logins
		&& pMobIndex->reset_chance < 1000
		&& number_range( 1, 1000 ) > pMobIndex->reset_chance )
		break;

	    /*
	     * Some hard coding.
	     */
	    if( !strcmp( spec_table[pMobIndex->spec_fun].spec_name,
			 "spec_cast_ghost" )
		&& pRoom->area->plane->weather.sunlight != SUN_DARK )
	    {
		LastMob = NULL;
		break;
	    }

	    /* enables negative maximums.
	       to count number of setinel mobs in the room, not game */
	    if( pReset->arg2 < 0 && xIS_SET( pMobIndex->act, ACT_SENTINEL ) )
	    {
		/* reuse iExit */
		iExit = 0;
		for( pMob = pRoom->people; pMob; pMob = pMob->next_in_room )
		{
		    if( !pMob->deleted && pMob->pIndexData == pMobIndex )
			iExit--;
		}
		if( iExit <= pReset->arg2 || pMobIndex->count <= pReset->arg2 )
		{
		    LastMob = NULL;
		    break;
		}
	    }
	    else if( pMobIndex->count >= abs( pReset->arg2 ) )
	    {
		LastMob = NULL;
		break;
	    }

	    pMob = create_mobile( pMobIndex );

	    /*
	     * Some more hard coding.
	     */
	    if( room_is_dark( pRoom ) )
		xSET_BIT( pMob->affected_by, AFF_INFRARED );

	    /*
	     * Pet shop mobiles get ACT_PET set.
	     */
	    {
		ROOM_INDEX_DATA *pRoomIndexPrev;

		pRoomIndexPrev = get_room_index( pRoom->vnum - 1 );
		if( pRoomIndexPrev
		    && IS_SET( pRoomIndexPrev->room_flags, ROOM_PET_SHOP ) )
		    xSET_BIT( pMob->act, ACT_PET );
	    }

	    char_to_room( pMob, pRoom );
	    pMob->recall_room = pRoom->vnum;
	    economize_mobgold( pMob );

	    LastMob = pMob;
	    level = URANGE( 0, pMob->level, LEVEL_HERO );

	    if( xIS_SET( pMobIndex->progtypes, REPOP_PROG ) )
		mprog_percent_check( pMob, NULL, NULL, NULL, REPOP_PROG );
	    break;

	case 'O':
	    LastObj = NULL;
	    if( !( pObjIndex = get_obj_index( pReset->arg1 ) ) )
	    {
		bad_reset( pRoom, pReset );
		break;
	    }

	    if( pRoom->area->nplayer > 0
		|| ( SysInfo->total_logins
		     && pObjIndex->reset_chance < 1000
		     && number_range( 1, 1000 ) > pObjIndex->reset_chance )
		|| count_obj_list( pObjIndex, pRoom->contents ) > 0 )
		break;

	    if( pObjIndex->level == 0 )
		pObj = create_object( pObjIndex, level );
	    else
		pObj = create_object( pObjIndex, 0 );

	    /* Some hard coding */
	    if( !IS_SET( pObj->extra_flags, ITEM_BURIED ) )
		pObj->cost = 0;

	    obj_to_room( pObj, pRoom );

	    /* for explosives and imp timer */
	    strip_events( &pObj->events, evn_obj_decay );
	    strip_events( &pObj->events, evn_imp_grab );

	    LastObj = pObj;

	    if( xIS_SET( pObjIndex->progtypes, REPOP_PROG ) )
		oprog_percent_check( NULL, pObj, NULL, REPOP_PROG );
	    break;

	case 'P':
	    /*
	     * Place an object inside the last object.
	     * At the moment this only allows one level of recursion
	     * Future versions may allow this by searching the top of
	     * object_list (maybe a flag of some sort would be required)
	     * tell me if you implement this.
	     * -- Symposium
	     */
	    if( !( pObjIndex = get_obj_index( pReset->arg1 ) ) )
	    {
		bad_reset( pRoom, pReset );
		break;
	    }

	    if( !( pObjToIndex = get_obj_index( pReset->arg2 ) ) )
	    {
		bad_reset( pRoom, pReset );
		break;
	    }

	    if( SysInfo->total_logins
		&& pObjIndex->reset_chance < 1000
		&& number_range( 1, 1000 ) > pObjIndex->reset_chance )
		break;

	    if( !LastObj )
	    {
		if( pRoom->area->nplayer > 0
		    || !( LastObj = get_obj_type( pObjToIndex ) )
		    || count_obj_list( pObjIndex, LastObj->contains ) > 0 )
		    break;
	    }
	    else
	    {
		for( ; LastObj->in_obj; LastObj = LastObj->in_obj )
		    if( LastObj->pIndexData == pObjToIndex )
			break;
	    }

	    if( pObjIndex->level == 0 )
		pObj = create_object( pObjIndex, level );
	    else
		pObj = create_object( pObjIndex, 0 );

	    obj_to_obj( pObj, LastObj );

	    /*
	     * Ensure that the container gets reset.	OLC 1.1b
	     */
	    if( LastObj->item_type == ITEM_CONTAINER )
		LastObj->value[1] = LastObj->pIndexData->value[1];
	    LastObj = pObj;
	    if( xIS_SET( pObjIndex->progtypes, REPOP_PROG ) )
		oprog_percent_check( NULL, pObj, NULL, REPOP_PROG );
	    break;

	case 'G':
	case 'E':
	    if( !( pObjIndex = get_obj_index( pReset->arg1 ) ) )
	    {
		bad_reset( pRoom, pReset );
		LastObj = NULL;
		break;
	    }

	    if( !LastMob )
	    {
		LastObj = NULL;
		break;
	    }

	    if( SysInfo->total_logins
		&& pObjIndex->reset_chance < 1000
		&& number_range( 1, 1000 ) > pObjIndex->reset_chance )
		break;

	    if( LastMob->pIndexData->pShop )	/* Shop-keeper? */
	    {
		pObj = create_object( pObjIndex, 0 );
		if( pReset->command == 'G' )
		    SET_BIT( pObj->extra_flags, ITEM_INVENTORY );
	    }
	    else
	    {
		if( pObjIndex->level == 0 )
		    pObj = create_object( pObjIndex, level );
		else
		    pObj = create_object( pObjIndex, 0 );
	    }
	    obj_to_char( pObj, LastMob );
	    if( pReset->command == 'E' )
		equip_char( LastMob, pObj, pReset->arg2 );
	    LastObj = pObj;
	    if( xIS_SET( pObjIndex->progtypes, REPOP_PROG ) )
		oprog_percent_check( NULL, pObj, NULL, REPOP_PROG );
	    break;

	case 'D':
	    LastObj = NULL;
	    break;

	case 'R':
	    LastObj = NULL;
	    if( IS_SET( pRoom->area->area_flags, AREA_CHANGED ) )
		break;

	    if( pReset->arg1 != 0 )
		for( d0 = 0; d0 < pReset->arg1 - 1; d0++ )
		{
		    d1 = number_range( d0, pReset->arg1 - 1 );
		    if( ( pRoom->exit[d0]
			  && IS_SET( pRoom->exit[d0]->exit_info,
				     EX_TEMPORARY ) )
			|| ( pRoom->exit[d1]
			     && IS_SET( pRoom->exit[d1]->exit_info,
					EX_TEMPORARY ) ) )
			continue;
		    pExit = pRoom->exit[d0];
		    pRoom->exit[d0] = pRoom->exit[d1];
		    pRoom->exit[d1] = pExit;
		}
	    else
		for( d0 = 0; d0 < 5; d0++ )
		{
		    if( !IS_SET( pReset->arg2, 1 << d0 ) )
			continue;

		    do
		    {
			d1 = number_range( d0, 5 );
		    }
		    while( !IS_SET( pReset->arg2, 1 << d1 ) );
		    if( ( pRoom->exit[d0]
			  && IS_SET( pRoom->exit[d0]->exit_info,
				     EX_TEMPORARY ) )
			|| ( pRoom->exit[d1]
			     && IS_SET( pRoom->exit[d1]->exit_info,
					EX_TEMPORARY ) ) )
			continue;
		    pExit = pRoom->exit[d0];
		    pRoom->exit[d0] = pRoom->exit[d1];
		    pRoom->exit[d1] = pExit;
		}
	    break;
	}
    }

    return;
}


/* OLC
 * Reset one area.
 */
void reset_area( AREA_DATA *pArea )
{
    ROOM_INDEX_DATA *pRoom;
    int vnum;

    /*
     * Karmic advancement for the order that holds a popular area.
     * Capped at 5.
     */
    if( pArea->order && pArea->nplayer > 0 )
	add_karma( pArea->order, 5 - power( 5, -20, pArea->nplayer ) );

    for( vnum = pArea->lvnum; vnum <= pArea->uvnum; vnum++ )
    {
	if( ( pRoom = get_room_index( vnum ) ) )
	    reset_room( pRoom );
    }

    return;
}


/*
 * Go through all areas, and set up initial economy based on mob
 * levels and gold.
 */
void init_economy( void )
{
    AREA_DATA *tarea;
    MOB_INDEX_DATA *mob;
    int idx;

    for( tarea = area_first; tarea; tarea = tarea->next )
    {
	/* skip area if they already have some gold */
	if( tarea->economy > 10000 )
	    continue;
	tarea->economy = tarea->ave * tarea->ave * 40;
	for( idx = tarea->lvnum; idx < tarea->uvnum; idx++ )
	    if( ( mob = get_mob_index( idx ) ) != NULL )
		tarea->economy += 20 + 3 * mob->level;
    }
}


/*
 * Take a character data from the free list and clean it out.
 */
CHAR_DATA *new_character( bool player )
{
    CHAR_DATA *ch;

    if( !char_free )
    {
	ch = alloc_perm( sizeof( CHAR_DATA ) );
    }
    else
    {
	ch = char_free;
	char_free = char_free->next;
    }

    clear_char( ch );

    if( player )
    {
	if( !pcdata_free )
	{
	    ch->pcdata = alloc_perm( sizeof( *ch->pcdata ) );
	}
	else
	{
	    ch->pcdata = pcdata_free;
	    pcdata_free = pcdata_free->next;
	}

	memset( ch->pcdata, 0, sizeof( PC_DATA ) );
    }

    ch->unique_key = SysInfo->char_key++;
    if( SysInfo->char_key >= (1<<30) )
	SysInfo->char_key = 1;

    ch->deleted = FALSE;

    return ch;
}


OBJ_DATA *new_object( )
{
    OBJ_DATA *obj;

    if( !obj_free )
    {
	obj = alloc_perm( sizeof( *obj ) );
    }
    else
    {
	obj = obj_free;
	obj_free = obj_free->next;
    }

    memset( obj, 0, sizeof( OBJ_DATA ) );

    obj->magic = MAGIC_NUM_OBJECT;
    obj->deleted = FALSE;

    return obj;
}


/*
 * Create an instance of a mobile.
 */
CHAR_DATA *create_mobile( MOB_INDEX_DATA *pMobIndex )
{
    CHAR_DATA *mob;
    int i;
    char *p;

    if( !pMobIndex )
    {
	bug( "Create_mobile: NULL pMobIndex." );
	exit( 1 );
    }

    mob = new_character( FALSE );

    mob->pIndexData = pMobIndex;

    if( ( ( p = strchr( pMobIndex->short_descr, '%' ) )
	  && ( str_str( p, "%a" ) || str_str( p, "%z" ) ) )
	|| ( ( p = strchr( pMobIndex->long_descr, '%' ) )
	     && ( str_str( p, "%a" ) || str_str( p, "%z" ) ) ) )
    {
	char buf[MAX_STRING_LENGTH];
	char tmp[MAX_INPUT_LENGTH];
	int col;

	col = number_range( 0, 7 );
	rand_colour( &mob->name, pMobIndex->name, col );
	rand_colour( &mob->short_descr, pMobIndex->short_descr, col );
	rand_colour( &mob->long_descr, pMobIndex->long_descr, col );
	strcpy( buf, mob->name );
	free_string( mob->name );
	mob->name = str_dup( kill_colour( tmp, buf ) );
    }
    else
    {
	mob->name = str_dup( pMobIndex->name );
	mob->short_descr = str_dup( pMobIndex->short_descr );
	mob->long_descr = str_dup( pMobIndex->long_descr );
    }
    mob->description = str_dup( pMobIndex->description );

    mob->num_hits = -1;
    mob->spec_fun = pMobIndex->spec_fun;

    mob->level = number_fuzzy( number_fuzzy( pMobIndex->level ) );
    mob->class = -1;
    vcopy( mob->act, pMobIndex->act );
    xSET_BIT( mob->act, ACT_IS_NPC );
    if( IS_SET( pMobIndex->area->area_flags, AREA_SENTINEL ) )
	xSET_BIT( mob->act, ACT_SENTINEL );
    vcopy( mob->affected_by, pMobIndex->affected_by );
    if( xIS_SET( mob->affected_by, AFF_SLEEP ) )
	mob->position = POS_SLEEPING;
    else
	mob->position = POS_STANDING;
    mob->alignment = pMobIndex->alignment;
    mob->sex = pMobIndex->sex;
    mob->race = pMobIndex->race;
    if( IS_SET( race_table[mob->race].race_abilities, RACE_SANCT ) )
	xSET_BIT( mob->affected_by, AFF_SANCTUARY );
    mob->body_parts = pMobIndex->body_parts;
    mob->class = pMobIndex->class;
    mob->gold = number_range( mob->level / 10 + 1, mob->level * 3 );
    if( xIS_SET( mob->act, ACT_BANKER ) )
	mob->gold *= number_fuzzy( 4 );
    mob->exp = get_tnl( mob ) * 10;

    mob->speed = number_fuzzy( mob->level / 8 + 1 )
	+ number_range( -1, mob->level / 20 )
	+ number_range( -1, mob->level / 20 ) - 1;

    mob->armour = 150 - mob->level * 9 - mob->level * mob->level / 50;
    mob->hitroll = mob->level / 2 + power( 5, 10, get_curr_str( mob ) - 15 ) - 4;
    mob->hitroll += get_speed( mob ) / 2;
    mob->hitroll += 40 * pow( 1.5, 0.023 * mob->level ) - 42;

    mob->max_hit = number_range( mob->level * mob->level / 3,
				 mob->level * mob->level * 5 / 4 );
    if( mob->class < 0 )
    {
	mob->max_hit = mob->level * 2 + get_curr_con( mob ) * mob->max_hit / 15;
	for( i = 0; i < MAGIC_MAX; ++i )
	    mob->max_mana[i] = 20;
    }
    else
    {
	mob->max_hit = mob->max_hit * get_curr_con( mob ) * class_table[mob->class].hp_max;
	mob->max_hit = mob->max_hit / 240 + mob->level * 6;
	for( i = 0; i < MAGIC_MAX; ++i )
	{
	    mob->max_mana[i] = ( 10 + get_curr_int( mob ) )
		* class_table[mob->class].fMana;
	    mob->max_mana[i] = mob->max_mana[i] * mob->level / 50 + 20;
	}
    }
    mob->max_hit *= 100 + race_table[mob->race].hp_gain / 2;
    mob->max_hit /= 100;
    if( mob->level > 10 )
	mob->max_hit += mob->level * mob->level / 2;
    mob->hit = mob->max_hit;
    mob->max_move = mob->move = mob->hit;
    for( i = 0; i < MAGIC_MAX; ++i )
    {
	mob->max_mana[i] *= 100 + race_table[mob->race].mana_gain[i] / 2;
	mob->max_mana[i] /= 100;
	mob->mana[i] = mob->max_mana[i];
    }
    /*
     * Insert in list.
     */
    mob->next = char_list;
    char_list = mob;
    pMobIndex->count++;
    add_mob_triggers( mob );	/* event system/mob program */

    return mob;
}


void rand_colour( char **to, char *from, int col )
{
    const char *colour_name[] =
    {
	"&wwhite", "&rred", "&yyellow", "&mmagenta",
	"&bblue", "&ccyan", "&kblack", "&ggreen"
    };
    char buf[MAX_STRING_LENGTH];
    char *p, *write;

    buf[0] = '\0';
    p = from;
    write = &buf[0];
    while( *p )
    {
	if( *p != '%' )
	{
	    *write++ = *p++;
	    continue;
	}

	p++;
	if( *p == 'z' )
	{
	    *write++ = '&';
	    *write++ = colour_name[col][1];
	}
	else if( *p == 'Z' )
	{
	    *write++ = '&';
	    *write++ = UPPER( colour_name[col][1] );
	}
	else if( UPPER( *p ) == 'A' )
	{
	    strcpy( write, colour_name[col] );
	    if( *p == 'A' )
		write[1] = UPPER( write[1] );
	    write = strchr( write, '\0' );
	}
	else
	    *write++ = *p;
	p++;
    }
    *write = '\0';
    *to = str_dup( buf );
}


/*
 * Create an instance of an object.
 */
OBJ_DATA *create_object( OBJ_INDEX_DATA *pObjIndex, int level )
{
    OBJ_DATA *obj;
    char *p;

    if( !pObjIndex )
    {
	bug( "Create_object: NULL pObjIndex." );
	exit( 1 );
    }

    obj = new_object( );

    obj->pIndexData = pObjIndex;
    obj->in_room = NULL;
    if( level )
	obj->level = number_fuzzy( level );
    else
	obj->level = number_fuzzy( pObjIndex->level );
    obj->wear_loc = WEAR_NONE;

    if( ( ( p = strchr( pObjIndex->short_descr, '%' ) )
	  && ( str_str( p, "%a" ) || str_str( p, "%z" ) ) )
	|| ( ( p = strchr( pObjIndex->description, '%' ) )
	     && ( str_str( p, "%a" ) || str_str( p, "%z" ) ) ) )
    {
	char buf[MAX_STRING_LENGTH];
	char tmp[MAX_INPUT_LENGTH];
	int col;

	col = number_range( 0, 7 );
	rand_colour( &obj->name, pObjIndex->name, col );
	rand_colour( &obj->short_descr, pObjIndex->short_descr, col );
	rand_colour( &obj->description, pObjIndex->description, col );
	rand_colour( &obj->action, pObjIndex->action, col );
	strcpy( buf, obj->name );
	obj->name = str_dup( kill_colour( tmp, buf ) );
    }
    else
    {
	obj->name = str_dup( pObjIndex->name );
	obj->short_descr = str_dup( pObjIndex->short_descr );
	obj->description = str_dup( pObjIndex->description );
	obj->action = str_dup( pObjIndex->action );
    }

    obj->item_type = pObjIndex->item_type;
    obj->extra_flags = pObjIndex->extra_flags;
    obj->wear_flags = pObjIndex->wear_flags;
    obj->value[0] = pObjIndex->value[0];
    obj->value[1] = pObjIndex->value[1];
    obj->value[2] = pObjIndex->value[2];
    obj->value[3] = pObjIndex->value[3];
    obj->weight = pObjIndex->weight;
    obj->required_skill = pObjIndex->required_skill;
    obj->condition = pObjIndex->condition;

    if( pObjIndex->cost )
	obj->cost = number_range( pObjIndex->cost * 4 / 5,
				  pObjIndex->cost * 6 / 5 );
    else
	obj->cost = number_fuzzy( 4 )
	    * number_fuzzy( obj->level ) * number_fuzzy( obj->level );
    obj->deleted = FALSE;

    /*
     * Mess with object properties.
     */
    switch( obj->item_type )
    {
    default:
	bug( "load_object: vnum %d bad type.", pObjIndex->vnum );
	break;

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

    case ITEM_GEM:
	obj->value[1] = number_fuzzy( obj->value[1] );
	break;

    case ITEM_EXPLOSIVE:
    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( obj->level / 4 + 1 ) );
	if( IS_SET( obj->extra_flags, ITEM_SHARP ) )
	    obj->value[1] = obj->value[1] * 2 - 2;
	obj->value[2] = number_fuzzy( number_fuzzy( 3 * obj->level / 4 + 5 ) );
	break;

    case ITEM_ARMOUR:
	obj->value[0] = number_fuzzy( obj->level / 8 + 1 );
	if( obj->value[1] >= 0 )
	    obj->value[1] += dice( 2, 11 ) - 12;
	break;

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

    case ITEM_MONEY:
    case ITEM_TREASURE:
	if( obj->value[0] < obj->cost / 2 )
	    obj->value[0] = obj->cost;
	break;

    case ITEM_BOOK:
	obj->cost *= 100;
	break;
    }
    obj->unique_key = SysInfo->object_key++;
    if( SysInfo->object_key >= (1<<30) )
	SysInfo->object_key = 1;

    obj->next = object_list;
    object_list = obj;
    pObjIndex->count++;

    return obj;
}


/*
 * Clear a new character.
 */
void clear_char( CHAR_DATA *ch )
{
    int i;

    memset( ch, 0, sizeof( CHAR_DATA ) );
    ch->magic		= MAGIC_NUM_CHAR;
    ch->name		= &str_empty[0];
    ch->short_descr	= &str_empty[0];
    ch->long_descr	= &str_empty[0];
    ch->description	= &str_empty[0];
    ch->logon		= current_time;
    ch->armour		= 100;
    ch->position	= POS_STANDING;
    ch->level		= 0;
    ch->race		= 0;
    ch->practice	= 300;
    ch->hit		= 20;
    ch->max_hit		= 20;
    for( i = 0; i < MAGIC_MAX; ++i )
    {
	ch->mana[i]	= 100;
	ch->max_mana[i]	= 20;
    }
    ch->move		= 100;
    ch->max_move	= 100;
    ch->leader		= NULL;
    ch->master		= NULL;
    ch->tracking	= NULL;
    ch->on		= NULL;
    ch->deleted		= FALSE;
    ch->extra_bits	= 0;
    ch->recall_room	= ROOM_VNUM_TEMPLE;
    vzero( ch->affected_by );
    return;
}


/*
 * Free a character.
 */
void free_char( CHAR_DATA *ch )
{
    OBJ_DATA *obj;
    OBJ_DATA *obj_next;
    AFFECT_DATA *paf;
    ALIAS_DATA *al, *next_al;
    MPROG_VAR *var, *varnext;

    /*
     * This places the character in a place we know exists and allows for
     * more controled removal of mud effects and such.	Suggested by Erwin.
     */
    ch->in_room = get_room_index( ROOM_VNUM_LIMBO );

    for( obj = ch->carrying; obj; obj = obj_next )
    {
	obj_next = obj->next_content;
	if( obj->deleted )
	    continue;
	extract_obj( obj );
    }

    for( paf = ch->affected; paf; paf = paf->next )
    {
	if( paf->deleted )
	    continue;
	affect_remove( ch, paf );
    }

    free_string( ch->name );
    free_string( ch->short_descr );
    free_string( ch->long_descr );
    free_string( ch->description );

    for( var = ch->variables; var; var = varnext )
    {
	varnext = var->next;
	free_mpvar( var );
    }
    if( ch->pcdata )
    {
	if( ch->pcdata->quest )
	    free_quest( ch->pcdata->quest );
	for( al = ch->pcdata->aliases; al; al = next_al )
	{
	    next_al = al->next;
	    free_alias( al );
	}
	free_string( ch->pcdata->pwd );
	free_string( ch->pcdata->immname );
	free_string( ch->pcdata->bamfin );
	free_string( ch->pcdata->bamfout );
	free_string( ch->pcdata->setmin );
	free_string( ch->pcdata->setmout );
	free_string( ch->pcdata->immskll );
	free_string( ch->pcdata->title );
	free_string( ch->pcdata->prompt );
	ch->pcdata->next = pcdata_free;
	pcdata_free = ch->pcdata;
    }

    ch->next = char_free;
    char_free = ch;
    return;
}


/*
 * Get an extra description from a list.
 */
char *get_extra_descr( const char *name, EXTRA_DESCR_DATA *ed )
{
    for( ; ed; ed = ed->next )
    {
	if( is_name( name, ed->keyword ) )
	{
	    if( ed->description[0] == '.' )
		return ed->description + 1;
	    else
		return ed->description;
	}
    }
    return NULL;
}


/*
 * Translates mob virtual number to its mob index struct.
 * Hash table lookup.
 */
MOB_INDEX_DATA *get_mob_index( int vnum )
{
    MOB_INDEX_DATA *pMobIndex;

    /*
     * There is a possibility of vnum passed is negative.
     * Trapping suggested by Erwin Andreasen  -Kahn
     */

    if( vnum < 0 )
	return NULL;

    for( pMobIndex = mob_index_hash[vnum % MAX_KEY_HASH];
	 pMobIndex;
	 pMobIndex = pMobIndex->next )
    {
	if( pMobIndex->vnum == vnum )
	    return pMobIndex;
    }

    return NULL;
}


/*
 * Translates mob virtual number to its obj index struct.
 * Hash table lookup.
 */
OBJ_INDEX_DATA *get_obj_index( int vnum )
{
    OBJ_INDEX_DATA *pObjIndex;

    /*
     * There is a possibility of vnum passed is negative.
     * Trapping suggested by Erwin Andreasen  -Kahn
     */

    if( vnum < 0 )
	return NULL;

    for( pObjIndex = obj_index_hash[vnum % MAX_KEY_HASH];
	 pObjIndex;
	 pObjIndex = pObjIndex->next )
    {
	if( pObjIndex->vnum == vnum )
	    return pObjIndex;
    }

    return NULL;
}


/*
 * Translates mob virtual number to its room index struct.
 * Hash table lookup.
 */
ROOM_INDEX_DATA *get_room_index( int vnum )
{
    ROOM_INDEX_DATA *pRoomIndex;

    /*
     * There is a possibility of vnum passed is negative.
     * Trapping suggested by Erwin Andreasen  -Kahn
     */

    if( vnum < 0 )
	return NULL;

    for( pRoomIndex = room_index_hash[vnum % MAX_KEY_HASH];
	 pRoomIndex;
	 pRoomIndex = pRoomIndex->next )
    {
	if( pRoomIndex->vnum == vnum )
	    return pRoomIndex;
    }

    return NULL;
}


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

    do
    {
/*        if( feof( fp ) )
	{
	    bug( "fread_letter: EOF encountered on read.\n\r" );
	    if( fBootDb )
		exit( 1 );
	    return '#';
	    }*/
	c = getc( fp );
    }
    while( isspace( c ) );

    return c;
}


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

    do
    {
	if( feof( fp ) )
	{
	    bug( "fread_to_eol: EOF encountered on read.\n\r" );
	    if( fBootDb )
		exit( 1 );
	    return;
	}
	c = getc( fp );
    }
    while( c != '\n' && c != '\r' && c != EOF );

    do
    {
	if( feof( fp ) )
	{
	    bug( "fread_to_eol: EOF encountered on read.\n\r" );
	    if( fBootDb )
		exit( 1 );
	    return;
	}
	c = getc( fp );
    }
    while( c == '\n' || c == '\r' );

    ungetc( c, fp );
    return;
}


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

    *status = 0;

    do
    {
	if( feof( fp ) )
	{
	    bug( "fread_word: EOF encountered on read.\n\r" );
	    if( fBootDb )
		exit( 1 );
	    return &str_empty[0];
	}
	cEnd = getc( fp );
    }
    while( isspace( cEnd ) );

    if( cEnd == '\'' || cEnd == '"' )
    {
	pword = word;
    }
    else
    {
	word[0] = cEnd;
	pword = word + 1;
	cEnd = ' ';
    }

    for( ; pword < word + MAX_INPUT_LENGTH; pword++ )
    {
	int c = getc( fp );
	if( c == EOF )
	{
	    bug( "fread_word: returned EOF." );
	    *pword = '\0';
	    return word;
	}
	*pword = c;
	if( cEnd == ' ' ? isspace( *pword ) : *pword == cEnd )
	{
	    if( cEnd == ' ' )
		ungetc( *pword, fp );
	    *pword = '\0';
	    return word;
	}
    }

    bug( "Fread_word: word too long." );
    *status = 1;
    return NULL;
}


/*
 * Allocate some ordinary memory,
 *   with the expectation of freeing it someday.
 */
void *alloc_mem( int sMem )
{
    void *pMem;
    int iList;

    for( iList = 0; iList < MAX_MEM_LIST; iList++ )
    {
	if( sMem <= rgSizeList[iList] )
	    break;
    }

    if( iList == MAX_MEM_LIST )
    {
	/* We're leaving, no-one needs to get this message.
	 * The wiznet call in bug() can cause infinite loops here.
	 */
	descriptor_list = NULL;
	char_list = NULL;
	bug( "Alloc_mem: size %d too large.", sMem );
	exit( 1 );
    }

    if( !rgFreeList[iList] )
    {
	pMem = alloc_perm( rgSizeList[iList] );
    }
    else
    {
	pMem = rgFreeList[iList];
	rgFreeList[iList] = *( (void **)rgFreeList[iList] );
    }

    return pMem;
}


/*
 * Free some memory.
 * Recycle it back onto the free list for blocks of that size.
 */
void free_mem( void *pMem, int sMem )
{
    int iList;

    for( iList = 0; iList < MAX_MEM_LIST; iList++ )
    {
	if( sMem <= rgSizeList[iList] )
	    break;
    }

    if( iList == MAX_MEM_LIST )
    {
	bug( "Free_mem: size %d too large.", sMem );
	exit( 1 );
    }

    *( (void **)pMem ) = rgFreeList[iList];
    rgFreeList[iList] = pMem;

    return;
}


/*
 * Allocate some permanent memory.
 * Permanent memory is never freed,
 *   pointers into it may be copied safely.
 */
void *alloc_perm( int sMem )
{
    void *pMem;

    while( sMem % sizeof( long ) != 0 )
	     sMem++;

    if( sMem > MAX_PERM_BLOCK )
    {
	bug( "Alloc_perm: %d too large.", sMem );
	exit( 1 );
    }

    if( !pMemPerm || iMemPerm + sMem > MAX_PERM_BLOCK )
    {
	iMemPerm = 0;
	if( !( pMemPerm = calloc( 1, MAX_PERM_BLOCK ) ) )
	{
	    perror( "Alloc_perm" );
	    exit( 1 );
	}
    }

    pMem = pMemPerm + iMemPerm;
    iMemPerm += sMem;
    nAllocPerm += 1;
    sAllocPerm += sMem;
    return pMem;
}


void dalloc_last( int sMem )
{
    while( sMem % sizeof( long ) != 0 )
	     sMem++;

    iMemPerm -= sMem;
    nAllocPerm -= 1;
    sAllocPerm -= sMem;
}


void do_areas( CHAR_DATA *ch, const char *argument )
{
    AREA_DATA *pArea;
    BUFFER *buf = buffer_new( MAX_STRING_LENGTH );
    char arg[MAX_INPUT_LENGTH];
    char tmp[MAX_INPUT_LENGTH];
    bool showall = FALSE, planerestrict = FALSE;
    int min = 0, max = LEVEL_HERO;
    int found = 0;

    if( IS_IMMORTAL( ch ) && !str_cmp( argument, "all" ) )
	showall = TRUE;
    argument = one_argument( argument, arg );
    if( is_number( arg ) )
    {
	min = max = atoi( arg );
	argument = one_argument( argument, arg );
    }
    if( is_number( arg ) )
    {
	max = atoi( arg );
	one_argument( argument, arg );
    }
    if( arg[0] != '\0' && !is_number( arg ) )
	planerestrict = TRUE;

    buffer_strcat( buf, "&b{&c min&b-&c max &r[&y ave&r]&b} &mName           "
		   "                &r[&yPlane     &r]&b {Religion}&n\n\r" );
    buffer_strcat( buf, LINE_SEPARATOR );
    for( pArea = area_first; pArea; pArea = pArea->next )
    {
	if( !showall && IS_SET( pArea->area_flags, AREA_HIDE ) )
	    continue;
	if( pArea->max < min || pArea->min > max )
	    continue;
	if( planerestrict && str_prefix( arg, pArea->plane->name ) )
	    continue;

	found++;
	bprintf( buf, "&b{&c%4d&b-&c%4d &r[&y%4d&r]&b} &m%s &r[&y%-10s&r]",
		 pArea->min, pArea->max, pArea->ave,
		 colour_strpad( tmp, pArea->name, 30 ), pArea->plane->name );
	if( pArea->order )
	    bprintf( buf, " &b{%s&b}&n\n\r", pArea->order->religion->display_name );
	else
	    buffer_strcat( buf, "&n\n\r" );
    }
    buffer_strcat( buf, LINE_SEPARATOR );
    bprintf( buf, "&gA total of %d area(s) were found.&n\n\r", found );

    send_to_char( buf->data, ch );
    buffer_free( buf );
    return;
}


#define show_mem( name, top, stct ) \
	bprintf( buf, "&g%-12s&c%5d &bx%5d =&c%5dKb&n\n\r", \
		    (name), (top), sizeof( stct ), \
		    (top) * sizeof( stct ) / 1024 )

void do_memory( CHAR_DATA *ch, const char *argument )
{
    CHAR_DATA *rch;
    BUFFER *buf;

    rch = get_char( ch );

    if( !authorized( rch, "memory" ) )
	return;

    buf = buffer_new( MAX_STRING_LENGTH );
    buffer_strcat( buf, "&w----------- &gPermanent Memory &w-----------&n\n\r" );

    show_mem( "Affects", top_affect, AFFECT_DATA );
    show_mem( "Areas", top_area, AREA_DATA );
    show_mem( "Clans", top_clan, CLAN_DATA );
    show_mem( "Events", top_event, EVENT );
    show_mem( "ExDesc", top_ed, EXTRA_DESCR_DATA );
    show_mem( "Exits", top_exit, EXIT_DATA );
    show_mem( "Helps", top_help, HELP_DATA );
    show_mem( "Mobs", top_mob_index, MOB_INDEX_DATA );
    show_mem( "MudProgs", top_mprog, MPROG_DATA );
    show_mem( "MudProgs(G)", top_mprog_global, MPROG_GLOBAL );
    show_mem( "Objects", top_obj_index, OBJ_INDEX_DATA );
    show_mem( "Poses", top_pose, POSE_DATA );
    show_mem( "Resets", top_reset, RESET_DATA );
    show_mem( "Religions", top_religion, RELIGION_DATA );
    show_mem( "Rooms", top_room, ROOM_INDEX_DATA );
    show_mem( "Shops", top_shop, SHOP_DATA );
    show_mem( "Socials", top_social, SOCIAL_DATA );
    bprintf( buf, "&gText Blocks &c%5d &bx????? =&c%5dKb&n\n\r",
	     top_text_block, size_text_block / 1024 );

    bprintf( buf, "&gPerms %7d blocks of %7d bytes.\n\r\n\r",
	     nAllocPerm, sAllocPerm );

    buffer_strcat( buf, "&w---------- &yShared String Heap &w----------&n\n\r" );
    bprintf( buf, "&gShared Strings   %5d strings of %7d bytes (max %d).&n\n\r",
	     nAllocString, sAllocString, MAX_STRING );
    if( nOverFlowString )
	bprintf( buf, "Overflow Strings %5d strings of %7d bytes.\n\r",
		 nOverFlowString, sOverFlowString );
    if( Full )
	bprintf( buf, "&RShared String Heap is full, increase MAX_CHUNKS.&n\n\r" );

    send_to_char( buf->data, ch );
    buffer_free( buf );

    return;
}
#undef show_mem


void do_sysinfo( CHAR_DATA *ch, const char *argument )
{
    send_to_char( "&mDalekenMUD&g Statistics:\n\r", ch );
    charprintf( ch, "&m%s&g, Version %d.%d build %d (%s)\n\r",
		SysInfo->name, SysInfo->version, SysInfo->minor,
		SysInfo->build, SysInfo->build_date );
    charprintf( ch, "File version: %9d   Running on %s.\n\r",
		SysInfo->file_version, SysInfo->platform );
    charprintf( ch, "Since system startup at %s\n\r", myctime( &boot_time ) );
    charprintf( ch, "Characters Online: %4d   Maximum since Startup: %4d\n\r",
		SysInfo->curr_descriptors, SysInfo->max_descriptors );
    charprintf( ch, "Total Logins: %9d   New Characters: %11d\n\r",
		SysInfo->total_logins, SysInfo->new_characters );
    charprintf( ch, "Levels Gained: %8d   Character deaths: %9d\n\r",
		SysInfo->levels, SysInfo->deaths );
    charprintf( ch, "Mobile Deaths: %8d&n\n\r",
		SysInfo->mob_deaths );

    if( get_trust( ch ) >= L_MAS )
    {
	double tot;
	char totu;
	double pm;
	const char *pmu;

	send_to_char( LINE_SEPARATOR, ch );
#if defined( unix )
	charprintf( ch, "User CPU time: %6d.%1.1d\n\r", last_checkpoint / 10,
		    last_checkpoint % 10 );
#endif
	tot = data_trans + data_trans_inc;
	if( tot > ( 1 << 21 ) )	/* 2Mb */
	{
	    tot /= 1 << 20;
	    totu = 'M';
	}
	else
	{
	    tot /= 1 << 10;
	    totu = 'K';
	}
	pm = 8 * ( data_trans + data_trans_inc ) / ( current_time - boot_time );
	if( pm > ( 1 << 12 ) )  /* 4Kb */
	{
	    pm /= 1 << 10;
	    pmu = "K";
	}
	else
	    pmu = "";

	charprintf( ch, "Data transmitted [total/per minute]: "
		    "%.2f%cb / %.2f%sbps\n\r", tot, totu, pm, pmu );

	tot = data_recv;
	if( tot > ( 1 << 21 ) )	/* 2Mb */
	{
	    tot /= 1 << 20;
	    totu = 'M';
	}
	else
	{
	    tot /= 1 << 10;
	    totu = 'K';
	}
	pm = 8 * data_recv / ( current_time - boot_time );
	if( pm > ( 1 << 12 ) )  /* 4Kb */
	{
	    pm /= 1 << 10;
	    pmu = "K";
	}
	else
	    pmu = "";
	charprintf( ch, "Data received [total/per minute]:    "
		    "%.2f%cb / %.2f%sbps\n\r",
		    tot, totu, pm, pmu );
    }

    return;
}


void do_mobdeath( CHAR_DATA *ch, const char *argument )
{
    struct {
	int number;
	MOB_INDEX_DATA *pMob;
    } deaths[10];
    int iHash, i, j;
    MOB_INDEX_DATA *pMob;

    for( i = 0; i < 10; i++ )
	deaths[i].number = -1;
    for( iHash = 0; iHash < MAX_KEY_HASH; iHash++ )
    {
	for( pMob = mob_index_hash[iHash];
	     pMob; pMob = pMob->next )
	{
	    for( i = 0; i < 10; i++ )
	    {
		if( pMob->killed > deaths[i].number )
		    break;
	    }
	    for( j = 9; j > i; j-- )
	    {
		deaths[j].number = deaths[j-1].number;
		deaths[j].pMob = deaths[j-1].pMob;
	    }
	    if( i < 10 )
	    {
		deaths[i].pMob = pMob;
		deaths[i].number = pMob->killed;
	    }
	}
    }
    send_to_char( "&gMost frequently killed mobiles since startup:\n\r", ch );
    charprintf( ch, "Total number killed: %4d.&n\n\r", SysInfo->mob_deaths );
    for( i = 0; i < 5; i++ )
    {
	char tmp[MAX_INPUT_LENGTH];

	if( deaths[i].number <= 0 )
	    break;
	charprintf(
	    ch, "&y%s &b[&r%3d&b]",
	    capitalize ( colour_strpad( tmp, deaths[i].pMob->short_descr, 30 ) ),
	    deaths[i].number );
	if( deaths[i+5].number > 0 )
	    charprintf(
		ch, " | &y%s &b[&r%3d&b]&n\n\r",
		capitalize( colour_strpad( tmp, deaths[i+5].pMob->short_descr, 30 ) ),
		deaths[i+5].number );
	else
	    send_to_char( "&n\n\r", 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 UMAX( 1, number );
}


/*
 * Stick a certain amount of fuzz on a number.
 */
int percent_fuzzy( int number, int prcnt )
{
    int amnt = prcnt * number / 100;
    return number_range( number - amnt, number + amnt );
}


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

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

    for( power = 2; power < to; power <<= 1 )
	;

    while( ( number = number_mm( ) & ( power - 1 ) ) >= to )
	;

    return from + number;
}


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

    while( ( percent = number_mm( ) & ( 128 - 1 ) ) > 99 )
	;

    return 1 + percent;
}


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

    while( ( door = number_mm( ) & ( 8 - 1 ) ) > 5 )
	;

    return door;
}


int number_bits( int width )
{
    return number_mm( ) & ( ( 1 << width ) - 1 );
}


/*
 * I've gotten too many bad reports on OS-supplied random number generators.
 * This is the Mitchell-Moore algorithm from Knuth Volume II.
 * Best to leave the constants alone unless you've read Knuth.
 * -- Furey
 */
static int rgiState[2 + 55];

void init_mm( )
{
    int *piState;
    int iState;

    log_string( "Init_mm: initializing random number generator." );

    piState = &rgiState[2];

    piState[-2] = 55 - 55;
    piState[-1] = 55 - 24;

    piState[0] = ( ( int )current_time ) & ( ( 1 << 30 ) - 1 );
    piState[1] = 1;
    for( iState = 2; iState < 55; iState++ )
    {
	piState[iState] = ( piState[iState - 1] + piState[iState - 2] )
	    & ( ( 1 << 30 ) - 1 );
    }
    return;
}


int number_mm( void )
{
    int *piState;
    int iState1;
    int iState2;
    int iRand;

    piState = &rgiState[2];
    iState1 = piState[-2];
    iState2 = piState[-1];
    iRand = ( piState[iState1] + piState[iState2] )
	& ( ( 1 << 30 ) - 1 );
    piState[iState1] = iRand;
    if( ++iState1 == 55 )
	iState1 = 0;
    if( ++iState2 == 55 )
	iState2 = 0;
    piState[-2] = iState1;
    piState[-1] = iState2;
    return iRand >> 6;
}


/*
 * 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;
}


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

    if( !bstr )
    {
	bug( "Str_cmp: null bstr." );
	return TRUE;
    }

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

    return FALSE;
}


/*
 * Find a substring, used for case-insensitiveness.
 */
char *str_str( const char *haystack, const char *needle )
{
    int i, j = strlen( haystack ) - strlen( needle );

    if( j < 0 )
	return NULL;
    for( i = 0; i <= j && str_prefix( needle, haystack++ ); i++ )
	;
    return ( i > j ) ? NULL : (char *)( haystack - 1 );
}



/*
 * Compare strings, case insensitive, for prefix matching.
 * Return TRUE if astr not a prefix of bstr
 *   (compatibility with historical functions).
 */
bool str_prefix( const char *astr, const char *bstr )
{
    if( !astr )
    {
	bug( "Str_prefix: null astr." );
	return TRUE;
    }

    if( !bstr )
    {
	bug( "Str_prefix: null bstr." );
	return TRUE;
    }

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

    return FALSE;
}


/*
 * Compare strings, case insensitive, for match anywhere.
 * Returns TRUE is astr not part of bstr.
 *   (compatibility with historical functions).
 */
bool str_infix( const char *astr, const char *bstr )
{
    char c0;
    int sstr1;
    int sstr2;
    int ichar;

    if( ( c0 = LOWER( astr[0] ) ) == '\0' )
	return FALSE;

    sstr1 = strlen( astr );
    sstr2 = strlen( bstr );

    for( ichar = 0; ichar <= sstr2 - sstr1; ichar++ )
    {
	if( c0 == LOWER( bstr[ichar] ) && !str_prefix( astr, bstr + ichar ) )
	    return FALSE;
    }

    return TRUE;
}


/*
 * Compare strings, case insensitive, for suffix matching.
 * Return TRUE if astr not a suffix of bstr
 *   (compatibility with historical functions).
 */
bool str_suffix( const char *astr, const char *bstr )
{
    int sstr1;
    int sstr2;

    sstr1 = strlen( astr );
    sstr2 = strlen( bstr );
    if( sstr1 <= sstr2 && !str_cmp( astr, bstr + sstr2 - sstr1 ) )
	return FALSE;
    else
	return TRUE;
}


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

    strcap[0] = str[0];
    for( i = 1; str[i] != '\0'; i++ )
	if( str[i - 1] != '&' )
	    strcap[i] = LOWER( str[i] );
	else
	    strcap[i] = str[i];
    strcap[i] = '\0';
    i = 0;
    while( strcap[i] == '&' )	/* colour codes at the start are ignored */
	i += 2;
    strcap[i] = UPPER( strcap[i] );
    return strcap;
}


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

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

    if( !( fp = open_file( file, "a", FALSE ) ) )
    {
	perror( file );
	send_to_char( "Could not open the file!\n\r", ch );
    }
    else
    {
	fprintf( fp, "[%5d] %s: %s\n",
		ch->in_room ? ch->in_room->vnum : 0, ch->name, str );
	close_file( fp );
    }
    return;
}


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

    va_start( args, fmt );
    vsprintf( str, fmt, args );
    va_end( args );

    if( fpArea )
    {
	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( fBootDb && ( fp = fopen( "SHUTDOWN.TXT", "a" ) ) )
	{
	    fprintf( fp, "[*****] %s\n", buf );
	    fclose( fp );
	}
    }

    strcpy( buf, "[*****] BUG: " );
    strcat( buf, str );
    log_string( buf );
    wiznet( NULL, WIZ_DEBUG, L_MAS, buf );

    /* Don't close the file handle if it's already closed.
     * It should be closed if we are loading from a file and the
     * system isn't booting.
     * This fixes pfile corruption caused when bug() is called while
     * loading a pfile.		--Symposium
     */
    if( ( fp = open_file( BUG_FILE, "a", FALSE ) ) )
    {
	fprintf( fp, "%s\n", buf );
	close_file( fp );
    }

    return;
}


/*
 * Writes a string to the log.
 */
void log_string( const char *fmt,... )
{
    char buf[MAX_STRING_LENGTH];
    va_list args;

    va_start( args, fmt );
    vsprintf( buf, fmt, args );
    va_end( args );

    fprintf( stderr, "%s :: %s\n", myctime( &current_time ), buf );
    wiznet( NULL, WIZ_LOG, L_OVL, buf );
    return;
}