From 337e1580093d86d3417291deeebd8b0547c19a34 Mon Sep 17 00:00:00 2001
From: "nethack.rankin" <nethack.rankin>
Date: Thu, 16 Feb 2012 02:40:24 +0000
Subject: [PATCH] #overview enhancements (trunk only)

1) add graves to the dungeon features being tracked;
2) report on known bones (determined by seeing map spot(s) where previous
   hero(es) died since there's no guarantee of graves or ghosts);
3) add automatic annotations for oracle, sokoban, bigroom, rogue level,
   Ft.Ludios, castle, valley, and Moloch's sanctum.  For bigroom and rogue
   level you just need to visit that level, for the others you need to get
   far enough along to learn something specific (oracle: her room, sokoban:
   annotation is either "solved" or "unsolved" depending upon whether all
   the holes and pits have been filled, fort and castle: see the drawbridge,
   valley and sanctum: see inside the tended temple).  Discovering the
   relevant locations via magic mapping counts as "far enough along".

     There should probably also be automatic annotations for Medusa and the
vibrating square but I'm not sure what criteria should be used for the
former or what phrasing to use for the latter.  Demon lord/prince lairs fall
into similar category as Medusa.

     TODO: add final #overview as an end of game disclosure option.  (I was
planning this even before I saw that nitrohack has implemented it....)
---
 include/dungeon.h    |  77 ++++----
 include/extern.h     |   4 +
 include/patchlevel.h |   2 +-
 src/bones.c          |   5 +
 src/dungeon.c        | 455 ++++++++++++++++++++++++++++++-------------
 src/hack.c           |  14 +-
 src/priest.c         |   5 +
 src/restore.c        |  12 +-
 src/save.c           |  19 +-
 9 files changed, 412 insertions(+), 181 deletions(-)

diff --git a/include/dungeon.h b/include/dungeon.h
index b5fd9d8ca..f92592ea9 100644
--- a/include/dungeon.h
+++ b/include/dungeon.h
@@ -1,5 +1,4 @@
 /* NetHack 3.5	dungeon.h	$Date$  $Revision$ */
-/*	SCCS Id: @(#)dungeon.h	3.5	2007/06/15	*/
 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
 /* NetHack may be freely redistributed.  See license for details. */
 
@@ -191,47 +190,59 @@ struct linfo {
 #define MSA_NEUTRAL 2
 #define MSA_CHAOTIC 3
 
-typedef struct mapseen_feat {
-	/* feature knowledge that must be calculated from levl array */
-	Bitfield(nfount, 2);
-	Bitfield(nsink, 2);
-	Bitfield(naltar, 2);
-	Bitfield(msalign, 2); /* corresponds to MSA_* above */
-	Bitfield(nthrone, 2);
-	Bitfield(ntree, 2);
-	/* water, lava, ice are too verbose so commented out for now */
-	/*
-	Bitfield(water, 1);
-	Bitfield(lava, 1);
-	Bitfield(ice, 1);
-	*/
-
-	/* calculated from rooms array */
-	Bitfield(nshop, 2);
-	Bitfield(ntemple, 2);
-	Bitfield(shoptype, 5);
-
-	Bitfield(forgot, 1); /* player has forgotten about this level? */
-} mapseen_feat;
-
-/* for mapseen->rooms */
-#define MSR_SEEN		1
-
 /* what the player knows about a single dungeon level */
 /* initialized in mklev() */
 typedef struct mapseen  {
 	struct mapseen *next; /* next map in the chain */
 	branch *br; /* knows about branch via taking it in goto_level */
 	d_level lev; /* corresponding dungeon level */
-
-	mapseen_feat feat;
-
+	struct mapseen_feat {
+	    /* feature knowledge that must be calculated from levl array */
+	    Bitfield(nfount, 2);
+	    Bitfield(nsink, 2);
+	    Bitfield(naltar, 2);
+	    Bitfield(nthrone, 2);
+
+	    Bitfield(ngrave, 2);
+	    Bitfield(ntree, 2);
+	    Bitfield(water, 2);
+	    Bitfield(lava, 2);
+
+	    Bitfield(ice, 2);
+	    /* calculated from rooms array */
+	    Bitfield(nshop, 2);
+	    Bitfield(ntemple, 2);
+	    /* altar alignment; MSA_NONE if there is more than one and
+	       they aren't all the same */
+	    Bitfield(msalign, 2);
+
+	    Bitfield(shoptype, 5);
+	} feat;
+	struct mapseen_flags {
+	    Bitfield(forgot, 1); /* player has forgotten about this level */
+	    Bitfield(knownbones, 1); /* player aware of bones */
+	    Bitfield(oracle, 1);
+	    Bitfield(sokosolved, 1);
+	    Bitfield(bigroom, 1);
+	    Bitfield(castle, 1);
+	    Bitfield(castletune, 1); /* add tune hint to castle annotation */
+	    Bitfield(valley, 1);
+
+	    Bitfield(msanctum, 1);
+	    Bitfield(ludios, 1);
+# ifdef REINCARNATION
+	    Bitfield(roguelevel, 1);
+# endif
+	} flags;
 	/* custom naming */
 	char *custom;
 	unsigned custom_lth;
-
-	/* maybe this should just be in struct mkroom? */
-	schar rooms[(MAXNROFROOMS+1)*2];
+	struct mapseen_rooms {
+	    Bitfield(seen, 1);
+	    Bitfield(untended, 1);	/* flag for shop without shk */
+	} msrooms[(MAXNROFROOMS+1)*2];	/* same size as rooms[] */
+	/* dead heroes; might not have graves or ghosts */
+	struct cemetery *final_resting_place; /* same as level.bonesinfo */
 } mapseen;
 
 #endif /* DUNGEON_OVERVIEW */
diff --git a/include/extern.h b/include/extern.h
index e21631c18..5f4a7b140 100644
--- a/include/extern.h
+++ b/include/extern.h
@@ -590,6 +590,8 @@ E int NDECL(dooverview);
 E void FDECL(forget_mapseen, (int));
 E void FDECL(init_mapseen, (d_level *));
 E void NDECL(recalc_mapseen);
+E void FDECL(mapseen_temple, (struct monst *));
+E void FDECL(room_discovered, (int));
 E void FDECL(recbranch_mapseen, (d_level *, d_level *));
 E void FDECL(remdun_mapseen, (int));
 #endif /* DUNGEON_OVERVIEW */
@@ -1931,6 +1933,7 @@ E NhRegion* FDECL(create_gas_cloud, (XCHAR_P, XCHAR_P, int, int));
 
 E void FDECL(inven_inuse, (BOOLEAN_P));
 E int FDECL(dorecover, (int));
+E void FDECL(restcemetery, (int,struct cemetery **));
 E void FDECL(trickery, (char *));
 E void FDECL(getlev, (int,int,XCHAR_P,BOOLEAN_P));
 E void FDECL(get_plname_from_file, (int, char *));
@@ -2030,6 +2033,7 @@ E void FDECL(def_bclose, (int));
 #if defined(ZEROCOMP)
 E void FDECL(zerocomp_bclose, (int));
 #endif
+E void FDECL(savecemetery, (int,int,struct cemetery **));
 E void FDECL(savefruitchn, (int,int));
 E void FDECL(store_plname_in_file, (int));
 E void NDECL(free_dungeons);
diff --git a/include/patchlevel.h b/include/patchlevel.h
index 2459c53ed..578386c9c 100644
--- a/include/patchlevel.h
+++ b/include/patchlevel.h
@@ -13,7 +13,7 @@
  * Incrementing EDITLEVEL can be used to force invalidation of old bones
  * and save files.
  */
-#define EDITLEVEL	52
+#define EDITLEVEL	53
 
 #define COPYRIGHT_BANNER_A \
 "NetHack, Copyright 1985-2012"
diff --git a/src/bones.c b/src/bones.c
index 2c05124d3..4b9738d3a 100644
--- a/src/bones.c
+++ b/src/bones.c
@@ -477,6 +477,11 @@ struct obj *corpse;
 		aligns[1 - u.ualign.type].filecode);
 	formatkiller(newbones->how, sizeof newbones->how, how);
 	Strcpy(newbones->when, yyyymmddhhmmss(when));
