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