]> granicus.if.org Git - nethack/commitdiff
experimental - dungeon overview (trunk only)
authornethack.allison <nethack.allison>
Thu, 20 Apr 2006 00:57:45 +0000 (00:57 +0000)
committernethack.allison <nethack.allison>
Thu, 20 Apr 2006 00:57:45 +0000 (00:57 +0000)
Add Hojita Discordia's Dungeon Map overview as
conditional code for experimentation and testing.
Everything is guarded by
#ifdef DUNGEON_OVERVIEW
#endif

The notes that accompanied the original patch follow.

Dungeon Map Overview Patch for Nethack 3.4.3
Version 3
=============================================================================
Changelist:
    v3: Changed #level to #annotate to avoid #levelchange collision.  Fixed
        handling of elemental planes and astral plane (oops).  Changed
formatting to be slightly closer to print_dungeon()'s.  Should be
"final" version for 3.4.3.
    v2: Added tracking of trees.  Changed ctrl-m command to ctrl-o.  Portals
        displayed as "sealed" instead of "closed".
    v1: First release.
    (Note: all versions are mutually save compatible.)
=============================================================================
This patch creates a dungeon map overview that is recorded as the player
explores the dungeon.  I was tired of returning to a game a few days later
and having no idea what the dungeon looked like.  Trying to name pieces
of armor with shorthand didn't work so well as an intermediate solution
either, especially around nymphs.

It can be assumed that this map is in the mind of the hero and thus
can't be stolen, can be read when blind, or when buried, or when the hero
doesn't have any hands, or eyes, or hands free, or...etc. On the other hand,
this implies that the hero doesn't remember all of the details ("a fountain",
"some fountains", "many fountains") and that the map is subject to amnesia
when applicable.

This overview tracks fountains, altars, stores, temples, sinks, thrones,
trees, and dungeon branches.  It attempts to not spoil the player nor
reveal more information than the hero knows.  For this reason, it only
tracks dungeon features found in the guidebook and dungeon branches.

This patch breaks save file compatibility.  Sorry.

Added commands
=============================================================================
#overview (ctrl-o, if not in wizard mode) - displays overview
#annotate (ctrl-n, if using numpad) - names current level

Example Output From #overview
=============================================================================
The Dungeons of Doom: levels 1 to level 15
   Level 1:
      A fountain
   Level 3: (My stash.)
      An altar, some fountains
      Stairs down to The Gnomish Mines
   Level 7:
      Many fountains
   Level 8:
      Stairs up to Sokoban, level 7
   Level 15:
      A general store
      Sealed portal to The Quest
The Gnomish Mines: levels 4 to level 7
   Level 7: <- You are here
      Many stores, some fountains, a temple

More Details
=============================================================================
The overview shows only levels that have anything interesting to display and
doesn't show branches that don't have any interesting levels.

To avoid the map revealing more information than the hero knows, the overview
only displays things that the hero has seen or touched.  (If the hero
blinds herself, levitates above a known fountain, and obliterates it with a
wand of digging, the overview will still say that there is a fountain.)

This is done, sadly, by adding 6 bits to the rm struct to track the last
known dungeon type.  On the other hand, this change could potentially allow
a window port to do something like drawing an item and a fountain on the same
square.

Things That Could Be Better And Maybe Some Feedback Would Help
=============================================================================
"<- You Are Here" is pretty goofy
    -...but an indicator of some sort is nice.
=============================================================================
Many thanks to all the kind folks on r.g.r.n. who had very good feedback
about this patch, in particular L (for the trees), <Someone> Papaganou (for the
#annotate suggestion and some formatting feedback), and <Someone> (for the suggestion
of just overriding ctrl-o instead of using the very broken ctrl-m.)
=============================================================================
20060311. Hojita Discordia. (My usenet email is bogus. Sorry.)

13 files changed:
include/config.h
include/dungeon.h
include/extern.h
include/rm.h
src/cmd.c
src/display.c
src/do.c
src/dungeon.c
src/mklev.c
src/quest.c
src/read.c
src/vision.c
util/makedefs.c

index b60db088c996322292dc29286c25fff64f827527..0aa825656aef62042b155c319dc0a411313c5e4e 100644 (file)
@@ -372,7 +372,8 @@ typedef unsigned char       uchar;
 #if !defined(MAC)
 # define CLIPPING      /* allow smaller screens -- ERS */
 #endif
-#define BARGETHROUGH   /* allow some monster to move others out of their way */
+#define AUTOPICKUP_EXCEPTIONS  /* exceptions to autopickup */
+#define BARGETHROUGH   /* allow some monsters to move others out of their way */
 
 #ifdef REDO
 # define DOAGAIN '\001' /* ^A, the "redo" key used in cmd.c and getline.c */