+#ifdef DUNGEON_OVERVIEW
+	/* final resting place, used to decide when bones are discovered */
+	newbones->frpx = u.ux, newbones->frpy = u.uy;
+	newbones->bonesknown = FALSE;
+#endif
 	/* if current character died on a bones level, the cememtery list
 	   will have multiple entries, most recent (this dead hero) first */
 	newbones->next = level.bonesinfo;
diff --git a/src/dungeon.c b/src/dungeon.c
index 5583fffec..22987f1cb 100644
--- a/src/dungeon.c
+++ b/src/dungeon.c
@@ -5,6 +5,7 @@
 #include "hack.h"
 #include "dgn_file.h"
 #include "dlb.h"
+#include "lev.h"
 
 #define DUNGEON_FILE	"dungeon"
 
@@ -66,6 +67,8 @@ STATIC_DCL void FDECL(print_mapseen, (winid,mapseen *,BOOLEAN_P));
 STATIC_DCL boolean FDECL(interest_mapseen, (mapseen *));
 STATIC_DCL const char *FDECL(seen_string, (XCHAR_P,const char *));
 STATIC_DCL const char *FDECL(br_string2, (branch *));
+STATIC_DCL const char *FDECL(shop_string, (int));
+STATIC_DCL char *FDECL(tunesuffix, (mapseen *,char *));
 #endif /* DUNGEON_OVERVIEW */
 
 #ifdef DEBUG
@@ -170,13 +173,13 @@ save_dungeon(fd, perform_write, free_data)
 	}
 	branches = 0;
 #ifdef DUNGEON_OVERVIEW
-    for (curr_ms = mapseenchn; curr_ms; curr_ms = next_ms) {
-	next_ms = curr_ms->next;
-	if (curr_ms->custom)
-	    free((genericptr_t)curr_ms->custom);
-	free((genericptr_t) curr_ms);
-    }
-    mapseenchn = 0;
+	for (curr_ms = mapseenchn; curr_ms; curr_ms = next_ms) {
+	    next_ms = curr_ms->next;
+	    if (curr_ms->custom)
+		free((genericptr_t)curr_ms->custom);
+	    free((genericptr_t) curr_ms);
+	}
+	mapseenchn = 0;
 #endif /* DUNGEON_OVERVIEW */
     }
 }
@@ -1897,8 +1900,7 @@ donamelevel()
     return 0;
 }
 
-/* find the particular mapseen object in the chain */
-/* may return 0 */
+/* find the particular mapseen object in the chain; may return null */
 STATIC_OVL mapseen *
 find_mapseen(lev)
 d_level *lev;
@@ -1916,6 +1918,7 @@ forget_mapseen(ledger_num)
 int ledger_num;
 {
     mapseen *mptr;
+    struct cemetery *bp;
 
     for (mptr = mapseenchn; mptr; mptr = mptr->next)
 	if (dungeons[mptr->lev.dnum].ledger_start + 
@@ -1923,7 +1926,7 @@ int ledger_num;
 
     /* if not found, then nothing to forget */
     if (mptr) {
-	mptr->feat.forgot = 1;
+	mptr->flags.forgot = 1;
 	mptr->br = (branch *)0;
 
 	/* custom names are erased, not just forgotten until revisted */
@@ -1932,7 +1935,9 @@ int ledger_num;
 	     free((genericptr_t)mptr->custom);
 	     mptr->custom = (char *)0;
 	}
-	(void) memset((genericptr_t) mptr->rooms, 0, sizeof mptr->rooms);
+	(void) memset((genericptr_t) mptr->msrooms, 0, sizeof mptr->msrooms);
+	for (bp = mptr->final_resting_place; bp; bp = bp->next)
+	    bp->bonesknown = FALSE;
     }
 }
 
