dawn/notes/
dawn/src/
dawn/src/docs/
/**************************************************************************/
// races.cpp - race related code
/***************************************************************************
 * 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 "areas.h"

/**************************************************************************/
void do_langedit(char_data *ch, char *argument);
/**************************************************************************/
char *race_get_races_set_for_n_array(unsigned char n_array[])
{
	static char buf[MSL*2];
	buf[0]='\0';
	buf[1]='\0';
	for(int i=0; race_table[i]; i++){
		if(IS_SETn(n_array, i)){
			strcat(buf, " ");
			strcat(buf, pack_word(race_table[i]->name));
		}
	}
	return &buf[1];
}

/**************************************************************************/
bool race_data::creation_selectable()
{  
	if( IS_ALL_SET(flags, (RACEFLAG_PCRACE|RACEFLAG_CREATION_ACTIVATED))
		&& !IS_SET(flags, RACEFLAG_PCRACE_WITH_NO_CLASSXP_SET)
		&& !IS_SET(flags, RACEFLAG_DYNAMICALLY_GENERATED))
	{
		return true;
	}
	return false;
};

/**************************************************************************/
const struct flag_type race_flags[] =
{
	{ "creation_activated",		RACEFLAG_CREATION_ACTIVATED,true},
	{ "pcrace",					RACEFLAG_PCRACE,			true},
	{ "dynamically_generated",	RACEFLAG_DYNAMICALLY_GENERATED,false}, // a dynamically generated class can't be selected
	{ "pcrace_with_no_classxp_set", RACEFLAG_PCRACE_WITH_NO_CLASSXP_SET,false}, // a dynamically generated class can't be selected

	{ "nightmap",				RACEFLAG_NIGHTMAP,					true},
	{ "regen_slow_in_light",	RACEFLAG_REGEN_SLOW_IN_LIGHT,		true},
	{ "need_twice_as_much_sleep",RACEFLAG_NEED_TWICE_AS_MUCH_SLEEP,	true},	
	{ "always_hidden_from_mortal_raceinfo",RACEFLAG_ALWAYS_HIDDEN_FROM_MORTAL_RACEINFO,true},	
	{ "hidden_from_mortal_raceinfo_when_above_their_remort",RACEFLAG_HIDDEN_FROM_MORTAL_RACEINFO_WHEN_ABOVE_THEIR_REMORT,true},	
	{ "lowcost_launch",			RACEFLAG_LOWCOST_LAUNCH,true},	
	{ "no_customization",		RACEFLAG_NO_CUSTOMIZATION,true},	
	{ NULL,0,0}
};

/**************************************************************************/
void racetype_write_generic_races_set_for_n_array(gio_type *gio_table, int tableIndex,
													  void *data, FILE *fp)
{
	unsigned char *array_n=(unsigned char*)((char *)data + gio_table[tableIndex].index);	
	char *text=race_get_races_set_for_n_array(array_n);
	
	if(IS_NULLSTR(text)){
		return;
	}

	// output the race restricts
	fprintf(fp, "%s %s~\n",gio_table[tableIndex].heading, text);
}
/**************************************************************************/
void racetype_read_generic_races_set_for_n_array(gio_type *gio_table, int tableIndex,
													  void *data, FILE *fp)
{
	unsigned char *array_n=(unsigned char*)((char *)data + gio_table[tableIndex].index);	
	char *all_rnames, *rnames;
	char name[MIL];
	int ri;

	all_rnames=fread_string(fp);
	rnames=all_rnames;

	// table has already been nulled by the gio system
	rnames=one_argument(rnames, name);
	while(!IS_NULLSTR(name)){
		ri=race_exact_lookup(name);

		if (ri<0){
			logf( "racetype_read_generic_races_set_for_n_array: race '%s' not found... "
				"ignoring (header='%s')!", name, gio_table[tableIndex].heading);
		}else{
			SET_BITn(array_n, ri);
		}

		// get the next name
		rnames=one_argument(rnames, name);
	}
}

