From 3ed1aedeb73e82e7339e06690524b22c2c79fc78 Mon Sep 17 00:00:00 2001 From: Tung Nguyen Date: Fri, 11 Mar 2016 00:24:40 +1100 Subject: [PATCH 01/81] Fix travel moving player back and forth infinitely This fixes a bug where hundreds of turns are wasted by the travel system moving the player back and forth when the player targets an unreachable space and sight-blocking obstacles occur in certain formations in-between. The player will only be stopped if they're interrupted externally, e.g. growing hungry or being hit by a monster. See the comment in the code for full details. Based on DynaHack commit 02da53e (Fix travel moving player back and forth repeatedly) by me. --- src/hack.c | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/src/hack.c b/src/hack.c index 6ce28907a..0669978cb 100644 --- a/src/hack.c +++ b/src/hack.c @@ -929,7 +929,42 @@ boolean guess; int nx = x + xdir[ordered[dir]]; int ny = y + ydir[ordered[dir]]; - if (!isok(nx, ny)) + /* + * When guessing and trying to travel as close as possible + * to an unreachable target space, don't include spaces + * that would never be picked as a guessed target in the + * travel matrix describing player-reachable spaces. + * This stops travel from getting confused and moving the + * player back and forth in certain degenerate configurations + * of sight-blocking obstacles, e.g. + * + * T 1. Dig this out and carry enough to not be + * #### able to squeeze through diagonal gaps. + * #--.--- Stand at @ and target travel at space T. + * @..... + * |..... + * + * T 2. couldsee() marks spaces marked a and x as + * #### eligible guess spaces to move the player + * a--.--- towards. Space a is closest to T, so it gets + * @xxxxx chosen. Travel system moves @ right to travel + * |xxxxx to space a. + * + * T 3. couldsee() marks spaces marked b, c and x + * #### as eligible guess spaces to move the player + * a--c--- towards. Since findtravelpath() is called + * b@xxxx repeatedly during travel, it doesn't remember + * |xxxxx that it wanted to go to space a, so in + * comparing spaces b and c, b is chosen, since + * it seems like the closest eligible space to T. + * Travel system moves @ left to go to space b. + * + * 4. Go to 2. + * + * By limiting the travel matrix here, space a in the example + * above is never included in it, preventing the cycle. + */ + if (!isok(nx, ny) || (guess && !couldsee(nx, ny))) continue; if ((!Passes_walls && !can_ooze(&youmonst) && closed_door(x, y)) || sobj_at(BOULDER, x, y) From 49da8b87d8acd225d6932dea273a5bd5ec899b0b Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 26 May 2016 18:35:27 -0700 Subject: [PATCH 02/81] '^O' command documentation and '^' command help While looking for things in core which might conceivably trigger the Windows ctype assertion failure (haven't found any yet), I noticed that help_dir() was still treating ^O as a wizard mode-only command. Also, documentation about that command was never brought up to date. I wish this change to ^O hadn't been done. #overview already has a meta/alt M-O shortcut and overloading wizard mode commands makes documentation more complicated since wizard mode stuff traditionally has been left unmentioned. --- dat/cmdhelp | 4 ++-- doc/Guidebook.mn | 8 ++++++++ doc/Guidebook.tex | 9 +++++++++ src/cmd.c | 16 ++++++++-------- 4 files changed, 27 insertions(+), 10 deletions(-) diff --git a/dat/cmdhelp b/dat/cmdhelp index b82560099..ed3f76107 100644 --- a/dat/cmdhelp +++ b/dat/cmdhelp @@ -1,5 +1,5 @@ ^ Show the type of a trap -^[ Cancel command +^[ Cancel command (same as ESCape key) ^A Redo the previous command ^C Quit the game ^D Kick something (usually a door, chest, or box) @@ -7,7 +7,7 @@ ^F Map the level (available in debug mode only) ^G Create a monster (available in debug mode only) ^I Identify all items (available in debug mode only) -^O Show location of special levels (available in debug mode only) +^O Show dungeon overview (normal play) or special levels (debug mode) ^P Toggle through previously displayed game messages ^R Redraw screen ^T Teleport around level diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 977cad2a6..1666c19c5 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -691,6 +691,14 @@ a further menu or prompt will appear once you've closed this menu. The available options are listed later in this Guidebook. Options are usually set before the game rather than with the `O' command; see the section on options below. +.lp ^O +Show overview or show dungeon layout +.lp "" +In normal play and in explore mode, a shortcut for the ``#overview'' +extended command to list interesting dungeon levels visited. +.lp "" +In debug mode, an extra command which lists the placement of all special +levels. .lp p Pay your shopping bill. .lp P diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index b66e5466c..45e010893 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -835,6 +835,15 @@ The available options are listed later in this Guidebook. Options are usually set before the game rather than with the `{\tt O}' command; see the section on options below. %.lp +\item[\tb{\^{}O}] +Show overview or show dungeon layout\\ +%.lp "" +In normal play and in explore mode, a shortcut for the ``{\tt \#overview}'' +extended command to list interesting dungeon levels visited.\\ +%.lp "" +In debug mode, an extra command which lists the placement of all special +levels. +%.lp \item[\tb{p}] Pay your shopping bill. %.lp diff --git a/src/cmd.c b/src/cmd.c index 80dbcd0f4..b4e126604 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -3794,9 +3794,9 @@ help_dir(sym, msg) char sym; const char *msg; { + static const char wiz_only_list[] = "EFGIVW"; char ctrl; winid win; - static const char wiz_only_list[] = "EFGIOVW"; char buf[BUFSZ], buf2[BUFSZ], *explain; win = create_nhwindow(NHW_TEXT); @@ -3807,10 +3807,10 @@ const char *msg; putstr(win, 0, buf); putstr(win, 0, ""); } - if (letter(sym)) { - sym = highc(sym); - ctrl = (sym - 'A') + 1; - if ((explain = dowhatdoes_core(ctrl, buf2)) + if (letter(sym) || sym == '[') { /* 'dat/cmdhelp' shows ESC as ^[ */ + sym = highc(sym); /* @A-Z[ (note: letter() accepts '@') */ + ctrl = (sym - 'A') + 1; /* 0-27 (note: 28-31 aren't applicable) */ + if ((explain = dowhatdoes_core(ctrl, buf2)) != 0 && (!index(wiz_only_list, sym) || wizard)) { Sprintf(buf, "Are you trying to use ^%c%s?", sym, index(wiz_only_list, sym) @@ -3820,9 +3820,9 @@ const char *msg; putstr(win, 0, ""); putstr(win, 0, explain); putstr(win, 0, ""); - putstr(win, 0, "To use that command, you press"); - Sprintf(buf, "the key, and the <%c> key at the same time.", - sym); + putstr(win, 0, + "To use that command, hold down the key as a shift"); + Sprintf(buf, "and press the <%c> key.", sym); putstr(win, 0, buf); putstr(win, 0, ""); } From d51db5a992e9513ca77bc5999b8878edd937fe2c Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Fri, 27 May 2016 18:47:56 +0300 Subject: [PATCH 03/81] Make getpos for doors also target drawbridges --- include/rm.h | 4 ++++ src/do_name.c | 8 +++----- src/pager.c | 3 --- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/include/rm.h b/include/rm.h index 5e7104898..21f5036f7 100644 --- a/include/rm.h +++ b/include/rm.h @@ -227,6 +227,10 @@ #define DARKROOMSYM (Is_rogue_level(&u.uz) ? S_stone : S_darkroom) +#define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap) +#define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge) +#define is_cmap_door(i) ((i) >= S_vodoor && (i) <= S_hcdoor) + struct symdef { uchar sym; const char *explanation; diff --git a/src/do_name.c b/src/do_name.c index 8e0af2934..179e0b53f 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -137,11 +137,9 @@ int glyph, gloc; && glyph != objnum_to_glyph(BOULDER) && glyph != objnum_to_glyph(ROCK)); case GLOC_DOOR: return (glyph_is_cmap(glyph) - && ((glyph_to_cmap(glyph) == S_hcdoor) - || (glyph_to_cmap(glyph) == S_vcdoor) - || (glyph_to_cmap(glyph) == S_hodoor) - || (glyph_to_cmap(glyph) == S_vodoor) - || (glyph_to_cmap(glyph) == S_ndoor))); + && (is_cmap_door(glyph_to_cmap(glyph)) + || is_cmap_drawbridge(glyph_to_cmap(glyph)) + || glyph_to_cmap(glyph) == S_ndoor)); } } diff --git a/src/pager.c b/src/pager.c index 33b867ce4..2e4256eeb 100644 --- a/src/pager.c +++ b/src/pager.c @@ -775,9 +775,6 @@ const char **firstmatch; } } -#define is_cmap_trap(i) ((i) >= S_arrow_trap && (i) <= S_polymorph_trap) -#define is_cmap_drawbridge(i) ((i) >= S_vodbridge && (i) <= S_hcdbridge) - /* Now check for graphics symbols */ alt_i = (sym == (looked ? showsyms[0] : defsyms[0].sym)) ? 0 : (2 + 1); for (hit_trap = FALSE, i = 0; i < MAXPCHARS; i++) { From 5987c6246838d080d7fd83a65b3495e961a7dc11 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 27 May 2016 16:10:03 -0700 Subject: [PATCH 04/81] getpos of doors Using 'd' to cycle through door locations is worthwhile when #terrain is executing as well as when picking a location. --- src/do_name.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/do_name.c b/src/do_name.c index 179e0b53f..4c29ec291 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -63,8 +63,9 @@ const char *goal; putstr(tmpwin, 0, "Use m or M to move the cursor to next monster."); if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) putstr(tmpwin, 0, "Use o or O to move the cursor to next object."); + putstr(tmpwin, 0, /* d,D are useful regardless of terrainmode */ + "Use d or D to move the cursor to next door or doorway."); if (!iflags.terrainmode) { - putstr(tmpwin, 0, "Use d or D to move the cursor to next door or doorway."); if (getpos_hilitefunc) putstr(tmpwin, 0, "Use $ to display valid locations."); putstr(tmpwin, 0, "Use # to toggle automatic description."); From cb14aa332d7f0c05049b25a6a1ff4b7f5563f8f9 Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 27 May 2016 18:39:17 -0700 Subject: [PATCH 05/81] number_pad:-1 qwertz keyboard Reported almost exactly one year ago by a beta tester proofreading the Guidebook, the number_pad setting to support the German keyboard which swaps the Y and Z keys is for a keyboard that is used in other places too. The report mentioned France and Belgium; Wikipedia's "keyboard layout" entry mentions "Germany, Austria, Switzerland and other parts of Central Europe". This changes references to "German keyboard" (there were only a couple) into "QWERTZ keyboard". --- doc/Guidebook.mn | 2 +- doc/Guidebook.tex | 2 +- include/flag.h | 2 +- src/cmd.c | 3 ++- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 1666c19c5..fb73c4de2 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -2388,7 +2388,7 @@ is the same as specifying 0. (Settings 2 and 4 are for compatibility with MSDOS or old PC Hack; in addition to the different behavior for `5', `Alt-5' acts as `G' and `Alt-0' acts as `I'. -Setting -1 is to accommodate some German keyboards which have the +Setting -1 is to accommodate some QWERTZ keyboards which have the location of the `y' and `z' keys swapped.) When moving by numbers, to enter a count prefix for those commands which accept one (such as ``12s'' to search twelve times), precede it diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 45e010893..55c9960bc 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -2845,7 +2845,7 @@ is the same as specifying {\tt 0}. (Settings {\tt 2} and {\tt 4} are for compatibility with MSDOS or old PC Hack; in addition to the different behavior for `{\tt 5}', `{\tt Alt-5}' acts as `{\tt G}' and `{\tt Alt-0}' acts as `{\tt I}'. -Setting {\tt -1} is to accommodate some German keyboards which have the +Setting {\tt -1} is to accommodate some QWERTZ keyboards which have the location of the `{\tt y}' and `{\tt z}' keys swapped.) When moving by numbers, to enter a count prefix for those commands which accept one (such as ``{\tt 12s}'' to search twelve times), precede it diff --git a/include/flag.h b/include/flag.h index 4136070ab..4480b43b3 100644 --- a/include/flag.h +++ b/include/flag.h @@ -434,7 +434,7 @@ struct cmd { boolean num_pad; /* same as iflags.num_pad except during updates */ boolean pcHack_compat; /* for numpad: affects 5, M-5, and M-0 */ boolean phone_layout; /* inverted keypad: 1,2,3 above, 7,8,9 below */ - boolean swap_yz; /* German keyboards; use z to move NW, y to zap */ + boolean swap_yz; /* QWERTZ keyboards; use z to move NW, y to zap */ char move_W, move_NW, move_N, move_NE, move_E, move_SE, move_S, move_SW; const char *dirchars; /* current movement/direction characters */ const char *alphadirchars; /* same as dirchars if !numpad */ diff --git a/src/cmd.c b/src/cmd.c index b4e126604..d33d3c431 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -3350,7 +3350,8 @@ boolean initial; Cmd.num_pad = flagtemp; ++updated; } - /* swap_yz mode (only applicable for !num_pad) */ + /* swap_yz mode (only applicable for !num_pad); intended for + QWERTZ keyboard used in Central Europe, particularly Germany */ flagtemp = (iflags.num_pad_mode & 1) ? !Cmd.num_pad : FALSE; if (flagtemp != Cmd.swap_yz) { Cmd.swap_yz = flagtemp; From 6459b5d30112d4a9972a5b5adf88a92fe95a87c7 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 28 May 2016 00:51:54 -0700 Subject: [PATCH 06/81] black&white lava Add MG_BW_LAVA to mapglyph() instead of hijacking MG_DETECT. Used to display lava in inverse video if color is disabled and lava is using the same display character as water (which is the default). (The use_inverse option must be enabled for tty to honor it. X11's text mode doesn't care. Win32 does care but probably shouldn't--it's not a case like tty where the hardware might not support it.) This implements both MG_DETECT and MG_BW_LAVA for X11, but only if the program is built with TEXTCOLOR enabled. Those should work even when color is not supported, but I suspect that configuration is unlikely to ever be used so didn't want to spend the time to figure out how to do it. (The relevant data is overloaded on the color data, so not available when TEXTCOLOR is disabled.) The win32 revision is untested. --- include/hack.h | 14 ++++++++------ src/mapglyph.c | 4 +--- win/X11/winmap.c | 23 +++++++++++++++-------- win/tty/wintty.c | 3 ++- win/win32/mhmap.c | 2 +- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/include/hack.h b/include/hack.h index fb25a1672..eeb5ce3e6 100644 --- a/include/hack.h +++ b/include/hack.h @@ -57,13 +57,15 @@ #define DISMOUNT_BYCHOICE 6 /* Special returns from mapglyph() */ -#define MG_CORPSE 0x01 -#define MG_INVIS 0x02 -#define MG_DETECT 0x04 -#define MG_PET 0x08 -#define MG_RIDDEN 0x10 -#define MG_STATUE 0x20 +#define MG_CORPSE 0x01 +#define MG_INVIS 0x02 +#define MG_DETECT 0x04 +#define MG_PET 0x08 +#define MG_RIDDEN 0x10 +#define MG_STATUE 0x20 #define MG_OBJPILE 0x40 /* more than one stack of objects */ +#define MG_BW_LAVA 0x80 /* 'black & white lava': highlight lava if it + can't be distringuished from water by color */ /* sellobj_state() states */ #define SELL_NORMAL (0) diff --git a/src/mapglyph.c b/src/mapglyph.c index c0c640164..dab688620 100644 --- a/src/mapglyph.c +++ b/src/mapglyph.c @@ -134,9 +134,7 @@ unsigned *ospecial; } else if (!iflags.use_color && offset == S_lava && (showsyms[idx] == showsyms[S_pool + SYM_OFF_P] || showsyms[idx] == showsyms[S_water + SYM_OFF_P])) { - /* temporary? hack; makes tty use inverse video if the - corresponding boolean option is enabled */ - special |= MG_DETECT; + special |= MG_BW_LAVA; } else { cmap_color(offset); } diff --git a/win/X11/winmap.c b/win/X11/winmap.c index bd7639e03..ec5e5fa4a 100644 --- a/win/X11/winmap.c +++ b/win/X11/winmap.c @@ -124,8 +124,9 @@ int bkglyph UNUSED; } #ifdef TEXTCOLOR co_ptr = &map_info->text_map.colors[y][x]; - colordif = (((special & MG_PET) && iflags.hilite_pet) - || ((special & MG_OBJPILE) && iflags.hilite_pile)) + colordif = (((special & MG_PET) != 0 && iflags.hilite_pet) + || ((special & MG_OBJPILE) != 0 && iflags.hilite_pile) + || ((special & (MG_DETECT | MG_BW_LAVA)) != 0)) ? CLR_MAX : 0; if (*co_ptr != (uchar) (color + colordif)) { *co_ptr = (uchar) (color + colordif); @@ -1223,7 +1224,7 @@ XtPointer widget_data; /* expose event from Window widget */ /* * Do the actual work of the putting characters onto our X window. This * is called from the expose event routine, the display window (flush) - * routine, and the display cursor routine. The later involves inverting + * routine, and the display cursor routine. The last involves inverting * the foreground and background colors, which are also inverted when the * position's color is above CLR_MAX. * @@ -1332,7 +1333,7 @@ boolean inverted; struct text_map_info_t *text_map = &map_info->text_map; #ifdef TEXTCOLOR - if (iflags.use_color) { + { register char *c_ptr; char *t_ptr; int cur_col, color, win_ystart; @@ -1359,8 +1360,13 @@ boolean inverted; } XDrawImageString(XtDisplay(wp->w), XtWindow(wp->w), - cur_inv ? text_map->inv_color_gcs[color] - : text_map->color_gcs[color], + iflags.use_color + ? (cur_inv + ? text_map->inv_color_gcs[color] + : text_map->color_gcs[color]) + : (cur_inv + ? text_map->inv_copy_gc + : text_map->copy_gc), text_map->square_lbearing + (text_map->square_width * cur_col), win_ystart, t_ptr, count); @@ -1370,8 +1376,8 @@ boolean inverted; cur_col += count; } /* col loop */ } /* row loop */ - } else -#endif /* TEXTCOLOR */ + } +#else /* !TEXTCOLOR */ { int win_row, win_xstart; @@ -1393,6 +1399,7 @@ boolean inverted; count); } } +#endif /* ?TEXTCOLOR */ } } diff --git a/win/tty/wintty.c b/win/tty/wintty.c index ed7eefd23..8ceb1dc4e 100644 --- a/win/tty/wintty.c +++ b/win/tty/wintty.c @@ -3220,7 +3220,8 @@ int bkglyph UNUSED; /* must be after color check; term_end_color may turn off inverse too */ if (((special & MG_PET) && iflags.hilite_pet) || ((special & MG_OBJPILE) && iflags.hilite_pile) - || ((special & MG_DETECT) && iflags.use_inverse)) { + || ((special & MG_DETECT) && iflags.use_inverse) + || ((special & MG_BW_LAVA) && iflags.use_inverse)) { term_start_attr(ATR_INVERSE); reverse_on = TRUE; } diff --git a/win/win32/mhmap.c b/win/win32/mhmap.c index 5e9ac2630..821c91802 100644 --- a/win/win32/mhmap.c +++ b/win/win32/mhmap.c @@ -682,7 +682,7 @@ onPaint(HWND hWnd) &special, i, j); ch = (char) mgch; if (((special & MG_PET) && iflags.hilite_pet) - || ((special & MG_DETECT) + || ((special & (MG_DETECT | MG_BW_LAVA)) && iflags.use_inverse)) { back_brush = CreateSolidBrush(nhcolor_to_RGB(CLR_GRAY)); From 3bbf12d8b5e8a313ee0f74de80ee935e45cfdd0b Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 28 May 2016 13:52:53 +0300 Subject: [PATCH 07/81] Minor code reorg in gather locs --- src/do_name.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/src/do_name.c b/src/do_name.c index 4c29ec291..7c108ae0a 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -8,6 +8,7 @@ STATIC_DCL char *NDECL(nextmbuf); STATIC_DCL void FDECL(getpos_help, (BOOLEAN_P, const char *)); STATIC_DCL int FDECL(CFDECLSPEC cmp_coord_distu, (const void *, const void *)); +STATIC_DCL boolean FDECL(gather_locs_interesting, (int, int, int)); STATIC_DCL void FDECL(gather_locs, (coord **, int *, int)); STATIC_DCL void FDECL(auto_describe, (int, int)); STATIC_DCL void NDECL(do_mname); @@ -126,10 +127,15 @@ const void *b; #define GLOC_OBJS 1 #define GLOC_DOOR 2 -boolean -gather_locs_glyphmatch(glyph, gloc) -int glyph, gloc; +STATIC_OVL boolean +gather_locs_interesting(x,y, gloc) +int x,y, gloc; { + /* TODO: if glyph is a pile glyph, convert to ordinary one + * in order to keep tail/boulder/rock check simple. + */ + int glyph = glyph_at(x, y); + switch (gloc) { default: case GLOC_MONS: return (glyph_is_monster(glyph) @@ -151,7 +157,7 @@ coord **arr_p; int *cnt_p; int gloc; { - int x, y, pass, glyph, idx; + int x, y, pass, idx; /* * We always include the hero's location even if there is no monster @@ -168,14 +174,10 @@ int gloc; for (pass = 0; pass < 2; pass++) { for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) { - /* TODO: if glyph is a pile glyph, convert to ordinary one - * in order to keep tail/boulder/rock check simple. - */ - glyph = glyph_at(x, y); /* unlike '/M', this skips monsters revealed by warning glyphs and remembered invisible ones */ if ((x == u.ux && y == u.uy) - || gather_locs_glyphmatch(glyph, gloc)) { + || gather_locs_interesting(x,y, gloc)) { if (!pass) { ++*cnt_p; } else { From ac8bde0f70966a9fde977febe386c0a765547bb2 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 28 May 2016 14:27:07 +0300 Subject: [PATCH 08/81] Some code reorg for the getloc nearest/farthest locs --- src/do_name.c | 81 ++++++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 50 deletions(-) diff --git a/src/do_name.c b/src/do_name.c index 7c108ae0a..a585c2486 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -123,9 +123,13 @@ const void *b; return dist_1 - dist_2; } -#define GLOC_MONS 0 -#define GLOC_OBJS 1 -#define GLOC_DOOR 2 +enum gloctypes { + GLOC_MONS = 0, + GLOC_OBJS, + GLOC_DOOR, + + NUM_GLOCS +}; STATIC_OVL boolean gather_locs_interesting(x,y, gloc) @@ -300,10 +304,9 @@ const char *goal; static const char pick_chars[] = ".,;:"; const char *cp; boolean hilite_state = FALSE; - coord *monarr = (coord *) 0, *objarr = (coord *) 0, - *doorarr = (coord *) 0; - int moncount = 0, monidx = 0, objcount = 0, objidx = 0, - doorcount = 0, dooridx = 0; + coord *garr[NUM_GLOCS] = DUMMY; + int gcount[NUM_GLOCS] = DUMMY; + int gidx[NUM_GLOCS] = DUMMY; if (!goal) goal = "desired location"; @@ -424,51 +427,30 @@ const char *goal; } else if (c == '@') { /* return to hero's spot */ /* reset 'm','M' and 'o','O'; otherwise, there's no way for player to achieve that except by manually cycling through all spots */ - monidx = objidx = dooridx = 0; + for (i = 0; i < NUM_GLOCS; i++) + gidx[i] = 0; cx = u.ux; cy = u.uy; goto nxtc; - } else if (c == 'm' || c == 'M') { /* nearest or farthest monster */ - if (!monarr) { - gather_locs(&monarr, &moncount, GLOC_MONS); - monidx = 0; /* monarr[0] is hero's spot */ + } else if (c == 'm' || c == 'M' /* nearest or farthest monster */ + || c == 'o' || c == 'O' /* nearest or farthest object */ + || c == 'd' || c == 'D') { /* door/doorway */ + int gloc = (c == 'o' || c == 'O') ? GLOC_OBJS + : (c == 'd' || c == 'D') ? GLOC_DOOR + : GLOC_MONS; + + if (!garr[gloc]) { + gather_locs(&garr[gloc], &gcount[gloc], gloc); + gidx[gloc] = 0; /* garr[][0] is hero's spot */ } - if (c == 'm') { - monidx = (monidx + 1) % moncount; + if (c == 'm' || c == 'o' || c == 'd') { + gidx[gloc] = (gidx[gloc] + 1) % gcount[gloc]; } else { - if (--monidx < 0) - monidx = moncount - 1; + if (--gidx[gloc] < 0) + gidx[gloc] = gcount[gloc] - 1; } - cx = monarr[monidx].x; - cy = monarr[monidx].y; - goto nxtc; - } else if (c == 'o' || c == 'O') { /* nearest or farthest object */ - if (!objarr) { - gather_locs(&objarr, &objcount, GLOC_OBJS); - objidx = 0; /* objarr[0] is hero's spot */ - } - if (c == 'o') { - objidx = (objidx + 1) % objcount; - } else { - if (--objidx < 0) - objidx = objcount - 1; - } - cx = objarr[objidx].x; - cy = objarr[objidx].y; - goto nxtc; - } else if (c == 'd' || c == 'D') { /* door/doorway */ - if (!doorarr) { - gather_locs(&doorarr, &doorcount, GLOC_DOOR); - dooridx = 0; /* objarr[0] is hero's spot */ - } - if (c == 'd') { - dooridx = (dooridx + 1) % doorcount; - } else { - if (--dooridx < 0) - dooridx = doorcount - 1; - } - cx = doorarr[dooridx].x; - cy = doorarr[dooridx].y; + cx = garr[gloc][gidx[gloc]].x; + cy = garr[gloc][gidx[gloc]].y; goto nxtc; } else { if (!index(quitchars, c)) { @@ -564,10 +546,9 @@ const char *goal; clear_nhwindow(WIN_MESSAGE); ccp->x = cx; ccp->y = cy; - if (monarr) - free((genericptr_t) monarr); - if (objarr) - free((genericptr_t) objarr); + for (i = 0; i < NUM_GLOCS; i++) + if (garr[i]) + free((genericptr_t) garr[i]); getpos_hilitefunc = (void FDECL((*), (int))) 0; return result; } From bbe02dca2b7ad270cdcd7d85705c76d62f6267a9 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 28 May 2016 14:49:13 +0300 Subject: [PATCH 09/81] Add rudimentary autoexplore to getloc Pressing 'x' or 'X' will move the cursor to a possibly unexplored location. --- src/do_name.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/do_name.c b/src/do_name.c index a585c2486..153002e76 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -67,6 +67,7 @@ const char *goal; putstr(tmpwin, 0, /* d,D are useful regardless of terrainmode */ "Use d or D to move the cursor to next door or doorway."); if (!iflags.terrainmode) { + putstr(tmpwin, 0, "Use x or X to move the cursor to unexplored location."); if (getpos_hilitefunc) putstr(tmpwin, 0, "Use $ to display valid locations."); putstr(tmpwin, 0, "Use # to toggle automatic description."); @@ -127,10 +128,17 @@ enum gloctypes { GLOC_MONS = 0, GLOC_OBJS, GLOC_DOOR, + GLOC_EXPLORE, NUM_GLOCS }; + +#define IS_UNEXPLORED_LOC(x,y) (isok((x), (y)) \ + && glyph_is_cmap(levl[(x)][(y)].glyph) \ + && glyph_to_cmap(levl[(x)][(y)].glyph) == S_stone \ + && !levl[(x)][(y)].seenv) + STATIC_OVL boolean gather_locs_interesting(x,y, gloc) int x,y, gloc; @@ -151,7 +159,20 @@ int x,y, gloc; && (is_cmap_door(glyph_to_cmap(glyph)) || is_cmap_drawbridge(glyph_to_cmap(glyph)) || glyph_to_cmap(glyph) == S_ndoor)); + case GLOC_EXPLORE: return (glyph_is_cmap(glyph) + && (is_cmap_door(glyph_to_cmap(glyph)) + || is_cmap_drawbridge(glyph_to_cmap(glyph)) + || glyph_to_cmap(glyph) == S_ndoor + || glyph_to_cmap(glyph) == S_room + || glyph_to_cmap(glyph) == S_darkroom + || glyph_to_cmap(glyph) == S_corr + || glyph_to_cmap(glyph) == S_litcorr) + && (IS_UNEXPLORED_LOC(x+1,y) + || IS_UNEXPLORED_LOC(x-1,y) + || IS_UNEXPLORED_LOC(x,y+1) + || IS_UNEXPLORED_LOC(x,y-1))); } + return FALSE; } /* gather locations for monsters or objects shown on the map */ @@ -434,16 +455,18 @@ const char *goal; goto nxtc; } else if (c == 'm' || c == 'M' /* nearest or farthest monster */ || c == 'o' || c == 'O' /* nearest or farthest object */ + || c == 'x' || c == 'X' /* unexplored area */ || c == 'd' || c == 'D') { /* door/doorway */ int gloc = (c == 'o' || c == 'O') ? GLOC_OBJS : (c == 'd' || c == 'D') ? GLOC_DOOR + : (c == 'x' || c == 'X') ? GLOC_EXPLORE : GLOC_MONS; if (!garr[gloc]) { gather_locs(&garr[gloc], &gcount[gloc], gloc); gidx[gloc] = 0; /* garr[][0] is hero's spot */ } - if (c == 'm' || c == 'o' || c == 'd') { + if (c == 'm' || c == 'o' || c == 'd' || c == 'x') { gidx[gloc] = (gidx[gloc] + 1) % gcount[gloc]; } else { if (--gidx[gloc] < 0) From a102ee9a1a3b95f533bb08d80c93a95fe6b33b8b Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 28 May 2016 17:18:51 -0700 Subject: [PATCH 10/81] ctype tweaks None of these explains the Windows assertion failure. end.c - post-3.6.0 options.c - in #else block of #if win32 winmenu.c - X11 --- src/end.c | 2 +- src/options.c | 15 +++++++-------- win/X11/winmenu.c | 3 +-- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/end.c b/src/end.c index 437b9072d..57f8425bc 100644 --- a/src/end.c +++ b/src/end.c @@ -1678,7 +1678,7 @@ boolean ask; pfx = !strncmpi(buf, "the ", 3) ? 0 : !strncmpi(buf, "an ", 3) ? 1 : !strncmpi(buf, "a ", 2) ? 2 - : !isdigit(buf[2]) ? 4 : 0; + : !digit(buf[2]) ? 4 : 0; if (class_header) ++pfx; Sprintf(buftoo, "%*s%s", pfx, "", buf); diff --git a/src/options.c b/src/options.c index 3f35583b5..69db5926f 100644 --- a/src/options.c +++ b/src/options.c @@ -2207,12 +2207,13 @@ boolean tinitial, tfrom_file; bad_negation(fullname, TRUE); return; } + #ifdef CHANGE_COLOR if (match_optname(opts, "palette", 3, TRUE) #ifdef MAC || match_optname(opts, "hicolor", 3, TRUE) #endif - ) { + ) { int color_number, color_incr; #ifndef WIN32 @@ -2227,17 +2228,16 @@ boolean tinitial, tfrom_file; } color_number = CLR_MAX + 4; /* HARDCODED inverse number */ color_incr = -1; - } else { + } else #endif + { if (negated) { bad_negation("palette", FALSE); return; } color_number = 0; color_incr = 1; -#ifdef MAC } -#endif #ifdef WIN32 op = string_for_opt(opts, TRUE); if (!alternative_palette(op)) @@ -2264,8 +2264,8 @@ boolean tinitial, tfrom_file; #else rgb <<= 8; #endif - tmp = *(pt++); - if (isalpha(tmp)) { + tmp = *pt++; + if (isalpha((uchar) tmp)) { tmp = (tmp + 9) & 0xf; /* Assumes ASCII... */ } else { tmp &= 0xf; /* Digits in ASCII too... */ @@ -2277,9 +2277,8 @@ boolean tinitial, tfrom_file; rgb += tmp; } } - if (*pt == '/') { + if (*pt == '/') pt++; - } change_color(color_number, rgb, reverse); color_number += color_incr; } diff --git a/win/X11/winmenu.c b/win/X11/winmenu.c index 97f8592a4..75c99b57a 100644 --- a/win/X11/winmenu.c +++ b/win/X11/winmenu.c @@ -38,7 +38,6 @@ #include "hack.h" #include "winX.h" -#include static void FDECL(menu_select, (Widget, XtPointer, XtPointer)); static void FDECL(invert_line, (struct xwindow *, x11_menu_item *, int, long)); @@ -251,7 +250,7 @@ Cardinal *num_params; select_none(wp); } else if (ch == '\n' || ch == '\r') { ; /* accept */ - } else if (isdigit(ch)) { + } else if (digit(ch)) { /* special case: '0' is also the default ball class */ if (ch == '0' && !menu_info->counting && index(menu_info->curr_menu.gacc, ch)) From ea9a95d463b3e48c1edca4213788ac1c3fb8a145 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 28 May 2016 18:39:11 -0700 Subject: [PATCH 11/81] travel comment formatting --- src/hack.c | 57 +++++++++++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/src/hack.c b/src/hack.c index 305f8399c..5c87a2c5e 100644 --- a/src/hack.c +++ b/src/hack.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 hack.c $NHDT-Date: 1462663937 2016/05/07 23:32:17 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.165 $ */ +/* NetHack 3.6 hack.c $NHDT-Date: 1464485934 2016/05/29 01:38:54 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.168 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -935,36 +935,38 @@ boolean guess; * When guessing and trying to travel as close as possible * to an unreachable target space, don't include spaces * that would never be picked as a guessed target in the - * travel matrix describing player-reachable spaces. - * This stops travel from getting confused and moving the - * player back and forth in certain degenerate configurations - * of sight-blocking obstacles, e.g. + * travel matrix describing hero-reachable spaces. + * This stops travel from getting confused and moving + * the hero back and forth in certain degenerate + * configurations of sight-blocking obstacles, e.g. * - * T 1. Dig this out and carry enough to not be - * #### able to squeeze through diagonal gaps. - * #--.--- Stand at @ and target travel at space T. - * @..... - * |..... + * T 1. Dig this out and carry enough to not be + * #### able to squeeze through diagonal gaps. + * #--.--- Stand at @ and target travel at space T. + * @..... + * |..... * - * T 2. couldsee() marks spaces marked a and x as - * #### eligible guess spaces to move the player - * a--.--- towards. Space a is closest to T, so it gets - * @xxxxx chosen. Travel system moves @ right to travel - * |xxxxx to space a. + * T 2. couldsee() marks spaces marked a and x + * #### as eligible guess spaces to move the hero + * a--.--- towards. Space a is closest to T, so it + * @xxxxx gets chosen. Travel system moves @ right + * |xxxxx to travel to space a. * - * T 3. couldsee() marks spaces marked b, c and x - * #### as eligible guess spaces to move the player - * a--c--- towards. Since findtravelpath() is called - * b@xxxx repeatedly during travel, it doesn't remember - * |xxxxx that it wanted to go to space a, so in - * comparing spaces b and c, b is chosen, since - * it seems like the closest eligible space to T. - * Travel system moves @ left to go to space b. + * T 3. couldsee() marks spaces marked b, c and x + * #### as eligible guess spaces to move the hero + * a--c--- towards. Since findtravelpath() is called + * b@xxxx repeatedly during travel, it doesn't + * |xxxxx remember that it wanted to go to space a, + * so in comparing spaces b and c, b is + * chosen, since it seems like the closest + * eligible space to T. Travel system moves @ + * left to go to space b. * - * 4. Go to 2. + * 4. Go to 2. * - * By limiting the travel matrix here, space a in the example - * above is never included in it, preventing the cycle. + * By limiting the travel matrix here, space a in the + * example above is never included in it, preventing + * the cycle. */ if (!isok(nx, ny) || (guess && !couldsee(nx, ny))) continue; @@ -995,8 +997,7 @@ boolean guess; nomul(0); /* reset run so domove run checks work */ context.run = 8; - iflags.travelcc.x = iflags.travelcc.y = - -1; + iflags.travelcc.x = iflags.travelcc.y = -1; } return TRUE; } From 0c70b1bd0613a301223cc09fa4b6d21f7d30c2d6 Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 28 May 2016 19:54:19 -0700 Subject: [PATCH 12/81] reset travel cache when changing levels When travel fails to reach its destination, it remembers the target spot to use as default next time. But that spot is only meaningful on the current level. Discard last travel destination when moving to a different level. Also, discard unlocking context when changing level unless the context is for a container being brought along (after having been picked up since you can't unlock a carried box). Previously, a door pointer on the new level could happen to match the last one being unlocked on the old level. Discard trap setting context when changing level even if the trap object is brought along. Somehow the code for applying a touchstone got inserted in between two sections of code for applying a trap (ages ago; probably since touchstone was first introduced however many versions back), so clean that up. --- doc/fixes36.1 | 1 + include/extern.h | 1 + src/apply.c | 28 ++++++++++++++-------------- src/do.c | 13 ++++++++++--- src/lock.c | 11 ++++++++++- 5 files changed, 36 insertions(+), 18 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 9b40d87f6..cdfdb46e2 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -53,6 +53,7 @@ dipping fruit juice into enlightenment gave different result than the inverse make travel walk up to a trap and stop when the trap blocks the only way forward, instead of trying to go straight line travel will displace pets rather than stop +discard travel cache when moving to a different dungeon level do not autopickup unpaid items in shops death due an unseen gas spore's explosion resulted in "killed by a died" allow optional parameter "true", "yes", "false", or "no" for boolean options diff --git a/include/extern.h b/include/extern.h index 3d9c6c5f4..1a20b24f2 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1013,6 +1013,7 @@ E boolean FDECL(picking_lock, (int *, int *)); E boolean FDECL(picking_at, (int, int)); E void FDECL(breakchestlock, (struct obj *, BOOLEAN_P)); E void NDECL(reset_pick); +E void NDECL(maybe_reset_pick); E int FDECL(pick_lock, (struct obj *)); E int NDECL(doforce); E boolean FDECL(boxlock, (struct obj *, struct obj *)); diff --git a/src/apply.c b/src/apply.c index 24db211d3..544619b94 100644 --- a/src/apply.c +++ b/src/apply.c @@ -2179,20 +2179,6 @@ struct obj *obj; update_inventory(); } -static struct trapinfo { - struct obj *tobj; - xchar tx, ty; - int time_needed; - boolean force_bungle; -} trapinfo; - -void -reset_trapset() -{ - trapinfo.tobj = 0; - trapinfo.force_bungle = 0; -} - /* touchstones - by Ken Arnold */ STATIC_OVL void use_stone(tstone) @@ -2322,6 +2308,20 @@ struct obj *tstone; return; } +static struct trapinfo { + struct obj *tobj; + xchar tx, ty; + int time_needed; + boolean force_bungle; +} trapinfo; + +void +reset_trapset() +{ + trapinfo.tobj = 0; + trapinfo.force_bungle = 0; +} + /* Place a landmine/bear trap. Helge Hafting */ STATIC_OVL void use_trap(otmp) diff --git a/src/do.c b/src/do.c index 5efa0da87..2cbfa3660 100644 --- a/src/do.c +++ b/src/do.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 do.c $NHDT-Date: 1454033599 2016/01/29 02:13:19 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.153 $ */ +/* NetHack 3.6 do.c $NHDT-Date: 1464487100 2016/05/29 01:58:20 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.156 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /* NetHack may be freely redistributed. See license for details. */ @@ -1523,13 +1523,20 @@ boolean at_stairs, falling, portal; save_currentstate(); #endif - if ((annotation = get_annotation(&u.uz))) + if ((annotation = get_annotation(&u.uz)) != 0) You("remember this level as %s.", annotation); /* assume this will always return TRUE when changing level */ (void) in_out_region(u.ux, u.uy); (void) pickup(1); - context.polearm.hitmon = NULL; + + /* discard context which applied to previous level */ + maybe_reset_pick(); /* for door or for box not accompanying hero */ + reset_trapset(); /* even if to-be-armed trap obj is accompanying hero */ + iflags.travelcc.x = iflags.travelcc.y = -1; /* travel destination cache */ + context.polearm.hitmon = (struct monst *) 0; /* polearm target */ + /* digging context is level-aware and can actually be resumed if + hero returns to the previous level without any intervening dig */ } STATIC_OVL void diff --git a/src/lock.c b/src/lock.c index 16f1c63b1..f899f9a0b 100644 --- a/src/lock.c +++ b/src/lock.c @@ -75,7 +75,8 @@ STATIC_PTR int picklock(VOID_ARGS) { if (xlock.box) { - if ((xlock.box->ox != u.ux) || (xlock.box->oy != u.uy)) { + if (xlock.box->where != OBJ_FLOOR + || xlock.box->ox != u.ux || xlock.box->oy != u.uy) { return ((xlock.usedtime = 0)); /* you or it moved */ } } else { /* door */ @@ -228,6 +229,14 @@ reset_pick() xlock.box = 0; } +/* level change; don't reset if hero is carrying xlock.box with him/her */ +void +maybe_reset_pick() +{ + if (!xlock.box || !carried(xlock.box)) + reset_pick(); +} + /* for doapply(); if player gives a direction or resumes an interrupted previous attempt then it costs hero a move even if nothing ultimately happens; when told "can't do that" before being asked for direction From d45d475468097f8d4fefe58d5d6d2357630f26f4 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 29 May 2016 09:35:51 +0300 Subject: [PATCH 13/81] Gremlin wailing in agony should wake up nearby monsters Also allow wizmode ^G create sleeping monsters --- src/read.c | 11 +++++++++-- src/uhitm.c | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/read.c b/src/read.c index 87b30ef1d..af8399037 100644 --- a/src/read.c +++ b/src/read.c @@ -2371,7 +2371,8 @@ create_particular() struct permonst *whichpm = NULL; struct monst *mtmp; boolean madeany = FALSE, randmonst = FALSE, - maketame, makepeaceful, makehostile, saddled, invisible; + maketame, makepeaceful, makehostile, saddled, invisible, + sleeping; int fem; tryct = 5; @@ -2379,7 +2380,7 @@ create_particular() monclass = MAXMCLASSES; which = urole.malenum; /* an arbitrary index into mons[] */ maketame = makepeaceful = makehostile = FALSE; - saddled = invisible = FALSE; + sleeping = saddled = invisible = FALSE; fem = -1; /* gender not specified */ getlin("Create what kind of monster? [type the name or symbol]", buf); bufp = mungspaces(buf); @@ -2389,6 +2390,10 @@ create_particular() saddled = TRUE; (void) memset(tmpp, ' ', sizeof "saddled " - 1); } + if ((tmpp = strstri(bufp, "sleeping ")) != 0) { + sleeping = TRUE; + (void) memset(tmpp, ' ', sizeof "sleeping " - 1); + } if ((tmpp = strstri(bufp, "invisible ")) != 0) { invisible = TRUE; (void) memset(tmpp, ' ', sizeof "invisible " - 1); @@ -2478,6 +2483,8 @@ create_particular() } if (invisible) mon_set_minvis(mtmp); + if (sleeping) + mtmp->msleeping = 1; madeany = TRUE; /* in case we got a doppelganger instead of what was asked for, make it start out looking like what was asked for */ diff --git a/src/uhitm.c b/src/uhitm.c index 0ca149785..b45b40453 100644 --- a/src/uhitm.c +++ b/src/uhitm.c @@ -2695,6 +2695,7 @@ int dmg; pline("%s %s!", Monnam(mon), (dmg > mon->mhp / 2) ? "wails in agony" : "cries out in pain"); mon->mhp -= dmg; + wake_nearto(mon->mx, mon->my, 30); if (mon->mhp <= 0) { if (context.mon_moving) monkilled(mon, (char *) 0, AD_BLND); From b4cefa724d3aca89e155439793ba929b5bb40a04 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 29 May 2016 16:00:09 +0300 Subject: [PATCH 14/81] Add fixes entry for the "autoexplore" --- doc/fixes36.1 | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index cdfdb46e2..6678b29d1 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -386,6 +386,7 @@ when #terrain is displaying a censored version of the map (no monsters, &c), moving the cursor will display farlook's brief autodescribe feedback interrupt a multi turn action if hp or pw is restored to maximum pressing d or D when cursor positioning targets doors and doorways +pressing x or X when cursor positioning targets possibly unexplored location Platform- and/or Interface-Specific New Features From 52efd541c03e0e2529c606eaa167cb43aaa80a04 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sun, 29 May 2016 22:20:52 +0300 Subject: [PATCH 15/81] Update fixes file --- doc/fixes36.1 | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 6678b29d1..313454491 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -270,9 +270,11 @@ while in shop, stone-to-flesh at self causing carried, hero owned figurine or statue to animate claimed it belonged to shopkeeper reviving one of a stack of N corpses in a shop charged a usage fee for all N; remaining N-1 were owned by hero if carried but by shop if on floor +gremlin wailing in agony should wake up nearby monsters +add more lighting variance to the second bigroom variant -Fixes to Post-3.6.0 Problems that Were Exposed Via git Respository +Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository ------------------------------------------------------------------ fix "object lost" panic during pickup caused by sortloot revamp more sortloot revisions @@ -367,7 +369,8 @@ feedback from probing of long worm now includes number of segments it has monk starts with 'shuriken' pre-discovered (despite language issue...) item-using monster on or next to a fire trap can use it to be cured of turning into slime -wizard mode ^G can now specify "male" or "female" when creating a monster +wizard mode ^G can now specify "sleeping", "male" or "female" when + creating a monster REPRODUCIBLE_BUILD is new config.h setting to fetch build date+time from environment instead of using current date+time, so that later rebuild could duplicate the original (disabled by default; tested for Unix) From 5addbf88ddd3aa7f805f9f888c37fd868c907827 Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 29 May 2016 17:24:47 -0700 Subject: [PATCH 16/81] knox level tweaks The level teleport arrival regions (which also apply when invoking the W quest artifact's temporary magic portal) on the Ft.Ludios level had the wrong bounding coordinates. While there, I've added a corridor between the welcome/zoo room and the empty room at the top. The doors are secret so monsters won't be able to use it until they've been discovered by the player (which happens with magic mapping as well as with searching). I put a giant in the formerly empty room, and made that room's original door secret too so that the giant won't smash it down to get out, then smash down the door between the zoo room and the main area around the fort. --- dat/knox.des | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/dat/knox.des b/dat/knox.des index 48cef7414..65e6c5606 100644 --- a/dat/knox.des +++ b/dat/knox.des @@ -12,13 +12,13 @@ MAP ---------------------------------------------------------------------------- | |........|...............................................................| | |........|.................................................------------..| -| -------+--.................................................|..........|..| -| |........}}}}}}}....................}}}}}}}..........|..........|..| -| |........}-----}....................}-----}..........--+--+--...|..| -| ---........}|...|}}}}}}}}}}}}}}}}}}}}}}|...|}.................|...|..| -| |..........}---S------------------------S---}.................|...|..| -| |..........}}}|...............|..........|}}}.................+...|..| -| -------..........}|...............S..........|}...................|...|..| +| --S----S--.................................................|..........|..| +| # |........}}}}}}}....................}}}}}}}..........|..........|..| +| # |........}-----}....................}-----}..........--+--+--...|..| +| # ---........}|...|}}}}}}}}}}}}}}}}}}}}}}|...|}.................|...|..| +| # |..........}---S------------------------S---}.................|...|..| +| # |..........}}}|...............|..........|}}}.................+...|..| +| --S----..........}|...............S..........|}...................|...|..| | |.....|..........}|...............|......\...S}...................|...|..| | |.....+........}}}|...............|..........|}}}.................+...|..| | |.....|........}---S------------------------S---}.................|...|..| @@ -35,8 +35,8 @@ NON_DIGGABLE:(00,00,75,19) # Portal arrival point BRANCH:(08,16,08,16),(0,0,0,0) # accessible via ^V in wizard mode; arrive near the portal -TELEPORT_REGION:(06,16,09,17),(0,0,0,0),up -TELEPORT_REGION:(06,16,09,17),(0,0,0,0),down +TELEPORT_REGION:(06,15,09,16),(0,0,0,0),up +TELEPORT_REGION:(06,15,09,16),(0,0,0,0),down # Throne room, with Croesus on the throne REGION:(37,08,46,11),lit,"throne" # 50% chance each to move throne and/or fort's entry secret door up one row @@ -82,6 +82,8 @@ DOOR:locked,(08,11) DOOR:open,(68,11) DOOR:closed,(63,14) DOOR:closed,(66,14) +DOOR:closed,(04,03) +DOOR:closed,(04,09) # Soldiers guarding the fort MONSTER:('@',"soldier"),(12,14) MONSTER:('@',"soldier"),(12,13) @@ -100,6 +102,8 @@ MONSTER:('@',"soldier"),(54,13) MONSTER:('@',"soldier"),(57,10) MONSTER:('@',"soldier"),(57,09) MONSTER:('@',"lieutenant"),(15,08) +# Possible source of a boulder +MONSTER:('H',"stone giant"),(03,01) # Four dragons guarding each side MONSTER:'D',(18,09) MONSTER:'D',(49,10) From 7404597ac547ba42b48916745ec6c8330c4ca33a Mon Sep 17 00:00:00 2001 From: PatR Date: Mon, 30 May 2016 17:56:47 -0700 Subject: [PATCH 17/81] getpos moving to 'cmap' characters While testing something I noticed that moving the cursor to visible '^' by typing '^' while getpos was asking me to pick a location, it didn't always cycle through all visible traps. The most straightforward culprit was after trap detection (via confused gold detection, not ^F) had found a trap door or level teleporter in a closet that itself was a secret corridor spot. But it turned out to be any location that hadn't been seen yet. This is a substantial overhaul of the relevant code and so far works for all the cases I've tried, but there are bound to be cases I haven't tried yet and those may or may not work correctly. There's also a bunch of formatting cleanup, and some simplification of the m/M/o/O/d/D/x/X handling. --- doc/fixes36.1 | 2 + src/do_name.c | 172 ++++++++++++++++++++++++++++---------------------- 2 files changed, 99 insertions(+), 75 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 313454491..fbb8b5a9b 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -272,6 +272,8 @@ reviving one of a stack of N corpses in a shop charged a usage fee for all N; remaining N-1 were owned by hero if carried but by shop if on floor gremlin wailing in agony should wake up nearby monsters add more lighting variance to the second bigroom variant +when getpost was picking a location, typing '^' to move to the next known trap + skipped some detected traps if their location was unseen Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/do_name.c b/src/do_name.c index 153002e76..5d5c419c6 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -64,10 +64,15 @@ const char *goal; putstr(tmpwin, 0, "Use m or M to move the cursor to next monster."); if (!iflags.terrainmode || (iflags.terrainmode & TER_OBJ) != 0) putstr(tmpwin, 0, "Use o or O to move the cursor to next object."); - putstr(tmpwin, 0, /* d,D are useful regardless of terrainmode */ - "Use d or D to move the cursor to next door or doorway."); + if (!iflags.terrainmode || (iflags.terrainmode & TER_MAP) != 0) { + /* both of these are primarily useful when choosing a travel + destination for the '_' command */ + putstr(tmpwin, 0, + "Use d or D to move the cursor to next door or doorway."); + putstr(tmpwin, 0, + "Use x or X to move the cursor to unexplored location."); + } if (!iflags.terrainmode) { - putstr(tmpwin, 0, "Use x or X to move the cursor to unexplored location."); if (getpos_hilitefunc) putstr(tmpwin, 0, "Use $ to display valid locations."); putstr(tmpwin, 0, "Use # to toggle automatic description."); @@ -134,10 +139,11 @@ enum gloctypes { }; -#define IS_UNEXPLORED_LOC(x,y) (isok((x), (y)) \ - && glyph_is_cmap(levl[(x)][(y)].glyph) \ - && glyph_to_cmap(levl[(x)][(y)].glyph) == S_stone \ - && !levl[(x)][(y)].seenv) +#define IS_UNEXPLORED_LOC(x,y) \ + (isok((x), (y)) \ + && glyph_is_cmap(levl[(x)][(y)].glyph) \ + && glyph_to_cmap(levl[(x)][(y)].glyph) == S_stone \ + && !levl[(x)][(y)].seenv) STATIC_OVL boolean gather_locs_interesting(x,y, gloc) @@ -150,28 +156,35 @@ int x,y, gloc; switch (gloc) { default: - case GLOC_MONS: return (glyph_is_monster(glyph) - && glyph != monnum_to_glyph(PM_LONG_WORM_TAIL)); - case GLOC_OBJS: return (glyph_is_object(glyph) - && glyph != objnum_to_glyph(BOULDER) - && glyph != objnum_to_glyph(ROCK)); - case GLOC_DOOR: return (glyph_is_cmap(glyph) - && (is_cmap_door(glyph_to_cmap(glyph)) - || is_cmap_drawbridge(glyph_to_cmap(glyph)) - || glyph_to_cmap(glyph) == S_ndoor)); - case GLOC_EXPLORE: return (glyph_is_cmap(glyph) - && (is_cmap_door(glyph_to_cmap(glyph)) - || is_cmap_drawbridge(glyph_to_cmap(glyph)) - || glyph_to_cmap(glyph) == S_ndoor - || glyph_to_cmap(glyph) == S_room - || glyph_to_cmap(glyph) == S_darkroom - || glyph_to_cmap(glyph) == S_corr - || glyph_to_cmap(glyph) == S_litcorr) - && (IS_UNEXPLORED_LOC(x+1,y) - || IS_UNEXPLORED_LOC(x-1,y) - || IS_UNEXPLORED_LOC(x,y+1) - || IS_UNEXPLORED_LOC(x,y-1))); + case GLOC_MONS: + /* unlike '/M', this skips monsters revealed by + warning glyphs and remembered unseen ones */ + return (glyph_is_monster(glyph) + && glyph != monnum_to_glyph(PM_LONG_WORM_TAIL)); + case GLOC_OBJS: + return (glyph_is_object(glyph) + && glyph != objnum_to_glyph(BOULDER) + && glyph != objnum_to_glyph(ROCK)); + case GLOC_DOOR: + return (glyph_is_cmap(glyph) + && (is_cmap_door(glyph_to_cmap(glyph)) + || is_cmap_drawbridge(glyph_to_cmap(glyph)) + || glyph_to_cmap(glyph) == S_ndoor)); + case GLOC_EXPLORE: + return (glyph_is_cmap(glyph) + && (is_cmap_door(glyph_to_cmap(glyph)) + || is_cmap_drawbridge(glyph_to_cmap(glyph)) + || glyph_to_cmap(glyph) == S_ndoor + || glyph_to_cmap(glyph) == S_room + || glyph_to_cmap(glyph) == S_darkroom + || glyph_to_cmap(glyph) == S_corr + || glyph_to_cmap(glyph) == S_litcorr) + && (IS_UNEXPLORED_LOC(x + 1, y) + || IS_UNEXPLORED_LOC(x - 1, y) + || IS_UNEXPLORED_LOC(x, y + 1) + || IS_UNEXPLORED_LOC(x, y - 1))); } + /*NOTREACHED*/ return FALSE; } @@ -199,10 +212,8 @@ int gloc; for (pass = 0; pass < 2; pass++) { for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) { - /* unlike '/M', this skips monsters revealed by - warning glyphs and remembered invisible ones */ if ((x == u.ux && y == u.uy) - || gather_locs_interesting(x,y, gloc)) { + || gather_locs_interesting(x, y, gloc)) { if (!pass) { ++*cnt_p; } else { @@ -317,13 +328,14 @@ coord *ccp; boolean force; const char *goal; { + static const char pick_chars[] = ".,;:", + mMoOdDxX[] = "mMoOdDxX"; + const char *cp; int result = 0; int cx, cy, i, c; int sidx, tx, ty; boolean msg_given = TRUE; /* clear message window by default */ boolean show_goal_msg = FALSE; - static const char pick_chars[] = ".,;:"; - const char *cp; boolean hilite_state = FALSE; coord *garr[NUM_GLOCS] = DUMMY; int gcount[NUM_GLOCS] = DUMMY; @@ -437,6 +449,10 @@ const char *goal; } goto nxtc; } else if (c == '#') { + /* unfortunately, using '#' as a command means we can't move + cursor to sinks, iron bars, and poison clouds; perhaps + when autodescribe is already on, next '#' should try to + move to '#' rather than to toggle off? (or ask; ick...) */ iflags.autodescribe = !iflags.autodescribe; pline("Automatic description %sis %s.", flags.verbose ? "of features under cursor " : "", @@ -446,29 +462,25 @@ const char *goal; msg_given = TRUE; goto nxtc; } else if (c == '@') { /* return to hero's spot */ - /* reset 'm','M' and 'o','O'; otherwise, there's no way for player + /* reset 'm&M', 'o&O', &c; otherwise, there's no way for player to achieve that except by manually cycling through all spots */ for (i = 0; i < NUM_GLOCS; i++) gidx[i] = 0; cx = u.ux; cy = u.uy; goto nxtc; - } else if (c == 'm' || c == 'M' /* nearest or farthest monster */ - || c == 'o' || c == 'O' /* nearest or farthest object */ - || c == 'x' || c == 'X' /* unexplored area */ - || c == 'd' || c == 'D') { /* door/doorway */ - int gloc = (c == 'o' || c == 'O') ? GLOC_OBJS - : (c == 'd' || c == 'D') ? GLOC_DOOR - : (c == 'x' || c == 'X') ? GLOC_EXPLORE - : GLOC_MONS; + } else if ((cp = index(mMoOdDxX, c)) != 0) { /* 'm|M', 'o|O', &c */ + /* nearest or farthest monster or object or door or unexplored */ + int gtmp = (int) (cp - mMoOdDxX), /* 0..7 */ + gloc = gtmp >> 1; /* 0..3 */ if (!garr[gloc]) { gather_locs(&garr[gloc], &gcount[gloc], gloc); gidx[gloc] = 0; /* garr[][0] is hero's spot */ } - if (c == 'm' || c == 'o' || c == 'd' || c == 'x') { + if (!(gtmp & 1)) { /* c=='m' || c=='o' || c=='d' || c=='x') */ gidx[gloc] = (gidx[gloc] + 1) % gcount[gloc]; - } else { + } else { /* c=='M' || c=='O' || c=='D' || c=='X') */ if (--gidx[gloc] < 0) gidx[gloc] = gcount[gloc] - 1; } @@ -481,9 +493,17 @@ const char *goal; int pass, lo_x, lo_y, hi_x, hi_y, k = 0; (void) memset((genericptr_t) matching, 0, sizeof matching); - for (sidx = 1; sidx < MAXPCHARS; sidx++) + for (sidx = 1; sidx < MAXPCHARS; sidx++) { /* [0] left as 0 */ + if (IS_DOOR(sidx) || IS_WALL(sidx) + || sidx == SDOOR || sidx == SCORR + || glyph_to_cmap(k) == S_room + || glyph_to_cmap(k) == S_darkroom + || glyph_to_cmap(k) == S_corr + || glyph_to_cmap(k) == S_litcorr) + continue; if (c == defsyms[sidx].sym || c == (int) showsyms[sidx]) matching[sidx] = (char) ++k; + } if (k) { for (pass = 0; pass <= 1; pass++) { /* pass 0: just past current pos to lower right; @@ -494,35 +514,39 @@ const char *goal; lo_x = (pass == 0 && ty == lo_y) ? cx + 1 : 1; hi_x = (pass == 1 && ty == hi_y) ? cx : COLNO - 1; for (tx = lo_x; tx <= hi_x; tx++) { - /* look at dungeon feature, not at - user-visible glyph */ - k = back_to_glyph(tx, ty); - /* uninteresting background glyph */ + /* first, look at what is currently visible + (might be monster) */ + k = glyph_at(tx, ty); if (glyph_is_cmap(k) - && (IS_DOOR(levl[tx][ty].typ) - || glyph_to_cmap(k) == S_room - || glyph_to_cmap(k) == S_darkroom - || glyph_to_cmap(k) == S_corr - || glyph_to_cmap(k) == S_litcorr)) { - /* what hero remembers to be at tx,ty */ - k = glyph_at(tx, ty); + && matching[glyph_to_cmap(k)]) + goto foundc; + /* next, try glyph that's remembered here + (might be trap or object) */ + if (level.flags.hero_memory + /* !terrainmode: don't move to remembered + trap or object if not currently shown */ + && !iflags.terrainmode) { + k = levl[tx][ty].glyph; + if (glyph_is_cmap(k) + && matching[glyph_to_cmap(k)]) + goto foundc; } - if (glyph_is_cmap(k) - && matching[glyph_to_cmap(k)] - && (levl[tx][ty].seenv - || iflags.terrainmode) - && !IS_WALL(levl[tx][ty].typ) - && levl[tx][ty].typ != SDOOR - && glyph_to_cmap(k) != S_room - && glyph_to_cmap(k) != S_corr - && glyph_to_cmap(k) != S_litcorr) { - cx = tx, cy = ty; - if (msg_given) { - clear_nhwindow(WIN_MESSAGE); - msg_given = FALSE; - } - goto nxtc; + /* last, try actual terrain here (shouldn't + we be using lastseentyp[][] instead?) */ + if (levl[tx][ty].seenv) { + k = back_to_glyph(tx, ty); + if (glyph_is_cmap(k) + && matching[glyph_to_cmap(k)]) + goto foundc; } + continue; + foundc: + cx = tx, cy = ty; + if (msg_given) { + clear_nhwindow(WIN_MESSAGE); + msg_given = FALSE; + } + goto nxtc; } /* column */ } /* row */ } /* pass */ @@ -545,10 +569,8 @@ const char *goal; } /* !quitchars */ if (force) goto nxtc; - if (!iflags.terrainmode) { - pline("Done."); - msg_given = FALSE; /* suppress clear */ - } + pline("Done."); + msg_given = FALSE; /* suppress clear */ cx = -1; cy = 0; result = 0; /* not -1 */ From 71113a697111604f044c91ff3c84075251190be7 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 31 May 2016 00:08:17 -0700 Subject: [PATCH 18/81] detecting chest and door traps When confused gold detection finds a door trap or a chest trap, it puts a bear trap glyph/tile on the map at that location. (They disappear once they're within sight.) Those should be given their own glyphs so that they can have their own tiles, but this doesn't do that. What it does do is describe such fake bear traps as "trapped door" or "trapped chest" when examined with far-look. The '^' command--if used while blind so that '^' hasn't disappeared yet--needs to catch up: it says "I can't see a trap there" when the adjacent '^' is a fake bear trap. --- doc/fixes36.1 | 4 +- include/extern.h | 2 + src/detect.c | 103 ++++++++++++++++++++++++++++++++++------------- src/pager.c | 12 +++++- 4 files changed, 90 insertions(+), 31 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index fbb8b5a9b..a7549db82 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -272,8 +272,10 @@ reviving one of a stack of N corpses in a shop charged a usage fee for all N; remaining N-1 were owned by hero if carried but by shop if on floor gremlin wailing in agony should wake up nearby monsters add more lighting variance to the second bigroom variant -when getpost was picking a location, typing '^' to move to the next known trap +when getpos was picking a location, typing '^' to move to the next known trap skipped some detected traps if their location was unseen +describe detected door traps and chest traps as trapped door and trapped chest + instead of bear trap; bear trap tile is still used on map though Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/include/extern.h b/include/extern.h index 1a20b24f2..4c5cb8e64 100644 --- a/include/extern.h +++ b/include/extern.h @@ -236,6 +236,8 @@ E void NDECL(decl_init); /* ### detect.c ### */ +E boolean FDECL(trapped_chest_at, (int, int, int)); +E boolean FDECL(trapped_door_at, (int, int, int)); E struct obj *FDECL(o_in, (struct obj *, CHAR_P)); E struct obj *FDECL(o_material, (struct obj *, unsigned)); E int FDECL(gold_detect, (struct obj *)); diff --git a/src/detect.c b/src/detect.c index 3726db68a..07b93227f 100644 --- a/src/detect.c +++ b/src/detect.c @@ -22,8 +22,44 @@ STATIC_PTR void FDECL(findone, (int, int, genericptr_t)); STATIC_PTR void FDECL(openone, (int, int, genericptr_t)); STATIC_DCL int FDECL(mfind0, (struct monst *, BOOLEAN_P)); -/* Recursively search obj for an object in class oclass and return 1st found - */ +/* this is checking whether a trap symbol represents a trapped chest, + not whether a trapped chest is actually present */ +boolean +trapped_chest_at(ttyp, x, y) +int ttyp; +int x, y; +{ + if (!glyph_is_trap(glyph_at(x, y))) + return FALSE; + if (ttyp != BEAR_TRAP || (Hallucination && rn2(20))) + return FALSE; + /* presence of any trappable container will do */ + return (sobj_at(CHEST, x, y) || sobj_at(LARGE_BOX, x, y)) ? TRUE : FALSE; +} + +/* this is checking whether a trap symbol represents a trapped door, + not whether the door here is actually trapped */ +boolean +trapped_door_at(ttyp, x, y) +int ttyp; +int x, y; +{ + struct rm *lev; + + if (!glyph_is_trap(glyph_at(x, y))) + return FALSE; + if (ttyp != BEAR_TRAP || (Hallucination && rn2(20))) + return FALSE; + lev = &levl[x][y]; + if (!IS_DOOR(lev->typ)) + return FALSE; + if ((lev->doormask & (D_NODOOR | D_BROKEN | D_ISOPEN)) != 0 + && trapped_chest_at(ttyp, x, y)) + return FALSE; + return TRUE; +} + +/* recursively search obj for an object in class oclass, return 1st found */ struct obj * o_in(obj, oclass) struct obj *obj; @@ -39,7 +75,7 @@ char oclass; for (otmp = obj->cobj; otmp; otmp = otmp->nobj) if (otmp->oclass == oclass) return otmp; - else if (Has_contents(otmp) && (temp = o_in(otmp, oclass))) + else if (Has_contents(otmp) && (temp = o_in(otmp, oclass)) != 0) return temp; } return (struct obj *) 0; @@ -64,7 +100,7 @@ unsigned material; if (objects[otmp->otyp].oc_material == material) return otmp; else if (Has_contents(otmp) - && (temp = o_material(otmp, material))) + && (temp = o_material(otmp, material)) != 0) return temp; } return (struct obj *) 0; @@ -179,7 +215,7 @@ register struct obj *sobj; if (findgold(mtmp->minvent) || monsndx(mtmp->data) == PM_GOLD_GOLEM) { known = TRUE; goto outgoldmap; /* skip further searching */ - } else + } else { for (obj = mtmp->minvent; obj; obj = obj->nobj) if (sobj->blessed && o_material(obj, GOLD)) { known = TRUE; @@ -188,6 +224,7 @@ register struct obj *sobj; known = TRUE; goto outgoldmap; /* skip further searching */ } + } } /* look for gold objects */ @@ -208,6 +245,7 @@ register struct obj *sobj; adjust message if you have gold in your inventory */ if (sobj) { char buf[BUFSZ]; + if (youmonst.data == &mons[PM_GOLD_GOLEM]) { Sprintf(buf, "You feel like a million %s!", currency(2L)); } else if (hidden_gold() || money_cnt(invent)) @@ -232,13 +270,13 @@ outgoldmap: u.uinwater = u.uburied = 0; /* Discover gold locations. */ for (obj = fobj; obj; obj = obj->nobj) { - if (sobj->blessed && (temp = o_material(obj, GOLD))) { + if (sobj->blessed && (temp = o_material(obj, GOLD)) != 0) { if (temp != obj) { temp->ox = obj->ox; temp->oy = obj->oy; } map_object(temp, 1); - } else if ((temp = o_in(obj, COIN_CLASS))) { + } else if ((temp = o_in(obj, COIN_CLASS)) != 0) { if (temp != obj) { temp->ox = obj->ox; temp->oy = obj->oy; @@ -251,24 +289,27 @@ outgoldmap: continue; /* probably overkill here */ if (findgold(mtmp->minvent) || monsndx(mtmp->data) == PM_GOLD_GOLEM) { struct obj gold; + gold = zeroobj; /* ensure oextra is cleared too */ gold.otyp = GOLD_PIECE; + gold.quan = (long) rnd(10); /* usually more than 1 */ gold.ox = mtmp->mx; gold.oy = mtmp->my; map_object(&gold, 1); - } else + } else { for (obj = mtmp->minvent; obj; obj = obj->nobj) - if (sobj->blessed && (temp = o_material(obj, GOLD))) { + if (sobj->blessed && (temp = o_material(obj, GOLD)) != 0) { temp->ox = mtmp->mx; temp->oy = mtmp->my; map_object(temp, 1); break; - } else if ((temp = o_in(obj, COIN_CLASS))) { + } else if ((temp = o_in(obj, COIN_CLASS)) != 0) { temp->ox = mtmp->mx; temp->oy = mtmp->my; map_object(temp, 1); break; } + } } newsym(u.ux, u.uy); u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; @@ -505,7 +546,7 @@ int class; /* an object class, 0 for all */ * Map all buried objects first. */ for (obj = level.buriedobjlist; obj; obj = obj->nobj) - if (!class || (otmp = o_in(obj, class))) { + if (!class || (otmp = o_in(obj, class)) != 0) { if (class) { if (otmp != obj) { otmp->ox = obj->ox; @@ -526,8 +567,8 @@ int class; /* an object class, 0 for all */ for (x = 1; x < COLNO; x++) for (y = 0; y < ROWNO; y++) for (obj = level.objects[x][y]; obj; obj = obj->nexthere) - if ((!class && !boulder) || (otmp = o_in(obj, class)) - || (otmp = o_in(obj, boulder))) { + if ((!class && !boulder) || (otmp = o_in(obj, class)) != 0 + || (otmp = o_in(obj, boulder)) != 0) { if (class || boulder) { if (otmp != obj) { otmp->ox = obj->ox; @@ -544,8 +585,8 @@ int class; /* an object class, 0 for all */ if (DEADMONSTER(mtmp)) continue; for (obj = mtmp->minvent; obj; obj = obj->nobj) - if ((!class && !boulder) || (otmp = o_in(obj, class)) - || (otmp = o_in(obj, boulder))) { + if ((!class && !boulder) || (otmp = o_in(obj, class)) != 0 + || (otmp = o_in(obj, boulder)) != 0) { if (!class && !boulder) otmp = obj; otmp->ox = mtmp->mx; /* at monster location */ @@ -558,8 +599,9 @@ int class; /* an object class, 0 for all */ && (!class || class == objects[mtmp->mappearance].oc_class)) { struct obj temp; - temp.oextra = (struct oextra *) 0; + temp = zeroobj; temp.otyp = mtmp->mappearance; /* needed for obj_to_glyph() */ + temp.quan = 1L; temp.ox = mtmp->mx; temp.oy = mtmp->my; temp.corpsenm = PM_TENGU; /* if mimicing a corpse */ @@ -567,8 +609,10 @@ int class; /* an object class, 0 for all */ } else if (findgold(mtmp->minvent) && (!class || class == COIN_CLASS)) { struct obj gold; + gold = zeroobj; /* ensure oextra is cleared too */ gold.otyp = GOLD_PIECE; + gold.quan = (long) rnd(10); /* usually more than 1 */ gold.ox = mtmp->mx; gold.oy = mtmp->my; map_object(&gold, 1); @@ -585,7 +629,6 @@ int class; /* an object class, 0 for all */ * and the detected object covers a known pool? */ docrt(); /* this will correctly reset vision */ - if (Underwater) under_water(2); if (u.uburied) @@ -675,7 +718,7 @@ int src_cursed; if (Hallucination || src_cursed) { struct obj obj; /* fake object */ - obj.oextra = (struct oextra *) 0; + obj = zeroobj; if (trap) { obj.ox = trap->tx; obj.oy = trap->ty; @@ -683,7 +726,9 @@ int src_cursed; obj.ox = x; obj.oy = y; } - obj.otyp = (src_cursed) ? GOLD_PIECE : random_object(); + obj.otyp = !Hallucination ? GOLD_PIECE : random_object(); + obj.quan = (long) ((obj.otyp == GOLD_PIECE) ? rnd(10) + : objects[obj.otyp].oc_merge ? rnd(2) : 1); obj.corpsenm = random_monster(); /* if otyp == CORPSE */ map_object(&obj, 1); } else if (trap) { @@ -691,6 +736,8 @@ int src_cursed; trap->tseen = 1; } else { struct trap temp_trap; /* fake trap */ + + (void) memset((genericptr_t) &temp_trap, 0, sizeof temp_trap); temp_trap.tx = x; temp_trap.ty = y; temp_trap.ttyp = BEAR_TRAP; /* some kind of trap */ @@ -735,8 +782,7 @@ int how; /* 1 for misleading map feedback */ */ int trap_detect(sobj) -register struct obj *sobj; -/* sobj is null if crystal ball, *scroll if gold detection scroll */ +struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ { register struct trap *ttmp; struct monst *mon; @@ -759,8 +805,7 @@ register struct obj *sobj; else found = TRUE; } - if ((tr = detect_obj_traps(level.buriedobjlist, FALSE, 0)) - != OTRAP_NONE) { + if ((tr = detect_obj_traps(level.buriedobjlist, FALSE, 0)) != OTRAP_NONE) { if (tr & OTRAP_THERE) goto outtrapmap; else @@ -798,6 +843,7 @@ register struct obj *sobj; /* traps exist, but only under me - no separate display required */ Your("%s itch.", makeplural(body_part(TOE))); return 0; + outtrapmap: cls(); @@ -993,10 +1039,10 @@ struct obj **optr; nomul(-rnd(10)); multi_reason = "gazing into a crystal ball"; nomovemsg = ""; - if (obj->spe <= 0) + if (obj->spe <= 0) { pline_The("vision is unclear."); - else { - int class; + } else { + int class, i; int ret = 0; makeknown(CRYSTAL_BALL); @@ -1019,11 +1065,10 @@ struct obj **optr; case '^': ret = trap_detect((struct obj *) 0); break; - default: { - int i = rn2(SIZE(level_detects)); + default: + i = rn2(SIZE(level_detects)); You_see("%s, %s.", level_detects[i].what, level_distance(level_detects[i].where)); - } ret = 0; break; } diff --git a/src/pager.c b/src/pager.c index 2e4256eeb..0dfbbf5a2 100644 --- a/src/pager.c +++ b/src/pager.c @@ -379,7 +379,17 @@ char *buf, *monbuf; } else if (glyph_is_trap(glyph)) { int tnum = what_trap(glyph_to_trap(glyph)); - Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation); + /* Trap detection displays a bear trap at locations having + * a trapped door or trapped container or both. + * TODO: we should create actual trap types for doors and + * chests so that they can have their own glyphs and tiles. + */ + if (trapped_chest_at(tnum, x, y)) + Strcpy(buf, "trapped chest"); /* might actually be a large box */ + else if (trapped_door_at(tnum, x, y)) + Strcpy(buf, "trapped door"); /* not "trap door"... */ + else + Strcpy(buf, defsyms[trap_to_defsym(tnum)].explanation); } else if (glyph_is_warning(glyph)) { int warnindx = glyph_to_warning(glyph); From 30855febad4818ddc24034f3cfaff36abd4751a3 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 31 May 2016 04:44:21 -0700 Subject: [PATCH 19/81] fix #H4353 - monster gets angry after being killed ...by thrown potion. The reported case was a shopkeeper killed by system shock from thrown potion of polymorph, but any death (acid, burning oil explosion, water against iron golem, holy water against undead, demon, or werecritter) to any peaceful monster could cause similar result. --- doc/fixes36.1 | 2 ++ src/potion.c | 51 ++++++++++++++++++++++++++++++--------------------- 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index a7549db82..26967c078 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -276,6 +276,8 @@ when getpos was picking a location, typing '^' to move to the next known trap skipped some detected traps if their location was unseen describe detected door traps and chest traps as trapped door and trapped chest instead of bear trap; bear trap tile is still used on map though +thrown potion that killed peaceful monster would cause "the gets argry" + after the message about it being killed Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/potion.c b/src/potion.c index 6d3e45f96..d2bf6a021 100644 --- a/src/potion.c +++ b/src/potion.c @@ -1241,16 +1241,18 @@ boolean your_fault; { const char *botlnam = bottlename(); boolean isyou = (mon == &youmonst); - int distance; + int distance, tx, ty; struct obj *saddle = (struct obj *) 0; boolean hit_saddle = FALSE; if (isyou) { + tx = u.ux, ty = u.uy; distance = 0; pline_The("%s crashes on your %s and breaks into shards.", botlnam, body_part(HEAD)); losehp(Maybe_Half_Phys(rnd(2)), "thrown potion", KILLED_BY_AN); } else { + tx = mon->mx, ty = mon->my; /* sometimes it hits the saddle */ if (((mon->misc_worn_check & W_SADDLE) && (saddle = which_armor(mon, W_SADDLE))) @@ -1259,10 +1261,10 @@ boolean your_fault; && ((rnl(10) > 7 && obj->cursed) || (rnl(10) < 4 && obj->blessed) || !rn2(3))))) hit_saddle = TRUE; - distance = distu(mon->mx, mon->my); - if (!cansee(mon->mx, mon->my)) + distance = distu(tx, ty); + if (!cansee(tx, ty)) { pline("Crash!"); - else { + } else { char *mnam = mon_nam(mon); char buf[BUFSZ]; @@ -1285,7 +1287,7 @@ boolean your_fault; } /* oil doesn't instantly evaporate; Neither does a saddle hit */ - if (obj->otyp != POT_OIL && !hit_saddle && cansee(mon->mx, mon->my)) + if (obj->otyp != POT_OIL && !hit_saddle && cansee(tx, ty)) pline("%s.", Tobjnam(obj, "evaporate")); if (isyou) { @@ -1313,7 +1315,7 @@ boolean your_fault; } else if (hit_saddle && saddle) { char *mnam, buf[BUFSZ], saddle_glows[BUFSZ]; boolean affected = FALSE; - boolean useeit = !Blind && canseemon(mon) && cansee(mon->mx, mon->my); + boolean useeit = !Blind && canseemon(mon) && cansee(tx, ty); mnam = x_monnam(mon, ARTICLE_THE, (char *) 0, (SUPPRESS_IT | SUPPRESS_SADDLE), FALSE); @@ -1331,10 +1333,8 @@ boolean your_fault; if (useeit && !affected) pline("%s %s wet.", buf, aobjnam(saddle, "get")); } else { - boolean angermon = TRUE; + boolean angermon = your_fault; - if (!your_fault) - angermon = FALSE; switch (obj->otyp) { case POT_HEALING: case POT_EXTRA_HEALING: @@ -1419,7 +1419,7 @@ boolean your_fault; pline("%s %s in pain!", Monnam(mon), is_silent(mon->data) ? "writhes" : "shrieks"); if (!is_silent(mon->data)) - wake_nearto(mon->mx, mon->my, mon->data->mlevel * 10); + wake_nearto(tx, ty, mon->data->mlevel * 10); mon->mhp -= d(2, 6); /* should only be by you */ if (mon->mhp < 1) @@ -1451,14 +1451,14 @@ boolean your_fault; break; case POT_OIL: if (obj->lamplit) - explode_oil(obj, mon->mx, mon->my); + explode_oil(obj, tx, ty); break; case POT_ACID: if (!resists_acid(mon) && !resist(mon, POTION_CLASS, 0, NOTELL)) { pline("%s %s in pain!", Monnam(mon), is_silent(mon->data) ? "writhes" : "shrieks"); if (!is_silent(mon->data)) - wake_nearto(mon->mx, mon->my, mon->data->mlevel * 10); + wake_nearto(tx, ty, mon->data->mlevel * 10); mon->mhp -= d(obj->cursed ? 2 : 1, obj->blessed ? 4 : 8); if (mon->mhp < 1) { if (your_fault) @@ -1480,27 +1480,36 @@ boolean your_fault; break; */ } - if (angermon) - wakeup(mon); - else - mon->msleeping = 0; + /* target might have been killed */ + if (mon->mhp > 0) { + if (angermon) + wakeup(mon); + else + mon->msleeping = 0; + } } /* Note: potionbreathe() does its own docall() */ - if ((distance == 0 || ((distance < 3) && rn2(5))) + if ((distance == 0 || (distance < 3 && rn2(5))) && (!breathless(youmonst.data) || haseyes(youmonst.data))) potionbreathe(obj); else if (obj->dknown && !objects[obj->otyp].oc_name_known - && !objects[obj->otyp].oc_uname && cansee(mon->mx, mon->my)) + && !objects[obj->otyp].oc_uname && cansee(tx, ty)) docall(obj); + if (*u.ushops && obj->unpaid) { struct monst *shkp = shop_keeper(*in_rooms(u.ux, u.uy, SHOPBASE)); - if (shkp) + /* neither of the first two cases should be able to happen; + only the hero should ever have an unpaid item, and only + when inside a tended shop */ + if (!shkp) /* if shkp was killed, unpaid ought to cleared already */ + obj->unpaid = 0; + else if (context.mon_moving) /* obj thrown by monster */ + subfrombill(obj, shkp); + else /* obj thrown by hero */ (void) stolen_value(obj, u.ux, u.uy, (boolean) shkp->mpeaceful, FALSE); - else - obj->unpaid = 0; } obfree(obj, (struct obj *) 0); } From c68dfe8f52d66e9c0de326f64c1505a59483192a Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 31 May 2016 05:35:56 -0700 Subject: [PATCH 20/81] wishing fix rnd_otyp_by_namedesc() had an off by one error when choosing which matching item to return, making it impossible to successfully wish for the Amulet of Yendor, always yielding the plastic imitation. n == 2, maxprob == 2 prob = rn2(maxprob); => 0 or 1 i = 0; while (i < n - 1 && (prob -= ... + 1) > 0) i++; always exited the loop on the first test of the condition because subtracting 1 from 0 or 1 never yielded a result greater than 0. It's still suboptimal: "amulet of yendor" should find an exact match and should never return "cheap plastic imitation of amulet of yendor" from the partial match. I think biasing the choice among multiple candidates based on their generation probabilities only makes sense when all the candidates are within the same class. If scroll of light occurred in 5% of scrolls and spellbook of light occurred in 10% of spellbooks (both percentages pulled out of thin air), having "light" get a 2 out 3 chance to be a spellbook doesn't seem right because scrolls are four times more likely than spellbooks (in most of the dungeon; books aren't randomly generated at all on the rogue level or in Gehennom). --- src/objnam.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/objnam.c b/src/objnam.c index 618d1b5d7..fe6b25795 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -2488,7 +2488,8 @@ char oclass; long prob = rn2(maxprob); i = 0; - while (i < n - 1 && (prob -= (objects[validobjs[i]].oc_prob + 1)) > 0) + while (i < n - 1 + && (prob -= (objects[validobjs[i]].oc_prob + 1)) >= 0) i++; return validobjs[i]; } From d41e097ea85246719e816cb61ea9a86c4c996df1 Mon Sep 17 00:00:00 2001 From: PatR Date: Tue, 31 May 2016 06:37:14 -0700 Subject: [PATCH 21/81] more door/chest trap detection Make the '^' command catch up with far-look as far as identifying trapped doors and trapped chests revealed by confused gold detect. You need to be blinded when approaching the '^', otherwise as soon as you can see a door or chest or whatever else is there the fake bear trap will be removed from the map. --- src/pager.c | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/pager.c b/src/pager.c index 0dfbbf5a2..8ffe31409 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1227,12 +1227,24 @@ int doidtrap() { register struct trap *trap; - int x, y, tt; + int x, y, tt, glyph; if (!getdir("^")) return 0; x = u.ux + u.dx; y = u.uy + u.dy; + + /* check fake bear trap from confused gold detection */ + glyph = glyph_at(x, y); + if (glyph_is_trap(glyph) && (tt = glyph_to_trap(glyph)) == BEAR_TRAP) { + boolean chesttrap = trapped_chest_at(tt, x, y); + + if (chesttrap || trapped_door_at(tt, x, y)) { + pline("That is a trapped %s.", chesttrap ? "chest" : "door"); + return 0; /* trap ID'd, but no time elapses */ + } + } + for (trap = ftrap; trap; trap = trap->ntrap) if (trap->tx == x && trap->ty == y) { if (!trap->tseen) From e138a4b069bba40311ce818a650bfc81d0556ee0 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Tue, 31 May 2016 20:41:35 +0300 Subject: [PATCH 22/81] Fix travel occasionally walking you into pools This was a bug introduced post-3.6.0 in commit 9a2eb370e704 --- src/hack.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hack.c b/src/hack.c index 5c87a2c5e..716958f19 100644 --- a/src/hack.c +++ b/src/hack.c @@ -791,8 +791,7 @@ int mode; /* Pick travel path that does not require crossing a trap. * Avoid water and lava using the usual running rules. * (but not u.ux/u.uy because findtravelpath walks toward u.ux/u.uy) */ - if (context.run == 8 - && (mode == TEST_MOVE || mode == TEST_TRAP) + if (context.run == 8 && (mode != DO_MOVE) && (x != u.ux || y != u.uy)) { struct trap *t = t_at(x, y); From 4ce52dc07f09709548fd6b55e920771d36964ad8 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 1 Jun 2016 05:58:22 -0700 Subject: [PATCH 23/81] iflags.save_uswallow Some groundwork for detection enhancements. you.h - just formatting flag.h - add iflags.save_uswallow so that u.uswallow manipulation ... save.c - ... can be undone if a hangup save occurs --- include/flag.h | 1 + include/you.h | 170 +++++++++++++++++++++++++------------------------ src/save.c | 2 + 3 files changed, 89 insertions(+), 84 deletions(-) diff --git a/include/flag.h b/include/flag.h index 4480b43b3..79bedc9b0 100644 --- a/include/flag.h +++ b/include/flag.h @@ -347,6 +347,7 @@ struct instance_flags { /* copies of values in struct u, used during detection when the originals are temporarily cleared; kept here rather than locally so that they can be restored during a hangup save */ + Bitfield(save_uswallow, 1); Bitfield(save_uinwater, 1); Bitfield(save_uburied, 1); }; diff --git a/include/you.h b/include/you.h index 78a3a1334..336545ade 100644 --- a/include/you.h +++ b/include/you.h @@ -27,9 +27,9 @@ struct RoleAdvance { }; struct u_have { - Bitfield(amulet, 1); /* carrying Amulet */ - Bitfield(bell, 1); /* carrying Bell */ - Bitfield(book, 1); /* carrying Book */ + Bitfield(amulet, 1); /* carrying Amulet */ + Bitfield(bell, 1); /* carrying Bell */ + Bitfield(book, 1); /* carrying Book */ Bitfield(menorah, 1); /* carrying Candelabrum */ Bitfield(questart, 1); /* carrying the Quest Artifact */ Bitfield(unused, 3); @@ -124,16 +124,16 @@ struct Role { short questarti; /* index (ART_) of quest artifact (questpgr.c) */ /*** Bitmasks ***/ - short allow; /* bit mask of allowed variations */ -#define ROLE_RACEMASK 0x0ff8 /* allowable races */ -#define ROLE_GENDMASK 0xf000 /* allowable genders */ -#define ROLE_MALE 0x1000 -#define ROLE_FEMALE 0x2000 -#define ROLE_NEUTER 0x4000 -#define ROLE_ALIGNMASK AM_MASK /* allowable alignments */ -#define ROLE_LAWFUL AM_LAWFUL -#define ROLE_NEUTRAL AM_NEUTRAL -#define ROLE_CHAOTIC AM_CHAOTIC + short allow; /* bit mask of allowed variations */ +#define ROLE_RACEMASK 0x0ff8 /* allowable races */ +#define ROLE_GENDMASK 0xf000 /* allowable genders */ +#define ROLE_MALE 0x1000 +#define ROLE_FEMALE 0x2000 +#define ROLE_NEUTER 0x4000 +#define ROLE_ALIGNMASK AM_MASK /* allowable alignments */ +#define ROLE_LAWFUL AM_LAWFUL +#define ROLE_NEUTRAL AM_NEUTRAL +#define ROLE_CHAOTIC AM_CHAOTIC /*** Attributes (from attrib.c and exper.c) ***/ xchar attrbase[A_MAX]; /* lowest initial attributes */ @@ -202,9 +202,9 @@ struct Race { xchar attrmax[A_MAX]; /* maximum allowable attribute */ struct RoleAdvance hpadv; /* hit point advancement */ struct RoleAdvance enadv; /* energy advancement */ -#if 0 /* DEFERRED */ - int nv_range; /* night vision range */ - int xray_range; /* X-ray vision range */ +#if 0 /* DEFERRED */ + int nv_range; /* night vision range */ + int xray_range; /* X-ray vision range */ #endif /*** Properties in variable-length arrays ***/ @@ -231,14 +231,14 @@ struct Gender { const char *filecode; /* file code */ short allow; /* equivalent ROLE_ mask */ }; -#define ROLE_GENDERS 2 /* number of permitted player genders */ -/* increment to 3 if you allow neuter roles */ +#define ROLE_GENDERS 2 /* number of permitted player genders + increment to 3 if you allow neuter roles */ extern const struct Gender genders[]; /* table of available genders */ -#define uhe() (genders[flags.female ? 1 : 0].he) -#define uhim() (genders[flags.female ? 1 : 0].him) -#define uhis() (genders[flags.female ? 1 : 0].his) -#define mhe(mtmp) (genders[pronoun_gender(mtmp)].he) +#define uhe() (genders[flags.female ? 1 : 0].he) +#define uhim() (genders[flags.female ? 1 : 0].him) +#define uhis() (genders[flags.female ? 1 : 0].his) +#define mhe(mtmp) (genders[pronoun_gender(mtmp)].he) #define mhim(mtmp) (genders[pronoun_gender(mtmp)].him) #define mhis(mtmp) (genders[pronoun_gender(mtmp)].his) @@ -250,32 +250,32 @@ struct Align { short allow; /* equivalent ROLE_ mask */ aligntyp value; /* equivalent A_ value */ }; -#define ROLE_ALIGNS 3 /* number of permitted player alignments */ +#define ROLE_ALIGNS 3 /* number of permitted player alignments */ extern const struct Align aligns[]; /* table of available alignments */ /*** Information about the player ***/ struct you { - xchar ux, uy; - schar dx, dy, dz; /* direction of move (or zap or ... ) */ - schar di; /* direction of FF */ - xchar tx, ty; /* destination of travel */ - xchar ux0, uy0; /* initial position FF */ - d_level uz, uz0; /* your level on this and the previous turn */ - d_level utolev; /* level monster teleported you to, or uz */ - uchar utotype; /* bitmask of goto_level() flags for utolev */ - boolean umoved; /* changed map location (post-move) */ - int last_str_turn; /* 0: none, 1: half turn, 2: full turn */ - /* +: turn right, -: turn left */ - int ulevel; /* 1 to MAXULEV */ + xchar ux, uy; /* current map coordinates */ + schar dx, dy, dz; /* direction of move (or zap or ... ) */ + schar di; /* direction of FF */ + xchar tx, ty; /* destination of travel */ + xchar ux0, uy0; /* initial position FF */ + d_level uz, uz0; /* your level on this and the previous turn */ + d_level utolev; /* level monster teleported you to, or uz */ + uchar utotype; /* bitmask of goto_level() flags for utolev */ + boolean umoved; /* changed map location (post-move) */ + int last_str_turn; /* 0: none, 1: half turn, 2: full turn + +: turn right, -: turn left */ + int ulevel; /* 1 to MAXULEV */ int ulevelmax; unsigned utrap; /* trap timeout */ unsigned utraptype; /* defined if utrap nonzero */ -#define TT_BEARTRAP 0 -#define TT_PIT 1 -#define TT_WEB 2 -#define TT_LAVA 3 -#define TT_INFLOOR 4 +#define TT_BEARTRAP 0 +#define TT_PIT 1 +#define TT_WEB 2 +#define TT_LAVA 3 +#define TT_INFLOOR 4 #define TT_BURIEDBALL 5 char urooms[5]; /* rooms (roomno + 3) occupied now */ char urooms0[5]; /* ditto, for previous position */ @@ -310,16 +310,16 @@ struct you { int bc_order; /* ball & chain order [see bc_order() in ball.c] */ int bc_felt; /* mask for ball/chain being felt */ - int umonster; /* hero's "real" monster num */ - int umonnum; /* current monster number */ + int umonster; /* hero's "real" monster num */ + int umonnum; /* current monster number */ - int mh, mhmax, mtimedone; /* for polymorph-self */ - struct attribs macurr, /* for monster attribs */ - mamax; /* for monster attribs */ - int ulycn; /* lycanthrope type */ + int mh, mhmax, mtimedone; /* for polymorph-self */ + struct attribs macurr, /* for monster attribs */ + mamax; /* for monster attribs */ + int ulycn; /* lycanthrope type */ unsigned ucreamed; - unsigned uswldtim; /* time you have been swallowed */ + unsigned uswldtim; /* time you have been swallowed */ Bitfield(uswallow, 1); /* true if swallowed */ Bitfield(uinwater, 1); /* if you're currently in water (only @@ -328,60 +328,62 @@ struct you { Bitfield(mfemale, 1); /* saved human value of flags.female */ Bitfield(uinvulnerable, 1); /* you're invulnerable (praying) */ Bitfield(uburied, 1); /* you're buried */ - Bitfield(uedibility, 1); /* blessed food detection; sense unsafe food */ + Bitfield(uedibility, 1); /* blessed food detect; sense unsafe food */ /* 1 free bit! */ - unsigned udg_cnt; /* how long you have been demigod */ - struct u_achieve uachieve; /* achievements */ - struct u_event uevent; /* certain events have happened */ - struct u_have uhave; /* you're carrying special objects */ - struct u_conduct uconduct; /* KMH, conduct */ + unsigned udg_cnt; /* how long you have been demigod */ + struct u_achieve uachieve; /* achievements */ + struct u_event uevent; /* certain events have happened */ + struct u_have uhave; /* you're carrying special objects */ + struct u_conduct uconduct; /* KMH, conduct */ struct u_roleplay uroleplay; - struct attribs acurr, /* your current attributes (eg. str)*/ - aexe, /* for gain/loss via "exercise" */ - abon, /* your bonus attributes (eg. str) */ - amax, /* your max attributes (eg. str) */ - atemp, /* used for temporary loss/gain */ - atime; /* used for loss/gain countdown */ - align ualign; /* character alignment */ -#define CONVERT 2 + struct attribs acurr, /* your current attributes (eg. str)*/ + aexe, /* for gain/loss via "exercise" */ + abon, /* your bonus attributes (eg. str) */ + amax, /* your max attributes (eg. str) */ + atemp, /* used for temporary loss/gain */ + atime; /* used for loss/gain countdown */ + align ualign; /* character alignment */ +#define CONVERT 2 #define A_ORIGINAL 1 -#define A_CURRENT 0 +#define A_CURRENT 0 aligntyp ualignbase[CONVERT]; /* for ualign conversion record */ schar uluck, moreluck; /* luck and luck bonus */ #define Luck (u.uluck + u.moreluck) -#define LUCKADD 3 /* added value when carrying luck stone */ -#define LUCKMAX 10 -#define LUCKMIN (-10) +#define LUCKADD 3 /* value of u.moreluck when carrying luck stone; + + when blessed or uncursed, - when cursed */ +#define LUCKMAX 10 /* maximum value of u.ulUck */ +#define LUCKMIN (-10) /* minimum value of u.uluck */ schar uhitinc; schar udaminc; schar uac; - uchar uspellprot; /* protection by SPE_PROTECTION */ - uchar usptime; /* #moves until uspellprot-- */ - uchar uspmtime; /* #moves between uspellprot-- */ - int uhp, uhpmax; - int uen, uenmax; /* magical energy - M. Stephenson */ - xchar uhpinc[MAXULEV], ueninc[MAXULEV]; /* increases from level gain */ - int ugangr; /* if the gods are angry at you */ - int ugifts; /* number of artifacts bestowed */ - int ublessed, ublesscnt; /* blessing/duration from #pray */ + uchar uspellprot; /* protection by SPE_PROTECTION */ + uchar usptime; /* #moves until uspellprot-- */ + uchar uspmtime; /* #moves between uspellprot-- */ + int uhp, uhpmax; /* hit points, aka health */ + int uen, uenmax; /* magical energy - M. Stephenson */ + xchar uhpinc[MAXULEV], /* increases to uhpmax for each level gain */ + ueninc[MAXULEV]; /* increases to uenmax for each level gain */ + int ugangr; /* if the gods are angry at you */ + int ugifts; /* number of artifacts bestowed */ + int ublessed, ublesscnt; /* blessing/duration from #pray */ long umoney0; long uspare1; long uexp, urexp; - long ucleansed; /* to record moves when player was cleansed */ - long usleep; /* sleeping; monstermove you last started */ + long ucleansed; /* to record moves when player was cleansed */ + long usleep; /* sleeping; monstermove you last started */ int uinvault; - struct monst *ustuck; - struct monst *usteed; - long ugallop; - int urideturns; - int umortality; /* how many times you died */ + struct monst *ustuck; /* engulfer or grabber, maybe grabbee if Upolyd */ + struct monst *usteed; /* mount when riding */ + long ugallop; /* turns steed will run after being kicked */ + int urideturns; /* time spent riding, for skill advancement */ + int umortality; /* how many times you died */ int ugrave_arise; /* you die and become something aside from a ghost */ - int weapon_slots; /* unused skill slots */ - int skills_advanced; /* # of advances made so far */ + int weapon_slots; /* unused skill slots */ + int skills_advanced; /* # of advances made so far */ xchar skill_record[P_SKILL_LIMIT]; /* skill advancements */ struct skills weapon_skills[P_NUM_SKILLS]; - boolean twoweap; /* KMH -- Using two-weapon combat */ + boolean twoweap; /* KMH -- Using two-weapon combat */ }; /* end of `struct you' */ diff --git a/src/save.c b/src/save.c index 93b0d06c5..078f276af 100644 --- a/src/save.c +++ b/src/save.c @@ -115,6 +115,8 @@ dosave0() a few of things before saving so that they won't be restored in an improper state; these will be no-ops for normal save sequence */ u.uinvulnerable = 0; + if (iflags.save_uswallow) + u.uswallow = 1, iflags.save_uswallow = 0; if (iflags.save_uinwater) u.uinwater = 1, iflags.save_uinwater = 0; if (iflags.save_uburied) From 5503c5e1e44adbdc4d6f6a3cbac33482b40adf27 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Wed, 1 Jun 2016 18:29:01 +0300 Subject: [PATCH 24/81] Disable object pile display when hallucinating Hallucination shows object piles under boulders --- src/mapglyph.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mapglyph.c b/src/mapglyph.c index dab688620..4ba4ee7e6 100644 --- a/src/mapglyph.c +++ b/src/mapglyph.c @@ -54,6 +54,9 @@ static int explcolors[] = { (currentgraphics == ROGUESET && SYMHANDLING(H_IBM)) #endif +#define is_objpile(x,y) (!Hallucination && level.objects[(x)][(y)] \ + && level.objects[(x)][(y)]->nexthere) + /*ARGSUSED*/ int mapglyph(glyph, ochar, ocolor, ospecial, x, y) @@ -83,7 +86,7 @@ unsigned *ospecial; else obj_color(STATUE); special |= MG_STATUE; - if (level.objects[x][y] && level.objects[x][y]->nexthere) + if (is_objpile(x,y)) special |= MG_OBJPILE; } else if ((offset = (glyph - GLYPH_WARNING_OFF)) >= 0) { /* warn flash */ idx = offset + SYM_OFF_W; @@ -156,8 +159,7 @@ unsigned *ospecial; } } else obj_color(offset); - if (offset != BOULDER && level.objects[x][y] - && level.objects[x][y]->nexthere) + if (offset != BOULDER && is_objpile(x,y)) special |= MG_OBJPILE; } else if ((offset = (glyph - GLYPH_RIDDEN_OFF)) >= 0) { /* mon ridden */ idx = mons[offset].mlet + SYM_OFF_M; @@ -176,7 +178,7 @@ unsigned *ospecial; else mon_color(offset); special |= MG_CORPSE; - if (level.objects[x][y] && level.objects[x][y]->nexthere) + if (is_objpile(x,y)) special |= MG_OBJPILE; } else if ((offset = (glyph - GLYPH_DETECT_OFF)) >= 0) { /* mon detect */ idx = mons[offset].mlet + SYM_OFF_M; From a5d3d0fdb88f5fe1f437f9a148e9d59147d8c808 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Wed, 1 Jun 2016 18:38:21 +0300 Subject: [PATCH 25/81] Make coyote names deterministic --- src/do_name.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/do_name.c b/src/do_name.c index 5d5c419c6..5e869da67 100644 --- a/src/do_name.c +++ b/src/do_name.c @@ -1613,7 +1613,7 @@ char *buf; Sprintf(buf, "%s - %s", x_monnam(mtmp, ARTICLE_NONE, (char *) 0, 0, TRUE), mtmp->mcan ? coynames[SIZE(coynames) - 1] - : coynames[rn2(SIZE(coynames) - 1)]); + : coynames[mtmp->m_id % (SIZE(coynames) - 1)]); } return buf; } From a5d57430be2ede01e150276e7bd660f6f14eb9a3 Mon Sep 17 00:00:00 2001 From: PatR Date: Wed, 1 Jun 2016 18:45:52 -0700 Subject: [PATCH 26/81] obscure polyself message Hero was poly'd into a hider monster and was swallowed by something. Attempting to hide via '#monster' gave "You can't hide while you're being held." It correctly blocked hiding due to u.ustuck but the feedback ignored the possibility of u.ustuck+u.uswallow. --- doc/fixes36.1 | 2 ++ src/polyself.c | 11 ++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 26967c078..8cfd5880a 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -278,6 +278,8 @@ describe detected door traps and chest traps as trapped door and trapped chest instead of bear trap; bear trap tile is still used on map though thrown potion that killed peaceful monster would cause "the gets argry" after the message about it being killed +when poly'd into a hider and engulfed, attempt to hide via #monster was blocked + but feedback said "can't hide while held" rather than "while engulfed" Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/polyself.c b/src/polyself.c index b67cc133a..d4c2afdf1 100644 --- a/src/polyself.c +++ b/src/polyself.c @@ -1399,11 +1399,12 @@ dohide() (except for floor hiders [trapper or mimic] in pits) */ if (u.ustuck || (u.utrap && (u.utraptype != TT_PIT || on_ceiling))) { You_cant("hide while you're %s.", - !u.ustuck ? "trapped" : !sticks(youmonst.data) - ? "being held" - : humanoid(u.ustuck->data) - ? "holding someone" - : "holding that creature"); + !u.ustuck ? "trapped" + : u.uswallow ? (is_animal(u.ustuck->data) ? "swallowed" + : "engulfed") + : !sticks(youmonst.data) ? "being held" + : (humanoid(u.ustuck->data) ? "holding someone" + : "holding that creature")); if (u.uundetected || (ismimic && youmonst.m_ap_type != M_AP_NOTHING)) { u.uundetected = 0; From 894d6c68593d65ba6bb9b8b8929113312e51405e Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 2 Jun 2016 01:08:34 -0700 Subject: [PATCH 27/81] detection revamp Change most instances of detection to offer the player a chance to move the cursor around on the map so that the getpos() autodescribe feature can explain things that might go away as soon as the current detection completes. The few instances that don't offer such a chance are the ones where everything which has been revealed will still be there once the action finishes (such as regular magic mapping and blessed/persistent monster detection). There were quite a lot of inconsistencies in things like handling for detection while swallowed or underwater. I didn't keep track of them to distinguish between 3.6.0, current dev code, or my patch in progess. They should be much more consistent now but without a comprehensive fixes36.1 entry. Blessed clairvoyance (divination spells at skilled or expert) now shows monsters as well as terrain. I first had it like that for any clairvoyance, but having getpos/autodescribe kick in every 15 or 30 turns once you have the amulet--or pay the appropriate amount to a temple preist--was nearly unplayable. When it only follows an explicit spell cast it is not intrusive. --- doc/fixes36.1 | 13 +- include/extern.h | 2 +- include/flag.h | 9 +- src/allmain.c | 2 +- src/detect.c | 401 ++++++++++++++++++++++++++++++++--------------- src/pager.c | 52 ++++-- src/spell.c | 9 +- 7 files changed, 334 insertions(+), 154 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 8cfd5880a..2e58f8f23 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -280,6 +280,8 @@ thrown potion that killed peaceful monster would cause "the gets argry" after the message about it being killed when poly'd into a hider and engulfed, attempt to hide via #monster was blocked but feedback said "can't hide while held" rather than "while engulfed" +various monster/object/food/gold/trap detections were inconsistent in how they + behaved if performed while engulfed or underwater Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository @@ -395,9 +397,12 @@ during end of game disclosure, the vanquished monsters list can be sorted in one of several ways by answering 'a' to "disclose vanquished monsters?" when #terrain is displaying a censored version of the map (no monsters, &c), moving the cursor will display farlook's brief autodescribe feedback +when monster/object/trap detection temporarily clears the map to highlight the + results of such detection, farlook autodescibe can be used to view it interrupt a multi turn action if hp or pw is restored to maximum pressing d or D when cursor positioning targets doors and doorways pressing x or X when cursor positioning targets possibly unexplored location + (potentially useful when using '_' [not mouse] to invoke travel) Platform- and/or Interface-Specific New Features @@ -420,10 +425,10 @@ X11: status display split into three columns to accomodate Stone/Deaf/Lev/&c; NetHack Community Patches (or Variation) Included ------------------------------------------------- Malcolm Ryan's improved tin opener -Ray Chason's keyboard may stop responding after locking or unlocking a door when - using altkeyhandler=nhraykey.dll -Ray Chason's fix: window interfaces that support transparency may give away unseen - parts of the map +Ray Chason's keyboard may stop responding after locking or unlocking a door + when using altkeyhandler=nhraykey.dll +Ray Chason's fix: window interfaces that support transparency may give away + unseen parts of the map Ray Chason's xprname should honor iflags.menu_tab_sep Ray Chason's punctuation for "That foo is really a mimic." Ray Chason's proper background tiles for lava and water diff --git a/include/extern.h b/include/extern.h index 4c5cb8e64..4b58776f5 100644 --- a/include/extern.h +++ b/include/extern.h @@ -248,7 +248,7 @@ E int FDECL(trap_detect, (struct obj *)); E const char *FDECL(level_distance, (d_level *)); E void FDECL(use_crystal_ball, (struct obj **)); E void NDECL(do_mapping); -E void NDECL(do_vicinity_map); +E void FDECL(do_vicinity_map, (struct obj *)); E void FDECL(cvt_sdoor_to_door, (struct rm *)); #ifdef USE_TRAMPOLI E void FDECL(findone, (int, int, genericptr_t)); diff --git a/include/flag.h b/include/flag.h index 79bedc9b0..a8a6d8593 100644 --- a/include/flag.h +++ b/include/flag.h @@ -187,10 +187,11 @@ struct instance_flags { int override_ID; /* true to force full identification of objects */ int suppress_price; /* controls doname() for unpaid objects */ int terrainmode; /* for getpos()'s autodescribe when #terrain is active */ -#define TER_MAP 0x01 -#define TER_TRP 0x02 -#define TER_OBJ 0x04 -#define TER_MON 0x08 +#define TER_MAP 0x01 +#define TER_TRP 0x02 +#define TER_OBJ 0x04 +#define TER_MON 0x08 +#define TER_DETECT 0x10 /* detect_foo magic rather than #terrain */ coord travelcc; /* coordinates for travel_cache */ boolean window_inited; /* true if init_nhwindows() completed */ boolean vision_inited; /* true if vision is ready */ diff --git a/src/allmain.c b/src/allmain.c index 33afd9927..3aecf399a 100644 --- a/src/allmain.c +++ b/src/allmain.c @@ -370,7 +370,7 @@ boolean resuming; clear_bypasses(); if ((u.uhave.amulet || Clairvoyant) && !In_endgame(&u.uz) && !BClairvoyant && !(moves % 15) && !rn2(2)) - do_vicinity_map(); + do_vicinity_map((struct obj *) 0); if (u.utrap && u.utraptype == TT_LAVA) sink_into_lava(); /* when/if hero escapes from lava, he can't just stay there */ diff --git a/src/detect.c b/src/detect.c index 07b93227f..7668d77bb 100644 --- a/src/detect.c +++ b/src/detect.c @@ -12,6 +12,10 @@ extern boolean known; /* from read.c */ +STATIC_DCL boolean NDECL(unconstrain_map); +STATIC_DCL void NDECL(reconstrain_map); +STATIC_DCL void FDECL(browse_map, (int, const char *)); +STATIC_DCL void FDECL(map_monst, (struct monst *, BOOLEAN_P)); STATIC_DCL void FDECL(do_dknown_of, (struct obj *)); STATIC_DCL boolean FDECL(check_map_spot, (int, int, CHAR_P, unsigned)); STATIC_DCL boolean FDECL(clear_stale_map, (CHAR_P, unsigned)); @@ -22,6 +26,62 @@ STATIC_PTR void FDECL(findone, (int, int, genericptr_t)); STATIC_PTR void FDECL(openone, (int, int, genericptr_t)); STATIC_DCL int FDECL(mfind0, (struct monst *, BOOLEAN_P)); +/* bring hero out from underwater or underground or being engulfed; + return True iff any change occurred */ +STATIC_OVL boolean +unconstrain_map() +{ + boolean res = u.uinwater || u.uburied || u.uswallow; + + /* bring Underwater, buried, or swallowed hero to normal map */ + iflags.save_uinwater = u.uinwater, u.uinwater = 0; + iflags.save_uburied = u.uburied, u.uburied = 0; + iflags.save_uswallow = u.uswallow, u.uswallow = 0; + + return res; +} + +/* put hero back underwater or underground or engulfed */ +STATIC_OVL void +reconstrain_map() +{ + u.uinwater = iflags.save_uinwater, iflags.save_uinwater = 0; + u.uburied = iflags.save_uburied, iflags.save_uburied = 0; + u.uswallow = iflags.save_uswallow, iflags.save_uswallow = 0; +} + +/* use getpos()'s 'autodescribe' to view whatever is currently shown on map */ +STATIC_DCL void +browse_map(ter_typ, ter_explain) +int ter_typ; +const char *ter_explain; +{ + coord dummy_pos; /* don't care whether player actually picks a spot */ + + dummy_pos.x = u.ux, dummy_pos.y = u.uy; /* starting spot for getpos() */ + iflags.autodescribe = TRUE; + iflags.terrainmode = ter_typ; + getpos(&dummy_pos, FALSE, ter_explain); + iflags.terrainmode = 0; + /* leave iflags.autodescribe 'on' even if previously 'off' */ +} + +/* extracted from monster_detection() so can be shared by do_vicinity_map() */ +STATIC_DCL void +map_monst(mtmp, showtail) +struct monst *mtmp; +boolean showtail; +{ + if (def_monsyms[(int) mtmp->data->mlet].sym == ' ') + show_glyph(mtmp->mx, mtmp->my, detected_mon_to_glyph(mtmp)); + else + show_glyph(mtmp->mx, mtmp->my, + mtmp->mtame ? pet_to_glyph(mtmp) : mon_to_glyph(mtmp)); + + if (showtail && mtmp->data == &mons[PM_LONG_WORM]) + detect_wsegs(mtmp, 0); +} + /* this is checking whether a trap symbol represents a trapped chest, not whether a trapped chest is actually present */ boolean @@ -29,12 +89,42 @@ trapped_chest_at(ttyp, x, y) int ttyp; int x, y; { + struct monst *mtmp; + struct obj *otmp; + if (!glyph_is_trap(glyph_at(x, y))) return FALSE; if (ttyp != BEAR_TRAP || (Hallucination && rn2(20))) return FALSE; - /* presence of any trappable container will do */ - return (sobj_at(CHEST, x, y) || sobj_at(LARGE_BOX, x, y)) ? TRUE : FALSE; + + /* + * TODO? We should check containers recursively like the trap + * detecting routine does. Chests and large boxes do not nest in + * themselves or each other, but could be contained inside statues. + * + * For farlook, we should also check for buried containers, but + * for '^' command to examine adjacent trap glyph, we shouldn't. + */ + + /* on map, presence of any trappable container will do */ + if (sobj_at(CHEST, x, y) || sobj_at(LARGE_BOX, x, y)) + return TRUE; + /* in inventory, we need to find one which is actually trapped */ + if (x == u.ux && y == u.uy) { + for (otmp = invent; otmp; otmp = otmp->nobj) + if (Is_box(otmp) && otmp->otrapped) + return TRUE; + if (u.usteed) { /* steed isn't on map so won't be found by m_at() */ + for (otmp = u.usteed->minvent; otmp; otmp = otmp->nobj) + if (Is_box(otmp) && otmp->otrapped) + return TRUE; + } + } + if ((mtmp = m_at(x, y)) != 0) + for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) + if (Is_box(otmp) && otmp->otrapped) + return TRUE; + return FALSE; } /* this is checking whether a trap symbol represents a trapped door, @@ -202,27 +292,34 @@ register struct obj *sobj; { register struct obj *obj; register struct monst *mtmp; - struct obj *temp; - boolean stale; + struct obj gold, *temp = 0; + boolean stale, ugold = FALSE, steedgold = FALSE; + int ter_typ = TER_DETECT | TER_OBJ; - known = stale = - clear_stale_map(COIN_CLASS, (unsigned) (sobj->blessed ? GOLD : 0)); + known = stale = clear_stale_map(COIN_CLASS, + (unsigned) (sobj->blessed ? GOLD : 0)); /* look for gold carried by monsters (might be in a container) */ for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; /* probably not needed in this case but... */ if (findgold(mtmp->minvent) || monsndx(mtmp->data) == PM_GOLD_GOLEM) { - known = TRUE; - goto outgoldmap; /* skip further searching */ + if (mtmp == u.usteed) { + steedgold = TRUE; + } else { + known = TRUE; + goto outgoldmap; /* skip further searching */ + } } else { for (obj = mtmp->minvent; obj; obj = obj->nobj) - if (sobj->blessed && o_material(obj, GOLD)) { - known = TRUE; - goto outgoldmap; - } else if (o_in(obj, COIN_CLASS)) { - known = TRUE; - goto outgoldmap; /* skip further searching */ + if ((sobj->blessed && o_material(obj, GOLD)) + || o_in(obj, COIN_CLASS)) { + if (mtmp == u.usteed) { + steedgold = TRUE; + } else { + known = TRUE; + goto outgoldmap; /* skip further searching */ + } } } } @@ -246,13 +343,21 @@ register struct obj *sobj; if (sobj) { char buf[BUFSZ]; - if (youmonst.data == &mons[PM_GOLD_GOLEM]) { + if (youmonst.data == &mons[PM_GOLD_GOLEM]) Sprintf(buf, "You feel like a million %s!", currency(2L)); - } else if (hidden_gold() || money_cnt(invent)) + else if (money_cnt(invent) || hidden_gold()) Strcpy(buf, "You feel worried about your future financial situation."); + else if (steedgold) + Sprintf(buf, "You feel interested in %s financial situation.", + s_suffix(x_monnam(u.usteed, + u.usteed->mtame ? ARTICLE_YOUR + : ARTICLE_THE, + (char *) 0, + SUPPRESS_SADDLE, FALSE))); else Strcpy(buf, "You feel materially poor."); + strange_feeling(sobj, buf); } return 1; @@ -266,8 +371,7 @@ register struct obj *sobj; outgoldmap: cls(); - iflags.save_uinwater = u.uinwater, iflags.save_uburied = u.uburied; - u.uinwater = u.uburied = 0; + (void) unconstrain_map(); /* Discover gold locations. */ for (obj = fobj; obj; obj = obj->nobj) { if (sobj->blessed && (temp = o_material(obj, GOLD)) != 0) { @@ -283,19 +387,21 @@ outgoldmap: } map_object(temp, 1); } + if (temp && temp->ox == u.ux && temp->oy == u.uy) + ugold = TRUE; } for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; /* probably overkill here */ + temp = 0; if (findgold(mtmp->minvent) || monsndx(mtmp->data) == PM_GOLD_GOLEM) { - struct obj gold; - gold = zeroobj; /* ensure oextra is cleared too */ gold.otyp = GOLD_PIECE; gold.quan = (long) rnd(10); /* usually more than 1 */ gold.ox = mtmp->mx; gold.oy = mtmp->my; map_object(&gold, 1); + temp = &gold; } else { for (obj = mtmp->minvent; obj; obj = obj->nobj) if (sobj->blessed && (temp = o_material(obj, GOLD)) != 0) { @@ -310,13 +416,19 @@ outgoldmap: break; } } + if (temp && temp->ox == u.ux && temp->oy == u.uy) + ugold = TRUE; + } + if (!ugold) { + newsym(u.ux, u.uy); + ter_typ |= TER_MON; /* so autodescribe will recognize hero */ } - newsym(u.ux, u.uy); - u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; - iflags.save_uinwater = iflags.save_uburied = 0; You_feel("very greedy, and sense gold!"); exercise(A_WIS, TRUE); - display_nhwindow(WIN_MAP, TRUE); + + browse_map(ter_typ, "gold"); + + reconstrain_map(); docrt(); if (Underwater) under_water(2); @@ -339,6 +451,8 @@ register struct obj *sobj; const char *what = confused ? something : "food"; stale = clear_stale_map(oclass, 0); + if (u.usteed) /* some situations leave steed with stale coordinates */ + u.usteed->mx = u.ux, u.usteed->my = u.uy; for (obj = fobj; obj; obj = obj->nobj) if (o_in(obj, oclass)) { @@ -347,12 +461,14 @@ register struct obj *sobj; else ct++; } - for (mtmp = fmon; mtmp && !ct; mtmp = mtmp->nmon) { - /* no DEADMONSTER(mtmp) check needed since dmons never have inventory - */ + for (mtmp = fmon; mtmp && (!ct || !ctu); mtmp = mtmp->nmon) { + /* no DEADMONSTER(mtmp) check needed -- dmons never have inventory */ for (obj = mtmp->minvent; obj; obj = obj->nobj) if (o_in(obj, oclass)) { - ct++; + if (mtmp->mx == u.ux && mtmp->my == u.uy) + ctu++; /* steed or an engulfer with inventory */ + else + ct++; break; } } @@ -369,6 +485,7 @@ register struct obj *sobj; } } else if (sobj) { char buf[BUFSZ]; + Sprintf(buf, "Your %s twitches%s.", body_part(NOSE), (sobj->blessed && !u.uedibility) ? " then starts to tingle" @@ -394,10 +511,11 @@ register struct obj *sobj; } } else { struct obj *temp; + int ter_typ = TER_DETECT | TER_OBJ; + known = TRUE; cls(); - iflags.save_uinwater = u.uinwater, iflags.save_uburied = u.uburied; - u.uinwater = u.uburied = 0; + (void) unconstrain_map(); for (obj = fobj; obj; obj = obj->nobj) if ((temp = o_in(obj, oclass)) != 0) { if (temp != obj) { @@ -407,8 +525,7 @@ register struct obj *sobj; map_object(temp, 1); } for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) - /* no DEADMONSTER(mtmp) check needed since dmons never have - * inventory */ + /* no DEADMONSTER() check needed -- dmons never have inventory */ for (obj = mtmp->minvent; obj; obj = obj->nobj) if ((temp = o_in(obj, oclass)) != 0) { temp->ox = mtmp->mx; @@ -416,9 +533,10 @@ register struct obj *sobj; map_object(temp, 1); break; /* skip rest of this monster's inventory */ } - newsym(u.ux, u.uy); - u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; - iflags.save_uinwater = iflags.save_uburied = 0; + if (!ctu) { + newsym(u.ux, u.uy); + ter_typ |= TER_MON; /* for autodescribe of self */ + } if (sobj) { if (sobj->blessed) { Your("%s %s to tingle and you smell %s.", body_part(NOSE), @@ -428,8 +546,11 @@ register struct obj *sobj; Your("%s tingles and you smell %s.", body_part(NOSE), what); } else You("sense %s.", what); - display_nhwindow(WIN_MAP, TRUE); exercise(A_WIS, TRUE); + + browse_map(ter_typ, "food"); + + reconstrain_map(); docrt(); if (Underwater) under_water(2); @@ -459,7 +580,7 @@ int class; /* an object class, 0 for all */ int ct = 0, ctu = 0; register struct obj *obj, *otmp = (struct obj *) 0; register struct monst *mtmp; - int sym, boulder = 0; + int sym, boulder = 0, ter_typ = TER_DETECT | TER_OBJ; if (class < 0 || class >= MAXOCLASSES) { impossible("object_detect: illegal class %d", class); @@ -509,6 +630,9 @@ int class; /* an object class, 0 for all */ do_dknown_of(obj); } + if (u.usteed) + u.usteed->mx = u.ux, u.usteed->my = u.uy; + for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; @@ -540,8 +664,7 @@ int class; /* an object class, 0 for all */ cls(); - iflags.save_uinwater = u.uinwater, iflags.save_uburied = u.uburied; - u.uinwater = u.uburied = 0; + (void) unconstrain_map(); /* * Map all buried objects first. */ @@ -618,16 +741,18 @@ int class; /* an object class, 0 for all */ map_object(&gold, 1); } } - - newsym(u.ux, u.uy); - u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; - iflags.save_uinwater = iflags.save_uburied = 0; + if (!glyph_is_object(glyph_at(u.ux, u.uy))) { + newsym(u.ux, u.uy); + ter_typ |= TER_MON; + } You("detect the %s of %s.", ct ? "presence" : "absence", stuff); - display_nhwindow(WIN_MAP, TRUE); - /* - * What are we going to do when the hero does an object detect while blind - * and the detected object covers a known pool? - */ + + if (!ct) + display_nhwindow(WIN_MAP, TRUE); + else + browse_map(ter_typ, "object"); + + reconstrain_map(); docrt(); /* this will correctly reset vision */ if (Underwater) under_water(2); @@ -668,26 +793,19 @@ int mclass; /* monster class, 0 for all */ : "You feel threatened."); return 1; } else { - boolean woken = FALSE; + boolean unconstrained, woken = FALSE; + unsigned swallowed = u.uswallow; /* before unconstrain_map() */ cls(); + unconstrained = unconstrain_map(); for (mtmp = fmon; mtmp; mtmp = mtmp->nmon) { if (DEADMONSTER(mtmp)) continue; if (!mclass || mtmp->data->mlet == mclass || (mtmp->data == &mons[PM_LONG_WORM] && mclass == S_WORM_TAIL)) - if (mtmp->mx > 0) { - if (mclass && def_monsyms[mclass].sym == ' ') - show_glyph(mtmp->mx, mtmp->my, - detected_mon_to_glyph(mtmp)); - else - show_glyph(mtmp->mx, mtmp->my, - mtmp->mtame ? pet_to_glyph(mtmp) : mon_to_glyph(mtmp)); - /* don't be stingy - display entire worm */ - if (mtmp->data == &mons[PM_LONG_WORM]) - detect_wsegs(mtmp, 0); - } + map_monst(mtmp, TRUE); + if (otmp && otmp->cursed && (mtmp->msleeping || !mtmp->mcanmove)) { mtmp->msleeping = mtmp->mfrozen = 0; @@ -695,12 +813,25 @@ int mclass; /* monster class, 0 for all */ woken = TRUE; } } - display_self(); + if (!swallowed) + display_self(); You("sense the presence of monsters."); if (woken) pline("Monsters sense the presence of you."); - display_nhwindow(WIN_MAP, TRUE); - docrt(); + + if ((otmp && otmp->blessed) && !unconstrained) { + /* persistent detection--just show updated map */ + display_nhwindow(WIN_MAP, TRUE); + } else { + /* one-shot detection--allow player to move cursor around and + get autodescribe feedback */ + EDetect_monsters |= I_SPECIAL; + browse_map(TER_DETECT | TER_MON, "monster of interest"); + EDetect_monsters &= ~I_SPECIAL; + } + + reconstrain_map(); + docrt(); /* redraw the screen to remove unseen monsters from map */ if (Underwater) under_water(2); if (u.uburied) @@ -734,7 +865,7 @@ int src_cursed; } else if (trap) { map_trap(trap, 1); trap->tseen = 1; - } else { + } else { /* trapped door or trapped chest */ struct trap temp_trap; /* fake trap */ (void) memset((genericptr_t) &temp_trap, 0, sizeof temp_trap); @@ -762,6 +893,11 @@ int how; /* 1 for misleading map feedback */ xchar x, y; int result = OTRAP_NONE; + /* + * TODO? Display locations of unarmed land mine and beartrap objects. + * If so, should they be displayed as objects or as traps? + */ + for (otmp = objlist; otmp; otmp = otmp->nobj) { if (Is_box(otmp) && otmp->otrapped && get_obj_location(otmp, &x, &y, BURIED_TOO | CONTAINED_TOO)) { @@ -786,11 +922,14 @@ struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ { register struct trap *ttmp; struct monst *mon; - int door, glyph, tr; + int door, glyph, tr, ter_typ = TER_DETECT | TER_TRP; int cursed_src = sobj && sobj->cursed; boolean found = FALSE; coord cc; + if (u.usteed) + u.usteed->mx = u.ux, u.usteed->my = u.uy; + /* floor/ceiling traps */ for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap) { if (ttmp->tx != u.ux || ttmp->ty != u.uy) @@ -847,9 +986,7 @@ struct obj *sobj; /* null if crystal ball, *scroll if gold detection scroll */ outtrapmap: cls(); - iflags.save_uinwater = u.uinwater, iflags.save_uburied = u.uburied; - u.uinwater = u.uburied = 0; - + (void) unconstrain_map(); /* show chest traps first, so that subsequent floor trap display will override if both types are present at the same location */ (void) detect_obj_traps(fobj, TRUE, cursed_src); @@ -872,15 +1009,16 @@ outtrapmap: /* redisplay hero unless sense_trap() revealed something at */ glyph = glyph_at(u.ux, u.uy); - if (!(glyph_is_trap(glyph) || glyph_is_object(glyph))) + if (!(glyph_is_trap(glyph) || glyph_is_object(glyph))) { newsym(u.ux, u.uy); - u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; - iflags.save_uinwater = iflags.save_uburied = 0; - + ter_typ |= TER_MON; /* for autodescribe at */ + } You_feel("%s.", cursed_src ? "very greedy" : "entrapped"); - /* wait for user to respond, then reset map display to normal */ - display_nhwindow(WIN_MAP, TRUE); - docrt(); + + browse_map(ter_typ, "trap of interest"); + + reconstrain_map(); + docrt(); /* redraw the screen to remove unseen traps from the map */ if (Underwater) under_water(2); if (u.uburied) @@ -1132,40 +1270,67 @@ void do_mapping() { register int zx, zy; + boolean unconstrained; - iflags.save_uinwater = u.uinwater, iflags.save_uburied = u.uburied; - u.uinwater = u.uburied = 0; + unconstrained = unconstrain_map(); for (zx = 1; zx < COLNO; zx++) for (zy = 0; zy < ROWNO; zy++) show_map_spot(zx, zy); - u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; - iflags.save_uinwater = iflags.save_uburied = 0; - if (!level.flags.hero_memory || Underwater) { + + if (!level.flags.hero_memory || unconstrained) { flush_screen(1); /* flush temp screen */ - display_nhwindow(WIN_MAP, TRUE); /* wait */ + /* browse_map() instead of display_nhwindow(WIN_MAP, TRUE) */ + browse_map(TER_DETECT | TER_MAP | TER_TRP | TER_OBJ, + "anything of interest"); docrt(); } + reconstrain_map(); exercise(A_WIS, TRUE); } +/* clairvoyance */ void -do_vicinity_map() +do_vicinity_map(sobj) +struct obj *sobj; /* scroll--actually fake spellbook--object */ { register int zx, zy; - int lo_y = (u.uy - 5 < 0 ? 0 : u.uy - 5), - hi_y = (u.uy + 6 > ROWNO ? ROWNO : u.uy + 6), - lo_x = (u.ux - 9 < 1 ? 1 : u.ux - 9), /* avoid column 0 */ - hi_x = (u.ux + 10 > COLNO ? COLNO : u.ux + 10); + struct monst *mtmp; + boolean unconstrained, refresh = FALSE, mdetected = FALSE, + extended = (sobj && sobj->blessed); + int lo_y = ((u.uy - 5 < 0) ? 0 : u.uy - 5), + hi_y = ((u.uy + 6 >= ROWNO) ? ROWNO - 1 : u.uy + 6), + lo_x = ((u.ux - 9 < 1) ? 1 : u.ux - 9), /* avoid column 0 */ + hi_x = ((u.ux + 10 >= COLNO) ? COLNO - 1 : u.ux + 10), + ter_typ = TER_DETECT | TER_MAP | TER_TRP | TER_OBJ; - for (zx = lo_x; zx < hi_x; zx++) - for (zy = lo_y; zy < hi_y; zy++) + unconstrained = unconstrain_map(); + for (zx = lo_x; zx <= hi_x; zx++) + for (zy = lo_y; zy <= hi_y; zy++) { show_map_spot(zx, zy); - if (!level.flags.hero_memory || Underwater) { + if (extended && (mtmp = m_at(zx, zy)) != 0 + && mtmp->mx == zx && mtmp->my == zy) { /* skip worm tails */ + int oldglyph = glyph_at(zx, zy); + + map_monst(mtmp, FALSE); + if (glyph_at(zx, zy) != oldglyph) + mdetected = TRUE; + } + } + + if (!level.flags.hero_memory || unconstrained || mdetected) { flush_screen(1); /* flush temp screen */ - display_nhwindow(WIN_MAP, TRUE); /* wait */ - docrt(); + if (extended || glyph_is_monster(glyph_at(u.ux, u.uy))) + ter_typ |= TER_MON; + if (extended) + EDetect_monsters |= I_SPECIAL; + browse_map(ter_typ, "anything of interest"); + EDetect_monsters &= ~I_SPECIAL; + refresh = TRUE; } + reconstrain_map(); + if (refresh) + docrt(); } /* convert a secret door into a normal door */ @@ -1379,12 +1544,10 @@ boolean via_warning; exercise(A_WIS, TRUE); if (!canspotmon(mtmp)) { if (glyph_is_invisible(levl[x][y].glyph)) { - /* found invisible monster in a square - * which already has an 'I' in it. - * Logically, this should still take - * time and lead to a return(1), but - * if we did that the player would keep - * finding the same monster every turn. + /* Found invisible monster in a square which already has + * an 'I' in it. Logically, this should still take time + * and lead to a return 1, but if we did that the player + * would keep finding the same monster every turn. */ return -1; } else { @@ -1392,18 +1555,16 @@ boolean via_warning; map_invisible(x, y); } } else if (!sensemon(mtmp)) - You("find %s.", mtmp->mtame - ? y_monnam(mtmp) - : a_monnam(mtmp)); + You("find %s.", + mtmp->mtame ? y_monnam(mtmp) : a_monnam(mtmp)); return 1; } if (!canspotmon(mtmp)) { if (mtmp->mundetected - && (is_hider(mtmp->data) - || mtmp->data->mlet == S_EEL)) + && (is_hider(mtmp->data) || mtmp->data->mlet == S_EEL)) if (via_warning) { Your("warning senses cause you to take a second %s.", - Blind ? "to check nearby" : "look close by"); + Blind ? "to check nearby" : "look close by"); display_nhwindow(WIN_MESSAGE, FALSE); /* flush messages */ } mtmp->mundetected = 0; @@ -1418,10 +1579,10 @@ dosearch0(aflag) register int aflag; /* intrinsic autosearch vs explicit searching */ { #ifdef GCC_BUG - /* some versions of gcc seriously muck up nested loops. if you get strange - crashes while searching in a version compiled with gcc, try putting - #define GCC_BUG in *conf.h (or adding -DGCC_BUG to CFLAGS in the - makefile). + /* Some old versions of gcc seriously muck up nested loops. If you get + * strange crashes while searching in a version compiled with gcc, try + * putting #define GCC_BUG in *conf.h (or adding -DGCC_BUG to CFLAGS in + * the makefile). */ volatile xchar x, y; #else @@ -1566,21 +1727,16 @@ int which_subset; /* when not full, whether to suppress objs and/or traps */ } else { int x, y, glyph, levl_glyph, default_glyph; uchar seenv; - unsigned save_swallowed; struct monst *mtmp; struct trap *t; - coord pos; char buf[BUFSZ]; /* there is a TER_MAP bit too; we always show map regardless of it */ boolean keep_traps = (which_subset & TER_TRP) !=0, keep_objs = (which_subset & TER_OBJ) != 0, keep_mons = (which_subset & TER_MON) != 0; /* not used */ + unsigned swallowed = u.uswallow; /* before unconstrain_map() */ - save_swallowed = u.uswallow; - iflags.save_uinwater = u.uinwater, iflags.save_uburied = u.uburied; - u.uinwater = u.uburied = 0; - u.uswallow = 0; - if (iflags.save_uinwater || iflags.save_uburied) + if (unconstrain_map()) docrt(); default_glyph = cmap_to_glyph(level.flags.arboreal ? S_tree : S_stone); /* for 'full', show the actual terrain for the entire level, @@ -1605,8 +1761,8 @@ int which_subset; /* when not full, whether to suppress objs and/or traps */ glyph, which will never be a monster (unless it is the invisible monster glyph, which is handled like an object, replacing any object or trap at its spot) */ - glyph = !save_swallowed ? glyph_at(x, y) : levl_glyph; - if (keep_mons && x == u.ux && y == u.uy && save_swallowed) + glyph = !swallowed ? glyph_at(x, y) : levl_glyph; + if (keep_mons && x == u.ux && y == u.uy && swallowed) glyph = mon_to_glyph(u.ustuck); else if (((glyph_is_monster(glyph) || glyph_is_warning(glyph)) && !keep_mons) @@ -1676,17 +1832,10 @@ int which_subset; /* when not full, whether to suppress objs and/or traps */ /* allow player to move cursor around and get autodescribe feedback based on what is visible now rather than what is on 'real' map */ - pos.x = u.ux, pos.y = u.uy; - iflags.autodescribe = TRUE; - iflags.terrainmode = which_subset | TER_MAP; /* guaranteed non-zero */ - getpos(&pos, FALSE, "anything of interest"); - iflags.terrainmode = 0; - /* leave iflags.autodescribe 'on' even if it was previously 'off' */ + which_subset |= TER_MAP; /* guarantee non-zero */ + browse_map(which_subset, "anything of interest"); - u.uinwater = iflags.save_uinwater, u.uburied = iflags.save_uburied; - iflags.save_uinwater = iflags.save_uburied = 0; - if (save_swallowed) - u.uswallow = 1; + reconstrain_map(); docrt(); /* redraw the screen, restoring regular map */ if (Underwater) under_water(2); diff --git a/src/pager.c b/src/pager.c index 8ffe31409..dac2bc3b5 100644 --- a/src/pager.c +++ b/src/pager.c @@ -74,11 +74,13 @@ char *outbuf; mons[u.umonnum].mname, plname); if (u.usteed) Sprintf(eos(outbuf), ", mounted on %s", y_monnam(u.usteed)); + if (u.uundetected || (Upolyd && youmonst.m_ap_type)) + mhidden_description(&youmonst, FALSE, eos(outbuf)); return outbuf; } /* describe a hidden monster; used for look_at during extended monster - detection and for probing */ + detection and for probing; also when looking at self */ void mhidden_description(mon, altmon, outbuf) struct monst *mon; @@ -86,8 +88,10 @@ boolean altmon; /* for probing: if mimicking a monster, say so */ char *outbuf; { struct obj *otmp; - boolean fakeobj; - int x = mon->mx, y = mon->my, glyph = levl[x][y].glyph; + boolean fakeobj, isyou = (mon == &youmonst); + int x = isyou ? u.ux : mon->mx, y = isyou ? u.uy : mon->my, + glyph = (level.flags.hero_memory && !isyou) ? levl[x][y].glyph + : glyph_at(x, y); *outbuf = '\0'; if (mon->m_ap_type == M_AP_FURNITURE @@ -113,7 +117,7 @@ char *outbuf; if (altmon) Sprintf(outbuf, ", masquerading as %s", an(mons[mon->mappearance].mname)); - } else if (mon->mundetected) { + } else if (isyou ? u.uundetected : mon->mundetected) { Strcpy(outbuf, ", hiding"); if (hides_under(mon->data)) { Strcat(outbuf, " under "); @@ -141,10 +145,16 @@ struct obj **obj_p; { boolean fakeobj = FALSE; struct monst *mtmp; - struct obj *otmp = vobj_at(x, y); + struct obj *otmp; int glyphotyp = glyph_to_obj(glyph); *obj_p = (struct obj *) 0; + /* TODO: check inside containers in case glyph came from detection */ + if ((otmp = sobj_at(glyphotyp, x, y)) == 0) + for (otmp = level.buriedobjlist; otmp; otmp = otmp->nobj) + if (otmp->ox == x && otmp->oy == y && otmp->otyp == glyphotyp) + break; + /* there might be a mimic here posing as an object */ mtmp = m_at(x, y); if (mtmp && is_obj_mappear(mtmp, (unsigned) glyphotyp)) @@ -194,7 +204,9 @@ int x, y, glyph; } else Strcpy(buf, something); /* sanity precaution */ - if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR) + if (otmp && otmp->where == OBJ_BURIED) + Strcat(buf, " (buried)"); + else if (levl[x][y].typ == STONE || levl[x][y].typ == SCORR) Strcat(buf, " embedded in stone"); else if (IS_WALL(levl[x][y].typ) || levl[x][y].typ == SDOOR) Strcat(buf, " embedded in a wall"); @@ -229,9 +241,14 @@ int x, y; ? "peaceful " : "", name); - if (u.ustuck == mtmp) - Strcat(buf, (Upolyd && sticks(youmonst.data)) - ? ", being held" : ", holding you"); + if (u.ustuck == mtmp) { + if (u.uswallow || iflags.save_uswallow) /* monster detection */ + Strcat(buf, is_animal(mtmp->data) + ? ", swallowing you" : ", engulfing you"); + else + Strcat(buf, (Upolyd && sticks(youmonst.data)) + ? ", being held" : ", holding you"); + } if (mtmp->mleashed) Strcat(buf, ", leashed to you"); @@ -328,6 +345,7 @@ char *buf, *monbuf; buf[0] = monbuf[0] = '\0'; glyph = glyph_at(x, y); if (u.ux == x && u.uy == y && canspotself() + && !(iflags.save_uswallow && glyph == mon_to_glyph(u.ustuck)) && (!iflags.terrainmode || (iflags.terrainmode & TER_MON) != 0)) { /* fill in buf[] */ (void) self_lookat(buf); @@ -342,7 +360,8 @@ char *buf, *monbuf; (even if you could also see yourself via other means). Sensing self while blind or swallowed is treated as if it were by normal vision (cf canseeself()). */ - if ((Invisible || u.uundetected) && !Blind && !u.uswallow) { + if ((Invisible || u.uundetected) && !Blind + && !(u.uswallow || iflags.save_uswallow)) { unsigned how = 0; if (Infravision) @@ -702,10 +721,15 @@ const char **firstmatch; * submerged will always both be False and skip this code.) */ x_str = 0; - if (looked && (u.uswallow || submerged) && distu(cc.x, cc.y) > 2) { + if (!looked) { + ; /* skip special handling */ + } else if (((u.uswallow || submerged) && distu(cc.x, cc.y) > 2) + /* detection showing some category, so mostly background */ + || ((iflags.terrainmode & (TER_DETECT | TER_MAP)) == TER_DETECT + && glyph == cmap_to_glyph(S_stone))) { x_str = unreconnoitered; need_to_look = FALSE; - } else if (looked && u.uswallow && is_swallow_sym(sym)) { + } else if (is_swallow_sym(sym)) { x_str = mon_interior; need_to_look = TRUE; /* for specific monster type */ } @@ -943,9 +967,7 @@ coord *click_cc; if (quick) { from_screen = TRUE; /* yes, we want to use the cursor */ i = 'y'; - } - - if (i != 'y') { + } else { menu_item *pick_list = (menu_item *) 0; winid win; anything any; diff --git a/src/spell.c b/src/spell.c index a8adba34c..e0abcb541 100644 --- a/src/spell.c +++ b/src/spell.c @@ -1136,6 +1136,7 @@ boolean atme; case SPE_INVISIBILITY: (void) peffects(pseudo); break; + /* end of potion-like spells */ case SPE_CURE_BLINDNESS: healup(0, 0, FALSE, TRUE); @@ -1151,10 +1152,12 @@ boolean atme; (void) make_familiar((struct obj *) 0, u.ux, u.uy, FALSE); break; case SPE_CLAIRVOYANCE: - if (!BClairvoyant) - do_vicinity_map(); + if (!BClairvoyant) { + if (role_skill >= P_SKILLED) + pseudo->blessed = 1; /* detect monsters as well as map */ + do_vicinity_map(pseudo); /* at present, only one thing blocks clairvoyance */ - else if (uarmh && uarmh->otyp == CORNUTHAUM) + } else if (uarmh && uarmh->otyp == CORNUTHAUM) You("sense a pointy hat on top of your %s.", body_part(HEAD)); break; case SPE_PROTECTION: From 8240db344517b1d8c432f69dc1c3eee9e845036d Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Thu, 2 Jun 2016 16:35:21 +0300 Subject: [PATCH 28/81] Show in inventory which monster a leash is attached to --- doc/fixes36.1 | 1 + src/objnam.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index 2e58f8f23..c0c09b019 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -282,6 +282,7 @@ when poly'd into a hider and engulfed, attempt to hide via #monster was blocked but feedback said "can't hide while held" rather than "while engulfed" various monster/object/food/gold/trap detections were inconsistent in how they behaved if performed while engulfed or underwater +show in inventory which monster a leash is attached to Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/objnam.c b/src/objnam.c index fe6b25795..2d07b6ab4 100644 --- a/src/objnam.c +++ b/src/objnam.c @@ -908,7 +908,8 @@ boolean with_price; break; } if (obj->otyp == LEASH && obj->leashmon != 0) { - Strcat(bp, " (in use)"); + Sprintf(eos(bp), " (attached to %s)", + a_monnam(find_mid(obj->leashmon, FM_FMON))); break; } if (obj->otyp == CANDELABRUM_OF_INVOCATION) { From 984785b13c50d813ed6a655deb667fb51ed5245a Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Thu, 2 Jun 2016 18:11:35 +0300 Subject: [PATCH 29/81] Give a hint that containers are looted instead of opened --- include/extern.h | 1 + src/lock.c | 3 +++ src/pickup.c | 3 +-- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/include/extern.h b/include/extern.h index 4b58776f5..e21ba04ec 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1769,6 +1769,7 @@ E int FDECL(query_objlist, (const char *, struct obj **, int, menu_item **, int, boolean (*)(OBJ_P))); E struct obj *FDECL(pick_obj, (struct obj *)); E int NDECL(encumber_msg); +E int FDECL(container_at, (int, int, BOOLEAN_P)); E int NDECL(doloot); E boolean FDECL(container_gone, (int (*)(OBJ_P))); E boolean NDECL(u_handsy); diff --git a/src/lock.c b/src/lock.c index f899f9a0b..6770556a6 100644 --- a/src/lock.c +++ b/src/lock.c @@ -618,6 +618,9 @@ int x, y; There("is no obvious way to open the drawbridge."); else if (portcullis || door->typ == DRAWBRIDGE_DOWN) pline_The("drawbridge is already open."); + else if (container_at(cc.x, cc.y, TRUE)) + pline("%s like something lootable over there.", + Blind ? "Feels" : "Seems"); else You("%s no door there.", Blind ? "feel" : "see"); return res; diff --git a/src/pickup.c b/src/pickup.c index c83bb8deb..7131428e0 100644 --- a/src/pickup.c +++ b/src/pickup.c @@ -39,7 +39,6 @@ STATIC_DCL int FDECL(traditional_loot, (BOOLEAN_P)); STATIC_DCL int FDECL(menu_loot, (int, BOOLEAN_P)); STATIC_DCL char FDECL(in_or_out_menu, (const char *, struct obj *, BOOLEAN_P, BOOLEAN_P, BOOLEAN_P, BOOLEAN_P)); -STATIC_DCL int FDECL(container_at, (int, int, BOOLEAN_P)); STATIC_DCL boolean FDECL(able_to_loot, (int, int, BOOLEAN_P)); STATIC_DCL boolean NDECL(reverse_loot); STATIC_DCL boolean FDECL(mon_beside, (int, int)); @@ -1513,7 +1512,7 @@ encumber_msg() } /* Is there a container at x,y. Optional: return count of containers at x,y */ -STATIC_OVL int +int container_at(x, y, countem) int x, y; boolean countem; From 94a4f57d5373b1bc41f5f84e294e4ff3321dc1ef Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 2 Jun 2016 17:11:13 -0700 Subject: [PATCH 30/81] farlook bullet-proofing When examining a buried object (after detection has revealed it), suppress setting of its dknown bit when hero is adjacent. [That couldn't actually happen, because the glyph on the map that we're trying to examine would be replaced by one for whatever is on the surface when sighted hero moved next to it, and an earlier clause in the same test prevents blinded hero from getting to this point.] --- src/pager.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/pager.c b/src/pager.c index dac2bc3b5..da529f253 100644 --- a/src/pager.c +++ b/src/pager.c @@ -179,6 +179,11 @@ struct obj **obj_p; (corpse type will be known even if dknown is 0, so we don't need a touch check for cockatrice corpse--we're looking without touching) */ if (otmp && distu(x, y) <= 2 && !Blind && !Hallucination + /* redundant: we only look for an object which matches current + glyph among floor and buried objects; when !Blind, any buried + object's glyph will have been replaced by whatever is present + on the surface as soon as we moved next to its spot */ + && otmp->where == OBJ_FLOOR /* not buried */ /* terrain mode views what's already known, doesn't learn new stuff */ && !iflags.terrainmode) /* so don't set dknown when in terrain mode */ otmp->dknown = 1; /* if a pile, clearly see the top item only */ From 358552a5871f6868f2a06dfbfce254d218672d88 Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 2 Jun 2016 18:01:28 -0700 Subject: [PATCH 31/81] comment formatting bit --- src/cmd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cmd.c b/src/cmd.c index d33d3c431..ae2b2bb84 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -3385,12 +3385,12 @@ boolean initial; ++updated; /* phone_layout has been toggled */ for (i = 0; i < 3; i++) { - c = '1' + i; /* 1,2,3 <-> 7,8,9 */ + c = '1' + i; /* 1,2,3 <-> 7,8,9 */ cmdtmp = Cmd.commands[c]; /* tmp = [1] */ Cmd.commands[c] = Cmd.commands[c + 6]; /* [1] = [7] */ Cmd.commands[c + 6] = cmdtmp; /* [7] = tmp */ - c = (M('1') & 0xff) + i; /* M-1,M-2,M-3 <-> M-7,M-8,M-9 */ - cmdtmp = Cmd.commands[c]; /* tmp = [M-1] */ + c = (M('1') & 0xff) + i; /* M-1,M-2,M-3 <-> M-7,M-8,M-9 */ + cmdtmp = Cmd.commands[c]; /* tmp = [M-1] */ Cmd.commands[c] = Cmd.commands[c + 6]; /* [M-1] = [M-7] */ Cmd.commands[c + 6] = cmdtmp; /* [M-7] = tmp */ } From cc839a56c62f85cd12a67e2a80198e7d883496a2 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Fri, 3 Jun 2016 19:33:51 +0300 Subject: [PATCH 32/81] Clear leashmon when leash is generated, even without obj init --- src/mkobj.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mkobj.c b/src/mkobj.c index a6680835e..aa1778a1d 100644 --- a/src/mkobj.c +++ b/src/mkobj.c @@ -872,9 +872,6 @@ boolean artif; case BAG_OF_HOLDING: mkbox_cnts(otmp); break; - case LEASH: - otmp->leashmon = 0; - break; case EXPENSIVE_CAMERA: case TINNING_KIT: case MAGIC_MARKER: @@ -1034,6 +1031,9 @@ boolean artif; /* case TIN: */ set_corpsenm(otmp, otmp->corpsenm); break; + case LEASH: + otmp->leashmon = 0; + break; case SPE_NOVEL: otmp->novelidx = -1; /* "none of the above"; will be changed */ otmp = oname(otmp, noveltitle(&otmp->novelidx)); From 4d594f77c29cc01ce633177b0d67d9ef11add521 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Fri, 3 Jun 2016 21:08:43 +0300 Subject: [PATCH 33/81] Split direction key showing into separate function --- src/cmd.c | 61 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 23 deletions(-) diff --git a/src/cmd.c b/src/cmd.c index ae2b2bb84..475010512 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -183,6 +183,7 @@ static const char *readchar_queue = ""; static coord clicklook_cc; STATIC_DCL char *NDECL(parse); +STATIC_DCL void FDECL(show_direction_keys, (winid, boolean)); STATIC_DCL boolean FDECL(help_dir, (CHAR_P, const char *)); STATIC_PTR int @@ -3790,6 +3791,37 @@ retry: return 1; } +STATIC_OVL void +show_direction_keys(win, nodiag) +winid win; +boolean nodiag; +{ + char buf[BUFSZ]; + + if (nodiag) { + Sprintf(buf, " %c ", Cmd.move_N); + putstr(win, 0, buf); + putstr(win, 0, " | "); + Sprintf(buf, " %c- . -%c", Cmd.move_W, Cmd.move_E); + putstr(win, 0, buf); + putstr(win, 0, " | "); + Sprintf(buf, " %c ", Cmd.move_S); + putstr(win, 0, buf); + } else { + Sprintf(buf, " %c %c %c", Cmd.move_NW, Cmd.move_N, + Cmd.move_NE); + putstr(win, 0, buf); + putstr(win, 0, " \\ | / "); + Sprintf(buf, " %c- . -%c", Cmd.move_W, Cmd.move_E); + putstr(win, 0, buf); + putstr(win, 0, " / | \\ "); + Sprintf(buf, " %c %c %c", Cmd.move_SW, Cmd.move_S, + Cmd.move_SE); + putstr(win, 0, buf); + }; +} + + STATIC_OVL boolean help_dir(sym, msg) char sym; @@ -3828,29 +3860,12 @@ const char *msg; putstr(win, 0, ""); } } - if (NODIAG(u.umonnum)) { - putstr(win, 0, "Valid direction keys in your current form are:"); - Sprintf(buf, " %c ", Cmd.move_N); - putstr(win, 0, buf); - putstr(win, 0, " | "); - Sprintf(buf, " %c- . -%c", Cmd.move_W, Cmd.move_E); - putstr(win, 0, buf); - putstr(win, 0, " | "); - Sprintf(buf, " %c ", Cmd.move_S); - putstr(win, 0, buf); - } else { - putstr(win, 0, "Valid direction keys are:"); - Sprintf(buf, " %c %c %c", Cmd.move_NW, Cmd.move_N, - Cmd.move_NE); - putstr(win, 0, buf); - putstr(win, 0, " \\ | / "); - Sprintf(buf, " %c- . -%c", Cmd.move_W, Cmd.move_E); - putstr(win, 0, buf); - putstr(win, 0, " / | \\ "); - Sprintf(buf, " %c %c %c", Cmd.move_SW, Cmd.move_S, - Cmd.move_SE); - putstr(win, 0, buf); - }; + + Sprintf(buf, "Valid direction keys %sare:", + NODIAG(u.umonnum) ? "in your current form " : ""); + putstr(win, 0, buf); + show_direction_keys(win, NODIAG(u.umonnum)); + putstr(win, 0, ""); putstr(win, 0, " < up"); putstr(win, 0, " > down"); From 5009a672644fbadab73dbf9ec8181a4c07a9bb3c Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 4 Jun 2016 01:06:00 +0300 Subject: [PATCH 34/81] Allow pets to use ranged attacks This is the Pet ranged attack -patch by Darshan Shaligram, with the spellcaster parts removed to keep it simpler. Pets will now throw, spit and breathe at other monsters. --- doc/fixes36.1 | 1 + include/extern.h | 4 + src/dogmove.c | 303 +++++++++++++++++++++++++++++++++++++++++++++++ src/mhitm.c | 49 +++++++- src/mthrowu.c | 262 +++++++++++++++++++++++++++++++++++++++- src/zap.c | 18 ++- 6 files changed, 626 insertions(+), 11 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index c0c09b019..b3d6a6a9f 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -436,6 +436,7 @@ Ray Chason's proper background tiles for lava and water Ray Chason's MS-DOS port restored to functionality with credit to Reddit user b_helyer for the fix to sys/share/pcmain.c Ray Chason's MSDOS port support for some VESA modes +Darshan Shaligram's pet ranged attack Code Cleanup and Reorganization diff --git a/include/extern.h b/include/extern.h index e21ba04ec..fe375b7dd 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1512,6 +1512,9 @@ E int FDECL(breamu, (struct monst *, struct attack *)); E boolean FDECL(linedup, (XCHAR_P, XCHAR_P, XCHAR_P, XCHAR_P, int)); E boolean FDECL(lined_up, (struct monst *)); E struct obj *FDECL(m_carrying, (struct monst *, int)); +E int FDECL(thrwmm, (struct monst *, struct monst *)); +E int FDECL(spitmm, (struct monst *, struct attack *, struct monst *)); +E int FDECL(breamm, (struct monst *, struct attack *, struct monst *)); E void FDECL(m_useupall, (struct monst *, struct obj *)); E void FDECL(m_useup, (struct monst *, struct obj *)); E void FDECL(m_throw, (struct monst *, int, int, int, int, int, struct obj *)); @@ -2797,6 +2800,7 @@ E struct monst *FDECL(boomhit, (struct obj *, int, int)); E int FDECL(zhitm, (struct monst *, int, int, struct obj **)); E int FDECL(burn_floor_objects, (int, int, BOOLEAN_P, BOOLEAN_P)); E void FDECL(buzz, (int, int, XCHAR_P, XCHAR_P, int, int)); +E void FDECL(dobuzz, (int, int, XCHAR_P, XCHAR_P, int, int, boolean)); E void FDECL(melt_ice, (XCHAR_P, XCHAR_P, const char *)); E void FDECL(start_melt_ice_timeout, (XCHAR_P, XCHAR_P, long)); E void FDECL(melt_ice_away, (ANY_P *, long)); diff --git a/src/dogmove.c b/src/dogmove.c index 73cc6f536..e59e7e801 100644 --- a/src/dogmove.c +++ b/src/dogmove.c @@ -11,6 +11,10 @@ extern boolean notonhead; STATIC_DCL boolean FDECL(dog_hunger, (struct monst *, struct edog *)); STATIC_DCL int FDECL(dog_invent, (struct monst *, struct edog *, int)); STATIC_DCL int FDECL(dog_goal, (struct monst *, struct edog *, int, int, int)); +STATIC_DCL struct monst* FDECL(find_targ, (struct monst *, int, int, int)); +STATIC_OVL int FDECL(find_friends, (struct monst *, struct monst *, int)); +STATIC_DCL struct monst* FDECL(best_target, (struct monst *)); +STATIC_DCL long FDECL(score_targ, (struct monst *, struct monst *)); STATIC_DCL boolean FDECL(can_reach_location, (struct monst *, XCHAR_P, XCHAR_P, XCHAR_P, XCHAR_P)); STATIC_DCL boolean FDECL(could_reach_item, (struct monst *, XCHAR_P, XCHAR_P)); @@ -610,6 +614,249 @@ int after, udist, whappr; return appr; } + +STATIC_OVL struct monst * +find_targ(mtmp, dx, dy, maxdist) +register struct monst *mtmp; +int dx, dy; +int maxdist; +{ + struct monst *targ = 0; + int curx = mtmp->mx, cury = mtmp->my; + int dist = 0; + + /* Walk outwards */ + for ( ; dist < maxdist; ++dist) { + curx += dx; + cury += dy; + if (!isok(curx, cury)) + break; + + /* FIXME: Check if we hit a wall/door/boulder to + * short-circuit unnecessary subsequent checks + */ + + /* If we can't see up to here, forget it - will this + * mean pets in corridors don't breathe at monsters + * in rooms? If so, is that necessarily bad? + */ + if (!m_cansee(mtmp, curx, cury)) + break; + + targ = m_at(curx, cury); + + if (curx == mtmp->mux && cury == mtmp->muy) + return &youmonst; + + if (targ) { + /* Is the monster visible to the pet? */ + if ((!targ->minvis || perceives(mtmp->data)) && + !targ->mundetected) + break; + + /* If the pet can't see it, it assumes it aint there */ + targ = 0; + } + } + return targ; +} + +STATIC_OVL int +find_friends(mtmp, mtarg, maxdist) +struct monst *mtmp, *mtarg; +int maxdist; +{ + struct monst *pal; + int dx = sgn(mtarg->mx - mtmp->mx), + dy = sgn(mtarg->my - mtmp->my); + int curx = mtarg->mx, cury = mtarg->my; + int dist = distmin(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my); + + for ( ; dist <= maxdist; ++dist) { + curx += dx; + cury += dy; + + if (!isok(curx, cury)) + return 0; + + /* If the pet can't see beyond this point, don't + * check any farther + */ + if (!m_cansee(mtmp, curx, cury)) + return 0; + + /* Does pet think you're here? */ + if (mtmp->mux == curx && mtmp->muy == cury) + return 1; + + pal = m_at(curx, cury); + + if (pal) { + if (pal->mtame) { + /* Pet won't notice invisible pets */ + if (!pal->minvis || perceives(mtmp->data)) + return 1; + } else { + /* Quest leaders and guardians are always seen */ + if (pal->data->msound == MS_LEADER + || pal->data->msound == MS_GUARDIAN) + return 1; + } + } + } + return 0; +} + + +STATIC_OVL long +score_targ(mtmp, mtarg) +struct monst *mtmp, *mtarg; +{ + long score = 0L; + + /* If the monster is confused, normal scoring is disrupted - + * anything may happen + */ + + /* Give 1 in 3 chance of safe breathing even if pet is confused or + * if you're on the quest start level */ + if (!mtmp->mconf || !rn2(3) || Is_qstart(&u.uz)) { + aligntyp align1, align2; /* For priests, minions */ + boolean faith1 = TRUE, faith2 = TRUE; + + if (mtmp->isminion) align1 = EMIN(mtmp)->min_align; + else if (mtmp->ispriest) align1 = EPRI(mtmp)->shralign; + else faith1 = FALSE; + if (mtarg->isminion) align2 = EMIN(mtarg)->min_align; /* MAR */ + else if (mtarg->ispriest) align2 = EPRI(mtarg)->shralign; /* MAR */ + else faith2 = FALSE; + + /* Never target quest friendlies */ + if (mtarg->data->msound == MS_LEADER + || mtarg->data->msound == MS_GUARDIAN) + return -5000L; + + /* D: Fixed angelic beings using gaze attacks on coaligned priests */ + if (faith1 && faith2 && align1 == align2 && mtarg->mpeaceful) { + score -= 5000L; + return score; + } + + /* Is monster adjacent? */ + if (distmin(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) <= 1) { + score -= 3000L; + return score; + } + + /* Is the monster peaceful or tame? */ + if (/*mtarg->mpeaceful ||*/ mtarg->mtame || mtarg == &youmonst) { + /* Pets will never be targeted */ + score -= 3000L; + return score; + } + + /* Is master/pet behind monster? Check up to 15 squares beyond + * pet. + */ + if (find_friends(mtmp, mtarg, 15)) { + score -= 3000L; + return score; + } + + /* Target hostile monsters in preference to peaceful ones */ + if (!mtarg->mpeaceful) + score += 10; + + /* Is the monster passive? Don't waste energy on it, if so */ + if (mtarg->data->mattk[0].aatyp == AT_NONE) + score -= 1000; + + /* Even weak pets with breath attacks shouldn't take on very + * low-level monsters. Wasting breath on lichens is ridiculous. + */ + if ((mtarg->m_lev < 2 && mtmp->m_lev > 5) + || (mtmp->m_lev > 12 && mtarg->m_lev < mtmp->m_lev - 9 + && u.ulevel > 8 && mtarg->m_lev < u.ulevel - 7)) + score -= 25; + + /* And pets will hesitate to attack vastly stronger foes. + * This penalty will be discarded if master's in trouble. + */ + if (mtarg->m_lev > mtmp->m_lev + 4L) + score -= (mtarg->m_lev - mtmp->m_lev) * 20L; + + /* All things being the same, go for the beefiest monster. This + * bonus should not be large enough to override the pet's aversion + * to attacking much stronger monsters. + */ + score += mtarg->m_lev * 2 + mtarg->mhp / 3; + } + + /* Fuzz factor to make things less predictable when very + * similar targets are abundant + */ + score += rnd(5); + + /* Pet may decide not to use ranged attack when confused */ + if (mtmp->mconf && !rn2(3)) + score -= 1000; + + return score; +} + + +STATIC_OVL struct monst * +best_target(mtmp) +struct monst *mtmp; /* Pet */ +{ + int dx, dy; + long bestscore = -40000L, currscore; + struct monst *best_targ = 0, *temp_targ = 0; + + /* Help! */ + if (!mtmp) + return 0; + + /* If the pet is blind, it's not going to see any target */ + if (!mtmp->mcansee) + return 0; + + /* Search for any monsters lined up with the pet, within an arbitrary + * distance from the pet (7 squares, even along diagonals). Monsters + * are assigned scores and the best score is chosen. + */ + for (dy = -1; dy < 2; ++dy) { + for (dx = -1; dx < 2; ++dx) { + if (!dx && !dy) + continue; + /* Traverse the line to find the first monster within 7 + * squares. Invisible monsters are skipped (if the + * pet doesn't have see invisible). + */ + temp_targ = find_targ(mtmp, dx, dy, 7); + + /* Nothing in this line? */ + if (!temp_targ) + continue; + + /* Decide how attractive the target is */ + currscore = score_targ(mtmp, temp_targ); + + if (currscore > bestscore) { + bestscore = currscore; + best_targ = temp_targ; + } + } + } + + /* Filter out targets the pet doesn't like */ + if (bestscore < 0L) + best_targ = 0; + + return best_targ; +} + + /* return 0 (no move), 1 (move) or 2 (dead) */ int dog_move(mtmp, after) @@ -872,6 +1119,62 @@ int after; /* this is extra fast monster movement */ nxti: ; } + + /* Pet hasn't attacked anything but is considering moving - + * now's the time for ranged attacks. Note that the pet can + * move after it performs its ranged attack. Should this be + * changed? + */ + { + struct monst *mtarg; + int hungry = 0; + + /* How hungry is the pet? */ + if (!mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + hungry = (monstermoves > (dog->hungrytime + 300)); + } + + /* Identify the best target in a straight line from the pet; + * if there is such a target, we'll let the pet attempt an + * attack. + */ + mtarg = best_target(mtmp); + + /* Hungry pets are unlikely to use breath/spit attacks */ + if (mtarg && (!hungry || !rn2(5))) { + int mstatus; + + if (mtarg == &youmonst) { + if (mattacku(mtmp)) + return 2; + } else { + mstatus = mattackm(mtmp, mtarg); + + /* Shouldn't happen, really */ + if (mstatus & MM_AGR_DIED) return 2; + + /* Allow the targeted nasty to strike back - if + * the targeted beast doesn't have a ranged attack, + * nothing will happen. + */ + if ((mstatus & MM_HIT) && !(mstatus & MM_DEF_DIED) + && rn2(4) && mtarg != &youmonst) { + + /* Can monster see? If it can, it can retaliate + * even if the pet is invisible, since it'll see + * the direction from which the ranged attack came; + * if it's blind or unseeing, it can't retaliate + */ + if (mtarg->mcansee && haseyes(mtarg->data)) { + mstatus = mattackm(mtarg, mtmp); + if (mstatus & MM_DEF_DIED) return 2; + } + } + } + } + } + newdogpos: if (nix != omx || niy != omy) { boolean wasseen; diff --git a/src/mhitm.c b/src/mhitm.c index b52c6f226..e4c05897b 100644 --- a/src/mhitm.c +++ b/src/mhitm.c @@ -356,6 +356,17 @@ register struct monst *magr, *mdef; attk = 1; switch (mattk->aatyp) { case AT_WEAP: /* "hand to hand" attacks */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) { + /* D: Do a ranged attack here! */ + strike = thrwmm(magr, mdef); + if (DEADMONSTER(mdef)) + res[i] = MM_DEF_DIED; + + if (DEADMONSTER(magr)) + res[i] |= MM_AGR_DIED; + + break; + } if (magr->weapon_check == NEED_WEAPON || !MON_WEP(magr)) { magr->weapon_check = NEED_HTH_WEAPON; if (mon_wield_item(magr) != 0) @@ -379,7 +390,9 @@ register struct monst *magr, *mdef; case AT_TENT: /* Nymph that teleported away on first attack? */ if (distmin(magr->mx, magr->my, mdef->mx, mdef->my) > 1) - return MM_MISS; + /* Continue because the monster may have a ranged + * attack */ + continue; /* Monsters won't attack cockatrices physically if they * have a weapon instead. This instinct doesn't work for * players, or under conflict or confusion. @@ -428,6 +441,10 @@ register struct monst *magr, *mdef; break; case AT_EXPL: + /* D: Prevent explosions from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; + res[i] = explmm(magr, mdef, mattk); if (res[i] == MM_MISS) { /* cancelled--no attack */ strike = 0; @@ -441,6 +458,9 @@ register struct monst *magr, *mdef; strike = 0; break; } + /* D: Prevent engulf from a distance */ + if (distmin(magr->mx,magr->my,mdef->mx,mdef->my) > 1) + continue; /* Engulfing attacks are directed at the hero if * possible. -dlc */ @@ -454,13 +474,38 @@ register struct monst *magr, *mdef; } break; + case AT_BREA: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = breamm(magr, mattk, mdef); + + /* We don't really know if we hit or not, but pretend + * we did */ + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + } + else + strike = 0; + break; + case AT_SPIT: + if (!monnear(magr, mdef->mx, mdef->my)) { + strike = spitmm(magr, mattk, mdef); + + /* We don't really know if we hit or not, but pretend + * we did */ + if (strike) res[i] |= MM_HIT; + if (DEADMONSTER(mdef)) res[i] = MM_DEF_DIED; + if (DEADMONSTER(magr)) res[i] |= MM_AGR_DIED; + } + break; default: /* no attack */ strike = 0; attk = 0; break; } - if (attk && !(res[i] & MM_AGR_DIED)) + if (attk && !(res[i] & MM_AGR_DIED) + && distmin(magr->mx,magr->my,mdef->mx,mdef->my) <= 1) res[i] = passivemm(magr, mdef, strike, res[i] & MM_DEF_DIED); if (res[i] & MM_DEF_DIED) diff --git a/src/mthrowu.c b/src/mthrowu.c index fbf9d08cf..1d9477f7c 100644 --- a/src/mthrowu.c +++ b/src/mthrowu.c @@ -5,12 +5,15 @@ #include "hack.h" STATIC_DCL int FDECL(drop_throw, (struct obj *, BOOLEAN_P, int, int)); +STATIC_DCL boolean FDECL(m_lined_up, (struct monst *, struct monst *)); #define URETREATING(x, y) \ (distmin(u.ux, u.uy, x, y) > distmin(u.ux0, u.uy0, x, y)) #define POLE_LIM 5 /* How far monsters can use pole-weapons */ +#define PET_MISSILE_RANGE2 36 /* Square of distance within which pets shoot */ + /* * Keep consistent with breath weapons in zap.c, and AD_* in monattk.h. */ @@ -129,6 +132,11 @@ int x, y; return retvalu; } +/* The monster that's being shot at when one monster shoots at another */ +STATIC_OVL struct monst *target = 0; +/* The monster that's doing the shooting/throwing */ +STATIC_OVL struct monst *archer = 0; + /* an object launched by someone/thing other than player attacks a monster; return 1 if the object has stopped moving (hit or its range used up) */ int @@ -143,17 +151,27 @@ boolean verbose; /* give message(s) even when you can't see what happened */ int damage, tmp; boolean vis, ismimic; int objgone = 1; + struct obj *mon_launcher = archer ? MON_WEP(archer) : NULL; notonhead = (bhitpos.x != mtmp->mx || bhitpos.y != mtmp->my); ismimic = mtmp->m_ap_type && mtmp->m_ap_type != M_AP_MONSTER; vis = cansee(bhitpos.x, bhitpos.y); tmp = 5 + find_mac(mtmp) + omon_adj(mtmp, otmp, FALSE); + /* High level monsters will be more likely to hit */ + /* This check applies only if this monster is the target + * the archer was aiming at. */ + if (archer && target == mtmp) { + if (archer->m_lev > 5) + tmp += archer->m_lev - 5; + if (mon_launcher && mon_launcher->oartifact) + tmp += spec_abon(mon_launcher, mtmp); + } if (tmp < rnd(20)) { if (!ismimic) { if (vis) miss(distant_name(otmp, mshot_xname), mtmp); - else if (verbose) + else if (verbose && !target) pline("It is missed."); } if (!range) { /* Last position; object drops */ @@ -177,7 +195,7 @@ boolean verbose; /* give message(s) even when you can't see what happened */ mtmp->msleeping = 0; if (vis) hit(distant_name(otmp, mshot_xname), mtmp, exclam(damage)); - else if (verbose) + else if (verbose && !target) pline("%s is hit%s", Monnam(mtmp), exclam(damage)); if (otmp->opoisoned && is_poisonable(otmp)) { @@ -199,24 +217,24 @@ boolean verbose; /* give message(s) even when you can't see what happened */ && mon_hates_silver(mtmp)) { if (vis) pline_The("silver sears %s flesh!", s_suffix(mon_nam(mtmp))); - else if (verbose) + else if (verbose && !target) pline("Its flesh is seared!"); } if (otmp->otyp == ACID_VENOM && cansee(mtmp->mx, mtmp->my)) { if (resists_acid(mtmp)) { - if (vis || verbose) + if (vis || (verbose && !target)) pline("%s is unaffected.", Monnam(mtmp)); damage = 0; } else { if (vis) pline_The("acid burns %s!", mon_nam(mtmp)); - else if (verbose) + else if (verbose && !target) pline("It is burned!"); } } mtmp->mhp -= damage; if (mtmp->mhp < 1) { - if (vis || verbose) + if (vis || (verbose && !target)) pline("%s is %s!", Monnam(mtmp), (nonliving(mtmp->data) || is_vampshifter(mtmp) || !canspotmon(mtmp)) @@ -482,6 +500,230 @@ struct obj *obj; /* missile (or stack providing it) */ } } +int +thrwmm(mtmp, mtarg) /* Monster throws item at monster */ +struct monst *mtmp, *mtarg; +{ + struct obj *otmp, *mwep; + register xchar x, y; + boolean ispole; + schar skill; + int multishot = 1; + + /* Polearms won't be applied by monsters against other monsters */ + if (mtmp->weapon_check == NEED_WEAPON || !MON_WEP(mtmp)) { + mtmp->weapon_check = NEED_RANGED_WEAPON; + /* mon_wield_item resets weapon_check as appropriate */ + if(mon_wield_item(mtmp) != 0) return 0; + } + + /* Pick a weapon */ + otmp = select_rwep(mtmp); + if (!otmp) return 0; + ispole = is_pole(otmp); + skill = objects[otmp->otyp].oc_skill; + + x = mtmp->mx; + y = mtmp->my; + + mwep = MON_WEP(mtmp); /* wielded weapon */ + + if(!ispole && m_lined_up(mtarg, mtmp)) { + /* WAC Catch this since rn2(0) is illegal */ + int chance = (BOLT_LIM-distmin(x,y,mtarg->mx,mtarg->my) > 0) ? + BOLT_LIM-distmin(x,y,mtarg->mx,mtarg->my) : 1; + + if(!mtarg->mflee || !rn2(chance)) { + const char *verb = "throws"; + + if (otmp->otyp == ARROW + || otmp->otyp == ELVEN_ARROW + || otmp->otyp == ORCISH_ARROW + || otmp->otyp == YA + || otmp->otyp == CROSSBOW_BOLT) verb = "shoots"; + + if (ammo_and_launcher(otmp, mwep) && is_launcher(mwep)) { + if (dist2(mtmp->mx, mtmp->my, mtarg->mx, mtarg->my) > + PET_MISSILE_RANGE2) + return 0; /* Out of range */ + } + + if (canseemon(mtmp)) { + pline("%s %s %s!", Monnam(mtmp), verb, + obj_is_pname(otmp) ? + the(singular(otmp, xname)) : + an(singular(otmp, xname))); + } + + /* Multishot calculations */ + if ((ammo_and_launcher(otmp, mwep) || skill == P_DAGGER + || skill == -P_DART || skill == -P_SHURIKEN) + && !mtmp->mconf) { + /* Assumes lords are skilled, princes are expert */ + if (is_lord(mtmp->data)) multishot++; + if (is_prince(mtmp->data)) multishot += 2; + + /* Elven Craftsmanship makes for light, quick bows */ + if (otmp->otyp == ELVEN_ARROW && !otmp->cursed) + multishot++; + if (mwep && mwep->otyp == ELVEN_BOW && + !mwep->cursed) multishot++; + /* 1/3 of object enchantment */ + if (mwep && mwep->spe > 1) + multishot += (long) rounddiv(mwep->spe,3); + /* Some randomness */ + if (multishot > 1L) + multishot = (long) rnd((int) multishot); + + switch (monsndx(mtmp->data)) { + case PM_RANGER: + multishot++; + break; + case PM_ROGUE: + if (skill == P_DAGGER) multishot++; + break; + case PM_SAMURAI: + if (otmp->otyp == YA && mwep && + mwep->otyp == YUMI) multishot++; + break; + default: + break; + } + { /* racial bonus */ + if (is_elf(mtmp->data) && + otmp->otyp == ELVEN_ARROW && + mwep && mwep->otyp == ELVEN_BOW) + multishot++; + else if (is_orc(mtmp->data) && + otmp->otyp == ORCISH_ARROW && + mwep && mwep->otyp == ORCISH_BOW) + multishot++; + } + + } + if (otmp->quan < multishot) multishot = (int)otmp->quan; + if (multishot < 1) multishot = 1; + + /* Set target monster */ + target = mtarg; + archer = mtmp; + while (multishot-- > 0) + m_throw(mtmp, mtmp->mx, mtmp->my, + sgn(tbx), sgn(tby), + distmin(mtmp->mx, mtmp->my, + mtarg->mx, mtarg->my), + otmp); + archer = (struct monst *)0; + target = (struct monst *)0; + nomul(0); + return 1; + } + } + return 0; +} + + +/* monster spits substance at monster */ +int +spitmm(mtmp, mattk, mtarg) +struct monst *mtmp, *mtarg; +struct attack *mattk; +{ + struct obj *otmp; + + if (mtmp->mcan) { + if (!Deaf) + pline("A dry rattle comes from %s throat.", + s_suffix(mon_nam(mtmp))); + return 0; + } + if (m_lined_up(mtarg, mtmp)) { + switch (mattk->adtyp) { + case AD_BLND: + case AD_DRST: + otmp = mksobj(BLINDING_VENOM, TRUE, FALSE); + break; + default: + impossible("bad attack type in spitmu"); + /* fall through */ + case AD_ACID: + otmp = mksobj(ACID_VENOM, TRUE, FALSE); + break; + } + if (!rn2(BOLT_LIM-distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my))) { + if (canseemon(mtmp)) + pline("%s spits venom!", Monnam(mtmp)); + target = mtarg; + m_throw(mtmp, mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), + distmin(mtmp->mx,mtmp->my,mtarg->mx,mtarg->my), otmp); + target = (struct monst *)0; + nomul(0); + + /* If this is a pet, it'll get hungry. Minions and + * spell beings won't hunger */ + if (mtmp->mtame && !mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + + /* Hunger effects will catch up next move */ + if (dog->hungrytime > 1) + dog->hungrytime -= 5; + } + + return 1; + } + } + return 0; +} + +int +breamm(mtmp, mattk, mtarg) /* monster breathes at monster (ranged) */ +struct monst *mtmp, *mtarg; +struct attack *mattk; +{ + /* if new breath types are added, change AD_ACID to max type */ + int typ = (mattk->adtyp == AD_RBRE) ? rnd(AD_ACID) : mattk->adtyp ; + + if (m_lined_up(mtarg, mtmp)) { + if (mtmp->mcan) { + if (!Deaf) { + if (canseemon(mtmp)) + pline("%s coughs.", Monnam(mtmp)); + else + You_hear("a cough."); + } + return(0); + } + if (!mtmp->mspec_used && rn2(3)) { + if ((typ >= AD_MAGM) && (typ <= AD_ACID)) { + if (canseemon(mtmp)) + pline("%s breathes %s!", Monnam(mtmp), + breathwep[typ-1]); + dobuzz((int) (-20 - (typ-1)), (int)mattk->damn, + mtmp->mx, mtmp->my, sgn(tbx), sgn(tby), FALSE); + nomul(0); + /* breath runs out sometimes. Also, give monster some + * cunning; don't breath if the target fell asleep. + */ + mtmp->mspec_used = 6+rn2(18); + + /* If this is a pet, it'll get hungry. Minions and + * spell beings won't hunger */ + if (mtmp->mtame && !mtmp->isminion) { + struct edog *dog = EDOG(mtmp); + + /* Hunger effects will catch up next move */ + if (dog->hungrytime >= 10) + dog->hungrytime -= 10; + } + } else impossible("Breath weapon %d used", typ-1); + } else + return (0); + } + return(1); +} + + + /* remove an entire item from a monster's inventory; destroy that item */ void m_useupall(mon, obj) @@ -803,6 +1045,14 @@ int boulderhandling; /* 0=block, 1=ignore, 2=conditionally block */ return FALSE; } +STATIC_OVL boolean +m_lined_up(mtarg, mtmp) +struct monst *mtarg, *mtmp; +{ + return (linedup(mtarg->mx, mtarg->my, mtmp->mx, mtmp->my, 0)); +} + + /* is mtmp in position to use ranged attack? */ boolean lined_up(mtmp) diff --git a/src/zap.c b/src/zap.c index 069d89f67..560f9f686 100644 --- a/src/zap.c +++ b/src/zap.c @@ -3869,6 +3869,15 @@ const char *fltxt; xkilled(mon, XKILL_NOMSG | XKILL_NOCORPSE); } +void +buzz(type,nd,sx,sy,dx,dy) +int type, nd; +xchar sx,sy; +int dx,dy; +{ + dobuzz(type, nd, sx, sy, dx, dy, TRUE); +} + /* * type == 0 to 9 : you shooting a wand * type == 10 to 19 : you casting a spell @@ -3879,10 +3888,11 @@ const char *fltxt; * called with dx = dy = 0 with vertical bolts */ void -buzz(type, nd, sx, sy, dx, dy) +dobuzz(type, nd, sx, sy, dx, dy,say) register int type, nd; register xchar sx, sy; register int dx, dy; +boolean say; /* Announce out of sight hit/miss events if true */ { int range, abstype = abs(type) % 10; register xchar lsx, lsy; @@ -4009,7 +4019,8 @@ register int dx, dy; } else { if (!otmp) { /* normal non-fatal hit */ - hit(fltxt, mon, exclam(tmp)); + if (say || canseemon(mon)) + hit(fltxt, mon, exclam(tmp)); } else { /* some armor was destroyed; no damage done */ if (canseemon(mon)) @@ -4024,7 +4035,8 @@ register int dx, dy; } range -= 2; } else { - miss(fltxt, mon); + if (say || canseemon(mon)) + miss(fltxt, mon); } } else if (sx == u.ux && sy == u.uy && range >= 0) { nomul(0); From b4c14f0c34ac3d4b8f0d2fa4a305b15d7afba914 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 4 Jun 2016 08:01:55 +0300 Subject: [PATCH 35/81] Fix #H4369: what-is reporting singular of null when given only spaces --- src/pager.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pager.c b/src/pager.c index da529f253..081bd190e 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1060,6 +1060,7 @@ coord *click_cc; case '?': from_screen = FALSE; getlin("Specify what? (type the word)", out_str); + mungspaces(out_str); if (out_str[0] == '\0' || out_str[0] == '\033') return 0; From ad9e3dcef91ed256d316610ca0f57e1acd6b8aac Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 4 Jun 2016 08:14:41 +0300 Subject: [PATCH 36/81] Comment the limitation and reason --- src/pager.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pager.c b/src/pager.c index 081bd190e..92c72787d 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1060,6 +1060,8 @@ coord *click_cc; case '?': from_screen = FALSE; getlin("Specify what? (type the word)", out_str); + /* mungspaces prevents querying for a space glyph (eg. a ghost), + but players almost always use '/' instead to look up glyphs */ mungspaces(out_str); if (out_str[0] == '\0' || out_str[0] == '\033') return 0; From c36bc0043c80fee6c40b1d4f217494d4916104e1 Mon Sep 17 00:00:00 2001 From: Pasi Kallinen Date: Sat, 4 Jun 2016 12:43:09 +0300 Subject: [PATCH 37/81] Improve the Guidebook config file section --- doc/Guidebook.mn | 144 ++++++++++++++++++++++++++----------- doc/Guidebook.tex | 179 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 236 insertions(+), 87 deletions(-) diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index fb73c4de2..b76fa5a69 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -1969,12 +1969,111 @@ Setting the options .pg Options may be set in a number of ways. Within the game, the `O' command allows you to view all options and change most of them. -You can also set options automatically by placing them in the -NETHACKOPTIONS environment variable or in a configuration file. +You can also set options automatically by placing them in a configuration +file, or in the NETHACKOPTIONS environment variable. Some versions of NetHack also have front-end programs that allow you to set options before starting the game or a global configuration for system administrators. .hn 2 +Using a configuration file +.pg +The default name of the configuration file varies on different +operating systems. On DOS and Windows, it is ``defaults.nh'' +in the same folder as nethack.exe or nethackW.exe. On Unix, Linux +and Mac OS X it is ``.nethackrc'' in the user's home directory. +The file may not exist, but it is a normal ASCII text file and +can be created with any text editor. +.pg +Any line in the configuration file starting with `#' is treated as a comment. +Empty lines are ignored. +.pg +You can use different configuration statements in the file, some +of which can be used multiple times. In general, the statements are +written in capital letters, followed by an equals sign, followed by +settings particular to that statement. Here is a list of allowed statements: +.lp OPTIONS +There are two types of options, boolean and compound options. +Boolean options toggle a setting on or off, while compound options +take more diverse values. +Prefix a boolean option with `no' or `!' to turn it off. +For compound options, the option name and value are separated by a colon. +Some options are persistent, and apply only to new games. +You can specify multiple OPTIONS statements, and multiple options +in a single OPTIONS statement. +.pg +Example: +.sd +\fBOPTIONS=dogname:Fido\fP +\fBOPTIONS=!legacy,autopickup,pickup_types:$"=/!?+\fP +.ed +.lp HACKDIR +Default location of files NetHack needs. On Windows HACKDIR +defaults to the location of the NetHack.exe or NetHackw.exe file +so setting HACKDIR to override that is not usually necessary or recommended. +.lp LEVELDIR +The location that in-progress level files are stored. Defaults to HACKDIR, +must be writeable. +.lp SAVEDIR +The location where saved games are kept. Defaults to HACKDIR, must be +writeable. +.lp BONESDIR +The location that bones files are kept. Defaults to HACKDIR, must be +writeable. +.lp LOCKDIR +The location that file synchronization locks are stored. Defaults to +HACKDIR, must be writeable. +.lp TROUBLEDIR +The location that a record of game aborts and self-diagnosed game problems +is kept. Defaults to HACKDIR, must be writeable. +.lp AUTOPICKUP_EXCEPTION +Set exceptions to the +.op pickup_types +option. See the ``Configuring Autopickup Exceptions'' section. +.lp MSGTYPE +Change the way messages are shown in the top status line. +See the ``Configuring Message Types`` section. +.lp MENUCOLOR +Highlight menu lines with different colors. +See the ``Configuring Menu Colors`` section. +.lp SYMBOLS +Override one or more symbols in the symbols files. +See the ``Modifying NetHack Symbols'' section. +.pg +Example: +.sd +\fBSYMBOLS=S_boulder:0\fP +.ed +.lp WIZKIT +Wizard-mode extra items, in a text file containing item names, +one per line, up to a maximum of 128 lines. Each line is processed +by the function that handles wishing. +.pg +Example: +.sd +\fBWIZKIT=~/wizkit.txt\fP +.ed +.lp SOUNDDIR +Define the directory that contains the sound files. +See the ``Configuring User Sounds'' section. +.lp SOUND +Define a sound mapping. See the ``Configuring User Sounds'' section. +.pg +Here is a short example of config file contents: +.sd +\fB# Set your character's role, race, gender, and alignment.\fP +\fBOPTIONS=role:Valkyrie, race:Human, gender:female, align:lawful\fP + +\fB# Turn on autopickup, and set automatically picked up object types\fP +\fBOPTIONS=autopickup,pickup_types:$"=/!?+\fP +\fB# Show colored text if possible\fP +\fBOPTIONS=color\fP +\fB# Show lit corridors differently\fP +\fBOPTIONS=lit_corridor\fP + +\fB# No startup splash screen. Windows GUI only.\fP +\fBOPTIONS=!splash_screen\fP +.ed +.hn 2 Using the NETHACKOPTIONS environment variable .pg The NETHACKOPTIONS variable is a comma-separated list of initial @@ -1997,48 +2096,9 @@ $ \fBNETHACKOPTIONS="autoquiver,!autopickup,name:Blue Meanie,fruit:papaya"\fP $ \fBexport NETHACKOPTIONS\fP .ed in \fIsh\fP or \fIksh\fP. -.hn 2 -Using a configuration file .pg -Any line in the configuration file starting with `#' is treated as a comment. -Any line in the configuration file starting with ``OPTIONS='' may be -filled out with options in the same syntax as in NETHACKOPTIONS. -Any line starting with ``SYMBOLS='' -is taken as defining the corresponding symbol -in a different syntax, a sequence of decimal numbers giving -the character position in the current font to be used in displaying -each entry. Such a sequence can be continued to multiple lines by -putting a `\e' -at the end of each line to be continued. -.pg -Any line starting with ``AUTOPICKUP_EXCEPTION='' is taken -as defining an exception to the -.op pickup_types -option. -There is a section of this Guidebook that discusses that. -.pg -The default name of the configuration file varies on different -operating systems. On DOS and Windows, it is ``defaults.nh'' -in the same folder as nethack.exe or nethackW.exe. On Unix, Linux -and Mac OS X it is ``.nethackrc'' in the user's home directory. -NETHACKOPTIONS can also be set to the full name of a file you +NETHACKOPTIONS can also be set to the full name of a configuration file you want to use (possibly preceded by an `@'). -.pg -Here is a short example of config file contents: -.sd -\fB# Set your character's role, race, gender, and alignment.\fP -\fBOPTIONS=role:Valkyrie, race:Human, gender:female, align:lawful\fP - -\fB# Turn on autopickup, and set automatically picked up object types\fP -\fBOPTIONS=autopickup,pickup_types:$"=/!?+\fP -\fB# Show colored text if possible\fP -\fBOPTIONS=color\fP -\fB# Show lit corridors differently\fP -\fBOPTIONS=lit_corridor\fP - -\fB# No startup splash screen. Windows GUI only.\fP -\fBOPTIONS=!splash_screen\fP -.ed .hn 2 Customization options .pg diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 55c9960bc..54dc5a542 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -2360,12 +2360,143 @@ behaves. %.pg Options may be set in a number of ways. Within the game, the `{\tt O}' command allows you to view all options and change most of them. -You can also set options automatically by placing them in the -``NETHACKOPTIONS'' environment variable or in a configuration file. +You can also set options automatically by placing them in a configuration +file, or in the ``NETHACKOPTIONS'' environment variable. Some versions of {\it NetHack\/} also have front-end programs that allow you to set options before starting the game or a global configuration for system administrators. +%.hn 2 +\subsection*{Using a configuration file} + +%.pg +The default name of the configuration file varies on different +operating systems. On DOS and Windows, it is ``defaults.nh'' +in the same folder as nethack.exe or nethackW.exe. On Unix, Linux +and Mac OS X it is ``.nethackrc'' in the user's home directory. +The file may not exist, but it is a normal ASCII text file and +can be created with any text editor. + +%.pg +Any line in the configuration file starting with `{\tt \#}' is treated as a comment. +Empty lines are ignore. + +%.pg +You can use different configuration statements in the file, some +of which can be used multiple times. In general, the statements are +written in capital letters, followed by an equals sign, followed by +settings particular to that statement. Here is a list of allowed statements: + +%.lp +\blist{} +\item[\bb{OPTIONS}] +There are two types of options, boolean and compound options. +Boolean options toggle a setting on or off, while compound options +take more diverse values. +Prefix a boolean option with `no' or `!' to turn it off. +For compound options, the option name and value are separated by a colon. +Some options are persistent, and apply only to new games. +You can specify multiple OPTIONS statements, and multiple options +in a single OPTIONS statement. + +%.pg +Example: +%.sd +\begin{verbatim} + OPTIONS=dogname:Fido + OPTIONS=!legacy,autopickup,pickup_types:$"=/!?+ +\end{verbatim} +%.ed + +%.lp +\item[\bb{HACKDIR}] +Default location of files NetHack needs. On Windows HACKDIR +defaults to the location of the NetHack.exe or NetHackw.exe file +so setting HACKDIR to override that is not usually necessary or recommended. +%.lp +\item[\bb{LEVELDIR}] +The location that in-progress level files are stored. Defaults to HACKDIR, +must be writeable. +%.lp +\item[\bb{SAVEDIR}] +The location where saved games are kept. Defaults to HACKDIR, must be +writeable. +%.lp +\item[\bb{BONESDIR}] +The location that bones files are kept. Defaults to HACKDIR, must be +writeable. +%.lp +\item[\bb{LOCKDIR}] +The location that file synchronization locks are stored. Defaults to +HACKDIR, must be writeable. +%.lp +\item[\bb{TROUBLEDIR}] +The location that a record of game aborts and self-diagnosed game problems +is kept. Defaults to HACKDIR, must be writeable. +%.lp +\item[\bb{AUTOPICKUP\_EXCEPTION}] +Set exceptions to the {{\it pickup\_types\/}} +option. See the ``Configuring Autopickup Exceptions'' section. +%.lp +\item[\bb{MSGTYPE}] +Change the way messages are shown in the top status line. +See the ``Configuring Message Types`` section. +%.lp +\item[\bb{MENUCOLOR}] +Highlight menu lines with different colors. +See the ``Configuring Menu Colors`` section. +%.lp +\item[\bb{SYMBOLS}] +Override one or more symbols in the symbols files. +See the ``Modifying NetHack Symbols'' section. +%.pg +Example: +%.sd +\begin{verbatim} + SYMBOLS=S_boulder:0 +\end{verbatim} +%.ed + +%.lp +\item[\bb{WIZKIT}] +Wizard-mode extra items, in a text file containing item names, +one per line, up to a maximum of 128 lines. Each line is processed +by the function that handles wishing. +%.pg +Example: +%.sd +\begin{verbatim} + WIZKIT=~/wizkit.txt +\end{verbatim} +%.ed +%.lp +\item[\bb{SOUNDDIR}] +Define the directory that contains the sound files. +See the ``Configuring User Sounds'' section. +%.lp +\item[\bb{SOUND}] +Define a sound mapping. See the ``Configuring User Sounds'' section. +\elist + +%.pg +Here is a short example of config file contents: +%.sd +\begin{verbatim} + # Set your character's role, race, gender, and alignment. + OPTIONS=role:Valkyrie, race:Human, gender:female, align:lawful + + # Turn on autopickup, and set automatically picked up object types + OPTIONS=autopickup,pickup_types:$"=/!?+ + # Show colored text if possible + OPTIONS=color + # Show lit corridors differently + OPTIONS=lit_corridor + + # No startup splash screen. Windows GUI only. + OPTIONS=!splash_screen +\end{verbatim} +%.ed + %.hn 2 \subsection*{Using the NETHACKOPTIONS environment variable} @@ -2400,52 +2531,10 @@ and the {\it fruit\/} is set to ``papaya'', you would enter the command \nd in {\it sh\/} or {\it ksh}. -%.hn 2 -\subsection*{Using a configuration file} - %.pg -Any line in the configuration file starting with `{\tt \#}' is treated as a comment. -Any line in the configuration file starting with ``{\tt OPTIONS=}'' may be -filled out with options in the same syntax as in NETHACKOPTIONS. -Any line starting with ``{\tt SYMBOLS=}'' -is taken as defining the corresponding {\it symbol} -in a different syntax, a sequence of decimal numbers giving -the character position in the current font to be used in displaying -each entry. Such a sequence can be continued to multiple lines by putting a -`{\tt \verb+\+}' at the end of each line to be continued. - -%.pg -Any line starting with ``{\tt AUTOPICKUP\verb+_+EXCEPTION=}'' -is taken as defining an exception to the ``{\tt pickup\verb+_+types}'' option. -There is a section of this Guidebook that discusses that. - -%.pg -The default name of the configuration file varies on different -operating systems. On DOS and Windows, it is ``{\tt defaults.nh}'' -in the same folder as nethack.exe or nethackW.exe. On Unix, Linux -and Mac OS X it is ``{\tt.nethackrc}'' in the user's home directory. -NETHACKOPTIONS can also be set to the full name of a file you +NETHACKOPTIONS can also be set to the full name of a configuration file you want to use (possibly preceded by an `{\tt @}'). -%.pg -Here is a short example of config file contents: -%.sd -\begin{verbatim} - # Set your character's role, race, gender, and alignment. - OPTIONS=role:Valkyrie, race:Human, gender:female, align:lawful - - # Turn on autopickup, and set automatically picked up object types - OPTIONS=autopickup,pickup_types:$"=/!?+ - # Show colored text if possible - OPTIONS=color - # Show lit corridors differently - OPTIONS=lit_corridor - - # No startup splash screen. Windows GUI only. - OPTIONS=!splash_screen -\end{verbatim} -%.ed - %.hn 2 \subsection*{Customization options} From 8e307be556385fa9a95e65b18d747fa0620d778d Mon Sep 17 00:00:00 2001 From: PatR Date: Sat, 4 Jun 2016 06:06:20 -0700 Subject: [PATCH 38/81] more /? of spaces Restore the ability to look up a single space by 'name'. I thought mungspaces("") kept one space, but it doesn't. It's a lucky accident that unnaming monsters and objects still works. There may be other places which intend to give a special meaning to a single space that don't still work.... --- doc/fixes36.1 | 2 ++ src/pager.c | 7 ++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/fixes36.1 b/doc/fixes36.1 index b3d6a6a9f..6b9242148 100644 --- a/doc/fixes36.1 +++ b/doc/fixes36.1 @@ -283,6 +283,8 @@ when poly'd into a hider and engulfed, attempt to hide via #monster was blocked various monster/object/food/gold/trap detections were inconsistent in how they behaved if performed while engulfed or underwater show in inventory which monster a leash is attached to +using /? to look up something by name, supplying multiple spaces (with no + other characters) as the name triggered impossible "singular of null?" Fixes to Post-3.6.0 Problems that Were Exposed Via git Repository diff --git a/src/pager.c b/src/pager.c index 92c72787d..77ade35b0 100644 --- a/src/pager.c +++ b/src/pager.c @@ -1060,9 +1060,10 @@ coord *click_cc; case '?': from_screen = FALSE; getlin("Specify what? (type the word)", out_str); - /* mungspaces prevents querying for a space glyph (eg. a ghost), - but players almost always use '/' instead to look up glyphs */ - mungspaces(out_str); + if (strcmp(out_str, " ")) /* keep single space as-is */ + /* remove leading and trailing whitespace and + condense consecutive internal whitespace */ + mungspaces(out_str); if (out_str[0] == '\0' || out_str[0] == '\033') return 0; From 4ef23c6ac48ca99cf5ee0a7f1f8d4df784d775ff Mon Sep 17 00:00:00 2001 From: PatR Date: Sun, 5 Jun 2016 01:09:52 -0700 Subject: [PATCH 39/81] 'whatdoes' command and new file: dat/keyhelp Make the whatdoes ('&' or '?f') command support the 'altmeta' option for meta-characters generated by two character seqeunce 'ESC char'. Also, make it be more descriptive when reporting "no such command" by including the numeric value it operated on when failing to match any command. That might provide a way for us to get some extra information when players report problems with odd keystrokes: we ask them to type such at the "what command?" prompt and then tell us what numbers come up. It's been given a help file to deal with assorted idiosyncracies which can come up when querying what keys do. Unfortunately that ended up being way more verbose than intended. Installation of the extra data file has only been done for Unix. Other platforms will get "can't open file" if they respond with '&' or '?' to the "what command?" prompt. The command will still work though, just without the extra text. --- Files | 14 +++++----- dat/keyhelp | 58 ++++++++++++++++++++++++++++++++++++++++ include/global.h | 3 ++- src/pager.c | 61 +++++++++++++++++++++++++++++++++++++------ sys/unix/Makefile.top | 4 +-- 5 files changed, 122 insertions(+), 18 deletions(-) create mode 100644 dat/keyhelp diff --git a/Files b/Files index 919d50808..8c3a948d0 100644 --- a/Files +++ b/Files @@ -12,13 +12,13 @@ dat: (files for all versions) Arch.des Barb.des Caveman.des Healer.des Knight.des Monk.des Priest.des Ranger.des Rogue.des Samurai.des -Tourist.des Valkyrie.des Wizard.des bigroom.des castle.des -cmdhelp data.base dungeon.def endgame.des gehennom.des -help hh history knox.des license -medusa.des mines.des opthelp oracle.des oracles.txt -quest.txt rumors.fal rumors.tru sokoban.des symbols -tower.des wizhelp yendor.des tribute bogusmon.txt -engrave.txt epitaph.txt +Tourist.des Valkyrie.des Wizard.des bigroom.des bogusmon.txt +castle.des cmdhelp data.base dungeon.def endgame.des +engrave.txt epitaph.txt gehennom.des help hh +history keyhelp knox.des license medusa.des +mines.des opthelp oracle.des oracles.txt quest.txt +rumors.fal rumors.tru sokoban.des symbols tower.des +tribute wizhelp yendor.des doc: (files for all versions) diff --git a/dat/keyhelp b/dat/keyhelp new file mode 100644 index 000000000..41ab37ef1 --- /dev/null +++ b/dat/keyhelp @@ -0,0 +1,58 @@ + Depending upon hardware or operating system or NetHack's interface, + some keystrokes may be off-limits. + + For example, ^S and ^Q are often used for XON/XOFF flow-control, + meaning that ^S suspends output and subsequent ^Q resumes suspended + output. When that it the case, neither of those characters will + reach NetHack when it is waiting for a command keystroke. So they + aren't used as commands, but 'whatdoes' might not be able to tell + you that if they don't get passed through to NetHack. + + ^M or or is likely to be transformed into ^J or + or 'newline' before being passed to NetHack for handling. + So it isn't used as a command, and 'whatdoes' might seem as if it + is reporting the wrong character but will be operating correctly if + it describes ^J when you type ^M. + + A NUL character, typed as ^ on some keyboards, ^@ on others, + and maybe not typeable at all on yet others. It is not used as a + command, and will be converted into ESC before reaching 'whatdoes'. + Unlike ^M, this transformation is performed by NetHack itself. + But like ^M, if you type NUL and get feedback about ESC, the + situation is expected. + + ESC itself is a synonym for ^[, and is another source of oddity. + Various function keys, including cursor arrow keys, may transmit + an "escape sequence" of ESC + [ + other stuff, confusing NetHack + as to what command was intended since the ESC will be processed + and then whatever follows will seem to NetHack like--and be used + as--something typed by the user. (If you press a function key and + a menu of the armor your hero is wearing appears, what happened + was that an escape sequence was sent to NetHack, its ESC aborted + any pending key operation, its '[' was then treated as a command + to show worn armor, and the "other stuff" probably got silently + discarded as invalid choices while you dismissed the menu.) + + If you have NetHack's 'altmeta' option enabled, meaning that the + or