@@ -1942,51 +1947,50 @@ int fd;
 mapseen *mptr;
 {
     branch *curr;
-    int count;
+    int brindx;
 
-    count = 0;
-    for (curr = branches; curr; curr = curr->next) {
+    for (brindx = 0, curr = branches; curr; curr = curr->next, ++brindx)
 	if (curr == mptr->br) break;
-	count++;
-    }
+    bwrite(fd, (genericptr_t) &brindx, sizeof brindx);
 
-    bwrite(fd, (genericptr_t) &count, sizeof(int));
-    bwrite(fd, (genericptr_t) &mptr->lev, sizeof(d_level));
-    bwrite(fd, (genericptr_t) &mptr->feat, sizeof(mapseen_feat));
-    bwrite(fd, (genericptr_t) &mptr->custom_lth, sizeof(unsigned));
+    bwrite(fd, (genericptr_t) &mptr->lev, sizeof mptr->lev);
+    bwrite(fd, (genericptr_t) &mptr->feat, sizeof mptr->feat);
+    bwrite(fd, (genericptr_t) &mptr->flags, sizeof mptr->flags);
+    bwrite(fd, (genericptr_t) &mptr->custom_lth, sizeof mptr->custom_lth);
     if (mptr->custom_lth)
-	bwrite(fd, (genericptr_t) mptr->custom, 
-	       sizeof(char) * mptr->custom_lth);
-    bwrite(fd, (genericptr_t) &mptr->rooms, sizeof(mptr->rooms));
+	bwrite(fd, (genericptr_t) mptr->custom, mptr->custom_lth);
+    bwrite(fd, (genericptr_t) &mptr->msrooms, sizeof mptr->msrooms);
+    savecemetery(fd, WRITE_SAVE, &mptr->final_resting_place);
 }
 
 STATIC_OVL mapseen *
 load_mapseen(fd)
 int fd;
 {
-    int branchnum, count;
+    int branchnum, brindx;
     mapseen *load;
     branch *curr;
 
-    load = (mapseen *) alloc(sizeof(mapseen));
-    mread(fd, (genericptr_t) &branchnum, sizeof(int));
+    load = (mapseen *) alloc(sizeof *load);
 
-    count = 0;
-    for (curr = branches; curr; curr = curr->next) {
-	if (count == branchnum) break;
-	count++;
-    }
+    mread(fd, (genericptr_t) &branchnum, sizeof branchnum);
+    for (brindx = 0, curr = branches; curr; curr = curr->next, ++brindx)
+	if (brindx == branchnum) break;
     load->br = curr;
 
-    mread(fd, (genericptr_t) &load->lev, sizeof(d_level));
-    mread(fd, (genericptr_t) &load->feat, sizeof(mapseen_feat));
-    mread(fd, (genericptr_t) &load->custom_lth, sizeof(unsigned));
-    if (load->custom_lth > 0) {
-	load->custom = (char *) alloc(sizeof(char) * load->custom_lth);
-	mread(fd, (genericptr_t) load->custom, 
-	      sizeof(char) * load->custom_lth);
-    } else load->custom = (char *) 0;
-    mread(fd, (genericptr_t) &load->rooms, sizeof(load->rooms));
+    mread(fd, (genericptr_t) &load->lev, sizeof load->lev);
+    mread(fd, (genericptr_t) &load->feat, sizeof load->feat);
+    mread(fd, (genericptr_t) &load->flags, sizeof load->flags);
+    mread(fd, (genericptr_t) &load->custom_lth, sizeof load->custom_lth);
+    if (load->custom_lth) {
+	/* length doesn't include terminator (which isn't saved & restored) */
+	load->custom = (char *) alloc(load->custom_lth + 1);
+	mread(fd, (genericptr_t) load->custom, load->custom_lth);
+	load->custom[load->custom_lth] = '\0';
+    } else
+	load->custom = 0;
+    mread(fd, (genericptr_t) &load->msrooms, sizeof load->msrooms);
+    restcemetery(fd, &load->final_resting_place);
 
     return load;
 }
@@ -2006,6 +2010,8 @@ int dnum;
 	    *mptraddr = mptr->next;
 	    if (mptr->custom)
 		free((genericptr_t) mptr->custom);
+	    if (mptr->final_resting_place)
+		savecemetery(-1, FREE_SAVE, &mptr->final_resting_place);
 	    free((genericptr_t) mptr);
 	} else
 	    mptraddr = &mptr->next;
@@ -2023,9 +2029,10 @@ d_level *lev;
 
     init = (mapseen *) alloc(sizeof *init);
     (void) memset((genericptr_t)init, 0, sizeof *init);
-    /* memset is fine for feature bits and rooms array;
+    /* memset is fine for feature bits, flags, and rooms array;
        explicitly initialize pointers to null */
     init->next = 0, init->br = 0, init->custom = 0;
+    init->final_resting_place = 0;
     /* lastseentyp[][] is reused for each level, so get rid of
        previous level's data */
     (void) memset((genericptr_t)lastseentyp, 0, sizeof lastseentyp);
@@ -2059,9 +2066,10 @@ d_level *lev;
 	 (feat).nsink || \
 	 (feat).nthrone || \
 	 (feat).naltar || \
+	 (feat).ngrave || \
+	 (feat).ntree || \
 	 (feat).nshop || \