/**************************************************************************/
// gio prototypes
GIO_CUSTOM_FUNCTION_PROTOTYPE(race_gioWriteClassExp);
GIO_CUSTOM_FUNCTION_PROTOTYPE(race_gioReadClassExp);
// gio structures
// race_data GIO lookup table 
GIO_START(race_data)
GIO_STR(name)
GIO_STR(short_name)
GIO_STRH(load_language, "language")
GIO_SHINT(remort_number)
GIO_WFLAG(flags, race_flags)
GIO_SHWFLAG(size, size_types)
GIO_WFLAG(act, act_flags)
GIO_WFLAG(aff, affect_flags)
GIO_WFLAG(aff2, affect2_flags)
GIO_WFLAG(off, off_flags)
GIO_WFLAG(imm, imm_flags)
GIO_WFLAG(res, res_flags)
GIO_WFLAG(vuln, vuln_flags)
GIO_WFLAG(form, form_flags)
GIO_WFLAG(parts, part_flags)
GIO_SHINT(points)
GIO_CUSTOM_WRITEH(name, "class_exp", race_gioWriteClassExp)
GIO_CUSTOM_READH(name, "class_exp", race_gioReadClassExp)
GIO_STRH(load_skills, "skills")
GIO_SHINTH(stat_modifier[STAT_ST], "statmod_ST")
GIO_SHINTH(stat_modifier[STAT_QU], "statmod_QU")
GIO_SHINTH(stat_modifier[STAT_PR], "statmod_PR")
GIO_SHINTH(stat_modifier[STAT_EM], "statmod_EM")
GIO_SHINTH(stat_modifier[STAT_IN], "statmod_IN")
GIO_SHINTH(stat_modifier[STAT_CO], "statmod_CO")
GIO_SHINTH(stat_modifier[STAT_AG], "statmod_AG")
GIO_SHINTH(stat_modifier[STAT_SD], "statmod_SD")
GIO_SHINTH(stat_modifier[STAT_ME], "statmod_ME")
GIO_SHINTH(stat_modifier[STAT_RE], "statmod_RE")
GIO_SHINT(start_hp)
GIO_INT(max_hp)
GIO_SHINT(low_size)
GIO_SHINT(high_size)
GIO_INT(recall_room)
GIO_INT(death_room)
GIO_INT(morgue)
GIO_INT(newbie_map_vnum)
GIO_SHINT(min_height)
GIO_SHINT(max_height)
GIO_SHINT(min_weight)
GIO_SHINT(max_weight)
GIO_INT(food_vnum)
GIO_FINISH_STRDUP_EMPTY

/**************************************************************************/
// generates the unknown race - used by the system to prevent crashes
race_data *race_create_race(const char *racename)
{
	race_data *r=new race_data;
	memset(r,0, sizeof(race_data));
	r->name=str_dup(racename);
	r->short_name=str_dup(racename);
	r->language=language_unknown;
	r->load_language=str_dup("");
	r->load_skills=str_dup("");
	r->skills[0]=-1;
	r->remort_number=0;
	r->recall_room=ROOM_VNUM_LIMBO;
	r->death_room=ROOM_VNUM_LIMBO;
	r->flags=RACEFLAG_DYNAMICALLY_GENERATED;
	r->act=0;
	r->aff=0; 				// aff bits for the race
	r->aff2=0; 				// aff2 bits for the race
	r->off=0; 				// off bits for the race
	r->imm=0;				// imm bits for the race
	r->res=0;				// res bits for the race
	r->vuln=0;				// vuln bits for the race
	r->form=0;				// default form flag for the race
	r->parts=0;				// default parts for the race
	r->points=0;
	r->size=SIZE_MEDIUM;
	r->start_hp		= 10;
	r->max_hp		= 150;
	r->low_size		= 20;
	r->high_size	= 70;	

	r->next=NULL;
	return r;
}
/**************************************************************************/
// create a race and add it to the race_table, returning its index
int race_generate_race_adding_to_race_table(const char *racename)
{
	assertp(race_table); // we assume the race_table has already been allocated

	race_data *newr=race_create_race(racename);

	race_data *r;
	for(r=race_list; r->next; r=r->next){
	}
	r->next=newr;

	// link it into the race_table
	int i;
	for(i=0; race_table[i];i++){
	}
	race_table[i]=newr;	
	return i;
}
/**************************************************************************/
void race_allocate_race_table()
{
	// allocate our race_table, and link its index positions to list positions
	race_table=(race_data **)calloc(MAX_RACE+2, sizeof(race_data*));	
}

