Add end-of-game dumplogs
authorPasi Kallinen <paxed@alt.org>
Sun, 19 Feb 2017 13:30:46 +0000 (15:30 +0200)
committerPasi Kallinen <paxed@alt.org>
Sun, 19 Feb 2017 13:33:27 +0000 (15:33 +0200)
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.

18 files changed:
doc/Guidebook.mn
doc/Guidebook.tex
doc/fixes36.1
include/config.h
include/extern.h
include/flag.h
include/sys.h
src/botl.c
src/detect.c
src/end.c
src/files.c
src/pline.c
src/sys.c
src/windows.c
sys/unix/hints/linux
sys/unix/sysconf
sys/winnt/sysconf
util/makedefs.c

index 8b73973365f9e2ecad5f4525d9ad2ecd40026025..cf0c5f73ccf5aac635722b1dd8d918fd0bab7602 100644 (file)
@@ -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
index b4243b8dbfdac59c7f1a5f7a4aa64e001598c0c3..e2ea3bbf60054ab649a11f743cd2e414bea539f3 100644 (file)
@@ -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
index 3a0149e98a90efe71de9e272a06a17da2a935dee..39188f466c7cb9eec07e1eeea5a2b06cb7abfc4e 100644 (file)
@@ -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
index 9fc7dc6fed12dd68dadeedca7965b88ec7e5e93a..ba5db5443b904544e123a7abbaefd589a2194d86 100644 (file)
@@ -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
index 52b810a60128689f94ecab57855a1890c7e78d84..ca51e491f6099c8dc4a1512c34beb5da36244d7c 100644 (file)
@@ -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);
index 612a147205d428cf768f6c9094922419360e0f6e..f5072081a9ea7dfdf89e401f57fb2056df449163 100644 (file)
@@ -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
      */
index bf652ede28eabdbfd78960bf811f0a3bd218d82c..91c5d3522fd6f9d933a4696c89ee2ae346ddf5dc 100644 (file)
@@ -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;
index f390861742da646844ca10d882f270b590c6c897..e6a771bdc178526ada3448f7222df1a46d1e5835 100644 (file)
@@ -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)
index 7668d77bb82eae5600e071f09343dcdb057f967b..8a5af31ddfb5bb9e89af9017f50766faa9777548 100644 (file)
@@ -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);
             }
 
index 09213205b2e6f5ab6120e989f3804b54a94870c1..e2e8a13afcfb001415414a51478a621d85cdbb68 100644 (file)
--- 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)
index 1138284a924c97550039d0f40bfd0d5bc8fd5df5..ddba24d09cd184815d943a1ea975f87dbcfa004a 100644 (file)
@@ -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)
index c38ff33af06dd8daa8ffffc00bc71cd72c702e68..0f21b444b43ac9d46a73423696adc0f59469fc06 100644 (file)
@@ -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)))
index deab17eb5ef82254bf138df689446ccf43aa0b56..c0803d480cc8700d67cf258e5044cb4648ceca46 100644 (file)
--- a/src/sys.c
+++ b/src/sys.c
@@ -31,6 +31,9 @@ sys_early_init()
     sysopt.debugfiles = (char *) 0;
 #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;
@@ -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;
index bdbd76e2d84b486ed61cd97d1f717981596dec40..b2939d31fea5f0149ea46eaedaf4f927fd786133 100644 (file)
@@ -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*/
index 888fd9050157ce3864b14e6a9e8971eaf77f2c77..4310e6afe41bf60852a08ac135903747401aac1c 100644 (file)
@@ -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:
index f7426f7857af72220e6fc859d1be3c5384b0b480..d83aeff7df69b4e9aa1091483abdae3afdd38f23 100644 (file)
@@ -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.
index d6cc37a6a069d2804b99d53f63897b2157db39d7..2ce1da300ea8d4916d91201ca51cc3d08213bc68 100644 (file)
@@ -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
 
index 25614d8b28bc778945835e7086cb643ae9751acd..7ebd5136bf55c62d7d1104830c2e26f72b5d7f62 100644 (file)
@@ -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