dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// dynamics.cpp - code for dynamic loading of tables etc
/***************************************************************************
 * The Dawn of Time v1.69r (c)1997-2004 Michael Garratt                    *
 * >> A number of people have contributed to the Dawn codebase, with the   *
 *    majority of code written by Michael Garratt - www.dawnoftime.org     *
 * >> To use this source code, you must fully comply with the dawn license *
 *    in licenses.txt... In particular, you may not remove this copyright  *
 *    notice.                                                              *
 **************************************************************************/
#include "include.h"
#include "dynamics.h"
#include "magic.h"

/**************************************************************************/
// Prototypes 
extern dynlookup_type dynlookup_table[];
extern dynspell_type spellpairs_table[];
char *flag_string( const struct flag_type *flag_table, int bits );
char *tochar_spellfunction(SPELL_FUN *psp);
sh_int spellfunctionindex_fromchar(const char *name);
int class_lookup (const char *name);
bool flush_char_outbuffer(char_data *ch);
void autonote(int type, char *sender, char *subject, char *to, char *text, bool reformat);
void do_write_socials( char_data *ch, char * );

/**************************************************************************/
// Returns the index of the class it just created, -1 if couldn't create it
// if a class with the same name already exists, it returns that class
// all classes created using this function have creation selectable set to false
// - Kal March 00
int create_class(char * class_name)
{
	int result;
	result=class_exact_lookup(class_name);
	if(result!=-1){
		bugf("create_class(): class '%s' already exists!", class_name);
		return result;
	}

	// find the end of the table
	int i;
	for(i=0;!IS_NULLSTR(class_table[i].name); i++){
		// do nothing but count thru table
	}

	// check if we are going to overflow the table
	if(i+1>=MAX_CLASS){
		bugf("create_class(): attempting to add class '%s', but there are already %d classes (as defined by MAX_CLASS), this would exceed the maximum - canceling",
			class_name, MAX_CLASS);
		return -1;
	}

	class_table[i+1].name=NULL; // mark the new end of the table
	class_table[i].name=str_dup(class_name);
	class_table[i].short_name=str_dup("???");

	char temp[MIL];
	sprintf(temp,"%s default", class_name);
	class_table[i].default_group=str_dup(temp);

	class_table[i].spinfo_letter=str_dup("-");
	class_table[i].creation_selectable=false;
	class_table[i].hp_min=1;
	class_table[i].hp_max=5;
	class_table[i].skill_adept=75;
	class_table[i].remort_number=0;

	// add our entry in the classnames_table
	classnames_flags[i].name=str_dup(class_table[i].name);
	classnames_flags[i].bit=1<<i;
	classnames_flags[i].settable=true;
	classnames_flags[i+1].name=NULL; // mark end of table

	// clear out the class specific entries in 
	// the other tables for the new class
	{
		int j;
		// skill_table
		for(j=0;!IS_NULLSTR(skill_table[j].name);j++){						
			skill_table[j].skill_level[i]=0;
			skill_table[j].rating[i]=0;
			skill_table[j].low_percent_level[i]=0;
			skill_table[j].maxprac_percent[i]=0;
			skill_table[j].learn_scale_percent[i]=0;
			skill_table[j].alignrestrict_flags[i]=0;
		}
		if(race_table){
			for(j=0; race_table[j]; j++){						
				race_table[j]->class_exp[i]=1000;
			}
		}
	}


	logf("create_class(): dynamically added class '%s'.", class_name);

	return i;
}
/**************************************************************************/
// Cant be bothered writing word information for the class table :)
GIO_START(class_type)
	GIO_STRH(short_name,	"Who_name         ")
	GIO_SHINT_ARRAY(attr_prime, 2)
	GIO_SHINTH(skill_adept, "skill_adept      ")
	GIO_SHINTH(thac0_00,	"thac0_00         ")
	GIO_SHINTH(thac0_32,	"thac0_32         ")
	GIO_SHINTH(hp_min,		"hp_min           ")
	GIO_SHINTH(hp_max,		"hp_max           ")
	GIO_BOOLH( fMana,		"fMana            ")
	GIO_STRH(spinfo_letter,	"spinfo_letter    ")
	GIO_STRH(default_group,	"default_group    ")
	GIO_BOOLH(creation_selectable,	"creation_selectable ")
	GIO_INTH(class_cast_type,  "class_cast_type  ") // dont need this once file format has been converted
	GIO_WFLAGH(class_cast_type,"cast_type        ", castnames_types)
	GIO_SHINTH(core_clss,	 "core_clss        ")
	GIO_INTH(object_restriction_index, "object_restriction_index ")
	GIO_INTH(objrestrict,	"objrestrict      ")
	GIO_SHINTH(class_id,	"class_id         ")
	GIO_WFLAGH(flags,		"Flags            ", classflag_flags)
	GIO_SHINTH(remort_number,"remort_number    ")
	GIO_INT(recall)	
	GIO_INT(morgue)	
	GIO_READ_TO_EOL("base_group") // remove from system
	GIO_STR_ARRAYH(pose_self,	"pose_self        ", MAX_LEVEL)
	GIO_STR_ARRAYH(pose_others,	"pose_others      ", MAX_LEVEL)