/**************************************************************************/
void race_populate_race_table()
{
	int i=0;
	for(race_data *r=race_list; r; r=r->next){
		if(i>=MAX_RACE){
			bugf("race_populate_race_table(): more than MAX_RACE races were read in (%d)", MAX_RACE);
			log_notef("The mud has currently been compiled to read in only %d races... but the races "
				"file '%s' contains more races that that... either increase MAX_RACE in params.h or "
				"remove some races from the races file.",
				MAX_RACE,
				RACES_FILE);
			exit_error( 1 , "race_populate_race_table", "too many races");
		}
		race_table[i++]=r;
	}
}

/**************************************************************************/
void race_convert_skills()
{
	char skillname[MIL];
	int skill, next_skill_index;
	char *arg;
	race_data *r;

	log_string("race_convert_skills(): Converting race skills format");

	for(r=race_list; r; r=r->next){
		arg=r->load_skills;
		next_skill_index=0;
		while(!IS_NULLSTR(arg)){
			arg=one_argument(arg, skillname);
			skill=skill_lookup(skillname);
			if(skill<0){
				bugf("Unknown skill '%s' for race '%s'... continuing bootup (%s).",
					skillname, r->name, r->load_skills);
			}else{
				if(next_skill_index>=MAX_RACIAL_SKILLS){
					bugf("race_convert_skills(): next_skill_index==%d (>=MAX_RACIAL_SKILLS)"
						"for race '%s', load_skills='%s'", next_skill_index,
						r->name, r->load_skills);
					exit_error( 1 , "race_convert_skills", "too many racial skills on race");
				}
				r->skills[next_skill_index]=skill;
				next_skill_index++;				
			}
		}
		replace_string(r->load_skills, ""); // free the memory
		if(next_skill_index<MAX_RACIAL_SKILLS){
			r->skills[next_skill_index]=-1; // mark the end of the skill list
		}
	}
};

