summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/direct.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/direct.cc')
-rw-r--r--crawl-ref/source/direct.cc1503
1 files changed, 1503 insertions, 0 deletions
diff --git a/crawl-ref/source/direct.cc b/crawl-ref/source/direct.cc
new file mode 100644
index 0000000000..8ff8a9732a
--- /dev/null
+++ b/crawl-ref/source/direct.cc
@@ -0,0 +1,1503 @@
+/*
+ * File: direct.cc
+ * Summary: Functions used when picking squares.
+ * Written by: Linley Henzell
+ *
+ * Change History (most recent first):
+ *
+ * <5> 01/08/01 GDL complete rewrite of direction()
+ * <4> 11/23/99 LRH Looking at monsters now
+ * displays more info
+ * <3> 5/12/99 BWR changes to allow for space selection of target.
+ * CR, ESC, and 't' in targeting.
+ * <2> 5/09/99 JDJ look_around no longer prints a prompt.
+ * <1> -/--/-- LRH Created
+ */
+
+#include "AppHdr.h"
+#include "direct.h"
+
+#include <cstdarg>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifdef DOS
+#include <conio.h>
+#endif
+
+#include "externs.h"
+
+#include "debug.h"
+#include "describe.h"
+#include "itemname.h"
+#include "monstuff.h"
+#include "mon-util.h"
+#include "player.h"
+#include "shopping.h"
+#include "stuff.h"
+#include "spells4.h"
+#include "stash.h"
+#include "travel.h"
+#include "view.h"
+
+#ifdef USE_MACROS
+#include "macro.h"
+#endif
+
+enum LOSSelect
+{
+ LOS_ANY = 0x00,
+
+ // Check only visible squares
+ LOS_VISIBLE = 0x01,
+
+ // Check only hidden squares
+ LOS_HIDDEN = 0x02,
+
+ LOS_VISMASK = 0x03,
+
+ // Flip from visible to hidden when going forward,
+ // or hidden to visible when going backwards.
+ LOS_FLIPVH = 0x20,
+
+ // Flip from hidden to visible when going forward,
+ // or visible to hidden when going backwards.
+ LOS_FLIPHV = 0x40,
+
+ LOS_NONE = 0xFFFF,
+};
+
+// x and y offsets in the following order:
+// SW, S, SE, W, E, NW, N, NE
+static const char xcomp[9] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 };
+static const char ycomp[9] = { 1, 1, 1, 0, 0, 0, -1, -1, -1 };
+
+// [dshaligram] Removed . and 5 from dirchars so it's easier to
+// special case them.
+static const char dirchars[19] = { "b1j2n3h4bbl6y7k8u9" };
+static const char DOSidiocy[10] = { "OPQKSMGHI" };
+static const char *aim_prompt = "Aim (move cursor or -/+/=, change mode with CTRL-F, select with . or >)";
+
+static void describe_feature(int mx, int my, bool oos);
+static void describe_cell(int mx, int my);
+
+static bool find_object( int x, int y, int mode );
+static bool find_monster( int x, int y, int mode );
+static bool find_feature( int x, int y, int mode );
+static char find_square( unsigned char xps, unsigned char yps,
+ FixedVector<char, 2> &mfp, char direction,
+ bool (*targ)(int, int, int),
+ int mode = TARG_ANY,
+ bool wrap = false,
+ int los = LOS_ANY);
+
+static bool is_mapped(int x, int y)
+{
+ return (is_player_mapped(x, y));
+}
+
+//---------------------------------------------------------------
+//
+// direction
+//
+// input: restricts : DIR_NONE accepts keypad dir or targetting
+// DIR_TARGET must use targetting.
+// DIR_DIR must use keypad direction
+//
+//
+// outputs: dist structure:
+//
+// isValid a valid target or direction was chosen
+// isCancel player hit 'escape'
+// isTarget targetting was used
+// tx,ty target x,y or logical beam extension to
+// edge of map if keypad direction used.
+// dx,dy direction delta if keypad used {-1,0,1}
+//
+// SYNOPSIS:
+//
+// gets a direction, or any of the follwing:
+//
+// * go to targetting mode
+// +,= go to targetting mode, next monster
+// - " , prev monster
+// t,p auto-select previous target
+//
+//
+// targetting mode is handled by look_around()
+//---------------------------------------------------------------
+void direction( struct dist &moves, int restrict, int mode )
+{
+ bool dirChosen = false;
+ bool targChosen = false;
+ int dir = 0;
+
+ // init
+ moves.isValid = false;
+ moves.isTarget = false;
+ moves.isMe = false;
+ moves.isCancel = false;
+ moves.dx = moves.dy = 0;
+ moves.tx = moves.ty = 0;
+
+ // XXX. this is ALWAYS in relation to the player. But a bit of a hack
+ // nonetheless! --GDL
+ gotoxy( 18, 9 );
+
+ int keyin = getchm(KC_TARGETING);
+
+ if (keyin == 0) // DOS idiocy (emulated by win32 console)
+ {
+ keyin = getchm(KC_TARGETING); // grrr.
+ if (keyin == '*')
+ {
+ targChosen = true;
+ dir = 0;
+ }
+ else
+ {
+ if (strchr(DOSidiocy, keyin) == NULL)
+ return;
+ dirChosen = true;
+ dir = (int)(strchr(DOSidiocy, keyin) - DOSidiocy);
+ }
+ }
+ else
+ {
+ if (strchr( dirchars, keyin ) != NULL)
+ {
+ dirChosen = true;
+ dir = (int)(strchr(dirchars, keyin) - dirchars) / 2;
+ }
+ else
+ {
+ switch (keyin)
+ {
+ case CONTROL('F'):
+ mode = (mode + 1) % TARG_NUM_MODES;
+
+ snprintf( info, INFO_SIZE, "Targeting mode is now: %s",
+ (mode == TARG_ANY) ? "any" :
+ (mode == TARG_ENEMY) ? "enemies"
+ : "friends" );
+
+ mpr( info );
+
+ targChosen = true;
+ dir = 0;
+ break;
+
+ case '-':
+ targChosen = true;
+ dir = -1;
+ break;
+
+ case '*':
+ targChosen = true;
+ dir = 0;
+ break;
+
+ case ';':
+ targChosen = true;
+ dir = -3;
+ break;
+
+ case '\'':
+ targChosen = true;
+ dir = -2;
+ break;
+
+ case '+':
+ case '=':
+ targChosen = true;
+ dir = 1;
+ break;
+
+ case 't':
+ case 'p':
+ targChosen = true;
+ dir = 2;
+ break;
+
+ case '.':
+ case '5':
+ dirChosen = true;
+ dir = 4;
+ break;
+
+ case ESCAPE:
+ moves.isCancel = true;
+ return;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ // at this point, we know exactly the input - validate
+ if (!(targChosen || dirChosen) || (targChosen && restrict == DIR_DIR))
+ {
+ mpr("What an unusual direction.");
+ return;
+ }
+
+ // special case: they typed a dir key, but they're in target-only mode
+ if (dirChosen && restrict == DIR_TARGET)
+ {
+ mpr(aim_prompt);
+ look_around( moves, false, dir, mode );
+ return;
+ }
+
+ if (targChosen)
+ {
+ if (dir < 2)
+ {
+ mpr(aim_prompt);
+ moves.prev_target = dir;
+ look_around( moves, false, -1, mode );
+ if (moves.prev_target != -1) // -1 means they pressed 'p'
+ return;
+ }
+
+ // chose to aim at previous target. do we have one?
+ if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU)
+ {
+ mpr("You haven't got a target.");
+ return;
+ }
+
+ // we have a valid previous target (maybe)
+ struct monsters *montarget = &menv[you.prev_targ];
+
+ if (!mons_near(montarget) || !player_monster_visible( montarget ))
+ {
+ mpr("You can't see that creature any more.");
+ return;
+ }
+ else
+ {
+ moves.isValid = true;
+ moves.isTarget = true;
+ moves.tx = montarget->x;
+ moves.ty = montarget->y;
+ }
+ return;
+ }
+
+ // at this point, we have a direction, and direction is allowed.
+ moves.isValid = true;
+ moves.isTarget = false;
+ moves.dx = xcomp[dir];
+ moves.dy = ycomp[dir];
+ if (xcomp[dir] == 0 && ycomp[dir] == 0)
+ moves.isMe = true;
+
+ // now the tricky bit - extend the target x,y out to map edge.
+ int mx, my;
+ mx = my = 0;
+
+ if (moves.dx > 0)
+ mx = (GXM - 1) - you.x_pos;
+ if (moves.dx < 0)
+ mx = you.x_pos;
+
+ if (moves.dy > 0)
+ my = (GYM - 1) - you.y_pos;
+ if (moves.dy < 0)
+ my = you.y_pos;
+
+ if (!(mx == 0 || my == 0))
+ {
+ if (mx < my)
+ my = mx;
+ else
+ mx = my;
+ }
+ moves.tx = you.x_pos + moves.dx * mx;
+ moves.ty = you.y_pos + moves.dy * my;
+}
+
+// Attempts to describe a square that's not in line-of-sight. If
+// there's a stash on the square, announces the top item and number
+// of items, otherwise, if there's a stair that's in the travel
+// cache and noted in the Dungeon (O)verview, names the stair.
+static void describe_oos_square(int x, int y)
+{
+ if (!is_mapped(x, y))
+ return;
+
+#ifdef STASH_TRACKING
+ describe_stash(x, y);
+#endif
+ describe_feature(x, y, true);
+}
+
+//---------------------------------------------------------------
+//
+// look_around
+//
+// Accessible by the x key and when using cursor aiming. Lets you
+// find out what symbols mean, and is the way to access monster
+// descriptions.
+//
+// input: dist.prev_target : -1 is last monster
+// 0 is no monster selected
+// 1 is next monster
+//
+// input: first_move is -1 if no initial cursor move, otherwise
+// make 1 move in that direction.
+//
+//
+// output: if dist.prev_target is -1 on OUTPUT, it means that
+// player selected 'p' ot 't' for last targetted monster.
+//
+// otherwise, usual dist fields are filled in (dx and dy are
+// always zero coming back from this function)
+//
+//---------------------------------------------------------------
+
+void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
+{
+ int keyin = 0;
+ bool dirChosen = false;
+ bool targChosen = false;
+ int dir = 0;
+ int cx = 17;
+ int cy = 9;
+ int newcx, newcy;
+ int mx, my; // actual map x,y (scratch)
+ int mid; // monster id (scratch)
+ FixedVector < char, 2 > monsfind_pos;
+ FixedVector < char, 2 > objfind_pos;
+
+ monsfind_pos[0] = objfind_pos[0] = you.x_pos;
+ monsfind_pos[1] = objfind_pos[1] = you.y_pos;
+
+ message_current_target();
+
+ // setup initial keystroke
+ if (first_move >= 0)
+ keyin = (int)'1' + first_move;
+ if (moves.prev_target == -1)
+ keyin = '-';
+ if (moves.prev_target == 1)
+ keyin = '+';
+ if (moves.prev_target == -2)
+ keyin = '\'';
+ if (moves.prev_target == -3)
+ keyin = ';';
+ // reset
+ moves.prev_target = 0;
+
+ // loop until some exit criteria reached
+ while(true)
+ {
+ dirChosen = false;
+ targChosen = false;
+ newcx = cx;
+ newcy = cy;
+
+ // move cursor to current position
+ gotoxy(cx+1, cy);
+
+ if (keyin == 0)
+ keyin = getchm(KC_TARGETING);
+
+ // [dshaligram] Classic Crawl behaviour was to use space to select
+ // targets when targeting. The patch changed the meaning of space
+ // from 'confirm' to 'cancel', which surprised some folks. I'm now
+ // arbitrarily defining space as 'cancel' for look-around, and
+ // 'confirm' for targeting.
+ if (!justLooking && keyin == ' ')
+ keyin = '\r';
+
+ // DOS idiocy
+ if (keyin == 0)
+ {
+ // get the extended key
+ keyin = getchm(KC_TARGETING);
+
+ // look for CR - change to '5' to indicate selection
+ if (keyin == 13)
+ keyin = 'S';
+
+ if (strchr(DOSidiocy, keyin) == NULL)
+ break;
+ dirChosen = true;
+ dir = (int)(strchr(DOSidiocy, keyin) - DOSidiocy);
+ }
+ else
+ {
+ if (strchr(dirchars, keyin) != NULL)
+ {
+ dirChosen = true;
+ dir = (int)(strchr(dirchars, keyin) - dirchars) / 2;
+ }
+ else
+ {
+ // handle non-directional keys
+ switch (keyin)
+ {
+ case CONTROL('F'):
+ mode = (mode + 1) % TARG_NUM_MODES;
+
+ snprintf( info, INFO_SIZE, "Targeting mode is now: %s",
+ (mode == TARG_ANY) ? "any" :
+ (mode == TARG_ENEMY) ? "enemies"
+ : "friends" );
+
+ mpr( info );
+ targChosen = true;
+ break;
+
+ case '^':
+ case '\t':
+ case '\\':
+ case '_':
+ case '<':
+ case '>':
+ {
+ if (find_square( cx, cy, objfind_pos, 1,
+ find_feature, keyin, true,
+ Options.target_los_first
+ ? LOS_FLIPVH : LOS_ANY))
+ {
+ newcx = objfind_pos[0];
+ newcy = objfind_pos[1];
+ }
+ else
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ targChosen = true;
+ break;
+ }
+ case ';':
+ case '/':
+ case '\'':
+ case '*':
+ {
+ int dir = keyin == ';' || keyin == '/'? -1 : 1;
+ if (find_square( cx, cy, objfind_pos, dir,
+ find_object, 0, true,
+ Options.target_los_first
+ ? (dir == 1? LOS_FLIPVH : LOS_FLIPHV)
+ : LOS_ANY))
+
+ {
+ newcx = objfind_pos[0];
+ newcy = objfind_pos[1];
+ }
+ else
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ targChosen = true;
+ break;
+ }
+
+ case '-':
+ case '+':
+ case '=':
+ {
+ int dir = keyin == '-'? -1 : 1;
+ if (find_square( cx, cy, monsfind_pos, dir,
+ find_monster, mode, Options.target_wrap ))
+ {
+ newcx = monsfind_pos[0];
+ newcy = monsfind_pos[1];
+ }
+ else
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ targChosen = true;
+ break;
+ }
+
+ case 't':
+ case 'p':
+ moves.prev_target = -1;
+ break;
+
+ case '?':
+ targChosen = true;
+ mx = you.x_pos + cx - 17;
+ my = you.y_pos + cy - 9;
+ mid = mgrd[mx][my];
+
+ if (mid == NON_MONSTER)
+ break;
+
+#if (!DEBUG_DIAGNOSTICS)
+ if (!player_monster_visible( &menv[mid] ))
+ break;
+#endif
+
+ describe_monsters( menv[ mid ].type, mid );
+ redraw_screen();
+ mesclr( true );
+ // describe the cell again.
+ describe_cell(you.x_pos + cx - 17, you.y_pos + cy - 9);
+ break;
+
+ case '\r':
+ case '\n':
+ case '.':
+ case '5':
+ // If we're in look-around mode, and the cursor is on
+ // the character and there's a valid travel target
+ // within the viewport, jump to that target.
+ if (justLooking && cx == 17 && cy == 9)
+ {
+ if (you.travel_x > 0 && you.travel_y > 0)
+ {
+ int nx = you.travel_x - you.x_pos + 17;
+ int ny = you.travel_y - you.y_pos + 9;
+ if (in_viewport_bounds(nx, ny))
+ {
+ newcx = nx;
+ newcy = ny;
+ targChosen = true;
+ }
+ }
+ }
+ else
+ {
+ dirChosen = true;
+ dir = 4;
+ }
+ break;
+
+ case ' ':
+ case ESCAPE:
+ moves.isCancel = true;
+ mesclr( true );
+ return;
+
+ default:
+ break;
+ }
+ }
+ }
+
+ // now we have parsed the input character completely. Reset & Evaluate:
+ keyin = 0;
+ if (!targChosen && !dirChosen)
+ break;
+
+ // check for SELECTION
+ if (dirChosen && dir == 4)
+ {
+ // [dshaligram] We no longer vet the square coordinates if
+ // we're justLooking. By not vetting the coordinates, we make 'x'
+ // look_around() nicer for travel purposes.
+ if (!justLooking)
+ {
+ // RULE: cannot target what you cannot see
+ if (!in_vlos(cx, cy))
+ {
+ // if (!justLooking)
+ mpr("Sorry, you can't target what you can't see.");
+ return;
+ }
+ }
+
+ moves.isValid = true;
+ moves.isTarget = true;
+ moves.tx = you.x_pos + cx - 17;
+ moves.ty = you.y_pos + cy - 9;
+
+ if (moves.tx == you.x_pos && moves.ty == you.y_pos)
+ moves.isMe = true;
+ else
+ {
+ // try to set you.previous target
+ mx = you.x_pos + cx - 17;
+ my = you.y_pos + cy - 9;
+ mid = mgrd[mx][my];
+
+ if (mid == NON_MONSTER)
+ break;
+
+ if (!player_monster_visible( &(menv[mid]) ))
+ break;
+
+ you.prev_targ = mid;
+ }
+ break;
+ }
+
+ // check for MOVE
+ if (dirChosen)
+ {
+ newcx = cx + xcomp[dir];
+ newcy = cy + ycomp[dir];
+ }
+
+ // bounds check for newcx, newcy
+ if (newcx < 1) newcx = 1;
+ if (newcx > 33) newcx = 33;
+ if (newcy < 1) newcy = 1;
+ if (newcy > 17) newcy = 17;
+
+ // no-op if the cursor doesn't move.
+ if (newcx == cx && newcy == cy)
+ continue;
+
+ // CURSOR MOVED - describe new cell.
+ cx = newcx;
+ cy = newcy;
+ mesclr( true );
+ if (!in_vlos(cx, cy))
+ {
+ mpr("You can't see that place.");
+ describe_oos_square(you.x_pos + cx - 17,
+ you.y_pos + cy - 9);
+ continue;
+ }
+ describe_cell(you.x_pos + cx - 17, you.y_pos + cy - 9);
+ } // end WHILE
+
+ mesclr( true );
+} // end look_around()
+
+bool in_vlos(int x, int y)
+{
+ return in_los_bounds(x, y) && (env.show[x - 8][y] || (x == 17 && y == 9));
+}
+
+bool in_los(int x, int y)
+{
+ const int tx = x + 9 - you.x_pos,
+ ty = y + 9 - you.y_pos;
+
+ if (!in_los_bounds(tx + 8, ty))
+ return (false);
+
+ return (x == you.x_pos && y == you.y_pos) || env.show[tx][ty];
+}
+
+static bool find_monster( int x, int y, int mode )
+{
+ const int targ_mon = mgrd[ x ][ y ];
+ return (targ_mon != NON_MONSTER
+ && in_los(x, y)
+ && player_monster_visible( &(menv[targ_mon]) )
+ && !mons_is_mimic( menv[targ_mon].type )
+ && (mode == TARG_ANY
+ || (mode == TARG_FRIEND && mons_friendly( &menv[targ_mon] ))
+ || (mode == TARG_ENEMY
+ && !mons_friendly( &menv[targ_mon] )
+ &&
+ (Options.target_zero_exp ||
+ !mons_flag( menv[targ_mon].type, M_NO_EXP_GAIN )) )));
+}
+
+static bool find_feature( int x, int y, int mode )
+{
+ // The stair need not be in LOS if the square is mapped.
+ if (!in_los(x, y) && (!Options.target_oos || !is_mapped(x, y)))
+ return (false);
+
+ return is_feature(mode, x, y);
+}
+
+static bool find_object(int x, int y, int mode)
+{
+ const int item = igrd[x][y];
+ // The square need not be in LOS if the stash tracker knows this item.
+ return (item != NON_ITEM
+ && (in_los(x, y)
+#ifdef STASH_TRACKING
+ || (Options.target_oos && is_mapped(x, y) && is_stash(x, y))
+#endif
+ ));
+}
+
+static int next_los(int dir, int los, bool wrap)
+{
+ if (los == LOS_ANY)
+ return (wrap? los : LOS_NONE);
+
+ bool vis = los & LOS_VISIBLE;
+ bool hidden = los & LOS_HIDDEN;
+ bool flipvh = los & LOS_FLIPVH;
+ bool fliphv = los & LOS_FLIPHV;
+
+ if (!vis && !hidden)
+ vis = true;
+
+ if (wrap)
+ {
+ if (!flipvh && !fliphv)
+ return (los);
+
+ // We have to invert flipvh and fliphv if we're wrapping. Here's
+ // why:
+ //
+ // * Say the cursor is on the last item in LOS, there are no
+ // items outside LOS, and wrap == true. flipvh is true.
+ // * We set wrap false and flip from visible to hidden, but there
+ // are no hidden items. So now we need to flip back to visible
+ // so we can go back to the first item in LOS. Unless we set
+ // fliphv, we can't flip from hidden to visible.
+ //
+ los = flipvh? LOS_FLIPHV : LOS_FLIPVH;
+ }
+ else
+ {
+ if (!flipvh && !fliphv)
+ return (LOS_NONE);
+
+ if (flipvh && vis != (dir == 1))
+ return (LOS_NONE);
+
+ if (fliphv && vis == (dir == 1))
+ return (LOS_NONE);
+ }
+
+ los = (los & ~LOS_VISMASK) | (vis? LOS_HIDDEN : LOS_VISIBLE);
+ return (los);
+}
+
+bool in_viewport_bounds(int x, int y)
+{
+ return (x >= 1 && x <= 33 && y >= 1 && y <= 17);
+}
+
+bool in_los_bounds(int x, int y)
+{
+ return !(x > 25 || x < 8 || y > 17 || y < 1);
+}
+
+//---------------------------------------------------------------
+//
+// find_square
+//
+// Finds the next monster/object/whatever (moving in a spiral
+// outwards from the player, so closer targets are chosen first;
+// starts to player's left) and puts its coordinates in mfp.
+// Returns 1 if it found something, zero otherwise. If direction
+// is -1, goes backwards.
+//
+// If the game option target_zero_exp is true, zero experience
+// monsters will be targeted.
+//
+//---------------------------------------------------------------
+static char find_square( unsigned char xps, unsigned char yps,
+ FixedVector<char, 2> &mfp, char direction,
+ bool (*find_targ)( int x, int y, int mode ),
+ int mode, bool wrap, int los )
+{
+ int temp_xps = xps;
+ int temp_yps = yps;
+ char x_change = 0;
+ char y_change = 0;
+
+ bool onlyVis = false, onlyHidden = false;
+
+ int i, j;
+
+ if (los == LOS_NONE)
+ return (0);
+
+ if (los == LOS_FLIPVH || los == LOS_FLIPHV)
+ {
+ if (in_los_bounds(xps, yps))
+ {
+ // We've been told to flip between visible/hidden, so we
+ // need to find what we're currently on.
+ bool vis = (env.show[xps - 8][yps] || (xps == 17 && yps == 9));
+
+ if (wrap && (vis != (los == LOS_FLIPVH)) == (direction == 1))
+ {
+ // We've already flipped over into the other direction,
+ // so correct the flip direction if we're wrapping.
+ los = los == LOS_FLIPHV? LOS_FLIPVH : LOS_FLIPHV;
+ }
+
+ los = (los & ~LOS_VISMASK) | (vis? LOS_VISIBLE : LOS_HIDDEN);
+ }
+ else
+ {
+ if (wrap)
+ los = LOS_HIDDEN | (direction == 1? LOS_FLIPHV : LOS_FLIPVH);
+ else
+ los |= LOS_HIDDEN;
+ }
+ }
+
+ onlyVis = (los & LOS_VISIBLE);
+ onlyHidden = (los & LOS_HIDDEN);
+
+ const int minx = 1, maxx = 33,
+ miny = -7, maxy = 25,
+ ctrx = 17, ctry = 9;
+
+ while (temp_xps >= minx - 1 && temp_xps <= maxx
+ && temp_yps <= maxy && temp_yps >= miny - 1)
+ {
+ if (direction == 1 && temp_xps == minx && temp_yps == maxy)
+ {
+ return find_square(ctrx, ctry, mfp, direction, find_targ, mode,
+ false, next_los(direction, los, wrap));
+ }
+ if (direction == -1 && temp_xps == ctrx && temp_yps == ctry)
+ {
+ return find_square(minx, maxy, mfp, direction, find_targ, mode,
+ false, next_los(direction, los, wrap));
+ }
+
+ if (direction == 1)
+ {
+ if (temp_xps == minx - 1)
+ {
+ x_change = 0;
+ y_change = -1;
+ }
+ else if (temp_xps == ctrx && temp_yps == ctry)
+ {
+ x_change = -1;
+ y_change = 0;
+ }
+ else if (abs(temp_xps - ctrx) <= abs(temp_yps - ctry))
+ {
+ if (temp_xps - ctrx >= 0 && temp_yps - ctry <= 0)
+ {
+ if (abs(temp_xps - ctrx) > abs(temp_yps - ctry + 1))
+ {
+ x_change = 0;
+ y_change = -1;
+ if (temp_xps - ctrx > 0)
+ y_change = 1;
+ goto finished_spiralling;
+ }
+ }
+ x_change = -1;
+ if (temp_yps - ctry < 0)
+ x_change = 1;
+ y_change = 0;
+ }
+ else
+ {
+ x_change = 0;
+ y_change = -1;
+ if (temp_xps - ctrx > 0)
+ y_change = 1;
+ }
+ } // end if (direction == 1)
+ else
+ {
+ /*
+ This part checks all eight surrounding squares to find the
+ one that leads on to the present square.
+ */
+ for (i = -1; i < 2; i++)
+ {
+ for (j = -1; j < 2; j++)
+ {
+ if (i == 0 && j == 0)
+ continue;
+
+ if (temp_xps + i == minx - 1)
+ {
+ x_change = 0;
+ y_change = -1;
+ }
+ else if (temp_xps + i - ctrx == 0 && temp_yps + j - ctry == 0)
+ {
+ x_change = -1;
+ y_change = 0;
+ }
+ else if (abs(temp_xps + i - ctrx) <= abs(temp_yps + j - ctry))
+ {
+ const int xi = temp_xps + i - ctrx;
+ const int yj = temp_yps + j - ctry;
+
+ if (xi >= 0 && yj <= 0)
+ {
+ if (abs(xi) > abs(yj + 1))
+ {
+ x_change = 0;
+ y_change = -1;
+ if (xi > 0)
+ y_change = 1;
+ goto finished_spiralling;
+ }
+ }
+
+ x_change = -1;
+ if (yj < 0)
+ x_change = 1;
+ y_change = 0;
+ }
+ else
+ {
+ x_change = 0;
+ y_change = -1;
+ if (temp_xps + i - ctrx > 0)
+ y_change = 1;
+ }
+
+ if (temp_xps + i + x_change == temp_xps
+ && temp_yps + j + y_change == temp_yps)
+ {
+ goto finished_spiralling;
+ }
+ }
+ }
+ } // end else
+
+
+ finished_spiralling:
+ x_change *= direction;
+ y_change *= direction;
+
+ temp_xps += x_change;
+ if (temp_yps + y_change <= maxy) // it can wrap, unfortunately
+ temp_yps += y_change;
+
+ const int targ_x = you.x_pos + temp_xps - ctrx;
+ const int targ_y = you.y_pos + temp_yps - ctry;
+
+ // We don't want to be looking outside the bounds of the arrays:
+ //if (!in_los_bounds(temp_xps, temp_yps))
+ // continue;
+
+ if (temp_xps < minx - 1 || temp_xps > maxx
+ || temp_yps < 1 || temp_yps > 17)
+ continue;
+
+ if (targ_x < 1 || targ_x >= GXM || targ_y < 1 || targ_y >= GYM)
+ continue;
+
+ if ((onlyVis || onlyHidden) && onlyVis != in_los(targ_x, targ_y))
+ continue;
+
+ if (find_targ(targ_x, targ_y, mode)) {
+ mfp[0] = temp_xps;
+ mfp[1] = temp_yps;
+ return (1);
+ }
+ }
+
+ return (direction == 1?
+ find_square(ctrx, ctry, mfp, direction, find_targ, mode, false,
+ next_los(direction, los, wrap))
+ : find_square(minx, maxy, mfp, direction, find_targ, mode, false,
+ next_los(direction, los, wrap)));
+}
+
+static bool is_shopstair(int x, int y)
+{
+ return (is_stair(grd[x][y]) || grd[x][y] == DNGN_ENTER_SHOP);
+}
+
+extern unsigned char (*mapch2) (unsigned char);
+static bool is_full_mapped(int x, int y)
+{
+ unsigned grid = grd[x][y];
+ int envch = env.map[x - 1][y - 1];
+ return (envch && envch == mapch2(grid));
+}
+
+static int surround_nonshopstair_count(int x, int y)
+{
+ int count = 0;
+ for (int ix = -1; ix < 2; ++ix)
+ {
+ for (int iy = -1; iy < 2; ++iy)
+ {
+ int nx = x + ix, ny = y + iy;
+ if (nx <= 0 || nx >= GXM || ny <= 0 || ny >= GYM)
+ continue;
+ if (is_full_mapped(nx, ny) && !is_shopstair(nx, ny))
+ count++;
+ }
+ }
+ return (count);
+}
+
+// For want of a better name...
+static bool clear_mapped(int x, int y)
+{
+ if (!is_full_mapped(x, y))
+ return (false);
+
+ if (is_shopstair(x, y))
+ return (surround_nonshopstair_count(x, y) > 0);
+
+ return (true);
+}
+
+static void describe_feature(int mx, int my, bool oos)
+{
+ if (oos && !clear_mapped(mx, my))
+ return;
+
+ unsigned oldfeat = grd[mx][my];
+ if (oos && env.map[mx - 1][my - 1] == mapch2(DNGN_SECRET_DOOR))
+ grd[mx][my] = DNGN_ROCK_WALL;
+
+ std::string desc = feature_description(mx, my);
+
+ grd[mx][my] = oldfeat;
+
+ if (desc.length())
+ {
+ if (oos)
+ desc = "[" + desc + "]";
+ mpr(desc.c_str());
+ }
+}
+
+std::string feature_description(int mx, int my)
+{
+ int trf; // used for trap type??
+ switch (grd[mx][my])
+ {
+ case DNGN_STONE_WALL:
+ return ("A stone wall.");
+ case DNGN_ROCK_WALL:
+ case DNGN_SECRET_DOOR:
+ if (you.level_type == LEVEL_PANDEMONIUM)
+ return ("A wall of the weird stuff which makes up Pandemonium.");
+ else
+ return ("A rock wall.");
+ case DNGN_PERMAROCK_WALL:
+ return ("An unnaturally hard rock wall.");
+ case DNGN_CLOSED_DOOR:
+ return ("A closed door.");
+ case DNGN_METAL_WALL:
+ return ("A metal wall.");
+ case DNGN_GREEN_CRYSTAL_WALL:
+ return ("A wall of green crystal.");
+ case DNGN_ORCISH_IDOL:
+ return ("An orcish idol.");
+ case DNGN_WAX_WALL:
+ return ("A wall of solid wax.");
+ case DNGN_SILVER_STATUE:
+ return ("A silver statue.");
+ case DNGN_GRANITE_STATUE:
+ return ("A granite statue.");
+ case DNGN_ORANGE_CRYSTAL_STATUE:
+ return ("An orange crystal statue.");
+ case DNGN_LAVA:
+ return ("Some lava.");
+ case DNGN_DEEP_WATER:
+ return ("Some deep water.");
+ case DNGN_SHALLOW_WATER:
+ return ("Some shallow water.");
+ case DNGN_UNDISCOVERED_TRAP:
+ case DNGN_FLOOR:
+ return ("Floor.");
+ case DNGN_OPEN_DOOR:
+ return ("An open door.");
+ case DNGN_ROCK_STAIRS_DOWN:
+ return ("A rock staircase leading down.");
+ case DNGN_STONE_STAIRS_DOWN_I:
+ case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III:
+ return ("A stone staircase leading down.");
+ case DNGN_ROCK_STAIRS_UP:
+ return ("A rock staircase leading upwards.");
+ case DNGN_STONE_STAIRS_UP_I:
+ case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III:
+ return ("A stone staircase leading up.");
+ case DNGN_ENTER_HELL:
+ return ("A gateway to hell.");
+ case DNGN_BRANCH_STAIRS:
+ return ("A staircase to a branch level.");
+ case DNGN_TRAP_MECHANICAL:
+ case DNGN_TRAP_MAGICAL:
+ case DNGN_TRAP_III:
+ for (trf = 0; trf < MAX_TRAPS; trf++)
+ {
+ if (env.trap[trf].x == mx
+ && env.trap[trf].y == my)
+ {
+ break;
+ }
+
+ if (trf == MAX_TRAPS - 1)
+ {
+ mpr("Error - couldn't find that trap.");
+ error_message_to_player();
+ break;
+ }
+ }
+
+ switch (env.trap[trf].type)
+ {
+ case TRAP_DART:
+ return ("A dart trap.");
+ case TRAP_ARROW:
+ return ("An arrow trap.");
+ case TRAP_SPEAR:
+ return ("A spear trap.");
+ case TRAP_AXE:
+ return ("An axe trap.");
+ case TRAP_TELEPORT:
+ return ("A teleportation trap.");
+ case TRAP_AMNESIA:
+ return ("An amnesia trap.");
+ case TRAP_BLADE:
+ return ("A blade trap.");
+ case TRAP_BOLT:
+ return ("A bolt trap.");
+ case TRAP_ZOT:
+ return ("A Zot trap.");
+ case TRAP_NEEDLE:
+ return ("A needle trap.");
+ default:
+ mpr("An undefined trap. Huh?");
+ error_message_to_player();
+ break;
+ }
+ break;
+ case DNGN_ENTER_SHOP:
+ return (shop_name(mx, my));
+ case DNGN_ENTER_LABYRINTH:
+ return ("A labyrinth entrance.");
+ case DNGN_ENTER_DIS:
+ return ("A gateway to the Iron City of Dis.");
+ case DNGN_ENTER_GEHENNA:
+ return ("A gateway to Gehenna.");
+ case DNGN_ENTER_COCYTUS:
+ return ("A gateway to the freezing wastes of Cocytus.");
+ case DNGN_ENTER_TARTARUS:
+ return ("A gateway to the decaying netherworld of Tartarus.");
+ case DNGN_ENTER_ABYSS:
+ return ("A gateway to the infinite Abyss.");
+ case DNGN_EXIT_ABYSS:
+ return ("A gateway leading out of the Abyss.");
+ case DNGN_STONE_ARCH:
+ return ("An empty arch of ancient stone.");
+ case DNGN_ENTER_PANDEMONIUM:
+ return ("A gate leading to the halls of Pandemonium.");
+ case DNGN_EXIT_PANDEMONIUM:
+ return ("A gate leading out of Pandemonium.");
+ case DNGN_TRANSIT_PANDEMONIUM:
+ return ("A gate leading to another region of Pandemonium.");
+ case DNGN_ENTER_ORCISH_MINES:
+ return ("A staircase to the Orcish Mines.");
+ case DNGN_ENTER_HIVE:
+ return ("A staircase to the Hive.");
+ case DNGN_ENTER_LAIR:
+ return ("A staircase to the Lair.");
+ case DNGN_ENTER_SLIME_PITS:
+ return ("A staircase to the Slime Pits.");
+ case DNGN_ENTER_VAULTS:
+ return ("A staircase to the Vaults.");
+ case DNGN_ENTER_CRYPT:
+ return ("A staircase to the Crypt.");
+ case DNGN_ENTER_HALL_OF_BLADES:
+ return ("A staircase to the Hall of Blades.");
+ case DNGN_ENTER_ZOT:
+ return ("A gate to the Realm of Zot.");
+ case DNGN_ENTER_TEMPLE:
+ return ("A staircase to the Ecumenical Temple.");
+ case DNGN_ENTER_SNAKE_PIT:
+ return ("A staircase to the Snake Pit.");
+ case DNGN_ENTER_ELVEN_HALLS:
+ return ("A staircase to the Elven Halls.");
+ case DNGN_ENTER_TOMB:
+ return ("A staircase to the Tomb.");
+ case DNGN_ENTER_SWAMP:
+ return ("A staircase to the Swamp.");
+ case DNGN_RETURN_FROM_ORCISH_MINES:
+ case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR:
+ case DNGN_RETURN_FROM_VAULTS:
+ case DNGN_RETURN_FROM_TEMPLE:
+ return ("A staircase back to the Dungeon.");
+ case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_SWAMP:
+ return ("A staircase back to the Lair.");
+ case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES:
+ return ("A staircase back to the Vaults.");
+ case DNGN_RETURN_FROM_ELVEN_HALLS:
+ return ("A staircase back to the Mines.");
+ case DNGN_RETURN_FROM_TOMB:
+ return ("A staircase back to the Crypt.");
+ case DNGN_RETURN_FROM_ZOT:
+ return ("A gate leading back out of this place.");
+ case DNGN_ALTAR_ZIN:
+ return ("A glowing white marble altar of Zin.");
+ case DNGN_ALTAR_SHINING_ONE:
+ return ("A glowing golden altar of the Shining One.");
+ case DNGN_ALTAR_KIKUBAAQUDGHA:
+ return ("An ancient bone altar of Kikubaaqudgha.");
+ case DNGN_ALTAR_YREDELEMNUL:
+ return ("A basalt altar of Yredelemnul.");
+ case DNGN_ALTAR_XOM:
+ return ("A shimmering altar of Xom.");
+ case DNGN_ALTAR_VEHUMET:
+ return ("A shining altar of Vehumet.");
+ case DNGN_ALTAR_OKAWARU:
+ return ("An iron altar of Okawaru.");
+ case DNGN_ALTAR_MAKHLEB:
+ return ("A burning altar of Makhleb.");
+ case DNGN_ALTAR_SIF_MUNA:
+ return ("A deep blue altar of Sif Muna.");
+ case DNGN_ALTAR_TROG:
+ return ("A bloodstained altar of Trog.");
+ case DNGN_ALTAR_NEMELEX_XOBEH:
+ return ("A sparkling altar of Nemelex Xobeh.");
+ case DNGN_ALTAR_ELYVILON:
+ return ("A silver altar of Elyvilon.");
+ case DNGN_BLUE_FOUNTAIN:
+ return ("A fountain of clear blue water.");
+ case DNGN_SPARKLING_FOUNTAIN:
+ return ("A fountain of sparkling water.");
+ case DNGN_DRY_FOUNTAIN_I:
+ case DNGN_DRY_FOUNTAIN_II:
+ case DNGN_DRY_FOUNTAIN_IV:
+ case DNGN_DRY_FOUNTAIN_VI:
+ case DNGN_DRY_FOUNTAIN_VIII:
+ case DNGN_PERMADRY_FOUNTAIN:
+ return ("A dry fountain.");
+ }
+ return ("");
+}
+
+static void describe_cell(int mx, int my)
+{
+ char str_pass[ ITEMNAME_SIZE ];
+ bool mimic_item = false;
+
+ if (mgrd[mx][my] != NON_MONSTER)
+ {
+ int i = mgrd[mx][my];
+
+ if (grd[mx][my] == DNGN_SHALLOW_WATER)
+ {
+ if (!player_monster_visible(&menv[i]) && !mons_flies(&menv[i]))
+ {
+ mpr("There is a strange disturbance in the water here.");
+ }
+ }
+
+#if DEBUG_DIAGNOSTICS
+ if (!player_monster_visible( &menv[i] ))
+ mpr( "There is a non-visible monster here.", MSGCH_DIAGNOSTICS );
+#else
+ if (!player_monster_visible( &menv[i] ))
+ goto look_clouds;
+#endif
+
+ const int mon_wep = menv[i].inv[MSLOT_WEAPON];
+ const int mon_arm = menv[i].inv[MSLOT_ARMOUR];
+
+ strcpy(info, ptr_monam( &(menv[i]), DESC_CAP_A ));
+ strcat(info, ".");
+ mpr(info);
+
+ if (menv[i].type != MONS_DANCING_WEAPON && mon_wep != NON_ITEM)
+ {
+ snprintf( info, INFO_SIZE, "%s is wielding ", mons_pronoun( menv[i].type,
+ PRONOUN_CAP ));
+ it_name(mon_wep, DESC_NOCAP_A, str_pass);
+ strcat(info, str_pass);
+
+ // 2-headed ogres can wield 2 weapons
+ if ((menv[i].type == MONS_TWO_HEADED_OGRE
+ || menv[i].type == MONS_ETTIN)
+ && menv[i].inv[MSLOT_MISSILE] != NON_ITEM)
+ {
+ strcat( info, " and " );
+ it_name(menv[i].inv[MSLOT_MISSILE], DESC_NOCAP_A, str_pass);
+ strcat(info, str_pass);
+ strcat(info, ".");
+
+ mpr(info);
+ }
+ else
+ {
+ strcat(info, ".");
+ mpr(info);
+ }
+ }
+
+ if (mon_arm != NON_ITEM)
+ {
+ it_name( mon_arm, DESC_NOCAP_A, str_pass );
+ snprintf( info, INFO_SIZE, "%s is wearing %s.",
+ mons_pronoun( menv[i].type, PRONOUN_CAP ),
+ str_pass );
+
+ mpr( info );
+ }
+
+
+ if (menv[i].type == MONS_HYDRA)
+ {
+ snprintf( info, INFO_SIZE, "It has %d head%s.",
+ menv[i].number, (menv[i].number > 1? "s" : "") );
+ mpr( info );
+ }
+
+ print_wounds(&menv[i]);
+
+
+ if (mons_is_mimic( menv[i].type ))
+ mimic_item = true;
+ else if (!mons_flag(menv[i].type, M_NO_EXP_GAIN))
+ {
+ if (menv[i].behaviour == BEH_SLEEP)
+ {
+ strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
+ strcat(info, " doesn't appear to have noticed you.");
+ mpr(info);
+ }
+ // Applies to both friendlies and hostiles
+ else if (menv[i].behaviour == BEH_FLEE)
+ {
+ strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
+ strcat(info, " is fleeing in terror.");
+ mpr(info);
+ }
+ // hostile with target != you
+ else if (!mons_friendly(&menv[i]) && menv[i].foe != MHITYOU)
+ {
+ // special case: batty monsters get set to BEH_WANDER as
+ // part of their special behaviour.
+ if (!testbits(menv[i].flags, MF_BATTY))
+ {
+ strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
+ strcat(info, " doesn't appear to be interested in you.");
+ mpr(info);
+ }
+ }
+ }
+
+ if (menv[i].attitude == ATT_FRIENDLY)
+ {
+ strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
+ strcat(info, " is friendly.");
+ mpr(info);
+ }
+
+ for (int p = 0; p < NUM_MON_ENCHANTS; p++)
+ {
+ strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
+ switch (menv[i].enchantment[p])
+ {
+ case ENCH_YOUR_ROT_I:
+ case ENCH_YOUR_ROT_II:
+ case ENCH_YOUR_ROT_III:
+ case ENCH_YOUR_ROT_IV:
+ strcat(info, " is rotting away."); //jmf: "covered in sores"?
+ break;
+ case ENCH_BACKLIGHT_I:
+ case ENCH_BACKLIGHT_II:
+ case ENCH_BACKLIGHT_III:
+ case ENCH_BACKLIGHT_IV:
+ strcat(info, " is softly glowing.");
+ break;
+ case ENCH_SLOW:
+ strcat(info, " is moving slowly.");
+ break;
+ case ENCH_HASTE:
+ strcat(info, " is moving very quickly.");
+ break;
+ case ENCH_CONFUSION:
+ strcat(info, " appears to be bewildered and confused.");
+ break;
+ case ENCH_INVIS:
+ strcat(info, " is slightly transparent.");
+ break;
+ case ENCH_CHARM:
+ strcat(info, " is in your thrall.");
+ break;
+ case ENCH_YOUR_STICKY_FLAME_I:
+ case ENCH_YOUR_STICKY_FLAME_II:
+ case ENCH_YOUR_STICKY_FLAME_III:
+ case ENCH_YOUR_STICKY_FLAME_IV:
+ case ENCH_STICKY_FLAME_I:
+ case ENCH_STICKY_FLAME_II:
+ case ENCH_STICKY_FLAME_III:
+ case ENCH_STICKY_FLAME_IV:
+ strcat(info, " is covered in liquid flames.");
+ break;
+ default:
+ info[0] = '\0';
+ break;
+ } // end switch
+ if (info[0])
+ mpr(info);
+ }
+
+#if DEBUG_DIAGNOSTICS
+ stethoscope(i);
+#endif
+ }
+
+#if (!DEBUG_DIAGNOSTICS)
+ // removing warning
+ look_clouds:
+#endif
+ if (env.cgrid[mx][my] != EMPTY_CLOUD)
+ {
+ const char cloud_inspected = env.cgrid[mx][my];
+
+ const char cloud_type = env.cloud[ cloud_inspected ].type;
+
+ strcpy(info, "There is a cloud of ");
+ strcat(info,
+ (cloud_type == CLOUD_FIRE
+ || cloud_type == CLOUD_FIRE_MON) ? "flame" :
+ (cloud_type == CLOUD_STINK
+ || cloud_type == CLOUD_STINK_MON) ? "noxious fumes" :
+ (cloud_type == CLOUD_COLD
+ || cloud_type == CLOUD_COLD_MON) ? "freezing vapour" :
+ (cloud_type == CLOUD_POISON
+ || cloud_type == CLOUD_POISON_MON) ? "poison gases" :
+ (cloud_type == CLOUD_GREY_SMOKE
+ || cloud_type == CLOUD_GREY_SMOKE_MON) ? "grey smoke" :
+ (cloud_type == CLOUD_BLUE_SMOKE
+ || cloud_type == CLOUD_BLUE_SMOKE_MON) ? "blue smoke" :
+ (cloud_type == CLOUD_PURP_SMOKE
+ || cloud_type == CLOUD_PURP_SMOKE_MON) ? "purple smoke" :
+ (cloud_type == CLOUD_STEAM
+ || cloud_type == CLOUD_STEAM_MON) ? "steam" :
+ (cloud_type == CLOUD_MIASMA
+ || cloud_type == CLOUD_MIASMA_MON) ? "foul pestilence" :
+ (cloud_type == CLOUD_BLACK_SMOKE
+ || cloud_type == CLOUD_BLACK_SMOKE_MON) ? "black smoke"
+ : "buggy goodness");
+ strcat(info, " here.");
+ mpr(info);
+ }
+
+ int targ_item = igrd[ mx ][ my ];
+
+ if (targ_item != NON_ITEM)
+ {
+ // If a mimic is on this square, we pretend its the first item -- bwr
+ if (mimic_item)
+ mpr("There is something else lying underneath.");
+ else
+ {
+ if (mitm[ targ_item ].base_type == OBJ_GOLD)
+ {
+ mpr( "A pile of gold coins." );
+ }
+ else
+ {
+ strcpy(info, "You see ");
+ it_name( targ_item, DESC_NOCAP_A, str_pass);
+ strcat(info, str_pass);
+ strcat(info, " here.");
+ mpr(info);
+ }
+
+ if (mitm[ targ_item ].link != NON_ITEM)
+ mpr("There is something else lying underneath.");
+ }
+ }
+
+ std::string feature_desc = feature_description(mx, my);
+ mpr(feature_desc.c_str());
+}