GIO_FINISH_NOCLEAR	
/**************************************************************************/
void skilltype_write_generic_short_class_entry(gio_type *gio_table, int tableIndex,
													  void *data, FILE *fp)
{
	short *array=(short*)((char *)data + gio_table[tableIndex].index);	
	int i;

	// make sure we have something to write before starting into it
	for(i=0; !IS_NULLSTR(class_table[i].name); i++)
	{
		if(array[i]!=0){
			break;
		}
	}
	if(IS_NULLSTR(class_table[i].name)){
		return; // nothing to write
	}

	// put out the header
	fprintf(fp, "%s\n",gio_table[tableIndex].heading);
	// the data for the non 0 classes
	for(i=0; !IS_NULLSTR(class_table[i].name); i++)
	{	
		if(array[i]!=0){
			if(has_space(class_table[i].name)){
				fprintf(fp, "'%-16s' %d\n",		
					class_table[i].name,
					array[i]);
			}else{
				fprintf(fp, "%-16s %d\n",		
					class_table[i].name,
					array[i]);
			}
		}
	}
	// the footer
	fprintf(fp, "/\n");
}
/**************************************************************************/
void skilltype_read_generic_short_class_entry(gio_type *gio_table, int tableIndex,
													  void *data, FILE *fp)
{
	short *array=(short*)((char *)data + gio_table[tableIndex].index);	
	char *className;
	short value;
	int classIndex;
	
	// table has already been nulled by the gio system

	while (true){
		// get the class name and modifier
		className=fread_word(fp);
		if (className[0]=='/' && className[1]=='\0'){
			break;
		}

		// read in the experience modifier		
		value=fread_number(fp);

		classIndex =class_lookup(className);

		if (classIndex<0){
			if (className[0]!='*'){
				bugf( "skilltype_read_generic_short_class_entry: class '%s' not found... "
					"ignoring '%s' field for that class!", className, gio_table[tableIndex].heading);
			}
		}else{
			array[classIndex]=value;
		}
	}
}
/**************************************************************************/
GIO_CUSTOM_FUNCTION_PROTOTYPE( skilltype_write_spellfunction );
GIO_CUSTOM_FUNCTION_PROTOTYPE( skilltype_read_spellfunction );

GIO_CUSTOM_FUNCTION_PROTOTYPE(racetype_write_generic_races_set_for_n_array);
GIO_CUSTOM_FUNCTION_PROTOTYPE(racetype_read_generic_races_set_for_n_array);
/**************************************************************************/
// create skill_type GIO lookup table 
GIO_START(skill_type)
// *** char * name				- is written by the table handling code
//###TODO skill_level[MAX_CLASS]	
//###TODO rating[MAX_CLASS]
#define	STWGSCE skilltype_write_generic_short_class_entry // work smarter not harder :)
#define	STRGSCE skilltype_read_generic_short_class_entry 
GIO_CUSTOM_WRITEH(skill_level,			"===skill_level",			STWGSCE)
GIO_CUSTOM_WRITEH(rating,				"===rating",				STWGSCE)
GIO_CUSTOM_WRITEH(low_percent_level,	"===low_percent_level",		STWGSCE)
GIO_CUSTOM_WRITEH(maxprac_percent,		"===maxprac_percent",		STWGSCE)
GIO_CUSTOM_WRITEH(learn_scale_percent,	"===learn_scale_percent",	STWGSCE)
GIO_CUSTOM_WRITEH(alignrestrict_flags,	"===alignrestrict_flags",	STWGSCE) // storing as a number (doesn't matter really)
GIO_CUSTOM_READH(skill_level,			"===skill_level",			STRGSCE)
GIO_CUSTOM_READH(rating,				"===rating",				STRGSCE)
GIO_CUSTOM_READH(low_percent_level,		"===low_percent_level",		STRGSCE)
GIO_CUSTOM_READH(maxprac_percent,		"===maxprac_percent",		STRGSCE)
GIO_CUSTOM_READH(learn_scale_percent,	"===learn_scale_percent",	STRGSCE)
GIO_CUSTOM_READH(alignrestrict_flags,	"===alignrestrict_flags",	STRGSCE) 

GIO_CUSTOM_WRITEH(spell_fun,	"SpellFunction   ", skilltype_write_spellfunction)
GIO_CUSTOM_READH(spell_fun,		"SpellFunction   ", skilltype_read_spellfunction)
GIO_SHWFLAGH(minimum_position,	"Position        ", position_types)
GIO_SHINTH(min_mana,			"Mana            ")
GIO_SHINTH(beats,				"Beats           ")
GIO_STRH(noun_damage,			"Noun_damage     ")
GIO_STRH(msg_off,				"Wearoff_msg     ")
GIO_STRH(msg_obj,				"Obj_wearoff_msg ")
GIO_CUSTOM_WRITEH(race_restrict_n, "RaceRestriction ", racetype_write_generic_races_set_for_n_array)
GIO_CUSTOM_READH(race_restrict_n,  "RaceRestriction ", racetype_read_generic_races_set_for_n_array)
GIO_WFLAGH(flags,				"Flags           ", skflags_flags)