/**************************************************************************/
// read in races from disk
void load_races()
{
	race_data *r, *prev;
    log_string("===Reading in races...");
    GIOLOAD_LIST(race_list, race_data, RACES_FILE);

	// remove any 'unknown' races loaded from the races file
	prev=NULL;
	for(r=race_list; r; r=r->next){
		if(IS_NULLSTR(r->name) || !str_cmp(r->name, "unknown")){
			log_notef("Discarding NULL/'unknown' race read in from %s... the "
				"unknown race must be dynamically generated by the code.",
				RACES_FILE);

			if(prev){
				prev->next=r->next;
			}else{
				race_list=r->next;
			}
			continue;
		}
		prev=r;
	}

	// if race file not found, try to import from old format
	if(!race_list){
		log_note("load_races(): no races loaded - WE NEED TO ATTEMPT TO IMPORT IN HERE!!!");
	}


	// dynamically create a single unknown race and put it at the start
	// of the races list.
	r=race_create_race("unknown");
	r->next=race_list;
	race_list=r;

	race_allocate_race_table();
	race_populate_race_table();

	// patch up the languages, skills etc  + do a count while here
	int count=0;
	bool creation_selectable_race_found=false;
	for(r=race_list; r; r=r->next){
		if(r->start_hp<1){
			r->start_hp=1;
		}
		if(r->creation_selectable()){
			creation_selectable_race_found=true;
		}
		if(IS_NULLSTR(r->load_language)){
			r->language=language_unknown;
		}else{
			r->language=language_lookup(r->load_language);
		}
		if(!r->language){
			logf("unfound language '%s' for race '%s' - creating language.", 
				r->load_language, r->name);
			do_langedit(NULL,FORMATF("create %s",r->load_language));

			r->language=language_lookup(r->load_language);
			if(!r->language){
				logf("Dynamic language creation failed, something is wrong - aborting bootup process.");
				do_abort();
			}
		}
		count++;

		r->update_dynamic_flags();
	}

	// if there are no creation selectable races, make the unknown race 
	// creation selectable so someone can create using it (and therefore 
	// create more races)
	if(!creation_selectable_race_found){
		log_note("load_races()  Note: no creation selectable races were loaded "
			"so the auto generated 'unknown' race has been configured as creation "
			"selectable - enabling you to create a character, then edit the races "
			"using the raceedit command.");
		SET_BIT(race_list->flags, RACEFLAG_PCRACE|RACEFLAG_CREATION_ACTIVATED);
	}

    logf("===Race loading completed - %d race%s in total.", 
		count, count==1?"":"s");
}
/**************************************************************************/
// save race to disk
void save_races()
{
	race_data *r;

	// prepare fields for saving
	for(r=race_list; r; r=r->next){
		 // prepare the load_language value
		replace_string(r->load_language, r->language->name);

		{ // prepare the load_skill value
			char skills_text[MSL];	
			skills_text[0]='\0';
			skills_text[1]='\0';
			for( int i = 0; i<MAX_RACIAL_SKILLS && r->skills[i]!=-1; i++ ) {
				strcat(skills_text, " ");
				strcat(skills_text, pack_word(skill_table[r->skills[i]].name));
			};
			replace_string(r->load_skills, &skills_text[1]);  // skip the leading space
		}
	}
	
    log_string ("===Saving races...");
	assert(!str_cmp(race_list->name, "unknown")); // first race should always be the 'unknown' race
	// save race_list->next, so we don't save the unknown race
    GIOSAVE_LIST(race_list->next, race_data, RACES_FILE, true);
	log_string ("===Races saved.");
}
/**************************************************************************/
// write the exp to play race for a particular class
void race_gioWriteClassExp(gio_type *gio_table, int tableIndex, void *data, FILE *fp){
	
	race_data * r;
	int classIndex;
	
	r= (race_data*) data;

	fprintf(fp, "%s\n", gio_table[tableIndex].heading);
	for (classIndex=0; !IS_NULLSTR(class_table[classIndex].name); classIndex++){
		// only write the classes with valid xp per level values
		if (r->class_exp[classIndex]==0 || (
			r->class_exp[classIndex]>=1000 
			&& r->class_exp[classIndex]<=20000)){
			fprintf(fp, "\t%s %d\n", pack_word(class_table[classIndex].name), 
				r->class_exp[classIndex]);
		}else{
			fprintf(fp, "\t*%s_has_an_invalid_xp_modifer %d\n", class_table[classIndex].name, 
				r->class_exp[classIndex]);
		}
	};
	fprintf(fp, "~\n");
}

/**************************************************************************/
int create_class(char * class_name);
/**************************************************************************/
// race the exp to play race for a particular class
void race_gioReadClassExp(gio_type *, int, void *data, FILE *fp)
{
	race_data* r;
	char className[MIL];
	int xpMod;
	int classIndex;

	r= (race_data*) data;

	// initialise all values to 0
	for (classIndex=0; class_table[classIndex].name; classIndex++){
		r->class_exp[classIndex]=0;
	}

	while (true){
		// get the class name and modifier
		//fscanf(fp, "%s ", className);
		strcpy(className,fread_word(fp));
		if (className[0]=='~' && className[1]=='\0'){
			break;
		}

		// read in the experience modifier
		xpMod=fread_number(fp);
//		fscanf(fp, "%d", &xpMod);

		classIndex =class_lookup(className);

		if (classIndex<0){
			if (className[0]!='*'){
				// add a new class if necessary
				classIndex=create_class(className);
				if(classIndex<0){
					bugf( "race_gioReadClassExp: class '%s' not found... problems dynamically adding... aborting!", className);
					do_abort();
				}else{
					bugf( "race_gioReadClassExp: class '%s' not found...\n"
						  "********* Dynamically added %s - creation selectable = false.", 
						  className, className);
					r->class_exp[classIndex]=xpMod;
				}
			}
		}else{
			r->class_exp[classIndex]=xpMod;
		}
	}
}

