From: nethack.allison Date: Thu, 20 Apr 2006 00:57:45 +0000 (+0000) Subject: experimental - dungeon overview (trunk only) X-Git-Tag: MOVE2GIT~1053 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=660d3589c57cf0a598ef6c215ee0eaa93f069df6;p=nethack experimental - dungeon overview (trunk only) 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), Papaganou (for the #annotate suggestion and some formatting feedback), and (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.) --- diff --git a/include/config.h b/include/config.h index b60db088c..0aa825656 100644 --- a/include/config.h +++ b/include/config.h @@ -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 */ diff --git a/include/dungeon.h b/include/dungeon.h index 6c2d27c35..3b1b04fc9 100644 --- a/include/dungeon.h +++ b/include/dungeon.h @@ -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 */ diff --git a/include/extern.h b/include/extern.h index 26e4cb183..72d3b9c8c 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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 ### */ diff --git a/include/rm.h b/include/rm.h index 65e7253d9..3851d25f0 100644 --- a/include/rm.h +++ b/include/rm.h @@ -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 */ }; /* diff --git a/src/cmd.c b/src/cmd.c index 412ac9f72..e95615fcf 100644 --- 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 diff --git a/src/display.c b/src/display.c index d23beabd7..4dd5fafc8 100644 --- a/src/display.c +++ b/src/display.c @@ -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 */ } /* diff --git a/src/do.c b/src/do.c index e72504a45..718ad2f38 100644 --- 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); diff --git a/src/dungeon.c b/src/dungeon.c index b3ee9ab2c..44f43361a 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -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*/ diff --git a/src/mklev.c b/src/mklev.c index e86596ff1..b335fba10 100644 --- a/src/mklev.c +++ b/src/mklev.c @@ -922,6 +922,9 @@ mklev() { struct mkroom *croom; +#ifdef DUNGEON_OVERVIEW + init_mapseen(&u.uz); +#endif if(getbones()) return; in_mklev = TRUE; makelevel(); diff --git a/src/quest.c b/src/quest.c index f7a7fe736..116c57806 100644 --- a/src/quest.c +++ b/src/quest.c @@ -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 diff --git a/src/read.c b/src/read.c index 4a0deb067..4cd0af8fb 100644 --- a/src/read.c +++ b/src/read.c @@ -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 } } diff --git a/src/vision.c b/src/vision.c index 0bc5fc06f..51007444d 100644 --- a/src/vision.c +++ b/src/vision.c @@ -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 } diff --git a/util/makedefs.c b/util/makedefs.c index efc241325..b1fb28aa0 100644 --- a/util/makedefs.c +++ b/util/makedefs.c @@ -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