@@ -390,10 +391,9 @@ typedef unsigned char      uchar;
  */
 
 /*#define GOLDOBJ */   /* Gold is kept on obj chains - Helge Hafting */
-#define AUTOPICKUP_EXCEPTIONS  /* exceptions to autopickup */
 #define STATUS_VIA_WINDOWPORT  /* re-work of the status line updating process */
 #define STATUS_HILITES         /* support hilites of status fields */
-
+#define DUNGEON_OVERVIEW       /* dungeon overview by Hojita Discordia */
 /* End of Section 5 */
 
 #include "global.h"    /* Define everything else according to choices above */
index 6c2d27c35b054f2a47690ec4ac03766dbe903884..3b1b04fc91457a6a6425158dbc0817e943816e4a 100644 (file)
@@ -167,4 +167,70 @@ struct linfo {
 #endif /* MFLOPPY */
 };
 
+#ifdef DUNGEON_OVERVIEW
+/* types and structures for dungeon map recording
+ *
+ * It is designed to eliminate the need for an external notes file for some of
+ * the more mundane dungeon elements.  "Where was the last altar I passed?" etc...
+ * Presumably the character can remember this sort of thing even if, months
+ * later in real time picking up an old save game, I can't.
+ *
+ * To be consistent, one can assume that this map is in the player's mind and
+ * has no physical correspondence (eliminating illiteracy/blind/hands/hands free
+ * concerns.) Therefore, this map is not exaustive nor detailed ("some fountains").
+ * This makes it also subject to player conditions (amnesia).
+ */
+
+/* Because clearly Nethack needs more ways to specify alignment */
+#define Amask2msa(x) ((x) == 4 ? 3 : (x) & AM_MASK)
+#define Msa2amask(x) ((x) == 3 ? 4 : (x))
+#define MSA_NONE       0  /* unaligned or multiple alignments */
+#define MSA_LAWFUL  1
+#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;
+
+       /* custom naming */
+       char *custom;
+       unsigned custom_lth;
+
+       /* maybe this should just be in struct mkroom? */
+       schar rooms[(MAXNROFROOMS+1)*2];
+} mapseen;
+
+#endif /* DUNGEON_OVERVIEW */
 #endif /* DUNGEON_H */
index 26e4cb1830ec3522b7a15e08fe10c09ad1da7317..72d3b9c8c255664c948fbe05e2be6a8664186503 100644 (file)
@@ -549,6 +549,15 @@ E schar FDECL(lev_by_name, (const char *));
 #ifdef WIZARD
 E schar FDECL(print_dungeon, (BOOLEAN_P,schar *,xchar *));
 #endif
+#ifdef DUNGEON_OVERVIEW
+E int NDECL(donamelevel);
+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(recbranch_mapseen, (d_level *, d_level *));
+E void FDECL(remdun_mapseen, (int));
+#endif /* DUNGEON_OVERVIEW */
 
 /* ### eat.c ### */
 
index 65e7253d945771044e73ed22335620d1f06242bb..3851d25f086222c3752ac66eef14e32b43131f93 100644 (file)
@@ -338,9 +338,15 @@ struct rm {
        Bitfield(horizontal,1); /* wall/door/etc is horiz. (more typ info) */
        Bitfield(lit,1);        /* speed hack for lit rooms */
        Bitfield(waslit,1);     /* remember if a location was lit */
+
        Bitfield(roomno,6);     /* room # for special rooms */
        Bitfield(edge,1);       /* marks boundaries for special rooms*/
        Bitfield(candig,1);     /* Exception to Can_dig_down; was a trapdoor */
+
+#ifdef DUNGEON_OVERVIEW
+       Bitfield(styp, 6);      /* last seen/touched dungeon typ */
+       /* 2 free bits */
+#endif /* DUNGEON_OVERVIEW */
 };
 
 /*
index 412ac9f720be99d14cc7240242f9199b336a1c34..e95615fcf59affeaa66628bf8e10a6ef4788f3f8 100644 (file)
--- a/src/cmd.c
+++ b/src/cmd.c
@@ -111,6 +111,9 @@ STATIC_PTR int NDECL(doprev_message);
 STATIC_PTR int NDECL(timed_occupation);
 STATIC_PTR int NDECL(doextcmd);
 STATIC_PTR int NDECL(domonability);
+#ifdef DUNGEON_OVERVIEW
+STATIC_PTR int NDECL(dooverview_or_wiz_where);
+#endif /* DUNGEON_OVERVIEW */
 STATIC_PTR int NDECL(dotravel);
 # ifdef WIZARD
 STATIC_PTR int NDECL(wiz_wish);
@@ -497,6 +500,19 @@ enter_explore_mode()
        return 0;
 }
 
