diff options
Diffstat (limited to 'crawl-ref/source/hiscores.cc')
-rw-r--r-- | crawl-ref/source/hiscores.cc | 2152 |
1 files changed, 1180 insertions, 972 deletions
diff --git a/crawl-ref/source/hiscores.cc b/crawl-ref/source/hiscores.cc index ab84678410..bb15621d36 100644 --- a/crawl-ref/source/hiscores.cc +++ b/crawl-ref/source/hiscores.cc @@ -3,6 +3,8 @@ * Summary: deal with reading and writing of highscore file * Written by: Gordon Lipford * + * Modified for Crawl Reference by $Author$ on $Date$ + * * Change History (most recent first): * * <1> 16feb2001 gdl Created @@ -35,9 +37,14 @@ #include "hiscores.h" #include "itemname.h" +#include "itemprop.h" +#include "items.h" +#include "libutil.h" +#include "misc.h" #include "mon-util.h" #include "player.h" #include "religion.h" +#include "shopping.h" #include "stuff.h" #include "tags.h" #include "view.h" @@ -51,7 +58,7 @@ #endif // enough memory allocated to snarf in the scorefile entries -static struct scorefile_entry hs_list[SCORE_FILE_ENTRIES]; +static scorefile_entry hs_list[SCORE_FILE_ENTRIES]; // hackish: scorefile position of newest entry. Will be highlit during // highscore printing (always -1 when run from command line). @@ -59,18 +66,19 @@ static int newest_entry = -1; static FILE *hs_open(const char *mode); static void hs_close(FILE *handle, const char *mode); -static bool hs_read(FILE *scores, struct scorefile_entry &dest); -static void hs_parse_numeric(char *inbuf, struct scorefile_entry &dest); -static void hs_parse_string(char *inbuf, struct scorefile_entry &dest); -static void hs_copy(struct scorefile_entry &dest, struct scorefile_entry &src); -static void hs_write(FILE *scores, struct scorefile_entry &entry); -static void hs_nextstring(char *&inbuf, char *dest); +static bool hs_read(FILE *scores, scorefile_entry &dest); +static void hs_parse_numeric(char *inbuf, scorefile_entry &dest); +static void hs_parse_string(char *inbuf, scorefile_entry &dest); +static void hs_write(FILE *scores, scorefile_entry &entry); +static void hs_nextstring(char *&inbuf, char *dest, size_t bufsize); static int hs_nextint(char *&inbuf); static long hs_nextlong(char *&inbuf); // functions dealing with old scorefile entries -static void hs_parse_generic_1(char *&inbuf, char *outbuf, const char *stopvalues); -static void hs_parse_generic_2(char *&inbuf, char *outbuf, const char *continuevalues); +static void hs_parse_generic_1(char *&inbuf, char *outbuf, size_t outsz, + const char *stopvalues); +static void hs_parse_generic_2(char *&inbuf, char *outbuf, size_t outsz, + const char *continuevalues); static void hs_stripblanks(char *buf); static void hs_search_death(char *inbuf, struct scorefile_entry &se); static void hs_search_where(char *inbuf, struct scorefile_entry &se); @@ -81,7 +89,7 @@ static bool lock_file_handle( FILE *handle, int type ); static bool unlock_file_handle( FILE *handle ); #endif // USE_FILE_LOCKING -void hiscores_new_entry( struct scorefile_entry &ne ) +void hiscores_new_entry( const scorefile_entry &ne ) { FILE *scores; int i, total_entries; @@ -90,6 +98,9 @@ void hiscores_new_entry( struct scorefile_entry &ne ) // open highscore file (reading) -- note that NULL is not fatal! scores = hs_open("r"); + for (i = 0; i < SCORE_FILE_ENTRIES; ++i) + hs_list[i].reset(); + // read highscore file, inserting new entry at appropriate point, for (i = 0; i < SCORE_FILE_ENTRIES; i++) { @@ -105,23 +116,23 @@ void hiscores_new_entry( struct scorefile_entry &ne ) // Fixed a nasty overflow bug here -- Sharp if (i+1 < SCORE_FILE_ENTRIES) { - hs_copy(hs_list[i+1], hs_list[i]); - hs_copy(hs_list[i], ne); + hs_list[i + 1] = hs_list[i]; + hs_list[i] = ne; i++; } else { - // copy new entry to current position - hs_copy(hs_list[i], ne); + // copy new entry to current position + hs_list[i] = ne; } } } - // special case: lowest score, with room + // special case: lowest score, with room if (!inserted && i < SCORE_FILE_ENTRIES) { newest_entry = i; inserted = true; // copy new entry - hs_copy(hs_list[i], ne); + hs_list[i] = ne; i++; } @@ -199,16 +210,16 @@ void hiscores_print_list( int display_count, int format ) // print position (tracked implicitly by order score file) snprintf( info, INFO_SIZE, "%3d.", i + 1 ); if (use_printf) - printf(info); + printf("%s", info); else - cprintf(info); + cprintf("%s", info); // format the entry if (format == SCORE_TERSE) { hiscores_format_single( info, hs_list[i] ); // truncate if we want short format - info[75] = '\0'; + info[75] = 0; } else { @@ -219,9 +230,9 @@ void hiscores_print_list( int display_count, int format ) // print entry strcat(info, EOL); if(use_printf) - printf(info); + printf("%s", info); else - cprintf(info); + cprintf("%s", info); if (i == newest_entry && !use_printf) textcolor(LIGHTGREY); @@ -233,7 +244,7 @@ static const char *const range_type_verb( const char *const aux ) { if (strncmp( aux, "Shot ", 5 ) == 0) // launched return ("shot"); - else if (aux[0] == '\0' // unknown + else if (aux[0] == 0 // unknown || strncmp( aux, "Hit ", 4 ) == 0 // thrown || strncmp( aux, "volley ", 7 ) == 0) // manticore spikes { @@ -243,274 +254,11 @@ static const char *const range_type_verb( const char *const aux ) return ("blasted"); // spells, wands } -void hiscores_format_single(char *buf, struct scorefile_entry &se) +void hiscores_format_single(char *buf, const scorefile_entry &se) { - char scratch[100]; - - // Now that we have a long format, I'm starting to make this - // more terse, in hopes that it will better fit. -- bwr - - // race_class_name overrides race & class - if (se.race_class_name[0] == '\0') - { - snprintf( scratch, sizeof(scratch), "%s%s", - get_species_abbrev( se.race ), get_class_abbrev( se.cls ) ); - } - else - { - strcpy( scratch, se.race_class_name ); - } - - se.name[10]='\0'; - sprintf( buf, "%8ld %-10s %s-%02d%s", se.points, se.name, - scratch, se.lvl, (se.wiz_mode == 1) ? "W" : "" ); - - // get monster type & number, if applicable - int mon_type = se.death_source; - int mon_number = se.mon_num; - - // remember -- we have 36 characters (not including initial space): - switch (se.death_type) - { - case KILLED_BY_MONSTER: - strcat( buf, " slain by " ); - - // if death_source_name is non-null, override lookup (names might have - // changed!) - if (se.death_source_name[0] != '\0') - strcat( buf, se.death_source_name ); - else - strcat( buf, monam( mon_number, mon_type, true, DESC_PLAIN ) ); - - break; - - case KILLED_BY_POISON: - //if (dam == -9999) strcat(buf, "an overload of "); - strcat( buf, " succumbed to poison" ); - break; - - case KILLED_BY_CLOUD: - if (se.auxkilldata[0] == '\0') - strcat( buf, " engulfed by a cloud" ); - else - { - const int len = strlen( se.auxkilldata ); - - // Squeeze out "a cloud of" if required. -- bwr - snprintf( scratch, sizeof(scratch), " engulfed by %s%s", - (len < 15) ? "a cloud of " : "", - se.auxkilldata ); - - strcat( buf, scratch ); - } - break; - - case KILLED_BY_BEAM: - // keeping this short to leave room for the deep elf spellcasters: - snprintf( scratch, sizeof(scratch), " %s by ", - range_type_verb( se.auxkilldata ) ); - strcat( buf, scratch ); - - // if death_source_name is non-null, override this - if (se.death_source_name[0] != '\0') - strcat( buf, se.death_source_name ); - else - strcat( buf, monam( mon_number, mon_type, true, DESC_PLAIN ) ); - break; - -/* - case KILLED_BY_DEATHS_DOOR: - // death's door running out - NOTE: This is no longer fatal - strcat(buf, " ran out of time"); - break; -*/ - - case KILLED_BY_LAVA: - if (se.race == SP_MUMMY) - strcat( buf, " turned to ash by lava" ); - else - strcat( buf, " took a swim in lava" ); - break; - - case KILLED_BY_WATER: - if (se.race == SP_MUMMY) - strcat( buf, " soaked and fell apart" ); - else - strcat( buf, " drowned" ); - break; - - // these three are probably only possible if you wear a ring - // of >= +3 ability, get drained to 3, then take it off, or have a very - // low abil and wear a -ve ring. or, as of 2.7x, mutations can cause this - // Don't forget decks of cards (they have some nasty code for this) -- bwr - case KILLED_BY_STUPIDITY: - strcat( buf, " died of stupidity" ); - break; - - case KILLED_BY_WEAKNESS: - strcat( buf, " became too weak to continue" ); - break; - - case KILLED_BY_CLUMSINESS: - strcat( buf, " slipped on a banana peel" ); - break; - - case KILLED_BY_TRAP: - snprintf( scratch, sizeof(scratch), " triggered a%s trap", - (se.auxkilldata[0] != '\0') ? se.auxkilldata : "" ); - strcat( buf, scratch ); - break; - - case KILLED_BY_LEAVING: - strcat( buf, " got out of the dungeon alive" ); - break; - - case KILLED_BY_WINNING: - strcat( buf, " escaped with the Orb!" ); - break; - - case KILLED_BY_QUITTING: - strcat( buf, " quit the game" ); - break; - - case KILLED_BY_DRAINING: - strcat( buf, " drained of all life" ); - break; - - case KILLED_BY_STARVATION: - strcat( buf, " starved to death" ); - break; - - case KILLED_BY_FREEZING: - strcat( buf, " froze to death" ); - break; - - case KILLED_BY_BURNING: // only sticky flame - strcat( buf, " burnt to a crisp" ); - break; - - case KILLED_BY_WILD_MAGIC: - if (se.auxkilldata[0] == '\0') - strcat( buf, " killed by wild magic" ); - else - { - const bool need_by = (strncmp( se.auxkilldata, "by ", 3 ) == 0); - const int len = strlen( se.auxkilldata ); - - // Squeeze out "killed" if we're getting a bit long. -- bwr - snprintf( scratch, sizeof(scratch), " %s%s%s", - (len + 3 * (need_by) < 30) ? "killed" : "", - (need_by) ? "by " : "", - se.auxkilldata ); - - strcat( buf, scratch ); - } - break; - - case KILLED_BY_XOM: // only used for old Xom kills - strcat( buf, " killed for Xom's enjoyment" ); - break; - - case KILLED_BY_STATUE: - strcat( buf, " killed by a statue" ); - break; - - case KILLED_BY_ROTTING: - strcat( buf, " rotted away" ); - break; - - case KILLED_BY_TARGETTING: - strcat( buf, " killed by bad targeting" ); - break; - - case KILLED_BY_SPORE: - strcat( buf, " killed by an exploding spore" ); - break; - - case KILLED_BY_TSO_SMITING: - strcat( buf, " smote by The Shining One" ); - break; - - case KILLED_BY_PETRIFICATION: - strcat( buf, " turned to stone" ); - break; - - case KILLED_BY_SHUGGOTH: - strcat( buf, " eviscerated by a hatching shuggoth" ); - break; - - case KILLED_BY_SOMETHING: - strcat( buf, " died" ); - break; - - case KILLED_BY_FALLING_DOWN_STAIRS: - strcat( buf, " fell down a flight of stairs" ); - break; - - case KILLED_BY_ACID: - strcat( buf, " splashed by acid" ); - break; - - default: - strcat( buf, " nibbled to death by software bugs" ); - break; - } // end switch - - if (se.death_type != KILLED_BY_LEAVING && se.death_type != KILLED_BY_WINNING) - { - if (se.level_type == LEVEL_ABYSS) - { - strcat(buf, " (Abyss)"); - return; - } - else if (se.level_type == LEVEL_PANDEMONIUM) - { - strcat(buf, " (Pan)"); - return; - } - else if (se.level_type == LEVEL_LABYRINTH) - { - strcat(buf, " (Lab)"); - return; - } - else if (se.branch == BRANCH_VESTIBULE_OF_HELL) - { - strcat(buf, " (Hell)"); // Gate? Vest? - return; - } - else if (se.branch == BRANCH_HALL_OF_BLADES) - { - strcat(buf, " (Blade)"); - return; - } - else if (se.branch == BRANCH_ECUMENICAL_TEMPLE) - { - strcat(buf, " (Temple)"); - return; - } - - snprintf( scratch, sizeof(scratch), " (%s%d)", - (se.branch == BRANCH_DIS) ? "Dis " : - (se.branch == BRANCH_GEHENNA) ? "Geh " : - (se.branch == BRANCH_COCYTUS) ? "Coc " : - (se.branch == BRANCH_TARTARUS) ? "Tar " : - (se.branch == BRANCH_ORCISH_MINES) ? "Orc " : - (se.branch == BRANCH_HIVE) ? "Hive " : - (se.branch == BRANCH_LAIR) ? "Lair " : - (se.branch == BRANCH_SLIME_PITS) ? "Slime " : - (se.branch == BRANCH_VAULTS) ? "Vault " : - (se.branch == BRANCH_CRYPT) ? "Crypt " : - (se.branch == BRANCH_HALL_OF_ZOT) ? "Zot " : - (se.branch == BRANCH_SNAKE_PIT) ? "Snake " : - (se.branch == BRANCH_ELVEN_HALLS) ? "Elf " : - (se.branch == BRANCH_TOMB) ? "Tomb " : - (se.branch == BRANCH_SWAMP) ? "Swamp " : "DLv ", - se.dlvl ); - - strcat( buf, scratch ); - } // endif - killed by winning - - return; + std::string line = se.hiscore_line(scorefile_entry::DDV_ONELINE); + strncpy(buf, line.c_str(), INFO_SIZE); + buf[INFO_SIZE - 1] = 0; } static bool hiscore_same_day( time_t t1, time_t t2 ) @@ -536,539 +284,21 @@ static void hiscore_date_string( time_t time, char buff[INFO_SIZE] ) date->tm_mday, date->tm_year + 1900 ); } -static void hiscore_newline( char *buf, int &line_count ) +static std::string hiscore_newline_string() { - strncat( buf, EOL " ", HIGHSCORE_SIZE ); - line_count++; + return (EOL " "); } -int hiscores_format_single_long( char *buf, struct scorefile_entry &se, +void hiscores_format_single_long( char *buf, const scorefile_entry &se, bool verbose ) { - char scratch[INFO_SIZE]; - int line_count = 1; - - // race_class_name could/used to override race & class - // strcpy(scratch, se.race_class_name); - - // Please excuse the following bit of mess in the name of flavour ;) - if (verbose) - { - strncpy( scratch, skill_title( se.best_skill, se.best_skill_lvl, - se.race, se.str, se.dex, se.god ), - INFO_SIZE ); - - snprintf( buf, HIGHSCORE_SIZE, "%8ld %s the %s (level %d", - se.points, se.name, scratch, se.lvl ); - - } - else - { - snprintf( buf, HIGHSCORE_SIZE, "%8ld %s the %s %s (level %d", - se.points, se.name, species_name(se.race, se.lvl), - get_class_name(se.cls), se.lvl ); - } - - if (se.final_max_max_hp > 0) // as the other two may be negative - { - snprintf( scratch, INFO_SIZE, ", %d/%d", se.final_hp, se.final_max_hp ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - - if (se.final_max_hp < se.final_max_max_hp) - { - snprintf( scratch, INFO_SIZE, " (%d)", se.final_max_max_hp ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - } - - strncat( buf, " HPs", HIGHSCORE_SIZE ); - } - - strncat( buf, ((se.wiz_mode) ? ") *WIZ*" : ")"), HIGHSCORE_SIZE ); - hiscore_newline( buf, line_count ); - - if (verbose) - { - const char *const race = species_name( se.race, se.lvl ); - - snprintf( scratch, INFO_SIZE, "Began as a%s %s %s", - is_vowel(race[0]) ? "n" : "", race, get_class_name(se.cls) ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - - if (se.birth_time > 0) - { - strncat( buf, " on ", HIGHSCORE_SIZE ); - hiscore_date_string( se.birth_time, scratch ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - } - - strncat( buf, "." , HIGHSCORE_SIZE ); - hiscore_newline( buf, line_count ); - - if (se.race != SP_DEMIGOD && se.god != -1) - { - if (se.god == GOD_XOM) - { - snprintf( scratch, INFO_SIZE, "Was a %sPlaything of Xom.", - (se.lvl >= 20) ? "Favourite " : "" ); - - strncat( buf, scratch, HIGHSCORE_SIZE ); - hiscore_newline( buf, line_count ); - } - else if (se.god != GOD_NO_GOD) - { - // Not exactly the same as the religon screen, but - // good enough to fill this slot for now. - snprintf( scratch, INFO_SIZE, "Was %s of %s%s", - (se.piety > 160) ? "the Champion" : - (se.piety >= 120) ? "a High Priest" : - (se.piety >= 100) ? "an Elder" : - (se.piety >= 75) ? "a Priest" : - (se.piety >= 50) ? "a Believer" : - (se.piety >= 30) ? "a Follower" - : "an Initiate", - god_name( se.god ), - (se.penance > 0) ? " (penitent)." : "." ); - - strncat( buf, scratch, HIGHSCORE_SIZE ); - hiscore_newline( buf, line_count ); - } - } - } - - // get monster type & number, if applicable - int mon_type = se.death_source; - int mon_number = se.mon_num; - - bool needs_beam_cause_line = false; - bool needs_called_by_monster_line = false; - bool needs_damage = false; - - switch (se.death_type) - { - case KILLED_BY_MONSTER: - // GDL: here's an example of using final_hp. Verbiage could be better. - // bwr: changed "blasted" since this is for melee - snprintf( scratch, INFO_SIZE, "%s %s", - (se.final_hp > -6) ? "Slain by" : - (se.final_hp > -14) ? "Mangled by" : - (se.final_hp > -22) ? "Demolished by" - : "Annihilated by", - - (se.death_source_name[0] != '\0') - ? se.death_source_name - : monam( mon_number, mon_type, true, DESC_PLAIN ) ); - - strncat( buf, scratch, HIGHSCORE_SIZE ); - - // put the damage on the weapon line if there is one - if (se.auxkilldata[0] == '\0') - needs_damage = true; - break; - - case KILLED_BY_POISON: - //if (dam == -9999) strcat(buf, "an overload of "); - strcat( buf, "Succumbed to poison" ); - break; - - case KILLED_BY_CLOUD: - if (se.auxkilldata[0] == '\0') - strcat( buf, "Engulfed by a cloud" ); - else - { - snprintf( scratch, sizeof(scratch), "Engulfed by a cloud of %s", - se.auxkilldata ); - strcat( buf, scratch ); - } - needs_damage = true; - break; - - case KILLED_BY_BEAM: - if (isupper( se.auxkilldata[0] )) // already made (ie shot arrows) - { - strcat( buf, se.auxkilldata ); - needs_damage = true; - } - else if (verbose && strncmp( se.auxkilldata, "by ", 3 ) == 0) - { - // "by" is used for priest attacks where the effect is indirect - // in verbose format we have another line for the monster - needs_called_by_monster_line = true; - snprintf( scratch, sizeof(scratch), "Killed %s", se.auxkilldata ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - } - else - { - // Note: This is also used for the "by" cases in non-verbose - // mode since listing the monster is more imporatant. - strcat( buf, "Killed from afar by " ); - - // if death_source_name is non-null, override this - if (se.death_source_name[0] != '\0') - strcat(buf, se.death_source_name); - else - strcat(buf, monam( mon_number, mon_type, true, DESC_PLAIN )); - - if (se.auxkilldata[0] != '\0') - needs_beam_cause_line = true; - } - break; - - case KILLED_BY_LAVA: - if (se.race == SP_MUMMY) - strcat( buf, "Turned to ash by lava" ); - else - strcat( buf, "Took a swim in molten lava" ); - break; - - case KILLED_BY_WATER: - if (se.race == SP_MUMMY) - strcat( buf, "Soaked and fell apart" ); - else - strcat( buf, "Drowned" ); - break; - - case KILLED_BY_STUPIDITY: - strcat( buf, "Forgot to breathe" ); - break; - - case KILLED_BY_WEAKNESS: - strcat( buf, "Collapsed under their own weight" ); - break; - - case KILLED_BY_CLUMSINESS: - strcat( buf, "Slipped on a banana peel" ); - break; - - case KILLED_BY_TRAP: - snprintf( scratch, sizeof(scratch), "Killed by triggering a%s trap", - (se.auxkilldata[0] != '\0') ? se.auxkilldata : "" ); - strcat( buf, scratch ); - needs_damage = true; - break; - - case KILLED_BY_LEAVING: - if (se.num_runes > 0) - strcat( buf, "Got out of the dungeon" ); - else - strcat( buf, "Got out of the dungeon alive!" ); - break; - - case KILLED_BY_WINNING: - strcat( buf, "Escaped with the Orb" ); - if (se.num_runes < 1) - strcat( buf, "!" ); - break; - - case KILLED_BY_QUITTING: - strcat( buf, "Quit the game" ); - break; - - case KILLED_BY_DRAINING: - strcat( buf, "Was drained of all life" ); - break; - - case KILLED_BY_STARVATION: - strcat( buf, "Starved to death" ); - break; - - case KILLED_BY_FREEZING: // refrigeration spell - strcat( buf, "Froze to death" ); - needs_damage = true; - break; - - case KILLED_BY_BURNING: // sticky flame - strcat( buf, "Burnt to a crisp" ); - needs_damage = true; - break; - - case KILLED_BY_WILD_MAGIC: - if (se.auxkilldata[0] == '\0') - strcat( buf, "Killed by wild magic" ); - else - { - // A lot of sources for this case... some have "by" already. - snprintf( scratch, sizeof(scratch), "Killed %s%s", - (strncmp( se.auxkilldata, "by ", 3 ) != 0) ? "by " : "", - se.auxkilldata ); - - strcat( buf, scratch ); - } - - needs_damage = true; - break; - - case KILLED_BY_XOM: // only used for old Xom kills - strcat( buf, "It was good that Xom killed them" ); - needs_damage = true; - break; - - case KILLED_BY_STATUE: - strcat( buf, "Killed by a statue" ); - needs_damage = true; - break; - - case KILLED_BY_ROTTING: - strcat( buf, "Rotted away" ); - break; - - case KILLED_BY_TARGETTING: - strcat( buf, "Killed themselves with bad targeting" ); - needs_damage = true; - break; - - case KILLED_BY_SPORE: - strcat( buf, "Killed by an exploding spore" ); - needs_damage = true; - break; - - case KILLED_BY_TSO_SMITING: - strcat( buf, "Smote by The Shining One" ); - needs_damage = true; - break; - - case KILLED_BY_PETRIFICATION: - strcat( buf, "Turned to stone" ); - break; - - case KILLED_BY_SHUGGOTH: - strcat( buf, "Eviscerated by a hatching shuggoth" ); - needs_damage = true; - break; - - case KILLED_BY_SOMETHING: - strcat( buf, "Died" ); - break; - - case KILLED_BY_FALLING_DOWN_STAIRS: - strcat( buf, "Fell down a flight of stairs" ); - needs_damage = true; - break; - - case KILLED_BY_ACID: - strcat( buf, "Splashed by acid" ); - needs_damage = true; - break; - - default: - strcat( buf, "Nibbled to death by software bugs" ); - break; - } // end switch - - // TODO: Eventually, get rid of "..." for cases where the text fits. - if (verbose) - { - bool done_damage = false; // paranoia - - if (needs_damage && se.damage > 0) - { - snprintf( scratch, INFO_SIZE, " (%d damage)", se.damage ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - needs_damage = false; - done_damage = true; - } - - if ((se.death_type == KILLED_BY_LEAVING - || se.death_type == KILLED_BY_WINNING) - && se.num_runes > 0) - { - hiscore_newline( buf, line_count ); - - snprintf( scratch, INFO_SIZE, "... %s %d rune%s", - (se.death_type == KILLED_BY_WINNING) ? "and" : "with", - se.num_runes, (se.num_runes > 1) ? "s" : "" ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - - if (se.num_diff_runes > 1) - { - snprintf( scratch, INFO_SIZE, " (of %d types)", - se.num_diff_runes ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - } - - if (se.death_time > 0 - && !hiscore_same_day( se.birth_time, se.death_time )) - { - strcat( buf, " on " ); - hiscore_date_string( se.death_time, scratch ); - strcat( buf, scratch ); - } - - strcat( buf, "!" ); - hiscore_newline( buf, line_count ); - } - else if (se.death_type != KILLED_BY_QUITTING) - { - hiscore_newline( buf, line_count ); - - if (se.death_type == KILLED_BY_MONSTER && se.auxkilldata[0]) - { - snprintf(scratch, INFO_SIZE, "... wielding %s", se.auxkilldata); - strncat(buf, scratch, HIGHSCORE_SIZE); - needs_damage = true; - } - else if (needs_beam_cause_line) - { - strcat( buf, (is_vowel( se.auxkilldata[0] )) ? "... with an " - : "... with a " ); - strcat( buf, se.auxkilldata ); - needs_damage = true; - } - else if (needs_called_by_monster_line) - { - snprintf( scratch, sizeof(scratch), "... called down by %s", - se.death_source_name ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - needs_damage = true; - } - - if (needs_damage && !done_damage) - { - if (se.damage > 0) - { - snprintf( scratch, INFO_SIZE, " (%d damage)", se.damage ); - strncat( buf, scratch, HIGHSCORE_SIZE ); - hiscore_newline( buf, line_count ); - } - } - } - } - - if (se.death_type == KILLED_BY_LEAVING - || se.death_type == KILLED_BY_WINNING) - { - // TODO: strcat "after reaching level %d"; for LEAVING - if (!verbose) - { - if (se.num_runes > 0) - strcat( buf, "!" ); - - hiscore_newline( buf, line_count ); - } - } - else - { - if (verbose && se.death_type != KILLED_BY_QUITTING) - strcat( buf, "..." ); - - if (se.level_type == LEVEL_ABYSS) - strcat( buf, " in the Abyss" ); - else if (se.level_type == LEVEL_PANDEMONIUM) - strcat( buf, " in Pandemonium" ); - else if (se.level_type == LEVEL_LABYRINTH) - strcat( buf, " in a labyrinth" ); - else - { - switch (se.branch) - { - case BRANCH_ECUMENICAL_TEMPLE: - strcat( buf, " in the Ecumenical Temple" ); - break; - case BRANCH_HALL_OF_BLADES: - strcat( buf, " in the Hall of Blades" ); - break; - case BRANCH_VESTIBULE_OF_HELL: - strcat( buf, " in the Vestibule" ); - break; - - case BRANCH_DIS: - strcat( buf, " on Dis" ); - break; - case BRANCH_GEHENNA: - strcat( buf, " on Gehenna" ); - break; - case BRANCH_COCYTUS: - strcat( buf, " on Cocytus" ); - break; - case BRANCH_TARTARUS: - strcat( buf, " on Tartarus" ); - break; - case BRANCH_ORCISH_MINES: - strcat( buf, " on Orcish Mines" ); - break; - case BRANCH_HIVE: - strcat( buf, " on Hive" ); - break; - case BRANCH_LAIR: - strcat( buf, " on Lair" ); - break; - case BRANCH_SLIME_PITS: - strcat( buf, " on Slime Pits" ); - break; - case BRANCH_VAULTS: - strcat( buf, " on Vault" ); - break; - case BRANCH_CRYPT: - strcat( buf, " on Crypt" ); - break; - case BRANCH_HALL_OF_ZOT: - strcat( buf, " on Hall of Zot" ); - break; - case BRANCH_SNAKE_PIT: - strcat( buf, " on Snake Pit" ); - break; - case BRANCH_ELVEN_HALLS: - strcat( buf, " on Elven Halls" ); - break; - case BRANCH_TOMB: - strcat( buf, " on Tomb" ); - break; - case BRANCH_SWAMP: - strcat( buf, " on Swamp" ); - break; - case BRANCH_MAIN_DUNGEON: - strcat( buf, " on Dungeon" ); - break; - } - - if (se.branch != BRANCH_VESTIBULE_OF_HELL - && se.branch != BRANCH_ECUMENICAL_TEMPLE - && se.branch != BRANCH_HALL_OF_BLADES) - { - snprintf( scratch, sizeof(scratch), " Level %d", se.dlvl ); - strcat( buf, scratch ); - } - } - - if (verbose && se.death_time - && !hiscore_same_day( se.birth_time, se.death_time )) - { - strcat( buf, " on " ); - hiscore_date_string( se.death_time, scratch ); - strcat( buf, scratch ); - } - - strcat( buf, "." ); - hiscore_newline( buf, line_count ); - } // endif - killed by winning - - if (verbose) - { - if (se.real_time > 0) - { - char username[80] = "The"; - char tmp[80]; - -#ifdef MULTIUSER - if (se.uid > 0) - { - struct passwd *pw_entry = getpwuid( se.uid ); - strncpy( username, pw_entry->pw_name, sizeof(username) ); - strncat( username, "'s", sizeof(username) ); - username[0] = toupper( username[0] ); - } -#endif - - make_time_string( se.real_time, tmp, sizeof(tmp) ); - - snprintf( scratch, INFO_SIZE, "%s game lasted %s (%ld turns).", - username, tmp, se.num_turns ); - - strncat( buf, scratch, HIGHSCORE_SIZE ); - hiscore_newline( buf, line_count ); - } - } - - return (line_count); + std::string line = + se.hiscore_line( + verbose? + scorefile_entry::DDV_VERBOSE + : scorefile_entry::DDV_NORMAL ); + strncpy(buf, line.c_str(), HIGHSCORE_SIZE); + buf[HIGHSCORE_SIZE - 1] = 0; } // -------------------------------------------------------------------------- @@ -1158,13 +388,10 @@ static bool unlock_file_handle( FILE *handle ) FILE *hs_open( const char *mode ) { -#ifdef SAVE_DIR_PATH - FILE *handle = fopen(SAVE_DIR_PATH "scores", mode); + std::string scores = Options.save_dir + "scores"; + FILE *handle = fopen(scores.c_str(), mode); #ifdef SHARED_FILES_CHMOD_PUBLIC - chmod(SAVE_DIR_PATH "scores", SHARED_FILES_CHMOD_PUBLIC); -#endif -#else - FILE *handle = fopen("scores", mode); + chmod(scores.c_str(), SHARED_FILES_CHMOD_PUBLIC); #endif #ifdef USE_FILE_LOCKING @@ -1199,104 +426,19 @@ void hs_close( FILE *handle, const char *mode ) #ifdef SHARED_FILES_CHMOD_PUBLIC if (stricmp(mode, "w") == 0) { - #ifdef SAVE_DIR_PATH - chmod(SAVE_DIR_PATH "scores", SHARED_FILES_CHMOD_PUBLIC); - #else - chmod("scores", SHARED_FILES_CHMOD_PUBLIC); - #endif + std::string scores = Options.save_dir + "scores"; + chmod(scores.c_str(), SHARED_FILES_CHMOD_PUBLIC); } #endif } -static void hs_init( struct scorefile_entry &dest ) -{ - // simple init - dest.version = 0; - dest.release = 0; - dest.points = -1; - dest.name[0] = '\0'; - dest.uid = 0; - dest.race = 0; - dest.cls = 0; - dest.lvl = 0; - dest.race_class_name[0] = '\0'; - dest.best_skill = 0; - dest.best_skill_lvl = 0; - dest.death_type = KILLED_BY_SOMETHING; - dest.death_source = 0; - dest.mon_num = 0; - dest.death_source_name[0] = '\0'; - dest.auxkilldata[0] = '\0'; - dest.dlvl = 0; - dest.level_type = 0; - dest.branch = 0; - dest.final_hp = -1; - dest.final_max_hp = -1; - dest.final_max_max_hp = -1; - dest.str = -1; - dest.intel = -1; - dest.dex = -1; - dest.damage = -1; - dest.god = -1; - dest.piety = -1; - dest.penance = -1; - dest.wiz_mode = 0; - dest.birth_time = 0; - dest.death_time = 0; - dest.real_time = -1; - dest.num_turns = -1; - dest.num_diff_runes = 0; - dest.num_runes = 0; -} - -void hs_copy(struct scorefile_entry &dest, struct scorefile_entry &src) -{ - // simple field copy -- assume src is well constructed. - - dest.version = src.version; - dest.release = src.release; - dest.points = src.points; - strcpy(dest.name, src.name); - dest.uid = src.uid; - dest.race = src.race; - dest.cls = src.cls; - dest.lvl = src.lvl; - strcpy(dest.race_class_name, src.race_class_name); - dest.best_skill = src.best_skill; - dest.best_skill_lvl = src.best_skill_lvl; - dest.death_type = src.death_type; - dest.death_source = src.death_source; - dest.mon_num = src.mon_num; - strcpy( dest.death_source_name, src.death_source_name ); - strcpy( dest.auxkilldata, src.auxkilldata ); - dest.dlvl = src.dlvl; - dest.level_type = src.level_type; - dest.branch = src.branch; - dest.final_hp = src.final_hp; - dest.final_max_hp = src.final_max_hp; - dest.final_max_max_hp = src.final_max_max_hp; - dest.str = src.str; - dest.intel = src.intel; - dest.dex = src.dex; - dest.damage = src.damage; - dest.god = src.god; - dest.piety = src.piety; - dest.penance = src.penance; - dest.wiz_mode = src.wiz_mode; - dest.birth_time = src.birth_time; - dest.death_time = src.death_time; - dest.real_time = src.real_time; - dest.num_turns = src.num_turns; - dest.num_diff_runes = src.num_diff_runes; - dest.num_runes = src.num_runes; -} - -bool hs_read( FILE *scores, struct scorefile_entry &dest ) +bool hs_read( FILE *scores, scorefile_entry &dest ) { char inbuf[200]; int c = EOF; - hs_init( dest ); + memset(inbuf, 0, sizeof inbuf); + dest.reset(); // get a character.. if (scores != NULL) @@ -1327,38 +469,46 @@ bool hs_read( FILE *scores, struct scorefile_entry &dest ) return true; } -static void hs_nextstring(char *&inbuf, char *dest) +static void hs_nextstring(char *&inbuf, char *dest, size_t destsize) { + ASSERT(destsize > 0); + char *p = dest; - if (*inbuf == '\0') + if (*inbuf == 0) { - *p = '\0'; + *p = 0; return; } // assume we're on a ':' - inbuf ++; - while(*inbuf != ':' && *inbuf != '\0') + if (*inbuf == ':') + inbuf++; + + while (*inbuf && *inbuf != ':' && (p - dest) < (int) destsize - 1) *p++ = *inbuf++; - *p = '\0'; + // If we ran out of buffer, discard the rest of the field. + while (*inbuf && *inbuf != ':') + inbuf++; + + *p = 0; } static int hs_nextint(char *&inbuf) { char num[20]; - hs_nextstring(inbuf, num); + hs_nextstring(inbuf, num, sizeof num); - return (num[0] == '\0' ? 0 : atoi(num)); + return (num[0] == 0 ? 0 : atoi(num)); } static long hs_nextlong(char *&inbuf) { char num[20]; - hs_nextstring(inbuf, num); + hs_nextstring(inbuf, num, sizeof num); - return (num[0] == '\0' ? 0 : atol(num)); + return (num[0] == 0 ? 0 : atol(num)); } static int val_char( char digit ) @@ -1371,7 +521,7 @@ static time_t hs_nextdate(char *&inbuf) char buff[20]; struct tm date; - hs_nextstring( inbuf, buff ); + hs_nextstring(inbuf, buff, sizeof buff); if (strlen( buff ) < 15) return (static_cast<time_t>(0)); @@ -1403,13 +553,13 @@ static void hs_parse_numeric(char *inbuf, struct scorefile_entry &se) se.points = hs_nextlong(inbuf); - hs_nextstring(inbuf, se.name); + hs_nextstring(inbuf, se.name, sizeof se.name); se.uid = hs_nextlong(inbuf); se.race = hs_nextint(inbuf); se.cls = hs_nextint(inbuf); - hs_nextstring(inbuf, se.race_class_name); + hs_nextstring(inbuf, se.race_class_name, sizeof se.race_class_name); se.lvl = hs_nextint(inbuf); se.best_skill = hs_nextint(inbuf); @@ -1418,15 +568,15 @@ static void hs_parse_numeric(char *inbuf, struct scorefile_entry &se) se.death_source = hs_nextint(inbuf); se.mon_num = hs_nextint(inbuf); - hs_nextstring(inbuf, se.death_source_name); + hs_nextstring(inbuf, se.death_source_name, sizeof se.death_source_name); // To try and keep the scorefile backwards compatible, // we'll branch on version > 4.0 to read the auxkilldata // text field. if (se.version == 4 && se.release >= 1) - hs_nextstring( inbuf, se.auxkilldata ); + hs_nextstring( inbuf, se.auxkilldata, sizeof se.auxkilldata ); else - se.auxkilldata[0] = '\0'; + se.auxkilldata[0] = 0; se.dlvl = hs_nextint(inbuf); se.level_type = hs_nextint(inbuf); @@ -1492,7 +642,7 @@ static void hs_parse_numeric(char *inbuf, struct scorefile_entry &se) se.num_runes = hs_nextint(inbuf); } -static void hs_write( FILE *scores, struct scorefile_entry &se ) +static void hs_write( FILE *scores, scorefile_entry &se ) { char buff[80]; // should be more than enough for date stamps @@ -1529,7 +679,7 @@ static void hs_write( FILE *scores, struct scorefile_entry &se ) static void hs_parse_string(char *inbuf, struct scorefile_entry &se) { /* old entries are of the following format (Brent introduced some - spacing at one point, we have to take this into account): + spacing at one point, we have to take this into account): // Actually, I believe it might have been Brian who added the spaces, // I was quite happy with the condensed version, given the 80 column @@ -1545,7 +695,7 @@ static void hs_parse_string(char *inbuf, struct scorefile_entry &se) 4. All numerics up to the comma are the clevel 5. From the comma, search for known fixed substrings and translate to death_type. Leave death source = 0 for old - scores, and just copy in the monster name. + scores, and just copy in the monster name. 6. Look for the branch type (again, substring search for fixed strings) and level. @@ -1554,40 +704,50 @@ static void hs_parse_string(char *inbuf, struct scorefile_entry &se) */ char scratch[80]; + const int inlen = strlen(inbuf); + char *start = inbuf; // 1. get score - hs_parse_generic_2(inbuf, scratch, "0123456789"); + hs_parse_generic_2(inbuf, scratch, sizeof scratch, "0123456789"); se.version = 0; // version # of converted score se.release = 0; se.points = atoi(scratch); // 2. get name - hs_parse_generic_1(inbuf, scratch, "-"); + hs_parse_generic_1(inbuf, scratch, sizeof scratch, "-"); hs_stripblanks(scratch); - strcpy(se.name, scratch); + strncpy(se.name, scratch, sizeof se.name); + se.name[ sizeof(se.name) - 1 ] = 0; // 3. get race, class - inbuf++; // skip '-' - hs_parse_generic_1(inbuf, scratch, "0123456789"); + // skip '-' + if (++inbuf - start >= inlen) + return; + + hs_parse_generic_1(inbuf, scratch, sizeof scratch, "0123456789"); hs_stripblanks(scratch); - strcpy(se.race_class_name, scratch); + strncpy(se.race_class_name, scratch, sizeof se.race_class_name); + se.race_class_name[ sizeof(se.race_class_name) - 1 ] = 0; se.race = 0; se.cls = 0; // 4. get clevel - hs_parse_generic_2(inbuf, scratch, "0123456789"); + hs_parse_generic_2(inbuf, scratch, sizeof scratch, "0123456789"); se.lvl = atoi(scratch); // 4a. get wizard mode - hs_parse_generic_1(inbuf, scratch, ","); + hs_parse_generic_1(inbuf, scratch, sizeof scratch, ","); if (strstr(scratch, "Wiz") != NULL) se.wiz_mode = 1; else se.wiz_mode = 0; + // Skip comma + if (++inbuf - start >= inlen) + return; + // 5. get death type - inbuf++; // skip comma hs_search_death(inbuf, se); // 6. get branch, level @@ -1613,27 +773,56 @@ static void hs_parse_string(char *inbuf, struct scorefile_entry &se) se.num_turns = -1; se.num_runes = 0; se.num_diff_runes = 0; - se.auxkilldata[0] = '\0'; + se.auxkilldata[0] = 0; } -static void hs_parse_generic_1(char *&inbuf, char *outbuf, const char *stopvalues) +static void hs_parse_generic_1(char *&inbuf, char *outbuf, size_t outsz, const char *stopvalues) { + ASSERT(outsz > 0); + char *p = outbuf; - while(strchr(stopvalues, *inbuf) == NULL && *inbuf != '\0') + if (!*inbuf) + { + *p = 0; + return; + } + + while (strchr(stopvalues, *inbuf) == NULL + && *inbuf != 0 + && (p - outbuf) < (int) outsz - 1) *p++ = *inbuf++; - *p = '\0'; + while (strchr(stopvalues, *inbuf) == NULL + && *inbuf != 0) + inbuf++; + + *p = 0; } -static void hs_parse_generic_2(char *&inbuf, char *outbuf, const char *continuevalues) +static void hs_parse_generic_2( + char *&inbuf, char *outbuf, size_t outsz, const char *continuevalues) { + ASSERT(outsz > 0); + char *p = outbuf; - while(strchr(continuevalues, *inbuf) != NULL && *inbuf != '\0') + if (!*inbuf) + { + *p = 0; + return; + } + + while (strchr(continuevalues, *inbuf) != NULL + && *inbuf + && (p - outbuf) < (int) outsz - 1) *p++ = *inbuf++; - *p = '\0'; + while (strchr(continuevalues, *inbuf) != NULL + && *inbuf) + inbuf++; + + *p = 0; } static void hs_stripblanks(char *buf) @@ -1644,16 +833,14 @@ static void hs_stripblanks(char *buf) // strip leading while(*p == ' ') p++; - while(*p != '\0') + + while(*p != 0 && p != q) *q++ = *p++; - *q-- = '\0'; + *q-- = 0; // strip trailing - while(*q == ' ') - { - *q = '\0'; - q--; - } + while (q >= buf && *q == ' ') + *q-- = 0; } static void hs_search_death(char *inbuf, struct scorefile_entry &se) @@ -1670,6 +857,8 @@ static void hs_search_death(char *inbuf, struct scorefile_entry &se) se.death_type = KILLED_BY_BEAM; else if (strstr(inbuf, "took a swim in molten lava") != NULL) se.death_type = KILLED_BY_LAVA; + else if (strstr(inbuf, "asphyxiated")) + se.death_type = KILLED_BY_CURARE; else if (strstr(inbuf, "soaked and fell apart") != NULL) { se.death_type = KILLED_BY_WATER; @@ -1715,13 +904,15 @@ static void hs_search_death(char *inbuf, struct scorefile_entry &se) se.death_type = KILLED_BY_TSO_SMITING; else if (strstr(inbuf, "turned to stone") != NULL) se.death_type = KILLED_BY_PETRIFICATION; - else if (strstr(inbuf, "eviscerated by a hatching") != NULL) - se.death_type = KILLED_BY_SHUGGOTH; + else if (strstr(inbuf, "melted into a puddle") != NULL) + se.death_type = KILLED_BY_MELTING; + else if (strstr(inbuf, "bled to death") != NULL) + se.death_type = KILLED_BY_BLEEDING; // whew! - // now, if we're still KILLED_BY_MONSTER, make sure that there is - // a "killed by" somewhere, or else we're setting it to UNKNOWN. + // now, if we're still KILLED_BY_MONSTER, make sure that there is + // a "killed by" somewhere, or else we're setting it to UNKNOWN. if (se.death_type == KILLED_BY_MONSTER) { if (strstr(inbuf, "killed by") == NULL) @@ -1731,21 +922,31 @@ static void hs_search_death(char *inbuf, struct scorefile_entry &se) // set some fields se.death_source = 0; se.mon_num = 0; - strcpy(se.death_source_name, ""); + *se.death_source_name = 0; // now try to pull the monster out. + // [dshaligram] Holy brain damage, Batman. if (se.death_type == KILLED_BY_MONSTER || se.death_type == KILLED_BY_BEAM) { char *p = strstr(inbuf, " by "); - p += 4; - char *q = strstr(inbuf, " on "); - if (q == NULL) - q = strstr(inbuf, " in "); - char *d = se.death_source_name; - while(p != q) - *d++ = *p++; - - *d = '\0'; + if (p) + { + p += 4; + char *q = strstr(inbuf, " on "); + if (q == NULL) + q = strstr(inbuf, " in "); + + if (q && q > p) + { + char *d = se.death_source_name; + const int maxread = sizeof(se.death_source_name) - 1; + + while (p < q && (d - se.death_source_name) < maxread) + *d++ = *p++; + + *d = 0; + } + } } } @@ -1758,7 +959,8 @@ static void hs_search_where(char *inbuf, struct scorefile_entry &se) se.dlvl = 0; // early out - if (se.death_type == KILLED_BY_LEAVING || se.death_type == KILLED_BY_WINNING) + if (se.death_type == KILLED_BY_LEAVING + || se.death_type == KILLED_BY_WINNING) return; // here we go again. @@ -1780,12 +982,12 @@ static void hs_search_where(char *inbuf, struct scorefile_entry &se) return; } - // from here, we have branch and level. + // from here, we have branch and level. char *p = strstr(inbuf, "on L"); if (p != NULL) { p += 4; - hs_parse_generic_2(p, scratch, "0123456789"); + hs_parse_generic_2(p, scratch, sizeof scratch, "0123456789"); se.dlvl = atoi( scratch ); } @@ -1825,3 +1027,1009 @@ static void hs_search_where(char *inbuf, struct scorefile_entry &se) else if (strstr(inbuf, "of the Swamp") != NULL) se.branch = BRANCH_SWAMP; } + +////////////////////////////////////////////////////////////////////////// +// scorefile_entry + +scorefile_entry::scorefile_entry(int dam, int dsource, int dtype, + const char *aux, bool death_cause_only) +{ + reset(); + + init_death_cause(dam, dsource, dtype, aux); + if (!death_cause_only) + init(); +} + +scorefile_entry::scorefile_entry() +{ + // Completely uninitialized, caveat user. + reset(); +} + +void scorefile_entry::init_death_cause(int dam, int dsrc, + int dtype, const char *aux) +{ + death_source = dsrc; + death_type = dtype; + damage = dam; + + // Set the default aux data value... + // If aux is passed in (ie for a trap), we'll default to that. + if (aux == NULL) + auxkilldata[0] = 0; + else + { + strncpy( auxkilldata, aux, ITEMNAME_SIZE ); + auxkilldata[ ITEMNAME_SIZE - 1 ] = 0; + } + + // for death by monster + if ((death_type == KILLED_BY_MONSTER || death_type == KILLED_BY_BEAM) + && death_source >= 0 && death_source < MAX_MONSTERS) + { + const monsters *monster = &menv[death_source]; + + if (monster->type > 0 || monster->type <= NUM_MONSTERS) + { + death_source = monster->type; + mon_num = monster->number; + + // Previously the weapon was only used for dancing weapons, + // but now we pass it in as a string through the scorefile + // entry to be appended in hiscores_format_single in long or + // medium scorefile formats. + // It still isn't used in monam for anything but flying weapons + // though + if (death_type == KILLED_BY_MONSTER + && monster->inv[MSLOT_WEAPON] != NON_ITEM) + { + // [ds] The highscore entry may be constructed while the player + // is alive (for notes), so make sure we don't reveal info we + // shouldn't. + if (you.hp <= 0) + { +#if HISCORE_WEAPON_DETAIL + set_ident_flags( mitm[monster->inv[MSLOT_WEAPON]], + ISFLAG_IDENT_MASK ); +#else + // changing this to ignore the pluses to keep it short + unset_ident_flags( mitm[monster->inv[MSLOT_WEAPON]], + ISFLAG_IDENT_MASK ); + + set_ident_flags( mitm[monster->inv[MSLOT_WEAPON]], + ISFLAG_KNOW_TYPE ); + + // clear "runed" description text to make shorter yet + set_equip_desc( mitm[monster->inv[MSLOT_WEAPON]], 0 ); +#endif + } + + // Setting this is redundant for dancing weapons, however + // we do care about the above indentification. -- bwr + if (monster->type != MONS_DANCING_WEAPON) + { + it_name( monster->inv[MSLOT_WEAPON], + DESC_NOCAP_A, + info ); + strncpy( auxkilldata, info, ITEMNAME_SIZE ); + auxkilldata[ ITEMNAME_SIZE - 1 ] = 0; + } + } + + strcpy( info, + monam( monster->number, monster->type, true, DESC_NOCAP_A, + monster->inv[MSLOT_WEAPON] ) ); + + strncpy( death_source_name, info, 40 ); + death_source_name[39] = 0; + } + } + else + { + death_source = death_source; + mon_num = 0; + death_source_name[0] = 0; + } +} + +void scorefile_entry::reset() +{ + // simple init + version = 0; + release = 0; + points = -1; + name[0] = 0; + uid = 0; + race = 0; + cls = 0; + lvl = 0; + race_class_name[0] = 0; + best_skill = 0; + best_skill_lvl = 0; + death_type = KILLED_BY_SOMETHING; + death_source = 0; + mon_num = 0; + death_source_name[0] = 0; + auxkilldata[0] = 0; + dlvl = 0; + level_type = 0; + branch = 0; + final_hp = -1; + final_max_hp = -1; + final_max_max_hp = -1; + str = -1; + intel = -1; + dex = -1; + damage = -1; + god = -1; + piety = -1; + penance = -1; + wiz_mode = 0; + birth_time = 0; + death_time = 0; + real_time = -1; + num_turns = -1; + num_diff_runes = 0; + num_runes = 0; +} + +void scorefile_entry::init() +{ + // Score file entry version: + // + // 4.0 - original versioned entry + // 4.1 - added real_time and num_turn fields + // 4.2 - stats and god info + + version = 4; + release = 2; + + strncpy( name, you.your_name, sizeof name ); + name[ sizeof(name) - 1 ] = 0; + +#ifdef MULTIUSER + uid = (int) getuid(); +#else + uid = 0; +#endif + + // do points first. + points = you.gold; + points += (you.experience * 7) / 10; + + //if (death_type == KILLED_BY_WINNING) points += points / 2; + //if (death_type == KILLED_BY_LEAVING) points += points / 10; + // these now handled by giving player the value of their inventory + char temp_id[4][50]; + + for (int d = 0; d < 4; d++) + { + for (int e = 0; e < 50; e++) + temp_id[d][e] = 1; + } + + FixedVector< int, NUM_RUNE_TYPES > rune_array; + + num_runes = 0; + num_diff_runes = 0; + + for (int i = 0; i < NUM_RUNE_TYPES; i++) + rune_array[i] = 0; + + // Calculate value of pack and runes when character leaves dungeon + if (death_type == KILLED_BY_LEAVING || death_type == KILLED_BY_WINNING) + { + for (int d = 0; d < ENDOFPACK; d++) + { + if (is_valid_item( you.inv[d] )) + { + points += item_value( you.inv[d], temp_id, true ); + + if (you.inv[d].base_type == OBJ_MISCELLANY + && you.inv[d].sub_type == MISC_RUNE_OF_ZOT) + { + if (rune_array[ you.inv[d].plus ] == 0) + num_diff_runes++; + + num_runes += you.inv[d].quantity; + rune_array[ you.inv[d].plus ] += you.inv[d].quantity; + } + } + } + + // Bonus for exploring different areas, not for collecting a + // huge stack of demonic runes in Pandemonium (gold value + // is enough for those). -- bwr + if (num_diff_runes >= 3) + points += ((num_diff_runes + 2) * (num_diff_runes + 2) * 1000); + } + + // Players will have a hard time getting 1/10 of this (see XP cap): + if (points > 99999999) + points = 99999999; + + race = you.species; + cls = you.char_class; + + race_class_name[0] = 0; + + lvl = you.experience_level; + best_skill = ::best_skill( SK_FIGHTING, NUM_SKILLS - 1, 99 ); + best_skill_lvl = you.skills[ best_skill ]; + + final_hp = you.hp; + final_max_hp = you.hp_max; + final_max_max_hp = you.hp_max + player_rotted(); + str = you.strength; + intel = you.intel; + dex = you.dex; + + god = you.religion; + if (you.religion != GOD_NO_GOD) + { + piety = you.piety; + penance = you.penance[you.religion]; + } + + // main dungeon: level is simply level + dlvl = you.your_level + 1; + switch (you.where_are_you) + { + case BRANCH_ORCISH_MINES: + case BRANCH_HIVE: + case BRANCH_LAIR: + case BRANCH_SLIME_PITS: + case BRANCH_VAULTS: + case BRANCH_CRYPT: + case BRANCH_HALL_OF_BLADES: + case BRANCH_HALL_OF_ZOT: + case BRANCH_ECUMENICAL_TEMPLE: + case BRANCH_SNAKE_PIT: + case BRANCH_ELVEN_HALLS: + case BRANCH_TOMB: + case BRANCH_SWAMP: + dlvl = you.your_level - you.branch_stairs[you.where_are_you - 10]; + break; + + case BRANCH_DIS: + case BRANCH_GEHENNA: + case BRANCH_VESTIBULE_OF_HELL: + case BRANCH_COCYTUS: + case BRANCH_TARTARUS: + case BRANCH_INFERNO: + case BRANCH_THE_PIT: + dlvl = you.your_level - 26; + break; + } + + branch = you.where_are_you; // no adjustments necessary. + level_type = you.level_type; // pandemonium, labyrinth, dungeon.. + + birth_time = you.birth_time; // start time of game + death_time = time( NULL ); // end time of game + + if (you.real_time != -1) + real_time = you.real_time + (death_time - you.start_time); + else + real_time = -1; + + num_turns = you.num_turns; + +#ifdef WIZARD + wiz_mode = (you.wizard ? 1 : 0); +#else + wiz_mode = 0; +#endif +} + +std::string scorefile_entry::hiscore_line(death_desc_verbosity verbosity) const +{ + std::string line = character_description(verbosity); + line += death_description(verbosity); + line += death_place(verbosity); + line += game_time(verbosity); + + return (line); +} + +std::string scorefile_entry::game_time(death_desc_verbosity verbosity) const +{ + std::string line; + + if (verbosity == DDV_VERBOSE) + { + if (real_time > 0) + { + char username[80] = "The"; + char scratch[INFO_SIZE]; + char tmp[80]; + +#ifdef MULTIUSER + if (uid > 0) + { + struct passwd *pw_entry = getpwuid( uid ); + strncpy( username, pw_entry->pw_name, sizeof(username) ); + strncat( username, "'s", sizeof(username) ); + username[0] = toupper( username[0] ); + } +#endif + + make_time_string( real_time, tmp, sizeof(tmp) ); + + snprintf( scratch, INFO_SIZE, "%s game lasted %s (%ld turns).", + username, tmp, num_turns ); + + line += scratch; + line += hiscore_newline_string(); + } + } + + return (line); +} + +const char *scorefile_entry::damage_verb() const +{ + // GDL: here's an example of using final_hp. Verbiage could be better. + // bwr: changed "blasted" since this is for melee + return (final_hp > -6) ? "Slain" : + (final_hp > -14) ? "Mangled" : + (final_hp > -22) ? "Demolished" : + "Annihilated"; +} + +const char *scorefile_entry::death_source_desc() const +{ + if (death_type != KILLED_BY_MONSTER && death_type != KILLED_BY_BEAM) + return (""); + + return (*death_source_name? + death_source_name + : monam( mon_num, death_source, true, DESC_PLAIN ) ); +} + +std::string scorefile_entry::damage_string(bool terse) const +{ + char scratch[50]; + snprintf( scratch, sizeof scratch, "(%d%s)", damage, + terse? "" : " damage" ); + return (scratch); +} + +std::string scorefile_entry::strip_article_a(const std::string &s) const +{ + if (s.find("a ") == 0) + return (s.substr(2)); + else if (s.find("an ") == 0) + return (s.substr(3)); + return (s); +} + +std::string scorefile_entry::terse_missile_cause() const +{ + std::string cause; + std::string aux = auxkilldata; + + std::string monster_prefix = " by "; + // We're looking for Shot with a%s %s by %s/ Hit by a%s %s thrown by %s + std::string::size_type by = aux.rfind(monster_prefix); + if (by == std::string::npos) + return ("???"); + + std::string mcause = aux.substr(by + monster_prefix.length()); + mcause = strip_article_a(mcause); + + std::string missile; + const std::string pre_post[][2] = { + { "Shot with a", " by " }, + { "Hit by a", " thrown by " } + }; + + for (unsigned i = 0; i < sizeof(pre_post) / sizeof(*pre_post); ++i) + { + if (aux.find(pre_post[i][0]) != 0) + continue; + + std::string::size_type end = aux.rfind(pre_post[i][1]); + if (end == std::string::npos) + continue; + + int istart = pre_post[i][0].length(); + int nchars = end - istart; + missile = aux.substr(istart, nchars); + + // Was this prefixed by "an"? + if (missile.find("n ") == 0) + missile = missile.substr(2); + } + + if (missile.length()) + mcause += "/" + missile; + + return (mcause); +} + +std::string scorefile_entry::terse_beam_cause() const +{ + std::string cause = auxkilldata; + if (cause.find("by ") == 0 || cause.find("By ") == 0) + cause = cause.substr(3); + return (cause); +} + +std::string scorefile_entry::terse_wild_magic() const +{ + return terse_beam_cause(); +} + +std::string scorefile_entry::terse_trap() const +{ + std::string trap = *auxkilldata? std::string(auxkilldata) + " trap" + : "trap"; + if (trap.find("n ") == 0) + trap = trap.substr(2); + trim_string(trap); + + return (trap); +} + +std::string scorefile_entry::single_cdesc() const +{ + char scratch[INFO_SIZE]; + char buf[INFO_SIZE]; + + if (!*race_class_name) + { + snprintf( scratch, sizeof(scratch), "%s%s", + get_species_abbrev( race ), get_class_abbrev( cls ) ); + } + else + { + strncpy( scratch, race_class_name, sizeof scratch ); + scratch[ sizeof(scratch) - 1 ] = 0; + } + + std::string scname = name; + if (scname.length() > 10) + scname = scname.substr(0, 10); + + snprintf( buf, sizeof buf, + "%8ld %-10s %s-%02d%s", points, scname.c_str(), + scratch, lvl, (wiz_mode == 1) ? "W" : "" ); + + return (buf); +} + +std::string +scorefile_entry::character_description(death_desc_verbosity verbosity) const +{ + bool single = verbosity == DDV_TERSE || verbosity == DDV_ONELINE; + + if (single) + return single_cdesc(); + + bool verbose = verbosity == DDV_VERBOSE; + char scratch[INFO_SIZE]; + char buf[INFO_SIZE]; + + std::string desc; + // Please excuse the following bit of mess in the name of flavour ;) + if (verbose) + { + strncpy( scratch, skill_title( best_skill, best_skill_lvl, + race, str, dex, god ), + INFO_SIZE ); + + snprintf( buf, HIGHSCORE_SIZE, "%8ld %s the %s (level %d", + points, name, scratch, lvl ); + + desc = buf; + } + else + { + snprintf( buf, HIGHSCORE_SIZE, "%8ld %s the %s %s (level %d", + points, name, species_name(race, lvl), + get_class_name(cls), lvl ); + desc = buf; + } + + if (final_max_max_hp > 0) // as the other two may be negative + { + snprintf( scratch, INFO_SIZE, ", %d/%d", final_hp, final_max_hp ); + desc += scratch; + + if (final_max_hp < final_max_max_hp) + { + snprintf( scratch, INFO_SIZE, " (%d)", final_max_max_hp ); + desc += scratch; + } + + desc += " HPs"; + } + + desc += wiz_mode? ") *WIZ*" : ")"; + desc += hiscore_newline_string(); + + if (verbose) + { + const char *const srace = species_name( race, lvl ); + + snprintf( scratch, INFO_SIZE, "Began as a%s %s %s", + is_vowel(*srace) ? "n" : "", srace, get_class_name(cls) ); + desc += scratch; + + if (birth_time > 0) + { + desc += " on "; + hiscore_date_string( birth_time, scratch ); + desc += scratch; + } + + desc += "."; + desc += hiscore_newline_string(); + + if (race != SP_DEMIGOD && god != -1) + { + if (god == GOD_XOM) + { + snprintf( scratch, INFO_SIZE, "Was a %sPlaything of Xom.", + (lvl >= 20) ? "Favourite " : "" ); + + desc += scratch; + desc += hiscore_newline_string(); + } + else if (god != GOD_NO_GOD) + { + // Not exactly the same as the religon screen, but + // good enough to fill this slot for now. + snprintf( scratch, INFO_SIZE, "Was %s of %s%s", + (piety > 160) ? "the Champion" : + (piety >= 120) ? "a High Priest" : + (piety >= 100) ? "an Elder" : + (piety >= 75) ? "a Priest" : + (piety >= 50) ? "a Believer" : + (piety >= 30) ? "a Follower" + : "an Initiate", + god_name( god ), + (penance > 0) ? " (penitent)." : "." ); + + desc += scratch; + desc += hiscore_newline_string(); + } + } + } + + return (desc); +} + +std::string scorefile_entry::death_place(death_desc_verbosity verbosity) const +{ + bool verbose = verbosity == DDV_VERBOSE; + std::string place; + + if (death_type == KILLED_BY_LEAVING || death_type == KILLED_BY_WINNING) + return (""); + + char scratch[ INFO_SIZE ]; + + if (verbosity == DDV_ONELINE || verbosity == DDV_TERSE) + { + snprintf( scratch, sizeof scratch, " (%s)", + place_name(get_packed_place(branch, dlvl, level_type), + false, true).c_str()); + return (scratch); + } + + if (verbose && death_type != KILLED_BY_QUITTING) + place += "..."; + + // where did we die? + std::string placename = + place_name(get_packed_place(branch, dlvl, level_type), + true, true); + + // add appropriate prefix + if (placename.find("Level") == 0) + place += " on "; + else + place += " in "; + + place += placename; + + if (verbose && death_time + && !hiscore_same_day( birth_time, death_time )) + { + place += " on "; + hiscore_date_string( death_time, scratch ); + place += scratch; + } + + place += "."; + place += hiscore_newline_string(); + + return (place); +} + +std::string +scorefile_entry::death_description(death_desc_verbosity verbosity) const +{ + bool needs_beam_cause_line = false; + bool needs_called_by_monster_line = false; + bool needs_damage = false; + + bool terse = verbosity == DDV_TERSE; + bool verbose = verbosity == DDV_VERBOSE; + bool oneline = verbosity == DDV_ONELINE; + + char scratch[INFO_SIZE]; + + std::string desc; + + if (oneline) + desc = " "; + + switch (death_type) + { + case KILLED_BY_MONSTER: + if (terse) + desc += death_source_desc(); + else if (oneline) + desc += std::string("slain by ") + death_source_desc(); + else + { + snprintf( scratch, sizeof scratch, "%s by %s", + damage_verb(), + death_source_desc() ); + + desc += scratch; + } + + // put the damage on the weapon line if there is one + if (auxkilldata[0] == 0) + needs_damage = true; + break; + + case KILLED_BY_POISON: + desc += terse? "poison" : "Succumbed to poison"; + break; + + case KILLED_BY_CLOUD: + if (auxkilldata[0] == 0) + desc += terse? "cloud" : "Engulfed by a cloud"; + else + { + snprintf( scratch, sizeof(scratch), "%scloud of %s", + terse? "" : "Engulfed by a ", + auxkilldata ); + desc += scratch; + } + needs_damage = true; + break; + + case KILLED_BY_BEAM: + if (oneline) + { + // keeping this short to leave room for the deep elf spellcasters: + snprintf( scratch, sizeof(scratch), "%s by ", + range_type_verb( auxkilldata ) ); + desc += scratch; + desc += death_source_desc(); + } + else if (isupper( auxkilldata[0] )) // already made (ie shot arrows) + { + // If terse we have to parse the information from the string. + // Darn it to heck. + desc += terse? terse_missile_cause() : auxkilldata; + needs_damage = true; + } + else if (verbose && strncmp( auxkilldata, "by ", 3 ) == 0) + { + // "by" is used for priest attacks where the effect is indirect + // in verbose format we have another line for the monster + needs_called_by_monster_line = true; + snprintf( scratch, sizeof(scratch), "Killed %s", auxkilldata ); + desc += scratch; + } + else + { + // Note: This is also used for the "by" cases in non-verbose + // mode since listing the monster is more imporatant. + if (!terse) + desc += "Killed from afar by "; + + desc += death_source_desc(); + + if (*auxkilldata) + needs_beam_cause_line = true; + + needs_damage = true; + } + break; + + case KILLED_BY_CURARE: + desc += terse? "asphyx" : "Asphyxiated"; + break; + + case KILLED_BY_LAVA: + if (terse) + desc += "lava"; + else + { + if (race == SP_MUMMY) + desc += "Turned to ash by lava"; + else + desc += "Took a swim in molten lava"; + } + break; + + case KILLED_BY_WATER: + if (race == SP_MUMMY) + desc += terse? "fell apart" : "Soaked and fell apart"; + else + desc += terse? "drowned" : "Drowned"; + break; + + case KILLED_BY_STUPIDITY: + desc += terse? "stupidity" : "Forgot to breathe"; + break; + + case KILLED_BY_WEAKNESS: + desc += terse? "collapsed " : "Collapsed under their own weight"; + break; + + case KILLED_BY_CLUMSINESS: + desc += terse? "clumsiness" : "Slipped on a banana peel"; + break; + + case KILLED_BY_TRAP: + if (terse) + desc += terse_trap(); + else + { + snprintf( scratch, sizeof(scratch), "Killed by triggering a%s trap", + (*auxkilldata) ? auxkilldata : "" ); + desc += scratch; + } + needs_damage = true; + break; + + case KILLED_BY_LEAVING: + if (terse) + desc += "left"; + else + { + if (num_runes > 0) + desc += "Got out of the dungeon"; + else + desc += "Got out of the dungeon alive"; + } + break; + + case KILLED_BY_WINNING: + desc += terse? "escaped" : "Escaped with the Orb"; + if (num_runes < 1) + desc += "!"; + break; + + case KILLED_BY_QUITTING: + desc += terse? "quit" : "Quit the game"; + break; + + case KILLED_BY_DRAINING: + desc += terse? "drained" : "Was drained of all life"; + break; + + case KILLED_BY_STARVATION: + desc += terse? "starvation" : "Starved to death"; + break; + + case KILLED_BY_FREEZING: // refrigeration spell + desc += terse? "frozen" : "Froze to death"; + needs_damage = true; + break; + + case KILLED_BY_BURNING: // sticky flame + desc += terse? "burnt" : "Burnt to a crisp"; + needs_damage = true; + break; + + case KILLED_BY_WILD_MAGIC: + if (!*auxkilldata) + desc += terse? "wild magic" : "Killed by wild magic"; + else + { + if (terse) + desc += terse_wild_magic(); + else + { + // A lot of sources for this case... some have "by" already. + snprintf( scratch, sizeof(scratch), "Killed %s%s", + (strncmp( auxkilldata, "by ", 3 ) != 0) ? "by " : "", + auxkilldata ); + desc += scratch; + } + } + + needs_damage = true; + break; + + case KILLED_BY_XOM: // only used for old Xom kills + desc += terse? "xom" : "Killed for Xom's enjoyment"; + needs_damage = true; + break; + + case KILLED_BY_STATUE: + desc += terse? "statue" : "Killed by a statue"; + needs_damage = true; + break; + + case KILLED_BY_ROTTING: + desc += terse? "rotting" : "Rotted away"; + break; + + case KILLED_BY_TARGETTING: + desc += terse? "shot self" : "Killed themselves with bad targeting"; + needs_damage = true; + break; + + case KILLED_BY_SPORE: + desc += terse? "spore" : "Killed by an exploding spore"; + needs_damage = true; + break; + + case KILLED_BY_TSO_SMITING: + desc += terse? "smote by Shining One" : "Smote by The Shining One"; + needs_damage = true; + break; + + case KILLED_BY_PETRIFICATION: + desc += terse? "petrified" : "Turned to stone"; + break; + + case KILLED_BY_MELTING: + desc += terse? "melted" : " melted into a puddle"; + break; + + case KILLED_BY_BLEEDING: + desc += terse? "bleeding" : " bled to death"; + break; + + case KILLED_BY_SOMETHING: + if (auxkilldata) + desc += auxkilldata; + else + desc += terse? "died" : "Died"; + needs_damage = true; + break; + + case KILLED_BY_FALLING_DOWN_STAIRS: + desc += terse? "fell downstairs" : "Fell down a flight of stairs"; + needs_damage = true; + break; + + case KILLED_BY_ACID: + desc += terse? "acid" : "Splashed by acid"; + needs_damage = true; + break; + + default: + desc += terse? "program bug" : "Nibbled to death by software bugs"; + break; + } // end switch + + if (oneline && desc.length() > 2) + desc[1] = tolower(desc[1]); + + // TODO: Eventually, get rid of "..." for cases where the text fits. + if (terse) + { + if (death_type == KILLED_BY_MONSTER && *auxkilldata) + { + desc += "/"; + desc += strip_article_a(auxkilldata); + needs_damage = true; + } + else if (needs_beam_cause_line) + desc += "/" + terse_beam_cause(); + else if (needs_called_by_monster_line) + desc += death_source_name; + + if (needs_damage && damage > 0) + desc += " " + damage_string(true); + } + else if (verbose) + { + bool done_damage = false; // paranoia + + if (needs_damage && damage > 0) + { + desc += " " + damage_string(); + needs_damage = false; + done_damage = true; + } + + if ((death_type == KILLED_BY_LEAVING + || death_type == KILLED_BY_WINNING) + && num_runes > 0) + { + desc += hiscore_newline_string(); + + snprintf( scratch, INFO_SIZE, "... %s %d rune%s", + (death_type == KILLED_BY_WINNING) ? "and" : "with", + num_runes, (num_runes > 1) ? "s" : "" ); + desc += scratch; + + if (num_diff_runes > 1) + { + snprintf( scratch, INFO_SIZE, " (of %d types)", + num_diff_runes ); + desc += scratch; + } + + if (death_time > 0 + && !hiscore_same_day( birth_time, death_time )) + { + desc += " on "; + hiscore_date_string( death_time, scratch ); + desc += scratch; + } + + desc += "!"; + desc += hiscore_newline_string(); + } + else if (death_type != KILLED_BY_QUITTING) + { + desc += hiscore_newline_string(); + + if (death_type == KILLED_BY_MONSTER && auxkilldata[0]) + { + snprintf(scratch, INFO_SIZE, "... wielding %s", auxkilldata); + desc += scratch; + needs_damage = true; + } + else if (needs_beam_cause_line) + { + desc += (is_vowel( auxkilldata[0] )) ? "... with an " + : "... with a "; + desc += auxkilldata; + needs_damage = true; + } + else if (needs_called_by_monster_line) + { + snprintf( scratch, sizeof(scratch), "... called down by %s", + death_source_name ); + desc += scratch; + needs_damage = true; + } + + if (needs_damage && !done_damage && damage > 0) + desc += " " + damage_string(); + + if (needs_damage) + desc += hiscore_newline_string(); + } + } + + if (!oneline) + { + if (death_type == KILLED_BY_LEAVING + || death_type == KILLED_BY_WINNING) + { + // TODO: strcat "after reaching level %d"; for LEAVING + if (verbosity == DDV_NORMAL) + { + if (num_runes > 0) + desc += "!"; + } + desc += hiscore_newline_string(); + } + } + + if (terse) + { + trim_string(desc); + desc = strip_article_a(desc); + } + + return (desc); +} |