From 673bdae75485d14f759af597c3c62b99601f9a43 Mon Sep 17 00:00:00 2001 From: peterb12 Date: Thu, 21 Jul 2005 02:34:44 +0000 Subject: Initial revision git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@3 c06c8d41-db1a-0410-9941-cceddc491573 --- trunk/source/hiscores.cc | 1824 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1824 insertions(+) create mode 100644 trunk/source/hiscores.cc (limited to 'trunk/source/hiscores.cc') diff --git a/trunk/source/hiscores.cc b/trunk/source/hiscores.cc new file mode 100644 index 0000000000..cbdab9ed2b --- /dev/null +++ b/trunk/source/hiscores.cc @@ -0,0 +1,1824 @@ +/* + * File: highscore.cc + * Summary: deal with reading and writing of highscore file + * Written by: Gordon Lipford + * + * Change History (most recent first): + * + * <1> 16feb2001 gdl Created + */ + +/* + * ----------- MODIFYING THE PRINTED SCORE FORMAT --------------------- + * Do this at your leisure. Change hiscores_format_single() as much + * as you like. + * + * + * ----------- IF YOU MODIFY THE INTERNAL SCOREFILE FORMAT ------------ + * .. as defined by the struct 'scorefile_entry' .. + * You MUST change hs_copy(), hs_parse_numeric(), hs_parse_string(), + * and hs_write(). It's also a really good idea to change the + * version numbers assigned in ouch() so that Crawl can tell the + * difference between your new entry and previous versions. + * + * + * + */ + + +#include +#include +#include + +#include "AppHdr.h" +#include "externs.h" + +#include "hiscores.h" +#include "itemname.h" +#include "mon-util.h" +#include "player.h" +#include "religion.h" +#include "stuff.h" +#include "tags.h" +#include "view.h" + +#include "skills2.h" + +#ifdef MULTIUSER + // includes to get passwd file access: + #include + #include +#endif + +// enough memory allocated to snarf in the scorefile entries +static struct 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). +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 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_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); + +// file locking stuff +#ifdef USE_FILE_LOCKING +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 ) +{ + FILE *scores; + int i, total_entries; + bool inserted = false; + + // open highscore file (reading) -- note that NULL is not fatal! + scores = hs_open("r"); + + // read highscore file, inserting new entry at appropriate point, + for (i = 0; i < SCORE_FILE_ENTRIES; i++) + { + if (hs_read(scores, hs_list[i]) == false) + break; + + // compare points.. + if (ne.points >= hs_list[i].points && inserted == false) + { + newest_entry = i; // for later printing + inserted = true; + // copy read entry to i+1th position + // 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); + i++; + } else { + // copy new entry to current position + hs_copy(hs_list[i], ne); + } + } + } + + // 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); + i++; + } + + total_entries = i; + + // close so we can re-open for writing + hs_close(scores,"r"); + + // open highscore file (writing) -- NULL *is* fatal here. + scores = hs_open("w"); + if (scores == NULL) + { + perror("Entry not added - failure opening score file for writing."); + return; + } + + // write scorefile entries. + for (i = 0; i < total_entries; i++) + { + hs_write(scores, hs_list[i]); + } + + // close scorefile. + hs_close(scores, "w"); +} + +void hiscores_print_list( int display_count, int format ) +{ + FILE *scores; + int i, total_entries; + bool use_printf = (Options.sc_entries > 0); + + if (display_count <= 0) + display_count = SCORE_FILE_ENTRIES; + + // open highscore file (reading) + scores = hs_open("r"); + if (scores == NULL) + { + // will only happen from command line + puts( "No high scores." ); + return; + } + + // read highscore file + for (i = 0; i < SCORE_FILE_ENTRIES; i++) + { + if (hs_read( scores, hs_list[i] ) == false) + break; + } + total_entries = i; + + // close off + hs_close( scores, "r" ); + + if (!use_printf) + textcolor(LIGHTGREY); + + int start = (newest_entry > 10) ? newest_entry - 10: 0; + + if (start + display_count > total_entries) + start = total_entries - display_count; + + if (start < 0) + start = 0; + + const int finish = start + display_count; + + for (i = start; i < finish && i < total_entries; i++) + { + // check for recently added entry + if (i == newest_entry && !use_printf) + textcolor(YELLOW); + + // print position (tracked implicitly by order score file) + snprintf( info, INFO_SIZE, "%3d.", i + 1 ); + if (use_printf) + printf(info); + else + cprintf(info); + + // format the entry + if (format == SCORE_TERSE) + { + hiscores_format_single( info, hs_list[i] ); + // truncate if we want short format + info[75] = '\0'; + } + else + { + hiscores_format_single_long( info, hs_list[i], + (format == SCORE_VERBOSE) ); + } + + // print entry + strcat(info, EOL); + if(use_printf) + printf(info); + else + cprintf(info); + + if (i == newest_entry && !use_printf) + textcolor(LIGHTGREY); + } +} + +// Trying to supply an appropriate verb for the attack type. -- bwr +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 + || strncmp( aux, "Hit ", 4 ) == 0 // thrown + || strncmp( aux, "volley ", 7 ) == 0) // manticore spikes + { + return ("hit from afar"); + } + + return ("blasted"); // spells, wands +} + +void hiscores_format_single(char *buf, struct 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; +} + +static bool hiscore_same_day( time_t t1, time_t t2 ) +{ + struct tm *d1 = localtime( &t1 ); + const int year = d1->tm_year; + const int mon = d1->tm_mon; + const int day = d1->tm_mday; + + struct tm *d2 = localtime( &t2 ); + + return (d2->tm_mday == day && d2->tm_mon == mon && d2->tm_year == year); +} + +static void hiscore_date_string( time_t time, char buff[INFO_SIZE] ) +{ + struct tm *date = localtime( &time ); + + const char *mons[12] = { "Jan", "Feb", "Mar", "Apr", "May", "June", + "July", "Aug", "Sept", "Oct", "Nov", "Dec" }; + + snprintf( buff, INFO_SIZE, "%s %d, %d", mons[date->tm_mon], + date->tm_mday, date->tm_year + 1900 ); +} + +static void hiscore_newline( char *buf, int &line_count ) +{ + strncat( buf, EOL " ", HIGHSCORE_SIZE ); + line_count++; +} + +int hiscores_format_single_long( char *buf, struct 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); +} + +// -------------------------------------------------------------------------- +// BEGIN private functions +// -------------------------------------------------------------------------- + +// first, some file locking stuff for multiuser crawl +#ifdef USE_FILE_LOCKING + +static bool lock_file_handle( FILE *handle, int type ) +{ + struct flock lock; + int status; + + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = type; + +#ifdef USE_BLOCKING_LOCK + + status = fcntl( fileno( handle ), F_SETLKW, &lock ); + +#else + + for (int i = 0; i < 30; i++) + { + status = fcntl( fileno( handle ), F_SETLK, &lock ); + + // success + if (status == 0) + break; + + // known failure + if (status == -1 && (errno != EACCES && errno != EAGAIN)) + break; + + perror( "Problems locking file... retrying..." ); + delay( 1000 ); + } + +#endif + + return (status == 0); +} + +static bool unlock_file_handle( FILE *handle ) +{ + struct flock lock; + int status; + + lock.l_whence = SEEK_SET; + lock.l_start = 0; + lock.l_len = 0; + lock.l_type = F_UNLCK; + +#ifdef USE_BLOCKING_LOCK + + status = fcntl( fileno( handle ), F_SETLKW, &lock ); + +#else + + for (int i = 0; i < 30; i++) + { + status = fcntl( fileno( handle ), F_SETLK, &lock ); + + // success + if (status == 0) + break; + + // known failure + if (status == -1 && (errno != EACCES && errno != EAGAIN)) + break; + + perror( "Problems unlocking file... retrying..." ); + delay( 1000 ); + } + +#endif + + return (status == 0); +} + +#endif + + + +FILE *hs_open( const char *mode ) +{ +#ifdef SAVE_DIR_PATH + FILE *handle = fopen(SAVE_DIR_PATH "scores", mode); +#else + FILE *handle = fopen("scores", mode); +#endif + +#ifdef USE_FILE_LOCKING + int locktype = F_RDLCK; + if (stricmp(mode, "w") == 0) + locktype = F_WRLCK; + + if (handle && !lock_file_handle( handle, locktype )) + { + perror( "Could not lock scorefile... " ); + fclose( handle ); + handle = NULL; + } +#endif + return handle; +} + +void hs_close( FILE *handle, const char *mode ) +{ + UNUSED( mode ); + + if (handle == NULL) + return; + +#ifdef USE_FILE_LOCKING + unlock_file_handle( handle ); +#endif + + // actually close + fclose(handle); + +#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 + } +#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 ) +{ + char inbuf[200]; + int c = EOF; + + hs_init( dest ); + + // get a character.. + if (scores != NULL) + c = fgetc(scores); + + // check for NULL scores file or EOF + if (scores == NULL || c == EOF) + return false; + + // get a line - this is tricky. "Lines" come in three flavors: + // 1) old-style lines which were 80 character blocks + // 2) 4.0 pr1 through pr7 versions which were newline terminated + // 3) 4.0 pr8 and onwards which are 'current' ASCII format, and + // may exceed 80 characters! + + // put 'c' in first spot + inbuf[0] = c; + + if (fgets(&inbuf[1], (c==':') ? (sizeof(inbuf) - 2) : 81, scores) == NULL) + return false; + + // check type; lines starting with a colon are new-style scores. + if (c == ':') + hs_parse_numeric(inbuf, dest); + else + hs_parse_string(inbuf, dest); + + return true; +} + +static void hs_nextstring(char *&inbuf, char *dest) +{ + char *p = dest; + + if (*inbuf == '\0') + { + *p = '\0'; + return; + } + + // assume we're on a ':' + inbuf ++; + while(*inbuf != ':' && *inbuf != '\0') + *p++ = *inbuf++; + + *p = '\0'; +} + +static int hs_nextint(char *&inbuf) +{ + char num[20]; + hs_nextstring(inbuf, num); + + return (num[0] == '\0' ? 0 : atoi(num)); +} + +static long hs_nextlong(char *&inbuf) +{ + char num[20]; + hs_nextstring(inbuf, num); + + return (num[0] == '\0' ? 0 : atol(num)); +} + +static int val_char( char digit ) +{ + return (digit - '0'); +} + +static time_t hs_nextdate(char *&inbuf) +{ + char buff[20]; + struct tm date; + + hs_nextstring( inbuf, buff ); + + if (strlen( buff ) < 15) + return (static_cast(0)); + + date.tm_year = val_char( buff[0] ) * 1000 + val_char( buff[1] ) * 100 + + val_char( buff[2] ) * 10 + val_char( buff[3] ) - 1900; + + date.tm_mon = val_char( buff[4] ) * 10 + val_char( buff[5] ); + date.tm_mday = val_char( buff[6] ) * 10 + val_char( buff[7] ); + date.tm_hour = val_char( buff[8] ) * 10 + val_char( buff[9] ); + date.tm_min = val_char( buff[10] ) * 10 + val_char( buff[11] ); + date.tm_sec = val_char( buff[12] ) * 10 + val_char( buff[13] ); + date.tm_isdst = (buff[14] == 'D'); + + return (mktime( &date )); +} + +static void hs_parse_numeric(char *inbuf, struct scorefile_entry &se) +{ + se.version = hs_nextint(inbuf); + se.release = hs_nextint(inbuf); + + // this would be a good point to check for version numbers and branch + // appropriately + + // acceptable versions are 0 (converted from old hiscore format) and 4 + if (se.version != 0 && se.version != 4) + return; + + se.points = hs_nextlong(inbuf); + + hs_nextstring(inbuf, se.name); + + se.uid = hs_nextlong(inbuf); + se.race = hs_nextint(inbuf); + se.cls = hs_nextint(inbuf); + + hs_nextstring(inbuf, se.race_class_name); + + se.lvl = hs_nextint(inbuf); + se.best_skill = hs_nextint(inbuf); + se.best_skill_lvl = hs_nextint(inbuf); + se.death_type = hs_nextint(inbuf); + se.death_source = hs_nextint(inbuf); + se.mon_num = hs_nextint(inbuf); + + hs_nextstring(inbuf, 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 ); + else + se.auxkilldata[0] = '\0'; + + se.dlvl = hs_nextint(inbuf); + se.level_type = hs_nextint(inbuf); + se.branch = hs_nextint(inbuf); + + // Trying to fix some bugs that have been around since at + // least pr19, if not longer. From now on, dlvl should + // be calculated on death and need no further modification. + if (se.version < 4 || se.release < 2) + { + if (se.level_type == LEVEL_DUNGEON) + { + if (se.branch == BRANCH_MAIN_DUNGEON) + se.dlvl += 1; + else if (se.branch < BRANCH_ORCISH_MINES) // ie the hells + se.dlvl -= 1; + } + } + + se.final_hp = hs_nextint(inbuf); + if (se.version == 4 && se.release >= 2) + { + se.final_max_hp = hs_nextint(inbuf); + se.final_max_max_hp = hs_nextint(inbuf); + se.damage = hs_nextint(inbuf); + se.str = hs_nextint(inbuf); + se.intel = hs_nextint(inbuf); + se.dex = hs_nextint(inbuf); + se.god = hs_nextint(inbuf); + se.piety = hs_nextint(inbuf); + se.penance = hs_nextint(inbuf); + } + else + { + se.final_max_hp = -1; + se.final_max_max_hp = -1; + se.damage = -1; + se.str = -1; + se.intel = -1; + se.dex = -1; + se.god = -1; + se.piety = -1; + se.penance = -1; + } + + se.wiz_mode = hs_nextint(inbuf); + + se.birth_time = hs_nextdate(inbuf); + se.death_time = hs_nextdate(inbuf); + + if (se.version == 4 && se.release >= 2) + { + se.real_time = hs_nextint(inbuf); + se.num_turns = hs_nextint(inbuf); + } + else + { + se.real_time = -1; + se.num_turns = -1; + } + + se.num_diff_runes = hs_nextint(inbuf); + se.num_runes = hs_nextint(inbuf); +} + +static void hs_write( FILE *scores, struct scorefile_entry &se ) +{ + char buff[80]; // should be more than enough for date stamps + + se.version = 4; + se.release = 2; + + fprintf( scores, ":%d:%d:%ld:%s:%ld:%d:%d:%s:%d:%d:%d", + se.version, se.release, se.points, se.name, + se.uid, se.race, se.cls, se.race_class_name, se.lvl, + se.best_skill, se.best_skill_lvl ); + + // XXX: need damage + fprintf( scores, ":%d:%d:%d:%s:%s:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d:%d", + se.death_type, se.death_source, se.mon_num, + se.death_source_name, se.auxkilldata, + se.dlvl, se.level_type, se.branch, + se.final_hp, se.final_max_hp, se.final_max_max_hp, se.damage, + se.str, se.intel, se.dex, + se.god, se.piety, se.penance, se.wiz_mode ); + + make_date_string( se.birth_time, buff ); + fprintf( scores, ":%s", buff ); + + make_date_string( se.death_time, buff ); + fprintf( scores, ":%s", buff ); + + fprintf( scores, ":%ld:%ld:%d:%d:\n", + se.real_time, se.num_turns, se.num_diff_runes, se.num_runes ); +} +// ------------------------------------------------------------------------- +// functions dealing with old-style scorefile entries. +// ------------------------------------------------------------------------- + +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): + + // Actually, I believe it might have been Brian who added the spaces, + // I was quite happy with the condensed version, given the 80 column + // restriction. -- bwr + +6263 BoBo - DSD10 Wiz, killed by an acid blob on L1 of the Slime Pits. +5877 Aldus-DGM10, killed by a lethal dose of poison on L10. +5419 Yarf - Koa10, killed by a warg on L1 of the Mines. + + 1. All numerics up to the first non-numeric are the score + 2. All non '-' characters are the name. Strip spaces. + 3. All alphabetics up to the first numeric are race/class + 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. + 6. Look for the branch type (again, substring search for + fixed strings) and level. + + Very ugly and time consuming. + + */ + + char scratch[80]; + + // 1. get score + hs_parse_generic_2(inbuf, 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_stripblanks(scratch); + strcpy(se.name, scratch); + + // 3. get race, class + inbuf++; // skip '-' + hs_parse_generic_1(inbuf, scratch, "0123456789"); + hs_stripblanks(scratch); + strcpy(se.race_class_name, scratch); + se.race = 0; + se.cls = 0; + + // 4. get clevel + hs_parse_generic_2(inbuf, scratch, "0123456789"); + se.lvl = atoi(scratch); + + // 4a. get wizard mode + hs_parse_generic_1(inbuf, scratch, ","); + if (strstr(scratch, "Wiz") != NULL) + se.wiz_mode = 1; + else + se.wiz_mode = 0; + + // 5. get death type + inbuf++; // skip comma + hs_search_death(inbuf, se); + + // 6. get branch, level + hs_search_where(inbuf, se); + + // set things that can't be picked out of old scorefile entries + se.uid = 0; + se.best_skill = 0; + se.best_skill_lvl = 0; + se.final_hp = 0; + se.final_max_hp = -1; + se.final_max_max_hp = -1; + se.damage = -1; + se.str = -1; + se.intel = -1; + se.dex = -1; + se.god = -1; + se.piety = -1; + se.penance = -1; + se.birth_time = 0; + se.death_time = 0; + se.real_time = -1; + se.num_turns = -1; + se.num_runes = 0; + se.num_diff_runes = 0; + se.auxkilldata[0] = '\0'; +} + +static void hs_parse_generic_1(char *&inbuf, char *outbuf, const char *stopvalues) +{ + char *p = outbuf; + + while(strchr(stopvalues, *inbuf) == NULL && *inbuf != '\0') + *p++ = *inbuf++; + + *p = '\0'; +} + +static void hs_parse_generic_2(char *&inbuf, char *outbuf, const char *continuevalues) +{ + char *p = outbuf; + + while(strchr(continuevalues, *inbuf) != NULL && *inbuf != '\0') + *p++ = *inbuf++; + + *p = '\0'; +} + +static void hs_stripblanks(char *buf) +{ + char *p = buf; + char *q = buf; + + // strip leading + while(*p == ' ') + p++; + while(*p != '\0') + *q++ = *p++; + + *q-- = '\0'; + // strip trailing + while(*q == ' ') + { + *q = '\0'; + q--; + } +} + +static void hs_search_death(char *inbuf, struct scorefile_entry &se) +{ + // assume killed by monster + se.death_type = KILLED_BY_MONSTER; + + // sigh.. + if (strstr(inbuf, "killed by a lethal dose of poison") != NULL) + se.death_type = KILLED_BY_POISON; + else if (strstr(inbuf, "killed by a cloud") != NULL) + se.death_type = KILLED_BY_CLOUD; + else if (strstr(inbuf, "killed from afar by") != NULL) + 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, "soaked and fell apart") != NULL) + { + se.death_type = KILLED_BY_WATER; + se.race = SP_MUMMY; + } + else if (strstr(inbuf, "drowned") != NULL) + se.death_type = KILLED_BY_WATER; + else if (strstr(inbuf, "died of stupidity") != NULL) + se.death_type = KILLED_BY_STUPIDITY; + else if (strstr(inbuf, "too weak to continue adventuring") != NULL) + se.death_type = KILLED_BY_WEAKNESS; + else if (strstr(inbuf, "slipped on a banana peel") != NULL) + se.death_type = KILLED_BY_CLUMSINESS; + else if (strstr(inbuf, "killed by a trap") != NULL) + se.death_type = KILLED_BY_TRAP; + else if (strstr(inbuf, "got out of the dungeon alive") != NULL) + se.death_type = KILLED_BY_LEAVING; + else if (strstr(inbuf, "escaped with the Orb") != NULL) + se.death_type = KILLED_BY_WINNING; + else if (strstr(inbuf, "quit") != NULL) + se.death_type = KILLED_BY_QUITTING; + else if (strstr(inbuf, "was drained of all life") != NULL) + se.death_type = KILLED_BY_DRAINING; + else if (strstr(inbuf, "starved to death") != NULL) + se.death_type = KILLED_BY_STARVATION; + else if (strstr(inbuf, "froze to death") != NULL) + se.death_type = KILLED_BY_FREEZING; + else if (strstr(inbuf, "burnt to a crisp") != NULL) + se.death_type = KILLED_BY_BURNING; + else if (strstr(inbuf, "killed by wild magic") != NULL) + se.death_type = KILLED_BY_WILD_MAGIC; + else if (strstr(inbuf, "killed by Xom") != NULL) + se.death_type = KILLED_BY_XOM; + else if (strstr(inbuf, "killed by a statue") != NULL) + se.death_type = KILLED_BY_STATUE; + else if (strstr(inbuf, "rotted away") != NULL) + se.death_type = KILLED_BY_ROTTING; + else if (strstr(inbuf, "killed by bad target") != NULL) + se.death_type = KILLED_BY_TARGETTING; + else if (strstr(inbuf, "killed by an exploding spore") != NULL) + se.death_type = KILLED_BY_SPORE; + else if (strstr(inbuf, "smote by The Shining One") != NULL) + 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; + + // 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. + if (se.death_type == KILLED_BY_MONSTER) + { + if (strstr(inbuf, "killed by") == NULL) + se.death_type = KILLED_BY_SOMETHING; + } + + // set some fields + se.death_source = 0; + se.mon_num = 0; + strcpy(se.death_source_name, ""); + + // now try to pull the monster out. + 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'; + } +} + +static void hs_search_where(char *inbuf, struct scorefile_entry &se) +{ + char scratch[6]; + + se.level_type = LEVEL_DUNGEON; + se.branch = BRANCH_MAIN_DUNGEON; + se.dlvl = 0; + + // early out + if (se.death_type == KILLED_BY_LEAVING || se.death_type == KILLED_BY_WINNING) + return; + + // here we go again. + if (strstr(inbuf, "in the Abyss") != NULL) + se.level_type = LEVEL_ABYSS; + else if (strstr(inbuf, "in Pandemonium") != NULL) + se.level_type = LEVEL_PANDEMONIUM; + else if (strstr(inbuf, "in a labyrinth") != NULL) + se.level_type = LEVEL_LABYRINTH; + + // early out for special level types + if (se.level_type != LEVEL_DUNGEON) + return; + + // check for vestible + if (strstr(inbuf, "in the Vestibule") != NULL) + { + se.branch = BRANCH_VESTIBULE_OF_HELL; + return; + } + + // 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"); + se.dlvl = atoi( scratch ); + } + + // get branch. + if (strstr(inbuf, "of Dis") != NULL) + se.branch = BRANCH_DIS; + else if (strstr(inbuf, "of Gehenna") != NULL) + se.branch = BRANCH_GEHENNA; + else if (strstr(inbuf, "of Cocytus") != NULL) + se.branch = BRANCH_COCYTUS; + else if (strstr(inbuf, "of Tartarus") != NULL) + se.branch = BRANCH_TARTARUS; + else if (strstr(inbuf, "of the Mines") != NULL) + se.branch = BRANCH_ORCISH_MINES; + else if (strstr(inbuf, "of the Hive") != NULL) + se.branch = BRANCH_HIVE; + else if (strstr(inbuf, "of the Lair") != NULL) + se.branch = BRANCH_LAIR; + else if (strstr(inbuf, "of the Slime Pits") != NULL) + se.branch = BRANCH_SLIME_PITS; + else if (strstr(inbuf, "of the Vaults") != NULL) + se.branch = BRANCH_VAULTS; + else if (strstr(inbuf, "of the Crypt") != NULL) + se.branch = BRANCH_CRYPT; + else if (strstr(inbuf, "of the Hall") != NULL) + se.branch = BRANCH_HALL_OF_BLADES; + else if (strstr(inbuf, "of Zot's Hall") != NULL) + se.branch = BRANCH_HALL_OF_ZOT; + else if (strstr(inbuf, "of the Temple") != NULL) + se.branch = BRANCH_ECUMENICAL_TEMPLE; + else if (strstr(inbuf, "of the Snake Pit") != NULL) + se.branch = BRANCH_SNAKE_PIT; + else if (strstr(inbuf, "of the Elf Hall") != NULL) + se.branch = BRANCH_ELVEN_HALLS; + else if (strstr(inbuf, "of the Tomb") != NULL) + se.branch = BRANCH_TOMB; + else if (strstr(inbuf, "of the Swamp") != NULL) + se.branch = BRANCH_SWAMP; +} -- cgit v1.2.3-54-g00ecf