+#ifdef DUNGEON_OVERVIEW
+STATIC_PTR int
+dooverview_or_wiz_where()
+{
+#ifdef WIZARD
+       if (wizard) return wiz_where();
+       else
+#endif
+       dooverview();
+       return 0;
+}
+
+#endif /* DUNGEON_OVERVIEW */
 #ifdef WIZARD
 
 /* ^W command - wish for something */
@@ -1561,9 +1577,14 @@ static const struct func_tab cmdlist[] = {
        {C('i'), TRUE, wiz_identify},
 #endif
        {C('l'), TRUE, doredraw}, /* if number_pad is set */
+#ifndef DUNGEON_OVERVIEW
 #ifdef WIZARD
        {C('o'), TRUE, wiz_where},
 #endif
+#else
+       {C('n'), TRUE, donamelevel}, /* if number_pad is set */
+       {C('o'), TRUE, dooverview_or_wiz_where}, /* depending on wizard status */
+#endif /* DUNGEON_OVERVIEW */
        {C('p'), TRUE, doprev_message},
        {C('r'), TRUE, doredraw},
        {C('t'), TRUE, dotele},
@@ -1673,6 +1694,9 @@ static const struct func_tab cmdlist[] = {
 
 struct ext_func_tab extcmdlist[] = {
        {"adjust", "adjust inventory letters", doorganize, TRUE},
+#ifdef DUNGEON_OVERVIEW
+       {"annotate", "name current level", donamelevel, TRUE},
+#endif /* DUNGEON_OVERVIEW */
        {"chat", "talk to someone", dotalk, TRUE},      /* converse? */
        {"conduct", "list voluntary challenges you have maintained",
                                                doconduct, TRUE},
@@ -1686,6 +1710,9 @@ struct ext_func_tab extcmdlist[] = {
        {"monster", "use a monster's special ability", domonability, TRUE},
        {"name", "name an item or type of object", ddocall, TRUE},
        {"offer", "offer a sacrifice to the gods", dosacrifice, FALSE},
+#ifdef DUNGEON_OVERVIEW
+       {"overview", "show an overview of the dungeon", dooverview, TRUE},
+#endif /* DUNGEON_OVERVIEW */
        {"pray", "pray to the gods for help", dopray, TRUE},
        {"quit", "exit without saving current game", done2, TRUE},
 #ifdef STEED
index d23beabd7394cdb3b0d28a8233a319b02d720ace..4dd5fafc8fc67929a03d6184f5556b163cc32332 100644 (file)
@@ -185,6 +185,9 @@ magic_map_background(x, y, show)
     if (level.flags.hero_memory)
        lev->glyph = glyph;
     if (show) show_glyph(x,y, glyph);
+#ifdef DUNGEON_OVERVIEW
+    lev->styp = lev->typ;
+#endif /* DUNGEON_OVERVIEW */
 }
 
 /*
index e72504a45dfd708c877b98573ff4b76e59779c87..718ad2f38cccf9f475d28210c82fda2389a87c0b 100644 (file)
--- a/src/do.c
+++ b/src/do.c
@@ -1070,6 +1070,9 @@ boolean at_stairs, falling, portal;
        keepdogs(FALSE);
        if (u.uswallow)                         /* idem */
                u.uswldtim = u.uswallow = 0;
+#ifdef DUNGEON_OVERVIEW
+       recalc_mapseen(); /* recalculate map overview before we leave the level */
+#endif /* DUNGEON_OVERVIEW */
        /*
         *  We no longer see anything on the level.  Make sure that this
         *  follows u.uswallow set to null since uswallow overrides all
@@ -1105,6 +1108,13 @@ boolean at_stairs, falling, portal;
 #ifdef USE_TILES
        substitute_tiles(newlevel);
 #endif
+#ifdef DUNGEON_OVERVIEW
+       /* record this level transition as a potential seen branch unless using
+        * some non-standard means of transportation (level teleport).
+        */
+       if ((at_stairs || falling || portal) && (u.uz.dnum != newlevel->dnum))
+               recbranch_mapseen(&u.uz, newlevel);
+#endif /* DUNGEON_OVERVIEW */
        assign_level(&u.uz0, &u.uz);
        assign_level(&u.uz, newlevel);
        assign_level(&u.utolev, newlevel);
index b3ee9ab2cba1fcc1de9688c03cfaba168ce55b69..44f43361aacc15042d5e8371f7752bfff03b1eff 100644 (file)
@@ -5,6 +5,9 @@
 #include "hack.h"
 #include "dgn_file.h"
 #include "dlb.h"
+#ifdef DUNGEON_OVERVIEW
+#include "display.h"
+#endif /* DUNGEON_OVERVIEW */
 
 #define DUNGEON_FILE   "dungeon"
 
@@ -57,6 +60,18 @@ STATIC_DCL const char *FDECL(br_string, (int));
 STATIC_DCL void FDECL(print_branch, (winid, int, int, int, BOOLEAN_P, struct lchoice *));
 #endif
 
+#ifdef DUNGEON_OVERVIEW
+mapseen *mapseenchn = (struct mapseen *)0;
+STATIC_DCL void FDECL(free_mapseen, (mapseen *));
+STATIC_DCL mapseen *FDECL(load_mapseen, (int));
+STATIC_DCL void FDECL(save_mapseen, (int, mapseen *));
+STATIC_DCL mapseen *FDECL(find_mapseen, (d_level *));
+STATIC_DCL void FDECL(print_mapseen, (winid,mapseen *,boolean));
+STATIC_DCL boolean FDECL(interest_mapseen, (mapseen *));
+STATIC_DCL char *FDECL(seen_string, (xchar x, const char *));
+STATIC_DCL const char *FDECL(br_string2, (branch *));
+#endif /* DUNGEON_OVERVIEW */
+
 #ifdef DEBUG
 #define DD     dungeons[i]
 STATIC_DCL void NDECL(dumpit);
@@ -118,6 +133,9 @@ save_dungeon(fd, perform_write, free_data)
     boolean perform_write, free_data;
 {
     branch *curr, *next;
+#ifdef DUNGEON_OVERVIEW
+    mapseen *curr_ms, *next_ms;
+#endif
     int    count;
 
     if (perform_write) {
@@ -138,6 +156,15 @@ save_dungeon(fd, perform_write, free_data)
        bwrite(fd, (genericptr_t) level_info,
                        (unsigned)count * sizeof (struct linfo));
        bwrite(fd, (genericptr_t) &inv_pos, sizeof inv_pos);
+
+#ifdef DUNGEON_OVERVIEW
+       for (count = 0, curr_ms = mapseenchn; curr_ms; curr_ms = curr_ms->next)
+           count++;
+       bwrite(fd, (genericptr_t) &count, sizeof(count));
+
+       for (curr_ms = mapseenchn; curr_ms; curr_ms = curr_ms->next)
+           save_mapseen(fd, curr_ms);
+#endif /* DUNGEON_OVERVIEW */
     }
 
     if (free_data) {
@@ -146,6 +173,15 @@ save_dungeon(fd, perform_write, free_data)
            free((genericptr_t) curr);
        }
        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;
+#endif /* DUNGEON_OVERVIEW */
     }
 }
 
@@ -156,6 +192,9 @@ restore_dungeon(fd)
 {
     branch *curr, *last;
     int    count, i;
+#ifdef DUNGEON_OVERVIEW
+    mapseen *curr_ms, *last_ms;
+#endif
 
     mread(fd, (genericptr_t) &n_dgns, sizeof(n_dgns));
     mread(fd, (genericptr_t) dungeons, sizeof(dungeon) * (unsigned)n_dgns);
@@ -181,6 +220,20 @@ restore_dungeon(fd)
        panic("level information count larger (%d) than allocated size", count);
     mread(fd, (genericptr_t) level_info, (unsigned)count*sizeof(struct linfo));
     mread(fd, (genericptr_t) &inv_pos, sizeof inv_pos);
+
+#ifdef DUNGEON_OVERVIEW
+    mread(fd, (genericptr_t) &count, sizeof(count));
+    last_ms = (mapseen *) 0;
+    for (i = 0; i < count; i++) {
+       curr_ms = load_mapseen(fd);
+       curr_ms->next = (mapseen *) 0;
+       if (last_ms)
+           last_ms->next = curr_ms;
+       else
+           mapseenchn = curr_ms;
+       last_ms = curr_ms;
+    }
+#endif /* DUNGEON_OVERVIEW */
 }
 
 static void
@@ -1762,4 +1815,608 @@ xchar *rdgn;
 }
 #endif /* WIZARD */
 
+#ifdef DUNGEON_OVERVIEW
+/* Record that the player knows about a branch from a level. This function
+ * will determine whether or not it was a "real" branch that was taken.
+ * This function should not be called for a transition done via level
+ * teleport or via the Eye.
+ */
+void 
+recbranch_mapseen(source, dest)
+       d_level *source;
+       d_level *dest;
+{
+       mapseen *mptr;
+       branch* br;
+
+       /* not a branch */
+       if (source->dnum == dest->dnum) return;
+
+       /* we only care about forward branches */
+       for (br = branches; br; br = br->next) {
+               if (on_level(source, &br->end1) && on_level(dest, &br->end2)) break;
+               if (on_level(source, &br->end2) && on_level(dest, &br->end1)) return;
+       }
+
+       /* branch not found, so not a real branch. */
+       if (!br) return;
+  
+       if (mptr = find_mapseen(source)) {
+               if (mptr->br && br != mptr->br)
+                       impossible("Two branches on the same level?");
+               mptr->br = br;
+       } else {
+               impossible("Can't note branch for unseen level (%d, %d)", 
+                       source->dnum, source->dlevel);
+       }
+}
+
+/* add a custom name to the current level */
+int
+donamelevel()
+{
+       mapseen *mptr;
+       char qbuf[QBUFSZ];      /* Buffer for query text */
+       char nbuf[BUFSZ];       /* Buffer for response */
+       int len;
+
+       if (!(mptr = find_mapseen(&u.uz))) return 0;
+
+       Sprintf(qbuf,"What do you want to call this dungeon level? ");
+       getlin(qbuf, nbuf);
+
+       if (index(nbuf, '\033')) return 0;
+
+       len = strlen(nbuf) + 1;
+       if (mptr->custom) {
+               free((genericptr_t)mptr->custom);
+               mptr->custom = (char *)0;
+               mptr->custom_lth = 0;
+       }
+       
+       if (*nbuf) {
+               mptr->custom = (char *) alloc(sizeof(char) * len);
+               mptr->custom_lth = len;
+               strcpy(mptr->custom, nbuf);
+       }
+   
+       return 0;
+}
+
+/* find the particular mapseen object in the chain */
+/* may return 0 */
+STATIC_OVL mapseen *
+find_mapseen(lev)
+d_level *lev;
+{
+       mapseen *mptr;
+
+       for (mptr = mapseenchn; mptr; mptr = mptr->next)
+               if (on_level(&(mptr->lev), lev)) break;
+
+       return mptr;
+}
+
+void
+forget_mapseen(ledger_no)
+int ledger_no;
+{
+       mapseen *mptr;
+
+       for (mptr = mapseenchn; mptr; mptr = mptr->next)
+               if (dungeons[mptr->lev.dnum].ledger_start + 
+                       mptr->lev.dlevel == ledger_no) break;
+
+       /* if not found, then nothing to forget */
+       if (mptr) {
+               mptr->feat.forgot = 1;
+               mptr->br = (branch *)0;
+
+               /* custom names are erased, not forgotten until revisted */
+               if (mptr->custom) {
+                       mptr->custom_lth = 0;
+                       free((genericptr_t)mptr->custom);
+                       mptr->custom = (char *)0;
+               }
+
+               memset((genericptr_t) mptr->rooms, 0, sizeof(mptr->rooms));
+       }
+}
+
+STATIC_OVL void
+save_mapseen(fd, mptr)
+int fd;
+mapseen *mptr;
+{
+       branch *curr;
+       int count;
+
+       count = 0;
+       for (curr = branches; curr; curr = curr->next) {
+               if (curr == mptr->br) break;
+               count++;
+       }
+
+       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));
+       if (mptr->custom_lth)
+               bwrite(fd, (genericptr_t) mptr->custom, 
+               sizeof(char) * mptr->custom_lth);
+       bwrite(fd, (genericptr_t) &mptr->rooms, sizeof(mptr->rooms));
+}
+
+STATIC_OVL mapseen *
+load_mapseen(fd)
+int fd;
+{
+       int branchnum, count;
+       mapseen *load;
+       branch *curr;
+
+       load = (mapseen *) alloc(sizeof(mapseen));
+       mread(fd, (genericptr_t) &branchnum, sizeof(int));
+
+       count = 0;
+       for (curr = branches; curr; curr = curr->next) {
+               if (count == branchnum) break;
+               count++;
+       }
+       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));
+
+       return load;
+}
+
+/* Remove all mapseen objects for a particular dnum.
+ * Useful during quest expulsion to remove quest levels.
+ */
+void
+remdun_mapseen(dnum)
+int dnum;
+{
+       mapseen *mptr, *prev;
+       
+       prev = mapseenchn;
+       if (!prev) return;
+       mptr = prev->next;
+
+       for (; mptr; prev = mptr, mptr = mptr->next) {
+               if (mptr->lev.dnum == dnum) {
+                       prev->next = mptr->next;
+                       free((genericptr_t) mptr);
+                       mptr = prev;
+               }
+       }
+}
+
+void
+init_mapseen(lev)
+d_level *lev;
+{
+       /* Create a level and insert in "sorted" order.  This is an insertion
+        * sort first by dungeon (in order of discovery) and then by level number.
+        */
+       mapseen *mptr;
+       mapseen *init;
+       mapseen *old;
+       
+       init = (mapseen *) alloc(sizeof(mapseen));
+       (void) memset((genericptr_t)init, 0, sizeof(mapseen));
+       init->lev.dnum = lev->dnum;
+       init->lev.dlevel = lev->dlevel;
+
+       if (!mapseenchn) {
+               mapseenchn = init;
+               return;
+       }
+
+       /* walk until we get to the place where we should
+        * insert init between mptr and mptr->next
+        */
+       for (mptr = mapseenchn; mptr->next; mptr = mptr->next) {
+               if (mptr->next->lev.dnum == init->lev.dnum) break;
+       }
+       for (; mptr->next; mptr = mptr->next) {
+               if ((mptr->next->lev.dnum != init->lev.dnum) ||
+                       (mptr->next->lev.dlevel > init->lev.dlevel)) break;
+       }
+
+       old = mptr->next;
+       mptr->next = init;
+       init->next = old;
+}
+
+#define INTEREST(feat) \
+       ((feat).nfount) || \
+       ((feat).nsink) || \
+       ((feat).nthrone) || \
+       ((feat).naltar) || \
+       ((feat).nshop) || \
+       ((feat).ntemple) || \
+       ((feat).ntree)
+       /*
+       || ((feat).water) || \
+       ((feat).ice) || \
+       ((feat).lava)
+       */
+
+/* returns true if this level has something interesting to print out */
+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)
+       ));
+}
+
+/* recalculate mapseen for the current level */
+void
+recalc_mapseen()
+{
+       mapseen *mptr;
+       struct monst *shkp;
+       int x, y, ridx;
+
+       /* Should not happen in general, but possible if in the process
+        * of being booted from the quest.  The mapseen object gets
+        * removed during the expulsion but prior to leaving the level
+        */
+       if (!(mptr = find_mapseen(&u.uz))) return;
+
+       /* reset all features */
+       memset((genericptr_t) &mptr->feat, 0, sizeof(mapseen_feat));
+
+       /* 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;
+       }
+
+       /* 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)
+                                       mptr->feat.shoptype = 0;
+                               mptr->feat.nshop = min(mptr->feat.nshop + 1, 3);
+                       } else if (rooms[x].rtype == TEMPLE)
+                               /* altar and temple alignment handled below */
+                               mptr->feat.ntemple = min(mptr->feat.ntemple + 1, 3);
+               }
+       }
+
+       /* Update styp with typ if and only if it is in sight or the hero can
+        * feel it on their current location (i.e. not levitating).  This *should*
+        * give the "last known typ" for each dungeon location.  (At the very least,
+        * it's a better assumption than determining what the player knows from
+        * the glyph and the typ (which is isn't quite enough information in some
+        * cases).
+        *
+        * It was reluctantly added to struct rm to track.  Alternatively
+        * 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.
+        *
+        * Although no current windowing systems (can) do this, this would add the
+        * ability to have non-dungeon glyphs float above the last known dungeon
+        * glyph (i.e. items on fountains).
+        *
+        * (vision-related styp update done in loop below)
+        */
+       if (!Levitation)
+               levl[u.ux][u.uy].styp = levl[u.ux][u.uy].typ;
+
+       for (x = 0; x < COLNO; x++) {
+               for (y = 0; y < ROWNO; y++) {
+                       /* update styp from viz_array */
+                       if (viz_array[y][x] & IN_SIGHT)
+                               levl[x][y].styp = levl[x][y].typ;
+
+                       switch (levl[x][y].styp) {
+                       /*
+                       case ICE:
+                               mptr->feat.ice = 1;
+                               break;
+                       case POOL:
+                       case MOAT:
+                       case WATER:
+                               mptr->feat.water = 1;
+                               break;
+                       case LAVAPOOL:
+                               mptr->feat.lava = 1;
+                               break;
+                       */
+                       case TREE:
+                               mptr->feat.ntree = min(mptr->feat.ntree + 1, 3);
+                               break;
+                       case FOUNTAIN:
+                               mptr->feat.nfount = min(mptr->feat.nfount + 1, 3);
+                               break;
+                       case THRONE:
+                               mptr->feat.nthrone = min(mptr->feat.nthrone + 1, 3);
+                               break;
+                       case SINK:
+                               mptr->feat.nsink = min(mptr->feat.nsink + 1, 3);
+                               break;
+                       case ALTAR:
+                               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 = MSA_NONE;
+                                               
+                               mptr->feat.naltar = min(mptr->feat.naltar + 1, 3);
+                               break;
+                       }
+               }
+       }
+}
+
+int
+dooverview()
+{
+       winid win;
+       mapseen *mptr;
+       boolean first;
+       boolean printdun;
+       int lastdun;
+
+       first = TRUE;
+
+       /* lazy intialization */
+       (void) recalc_mapseen();
+
+       win = create_nhwindow(NHW_MENU);
+
+       for (mptr = mapseenchn; mptr; mptr = mptr->next) {
+
+               /* only print out info for a level or a dungeon if interest */
+               if (interest_mapseen(mptr)) {
+                       printdun = (first || lastdun != mptr->lev.dnum);
+                       /* if (!first) putstr(win, 0, ""); */
+                       print_mapseen(win, mptr, printdun);
+
+                       if (printdun) {
+                               first = FALSE;
+                               lastdun = mptr->lev.dnum;
+                       }
+               }
+       }
+
+       display_nhwindow(win, TRUE);
+       destroy_nhwindow(win);
+
+       return 0;
+}
+
+STATIC_OVL char *
+seen_string(x, obj)
+xchar x;
+const char *obj;
+{
+       /* players are computer scientists: 0, 1, 2, n */
+       switch(x) {
+       case 0: return "no";
+       /* an() returns too much.  index is ok in this case */
+       case 1: return index(vowels, *obj) ? "an" : "a";
+       case 2: return "some";
+       case 3: return "many";
+       }
+
+       return "(unknown)";
+}
+
+/* better br_string */
+STATIC_OVL const char *
+br_string2(br)
+branch *br;
+{
+       /* Special case: quest portal says closed if kicked from quest */
+       boolean closed_portal = 
+               (br->end2.dnum == quest_dnum && u.uevent.qexpelled);
+       switch(br->type)
+       {
+       case BR_PORTAL:  return closed_portal ? "Sealed portal" : "Portal";
+       case BR_NO_END1: return "Connection";
+       case BR_NO_END2: return (br->end1_up) ? "One way stairs up" : 
+               "One way stairs down";
+       case BR_STAIR:   return (br->end1_up) ? "Stairs up" : "Stairs down";
+       }
+
+       return "(unknown)";
+}
+
+STATIC_OVL const char*
+shop_string(rtype)
+int rtype;
+{
+       /* 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 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";
+       }
+}
+
+/* some utility macros for print_mapseen */
+#define TAB "   "
+#define BULLET ""
+#define PREFIX TAB TAB BULLET
+#define COMMA (i++ > 0 ? ", " : PREFIX)
+#define ADDNTOBUF(nam, var) { if (var) \
+       Sprintf(eos(buf), "%s%s " nam "%s", COMMA, seen_string((var), (nam)), \
+       ((var) != 1 ? "s" : "")); }
+#define ADDTOBUF(nam, var) { if (var) Sprintf(eos(buf), "%s " nam, COMMA); }
+
+STATIC_OVL void
+print_mapseen(win, mptr, printdun)
+winid win;
+mapseen *mptr;
+boolean printdun;
+{
+       char buf[BUFSZ];
+       int i, depthstart;
+
+       /* Damnable special cases */
+       /* The quest and knox should appear to be level 1 to match
+        * other text.
+        */
+       if (mptr->lev.dnum == quest_dnum || mptr->lev.dnum == knox_level.dnum)
+               depthstart = 1;
+       else
+               depthstart = dungeons[mptr->lev.dnum].depth_start;  
+
+       if (printdun) {
+               /* Sokoban lies about dunlev_ureached and we should
+                * suppress the negative numbers in the endgame.
+                */
+               if (dungeons[mptr->lev.dnum].dunlev_ureached == 1 ||
+                       mptr->lev.dnum == sokoban_dnum || In_endgame(&mptr->lev))
+                       Sprintf(buf, "%s:", dungeons[mptr->lev.dnum].dname);
+               else
+                       Sprintf(buf, "%s: levels %d to %d", 
+                               dungeons[mptr->lev.dnum].dname,
+                               depthstart, depthstart + 
+                               dungeons[mptr->lev.dnum].dunlev_ureached - 1);
+               putstr(win, ATR_INVERSE, buf);
+       }
+
+       /* calculate level number */
+       i = depthstart + mptr->lev.dlevel - 1;
+       if (Is_astralevel(&mptr->lev))
+               Sprintf(buf, TAB "Astral Plane:");
+       else if (In_endgame(&mptr->lev))
+               /* Negative numbers are mildly confusing, since they are never
+                * shown to the player, except in wizard mode.  We could show
+                * "Level -1" for the earth plane, for example.  Instead,
+                * show "Plane 1" for the earth plane to differentiate from
+                * level 1.  There's not much to show, but maybe the player
+                * wants to #annotate them for some bizarre reason.
+                */
+               Sprintf(buf, TAB "Plane %i:", -i);
+       else
+               Sprintf(buf, TAB "Level %d:", i);
+       
+#ifdef WIZARD
+       /* wizmode prints out proto dungeon names for clarity */
+       if (wizard) {
+               s_level *slev;
+               if (slev = Is_special(&mptr->lev))
+                       Sprintf(eos(buf), " [%s]", slev->proto);
+       }
+#endif
+
+       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" : "");
+       putstr(win, ATR_BOLD, buf);
+
+       if (mptr->feat.forgot) return;
+
+       if (INTEREST(mptr->feat)) {
+               buf[0] = 0;
+               
+               i = 0; /* interest counter */
+
+               /* 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));
+
+               ADDNTOBUF("fountain", mptr->feat.nfount)
+               ADDNTOBUF("sink", mptr->feat.nsink)
+               ADDNTOBUF("throne", mptr->feat.nthrone)
+               ADDNTOBUF("tree", mptr->feat.ntree);
+               /*
+               ADDTOBUF("water", mptr->feat.water)
+               ADDTOBUF("lava", mptr->feat.lava)
+               ADDTOBUF("ice", mptr->feat.ice)
+               */
+
+               /* capitalize afterwards */
+               i = strlen(PREFIX);
+               buf[i] = toupper(buf[i]);
+
+               putstr(win, 0, buf);
+       }
+
+       /* print out branches */
+       if (mptr->br) {
+               Sprintf(buf, PREFIX "%s to %s", br_string2(mptr->br), 
+                       dungeons[mptr->br->end2.dnum].dname);
+
+               /* since mapseen objects are printed out in increasing order
+                * of dlevel, clarify which level this branch is going to
+                * if the branch goes upwards.  Unless it's the end game
+                */
+               if (mptr->br->end1_up && !In_endgame(&(mptr->br->end2)))
+                       Sprintf(eos(buf), ", level %d", depth(&(mptr->br->end2)));
+               putstr(win, 0, buf);
+       }
+}
+#endif /* DUNGEON_OVERVIEW */
+
 /*dungeon.c*/
