than the old format by about 2x, but is more maintainable and comprehensible.
Removed support for parsing scorefiles/logfiles older than 4.0 beta 26.
Added shim to make 0.1.7 logfiles compatible with 0.2
Using the -scorefile option alone (no -scores, -tscores, etc.) causes Crawl to read in the existing scorefile/logfile and write it out to stdout in the new format.
Ghouls get claw damage messages in unarmed combat.
Plain oozes lose acid damage attacks (added inadvertently).
Prompt the user when trying to displace a friendly over water (the old fix was to simply say "The foo resists").
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@994 c06c8d41-db1a-0410-9941-cceddc491573
RISMOCQM6BKK4XSIRKYLOBB2UPDYJNDAL6OGIIR5GGNZQAK5YSZAC
GTCB2E2OCYXD7LEPFLW6JMKXZPCCWITBN3KRW42CQFVRNCSUUZIQC
SIKFXNXSAMU6IYRGDG6SWP3LOX6SEE7PDFA7RVQAGG2SLWQ72D2QC
H5M5SV7QUYUJ2X7U7CQCVGXIODWNRPCOGQO5NUWLUR2GLFBHTSFAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
T4IH76FA5TWHFOZUJFHLQXQJENJHWTUZZP4EGNA7D4GTZY7D4ZKAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
5ASC3STDYCNLZFEBN6UTMUCGDETHBR2OCBZCF5VIAZ5RRWLOTDYQC
I5N4EIR6SCLLRGKRBUKW5FKUVYK62EA5DOWIAS5XFIHZQKMCXWBAC
R22TTMI6WXWULC7ODKFF3QCB7MOTETQQ6IR4BUCUPOCQKQNCTT5AC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
NNG27Y5ZQAZX6UD7F7M4F6KEZBEDFXPEEC3LFUSX4ESKT7K6UJQAC
GR6ZABTGAAQTKZBVA7PTOYQG6G3ACF62ITT5COLHTRN2HGIPRG2AC
5UVDIVD4NSXA52U4QMQIVST3GSZJ2A2YZK3RUEXKPM43YVQ7LI5AC
MWHMD65QP6UKXO6Q4ZVEAMXY563AJ6KH7J6UEZOB5CRPPSRB762QC
// Does a case-sensitive lookup of the species name supplied.
int str_to_species(const std::string &species)
{
if (species.empty())
return SP_HUMAN;
for (int i = SP_HUMAN; i < NUM_SPECIES; ++i)
{
if (species == species_name(i, 10))
return (i);
}
for (int i = SP_HUMAN; i < NUM_SPECIES; ++i)
{
if (species == species_name(i, 1))
return (i);
}
bool player::has_usable_claws() const
{
return (equip[EQ_GLOVES] == -1 &&
(attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON
|| mutation[MUT_CLAWS]
|| species == SP_TROLL
|| species == SP_GHOUL));
}
swap = habitat_okay( monster, grd[loc_x][loc_y] )
&& !is_grid_dangerous(mgrid);
const bool mon_dest_okay = habitat_okay( monster, grd[loc_x][loc_y] );
const bool you_dest_okay =
!is_grid_dangerous(mgrid)
|| yesno("Do you really want to step there?", false, 'n');
if (!you_dest_okay)
return (false);
bool swap = mon_dest_okay;
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, 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);
static void hs_nextstring(const char *&inbuf, char *dest, size_t bufsize);
static int hs_nextint(const char *&inbuf);
static long hs_nextlong(const char *&inbuf);
static time_t parse_time(const std::string &st);
// 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 std::string hs_nextstring(const char *&inbuf, size_t destsize = 800)
{
char *buf = new char[destsize];
if (!buf)
return ("");
hs_nextstring(inbuf, buf, destsize);
const std::string res = buf;
delete [] buf;
return (res);
if (strlen( buff ) < 15)
return (static_cast<time_t>(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 ));
return parse_time(buff);
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, 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, sizeof 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, 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, sizeof 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;
}
}
struct tm date;
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;
}
if (st.length() < 15)
return (static_cast<time_t>(0));
se.birth_time = hs_nextdate(inbuf);
se.death_time = hs_nextdate(inbuf);
date.tm_mon = val_char( st[4] ) * 10 + val_char( st[5] );
date.tm_mday = val_char( st[6] ) * 10 + val_char( st[7] );
date.tm_hour = val_char( st[8] ) * 10 + val_char( st[9] );
date.tm_min = val_char( st[10] ) * 10 + val_char( st[11] );
date.tm_sec = val_char( st[12] ) * 10 + val_char( st[13] );
date.tm_isdst = (st[14] == 'D');
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);
return (mktime( &date ));
/* old entries are of the following format (Brent introduced some
spacing at one point, we have to take this into account):
"mon", "pois", "cloud", "beam", "deaths_door", "lava", "water",
"stupidity", "weakness", "clumsiness", "trap", "leaving", "winning",
"quitting", "draining", "starvation", "freezing", "burning", "wild_magic",
"xom", "statue", "rotting", "targetting", "spore", "tso_smiting",
"petrification", "unknown", "something", "falling_down_stairs", "acid",
"curare", "melting", "bleeding",
};
// 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
const char *kill_method_name(kill_method_type kmt)
{
ASSERT(NUM_KILLBY ==
(int) sizeof(kill_method_names) / sizeof(*kill_method_names));
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];
const int inlen = strlen(inbuf);
char *start = inbuf;
return kill_method_names[kmt];
}
// 1. get score
hs_parse_generic_2(inbuf, scratch, sizeof scratch, "0123456789");
kill_method_type str_to_kill_method(const std::string &s)
{
ASSERT(NUM_KILLBY ==
(int) sizeof(kill_method_names) / sizeof(*kill_method_names));
for (int i = 0; i < NUM_KILLBY; ++i)
{
if (s == kill_method_names[i])
return static_cast<kill_method_type>(i);
}
// 2. get name
hs_parse_generic_1(inbuf, scratch, sizeof scratch, "-");
hs_stripblanks(scratch);
strncpy(se.name, scratch, sizeof se.name);
se.name[ sizeof(se.name) - 1 ] = 0;
//////////////////////////////////////////////////////////////////////////
// scorefile_entry
hs_parse_generic_1(inbuf, scratch, sizeof scratch, "0123456789");
hs_stripblanks(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;
init_death_cause(dam, dsource, dtype, aux);
if (!death_cause_only)
init();
}
// 4. get clevel
hs_parse_generic_2(inbuf, scratch, sizeof scratch, "0123456789");
se.lvl = atoi(scratch);
scorefile_entry::scorefile_entry()
{
// Completely uninitialized, caveat user.
reset();
}
// 4a. get wizard mode
hs_parse_generic_1(inbuf, scratch, sizeof scratch, ",");
if (strstr(scratch, "Wiz") != NULL)
se.wiz_mode = 1;
else
se.wiz_mode = 0;
scorefile_entry::scorefile_entry(const scorefile_entry &se)
{
init_from(se);
}
// 5. get death type
hs_search_death(inbuf, se);
void scorefile_entry::init_from(const scorefile_entry &se)
{
version = se.version;
release = se.release;
points = se.points;
name = se.name;
uid = se.uid;
race = se.race;
cls = se.cls;
race_class_name = se.race_class_name;
lvl = se.lvl;
best_skill = se.best_skill;
best_skill_lvl = se.best_skill_lvl;
death_type = se.death_type;
death_source = se.death_source;
mon_num = se.mon_num;
death_source_name = se.death_source_name;
auxkilldata = se.auxkilldata;
dlvl = se.dlvl;
level_type = se.level_type;
branch = se.branch;
final_hp = se.final_hp;
final_max_hp = se.final_max_hp;
final_max_max_hp = se.final_max_max_hp;
damage = se.damage;
str = se.str;
intel = se.intel;
dex = se.dex;
god = se.god;
piety = se.piety;
penance = se.penance;
wiz_mode = se.wiz_mode;
birth_time = se.birth_time;
death_time = se.death_time;
real_time = se.real_time;
num_turns = se.num_turns;
num_diff_runes = se.num_diff_runes;
num_runes = se.num_runes;
}
// 6. get branch, level
hs_search_where(inbuf, se);
bool scorefile_entry::parse(const std::string &line)
{
// Scorefile formats down the ages:
//
// 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 colon-separated fields (and
// start with a colon), and may exceed 80 characters!
// 4) 0.2 and onwards, which are xlogfile format - no leading
// colon, fields separated by colons, each field specified as
// key=value. Colons are not allowed in key names, must be escaped to
// | in values. Literal | must be escaped as || in values.
//
// 0.2 only reads entries of type (3) and (4), and only writes entries of
// type (4).
// 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;
// Leading colon implies 4.0 style line:
if (line[0] == ':')
return (parse_obsolete_scoreline(line));
else
return (parse_scoreline(line));
char *p = outbuf;
if (!*inbuf)
// xlogfile unescape: s/\\(.)/$1/g, s/|/:/g
std::string scorefile_entry::xlog_unescape(const std::string &s) const
{
std::string unesc = s;
bool escaped = false;
for (int i = 0, size = unesc.size(); i < size; ++i)
while (strchr(stopvalues, *inbuf) == NULL
&& *inbuf != 0
&& (p - outbuf) < (int) outsz - 1)
*p++ = *inbuf++;
std::string scorefile_entry::raw_string() const
{
set_score_fields();
if (!fields.get())
return ("");
std::string line;
for (int i = 0, size = fields->size(); i < size; ++i)
{
const std::pair<std::string, std::string> &f = (*fields)[i];
ASSERT(outsz > 0);
std::vector<std::string> rawfields = split_string(":", line);
fields.reset(new hs_fields);
for (int i = 0, size = rawfields.size(); i < size; ++i)
{
const std::string field = rawfields[i];
std::string::size_type st = field.find('=');
if (st == std::string::npos)
continue;
while (strchr(continuevalues, *inbuf) != NULL
&& *inbuf
&& (p - outbuf) < (int) outsz - 1)
*p++ = *inbuf++;
void scorefile_entry::add_field(const std::string &key,
const char *format,
...) const
{
char buf[400];
va_list args;
va_start(args, format);
vsnprintf(buf, sizeof buf, format, args);
va_end(args);
while(*p != 0 && p != q)
*q++ = *p++;
std::string scorefile_entry::str_field(const std::string &s) const
{
hs_map::const_iterator i = fieldmap->find(s);
if (i == fieldmap->end())
return ("");
// assume killed by monster
se.death_type = KILLED_BY_MONSTER;
std::string field = str_field(s);
return atoi(field.c_str());
}
long scorefile_entry::long_field(const std::string &s) const
{
std::string field = str_field(s);
return atol(field.c_str());
}
// 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, "asphyxiated"))
se.death_type = KILLED_BY_CURARE;
else if (strstr(inbuf, "soaked and fell apart") != NULL)
void scorefile_entry::map_fields()
{
fieldmap.reset(new hs_map);
for (int i = 0, size = fields->size(); i < size; ++i)
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, "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;
}
// 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)
static int str_to_branch(const std::string &branch)
{
for (int i = 0; i < NUM_BRANCHES; ++i)
// 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 ");
if (p)
{
p += 4;
char *q = strstr(inbuf, " on ");
if (q == NULL)
q = strstr(inbuf, " in ");
static const char *level_area_type_name(int level_type)
{
if (level_type >= 0 && level_type < NUM_LEVEL_AREA_TYPES)
return level_type_names[level_type];
return ("");
}
if (q && q > p)
{
char *d = se.death_source_name;
const int maxread = sizeof(se.death_source_name) - 1;
static int str_to_level_area_type(const std::string &s)
{
for (int i = 0; i < NUM_LEVEL_AREA_TYPES; ++i)
if (s == level_type_names[i])
return (i);
return (LEVEL_DUNGEON);
}
// 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;
best_skill = str_to_skill(str_field("sk"));
best_skill_lvl = int_field("sklev");
death_type = str_to_kill_method(str_field("ktyp"));
death_source_name = str_field("killer");
// check for vestibule
if (strstr(inbuf, "in the Vestibule") != NULL)
{
se.branch = BRANCH_VESTIBULE_OF_HELL;
branch = str_to_branch(str_field("br"));
dlvl = int_field("lvl");
level_type = str_to_level_area_type(str_field("ltyp"));
final_hp = int_field("hp");
final_max_hp = int_field("mhp");
final_max_max_hp = int_field("mmhp");
damage = int_field("dam");
str = int_field("str");
intel = int_field("int");
dex = int_field("dex");
god = str_to_god(str_field("god"));
piety = int_field("piety");
penance = int_field("pen");
wiz_mode = int_field("wiz");
birth_time = parse_time(str_field("start"));
death_time = parse_time(str_field("end"));
real_time = long_field("dur");
num_turns = long_field("turn");
num_diff_runes = int_field("urune");
num_runes = int_field("nrune");
}
void scorefile_entry::set_score_fields() const
{
fields.reset(new hs_fields());
if (!fields.get())
}
add_field("v", VER_NUM);
add_field("lv", SCORE_VERSION);
add_field("sc", "%ld", points);
add_field("name", "%s", name.c_str());
add_field("uid", "%d", uid);
add_field("race", "%s", species_name(race, lvl));
add_field("cls", "%s", get_class_name(cls));
add_field("xl", "%d", lvl);
add_field("sk", "%s", skill_name(best_skill));
add_field("sklev", "%d", best_skill_lvl);
add_field("title", "%s", skill_title( best_skill, best_skill_lvl,
race, str, dex, god ) );
add_field("ktyp", ::kill_method_name(kill_method_type(death_type)));
add_field("killer", death_source_desc());
add_auxkill_field();
add_field("place", "%s",
place_name(get_packed_place(branch, dlvl, level_type),
false, true).c_str());
add_field("br", "%s", short_branch_name(branch));
add_field("lvl", "%d", dlvl);
add_field("ltyp", "%s", level_area_type_name(level_type));
add_field("hp", "%d", final_hp);
add_field("mhp", "%d", final_max_hp);
add_field("mmhp", "%d", final_max_max_hp);
add_field("dam", "%d", damage);
add_field("str", "%d", str);
add_field("int", "%d", intel);
add_field("dex", "%d", dex);
// Don't write No God to save some space.
if (god != -1)
add_field("god", "%s", god == GOD_NO_GOD? "" : god_name(god));
if (piety > 0)
add_field("piety", "%d", piety);
if (penance > 0)
add_field("pen", "%d", penance);
if (wiz_mode)
add_field("wiz", "%d", wiz_mode);
add_field("start", "%s", make_date_string(birth_time).c_str());
add_field("end", "%s", make_date_string(death_time).c_str());
add_field("dur", "%ld", real_time);
add_field("turn", "%ld", num_turns);
if (num_diff_runes)
add_field("urune", "%d", num_diff_runes);
if (num_runes)
add_field("nrune", "%d", num_runes);
#ifdef DGL_EXTENDED_LOGFILES
const std::string short_msg = short_kill_message();
add_field("tmsg", "%s", short_msg.c_str());
const std::string long_msg = long_kill_message();
if (long_msg != short_msg)
add_field("vmsg", "%s", long_msg.c_str());
#endif
}
// from here, we have branch and level.
char *p = strstr(inbuf, "on L");
if (p != NULL)
std::string scorefile_entry::make_oneline(const std::string &ml) const
{
std::vector<std::string> lines = split_string(EOL, ml);
for (int i = 0, size = lines.size(); i < size; ++i)
p += 4;
hs_parse_generic_2(p, scratch, sizeof scratch, "0123456789");
se.dlvl = atoi( scratch );
std::string &s = lines[i];
if (s.find("...") == 0)
{
s = s.substr(3);
trim_string(s);
}
// 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;
std::string scorefile_entry::long_kill_message() const
{
std::string msg = death_description(DDV_LOGVERBOSE);
msg = make_oneline(msg);
msg[0] = tolower(msg[0]);
trim_string(msg);
return (msg);
//////////////////////////////////////////////////////////////////////////
// scorefile_entry
std::string scorefile_entry::short_kill_message() const
{
std::string msg = death_description(DDV_ONELINE);
msg = make_oneline(msg);
msg[0] = tolower(msg[0]);
trim_string(msg);
return (msg);
}
scorefile_entry::scorefile_entry(int dam, int dsource, int dtype,
const char *aux, bool death_cause_only)
// Maps a 0.1.x branch id to a 0.2 branch id. Ugh. Fortunately we need this
// only to read old logfiles/scorefiles.
int scorefile_entry::kludge_branch(int branch_01) const
reset();
static int branch_map[] = {
BRANCH_MAIN_DUNGEON, BRANCH_DIS, BRANCH_GEHENNA,
BRANCH_VESTIBULE_OF_HELL, BRANCH_COCYTUS, BRANCH_TARTARUS,
BRANCH_INFERNO, BRANCH_THE_PIT, BRANCH_MAIN_DUNGEON,
BRANCH_MAIN_DUNGEON, BRANCH_ORCISH_MINES, BRANCH_HIVE,
BRANCH_LAIR, BRANCH_SLIME_PITS, BRANCH_VAULTS, BRANCH_CRYPT,
BRANCH_HALL_OF_BLADES, BRANCH_HALL_OF_ZOT, BRANCH_ECUMENICAL_TEMPLE,
BRANCH_SNAKE_PIT, BRANCH_ELVEN_HALLS, BRANCH_TOMB, BRANCH_SWAMP,
BRANCH_CAVERNS
};
scorefile_entry::scorefile_entry()
// [ds] This is the 4.0 b26 logfile parser. Old-style logs are now deprecated;
// support for reading them may be discontinued in the next version.
bool scorefile_entry::parse_obsolete_scoreline(const std::string &line)
// Completely uninitialized, caveat user.
reset();
const char *inbuf = line.c_str();
version = hs_nextint(inbuf);
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 (version != 4 || release < 2)
return (false);
points = hs_nextlong(inbuf);
name = hs_nextstring(inbuf);
uid = hs_nextlong(inbuf);
race = hs_nextint(inbuf);
cls = hs_nextint(inbuf);
race_class_name = hs_nextstring(inbuf, 6);
lvl = hs_nextint(inbuf);
best_skill = hs_nextint(inbuf);
best_skill_lvl = hs_nextint(inbuf);
death_type = hs_nextint(inbuf);
death_source = hs_nextint(inbuf);
mon_num = hs_nextint(inbuf);
death_source_name = hs_nextstring(inbuf);
// To try and keep the scorefile backwards compatible,
// we'll branch on version > 4.0 to read the auxkilldata
// text field.
if (version == 4 && release >= 1)
auxkilldata = hs_nextstring( inbuf, ITEMNAME_SIZE );
else
auxkilldata[0] = 0;
dlvl = hs_nextint(inbuf);
level_type = hs_nextint(inbuf);
branch = kludge_branch( hs_nextint(inbuf) );
final_hp = hs_nextint(inbuf);
final_max_hp = hs_nextint(inbuf);
final_max_max_hp = hs_nextint(inbuf);
damage = hs_nextint(inbuf);
str = hs_nextint(inbuf);
intel = hs_nextint(inbuf);
dex = hs_nextint(inbuf);
god = hs_nextint(inbuf);
piety = hs_nextint(inbuf);
penance = hs_nextint(inbuf);
wiz_mode = hs_nextint(inbuf);
birth_time = hs_nextdate(inbuf);
death_time = hs_nextdate(inbuf);
real_time = hs_nextint(inbuf);
num_turns = hs_nextint(inbuf);
num_diff_runes = hs_nextint(inbuf);
num_runes = hs_nextint(inbuf);
return (true);
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;
return (missile);
}
std::string scorefile_entry::terse_missile_cause() const
{
std::string cause;
const 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 ("???");
bool terse = verbosity == DDV_TERSE;
bool verbose = verbosity == DDV_VERBOSE;
bool oneline = verbosity == DDV_ONELINE;
const bool terse = verbosity == DDV_TERSE;
const bool semiverbose = verbosity == DDV_LOGVERBOSE;
const bool verbose = verbosity == DDV_VERBOSE || semiverbose;
const bool oneline = verbosity == DDV_ONELINE;
desc += (is_vowel( auxkilldata[0] )) ? "... with an "
: "... with a ";
desc += auxkilldata;
needs_damage = true;
if (!semiverbose)
{
desc += (is_vowel( auxkilldata[0] )) ? "... with an "
: "... with a ";
desc += auxkilldata;
needs_damage = true;
}
bool parse_obsolete_scoreline(const std::string &line);
bool parse_scoreline(const std::string &line);
void init_with_fields();
void add_field(const std::string &key,
const char *format, ...) const;
void add_auxkill_field() const;
void set_score_fields() const;
std::string short_kill_message() const;
std::string long_kill_message() const;
std::string make_oneline(const std::string &s) const;
std::string str_field(const std::string &key) const;
int int_field(const std::string &key) const;
long long_field(const std::string &key) const;
std::string xlog_escape(const std::string &s) const;
std::string xlog_unescape(const std::string &s) const;
void read_auxkill_field();
void map_fields();
void init_from(const scorefile_entry &other);
int kludge_branch(int branch_01) const;
// DGL_CLEAR_SCREEN specifies the escape sequence to use to clear
// the screen (used only when DGAMELAUNCH is defined). We make no
// attempt to discover an appropriate escape sequence for the
// term, assuming that dgamelaunch admins can adjust this as
// needed.
//
// Why this is necessary: dgamelaunch's ttyplay initialises
// playback by jumping to the last screen clear and playing back
// from there. For that to work, ttyplay must be able to recognise
// the clear screen sequence, and ncurses clear()+refresh()
// doesn't do the trick.
//
#define DGL_CLEAR_SCREEN "\033[2J"
#ifdef DGAMELAUNCH
// DGL_CLEAR_SCREEN specifies the escape sequence to use to clear
// the screen (used only when DGAMELAUNCH is defined). We make no
// attempt to discover an appropriate escape sequence for the
// term, assuming that dgamelaunch admins can adjust this as
// needed.
//
// Why this is necessary: dgamelaunch's ttyplay initialises
// playback by jumping to the last screen clear and playing back
// from there. For that to work, ttyplay must be able to recognise
// the clear screen sequence, and ncurses clear()+refresh()
// doesn't do the trick.
//
#define DGL_CLEAR_SCREEN "\033[2J"
// If defined, the hiscores code dumps preformatted verbose and terse
// death message strings in the logfile for the convenience of logfile
// parsers.
#define DGL_EXTENDED_LOGFILES
#endif