/**************************************************************************/
void race_data::update_dynamic_flags()
{ 
	// pcrace_with_no_classxp_set flag
	if(!IS_SET(flags, RACEFLAG_PCRACE)){
		REMOVE_BIT(flags, RACEFLAG_PCRACE_WITH_NO_CLASSXP_SET);
	}else{
		int count=0;
		for ( int i=0; !IS_NULLSTR(class_table[i].name); i++){ 
			if(class_exp[i]){ 
				count++; 
			}	
		} 
		if(count){
			REMOVE_BIT(flags, RACEFLAG_PCRACE_WITH_NO_CLASSXP_SET);
		}else{
			SET_BIT(flags, RACEFLAG_PCRACE_WITH_NO_CLASSXP_SET);
		}
	}
};
/**************************************************************************/
void display_race_selection(connection_data *d);
/**************************************************************************/
// Kal - Aug 01
void do_raceinfo( char_data *ch, char *argument )
{
	char titlebar[MIL];
	int stat, race, col_index=1;
	bool customization_disabled=false;
	char buf[MIL], linebuf[MSL];
	char col;
	int total;		
	connection_data *d=ch->desc;
	if(!d){
		ch->println("You have to be connected to use this command.");
		return;
	}

	// display races
	ch->print_blank_lines(1);
	if(IS_IMMORTAL(ch)){
		ch->println("`xThe game currently has the following player races (`Y*`x=not creation selectable):");
	}else{
		ch->println("`xThe game currently has the following player selectable races:");
	}

	// create and display a titlebar
	sprintf(titlebar, "%s====`srace name`m====`sst`m==`squ`m==`spr`m==`sem`m==`sin`m==`sco`m==`sag"
		"`m==`ssd`m==`sme`m==`sre`m=`stotal`m==`scp`m===`shp`m==`x",
		(GAMESETTING(GAMESET_REMORT_SUPPORTED)?"`sremort`m":"`m====="));	
	ch->println(titlebar);

	for( race = 1; race_table[race]; race++ )
	{
		if(!race_table[race]->pc_race())
			continue;

		if(!IS_IMMORTAL(ch)){
			if(!race_table[race]->creation_selectable()){
				continue;
			}
			if(IS_SET(race_table[race]->flags, RACEFLAG_ALWAYS_HIDDEN_FROM_MORTAL_RACEINFO)){
				continue;
			}

			if(GAMESETTING(GAMESET_REMORT_SUPPORTED)
				&& ch->desc 
				&& ch->desc->connected_state == CON_PLAYING
				&& IS_SET(race_table[race]->flags, RACEFLAG_HIDDEN_FROM_MORTAL_RACEINFO_WHEN_ABOVE_THEIR_REMORT)
				&& race_table[race]->remort_number > ch->remort)
			{
				continue;
			}
			
		}
		
		if(ch->desc && ch->desc->connected_state != CON_PLAYING){
			if(race_table[race]->remort_number > ch->desc->creation_remort_number)
				continue;
		}

		++col_index%=2;

		col= col_index?'M':'x';
		
		linebuf[0]='\0';

		if(GAMESETTING(GAMESET_REMORT_SUPPORTED)){
			sprintf(buf,"`s%d",race_table[race]->remort_number);			
			strcat(linebuf,buf);
		}
		sprintf(buf,"`%c%21s`%c=%s ", col, FORMATF("`=_%s",race_table[race]->name), 
			col, race_table[race]->creation_selectable()?" ":"`Y*");
		strcat(linebuf,buf);

		total=0;
		for(stat = 0; stat < MAX_STATS; stat++)
		{
			sprintf(buf, "`%c%+3d`%c=",
				race_table[race]->stat_modifier[stat]<0?
					'R':race_table[race]->stat_modifier[stat]==0?'g':'B',
				race_table[race]->stat_modifier[stat],
				col);
			strcat(linebuf,buf);
			total+=race_table[race]->stat_modifier[stat];
		}		

		// display the total value as well
		sprintf(buf, "`%c%+4d`%c=", total<0?'R':total==0?'g':'B',total,col);
		strcat(linebuf,buf);

		sprintf(buf," %3d %4d", 
			race_table[race]->points,
			race_table[race]->max_hp); 
		strcat(linebuf,buf);

		if(!GAMESETTING5(GAMESET5_CREATION_DISABLE_CUSTOMIZATION) 
			&& IS_SET(race_table[race]->flags, RACEFLAG_NO_CUSTOMIZATION))
		{
			strcat(linebuf,"`Y-`x");	
			customization_disabled=true;
		}
		strcat(linebuf,"\r\n");
		ch->print(linebuf);
	}	
	// repeat the titlebar
	ch->println(titlebar);
	if(customization_disabled ){
		ch->println("`Y-`x = this race has no advanced customization available.");
	}
	ch->print_blank_lines(1);
}
/**************************************************************************/
void do_saveraces( char_data *ch, char * )
{
	save_races();
    log_string("save completed");
    ch->println("Races manually saved.");
}

