From 7d8b4d4f972918a25a416bba27424d7ae7e32983 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 19 Feb 2017 15:30:46 +0200 Subject: [PATCH] Add end-of-game dumplogs This is based on the "new" dumplog patch for 3.6.0, by Maxime Bacoux. Define DUMPLOG to enable. By default only enabled for the TTY linux. --- doc/Guidebook.mn | 18 ++++ doc/Guidebook.tex | 18 ++++ doc/fixes36.1 | 1 + include/config.h | 26 ++++++ include/extern.h | 8 ++ include/flag.h | 1 + include/sys.h | 3 + src/botl.c | 37 ++++---- src/detect.c | 180 +++++++++++++++++++++++--------------- src/end.c | 171 +++++++++++++++++++++++++++--------- src/files.c | 9 +- src/pline.c | 13 +++ src/sys.c | 7 ++ src/windows.c | 239 +++++++++++++++++++++++++++++++++++++++++++++++++++ sys/unix/hints/linux | 1 + sys/unix/sysconf | 14 +++ sys/winnt/sysconf | 14 +++ util/makedefs.c | 3 + 18 files changed, 631 insertions(+), 132 deletions(-) diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 8b739733..cf0c5f73 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -3776,6 +3776,24 @@ to identify unique people for the score file. .lp MAX_STATUENAME_RANK\ =\ Maximum number of score file entries to use for random statue names (default is 10). +.lp +DUMPLOGFILE\ =\ A filename where the end-of-game dumplog is saved. +Not defining this will prevent dumplog from being created. Only available +if your game is compiled with DUMPLOG. Allows the following placeholders: +.sd +.si +%% - literal '%' +%v - version (eg. "3.6.1-0") +%u - game UID +%t - game start time, UNIX timestamp format +%T - current time, UNIX timestamp format +%d - game start time, YYYYMMDDhhmmss format +%D - current time, YYYYMMDDhhmmss format +%n - player name +%N - first character of player name +.ei +.ed + .hn 1 Scoring .pg diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index b4243b8d..e2ea3bbf 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -4589,6 +4589,24 @@ Minimum number of points to get an entry in the score file. \item[\ib{PERS\verb+_+IS\verb+_+UID}] 0 or 1 to use user names or numeric userids, respectively, to identify unique people for the score file +%.lp +\item[\ib{DUMPLOGFILE}] +A filename where the end-of-game dumplog is saved. +Not defining this will prevent dumplog from being created. Only available +if your game is compiled with DUMPLOG. Allows the following placeholders: +%.sd +%.si +{\tt \%\%} --- literal `{\tt \%}'\\ +{\tt \%v} --- version (eg. "3.6.1-0")\\ +{\tt \%u} --- game UID\\ +{\tt \%t} --- game start time, UNIX timestamp format\\ +{\tt \%T} --- current time, UNIX timestamp format\\ +{\tt \%d} --- game start time, YYYYMMDDhhmmss format\\ +{\tt \%D} --- current time, YYYYMMDDhhmmss format\\ +{\tt \%n} --- player name\\ +{\tt \%N} --- first character of player name +%.ei +%.ed \elist %.hn 1 diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 3a0149e9..39188f46 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -562,6 +562,7 @@ Ray Chason's MS-DOS port restored to functionality with credit to Reddit user Ray Chason's MSDOS port support for some VESA modes Darshan Shaligram's pet ranged attack Jason Dorje Short's key rebinding +Maxime Bacoux's new dumplog Code Cleanup and Reorganization diff --git a/include/config.h b/include/config.h index 9fc7dc6f..ba5db544 100644 --- a/include/config.h +++ b/include/config.h @@ -508,6 +508,32 @@ typedef unsigned char uchar; but it isn't necessary for successful operation of the program */ #define FREE_ALL_MEMORY /* free all memory at exit */ +/* #define DUMPLOG */ /* End-of-game dump logs */ +#ifdef DUMPLOG + +#ifndef DUMPLOG_MSG_COUNT +#define DUMPLOG_MSG_COUNT 50 +#endif + +#ifndef DUMPLOG_FILE +#define DUMPLOG_FILE "/tmp/nethack.%n.%d.log" +/* DUMPLOG_FILE allows following placeholders: + %% literal '%' + %v version (eg. "3.6.1-0") + %u game UID + %t game start time, UNIX timestamp format + %T current time, UNIX timestamp format + %d game start time, YYYYMMDDhhmmss format + %D current time, YYYYMMDDhhmmss format + %n player name + %N first character of player name + DUMPLOG_FILE is not used if SYSCF is defined +*/ +#endif + +#endif + + /* End of Section 4 */ #ifdef TTY_TILES_ESCCODES diff --git a/include/extern.h b/include/extern.h index 52b810a6..ca51e491 100644 --- a/include/extern.h +++ b/include/extern.h @@ -141,6 +141,8 @@ E int NDECL(getbones); /* ### botl.c ### */ +E char *NDECL(do_statusline1); +E char *NDECL(do_statusline2); E int FDECL(xlev_to_rank, (int)); E int FDECL(title_to_mon, (const char *, int *, int *)); E void NDECL(max_rank_sz); @@ -268,6 +270,7 @@ E void NDECL(warnreveal); E int FDECL(dosearch0, (int)); E int NDECL(dosearch); E void NDECL(sokoban_detect); +E void NDECL(dump_map); E void FDECL(reveal_terrain, (int, int)); /* ### dig.c ### */ @@ -2711,6 +2714,11 @@ E void FDECL(genl_status_threshold, (int, int, anything, int, int, int)); #endif #endif +E void FDECL(dump_open_log, (time_t)); +E void NDECL(dump_close_log); +E void FDECL(dump_redirect, (boolean)); +E void FDECL(dump_forward_putstr, (winid, int, const char*, int)); + /* ### wizard.c ### */ E void NDECL(amulet); diff --git a/include/flag.h b/include/flag.h index 612a1472..f5072081 100644 --- a/include/flag.h +++ b/include/flag.h @@ -201,6 +201,7 @@ struct instance_flags { boolean vision_inited; /* true if vision is ready */ boolean sanity_check; /* run sanity checks */ boolean mon_polycontrol; /* debug: control monster polymorphs */ + boolean in_dumplog; /* doing the dumplog right now? */ /* stuff that is related to options and/or user or platform preferences */ diff --git a/include/sys.h b/include/sys.h index bf652ede..91c5d352 100644 --- a/include/sys.h +++ b/include/sys.h @@ -15,6 +15,9 @@ struct sysopt { char *shellers; /* like wizards, for ! command (-DSHELL); also ^Z */ char *genericusers; /* usernames that prompt for user name */ char *debugfiles; /* files to show debugplines in. '*' is all. */ +#ifdef DUMPLOG + char *dumplogfile; /* where the dump file is saved */ +#endif int env_dbgfl; /* 1: debugfiles comes from getenv("DEBUGFILES") * so sysconf's DEBUGFILES shouldn't override it; * 0: getenv() hasn't been attempted yet; diff --git a/src/botl.c b/src/botl.c index f3908617..e6a771bd 100644 --- a/src/botl.c +++ b/src/botl.c @@ -13,15 +13,12 @@ const char *const enc_stat[] = { "", "Burdened", "Stressed", STATIC_OVL NEARDATA int mrank_sz = 0; /* loaded by max_rank_sz (from u_init) */ STATIC_DCL const char *NDECL(rank); -#ifndef STATUS_VIA_WINDOWPORT - -STATIC_DCL void NDECL(bot1); -STATIC_DCL void NDECL(bot2); +#if !defined(STATUS_VIA_WINDOWPORT) || defined(DUMPLOG) -STATIC_OVL void -bot1() +char * +do_statusline1() { - char newbot1[MAXCO]; + static char newbot1[BUFSZ]; register char *nb; register int i, j; @@ -71,14 +68,13 @@ bot1() if (flags.showscore) Sprintf(nb = eos(nb), " S:%ld", botl_score()); #endif - curs(WIN_STATUS, 1, 0); - putstr(WIN_STATUS, 0, newbot1); + return newbot1; } -STATIC_OVL void -bot2() +char * +do_statusline2() { - char newbot2[MAXCO], /* MAXCO: botl.h */ + static char newbot2[BUFSZ], /* MAXCO: botl.h */ /* dungeon location (and gold), hero health (HP, PW, AC), experience (HD if poly'd, else Exp level and maybe Exp points), time (in moves), varying number of status conditions */ @@ -102,7 +98,8 @@ bot2() if ((money = money_cnt(invent)) < 0L) money = 0L; /* ought to issue impossible() and then discard gold */ Sprintf(eos(dloc), "%s:%-2ld", /* strongest hero can lift ~300000 gold */ - encglyph(objnum_to_glyph(GOLD_PIECE)), min(money, 999999L)); + iflags.in_dumplog ? "$" : encglyph(objnum_to_glyph(GOLD_PIECE)), + min(money, 999999L)); dln = strlen(dloc); /* '$' encoded as \GXXXXNNNN is 9 chars longer than display will need */ dx = strstri(dloc, "\\G") ? 9 : 0; @@ -205,23 +202,25 @@ bot2() /* only two or three consecutive spaces available to squeeze out */ mungspaces(newbot2); } - - curs(WIN_STATUS, 1, 1); - putmixed(WIN_STATUS, 0, newbot2); + return newbot2; } +#ifndef STATUS_VIA_WINDOWPORT void bot() { if (youmonst.data && iflags.status_updates) { - bot1(); - bot2(); + curs(WIN_STATUS, 1, 0); + putstr(WIN_STATUS, 0, do_statusline1()); + curs(WIN_STATUS, 1, 1); + putmixed(WIN_STATUS, 0, do_statusline2()); } context.botl = context.botlx = 0; } - #endif /* !STATUS_VIA_WINDOWPORT */ +#endif /* !STATUS_VIA_WINDOWPORT || DUMPLOG */ + /* convert experience level (1..30) to rank index (0..8) */ int xlev_to_rank(xlev) diff --git a/src/detect.c b/src/detect.c index 7668d77b..8a5af31d 100644 --- a/src/detect.c +++ b/src/detect.c @@ -25,6 +25,7 @@ STATIC_DCL void FDECL(show_map_spot, (int, int)); STATIC_PTR void FDECL(findone, (int, int, genericptr_t)); STATIC_PTR void FDECL(openone, (int, int, genericptr_t)); STATIC_DCL int FDECL(mfind0, (struct monst *, BOOLEAN_P)); +STATIC_DCL int FDECL(reveal_terrain_getglyph, (int, int, int, unsigned, int, int)); /* bring hero out from underwater or underground or being engulfed; return True iff any change occurred */ @@ -1715,6 +1716,111 @@ sokoban_detect() } } +STATIC_DCL int +reveal_terrain_getglyph(x,y, full, swallowed, default_glyph, which_subset) +int x,y, full; +unsigned swallowed; +int default_glyph, which_subset; +{ + int glyph, levl_glyph; + uchar seenv; + boolean keep_traps = (which_subset & TER_TRP) !=0, + keep_objs = (which_subset & TER_OBJ) != 0, + keep_mons = (which_subset & TER_MON) != 0; + struct monst *mtmp; + struct trap *t; + + /* for 'full', show the actual terrain for the entire level, + otherwise what the hero remembers for seen locations with + monsters, objects, and/or traps removed as caller dictates */ + seenv = (full || level.flags.hero_memory) + ? levl[x][y].seenv : cansee(x, y) ? SVALL : 0; + if (full) { + levl[x][y].seenv = SVALL; + glyph = back_to_glyph(x, y); + levl[x][y].seenv = seenv; + } else { + levl_glyph = level.flags.hero_memory + ? levl[x][y].glyph + : seenv + ? back_to_glyph(x, y) + : default_glyph; + /* glyph_at() returns the displayed glyph, which might + be a monster. levl[][].glyph contains the remembered + glyph, which will never be a monster (unless it is + the invisible monster glyph, which is handled like + an object, replacing any object or trap at its spot) */ + glyph = !swallowed ? glyph_at(x, y) : levl_glyph; + if (keep_mons && x == u.ux && y == u.uy && swallowed) + glyph = mon_to_glyph(u.ustuck); + else if (((glyph_is_monster(glyph) + || glyph_is_warning(glyph)) && !keep_mons) + || glyph_is_swallow(glyph)) + glyph = levl_glyph; + if (((glyph_is_object(glyph) && !keep_objs) + || glyph_is_invisible(glyph)) + && keep_traps && !covers_traps(x, y)) { + if ((t = t_at(x, y)) != 0 && t->tseen) + glyph = trap_to_glyph(t); + } + if ((glyph_is_object(glyph) && !keep_objs) + || (glyph_is_trap(glyph) && !keep_traps) + || glyph_is_invisible(glyph)) { + if (!seenv) { + glyph = default_glyph; + } else if (lastseentyp[x][y] == levl[x][y].typ) { + glyph = back_to_glyph(x, y); + } else { + /* look for a mimic here posing as furniture; + if we don't find one, we'll have to fake it */ + if ((mtmp = m_at(x, y)) != 0 + && mtmp->m_ap_type == M_AP_FURNITURE) { + glyph = cmap_to_glyph(mtmp->mappearance); + } else { + /* we have a topology type but we want a + screen symbol in order to derive a glyph; + some screen symbols need the flags field + of levl[][] in addition to the type + (to disambiguate STAIRS to S_upstair or + S_dnstair, for example; current flags + might not be intended for remembered + type, but we've got no other choice) */ + schar save_typ = levl[x][y].typ; + + levl[x][y].typ = lastseentyp[x][y]; + glyph = back_to_glyph(x, y); + levl[x][y].typ = save_typ; + } + } + } + } + if (glyph == cmap_to_glyph(S_darkroom)) + glyph = cmap_to_glyph(S_room); /* FIXME: dirty hack */ + return glyph; +} + +void +dump_map() +{ + int x, y, glyph; + int subset = TER_MAP|TER_TRP|TER_OBJ|TER_MON; + int default_glyph = cmap_to_glyph(level.flags.arboreal ? S_tree : S_stone); + char buf[BUFSZ]; + + for (y = 0; y < ROWNO; y++) { + for (x = 1; x < COLNO; x++) { + int ch, color; + unsigned special; + glyph = reveal_terrain_getglyph(x,y, FALSE, u.uswallow, + default_glyph, subset); + (void) mapglyph(glyph, &ch, &color, &special, x, y); + buf[x-1] = ch; + } + buf[x-2] = '\0'; + putstr(0,0, buf); + } +} + /* idea from crawl; show known portion of map without any monsters, objects, or traps occluding the view of the underlying terrain */ void @@ -1725,10 +1831,7 @@ int which_subset; /* when not full, whether to suppress objs and/or traps */ if ((Hallucination || Stunned || Confusion) && !full) { You("are too disoriented for this."); } else { - int x, y, glyph, levl_glyph, default_glyph; - uchar seenv; - struct monst *mtmp; - struct trap *t; + int x, y, glyph, default_glyph; char buf[BUFSZ]; /* there is a TER_MAP bit too; we always show map regardless of it */ boolean keep_traps = (which_subset & TER_TRP) !=0, @@ -1739,74 +1842,11 @@ int which_subset; /* when not full, whether to suppress objs and/or traps */ if (unconstrain_map()) docrt(); default_glyph = cmap_to_glyph(level.flags.arboreal ? S_tree : S_stone); - /* for 'full', show the actual terrain for the entire level, - otherwise what the hero remembers for seen locations with - monsters, objects, and/or traps removed as caller dictates */ + for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) { - seenv = (full || level.flags.hero_memory) - ? levl[x][y].seenv : cansee(x, y) ? SVALL : 0; - if (full) { - levl[x][y].seenv = SVALL; - glyph = back_to_glyph(x, y); - levl[x][y].seenv = seenv; - } else { - levl_glyph = level.flags.hero_memory - ? levl[x][y].glyph - : seenv - ? back_to_glyph(x, y) - : default_glyph; - /* glyph_at() returns the displayed glyph, which might - be a monster. levl[][].glyph contains the remembered - glyph, which will never be a monster (unless it is - the invisible monster glyph, which is handled like - an object, replacing any object or trap at its spot) */ - glyph = !swallowed ? glyph_at(x, y) : levl_glyph; - if (keep_mons && x == u.ux && y == u.uy && swallowed) - glyph = mon_to_glyph(u.ustuck); - else if (((glyph_is_monster(glyph) - || glyph_is_warning(glyph)) && !keep_mons) - || glyph_is_swallow(glyph)) - glyph = levl_glyph; - if (((glyph_is_object(glyph) && !keep_objs) - || glyph_is_invisible(glyph)) - && keep_traps && !covers_traps(x, y)) { - if ((t = t_at(x, y)) != 0 && t->tseen) - glyph = trap_to_glyph(t); - } - if ((glyph_is_object(glyph) && !keep_objs) - || (glyph_is_trap(glyph) && !keep_traps) - || glyph_is_invisible(glyph)) { - if (!seenv) { - glyph = default_glyph; - } else if (lastseentyp[x][y] == levl[x][y].typ) { - glyph = back_to_glyph(x, y); - } else { - /* look for a mimic here posing as furniture; - if we don't find one, we'll have to fake it */ - if ((mtmp = m_at(x, y)) != 0 - && mtmp->m_ap_type == M_AP_FURNITURE) { - glyph = cmap_to_glyph(mtmp->mappearance); - } else { - /* we have a topology type but we want a - screen symbol in order to derive a glyph; - some screen symbols need the flags field - of levl[][] in addition to the type - (to disambiguate STAIRS to S_upstair or - S_dnstair, for example; current flags - might not be intended for remembered - type, but we've got no other choice) */ - schar save_typ = levl[x][y].typ; - - levl[x][y].typ = lastseentyp[x][y]; - glyph = back_to_glyph(x, y); - levl[x][y].typ = save_typ; - } - } - } - } - if (glyph == cmap_to_glyph(S_darkroom)) - glyph = cmap_to_glyph(S_room); /* FIXME: dirty hack */ + glyph = reveal_terrain_getglyph(x,y, full, swallowed, + default_glyph, which_subset); show_glyph(x, y, glyph); } diff --git a/src/end.c b/src/end.c index 09213205..e2e8a13a 100644 --- a/src/end.c +++ b/src/end.c @@ -667,6 +667,88 @@ char *defquery; return TRUE; } +#ifdef DUMPLOG +STATIC_OVL void +dump_plines() +{ + int i; + char* str; + extern char* saved_plines[]; + + putstr(0, 0, ""); + putstr(0, 0, "Latest messages:"); + for (i = 0; i < DUMPLOG_MSG_COUNT; ++i) + { + str = saved_plines[DUMPLOG_MSG_COUNT - 1 - i]; + if (str) { + char buf[BUFSZ]; + Sprintf(buf, " %s", str); + putstr(0, 0, buf); + } +#ifdef FREE_ALL_MEMORY + free(str); +#endif + } +} +#endif + +STATIC_OVL void +dump_everything(how, taken) +int how; +boolean taken; +{ +#ifdef DUMPLOG + struct obj* obj; + struct topl* topl; + char pbuf[BUFSZ]; + + dump_redirect(TRUE); + if (!iflags.in_dumplog) + return; + + init_symbols(); + + for (obj = invent; obj; obj = obj->nobj) { + makeknown(obj->otyp); + obj->known = obj->bknown = obj->dknown = obj->rknown = 1; + if (Is_container(obj) || obj->otyp == STATUE) + obj->cknown = obj->lknown = 1; + } + + Sprintf(pbuf, "%s, %s %s %s %s", plname, + aligns[1 - u.ualign.type].adj, + genders[flags.female].adj, + urace.adj, + (flags.female && urole.name.f) ? urole.name.f : urole.name.m); + putstr(0, 0, pbuf); + putstr(0, 0, ""); + + dump_map(); + putstr(0, 0, do_statusline1()); + putstr(0, 0, do_statusline2()); + putstr(0, 0, ""); + + dump_plines(); + putstr(0, 0, ""); + putstr(0, 0, "Inventory:"); + display_inventory((char *) 0, TRUE); + container_contents(invent, TRUE, TRUE, FALSE); + enlightenment((BASICENLIGHTENMENT | MAGICENLIGHTENMENT), + (how >= PANICKED) ? ENL_GAMEOVERALIVE + : ENL_GAMEOVERDEAD); + putstr(0, 0, ""); + list_vanquished('y', FALSE); + putstr(0, 0, ""); + list_genocided('a', FALSE); + putstr(0, 0, ""); + show_conduct((how >= PANICKED) ? 1 : 2); + putstr(0, 0, ""); + show_overview((how >= PANICKED) ? 1 : 2, how); + putstr(0, 0, ""); + dump_redirect(FALSE); +#endif +} + STATIC_OVL void disclose(how, taken) int how; @@ -1016,6 +1098,7 @@ int how; urealtime.finish_time = endtime = getnow(); urealtime.realtime += (long) (endtime - urealtime.start_timing); + dump_open_log(endtime); /* Sometimes you die on the first move. Life's not fair. * On those rare occasions you get hosed immediately, go out * smiling... :-) -3. @@ -1081,6 +1164,7 @@ int how; if (strcmp(flags.end_disclose, "none") && how != PANICKED) disclose(how, taken); + dump_everything(how, taken); /* finish_paybill should be called after disclosure but before bones */ if (bones_ok && taken) @@ -1179,6 +1263,11 @@ int how; } else done_stopprint = 1; /* just avoid any more output */ +#ifdef DUMPLOG + dump_redirect(TRUE); + genl_outrip(0, how, endtime); + dump_redirect(FALSE); +#endif if (u.uhave.amulet) { Strcat(killer.name, " (with the Amulet)"); } else if (how == ESCAPED) { @@ -1189,16 +1278,14 @@ int how; /* don't bother counting to see whether it should be plural */ } - if (!done_stopprint) { - Sprintf(pbuf, "%s %s the %s...", Goodbye(), plname, - (how != ASCENDED) - ? (const char *) ((flags.female && urole.name.f) - ? urole.name.f - : urole.name.m) - : (const char *) (flags.female ? "Demigoddess" : "Demigod")); - putstr(endwin, 0, pbuf); - putstr(endwin, 0, ""); - } + Sprintf(pbuf, "%s %s the %s...", Goodbye(), plname, + (how != ASCENDED) + ? (const char *) ((flags.female && urole.name.f) + ? urole.name.f + : urole.name.m) + : (const char *) (flags.female ? "Demigoddess" : "Demigod")); + dump_forward_putstr(endwin, 0, pbuf, done_stopprint); + dump_forward_putstr(endwin, 0, "", done_stopprint); if (how == ESCAPED || how == ASCENDED) { struct monst *mtmp; @@ -1223,45 +1310,48 @@ int how; /* count the points for artifacts */ artifact_score(invent, TRUE, endwin); +#ifdef DUMPLOG + dump_redirect(TRUE); + artifact_score(invent, TRUE, endwin); + dump_redirect(FALSE); +#endif viz_array[0][0] |= IN_SIGHT; /* need visibility for naming */ mtmp = mydogs; - if (!done_stopprint) - Strcpy(pbuf, "You"); + Strcpy(pbuf, "You"); if (!Schroedingers_cat) /* check here in case disclosure was off */ Schroedingers_cat = odds_and_ends(invent, CAT_CHECK); if (Schroedingers_cat) { int mhp, m_lev = adj_lev(&mons[PM_HOUSECAT]); mhp = d(m_lev, 8); nowrap_add(u.urexp, mhp); - if (!done_stopprint) - Strcat(eos(pbuf), " and Schroedinger's cat"); + Strcat(eos(pbuf), " and Schroedinger's cat"); } if (mtmp) { while (mtmp) { - if (!done_stopprint) - Sprintf(eos(pbuf), " and %s", mon_nam(mtmp)); + Sprintf(eos(pbuf), " and %s", mon_nam(mtmp)); if (mtmp->mtame) nowrap_add(u.urexp, mtmp->mhp); mtmp = mtmp->nmon; } - if (!done_stopprint) - putstr(endwin, 0, pbuf); + dump_forward_putstr(endwin, 0, pbuf, done_stopprint); pbuf[0] = '\0'; } else { - if (!done_stopprint) - Strcat(pbuf, " "); - } - if (!done_stopprint) { - Sprintf(eos(pbuf), "%s with %ld point%s,", - how == ASCENDED ? "went to your reward" - : "escaped from the dungeon", - u.urexp, plur(u.urexp)); - putstr(endwin, 0, pbuf); + Strcat(pbuf, " "); } + Sprintf(eos(pbuf), "%s with %ld point%s,", + how == ASCENDED ? "went to your reward" + : "escaped from the dungeon", + u.urexp, plur(u.urexp)); + dump_forward_putstr(endwin, 0, pbuf, done_stopprint); if (!done_stopprint) artifact_score(invent, FALSE, endwin); /* list artifacts */ +#if DUMPLOG + dump_redirect(TRUE); + artifact_score(invent, FALSE, 0); + dump_redirect(FALSE); +#endif /* list valuables here */ for (val = valuables; val->list; val++) { @@ -1288,11 +1378,11 @@ int how; Sprintf(pbuf, "%8ld worthless piece%s of colored glass,", count, plur(count)); } - putstr(endwin, 0, pbuf); + dump_forward_putstr(endwin, 0, pbuf, 0); } } - } else if (!done_stopprint) { + } else { /* did not escape or ascend */ if (u.uz.dnum == 0 && u.uz.dlevel <= 0) { /* level teleported out of the dungeon; `how' is DIED, @@ -1312,26 +1402,23 @@ int how; } Sprintf(eos(pbuf), " with %ld point%s,", u.urexp, plur(u.urexp)); - putstr(endwin, 0, pbuf); + dump_forward_putstr(endwin, 0, pbuf, done_stopprint); } - if (!done_stopprint) { - Sprintf(pbuf, "and %ld piece%s of gold, after %ld move%s.", umoney, - plur(umoney), moves, plur(moves)); - putstr(endwin, 0, pbuf); - } - if (!done_stopprint) { - Sprintf(pbuf, - "You were level %d with a maximum of %d hit point%s when you %s.", - u.ulevel, u.uhpmax, plur(u.uhpmax), ends[how]); - putstr(endwin, 0, pbuf); - putstr(endwin, 0, ""); - } + Sprintf(pbuf, "and %ld piece%s of gold, after %ld move%s.", umoney, + plur(umoney), moves, plur(moves)); + dump_forward_putstr(endwin, 0, pbuf, done_stopprint); + Sprintf(pbuf, + "You were level %d with a maximum of %d hit point%s when you %s.", + u.ulevel, u.uhpmax, plur(u.uhpmax), ends[how]); + dump_forward_putstr(endwin, 0, pbuf, done_stopprint); + dump_forward_putstr(endwin, 0, "", done_stopprint); if (!done_stopprint) display_nhwindow(endwin, TRUE); if (endwin != WIN_ERR) destroy_nhwindow(endwin); + dump_close_log(); /* "So when I die, the first thing I will see in Heaven is a * score list?" */ if (have_windows && !iflags.toptenwin) diff --git a/src/files.c b/src/files.c index 1138284a..ddba24d0 100644 --- a/src/files.c +++ b/src/files.c @@ -2267,8 +2267,15 @@ int src; free((genericptr_t) sysopt.debugfiles); sysopt.debugfiles = dupstr(bufp); } + } else if (src == SET_IN_SYS && match_varname(buf, "DUMPLOGFILE", 7)) { +#ifdef DUMPLOG + if (sysopt.dumplogfile) + free((genericptr_t) sysopt.dumplogfile); + sysopt.dumplogfile = dupstr(bufp); +#endif } else if (src == SET_IN_SYS && match_varname(buf, "GENERICUSERS", 12)) { - if (sysopt.genericusers) free(sysopt.genericusers); + if (sysopt.genericusers) + free((genericptr_t) sysopt.genericusers); sysopt.genericusers = dupstr(bufp); } else if (src == SET_IN_SYS && match_varname(buf, "SUPPORT", 7)) { if (sysopt.support) diff --git a/src/pline.c b/src/pline.c index c38ff33a..0f21b444 100644 --- a/src/pline.c +++ b/src/pline.c @@ -14,6 +14,10 @@ static char *FDECL(You_buf, (int)); static void FDECL(execplinehandler, (const char *)); #endif +#ifdef DUMPLOG +char* saved_plines[DUMPLOG_MSG_COUNT] = {0}; +#endif + /*VARARGS1*/ /* Note that these declarations rely on knowledge of the internals * of the variable argument handling stuff in "tradstdc.h" @@ -87,6 +91,15 @@ VA_DECL(const char *, line) return; } +#ifdef DUMPLOG + /* We hook here early to have options-agnostic output. */ + free(saved_plines[DUMPLOG_MSG_COUNT - 1]); + for (ln = 0; ln < DUMPLOG_MSG_COUNT - 1; ++ln) + saved_plines[DUMPLOG_MSG_COUNT - ln - 1] = saved_plines[DUMPLOG_MSG_COUNT - ln - 2]; + saved_plines[0] = malloc(strlen(line) + 1); + (void) strcpy(saved_plines[0], line); +#endif + msgtyp = msgtype_type(line, no_repeat); if (msgtyp == MSGTYP_NOSHOW || (msgtyp == MSGTYP_NOREP && !strcmp(line, prevmsg))) diff --git a/src/sys.c b/src/sys.c index deab17eb..c0803d48 100644 --- a/src/sys.c +++ b/src/sys.c @@ -32,6 +32,9 @@ sys_early_init() #else sysopt.debugfiles = dupstr(DEBUGFILES); #endif +#ifdef DUMPLOG + sysopt.dumplogfile = (char *) 0; +#endif sysopt.env_dbgfl = 0; /* haven't checked getenv("DEBUGFILES") yet */ sysopt.shellers = (char *) 0; sysopt.explorers = (char *) 0; @@ -95,6 +98,10 @@ sysopt_release() if (sysopt.debugfiles) free((genericptr_t) sysopt.debugfiles), sysopt.debugfiles = (char *) 0; +#ifdef DUMPLOG + if (sysopt.dumplogfile) + free((genericptr_t)sysopt.dumplogfile), sysopt.dumplogfile=(char *)0; +#endif if (sysopt.genericusers) free((genericptr_t) sysopt.genericusers), sysopt.genericusers = (char *) 0; diff --git a/src/windows.c b/src/windows.c index bdbd76e2..b2939d31 100644 --- a/src/windows.c +++ b/src/windows.c @@ -1032,4 +1032,243 @@ int behavior UNUSED, under UNUSED, over UNUSED; #endif /* STATUS_HILITES */ #endif /* STATUS_VIA_WINDOWPORT */ +STATIC_VAR struct window_procs dumplog_windowprocs_backup; +STATIC_PTR FILE* dumplog_file; + +#ifdef DUMPLOG +char * +dump_fmtstr(fmt, buf) +char *fmt; +char *buf; +{ + char *fp = fmt, *bp = buf; + int slen, len = 0; + char tmpbuf[BUFSZ]; + char verbuf[BUFSZ]; + + time_t now = getnow(); + int uid = getuid(); + + while (fp && *fp && len < BUFSZ-1) { + if (*fp == '%') { + fp++; + switch (*fp) { + default: goto finish; + case '\0': /* fallthrough */ + case '%': /* literal % */ + Sprintf(tmpbuf,"%%"); + break; + case 't': /* game start, timestamp */ + Sprintf(tmpbuf, "%ld", ubirthday); + break; + case 'T': /* current time, timestamp */ + Sprintf(tmpbuf, "%ld", now); + break; + case 'd': /* game start, YYYYMMDDhhmmss */ + Sprintf(tmpbuf, "%08ld%06ld", + yyyymmdd(ubirthday), hhmmss(ubirthday)); + break; + case 'D': /* current time, YYYYMMDDhhmmss */ + Sprintf(tmpbuf, "%08ld%06ld", yyyymmdd(now), hhmmss(now)); + break; + case 'v': /* version, eg. "3.6.1-0" */ + Sprintf(tmpbuf, "%s", version_string(verbuf)); + break; + case 'u': /* UID */ + Sprintf(tmpbuf, "%d", uid); + break; + case 'n': /* player name */ + Sprintf(tmpbuf, "%s", (plname ? plname : "unknown")); + break; + case 'N': /* first character of player name */ + Sprintf(tmpbuf, "%c", (plname ? *plname : 'u')); + break; + } + + slen = strlen(tmpbuf); + if (len + slen < BUFSZ-1) { + len += slen; + Sprintf(bp, "%s", tmpbuf); + bp += slen; + if (*fp) fp++; + } else + break; + } else { + *bp = *fp; + bp++; + fp++; + len++; + } + } + finish: + *bp = '\0'; + return buf; +} +#endif /* DUMPLOG */ + + +void +dump_open_log(now) +time_t now; +{ +#ifdef DUMPLOG + char buf[BUFSZ]; + char *fname; + +#ifdef SYSCF + if (!sysopt.dumplogfile) + return; + fname = dump_fmtstr(sysopt.dumplogfile, buf); +#else + fname = dump_fmtstr(DUMPLOG_FILE, buf); +#endif + + dumplog_file = fopen(fname, "w"); + dumplog_windowprocs_backup = windowprocs; +#endif +} + +void +dump_close_log() +{ + if (dumplog_file) { + fclose(dumplog_file); + dumplog_file = NULL; + } +} + +void +dump_putc(ch) +int ch; +{ + /* Not very efficient, but we mostly don't care. */ + if (dumplog_file) + putc(ch, dumplog_file); +} + +void +dump_forward_putstr(win, attr, str, no_forward) +winid win; +int attr; +const char* str; +int no_forward; +{ + if (dumplog_file) + fprintf(dumplog_file, "%s\n", str); + if (!no_forward) + putstr(win, attr, str); +} + +STATIC_OVL void +dump_putstr(win, attr, str) +winid win; +int attr; +const char* str; +{ + if (dumplog_file) + fprintf(dumplog_file, "%s\n", str); +} + +STATIC_OVL winid +dump_create_nhwindow(dummy) +int dummy; +{ + return dummy; +} + +STATIC_OVL void +dump_clear_nhwindow(win) +winid win; +{ + +} + +STATIC_OVL void +dump_display_nhwindow(win, p) +winid win; +BOOLEAN_P p; +{ + +} + +STATIC_OVL void +dump_destroy_nhwindow(win) +winid win; +{ + +} + +STATIC_OVL void +dump_start_menu(win) +winid win; +{ + +} + +STATIC_OVL void +dump_add_menu(win, glyph, identifier, ch, gch, attr, str, preselected) +winid win; +int glyph; +const ANY_P* identifier; +CHAR_P ch; +CHAR_P gch; +int attr; +const char* str; +BOOLEAN_P preselected; +{ + if (dumplog_file) { + if (glyph == NO_GLYPH) + fprintf(dumplog_file, " %s\n", str); + else + fprintf(dumplog_file, " %c - %s\n", ch, str); + } +} + +STATIC_OVL void +dump_end_menu(win, str) +winid win; +const char* str; +{ + if (dumplog_file) { + if (str) + fprintf(dumplog_file, "%s\n", str); + else + fputs("\n", dumplog_file); + } +} + +STATIC_OVL int +dump_select_menu(win, index, item) +winid win; +int index; +MENU_ITEM_P** item; +{ + *item = NULL; + return 0; +} + +void +dump_redirect(flag) +boolean flag; +{ + if (dumplog_file) { + if (flag) { + windowprocs.win_create_nhwindow = dump_create_nhwindow; + windowprocs.win_clear_nhwindow = dump_clear_nhwindow; + windowprocs.win_display_nhwindow = dump_display_nhwindow; + windowprocs.win_destroy_nhwindow = dump_destroy_nhwindow; + windowprocs.win_start_menu = dump_start_menu; + windowprocs.win_add_menu = dump_add_menu; + windowprocs.win_end_menu = dump_end_menu; + windowprocs.win_select_menu = dump_select_menu; + windowprocs.win_putstr = dump_putstr; + } else { + windowprocs = dumplog_windowprocs_backup; + } + iflags.in_dumplog = flag; + } else { + iflags.in_dumplog = FALSE; + } +} + /*windows.c*/ diff --git a/sys/unix/hints/linux b/sys/unix/hints/linux index 888fd905..4310e6af 100644 --- a/sys/unix/hints/linux +++ b/sys/unix/hints/linux @@ -25,6 +25,7 @@ CFLAGS1=-DCOMPRESS=\"/bin/gzip\" -DCOMPRESS_EXTENSION=\".gz\" CFLAGS+=-DSYSCF -DSYSCF_FILE=\"$(HACKDIR)/sysconf\" -DSECURE CFLAGS+=-DTIMED_DELAY CFLAGS+=-DHACKDIR=\"$(HACKDIR)\" +CFLAGS+=-DDUMPLOG LINK=$(CC) # Only needed for GLIBC stack trace: diff --git a/sys/unix/sysconf b/sys/unix/sysconf index f7426f78..d83aeff7 100644 --- a/sys/unix/sysconf +++ b/sys/unix/sysconf @@ -84,6 +84,20 @@ MAXPLAYERS=10 # overridden via DEBUGFILES environment variable. #DEBUGFILES=* +# Save end of game dump log to this file. +# Only available if NetHack was compiled with DUMPLOG +# Allows following placeholders: +# %% literal '%' +# %v version (eg. "3.6.1-0") +# %u game UID +# %t game start time, UNIX timestamp format +# %T current time, UNIX timestamp format +# %d game start time, YYYYMMDDhhmmss format +# %D current time, YYYYMMDDhhmmss format +# %n player name +# %N first character of player name +#DUMPLOGFILE=/tmp/nethack.%n.%d.log + # Try to get more info in case of a program bug or crash. Only used # if the program is built with the PANICTRACE compile-time option enabled. # By default PANICTRACE is enabled if BETA is defined, otherwise disabled. diff --git a/sys/winnt/sysconf b/sys/winnt/sysconf index d6cc37a6..2ce1da30 100644 --- a/sys/winnt/sysconf +++ b/sys/winnt/sysconf @@ -18,6 +18,20 @@ WIZARDS=* # Only available if game has been compiled with DEBUG. #DEBUGFILES=* +# Save end of game dump log to this file. +# Only available if NetHack was compiled with DUMPLOG +# Allows following placeholders: +# %% literal '%' +# %v version (eg. "3.6.1-0") +# %u game UID +# %t game start time, UNIX timestamp format +# %T current time, UNIX timestamp format +# %d game start time, YYYYMMDDhhmmss format +# %D current time, YYYYMMDDhhmmss format +# %n player name +# %N first character of player name +#DUMPLOGFILE=nethack-%n-%d.log + # Limit the number of simultaneous games (see also nethack.sh). #MAXPLAYERS=10 diff --git a/util/makedefs.c b/util/makedefs.c index 25614d8b..7ebd5136 100644 --- a/util/makedefs.c +++ b/util/makedefs.c @@ -1434,6 +1434,9 @@ static const char *build_opts[] = { #ifdef DLB "data librarian", #endif +#ifdef DUMPLOG + "end-of-game dumplogs", +#endif #ifdef MFLOPPY "floppy drive support", #endif -- 2.11.4.GIT