-	 (feat).ntemple || \
-	 (feat).ntree)
+	 (feat).ntemple)
 	/*
 	|| ((feat).water) || \
 	((feat).ice) || \
@@ -2073,9 +2081,24 @@ STATIC_OVL boolean
 interest_mapseen(mptr)
 mapseen *mptr;
 {
-    return on_level(&u.uz, &mptr->lev) ||
-		(!mptr->feat.forgot &&
-		    (INTEREST(mptr->feat) || (mptr->custom) || (mptr->br)));
+    if (on_level(&u.uz, &mptr->lev)) return TRUE;
+    if (mptr->flags.forgot) return FALSE;
+    if (In_endgame(&u.uz) && !In_endgame(&mptr->lev)) return FALSE;
+    if (mptr->flags.oracle || mptr->flags.bigroom ||
+# ifdef REINCARNATION
+	mptr->flags.roguelevel ||
+# endif
+	mptr->flags.castle || mptr->flags.valley ||
+	mptr->flags.msanctum) return TRUE;
+    /* when in Sokoban, list all sokoban levels visited; when not in it,
+       list any visited Sokoban level which remains unsolved (could only
+       be furthest one reached, unless level teleporting in wizard mode) */
+    if (In_sokoban(&mptr->lev) &&
+	(In_sokoban(&u.uz) || !mptr->flags.sokosolved)) return TRUE;
+    return (INTEREST(mptr->feat) ||
+	    (mptr->final_resting_place &&
+		(mptr->flags.knownbones || wizard)) ||
+	    mptr->custom || mptr->br);
 }
 
 /* recalculate mapseen for the current level */