/**************************************************************************/
// Kalahn - August 98
void do_npcinfo( char_data *ch, char *)
{
	int raceIndex, raceCount=0;

	ch->titlebar("NPC INFO");

	for ( raceIndex= 0; race_table[raceIndex]; raceIndex++ )
	{
		raceCount++;
		ch->printf(" `x%-15s `G%4d`g%5.1f%%`M%4d`m%5.1f%%  `x", 
			race_table[raceIndex]->name,
			race_table[raceIndex]->inuse,
			((float)(race_table[raceIndex]->inuse*100)/(float)total_npcracescount),
			race_table[raceIndex]->areacount,
			((float)(race_table[raceIndex]->areacount*100)/(float)top_area));
		if (raceCount%2==0)
		{
			ch->println("");
		}
    }

	if (raceCount%2==1)
	{
		ch->println("");
	}

	ch->printf("\r\n  `RTOTALS: `xTotal number of NPC races: %d/%d\r\n"
		"  Total number of mobs: `G%-5d`x       Total number of areas: `M%3d`x\r\n"
		"  Total number of different races used in different areas: `Y%3d`x\r\n",
		raceCount, MAX_RACE, total_npcracescount, top_area, total_npcareacount);


};
/**************************************************************************/
void do_racelist( char_data *ch, char *arg)
{
	bool editing=false;
	if(!str_cmp("edit",arg)){
		editing=true;
	}
	int count=-1;
	int ri;
	char name[MIL];
	// loop thru showing all the creation selectable races
	for( ri= 1; race_table[ri]; ri++ ){
		if(!race_table[ri]->creation_selectable()){
			continue;
		}
		sprintf(name, "%-16s",race_table[ri]->name);
		if(GAMESETTING(GAMESET_REMORT_SUPPORTED)){
			ch->printf(" `S[%d]`x`Y%s", 
				race_table[ri]->remort_number,
				editing?mxp_create_send(ch,FORMATF("raceedit %s", race_table[ri]->name), name):name);
		}else{
			ch->printf("    `Y%s", editing?mxp_create_send(ch,FORMATF("raceedit %s", race_table[ri]->name), name):name);
		}
		if(++count%3==2){
			ch->print_blank_lines(1);
		}
	}
	// loop thru showing all the non creation selectable races
	for(ri= 1; race_table[ri]; ri++ ){
		if(race_table[ri]->creation_selectable()){
			continue;
		}
		sprintf(name, "   %-16s",race_table[ri]->name);
		ch->printf(" `x%s", editing?mxp_create_send(ch,FORMATF("raceedit %s", race_table[ri]->name), name):name);
		if(++count%3==2){
			ch->print_blank_lines(1);
		}
	}
	if(count!=2){
		ch->print_blank_lines(1);
	}
	ch->println("Yellow races are creation selectable.`x");
}
/**************************************************************************/