index e86596ff160978c5a45851b5b613d51c46444fa5..b335fba10c15875c9216a951b21a8dd42ece9ea1 100644 (file)
@@ -922,6 +922,9 @@ mklev()
 {
        struct mkroom *croom;
 
+#ifdef DUNGEON_OVERVIEW
+       init_mapseen(&u.uz);
+#endif
        if(getbones()) return;
        in_mklev = TRUE;
        makelevel();
index f7a7fe73665f5f0ebf35ec9024c64baff0df73ea..116c578069f1e5861f93ffadaf38c2951c694ed0 100644 (file)
@@ -157,6 +157,9 @@ boolean seal;
     if (seal) {        /* remove the portal to the quest - sealing it off */
        int reexpelled = u.uevent.qexpelled;
        u.uevent.qexpelled = 1;
+#ifdef DUNGEON_OVERVIEW
+       remdun_mapseen(quest_dnum);
+#endif
        /* Delete the near portal now; the far (main dungeon side)
           portal will be deleted as part of arrival on that level.
           If monster movement is in progress, any who haven't moved
index 4a0deb067266ac21f7279b58fc72a6716e0972c8..4cd0af8fbce89c448ac85f088450caa95a693037 100644 (file)
@@ -537,6 +537,9 @@ forget_map(howmuch)
                levl[zx][zy].seenv = 0;
                levl[zx][zy].waslit = 0;
                levl[zx][zy].glyph = cmap_to_glyph(S_stone);
+#ifdef DUNGEON_OVERVIEW
+               levl[zx][zy].styp = STONE;
+#endif
            }
 }
 
@@ -597,6 +600,9 @@ forget_levels(percent)
        count = ((count * percent) + 50) / 100;
        for (i = 0; i < count; i++) {
            level_info[indices[i]].flags |= FORGOTTEN;
+#ifdef DUNGEON_OVERVIEW
+           forget_mapseen(indices[i]);
+#endif
        }
 }
 
index 0bc5fc06fa60aac8f8bf5e84667f15d254a126c1..51007444d70aa77bd52fe45f45fc7ad4fa49a8e9 100644 (file)
@@ -808,6 +808,10 @@ skip:
     /* Set the new min and max pointers. */
     viz_rmin  = next_rmin;
     viz_rmax = next_rmax;
+
+#ifdef DUNGEON_OVERVIEW
+    recalc_mapseen();
+#endif
 }
 
 
index efc2413259e05ef564f59103f812177d89352243..b1fb28aa05ca1792e197cdac9961b348885fb2cb 100644 (file)
@@ -677,6 +677,9 @@ static const char *build_opts[] = {
 #ifdef WIZARD
                "debug mode",
 #endif
+#ifdef DUNGEON_OVERVIEW
+               "dungeon map overview patch",
+#endif
 #ifdef ELBERETH
                "Elbereth",
 #endif