GIO_SHWFLAGH(category,			"Category        ", category_types)
GIO_SHWFLAGH(type,				"Type            ", sktype_types)
GIO_SHWFLAGH(damtype,			"Damtype         ", damtype_types)
GIO_WFLAGH(realms,				"Realms          ", realm_flags)
GIO_WFLAGH(spheres,				"Spheres         ", sphere_flags)
GIO_WFLAGH(elements,			"Elements        ", element_flags)
GIO_WFLAGH(spellgroup,			"Spell_Group     ", spell_group_flags)
GIO_WFLAGH(sect_restrict,		"Sector_Restrict ", sectorbit_flags)
GIO_WFLAGH(sect_enhance,		"Sector_Enhance  ", sectorbit_flags)
GIO_WFLAGH(sect_dampen,			"Sector_Dampen   ", sectorbit_flags)
GIO_SHINTH(component_based,		"Component       ")
GIO_STRH(msp_sound,				"MSPSound        ")
GIO_FINISH_NOCLEAR

/**************************************************************************/
// Output the class_table to disk 
void do_write_classes(char_data *ch, char *)
{
	FILE *fp;
	int i;

	logf("Writing class table to " CLASSES_LIST ".write ...");
	fclose( fpReserve );

    if ( ( fp = fopen( CLASSES_LIST ".write", "w" ) ) == NULL )
    {
		bugf("do_write_classes(): fopen '%s' for write - error %d (%s)",
			CLASSES_LIST ".write", errno, strerror( errno));
		ch->printf("An error occured opening " CLASSES_LIST ".write for writing!\r\n");
		autonote(NOTE_SNOTE, "do_write_classes()", "Problems saving class table", "code cc: imm", 
			"An error occured opening " CLASSES_LIST ".write for writing!\r\n", true);
    }else{
		// LOOP thru everything in the table, writing it
		for ( i = 0; !IS_NULLSTR(class_table[i].name); i++ )
		{
			fprintf(fp,"######NAME        %s~\n", class_table[i].name);
			GIO_SAVE_RECORD(class_type, &class_table[i], fp, NULL);
			fprintf(fp,"\n");
		}

		int bytes_written=fprintf(fp, "EOF~\n");
		fclose( fp );
		if(   bytes_written != str_len("EOF~\n") ){
			bugf("do_write_classes(): fprintf to '%s' - error %d (%s)",
				CLASSES_LIST ".write", errno, strerror( errno));
			bugf("Incomplete write of " CLASSES_LIST ".write, write aborted - check diskspace!");
			ch->printf("Incomplete write of " CLASSES_LIST ".write, write aborted - check diskspace!\r\n");
			autonote(NOTE_SNOTE, "do_write_classes()", "Problems saving class table", "code cc: imm", 
				"Incomplete write of " CLASSES_LIST ".write, write aborted - check diskspace!\r\n", true);
		}else{		
			ch->printf("Finished writing class table to "CLASSES_LIST ".write\r\n");

			ch->printf("Renaming old " CLASSES_LIST " to " CLASSES_LIST ".bak\r\n");
			unlink(CLASSES_LIST".bak");
			rename(CLASSES_LIST, CLASSES_LIST".bak");

			ch->printf("Renaming new " CLASSES_LIST ".write to " CLASSES_LIST "\r\n");
			unlink(CLASSES_LIST);
			rename(CLASSES_LIST".write", CLASSES_LIST);
		}
    }
	fpReserve = fopen( NULL_FILE, "r" );
	logf("Finished writing class table.");
}
/**************************************************************************/
// read in the class_table from disk
void do_read_classes(char_data *ch, char *)
{
	FILE *fp;
	int count=0;
	int i;

	// because system doesn't support reassigning class numbers, only allow 
	// this to run at bootup and when one imm is on
	if(runlevel!=RUNLEVEL_BOOTING){
//		if(player_list && !player_list->next_player){ // only one in the game
//			ch->println("Allowing class read in due to a single person being logged in.");
//		}else{
			ch->println("do_read_classes() currently doesn't support being called a game time... boot only!");
			ch->println("Doing so in its current state might stuff up everyones class,");
			ch->println("and object classrestrictions in area files... DONT DO IT!");
			return;
//		}
	}

	logf("Reading in class table from " CLASSES_LIST "...");
	fclose( fpReserve );

    if ( ( fp = fopen( CLASSES_LIST, "r" ) ) == NULL )
    {	// couldn't find the file to read in
		bugf("do_read_classes(): fopen '%s' for read - error %d (%s)",
			CLASSES_LIST, errno, strerror( errno));
		bugf("An error occured trying to open " CLASSES_LIST " for reading!");
		ch->printf("An error occured trying to open " CLASSES_LIST " for reading!\r\n");
		if(file_exists(CLASSES_LIST )){
			bugf("File " CLASSES_LIST "found, but it wasnt openned for some reason?!?");
			write_shutdown_file(NULL);
			do_abort();
		}else{
			if(runlevel==RUNLEVEL_BOOTING){
				log_notef("File " CLASSES_LIST " not found... the mud can't boot without this file!`1"
				"`1"
				"This file contains the basic information about all classes, if the mud "
				"booted without this file then there would be no classes, and therefore "
				"none would be able to login.`1"
				"`1"
				"If you are in the process of setting up this mud, it is most likely that you "
				"either haven't downloaded the dawn support files (which include the required system "
				"files, helps and optional area set), or haven't correctly installed the support files.  "
				"Download and install them now, then if you are still having "
				"problems which you can't resolve, ask for assistance on the dawn forums at "
				"http://forums.dawnoftime.org/`1"
				"`1"
				"If this error has just started to occur on a mud which was previously "
				"working, the information of the previous class table might be "
				"contained in " CLASSES_LIST ".bak or "
				"secondly " CLASSES_LIST ".write... (the bak file is probably the better "
				"of the two if you have both) if you have either of these files copy them to "
				CLASSES_LIST" then restart the mud.`1");
				write_shutdown_file(NULL);
				exit_error( 1 , "do_read_classes", "classes list not found");
			}else{
				ch->printf("Couldn't find " CLASSES_LIST " to read in! - read the inote for more detail!\r\n");
				autonote(NOTE_SNOTE, "do_read_classes()", "Problems finding class table file", "code cc: imm", 
					"Couldn't find " CLASSES_LIST " to read into the class table!`1"
					"The mud will not be able to automatically start up if this file "
					"is not there next time the mud starts... the recommended fix to "
					"this condition (`RAssuming there is NO corruption to the class "
					"information in the currently running mud`x) is to make sure there "
					"is enough free diskspace to store this file then get the mud to "
					"recreate this file using the write_classes command.`1`1"
					"It is important that everyone does not do this, it should be done "
					"only once, and it is best if it is not done if the mud has "
					"already rebooted since the date of this note... if you dont "
					"understand what this note is explaining, it is best you leave it.",					
					true);
			}
		}
    }else{ // CLASSES_LIST was found, read in
		bool morefile=true;
		char *readword;
		
		if(runlevel==RUNLEVEL_BOOTING){
			// *** Mark the existing table as empty - since we are loading everything in
			// - This could cause class corruption if done when people are online...
			//   that is why it can be only done if one is online or during booting.
			class_table[0].name=NULL; 
		}

		for ( i=0; i<MAX_CLASS; i++)
		{ 
			class_table[i].already_loaded=false;
		}

		// loop thru till we get the end of the table
		while (morefile && !feof(fp)) {
			int load_index;
			readword= fread_word(fp);

			if (!str_cmp(readword, "EOF") || !str_cmp(readword, "EOF~")){
				morefile=false;
			}else{
				if(!str_cmp(readword, "######NAME")){					

					// get the name of the class
					readword=fread_string(fp);

					// check if the class hasn't already been loaded - no duplicates
					load_index=class_exact_lookup(readword);
					
					if(load_index==-1){ // if not found, allocate a space for a new class
						// find the end of the class table
						for ( load_index=0; !IS_NULLSTR(class_table[load_index].name); load_index++)
						{ // do nothing till we find the end
						}
						//load_index now points to the end of the list
						if(load_index>=MAX_CLASS){
							bugf("Too many classes (%d) trying to load class '%s' from %s, increase MAX_CLASS - aborting",
									i, readword, CLASSES_LIST);
							ch->printlnf("Too many classes (%d) trying to load class '%s' from %s, increase MAX_CLASS - aborting",
									i, readword, CLASSES_LIST);
							flush_char_outbuffer(ch);
							write_shutdown_file(NULL);
							exit_error( 1 , "do_read_classes", "too many classes");
						}
						class_table[load_index+1].name=NULL; // mark the new end of the table
						class_table[load_index].name=str_dup(readword);

					}
					// prevent duplicate classes in the file
					if(class_table[load_index].already_loaded){
						bugf("Duplicate class '%s' in %s, aborting",
								readword, CLASSES_LIST);
						ch->printlnf("Duplicate class '%s' in %s, aborting",
								readword, CLASSES_LIST);
						flush_char_outbuffer(ch);
						write_shutdown_file(NULL);
						exit_error( 1 , "do_read_classes", "duplicate class");
					}

					// initialise the pose data
					for(i=0; i<MAX_LEVEL; i++){
						class_table[load_index].pose_self[i]=str_dup("");
						class_table[load_index].pose_others[i]=str_dup("");
					}

					// read in the data
					GIO_LOAD_RECORD(class_type, &class_table[load_index], fp);
					class_table[load_index].already_loaded=true;
				
					count++;
				}else{// unexpected file format
					bugf("Unexpected fileformat in '%s' - found '%s' "
						"expecting '######NAME'", CLASSES_LIST , readword);
					ch->printf( "Unexpected fileformat in '%s' - found '%s' "
						"expecting '######NAME'\r\n", CLASSES_LIST , readword);
					flush_char_outbuffer(ch);
					write_shutdown_file(NULL);
					do_abort();
					return;
				}
			}
		}
		fclose( fp );

		ch->printlnf("Finished reading class_table from " CLASSES_LIST  ". (read in %d)", count);
		logf("Finished reading class_table from " CLASSES_LIST ". (read in %d)", count);

		for ( i=0; !IS_NULLSTR(class_table[i].name); i++){
			if(!class_table[i].already_loaded){
				ch->printlnf("Class '`R%s`x' wasn't found in file to load in!, after next reboot it wont be there.",
					class_table[i].name);
				bugf("Class '%s' wasn't found in file to load in.",
					class_table[i].name);
			}
		}

    }
	fpReserve = fopen( NULL_FILE, "r" );
	
	// create a new the classnames_types
	{
		int i;
		logf("Creating a new class lookup table in classnames_types[]");

		// first attempt exact match 
		for (i=0; !IS_NULLSTR(class_table[i].name); i++)
		{	
			classnames_flags[i].name=str_dup(class_table[i].name);
			classnames_flags[i].bit=1<<i;
			classnames_flags[i].settable=true;
		}
		classnames_flags[i].name=NULL; // mark end of table
	}

	return;
}
/**************************************************************************/
char *tochar_spellfunction(SPELL_FUN *psp)
{
	sh_int sn;

    for ( sn = 0; !IS_NULLSTR(spellpairs_table[sn].name); sn++ )
    {
		if(spellpairs_table[sn].psp==psp){
			return spellpairs_table[sn].name;
		}
	}
	bugf("tochar_spellfunction: spell function not found, returning "
		"\"spell_null\" sn=%d",sn);
	return "spell_null";
}
/**************************************************************************/
sh_int spellfunctionindex_fromchar(const char *name)
{
	sh_int sn;

    for ( sn = 0; !IS_NULLSTR(spellpairs_table[sn].name); sn++ )
    {
		if(!str_cmp(spellpairs_table[sn].name,name)){
			return sn;
		}
	}
	return -1;
}
/**************************************************************************/
// write the SpellFunction section
void skilltype_write_spellfunction(gio_type *gio_table, int tableIndex,
													  void *data, FILE *fp)
{
	skill_type * psk;

	psk= (skill_type*) data;

	if(	psk->spell_fun!= NULL 
		&& psk->spell_fun!=spell_null)
	{
		fprintf(fp, "%s %s~\n",		
			gio_table[tableIndex].heading,
			tochar_spellfunction(psk->spell_fun));
	}
}
/**************************************************************************/
// read the spell function in
void skilltype_read_spellfunction(gio_type *, int, void *data, FILE *fp)
{
	skill_type * psk;
	char *pstr;

	psk= (skill_type*) data;

	pstr=fread_string(fp);
	psk->spell_function_index=spellfunctionindex_fromchar(pstr);
	if(psk->spell_function_index>-1){
		psk->target=spellpairs_table[psk->spell_function_index].target;
		psk->spell_fun= spellpairs_table[psk->spell_function_index].psp;
	}else{
		psk->spell_fun=spell_null;
		psk->target=TAR_IGNORE;
	}
	
	free_string(pstr);
}
/**************************************************************************/
// writes to the fp a table for cross referencing spell names and
// their spell function, this assumes that the function is named based
// of the actual spell name in the table
void write_dynlookup_table(FILE *fp)
{
    sh_int sn;
	char buf[MIL];
	char type[MIL];
	fprintf(fp,"dynlookup_type dynlookup_table[]=" BRACKET_OPEN "\n");

    for ( sn = 0; sn < MAX_SKILL; sn++ )
    {
		if ( skill_table[sn].name == NULL )
		    break;
		sprintf(buf,"\"%s\",",skill_table[sn].name);

		// generate the type
		if(IS_SPELL(sn)){
			sprintf(type,"DYNTYPE_SPELL");
		}else if(IS_REALM(sn)){
			sprintf(type,"DYNTYPE_REALM");
		}else if(IS_SKILL(sn)){
			sprintf(type,"DYNTYPE_SKILL");
		}else{
			sprintf(type,"DYNTYPE_UNDEFINED");
		}

		fprintf(fp, "\t` %-25s %s, &gsn_%s },\n", 
			buf, type, underscore_word(skill_table[sn].name)
			);		
    }
	fprintf(fp,"\t` NULL, DYNTYPE_UNDEFINED, NULL}\n");
	fprintf(fp,"};\n\n");
}
/**************************************************************************/
void write_spellpairs_table(FILE *fp)
{
    sh_int sn;
	fprintf(fp,"dynspell_type spellpairs_table[]="BRACKET_OPEN"\n");
	fprintf(fp, "\t` \"spell_null\", spell_null },\n");

    for ( sn = 0; !IS_NULLSTR(skill_table[sn].name); sn++ )
    {
		if(skill_table[sn].spell_fun== NULL 
			|| skill_table[sn].spell_fun==spell_null)
			continue;

		fprintf(fp, "\t` \"spell_%s\", spell_%s, false},\n", 
			underscore_word(skill_table[sn].name),
			underscore_word(skill_table[sn].name));
    }
	fprintf(fp,"\t` \"\", NULL}\n");
	fprintf(fp,"};\n");
}
/**************************************************************************/
// this function is no longer used, but included because someone one day
// may find it useful - Kal, Nov 03
void do_write_dynamic_include(char_data *ch, char *)
{
	FILE *fp;
	fclose( fpReserve );
#ifndef WIN32
    if ( ( fp = fopen( DYNAMIC_INCLUDE, "w" ) ) == NULL )
    {
		bugf("do_write_dynamic_include(): fopen '%s' for write - error %d (%s)",
			DYNAMIC_INCLUDE, errno, strerror( errno));
		ch->printf("An error occured! writing to " DYNAMIC_INCLUDE "\r\n");
    }
#else
	if ( ( fp = fopen( SRC_DIR DYNAMIC_INCLUDE, "w" ) ) == NULL )
    {
		bugf("do_write_dynamic_include(): fopen '%s' for write - error %d (%s)",
			SRC_DIR DYNAMIC_INCLUDE, errno, strerror( errno));
		ch->printf("An error occured! writing to " SRC_DIR DYNAMIC_INCLUDE "\r\n");
    }
#endif
    else
    {
		fprintf(fp,
		"// Dyntable.cpp - The Dawn of time dynamically written cpp file.\n"
		"// used for dynamic table lookups.\n"
		"#include \"include.h\"\n"
		"#include \"dynamics.h\"\n"
		"#include \"magic.h\"\n\n");
		write_dynlookup_table(fp);
		write_spellpairs_table(fp);
	
		fclose( fp );
		ch->println("Done.");
    }
	fpReserve = fopen( NULL_FILE, "r" );

	return;
}
/**************************************************************************/
// initialises the skill table with the info in dynlookup_table stored in
// dyntable.cpp
void init_skilltable(void)
{
	static skill_type skill_zero;
	sh_int sn;
	int count=0;
	skill_zero.spell_fun=spell_null;
	
	logf("init_skilltable: initialising table...");
	// add all our entries
	for ( sn = 0; !IS_NULLSTR(dynlookup_table[sn].name); sn++ )
    {
		skill_table[sn]=skill_zero;
		skill_table[sn].name=str_dup(dynlookup_table[sn].name);
		skill_table[sn].pgsn=dynlookup_table[sn].pgsn;
		if ( skill_table[sn].pgsn != NULL )
			*skill_table[sn].pgsn = sn;
		count++;
    }

	// mark the end of the table
	skill_table[sn].name=&str_empty[0];
	logf("init_skilltable: %d entr%s added.", count, count==1?"y":"ies");
}
/**************************************************************************/
// a basic linked list function which sorts insertion by name alphabetically
void addlist(name_linkedlist_type **list,char *name, int tag, bool duplicates, bool reversed)
{
	//handle the easy cases first
	name_linkedlist_type *plist,*newnode, *prev;
	int val=0;

	if(IS_NULLSTR(name))
		return;

	newnode=new name_linkedlist_type;
	newnode->name=str_dup(name);
	newnode->tag=tag;
	newnode->count=1;
//	logf("added %s", name);
	plist=*list;
	prev=NULL;
	if(plist){// insert sorted into the list	

		// loop thru until
		while(plist)
		{
			val=strcmp(plist->name,name);
			if(reversed){
				if(val<0) // later
					break;
			}else{
				if(val>=0) // earlier
					break;
			}
			prev=plist;
			plist=plist->next;
		}

		// add it into the list if not a duplicate
		if(duplicates || val)
		{
			if(prev){
				newnode->next=prev->next;
				prev->next=newnode;
			}else{
				newnode->next=*list;
				*list=newnode;
			}
		}else{ 
			plist->count++; // increase the count of number of duplicates
			// deallocate the memory
			free_string(newnode->name);
			delete newnode;
		}
	}else{// new list, just add us at the top
		newnode->next=NULL;
		*list=newnode;
	}

};
/**************************************************************************/
// loads in the skill table using the following steps
// - Reading in the skill file, making linked lists of the 
//   different skills/spells/realms etc while loading, sorting them 
//   alphabetically while loading in.  These linked lists only hold the 
//   name of the entry, if this is successful it continues.
// - Clears the skill_table
// - These names are then copied into the skill table and the first_spell
//   and last_spell short ints are assigned.  Also in the copy it leaves
//   space after the bottom spell, and the first skill allowing the addition
//   of a few spells without rebooting. (after the next reboot there 
//   will be more space)
// - The gsn pointers are then copied, and their values are set. 
// - The skill text file is then reread, this time copying in the information
//   from the text file into the appropriate places
void do_loadskilltable(char_data *ch, char *)
{
	FILE *fp;
	int count, index;
	skill_type skill_prescan;
	int sn;

	// declare and setup our reading in lists.
	name_linkedlist_type *list[10], *plist;
	for(count=0;count<10; count++){
		list[count]=NULL;
	}
	count=0;

	static skill_type skill_zero;
	skill_zero.spell_fun=spell_null;

	// read in the skill text file making sorted linked lists.
//#define SKTYPE_UNDEFINED	0
//#define SKTYPE_SPELL		1
//#define SKTYPE_SKILL		2
//#define SKTYPE_OTHER		3

	logf("Doing prescan of " SKILLS_FILE "... for skills, spells etc");

	// open the file for input
	fclose( fpReserve );
    if ( ( fp = fopen( SKILLS_FILE, "r" ) ) == NULL )
    {
		bugf("do_loadskilltable(): fopen '%s' for read - error %d (%s)",
			SKILLS_FILE, errno, strerror( errno));
		ch->printf("An error occured trying to open " SKILLS_FILE" for reading!\n");
		write_shutdown_file(NULL);
		do_abort();
    }
    
	{
		bool morefile=true;
		char *readword;
		char buf[MIL];

		while (morefile && !feof(fp)) {
			readword= fread_word(fp);

			if (!str_cmp(readword, "EOF") || !str_cmp(readword, "EOF~")){
				morefile=false;
			}else{
				if(!str_cmp(readword, "######NAME")){
					readword=fread_string(fp);
					sprintf(buf,"%s", readword);
					// load the skill entry for the prescan
					GIO_LOAD_RECORD(skill_type, &skill_prescan, fp);

					if(str_cmp(readword, "reserved"))
					{
						count++;
						// add to list, tag of skill_prescan.type, allow duplicates = true
						addlist(&list[skill_prescan.type],readword, 0, true, false);
// different types
//	{"undefined",		SKTYPE_UNDEFINED,	true},
//	{"spell",			SKTYPE_SPELL,		true},
//	{"skill",			SKTYPE_SKILL,		true},
//	{"other",			SKTYPE_OTHER,		true},

					}
				}else{// unexpected file format
					bugf("Unexpected fileformat in '%s' - found '%s' "
						"expecting '######NAME'", SKILLS_FILE, readword);
					write_shutdown_file(NULL);
					do_abort();
					return;
				}
			}
		}
		
		logf("Prescan found %d skill%s.", count, count==1?"":"s");		
		if(count+MAX_RESERVED_SPELL_SPACE>=MAX_SKILL){
			logf("Prescan +%d found more skills than MAX_SKILL, increase MAX_SKILL above %d\n", 
				MAX_RESERVED_SPELL_SPACE, MAX_SKILL);
			logf("The +%d is due to space allocation to allow adding of spells while online.\n",
				MAX_RESERVED_SPELL_SPACE);
			write_shutdown_file(NULL);
			exit_error( 1 , "do_loadskilltable", "prescan found more skills than allocated storage space.");
		}
		
		fclose( fp );

		// manually add all the skills/spells not found in the prescan
		// that values exist in the gsn table
		// - this means that no manual editing is required to add the skill
		for ( sn = 0; !IS_NULLSTR(dynlookup_table[sn].name); sn++ )
		{
			if(dynlookup_table[sn].type==DYNTYPE_SKILL){
				// add it to the skill list without duplicates
				addlist(&list[SKTYPE_SKILL], dynlookup_table[sn].name, SKTYPE_SKILL, false, false); 
			}
			if(dynlookup_table[sn].type==DYNTYPE_SPELL){
				// add it to the spell list without duplicates
				addlist(&list[SKTYPE_SPELL], dynlookup_table[sn].name, SKTYPE_SPELL, false, false); 
			}
		}

		{
			static skill_type skill_zero;
			skill_zero.spell_fun=spell_null;
			index=0;

			for(count=0;count<10; count++){
				if(list[count]==NULL)
					continue;
//				logf("list #%d",count);	
				
				if(count==SKTYPE_SPELL)
				{
					FIRST_SPELL=index;
				}
				for(plist=list[count];plist; plist=plist->next){
//					logf("--->>%d <- %s", index, plist->name);
					// check for duplicate already.
					skill_table[index+1].name=NULL;
					if(skill_exact_lookup(plist->name)!=-1){
						bugf("DUPLICATE ENTRY '%s' - last entry used.", plist->name);
					}else{
						skill_table[index]=skill_zero; // clear entry fully
						skill_table[index].name=plist->name; // no need to str_dup it
															 // because addlist str_dup's

						// set the default type in order to work with loading the gsn
						// table entries, and getting the correct type when they 
						// haven't manually been added to the skills.txt file
						if(plist->tag){
							logf("Detected '%s' not in prescan, but in dyntable - added.",
								plist->name);
							skill_table[index].type=plist->tag; 							

							// bind the spell function if possible
							if(plist->tag==SKTYPE_SPELL){
								char possible_spellname[MIL];
								// get the lowercase with spaces converted to underscores version
								sprintf(possible_spellname,"spell_%s", underscore_word(plist->name));

								skill_table[index].spell_function_index=spellfunctionindex_fromchar(possible_spellname);
								if(skill_table[index].spell_function_index>-1){
									logf("SPELL '%s' - binding spell func %s",
										plist->name, possible_spellname);						

									skill_table[index].target=spellpairs_table[skill_table[index].spell_function_index].target;
									skill_table[index].spell_fun= spellpairs_table[skill_table[index].spell_function_index].psp;
								}else{
									logf("SPELL '%s' couldn't find %s, binding spell_null instead!",
										plist->name, possible_spellname);						
									skill_table[index].spell_fun=spell_null;
									skill_table[index].target=TAR_IGNORE;
								}
							}
						}
						
						index++;
					}
				}
				// add extra space for adding new spells
				if(count==SKTYPE_SPELL)
				{
					int space;
					LAST_SPELL=index;
					for(space=0;space<MAX_RESERVED_SPELL_SPACE; space++){
						skill_table[index].name=str_dup("reserved");
						index++;
					}
				}
			}
			logf("Marked end of skill table at index %d.", index);
			skill_table[index].name=NULL; // mark it end of the table
			TOP_SKILL=index; 
		}
    }
	fpReserve = fopen( NULL_FILE, "r" );

	logf("Finished prescan of skill file, assigning gsn's");

	// copy the gsn pointers and assign them 
	{ 
		sh_int index;
		count=0;

		for ( sn = 0; !IS_NULLSTR(dynlookup_table[sn].name); sn++ )
		{
			index=skill_lookup(dynlookup_table[sn].name);

			if(index>=0){
				skill_table[index].pgsn=dynlookup_table[sn].pgsn;
				if (skill_table[index].pgsn)
				{
					*skill_table[index].pgsn = index;
					count++;
				}
			}else{
				logf("Ignoring dyntable's '%s' gsn since wasn't found in prescan.",
					dynlookup_table[sn].name);
			}
		}
		logf("%d gsn%s assigned, loading in all data from skill file now.",
			count, count==1?"":"s");
	}

	// load in the whole skill table now

	// open the file for input
	fclose( fpReserve );
    if ( ( fp = fopen( SKILLS_FILE, "r" ) ) == NULL )
    {
		bugf("do_loadskilltable(): fopen '%s' for read - error %d (%s)",
			SKILLS_FILE, errno, strerror( errno));
		ch->printf("An error occured trying to open " SKILLS_FILE" for reading!\n");
		write_shutdown_file(NULL);
		do_abort();
    }

    {
		bool morefile=true;
		char *readword;
		char buf[MIL];
		sh_int sn;

		count=0;
		
		while (morefile && !feof(fp)) {
			readword= fread_word(fp);
			
			if (!str_cmp(readword, "EOF") || !str_cmp(readword, "EOF~")){
				morefile=false;
			}else{
				if(!str_cmp(readword, "######NAME")){
					readword=fread_string(fp);
					if(str_cmp(readword, "reserved"))
					{
						sprintf(buf,"%s", readword);
						// lookup the skill and read it in.
						sn=skill_lookup(buf);
						if(sn>=0){
							GIO_LOAD_RECORD(skill_type, &skill_table[sn], fp);
							count++;
							// count the spell functions in use
							if(skill_table[sn].spell_function_index>=0){
								spellpairs_table[skill_table[sn].spell_function_index].count++;
							}

							// this isn't the most efficient way, but I can't be bothered 
							// writing something more efficient
							if(!IS_NULLSTR(race_get_races_set_for_n_array(skill_table[sn].race_restrict_n))){
								SET_BIT(skill_table[sn].flags, SKFLAGS_USE_RACE_RESTRICTIONS);
							}

						}else{
							// if we can't find the entry in the skill table then
							// the skill file has been modified in the 2 second 
							// period or something it corrupting info, therefore abort
							bugf("do_loadskilltable: Something very screwy is going on!\n"
								"a NEW entry ('%s') have appeared in the skill file '%s' since the prescan!n"
								"Aborting!", buf,SKILLS_FILE);
							write_shutdown_file(NULL);
							do_abort();
							return;
						}
					}else{
						// ignore the reserved record
						GIO_LOAD_RECORD(skill_type, &skill_prescan, fp);
					}
				}else{// unexpected file format
					bugf("Unexpected fileformat in '%s' - found '%s' "
						"expecting '######NAME'", SKILLS_FILE, readword);
					write_shutdown_file(NULL);
					exit_error( 1 , "do_loadskilltable", "unexpected file format");
					return;
				}
			}
		}
		logf("Finished reading skill file. (read in %d total)", count);
	}
	fclose( fp );

	fpReserve = fopen( NULL_FILE, "r" );
	return;
}
/**************************************************************************/
// Output the skill_table to disk
void do_write_skills(char_data *ch, char *)
{
	FILE *fp;
	sh_int sn;

	logf("Writing skill table to " SKILLS_FILE ".write ...");
	fclose( fpReserve );

    if ( ( fp = fopen( SKILLS_FILE".write", "w" ) ) == NULL )
    {
		bugf("do_write_skills(): fopen '%s' for write - error %d (%s)",
			SKILLS_FILE".write", errno, strerror( errno));
		ch->printf("An error occured opening " SKILLS_FILE".write for writing!\r\n");
		autonote(NOTE_SNOTE, "do_write_skills()", "Problems saving skill table", "code cc: imm", 
			"An error occured opening " SKILLS_FILE".write for writing!\r\n", true);
    }else{
		// LOOP thru everything in the table, writing it
		for ( sn = 0; !IS_NULLSTR(skill_table[sn].name); sn++ )
		{
			if(!str_cmp("reserved", skill_table[sn].name)){
				continue;
			}
			fprintf(fp,"######NAME       %s~\n", skill_table[sn].name);
			GIO_SAVE_RECORD(skill_type, &skill_table[sn], fp, NULL);
			fprintf(fp,"\n");
		}
		fprintf(fp, "EOF~\n");

		int bytes_written=fprintf(fp, "EOF~\n");
		fclose( fp );
		if(   bytes_written != str_len("EOF~\n") ){
			bugf("do_write_skills(): fprintf to '%s' incomplete - error %d (%s)",
				SKILLS_FILE".write", errno, strerror( errno));
			bugf("Incomplete write of " SKILLS_FILE".write, write aborted - check diskspace!");
			ch->printf("Incomplete write of " SKILLS_FILE".write, write aborted - check diskspace!\r\n");
			autonote(NOTE_SNOTE, "do_write_skills()", "Problems saving skill table", "code cc: imm", 
				"Incomplete write of " SKILLS_FILE ".write, write aborted - check diskspace!\r\n", true);
		}else{		
			ch->printf("Finished writing skill table to "SKILLS_FILE ".write\r\n");
			logf("Finished writing skill table to "SKILLS_FILE ".write");

			ch->printf("Renaming old " SKILLS_FILE " to " SKILLS_FILE ".bak\r\n");
			logf("Renaming old " SKILLS_FILE " to " SKILLS_FILE ".bak");
			unlink(SKILLS_FILE ".bak");
			rename(SKILLS_FILE , SKILLS_FILE ".bak");

			ch->printf("Renaming new " SKILLS_FILE ".write to " SKILLS_FILE "\r\n");
			logf("Renaming new " SKILLS_FILE ".write to " SKILLS_FILE);
			unlink(SKILLS_FILE );
			rename(SKILLS_FILE ".write", SKILLS_FILE );
			logf("Finished writing skill table.");
		}
    }
	fpReserve = fopen( NULL_FILE, "r" );

}
/**************************************************************************/