diff --git a/include/dungeon.h b/include/dungeon.h index f92592ea9..707ac6cf7 100644 --- a/include/dungeon.h +++ b/include/dungeon.h @@ -219,6 +219,7 @@ typedef struct mapseen { Bitfield(shoptype, 5); } feat; struct mapseen_flags { + Bitfield(unreachable, 1); /* can't get back to this level */ Bitfield(forgot, 1); /* player has forgotten about this level */ Bitfield(knownbones, 1); /* player aware of bones */ Bitfield(oracle, 1); @@ -226,8 +227,8 @@ typedef struct mapseen { Bitfield(bigroom, 1); Bitfield(castle, 1); Bitfield(castletune, 1); /* add tune hint to castle annotation */ - Bitfield(valley, 1); + Bitfield(valley, 1); Bitfield(msanctum, 1); Bitfield(ludios, 1); # ifdef REINCARNATION diff --git a/include/patchlevel.h b/include/patchlevel.h index 8712cf3c3..bb398bd4f 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 54 +#define EDITLEVEL 55 #define COPYRIGHT_BANNER_A \ "NetHack, Copyright 1985-2012" diff --git a/include/you.h b/include/you.h index 55f0864a6..4b79f7fc5 100644 --- a/include/you.h +++ b/include/you.h @@ -50,6 +50,7 @@ struct u_event { Bitfield(uhand_of_elbereth,2); /* became Hand of Elbereth */ #endif Bitfield(udemigod,1); /* killed the wiz */ + Bitfield(uvibrated,1); /* stepped on "vibrating square" */ Bitfield(ascended,1); /* has offered the Amulet */ }; diff --git a/src/do.c b/src/do.c index 532e3e7c5..7818ec579 100644 --- a/src/do.c +++ b/src/do.c @@ -20,6 +20,8 @@ STATIC_DCL int NDECL(currentlevel_rewrite); STATIC_DCL void NDECL(final_level); /* static boolean FDECL(badspot, (XCHAR_P,XCHAR_P)); */ +extern int n_dgns; /* number of dungeons, from dungeon.c */ + static NEARDATA const char drop_types[] = { ALLOW_COUNT, COIN_CLASS, ALL_CLASSES, 0 }; @@ -1100,6 +1102,11 @@ boolean at_stairs, falling, portal; /* discard unreachable levels; keep #0 */ for (l_idx = maxledgerno(); l_idx > 0; --l_idx) delete_levelfile(l_idx); +#ifdef DUNGEON_OVERVIEW + /* mark #overview data for all dungeon branches as uninteresting */ + for (l_idx = 0; l_idx < n_dgns; ++l_idx) + remdun_mapseen(l_idx); +#endif } #ifdef REINCARNATION diff --git a/src/dungeon.c b/src/dungeon.c index 57fcacb6f..e9b66e194 100644 --- a/src/dungeon.c +++ b/src/dungeon.c @@ -24,8 +24,8 @@ struct proto_dungeon { int n_brs; /* number of tmpbranch entries */ }; -int n_dgns; /* number of dungeons (used here, */ - /* and mklev.c) */ +int n_dgns; /* number of dungeons (also used */ + /* in mklev.c and do.c) */ static branch *branches = (branch *) 0; /* dungeon branch list */ struct lchoice { @@ -65,8 +65,10 @@ STATIC_DCL void FDECL(save_mapseen, (int, mapseen *)); STATIC_DCL mapseen *FDECL(find_mapseen, (d_level *)); STATIC_DCL void FDECL(print_mapseen, (winid,mapseen *,int,int,BOOLEAN_P)); STATIC_DCL boolean FDECL(interest_mapseen, (mapseen *)); +STATIC_DCL void FDECL(traverse_mapseenchn, (BOOLEAN_P,winid,int,int,int *)); STATIC_DCL const char *FDECL(seen_string, (XCHAR_P,const char *)); STATIC_DCL const char *FDECL(br_string2, (branch *)); +STATIC_DCL const char *FDECL(endgamelevelname, (char *,int)); STATIC_DCL const char *FDECL(shop_string, (int)); STATIC_DCL char *FDECL(tunesuffix, (mapseen *,char *)); #endif /* DUNGEON_OVERVIEW */ @@ -1872,18 +1874,16 @@ d_level *dest; } } -/* add a custom name to the current level */ +/* #annotate command - 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 */ if (!(mptr = find_mapseen(&u.uz))) return 0; - Sprintf(qbuf,"What do you want to call this dungeon level? "); - getlin(qbuf, nbuf); + getlin("What do you want to call this dungeon level?", nbuf); if (index(nbuf, '\033')) return 0; /* discard old annotation, if any */ @@ -1997,6 +1997,8 @@ int fd; /* Remove all mapseen objects for a particular dnum. * Useful during quest expulsion to remove quest levels. + * [No longer deleted, just marked as unreachable. #overview will + * ignore such levels, end of game disclosure will include them.] */ void remdun_mapseen(dnum) @@ -2007,6 +2009,10 @@ int dnum; mptraddr = &mapseenchn; while ((mptr = *mptraddr) != 0) { if (mptr->lev.dnum == dnum) { +#if 1 /* use this... */ + mptr->flags.unreachable = 1; + } +#else /* old deletion code */ *mptraddr = mptr->next; if (mptr->custom) free((genericptr_t) mptr->custom); @@ -2014,6 +2020,7 @@ int dnum; savecemetery(-1, FREE_SAVE, &mptr->final_resting_place); free((genericptr_t) mptr); } else +#endif mptraddr = &mptr->next; } } @@ -2025,7 +2032,7 @@ 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, *init, *old; + mapseen *mptr, *init, *prev; init = (mapseen *) alloc(sizeof *init); (void) memset((genericptr_t)init, 0, sizeof *init); @@ -2040,25 +2047,20 @@ d_level *lev; init->lev.dnum = lev->dnum; init->lev.dlevel = lev->dlevel; - if (!mapseenchn) { + /* walk until we get to the place where we should insert init */ + for (mptr = mapseenchn, prev = 0; mptr; prev = mptr, mptr = mptr->next) + if (mptr->lev.dnum > init->lev.dnum || + (mptr->lev.dnum == init->lev.dnum && + mptr->lev.dlevel > init->lev.dlevel)) + break; + if (!prev) { + init->next = mapseenchn; mapseenchn = init; - return; + } else { + mptr = prev->next; + prev->next = init; + init->next = mptr; } - - /* 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) \ @@ -2082,8 +2084,8 @@ interest_mapseen(mptr) mapseen *mptr; { 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.unreachable || mptr->flags.forgot) return FALSE; + /* level is of interest if it has an auto-generated annotation */ if (mptr->flags.oracle || mptr->flags.bigroom || # ifdef REINCARNATION mptr->flags.roguelevel || @@ -2095,6 +2097,12 @@ mapseen *mptr; be furthest one reached, unless level teleporting in wizard mode) */ if (In_sokoban(&mptr->lev) && (In_sokoban(&u.uz) || !mptr->flags.sokosolved)) return TRUE; + /* when in the endgame, list all endgame levels visited, whether they + have annotations or not, so that #overview doesn't become extremely + sparse once the rest of the dungeon has been flagged as unreachable */ + if (In_endgame(&u.uz)) return In_endgame(&mptr->lev); + /* level is of interest if it has non-zero feature count or known bones + or user annotation or known connection to another dungeon brancth */ return (INTEREST(mptr->feat) || (mptr->final_resting_place && (mptr->flags.knownbones || wizard)) || @@ -2109,17 +2117,34 @@ recalc_mapseen() struct monst *mtmp; struct cemetery *bp, **bonesaddr; unsigned i, ridx; - int x, y, count, atmp; + int x, y, ltyp, count, atmp; /* 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 + * [Since quest explusion no longer deletes quest mapseen data, + * null return from find_mapseen() should now be impossible.] */ if (!(mptr = find_mapseen(&u.uz))) return; /* 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 */ + if (mptr->flags.unreachable) { + mptr->flags.unreachable = 0; /* reached it; Eye of the Aethiopica? */ + if (In_quest(&u.uz)) { + mapseen *mptrtmp = mapseenchn; + + /* when quest was unreachable due to ejection and portal removal, + getting back to it via arti-invoke should revive annotation + data for all quest levels, not just the one we're on now */ + do { + if (mptrtmp->lev.dnum == mptr->lev.dnum) + mptrtmp->flags.unreachable = 0; + mptrtmp = mptrtmp->next; + } while (mptrtmp); + } + } mptr->flags.knownbones = 0; mptr->flags.sokosolved = In_sokoban(&u.uz) && !Sokoban; /* mptr->flags.bigroom retains previous value when hero can't see */ @@ -2190,17 +2215,21 @@ recalc_mapseen() * the ability to have non-dungeon glyphs float above the last known * dungeon glyph (i.e. items on fountains). */ - if (!Levitation) - lastseentyp[u.ux][u.uy] = levl[u.ux][u.uy].typ; - for (x = 1; x < COLNO; x++) { for (y = 0; y < ROWNO; y++) { - if (cansee(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) : - levl[x][y].typ; + if (cansee(x, y) || (x == u.ux && y == u.uy && !Levitation)) { + ltyp = levl[x][y].typ; + if (ltyp == DRAWBRIDGE_UP) + switch (levl[x][y].drawbridgemask & DB_UNDER) { + case DB_ICE: ltyp = ICE; break; + case DB_LAVA: ltyp = LAVAPOOL; break; + case DB_MOAT: ltyp = MOAT; break; + default: ltyp = STONE; break; + } + if ((mtmp = m_at(x, y)) != 0 && + mtmp->m_ap_type == M_AP_FURNITURE && canseemon(mtmp)) + ltyp = cmap_to_type(mtmp->mappearance); + lastseentyp[x][y] = ltyp; } switch (lastseentyp[x][y]) { @@ -2323,6 +2352,7 @@ int roomno; mptr->msrooms[roomno].seen = 1; } +/* #overview command */ int dooverview() { @@ -2330,32 +2360,53 @@ dooverview() return 0; } +/* called for #overview or for end of game disclosure */ void show_overview(why, reason) int why; /* 0 => #overview command, - 1 or 2 => end of game disclosure (1: alive, 2: dead) */ + 1 or 2 => final disclosure (1: hero lived, 2: hero died) */ int reason; /* how hero died; used when disclosing end-of-game level */ { winid win; - mapseen *mptr; int lastdun = -1; /* 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 (why > 0 || interest_mapseen(mptr)) { - print_mapseen(win, mptr, why, reason, - (boolean)(mptr->lev.dnum != lastdun)); - lastdun = mptr->lev.dnum; - } - } + /* show the endgame levels before the rest of the dungeon, + so that the Planes (dnum 5-ish) come out above main dungeon (dnum 0) */ + if (In_endgame(&u.uz)) + traverse_mapseenchn(TRUE, win, why, reason, &lastdun); + /* if game is over or we're not in the endgame yet, show the dungeon */ + if (why > 0 || !In_endgame(&u.uz)) + traverse_mapseenchn(FALSE, win, why, reason, &lastdun); display_nhwindow(win, TRUE); destroy_nhwindow(win); } +/* display endgame levels or non-endgame levels, not both */ +STATIC_OVL void +traverse_mapseenchn(viewendgame, win, why, reason, lastdun_p) +boolean viewendgame; +winid win; +int why, reason, *lastdun_p; +{ + mapseen *mptr; + boolean showheader; + + for (mptr = mapseenchn; mptr; mptr = mptr->next) { + if (viewendgame ^ In_endgame(&mptr->lev)) continue; + + /* only print out info for a level or a dungeon if interest */ + if (why > 0 || interest_mapseen(mptr)) { + showheader = (boolean)(mptr->lev.dnum != *lastdun_p); + print_mapseen(win, mptr, why, reason, showheader); + *lastdun_p = mptr->lev.dnum; + } + } +} + STATIC_OVL const char * seen_string(x, obj) xchar x; @@ -2393,6 +2444,29 @@ branch *br; return "(unknown)"; } +/* get the name of an endgame level; topten.c does something similar */ +STATIC_OVL const char * +endgamelevelname(outbuf, indx) +char *outbuf; +int indx; +{ + const char *planename = 0; + + *outbuf = '\0'; + switch (indx) { + case -5: Strcpy(outbuf, "Astral Plane"); break; + case -4: planename = "Water"; break; + case -3: planename = "Fire"; break; + case -2: planename = "Air"; break; + case -1: planename = "Earth"; break; + } + if (planename) + Sprintf(outbuf, "Plane of %s", planename); + else if (!*outbuf) + Sprintf(outbuf, "unknown plane #%d", indx); + return outbuf; +} + STATIC_OVL const char * shop_string(rtype) int rtype; @@ -2461,11 +2535,11 @@ char *outbuf; } while (0) STATIC_OVL void -print_mapseen(win, mptr, final, reason, printdun) +print_mapseen(win, mptr, final, how, printdun) winid win; mapseen *mptr; int final; /* 0: not final; 1: game over, alive; 2: game over, dead */ -int reason; /* cause of death; only used if final==2 and mptr->lev==u.uz */ +int how; /* cause of death; only used if final==2 and mptr->lev==u.uz */ boolean printdun; { char buf[BUFSZ], tmpbuf[BUFSZ]; @@ -2492,24 +2566,13 @@ boolean printdun; 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); + putstr(win, !final ? ATR_INVERSE : 0, buf); } /* calculate level number */ i = depthstart + mptr->lev.dlevel - 1; - if (Is_astralevel(&mptr->lev)) - Sprintf(buf, "%sAstral Plane:", TAB); - 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 reason such as keeping - * track of encounters with the Wizard. - * [TODO: change this to be "Plane of :"] - */ - Sprintf(buf, "%sPlane %i:", TAB, -i); + if (In_endgame(&mptr->lev)) + Sprintf(buf, "%s%s:", TAB, endgamelevelname(tmpbuf, i)); else /* FIXME: when this branch has only one level (Ft.Ludios), * listing "Level 1:" for it might confuse inexperienced @@ -2530,7 +2593,8 @@ boolean printdun; if (mptr->custom) Sprintf(eos(buf), " (%s)", mptr->custom); if (on_level(&u.uz, &mptr->lev)) - Sprintf(eos(buf), " <- You %s here", !final ? "are" : "were"); + Sprintf(eos(buf), " <- You %s here.", + (!final || (final == 1 && how == ASCENDED)) ? "are" : "were"); putstr(win, !final ? ATR_BOLD : 0, buf); if (mptr->flags.forgot) return; @@ -2539,7 +2603,6 @@ boolean printdun; buf[0] = 0; i = 0; /* interest counter */ - /* List interests in an order vaguely corresponding to * how important they are. */ @@ -2592,6 +2655,9 @@ boolean printdun; } else if (mptr->flags.roguelevel) { Sprintf(buf, "%sA primitive area.", PREFIX); # endif + } else if (on_level(&mptr->lev, &qstart_level)) { + Sprintf(buf, "%sHome%s.", PREFIX, + mptr->flags.unreachable ? " (no way back...)" : ""); } 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 @@ -2634,7 +2700,7 @@ boolean printdun; if (died_here) { /* disclosure occurs before bones creation, so listing dead hero here doesn't give away whether bones are produced */ - formatkiller(tmpbuf, sizeof tmpbuf, reason); + formatkiller(tmpbuf, sizeof tmpbuf, how); /* rephrase a few death reasons to work with "you" */ (void) strsubst(tmpbuf, " himself", " yourself"); (void) strsubst(tmpbuf, " herself", " yourself"); diff --git a/src/hack.c b/src/hack.c index 0f9082e40..ed56ac9ee 100644 --- a/src/hack.c +++ b/src/hack.c @@ -1638,6 +1638,7 @@ invocation_message() else Sprintf(buf, "under your %s", makeplural(body_part(FOOT))); You_feel("a strange vibration %s.", buf); + u.uevent.uvibrated = 1; if (otmp && otmp->spe == 7 && otmp->lamplit) pline("%s %s!", The(xname(otmp)), Blind ? "throbs palpably" : "glows with a strange light");