@@ -2083,8 +2106,10 @@ void
 recalc_mapseen()
 {
     mapseen *mptr;
-    struct monst *shkp;
-    unsigned int x, y, ridx;
+    struct monst *mtmp;
+    struct cemetery *bp, **bonesaddr;
+    unsigned i, ridx;
+    int x, y, count, atmp;
 
     /* Should not happen in general, but possible if in the process
      * of being booted from the quest.  The mapseen object gets
@@ -2092,36 +2117,58 @@ recalc_mapseen()
      */
     if (!(mptr = find_mapseen(&u.uz))) return;
 
-    /* reset all features; mptr->feat.* = 0, mptr->feat.forgot = 0; */
-    memset((genericptr_t) &mptr->feat, 0, sizeof(mapseen_feat));
+    /* reset all features; mptr->feat.* = 0; */
+    (void) memset((genericptr_t) &mptr->feat, 0, sizeof mptr->feat);
+    /* reset most flags; some level-specific ones are left as-is */
+    mptr->flags.knownbones = 0;
+    mptr->flags.sokosolved = In_sokoban(&u.uz) && !Sokoban;
+    /* mptr->flags.bigroom retains previous value when hero can't see */
+    if (!Blind)
+	mptr->flags.bigroom = Is_bigroom(&u.uz);
+    else if (mptr->flags.forgot)
+	mptr->flags.bigroom = 0;
+# ifdef REINCARNATION
+    mptr->flags.roguelevel = Is_rogue_level(&u.uz);
+# endif
+    mptr->flags.oracle = 0;	/* recalculated during room traversal below */
+    mptr->flags.castletune = 0;
+    /* flags.castle, flags.valley, flags.msanctum retain previous value */
+    mptr->flags.forgot = 0;
 
     /* track rooms the hero is in */
-    for (x = 0; x < sizeof(u.urooms); x++) {
-	if (!u.urooms[x]) continue;
-
-	ridx = u.urooms[x] - ROOMOFFSET;
-	if (rooms[ridx].rtype < SHOPBASE ||
-		((shkp = shop_keeper(u.urooms[x])) && inhishop(shkp)))
-	    mptr->rooms[ridx] |= MSR_SEEN;
-	else
-	    /* shops without shopkeepers are no shops at all */
-	    mptr->rooms[ridx] &= ~MSR_SEEN;
+    for (i = 0; i < SIZE(u.urooms); ++i) {
+	if (!u.urooms[i]) continue;
+
+	ridx = u.urooms[i] - ROOMOFFSET;
+	mptr->msrooms[ridx].seen = 1;
+	mptr->msrooms[ridx].untended = (rooms[ridx].rtype >= SHOPBASE) ?
+		      (!(mtmp = shop_keeper(u.urooms[i])) || !inhishop(mtmp)) :
+					 (rooms[ridx].rtype == TEMPLE) ?
+		    (!(mtmp = findpriest(u.urooms[i])) || !inhistemple(mtmp)) :
+					   0;
     }
 
     /* recalculate room knowledge: for now, just shops and temples
      * this could be extended to an array of 0..SHOPBASE
      */
-    for (x = 0; x < sizeof(mptr->rooms); x++) {
-	if (mptr->rooms[x] & MSR_SEEN) {
-	    if (rooms[x].rtype >= SHOPBASE) {
-		if (!mptr->feat.nshop)
-		    mptr->feat.shoptype = rooms[x].rtype;
-		else if (mptr->feat.shoptype != (unsigned)rooms[x].rtype)
+    for (i = 0; i < SIZE(mptr->msrooms); ++i) {
+	if (mptr->msrooms[i].seen) {
+	    if (rooms[i].rtype >= SHOPBASE) {
+		if (mptr->msrooms[i].untended)
+		    mptr->feat.shoptype = SHOPBASE - 1;
+		else if (!mptr->feat.nshop)
+		    mptr->feat.shoptype = rooms[i].rtype;
+		else if (mptr->feat.shoptype != (unsigned)rooms[i].rtype)
 		    mptr->feat.shoptype = 0;
-		mptr->feat.nshop = min(mptr->feat.nshop + 1, 3);
-	    } else if (rooms[x].rtype == TEMPLE)
+		count = mptr->feat.nshop + 1;
+		if (count <= 3) mptr->feat.nshop = count;
+	    } else if (rooms[i].rtype == TEMPLE) {
 		/* altar and temple alignment handled below */
-		mptr->feat.ntemple = min(mptr->feat.ntemple + 1, 3);
+		count = mptr->feat.ntemple + 1;
+		if (count <= 3) mptr->feat.ntemple = count;
+	    } else if (rooms[i].orig_rtype == DELPHI) {
+		mptr->flags.oracle = 1;
+	    }
 	}
     }
 
@@ -2136,6 +2183,8 @@ recalc_mapseen()
      * we could track "features" and then update them all here, and keep
      * track of when new features are created or destroyed, but this
      * seemed the most elegant, despite adding more data to struct rm.
+     * [3.5.0: we're using lastseentyp[][] rather than level.locations
+     * to track the features seen.]
      *
      * Although no current windowing systems (can) do this, this would add
      * the ability to have non-dungeon glyphs float above the last known
@@ -2144,11 +2193,10 @@ recalc_mapseen()
     if (!Levitation)
 	lastseentyp[u.ux][u.uy] = levl[u.ux][u.uy].typ;
 
-    for (x = 0; x < COLNO; x++) {
+    for (x = 1; x < COLNO; x++) {
 	for (y = 0; y < ROWNO; y++) {
 	    if (cansee(x, y)) {
-		struct monst *mtmp = m_at(x, y);
-
+		mtmp = m_at(x, y);
 		lastseentyp[x][y] =
 	      (mtmp && mtmp->m_ap_type == M_AP_FURNITURE && canseemon(mtmp)) ?
 				    cmap_to_type(mtmp->mappearance) :
@@ -2158,39 +2206,121 @@ recalc_mapseen()
 	    switch (lastseentyp[x][y]) {
 #if 0
 	    case ICE:
-		mptr->feat.ice = 1;
+		count = mptr->feat.ice + 1;
+		if (count <= 3) mptr->feat.ice = count;
 		break;
 	    case POOL:
 	    case MOAT:
 	    case WATER:
-		mptr->feat.water = 1;
+		count = mptr->feat.water + 1;
+		if (count <= 3) mptr->feat.water = count;
 		break;
 	    case LAVAPOOL:
-		mptr->feat.lava = 1;
+		count = mptr->feat.lava + 1;
+		if (count <= 3) mptr->feat.lava = count;
 		break;
 #endif
 	    case TREE:
-		mptr->feat.ntree = min(mptr->feat.ntree + 1, 3);
+		count = mptr->feat.ntree + 1;
+		if (count <= 3) mptr->feat.ntree = count;
 		break;
 	    case FOUNTAIN:
-		mptr->feat.nfount = min(mptr->feat.nfount + 1, 3);
+		count = mptr->feat.nfount + 1;
+		if (count <= 3) mptr->feat.nfount = count;
 		break;
 	    case THRONE:
-		mptr->feat.nthrone = min(mptr->feat.nthrone + 1, 3);
+		count = mptr->feat.nthrone + 1;
+		if (count <= 3) mptr->feat.nthrone = count;
 		break;
 	    case SINK:
-		mptr->feat.nsink = min(mptr->feat.nsink + 1, 3);
+		count = mptr->feat.nsink + 1;
+		if (count <= 3) mptr->feat.nsink = count;
+		break;
+	    case GRAVE:
+		count = mptr->feat.ngrave + 1;
+		if (count <= 3) mptr->feat.ngrave = count;
 		break;
 	    case ALTAR:
+		atmp = Amask2msa(levl[x][y].altarmask);
 		if (!mptr->feat.naltar)
-		    mptr->feat.msalign = Amask2msa(levl[x][y].altarmask);
-		else if (mptr->feat.msalign != Amask2msa(levl[x][y].altarmask))
+		    mptr->feat.msalign = atmp;
+		else if (mptr->feat.msalign != atmp)
 		    mptr->feat.msalign = MSA_NONE;
-		mptr->feat.naltar = min(mptr->feat.naltar + 1, 3);
+		count = mptr->feat.naltar + 1;
+		if (count <= 3) mptr->feat.naltar = count;
+		break;
+	    /*	An automatic annotation is added to the Castle and
+	     *	to Fort Ludios once their struncture's main entrance
+	     *	has been seen (in person or via magic mapping).
+	     * DOOR: possibly a lowered drawbridge's open portcullis;
+	     * DBWALL: a raised drawbridge's "closed door";
+	     * DRAWBRIDGE_DOWN: the span provided by lowered bridge,
+	     *	with moat or other terrain hidden underneath;
+	     * DRAWBRIDGE_UP: moat in front of a raised drawbridge,
+	     *	not recognizable as a bridge location unless/until
+	     *	the adjacent DBWALL has been seen.
+	     */
+	    case DOOR:
+		if (is_drawbridge_wall(x, y) < 0) break;
+		/* else FALLTHRU */
+	    case DBWALL:
+	    case DRAWBRIDGE_DOWN:
+		if (Is_stronghold(&u.uz))
+		    mptr->flags.castle = 1, mptr->flags.castletune = 1;
+		else if (Is_knox(&u.uz))
+		    mptr->flags.ludios = 1;
+		break;
+	    default:
 		break;
 	    }
 	}
     }
+
+    if (level.bonesinfo && !mptr->final_resting_place) {
+	/* clone the bonesinfo so we aren't dependent upon this
+	   level being in memory */
+	bonesaddr = &mptr->final_resting_place;
+	bp = level.bonesinfo;
+	do {
+	    *bonesaddr = (struct cemetery *)alloc(sizeof **bonesaddr);
+	    **bonesaddr = *bp;
+	    bp = bp->next;
+	    bonesaddr = &(*bonesaddr)->next;
+	} while (bp);
+	*bonesaddr = 0;
+    }
+    /* decide which past hero deaths have become known; there's no
+       guarantee of either a grave or a ghost, so we go by whether the
+       current hero has seen the map location where each old one died */
+    for (bp = mptr->final_resting_place; bp; bp = bp->next)
+	if (lastseentyp[bp->frpx][bp->frpy]) {
+	    bp->bonesknown = TRUE;
+	    mptr->flags.knownbones = 1;
+	}
+}
+
+/*ARGUSED*/
+/* valley and sanctum levels get automatic annotation once temple is entered */
+void
+mapseen_temple(priest)
+struct monst *priest UNUSED;	/* currently unused; might be useful someday */
+{
+    mapseen *mptr = find_mapseen(&u.uz);
+
+    if (Is_valley(&u.uz))
+	mptr->flags.valley = 1;
+    else if (Is_sanctum(&u.uz))
+	mptr->flags.msanctum = 1;
+}
+
+/* room entry message has just been delivered so learn room even if blind */
+void
+room_discovered(roomno)
+int roomno;
+{
+    mapseen *mptr = find_mapseen(&u.uz);
+
+    mptr->msrooms[roomno].seen = 1;
 }
 
 int
@@ -2254,40 +2384,49 @@ branch *br;
     return "(unknown)";
 }
 
-STATIC_OVL const char*
+STATIC_OVL const char *
 shop_string(rtype)
 int rtype;
 {
+    const char *str = "shop";	/* catchall */
+
     /* Yuck, redundancy...but shclass.name doesn't cut it as a noun */
     switch (rtype) {
-    case SHOPBASE:
-	return "general store";
-    case ARMORSHOP:
-	return "armor shop";
-    case SCROLLSHOP:
-	return "scroll shop";
-    case POTIONSHOP:
-	return "potion shop";
-    case WEAPONSHOP:
-	return "weapon shop";
-    case FOODSHOP:
-	return "delicatessen";
-    case RINGSHOP:
-	return "jewelers";
-    case WANDSHOP:
-	return "wand shop";
-    case BOOKSHOP:
-	return "bookstore";
-    case FODDERSHOP:
-	return "health food store";
-    case CANDLESHOP:
-	return "lighting shop";
-    default:
-	/* In case another patch adds a shop type that doesn't exist,
-	 * do something reasonable like "a shop".
-	 */
-	return "shop";
+    case SHOPBASE - 1:	str = "untended shop";	break; /* see recalc_mapseen */
+    case SHOPBASE:	str = "general store";	break;
+    case ARMORSHOP:	str = "armor shop";	break;
+    case SCROLLSHOP:	str = "scroll shop";	break;
+    case POTIONSHOP:	str = "potion shop";	break;
+    case WEAPONSHOP:	str = "weapon shop";	break;
+    case FOODSHOP:	str = "delicatessen";	break;
+    case RINGSHOP:	str = "jewelers";	break;
+    case WANDSHOP:	str = "wand shop";	break;
+    case BOOKSHOP:	str = "bookstore";	break;
+    case FODDERSHOP:	str = "health food store"; break;
+    case CANDLESHOP:	str = "lighting shop";	break;
+    default: break;
+    }
+    return str;
+}
+
+/* if player knows about the mastermind tune, append it to Castle annotation;
+   if drawbridge has been destroyed, flags.castletune will be zero */
+STATIC_OVL char *
+tunesuffix(mptr, outbuf)
+mapseen *mptr;
+char *outbuf;
+{
+    *outbuf = '\0';
+    if (mptr->flags.castletune && u.uevent.uheard_tune) {
+	char tmp[BUFSZ];
+
+	if (u.uevent.uheard_tune == 2)
+	    Sprintf(tmp, "notes \"%s\"", tune);
+	else
+	    Strcpy(tmp, "5-note tune");
+	Sprintf(outbuf, " (play %s to open or close drawbridge)", tmp);
     }
+    return outbuf;
 }
 
 /* some utility macros for print_mapseen */
@@ -2360,6 +2499,10 @@ boolean printdun;
 	 */
 	Sprintf(buf, "%sPlane %i:", TAB, -i);
     else
+	/* FIXME: when this branch has only one level (Ft.Ludios),
+	 * listing "Level 1:" for it might confuse inexperienced
+	 * players into thinking there's more than one.
+	 */
 	Sprintf(buf, "%sLevel %d:", TAB, i);
 	
 #ifdef WIZARD
@@ -2371,16 +2514,14 @@ boolean printdun;
 	    Sprintf(eos(buf), " [%s]", slev->proto);
     }
 #endif
-
+    /* [perhaps print custom annotation on its own line when it's long] */
     if (mptr->custom)
 	Sprintf(eos(buf), " (%s)", mptr->custom);
-
-    /* print out glyph or something more interesting? */
-    Sprintf(eos(buf), "%s",
-	    on_level(&u.uz, &mptr->lev) ? " <- You are here" : "");
+    if (on_level(&u.uz, &mptr->lev))
+	Strcat(buf, " <- You are here");
     putstr(win, ATR_BOLD, buf);
 
-    if (mptr->feat.forgot) return;
+    if (mptr->flags.forgot) return;
 
     if (INTEREST(mptr->feat)) {
 	buf[0] = 0;
@@ -2390,25 +2531,28 @@ boolean printdun;
 	/* List interests in an order vaguely corresponding to
 	 * how important they are.
 	 */
-	if (mptr->feat.nshop > 1)
-	    ADDNTOBUF("shop", mptr->feat.nshop);
-	else if (mptr->feat.nshop == 1)
-	    Sprintf(eos(buf), "%s%s", COMMA, 
-		    an(shop_string(mptr->feat.shoptype)));
-
-	/* Temples + non-temple altars get munged into just "altars" */
-	if (!mptr->feat.ntemple || mptr->feat.ntemple != mptr->feat.naltar)
-	    ADDNTOBUF("altar", mptr->feat.naltar);
-	else
-	    ADDNTOBUF("temple", mptr->feat.ntemple);
-
-	/* only print out altar's god if they are all to your god */
-	if (Amask2align(Msa2amask(mptr->feat.msalign)) == u.ualign.type)
-	    Sprintf(eos(buf), " to %s", align_gname(u.ualign.type));
+	if (mptr->feat.nshop > 0) {
+	    if (mptr->feat.nshop > 1)
+		ADDNTOBUF("shop", mptr->feat.nshop);
+	    else
+		Sprintf(eos(buf), "%s%s", COMMA,
+			an(shop_string(mptr->feat.shoptype)));
+	}
+	if (mptr->feat.naltar > 0) {
+	    /* Temples + non-temple altars get munged into just "altars" */
+	    if (mptr->feat.ntemple != mptr->feat.naltar)
+		ADDNTOBUF("altar", mptr->feat.naltar);
+	    else
+		ADDNTOBUF("temple", mptr->feat.ntemple);
 
+	    /* only print out altar's god if they are all to your god */
+	    if (Amask2align(Msa2amask(mptr->feat.msalign)) == u.ualign.type)
+		Sprintf(eos(buf), " to %s", align_gname(u.ualign.type));
+	}
 	ADDNTOBUF("throne", mptr->feat.nthrone);
 	ADDNTOBUF("fountain", mptr->feat.nfount);
 	ADDNTOBUF("sink", mptr->feat.nsink);
+	ADDNTOBUF("grave", mptr->feat.ngrave);
 	ADDNTOBUF("tree", mptr->feat.ntree);
 #if 0
 	ADDTOBUF("water", mptr->feat.water);
@@ -2423,6 +2567,35 @@ boolean printdun;
 	putstr(win, 0, buf);
     }
 
+    /* we assume that these are mutually exclusive */
+    *buf = '\0';
+    if (mptr->flags.oracle) {
+	Sprintf(buf, "%sOracle of Delphi.", PREFIX);
+    } else if (In_sokoban(&mptr->lev)) {
+	Sprintf(buf, "%s%s.", PREFIX,
+		mptr->flags.sokosolved ? "Solved" : "Unsolved");
+    } else if (mptr->flags.bigroom) {
+	Sprintf(buf, "%sA very big room.", PREFIX);
+# ifdef REINCARNATION
+    } else if (mptr->flags.roguelevel) {
+	Sprintf(buf, "%sA primitive area.", PREFIX);
+# endif
+    } else if (mptr->flags.ludios) {
+	/* presence of the ludios branch in #overview output indicates that
+	   the player has made it onto the level; presence of this annotation
+	   indicates that the fort's entrance has been seen (or mapped) */
+	Sprintf(buf, "%sFort Ludios.", PREFIX);
+    } else if (mptr->flags.castle) {
+	char tmpbuf[BUFSZ];
+
+	Sprintf(buf, "%sThe castle%s.", PREFIX, tunesuffix(mptr, tmpbuf));
+    } else if (mptr->flags.valley) {
+	Sprintf(buf, "%sValley of the Dead.", PREFIX);
+    } else if (mptr->flags.msanctum) {
+	Sprintf(buf, "%sMoloch's Sanctum.", PREFIX);
+    }
+    if (*buf) putstr(win, 0, buf);
+
     /* print out branches */
     if (mptr->br) {
 	Sprintf(buf, "%s%s to %s", PREFIX, br_string2(mptr->br), 
@@ -2437,6 +2610,26 @@ boolean printdun;
 	Strcat(buf, ".");
 	putstr(win, 0, buf);
     }
+
+    /* maybe print out bones details */
+    if (mptr->final_resting_place) {
+	struct cemetery *bp;
+	int kncnt = 0;
+
+	for (bp = mptr->final_resting_place; bp; bp = bp->next)
+	    if (bp->bonesknown || wizard) ++kncnt;
+	if (kncnt) {
+	    Sprintf(buf, "%s%s", PREFIX, "Final resting place for");
+	    putstr(win, 0, buf);
+	    for (bp = mptr->final_resting_place; bp; bp = bp->next) {
+		if (bp->bonesknown || wizard) {
+		    Sprintf(buf, "%s%s%s, %s%c", PREFIX, TAB,
+			    bp->who, bp->how, --kncnt ? ',' : '.');
+		    putstr(win, 0, buf);
+		}
+	    }
+	}
+    }
 }
 #endif /* DUNGEON_OVERVIEW */
 
diff --git a/src/hack.c b/src/hack.c
index b656c4e5d..0f9082e40 100644
--- a/src/hack.c
+++ b/src/hack.c
@@ -2067,7 +2067,9 @@ register boolean newlev;
 	    u_entered_shop(u.ushops_entered);
 
 	for (ptr = &u.uentered[0]; *ptr; ptr++) {
-	    register int roomno = *ptr - ROOMOFFSET, rt = rooms[roomno].rtype;
+	    int roomno = *ptr - ROOMOFFSET,
+		rt = rooms[roomno].rtype;
+	    boolean msg_given = TRUE;
 
 	    /* Did we just enter some other special room? */
 	    /* vault.c insists that a vault remain a VAULT,
@@ -2124,15 +2126,21 @@ register boolean newlev;
 			    else
 				verbalize("%s, %s, welcome to Delphi!",
 					Hello((struct monst *) 0), plname);
-			}
+			} else
+			    msg_given = FALSE;
 		        break;
 		    }
 		case TEMPLE:
 		    intemple(roomno + ROOMOFFSET);
-		    /* fall through */
+		    /*FALLTHRU*/
 		default:
+		    msg_given = (rt == TEMPLE);
 		    rt = 0;
+		    break;
 	    }
+#ifdef DUNGEON_OVERVIEW
+	    if (msg_given) room_discovered(roomno);
+#endif
 
 	    if (rt != 0) {
 		rooms[roomno].rtype = OROOM;
diff --git a/src/priest.c b/src/priest.c
index 4a831fb6b..e5a112d2c 100644
--- a/src/priest.c
+++ b/src/priest.c
@@ -444,6 +444,11 @@ int roomno;
 		if (*this_time <= *other_time) *other_time = *this_time - 1L;
 	    }
 	}
+#ifdef DUNGEON_OVERVIEW
+	/* recognize the Valley of the Dead and Moloch's Sanctum
+	   once hero has encountered the temple priest on those levels */
+	mapseen_temple(priest);
+#endif
     } else {
 	/* untended */
 
diff --git a/src/restore.c b/src/restore.c
index 65247370a..cd2bc4006 100644
--- a/src/restore.c
+++ b/src/restore.c
@@ -39,7 +39,6 @@ STATIC_DCL void FDECL(restlevelstate, (unsigned int, unsigned int));
 STATIC_DCL int FDECL(restlevelfile, (int,XCHAR_P));
 STATIC_OVL void FDECL(restore_msghistory, (int));
 STATIC_DCL void FDECL(reset_oattached_mids, (BOOLEAN_P));
-STATIC_DCL void FDECL(restcemetery, (int));
 STATIC_DCL void FDECL(rest_levl, (int,BOOLEAN_P));
 
 static struct restore_procs {
@@ -902,16 +901,17 @@ register int fd;
 	return(1);
 }
 
-STATIC_OVL void
-restcemetery(fd)
+void
+restcemetery(fd, cemeteryaddr)
 int fd;
+struct cemetery **cemeteryaddr;
 {
     struct cemetery *bonesinfo, **bonesaddr;
     int flag;
 
     mread(fd, (genericptr_t)&flag, sizeof flag);
     if (flag == 0) {
-	bonesaddr = &level.bonesinfo;
+	bonesaddr = cemeteryaddr;
 	do {
 	    bonesinfo = (struct cemetery *)alloc(sizeof *bonesinfo);
 	    mread(fd, (genericptr_t)bonesinfo, sizeof *bonesinfo);
@@ -919,7 +919,7 @@ int fd;
 	    bonesaddr = &(*bonesaddr)->next;
 	} while (*bonesaddr);
     } else {
-	level.bonesinfo = 0;
+	*cemeteryaddr = 0;
     }
 }
 
@@ -1020,7 +1020,7 @@ boolean ghostly;
 #endif
 	    trickery(trickbuf);
 	}
-	restcemetery(fd);
+	restcemetery(fd, &level.bonesinfo);
 	rest_levl(fd, (boolean)((sfrestinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP));
 #ifdef DUNGEON_OVERVIEW
 	mread(fd, (genericptr_t)lastseentyp, sizeof(lastseentyp));
diff --git a/src/save.c b/src/save.c
index c1ad372e2..51ab3fc36 100644
--- a/src/save.c
+++ b/src/save.c
@@ -22,7 +22,6 @@ int dotcnt, dotrow;	/* also used in restore */
 #endif
 
 STATIC_DCL void FDECL(savelevchn, (int,int));
-STATIC_DCL void FDECL(savecemetery, (int,int));
 STATIC_DCL void FDECL(savedamage, (int,int));
 STATIC_DCL void FDECL(saveobj, (int,struct obj *));
 STATIC_DCL void FDECL(saveobjchn, (int,struct obj *,int));
@@ -521,7 +520,7 @@ int mode;
 #else
 	bwrite(fd,(genericptr_t) &lev,sizeof(lev));
 #endif
-	savecemetery(fd, mode);
+	savecemetery(fd, mode, &level.bonesinfo);
 	savelevl(fd, (boolean)((sfsaveinfo.sfi1 & SFI1_RLECOMP) == SFI1_RLECOMP));
 #ifdef DUNGEON_OVERVIEW
 	bwrite(fd,(genericptr_t) lastseentyp,sizeof(lastseentyp));
@@ -540,6 +539,8 @@ int mode;
 
 	/* from here on out, saving also involves allocated memory cleanup */
  skip_lots:
+	/* this comes before the map, so need cleanup here if we skipped */
+	if (mode == FREE_SAVE) savecemetery(fd, mode, &level.bonesinfo);
 	/* must be saved before mons, objs, and buried objs */
 	save_timers(fd, mode, RANGE_LEVEL);
 	save_light_sources(fd, mode, RANGE_LEVEL);
@@ -556,7 +557,7 @@ int mode;
 	    fobj = 0;
 	    level.buriedobjlist = 0;
 	    billobjs = 0;
-	    level.bonesinfo = 0;
+	    /* level.bonesinfo = 0; -- handled by savecemetery() */
 	}
 	save_engravings(fd, mode);
 	savedamage(fd, mode);
@@ -921,18 +922,20 @@ register int fd, mode;
 	    sp_levchn = 0;
 }
 
-STATIC_OVL void
-savecemetery(fd, mode)
+/* used when saving a level and also when saving dungeon overview data */
+void
+savecemetery(fd, mode, cemeteryaddr)
 int fd;
 int mode;
+struct cemetery **cemeteryaddr;
 {
     struct cemetery *thisbones, *nextbones;
     int flag;
 
-    flag = level.bonesinfo ? 0 : -1;
+    flag = *cemeteryaddr ? 0 : -1;
     if (perform_bwrite(mode))
 	bwrite(fd, (genericptr_t)&flag, sizeof flag);
-    nextbones = level.bonesinfo;
+    nextbones = *cemeteryaddr;
     while ((thisbones = nextbones) != 0) {
 	nextbones = thisbones->next;
 	if (perform_bwrite(mode))
@@ -940,6 +943,8 @@ int mode;
 	if (release_data(mode))
 	    free((genericptr_t)thisbones);
     }
+    if (release_data(mode))
+	*cemeteryaddr = 0;
 }
 
 STATIC_OVL void
-- 
2.40.0