summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/directn.cc
diff options
context:
space:
mode:
authordshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2008-04-19 23:13:26 +0000
committerdshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2008-04-19 23:13:26 +0000
commit9ba57ee3a6f72308ff01263dfe143cf23c1cf321 (patch)
tree968cb123684f700f4a612804f8670b0e8e049a93 /crawl-ref/source/directn.cc
parent003c3dc779fbd0ba44bc4fafadafb45db537fda7 (diff)
downloadcrawl-ref-9ba57ee3a6f72308ff01263dfe143cf23c1cf321.tar.gz
crawl-ref-9ba57ee3a6f72308ff01263dfe143cf23c1cf321.zip
Fixes to compile with Visual C++. Moved direct.cc and direct.h to directn.* to avoid conflict with VC++ direct.h header.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@4390 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref/source/directn.cc')
-rw-r--r--crawl-ref/source/directn.cc2436
1 files changed, 2436 insertions, 0 deletions
diff --git a/crawl-ref/source/directn.cc b/crawl-ref/source/directn.cc
new file mode 100644
index 0000000000..f18e22e0f5
--- /dev/null
+++ b/crawl-ref/source/directn.cc
@@ -0,0 +1,2436 @@
+/*
+ * File: direct.cc
+ * Summary: Functions used when picking squares.
+ * Written by: Linley Henzell
+ *
+ * Modified for Crawl Reference by $Author$ on $Date$
+ *
+ * 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 "directn.h"
+#include "format.h"
+
+#include <cstdarg>
+#include <sstream>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <algorithm>
+
+#ifdef DOS
+ #include <conio.h>
+#endif
+
+#include "externs.h"
+
+#include "cio.h"
+#include "command.h"
+#include "debug.h"
+#include "describe.h"
+#include "dungeon.h"
+#include "itemname.h"
+#include "mapmark.h"
+#include "message.h"
+#include "misc.h"
+#include "menu.h"
+#include "monstuff.h"
+#include "mon-util.h"
+#include "player.h"
+#include "shopping.h"
+#include "state.h"
+#include "stuff.h"
+#include "spells4.h"
+#include "stash.h"
+#include "tiles.h"
+#include "terrain.h"
+#include "traps.h"
+#include "travel.h"
+#include "tutorial.h"
+#include "view.h"
+
+#include "macro.h"
+
+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
+};
+
+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, int range );
+static bool find_monster( int x, int y, int mode, int range );
+static bool find_feature( int x, int y, int mode, int range );
+
+static char find_square_wrapper( int tx, int ty,
+ FixedVector<char, 2> &mfp, char direction,
+ bool (*targ)(int, int, int, int),
+ int mode = TARG_ANY, int range = -1,
+ bool wrap = false,
+ int los = LOS_ANY);
+
+static char find_square( int xps, int yps,
+ FixedVector<char, 2> &mfp, int direction,
+ bool (*targ)(int, int, int, int),
+ int mode = TARG_ANY, int range = -1,
+ bool wrap = false,
+ int los = LOS_ANY);
+
+static int targeting_cmd_to_compass( command_type command );
+static void describe_oos_square(int x, int y);
+static void extend_move_to_edge(dist &moves);
+
+void direction_choose_compass( dist& moves, targeting_behaviour *beh)
+{
+ moves.isValid = true;
+ moves.isTarget = false;
+ moves.isMe = false;
+ moves.isCancel = false;
+ moves.dx = moves.dy = 0;
+
+#ifdef USE_TILE
+ mouse_control mc(MOUSE_MODE_TARGET_DIR);
+#endif
+
+ beh->compass = true;
+
+ do
+ {
+ const command_type key_command = beh->get_command();
+
+ if (key_command == CMD_TARGET_SELECT)
+ {
+ moves.dx = moves.dy = 0;
+ moves.isMe = true;
+ break;
+ }
+
+ const int i = targeting_cmd_to_compass(key_command);
+ if ( i != -1 )
+ {
+ moves.dx = Compass[i].x;
+ moves.dy = Compass[i].y;
+ }
+ else if ( key_command == CMD_TARGET_CANCEL )
+ {
+ moves.isCancel = true;
+ moves.isValid = false;
+ }
+ }
+ while ( !moves.isCancel && moves.dx == 0 && moves.dy == 0 );
+
+ return;
+}
+
+static int targeting_cmd_to_compass( command_type command )
+{
+ switch ( command )
+ {
+ case CMD_TARGET_UP: case CMD_TARGET_DIR_UP:
+ return 0;
+ case CMD_TARGET_UP_RIGHT: case CMD_TARGET_DIR_UP_RIGHT:
+ return 1;
+ case CMD_TARGET_RIGHT: case CMD_TARGET_DIR_RIGHT:
+ return 2;
+ case CMD_TARGET_DOWN_RIGHT: case CMD_TARGET_DIR_DOWN_RIGHT:
+ return 3;
+ case CMD_TARGET_DOWN: case CMD_TARGET_DIR_DOWN:
+ return 4;
+ case CMD_TARGET_DOWN_LEFT: case CMD_TARGET_DIR_DOWN_LEFT:
+ return 5;
+ case CMD_TARGET_LEFT: case CMD_TARGET_DIR_LEFT:
+ return 6;
+ case CMD_TARGET_UP_LEFT: case CMD_TARGET_DIR_UP_LEFT:
+ return 7;
+ default:
+ return -1;
+ }
+}
+
+static int targeting_cmd_to_feature( command_type command )
+{
+ switch ( command )
+ {
+ case CMD_TARGET_FIND_TRAP:
+ return '^';
+ case CMD_TARGET_FIND_PORTAL:
+ return '\\';
+ case CMD_TARGET_FIND_ALTAR:
+ return '_';
+ case CMD_TARGET_FIND_UPSTAIR:
+ return '<';
+ case CMD_TARGET_FIND_DOWNSTAIR:
+ return '>';
+ default:
+ return 0;
+ }
+}
+
+static command_type shift_direction(command_type cmd)
+{
+ switch (cmd)
+ {
+ case CMD_TARGET_DOWN_LEFT: return CMD_TARGET_DIR_DOWN_LEFT;
+ case CMD_TARGET_LEFT: return CMD_TARGET_DIR_LEFT;
+ case CMD_TARGET_DOWN: return CMD_TARGET_DIR_DOWN;
+ case CMD_TARGET_UP: return CMD_TARGET_DIR_UP;
+ case CMD_TARGET_RIGHT: return CMD_TARGET_DIR_RIGHT;
+ case CMD_TARGET_DOWN_RIGHT: return CMD_TARGET_DIR_DOWN_RIGHT;
+ case CMD_TARGET_UP_RIGHT: return CMD_TARGET_DIR_UP_RIGHT;
+ case CMD_TARGET_UP_LEFT: return CMD_TARGET_DIR_UP_LEFT;
+ default: return (cmd);
+ }
+}
+
+static const char *target_mode_help_text(int mode)
+{
+ switch (mode)
+ {
+ case DIR_NONE:
+ return Options.target_unshifted_dirs? "? - help" :
+ "? - help, Shift-Dir - shoot in a straight line";
+ case DIR_TARGET:
+ return "? - help, Dir - move target cursor";
+ default:
+ return "? - help";
+ }
+}
+
+static void draw_ray_glyph(const coord_def &pos, int colour,
+ int glych, int mcol)
+{
+#ifdef USE_TILE
+ tile_place_ray(pos);
+#else
+ int mid = mgrd(pos);
+ if (mid != NON_MONSTER)
+ {
+ const monsters *mons = &menv[mid];
+ if (mons->alive() && player_monster_visible(mons))
+ {
+ glych = get_screen_glyph(pos.x, pos.y);
+ colour = mcol;
+ }
+ }
+ const coord_def vp = grid2view(pos);
+ cgotoxy(vp.x, vp.y, GOTO_DNGN);
+ textcolor( real_colour(colour) );
+ putch(glych);
+#endif
+}
+
+// Unseen monsters in shallow water show a "strange disturbance".
+// (Unless flying!)
+static bool _mon_submerged_in_water(const monsters *mon)
+{
+ if (!mon)
+ return false;
+
+ return (grd[mon->x][mon->y] == DNGN_SHALLOW_WATER
+ && see_grid(mon->x, mon->y)
+ && !player_monster_visible(mon)
+ && !mons_flies(mon));
+}
+
+static bool _is_target_in_range(int x, int y, int range)
+{
+ // range doesn't matter
+ if (range == -1)
+ return true;
+
+ return (grid_distance(you.x_pos, you.y_pos, x, y) <= range);
+}
+
+// We handle targeting for repeating commands and re-doing the
+// previous command differently (i.e., not just letting the keys
+// stuffed into the macro buffer replay as-is) because if the player
+// targeted a monster using the movement keys and the monster then
+// moved between repititions, then simply replaying the keys in the
+// buffer will target an empty square.
+static void direction_again(dist& moves, targeting_type restricts,
+ targ_mode_type mode, int range, bool just_looking,
+ const char *prompt, targeting_behaviour *beh)
+{
+ moves.isValid = false;
+ moves.isTarget = false;
+ moves.isMe = false;
+ moves.isCancel = false;
+ moves.isEndpoint = false;
+ moves.choseRay = false;
+
+ if (you.prev_targ == MHITNOT && you.prev_grd_targ == coord_def(0, 0))
+ {
+ moves.isCancel = true;
+ crawl_state.cancel_cmd_repeat();
+ return;
+ }
+
+ int targ_types = 0;
+ if (you.prev_targ != MHITNOT && you.prev_targ != MHITYOU)
+ targ_types++;
+ if (you.prev_targ == MHITYOU)
+ targ_types++;
+ if (you.prev_grd_targ != coord_def(0, 0))
+ targ_types++;
+ ASSERT(targ_types == 1);
+
+ // Discard keys until we get to a set-target command
+ command_type key_command = CMD_NO_CMD;
+
+ while (crawl_state.is_replaying_keys())
+ {
+ key_command = beh->get_command();
+
+ if (key_command == CMD_TARGET_PREV_TARGET
+ || key_command == CMD_TARGET_SELECT_ENDPOINT
+ || key_command == CMD_TARGET_SELECT
+ || key_command == CMD_TARGET_MAYBE_PREV_TARGET)
+ {
+ break;
+ }
+ }
+
+ if (!crawl_state.is_replaying_keys())
+ {
+ moves.isCancel = true;
+
+ mpr("Ran out of keys.");
+
+ return;
+ }
+
+ if (key_command == CMD_TARGET_SELECT_ENDPOINT)
+ moves.isEndpoint = true;
+
+ if (you.prev_grd_targ != coord_def(0, 0))
+ {
+ if (!see_grid(you.prev_grd_targ))
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("You can no longer see the dungeon "
+ "square you previously targeted.");
+ return;
+ }
+ else if (you.prev_grd_targ.x == you.x_pos
+ && you.prev_grd_targ.y == you.y_pos)
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("You are now standing on your "
+ "previously targeted dungeon "
+ "square.");
+ return;
+ }
+ else if (!_is_target_in_range(you.prev_grd_targ.x, you.prev_grd_targ.y,
+ range))
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("Your previous target is now out of "
+ "range.");
+ return;
+ }
+
+ moves.tx = you.prev_grd_targ.x;
+ moves.ty = you.prev_grd_targ.y;
+
+ ray_def ray;
+ find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty, true, ray,
+ 0, true);
+ moves.ray = ray;
+ }
+ else if (you.prev_targ == MHITYOU)
+ {
+ moves.isMe = true;
+ moves.tx = you.x_pos;
+ moves.ty = you.y_pos;
+
+ // Discard 'Y' player gave to yesno()
+ if (mode == TARG_ENEMY)
+ getchm();
+ }
+ else
+ {
+ const monsters *montarget = &menv[you.prev_targ];
+
+ if (!mons_near(montarget)
+ || !player_monster_visible( montarget ))
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("Your target is gone.");
+
+ return;
+ }
+ else if (!_is_target_in_range(you.prev_grd_targ.x, you.prev_grd_targ.y,
+ range))
+ {
+ moves.isCancel = true;
+
+ crawl_state.cancel_cmd_repeat("Your previous target is now out of "
+ "range.");
+ return;
+ }
+
+ moves.tx = montarget->x;
+ moves.ty = montarget->y;
+
+ ray_def ray;
+ find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty, true, ray,
+ 0, true);
+ moves.ray = ray;
+ }
+
+ moves.isValid = true;
+ moves.isTarget = true;
+
+ return;
+}
+
+//---------------------------------------------------------------
+//
+// direction
+//
+// use restrict == DIR_DIR to allow only a compass direction;
+// == DIR_TARGET to allow only choosing a square;
+// == DIR_NONE to allow either.
+//
+// outputs: dist structure:
+//
+// isValid a valid target or direction was chosen
+// isCancel player hit 'escape'
+// isTarget targetting was used
+// choseRay player wants a specific ray
+// ray ...this one
+// isEndpoint player wants the ray to stop on the dime
+// tx,ty target x,y
+// dx,dy direction delta for DIR_DIR
+//
+//---------------------------------------------------------------
+void direction(dist& moves, targeting_type restricts,
+ targ_mode_type mode, int range, bool just_looking,
+ bool needs_path, const char *prompt,
+ targeting_behaviour *beh)
+{
+ static targeting_behaviour stock_behaviour;
+ if (!beh)
+ beh = &stock_behaviour;
+
+ beh->just_looking = just_looking;
+
+ if (crawl_state.is_replaying_keys() && restricts != DIR_DIR)
+ {
+ direction_again(moves, restricts, mode, range, just_looking,
+ prompt, beh);
+ return;
+ }
+
+ // NOTE: Even if just_looking is set, moves is still interesting,
+ // because we can travel there!
+
+ if ( restricts == DIR_DIR )
+ {
+ direction_choose_compass( moves, beh );
+ return;
+ }
+
+ cursor_control con(!Options.use_fake_cursor);
+
+ int dir = 0;
+ bool show_beam = Options.show_beam && !just_looking && needs_path;
+ ray_def ray;
+
+ FixedVector < char, 2 > objfind_pos;
+ FixedVector < char, 2 > monsfind_pos;
+
+ // init
+ moves.dx = moves.dy = 0;
+ moves.tx = you.x_pos;
+ moves.ty = you.y_pos;
+
+ // If we show the beam on startup, we have to initialise it.
+ if ( show_beam )
+ find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty, true, ray);
+
+ bool skip_iter = false;
+ bool found_autotarget = false;
+ bool target_unshifted = Options.target_unshifted_dirs;
+
+ // Find a default target
+ if ( Options.default_target && mode == TARG_ENEMY )
+ {
+ skip_iter = true; // skip first iteration...XXX mega-hack
+ if ( you.prev_targ != MHITNOT && you.prev_targ != MHITYOU )
+ {
+ const monsters *montarget = &menv[you.prev_targ];
+ if ( mons_near(montarget) && player_monster_visible(montarget)
+ && !mons_friendly(montarget) // not made friendly since then
+ && _is_target_in_range(montarget->x, montarget->y, range) )
+ {
+ found_autotarget = true;
+ moves.tx = montarget->x;
+ moves.ty = montarget->y;
+ }
+ }
+ }
+
+ bool show_prompt = true;
+ while (true)
+ {
+ // Prompts might get scrolled off if you have too few lines available.
+ // We'll live with that.
+ if ( !just_looking && (show_prompt || beh->should_redraw()) )
+ {
+ mprf(MSGCH_PROMPT, "%s (%s)", prompt? prompt : "Aim",
+ target_mode_help_text(restricts));
+
+ if ((mode == TARG_ANY || mode == TARG_FRIEND)
+ && moves.tx == you.x_pos && moves.ty == you.y_pos)
+ {
+ terse_describe_square(moves.target());
+ }
+
+ show_prompt = false;
+ }
+
+ // Reinit...this needs to be done every loop iteration
+ // because moves is more persistent than loop_done.
+ moves.isValid = false;
+ moves.isTarget = false;
+ moves.isMe = false;
+ moves.isCancel = false;
+ moves.isEndpoint = false;
+ moves.choseRay = false;
+
+ cursorxy( grid2viewX(moves.tx), grid2viewY(moves.ty) );
+
+ command_type key_command;
+
+ if ( skip_iter )
+ {
+ if ( found_autotarget )
+ key_command = CMD_NO_CMD;
+ else
+ key_command = CMD_TARGET_CYCLE_FORWARD; // find closest enemy
+ }
+ else
+ {
+#ifdef USE_TILE
+ mouse_control mc(MOUSE_MODE_TARGET);
+#endif
+ key_command = beh->get_command();
+ }
+
+#ifdef USE_TILE
+ // if a mouse command, update location to mouse position...
+ if ( key_command == CMD_TARGET_MOUSE_MOVE
+ || key_command == CMD_TARGET_MOUSE_SELECT )
+ {
+ coord_def gc;
+ if (gui_get_mouse_grid_pos(gc))
+ {
+ moves.tx = gc.x;
+ moves.ty = gc.y;
+
+ if (key_command == CMD_TARGET_MOUSE_SELECT)
+ key_command = CMD_TARGET_SELECT;
+ }
+ else
+ {
+ key_command = CMD_NO_CMD;
+ }
+ }
+#endif
+
+ if (target_unshifted && moves.tx == you.x_pos && moves.ty == you.y_pos
+ && restricts != DIR_TARGET)
+ {
+ key_command = shift_direction(key_command);
+ }
+
+ if (target_unshifted
+ && (key_command == CMD_TARGET_CYCLE_FORWARD
+ || key_command == CMD_TARGET_CYCLE_BACK
+ || key_command == CMD_TARGET_OBJ_CYCLE_FORWARD
+ || key_command == CMD_TARGET_OBJ_CYCLE_BACK))
+ {
+ target_unshifted = false;
+ }
+
+ if ( key_command == CMD_TARGET_MAYBE_PREV_TARGET )
+ {
+ if ( moves.tx == you.x_pos && moves.ty == you.y_pos )
+ key_command = CMD_TARGET_PREV_TARGET;
+ else
+ key_command = CMD_TARGET_SELECT;
+ }
+
+ bool need_beam_redraw = false;
+ bool force_redraw = false;
+ bool loop_done = false;
+
+ const int old_tx = moves.tx + (skip_iter ? 500 : 0); // hmmm...hack
+ const int old_ty = moves.ty;
+
+ int i, mid;
+
+ switch ( key_command )
+ {
+ // standard movement
+ case CMD_TARGET_DOWN_LEFT:
+ case CMD_TARGET_DOWN:
+ case CMD_TARGET_DOWN_RIGHT:
+ case CMD_TARGET_LEFT:
+ case CMD_TARGET_RIGHT:
+ case CMD_TARGET_UP_LEFT:
+ case CMD_TARGET_UP:
+ case CMD_TARGET_UP_RIGHT:
+ i = targeting_cmd_to_compass(key_command);
+ moves.tx += Compass[i].x;
+ moves.ty += Compass[i].y;
+ break;
+
+ case CMD_TARGET_DIR_DOWN_LEFT:
+ case CMD_TARGET_DIR_DOWN:
+ case CMD_TARGET_DIR_DOWN_RIGHT:
+ case CMD_TARGET_DIR_LEFT:
+ case CMD_TARGET_DIR_RIGHT:
+ case CMD_TARGET_DIR_UP_LEFT:
+ case CMD_TARGET_DIR_UP:
+ case CMD_TARGET_DIR_UP_RIGHT:
+ i = targeting_cmd_to_compass(key_command);
+
+ if ( restricts != DIR_TARGET )
+ {
+ // A direction is allowed, and we've selected it.
+ moves.dx = Compass[i].x;
+ moves.dy = Compass[i].y;
+ // Needed for now...eventually shouldn't be necessary
+ moves.tx = you.x_pos + moves.dx;
+ moves.ty = you.y_pos + moves.dy;
+ moves.isValid = true;
+ moves.isTarget = false;
+ show_beam = false;
+ moves.choseRay = false;
+ loop_done = true;
+ }
+ else
+ {
+ // Direction not allowed, so just move in that direction.
+ // Maybe make this a bigger jump?
+ if (restricts == DIR_TARGET)
+ {
+ moves.tx += Compass[i].x * 3;
+ moves.ty += Compass[i].y * 3;
+ }
+ else
+ {
+ moves.tx += Compass[i].x;
+ moves.ty += Compass[i].y;
+ }
+ }
+ break;
+
+#ifdef WIZARD
+ case CMD_TARGET_CYCLE_BEAM:
+ show_beam = find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty,
+ true, ray, (show_beam ? 1 : 0));
+ need_beam_redraw = true;
+ break;
+#endif
+
+ case CMD_TARGET_HIDE_BEAM:
+ if (show_beam)
+ {
+ show_beam = false;
+ need_beam_redraw = true;
+ }
+ else
+ {
+ if (!needs_path)
+ {
+ mprf(MSGCH_EXAMINE_FILTER,
+ "This spell doesn't need a beam path.");
+ break;
+ }
+
+ show_beam = find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty,
+ true, ray, 0, true);
+ need_beam_redraw = show_beam;
+ }
+ break;
+
+ case CMD_TARGET_FIND_YOU:
+ moves.tx = you.x_pos;
+ moves.ty = you.y_pos;
+ moves.dx = 0;
+ moves.dy = 0;
+ break;
+
+ case CMD_TARGET_FIND_TRAP:
+ case CMD_TARGET_FIND_PORTAL:
+ case CMD_TARGET_FIND_ALTAR:
+ case CMD_TARGET_FIND_UPSTAIR:
+ case CMD_TARGET_FIND_DOWNSTAIR:
+ {
+ const int thing_to_find = targeting_cmd_to_feature(key_command);
+ if (find_square_wrapper(moves.tx, moves.ty, objfind_pos, 1,
+ find_feature, thing_to_find, range, true,
+ Options.target_los_first ?
+ LOS_FLIPVH : LOS_ANY))
+ {
+ moves.tx = objfind_pos[0];
+ moves.ty = objfind_pos[1];
+ }
+ else
+ {
+ if (!skip_iter)
+ flush_input_buffer(FLUSH_ON_FAILURE);
+ }
+ break;
+ }
+
+ case CMD_TARGET_CYCLE_TARGET_MODE:
+ mode = static_cast<targ_mode_type>((mode + 1) % TARG_NUM_MODES);
+ mprf( "Targeting mode is now: %s",
+ (mode == TARG_ANY) ? "any" :
+ (mode == TARG_ENEMY) ? "enemies"
+ : "friends" );
+ break;
+
+ case CMD_TARGET_PREV_TARGET:
+ // Do we have a previous target?
+ if (you.prev_targ == MHITNOT || you.prev_targ == MHITYOU)
+ {
+ mpr("You haven't got a previous target.", MSGCH_EXAMINE_FILTER);
+ break;
+ }
+
+ // we have a valid previous target (maybe)
+ {
+ const monsters *montarget = &menv[you.prev_targ];
+
+ if (!mons_near(montarget)
+ || !player_monster_visible( montarget ))
+ {
+ mpr("You can't see that creature any more.",
+ MSGCH_EXAMINE_FILTER);
+ }
+ else
+ {
+ // We have all the information we need
+ moves.isValid = true;
+ moves.isTarget = true;
+ moves.tx = montarget->x;
+ moves.ty = montarget->y;
+ if ( !just_looking )
+ {
+ // We have to turn off show_beam, because
+ // when jumping to a previous target we don't
+ // care about the beam; otherwise Bad Things
+ // will happen because the ray is invalid,
+ // and we don't get a chance to update it before
+ // breaking from the loop.
+ show_beam = false;
+ loop_done = true;
+ }
+ }
+ break;
+ }
+
+ case CMD_TARGET_SELECT_ENDPOINT:
+ moves.isEndpoint = true;
+ // intentional fall-through
+ case CMD_TARGET_SELECT: // finalize current choice
+ if (!moves.isEndpoint
+ && mgrd[moves.tx][moves.ty] != NON_MONSTER
+ && _mon_submerged_in_water(&menv[mgrd[moves.tx][moves.ty]]))
+ {
+ moves.isEndpoint = true;
+ }
+ moves.isValid = true;
+ moves.isTarget = true;
+ loop_done = true;
+
+ you.prev_grd_targ = coord_def(0, 0);
+
+ // maybe we should except just_looking here?
+ mid = mgrd[moves.tx][moves.ty];
+
+ if ( mid != NON_MONSTER )
+ you.prev_targ = mid;
+ else if (moves.tx == you.x_pos && moves.ty == you.y_pos)
+ you.prev_targ = MHITYOU;
+ else
+ you.prev_grd_targ = coord_def(moves.tx, moves.ty);
+ break;
+
+ case CMD_TARGET_OBJ_CYCLE_BACK:
+ case CMD_TARGET_OBJ_CYCLE_FORWARD:
+ dir = (key_command == CMD_TARGET_OBJ_CYCLE_BACK) ? -1 : 1;
+ if (find_square_wrapper( moves.tx, moves.ty, objfind_pos, dir,
+ find_object, 0, range, true,
+ Options.target_los_first
+ ? (dir == 1? LOS_FLIPVH : LOS_FLIPHV)
+ : LOS_ANY))
+ {
+ moves.tx = objfind_pos[0];
+ moves.ty = objfind_pos[1];
+ }
+ else if (!skip_iter)
+ flush_input_buffer(FLUSH_ON_FAILURE);
+
+ break;
+
+ case CMD_TARGET_CYCLE_FORWARD:
+ case CMD_TARGET_CYCLE_BACK:
+ dir = (key_command == CMD_TARGET_CYCLE_BACK) ? -1 : 1;
+ if (find_square_wrapper( moves.tx, moves.ty, monsfind_pos, dir,
+ find_monster, mode, range,
+ Options.target_wrap))
+ {
+ moves.tx = monsfind_pos[0];
+ moves.ty = monsfind_pos[1];
+ }
+ else if (!skip_iter)
+ flush_input_buffer(FLUSH_ON_FAILURE);
+
+ break;
+
+ case CMD_TARGET_CANCEL:
+ loop_done = true;
+ moves.isCancel = true;
+ break;
+
+#ifdef WIZARD
+ case CMD_TARGET_WIZARD_MAKE_FRIENDLY:
+ // Maybe we can skip this check...but it can't hurt
+ if (!you.wizard || !in_bounds(moves.tx, moves.ty))
+ break;
+ mid = mgrd[moves.tx][moves.ty];
+ if (mid == NON_MONSTER) // can put in terrain description here
+ break;
+
+ {
+ monsters &m = menv[mid];
+ switch (m.attitude)
+ {
+ case ATT_FRIENDLY:
+ m.attitude = ATT_NEUTRAL;
+ m.flags &= ~MF_CREATED_FRIENDLY;
+ m.flags |= MF_WAS_NEUTRAL;
+ break;
+ case ATT_NEUTRAL:
+ m.attitude = ATT_HOSTILE;
+ m.flags &= ~MF_WAS_NEUTRAL;
+ break;
+ case ATT_HOSTILE:
+ m.attitude = ATT_FRIENDLY;
+ m.flags |= MF_CREATED_FRIENDLY;
+ break;
+ default:
+ break;
+ }
+
+ // To update visual branding of friendlies. Only
+ // seem capabable of adding bolding, not removing it,
+ // though.
+ viewwindow(true, false);
+ }
+ break;
+
+ case CMD_TARGET_WIZARD_MAKE_SHOUT:
+ // Maybe we can skip this check...but it can't hurt
+ if (!you.wizard || !in_bounds(moves.tx, moves.ty))
+ break;
+ mid = mgrd[moves.tx][moves.ty];
+ if (mid == NON_MONSTER) // can put in terrain description here
+ break;
+
+ debug_make_monster_shout(&menv[mid]);
+ break;
+
+ case CMD_TARGET_WIZARD_GIVE_ITEM:
+ if (!you.wizard || !in_bounds(moves.tx, moves.ty))
+ break;
+ mid = mgrd[moves.tx][moves.ty];
+ if (mid == NON_MONSTER) // can put in terrain description here
+ break;
+
+ wizard_give_monster_item(&menv[mid]);
+ break;
+#endif
+
+ case CMD_TARGET_DESCRIBE:
+ full_describe_square(moves.target());
+ force_redraw = true;
+ break;
+
+ case CMD_TARGET_HELP:
+ show_targeting_help();
+ force_redraw = true;
+ redraw_screen();
+ mesclr(true);
+ show_prompt = true;
+ break;
+
+ default:
+ break;
+ }
+
+ if ( loop_done == true )
+ {
+ // This is where we either finalize everything, or else
+ // decide that we're not really done and continue looping.
+
+ if ( just_looking ) // easy out
+ break;
+
+ // A bunch of confirmation tests; if we survive them all,
+ // then break out.
+
+ // Confirm self-targeting on TARG_ENEMY.
+ // Conceivably we might want to confirm on TARG_ANY too.
+ if ( moves.isTarget
+ && moves.tx == you.x_pos && moves.ty == you.y_pos
+ && mode == TARG_ENEMY
+ && !yesno("Really target yourself?", false, 'n'))
+ {
+ mesclr();
+ show_prompt = true;
+ }
+ else if ( moves.isTarget && !see_grid(moves.tx, moves.ty) )
+ {
+ mpr("Sorry, you can't target what you can't see.",
+ MSGCH_EXAMINE_FILTER);
+ }
+ // Ask for confirmation if we're quitting for some odd reason
+ else if ( moves.isValid || moves.isCancel
+ || yesno("Are you sure you want to fizzle?", false, 'n') )
+ {
+ // Finalize whatever is inside the loop
+ // (moves-internal finalizations can be done later)
+ moves.choseRay = show_beam;
+ moves.ray = ray;
+ break;
+ }
+ }
+
+ // We'll go on looping. Redraw whatever is necessary.
+
+ if ( !in_viewport_bounds(grid2viewX(moves.tx), grid2viewY(moves.ty)) )
+ {
+ // Tried to step out of bounds
+ moves.tx = old_tx;
+ moves.ty = old_ty;
+ }
+
+ bool have_moved = false;
+
+ if ( old_tx != moves.tx || old_ty != moves.ty )
+ {
+ have_moved = true;
+ show_beam = show_beam &&
+ find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty, true, ray,
+ 0, true);
+ }
+
+ if ( force_redraw )
+ have_moved = true;
+
+ if ( have_moved )
+ {
+ // If the target x,y has changed, the beam must have changed.
+ if ( show_beam )
+ need_beam_redraw = true;
+
+ if ( !skip_iter ) // don't clear before we get a chance to see
+ mesclr(true); // maybe not completely necessary
+
+ terse_describe_square(moves.target());
+ }
+
+#ifdef USE_TILE
+ // tiles always need a beam redraw if show_beam is true (and if valid...)
+ if ( need_beam_redraw
+ || show_beam && find_ray(you.x_pos, you.y_pos, moves.tx, moves.ty,
+ true, ray, 0, true) )
+ {
+#else
+ if ( need_beam_redraw )
+ {
+ viewwindow(true, false);
+#endif
+ if ( show_beam
+ && in_vlos(grid2viewX(moves.tx), grid2viewY(moves.ty))
+ && moves.target() != you.pos() )
+ {
+ // Draw the new ray with magenta '*'s, not including
+ // your square or the target square.
+ ray_def raycopy = ray; // temporary copy to work with
+ while ( raycopy.pos() != moves.target() )
+ {
+ if ( raycopy.pos() != you.pos() )
+ {
+ // Sanity: don't loop forever if the ray is problematic
+ if ( !in_los(raycopy.x(), raycopy.y()) )
+ break;
+ draw_ray_glyph(raycopy.pos(), MAGENTA, '*',
+ MAGENTA | COLFLAG_REVERSE);
+ }
+ raycopy.advance_through(moves.target());
+ }
+ textcolor(LIGHTGREY);
+#ifdef USE_TILE
+ draw_ray_glyph(moves.target(), MAGENTA, '*',
+ MAGENTA | COLFLAG_REVERSE);
+ }
+ viewwindow(true, false);
+#else
+ }
+#endif
+
+ }
+ skip_iter = false; // only skip one iteration at most
+ }
+ moves.isMe = (moves.tx == you.x_pos && moves.ty == you.y_pos);
+ mesclr();
+
+ // We need this for directional explosions, otherwise they'll explode one
+ // square away from the player.
+ extend_move_to_edge(moves);
+}
+
+void terse_describe_square(const coord_def &c)
+{
+ if (!see_grid(c.x, c.y))
+ describe_oos_square(c.x, c.y);
+ else if (in_bounds(c) )
+ describe_cell(c.x, c.y);
+}
+
+void full_describe_square(const coord_def &c)
+{
+ // Don't give out information for things outside LOS
+ if (!see_grid(c.x, c.y))
+ return;
+
+ const int mid = mgrd(c);
+ const int oid = igrd(c);
+
+ if (mid != NON_MONSTER && player_monster_visible(&menv[mid]))
+ {
+ // First priority: monsters
+ describe_monsters(menv[mid]);
+ }
+ else if (oid != NON_ITEM)
+ {
+ // Second priority: objects
+ describe_item( mitm[oid] );
+ }
+ else
+ {
+ // Third priority: features
+ describe_feature_wide(c.x, c.y);
+ }
+
+ redraw_screen();
+ mesclr(true);
+}
+
+static void extend_move_to_edge(dist &moves)
+{
+ if (!moves.dx && !moves.dy)
+ return;
+
+ // now the tricky bit - extend the target x,y out to map edge.
+ int mx = 0, 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)
+{
+ mpr("You can't see that place.", MSGCH_EXAMINE_FILTER);
+
+ if (!in_bounds(x, y) || !is_terrain_seen(x, y))
+ return;
+
+ describe_stash(x, y);
+ describe_feature(x, y, true);
+}
+
+bool in_vlos(int x, int y)
+{
+ return (in_los_bounds(x, y)
+ && (env.show(view2show(coord_def(x, y)))
+ || coord_def(x, y) == grid2view(you.pos())));
+}
+
+bool in_vlos(const coord_def &pos)
+{
+ return (in_vlos(pos.x, pos.y));
+}
+
+bool in_los(int x, int y)
+{
+ return (in_vlos(grid2view(coord_def(x, y))));
+}
+
+static bool find_monster( int x, int y, int mode, int range = -1)
+{
+ // target the player for friendly and general spells
+ if ((mode == TARG_FRIEND || mode == TARG_ANY)
+ && x == you.x_pos && y == you.y_pos)
+ {
+ return (true);
+ }
+
+ // don't target out of range
+ if (!_is_target_in_range(x, y, range))
+ return (false);
+
+ const int targ_mon = mgrd[ x ][ y ];
+
+ // No monster or outside LOS.
+ if (targ_mon == NON_MONSTER || !in_los(x,y))
+ return (false);
+
+ monsters *mon = &menv[targ_mon];
+
+ // Unknown mimics don't count as monsters, either.
+ if (mons_is_mimic(mon->type)
+ && !(mon->flags & MF_KNOWN_MIMIC))
+ {
+ return (false);
+ }
+
+ // Don't usually target unseen monsters...
+ if (!player_monster_visible(mon))
+ {
+ // ... unless it creates a "disturbance in the water".
+ // Since you can't see the monster, assume it's not a friend.
+ // Also don't target submerged monsters if there are other targets
+ // in sight. (This might be too restrictive.)
+ return (mode != TARG_FRIEND
+ && _mon_submerged_in_water(mon)
+ && i_feel_safe(false, false, true, range));
+ }
+
+ // Now compare target modes.
+ if (mode == TARG_ANY)
+ return true;
+
+ if (mode == TARG_FRIEND)
+ return (mons_friendly(&menv[targ_mon] ));
+
+ ASSERT(mode == TARG_ENEMY);
+ if (mons_friendly(&menv[targ_mon]))
+ return false;
+
+ // Don't target zero xp monsters, unless target_zero_exp is set.
+ return (Options.target_zero_exp
+ || !mons_class_flag( menv[targ_mon].type, M_NO_EXP_GAIN ));
+}
+
+static bool find_feature( int x, int y, int mode, int /* range */)
+{
+ // The stair need not be in LOS if the square is mapped.
+ if (!in_los(x, y) && (!Options.target_oos || !is_terrain_seen(x, y)))
+ return (false);
+
+ return is_feature(mode, x, y);
+}
+
+static bool find_object(int x, int y, int mode, int /* range */)
+{
+ // First, check for mimics.
+ bool is_mimic = false;
+ const int mons = mgrd[ x ][ y ];
+ if (mons != NON_MONSTER
+ && player_monster_visible( &(menv[mons]) )
+ && mons_is_mimic(menv[mons].type)
+ && !(menv[mons].flags & MF_KNOWN_MIMIC))
+ {
+ is_mimic = true;
+ }
+
+ const int item = igrd[x][y];
+ if (item == NON_ITEM && !is_mimic)
+ return false;
+
+ return (in_los(x, y) || Options.target_oos && is_terrain_seen(x, y)
+ && (is_stash(x, y) || is_mimic));
+}
+
+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 crawl_view.in_view_viewport(coord_def(x, y));
+}
+
+bool in_los_bounds(int x, int y)
+{
+ return crawl_view.in_view_los(coord_def(x, y));
+}
+
+//---------------------------------------------------------------
+//
+// 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( int xps, int yps,
+ FixedVector<char, 2> &mfp, int direction,
+ bool (*find_targ)( int x, int y, int mode, int range ),
+ int mode, int range, bool wrap, int los )
+{
+ // the day will come when [unsigned] chars will be consigned to
+ // the fires of Gehenna. Not quite yet, though.
+
+ int temp_xps = xps;
+ int temp_yps = yps;
+ int x_change = 0;
+ int 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.
+ const bool vis = (env.show(view2show(coord_def(xps, yps)))
+ || view2grid(coord_def(xps, yps)) == you.pos());
+
+ 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);
+
+ int radius = 0;
+ if (crawl_view.viewsz.x > crawl_view.viewsz.y)
+ radius = crawl_view.viewsz.x - LOS_RADIUS - 1;
+ else
+ radius = crawl_view.viewsz.y - LOS_RADIUS - 1;
+
+ const coord_def vyou = grid2view(you.pos());
+
+ const int minx = vyou.x - radius, maxx = vyou.x + radius,
+ miny = vyou.y - radius, maxy = vyou.y + radius,
+ ctrx = vyou.x, ctry = vyou.y;
+
+ while (temp_xps >= minx - 1 && temp_xps <= maxx
+ && temp_yps <= maxy && temp_yps >= miny - 1)
+ {
+ if (direction == 1 && temp_xps == minx && temp_yps == maxy)
+ {
+ if (find_targ(you.x_pos, you.y_pos, mode, range))
+ {
+ mfp[0] = ctrx;
+ mfp[1] = ctry;
+ return (1);
+ }
+ return find_square(ctrx, ctry, mfp, direction, find_targ, mode,
+ range, 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,
+ range, 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
+ && 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 (!crawl_view.in_grid_viewport(coord_def(targ_x, targ_y)))
+ continue;
+
+ if (!in_bounds(targ_x, targ_y))
+ continue;
+
+ if ((onlyVis || onlyHidden) && onlyVis != in_los(targ_x, targ_y))
+ continue;
+
+ if (find_targ(targ_x, targ_y, mode, range))
+ {
+ mfp[0] = temp_xps;
+ mfp[1] = temp_yps;
+ return (1);
+ }
+ }
+
+ return (direction == 1?
+ find_square(ctrx, ctry, mfp, direction, find_targ, mode, range, false,
+ next_los(direction, los, wrap))
+ : find_square(minx, maxy, mfp, direction, find_targ, mode, range, false,
+ next_los(direction, los, wrap)));
+}
+
+// XXX Unbelievably hacky. And to think that my goal was to clean up the code.
+// Identical to find_square, except that input (tx, ty) and output
+// (mfp) are in grid coordinates rather than view coordinates.
+static char find_square_wrapper( int tx, int ty,
+ FixedVector<char, 2> &mfp, char direction,
+ bool (*find_targ)( int x, int y, int mode,
+ int range ),
+ int mode, int range, bool wrap, int los )
+{
+ const char r = find_square(grid2viewX(tx), grid2viewY(ty), mfp,
+ direction, find_targ, mode, range, wrap, los);
+ mfp[0] = view2gridX(mfp[0]);
+ mfp[1] = view2gridY(mfp[1]);
+ return r;
+}
+
+static void describe_feature(int mx, int my, bool oos)
+{
+ if (oos && !is_terrain_seen(mx, my))
+ return;
+
+ dungeon_feature_type grid = grd[mx][my];
+ if ( grid == DNGN_SECRET_DOOR )
+ grid = grid_secret_door_appearance(mx, my);
+
+ std::string desc = feature_description(grid);
+ if (desc.length())
+ {
+ if (oos)
+ desc = "[" + desc + "]";
+
+ msg_channel_type channel = MSGCH_EXAMINE;
+ if (oos || grid == DNGN_FLOOR)
+ channel = MSGCH_EXAMINE_FILTER;
+
+ mpr(desc.c_str(), channel);
+ }
+}
+
+// Returns a vector of features matching the given pattern.
+std::vector<dungeon_feature_type> features_by_desc(const base_pattern &pattern)
+{
+ std::vector<dungeon_feature_type> features;
+
+ if (pattern.valid())
+ {
+ for (int i = 0; i < NUM_FEATURES; ++i)
+ {
+ std::string fdesc =
+ feature_description(static_cast<dungeon_feature_type>(i));
+ if (fdesc.empty())
+ continue;
+
+ if (pattern.matches( fdesc ))
+ features.push_back( dungeon_feature_type(i) );
+ }
+ }
+ return (features);
+}
+
+void describe_floor()
+{
+ const int grid = grd(you.pos());
+
+ std::string prefix = "There is ";
+ std::string feat;
+ std::string suffix = " here.";
+ switch (grid)
+ {
+ case DNGN_FLOOR:
+ case DNGN_FLOOR_SPECIAL:
+ return;
+
+ case DNGN_ENTER_SHOP:
+ prefix = "There is an entrance to ";
+ break;
+
+ default:
+ break;
+ }
+
+ feat = feature_description(you.x_pos, you.y_pos,
+ is_bloodcovered(you.x_pos, you.y_pos),
+ DESC_NOCAP_A, false);
+ if (feat.empty())
+ return;
+
+ msg_channel_type channel = MSGCH_EXAMINE;
+
+ // water is not terribly important if you don't mind it
+ if ((grd[you.x_pos][you.y_pos] == DNGN_DEEP_WATER
+ || grd[you.x_pos][you.y_pos] == DNGN_SHALLOW_WATER)
+ && player_likes_water())
+ {
+ channel = MSGCH_EXAMINE_FILTER;
+ }
+ mpr((prefix + feat + suffix).c_str(), channel);
+ if (grid == DNGN_ENTER_LABYRINTH && you.is_undead != US_UNDEAD)
+ mpr("Beware, for starvation awaits!", MSGCH_EXAMINE);
+}
+
+static std::string feature_do_grammar(description_level_type dtype,
+ bool add_stop,
+ bool force_article,
+ std::string desc)
+{
+ if (add_stop)
+ desc += ".";
+ if (dtype == DESC_PLAIN || (!force_article && isupper(desc[0])))
+ {
+ if (isupper(desc[0]))
+ {
+ switch (dtype)
+ {
+ case DESC_PLAIN: case DESC_NOCAP_THE: case DESC_NOCAP_A:
+ desc[0] = tolower(desc[0]);
+ break;
+ default:
+ break;
+ }
+ }
+ return (desc);
+ }
+
+ switch (dtype)
+ {
+ case DESC_CAP_THE:
+ return "The " + desc;
+ case DESC_NOCAP_THE:
+ return "the " + desc;
+ case DESC_CAP_A:
+ return article_a(desc, false);
+ case DESC_NOCAP_A:
+ return article_a(desc, true);
+ case DESC_NONE:
+ return ("");
+ default:
+ return (desc);
+ }
+}
+
+std::string feature_description(dungeon_feature_type grid,
+ trap_type trap, bool bloody,
+ description_level_type dtype,
+ bool add_stop)
+{
+ std::string desc = raw_feature_description(grid, trap);
+ if (bloody)
+ desc += ", spattered with blood";
+
+ return feature_do_grammar(dtype, add_stop, grid_is_trap(grid), desc);
+}
+
+std::string raw_feature_description(dungeon_feature_type grid,
+ trap_type trap)
+{
+ if (grid_is_trap(grid) && trap != NUM_TRAPS)
+ {
+ switch (trap)
+ {
+ case TRAP_DART:
+ return ("dart trap");
+ case TRAP_ARROW:
+ return ("arrow trap");
+ case TRAP_SPEAR:
+ return ("spear trap");
+ case TRAP_AXE:
+ return ("axe trap");
+ case TRAP_TELEPORT:
+ return ("teleportation trap");
+ case TRAP_ALARM:
+ return ("alarm trap");
+ case TRAP_BLADE:
+ return ("blade trap");
+ case TRAP_BOLT:
+ return ("bolt trap");
+ case TRAP_NET:
+ return ("net trap");
+ case TRAP_ZOT:
+ return ("Zot trap");
+ case TRAP_NEEDLE:
+ return ("needle trap");
+ case TRAP_SHAFT:
+ return ("shaft");
+ default:
+ error_message_to_player();
+ return ("undefined trap");
+ }
+ }
+
+ switch (grid)
+ {
+ case DNGN_STONE_WALL:
+ return ("stone wall");
+ case DNGN_ROCK_WALL:
+ case DNGN_SECRET_DOOR:
+ if (you.level_type == LEVEL_PANDEMONIUM)
+ return ("wall of the weird stuff which makes up Pandemonium");
+ else
+ return ("rock wall");
+ case DNGN_PERMAROCK_WALL:
+ return ("unnaturally hard rock wall");
+ case DNGN_CLOSED_DOOR:
+ return ("closed door");
+ case DNGN_METAL_WALL:
+ return ("metal wall");
+ case DNGN_GREEN_CRYSTAL_WALL:
+ return ("wall of green crystal");
+ case DNGN_CLEAR_ROCK_WALL:
+ return ("translucent rock wall");
+ case DNGN_CLEAR_STONE_WALL:
+ return ("translucent stone wall");
+ case DNGN_CLEAR_PERMAROCK_WALL:
+ return ("translucent unnaturally hard rock wall");
+ case DNGN_ORCISH_IDOL:
+ if (you.species == SP_HILL_ORC)
+ return ("idol of Beogh");
+ else
+ return ("orcish idol");
+ case DNGN_WAX_WALL:
+ return ("wall of solid wax");
+ case DNGN_GRANITE_STATUE:
+ return ("granite 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:
+ case DNGN_FLOOR_SPECIAL:
+ return ("Floor");
+ case DNGN_OPEN_DOOR:
+ return ("open door");
+ case DNGN_ESCAPE_HATCH_DOWN:
+ return ("escape hatch in the floor");
+ case DNGN_ESCAPE_HATCH_UP:
+ return ("escape hatch in the ceiling");
+ case DNGN_STONE_STAIRS_DOWN_I:
+ case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III:
+ return ("stone staircase leading down");
+ case DNGN_STONE_STAIRS_UP_I:
+ case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III:
+ return ("stone staircase leading up");
+ case DNGN_ENTER_HELL:
+ return ("gateway to Hell");
+ case DNGN_TRAP_MECHANICAL:
+ return ("mechanical trap");
+ case DNGN_TRAP_MAGICAL:
+ return ("magical trap");
+ case DNGN_TRAP_NATURAL:
+ return ("natural trap");
+ case DNGN_ENTER_SHOP:
+ return ("shop");
+ case DNGN_ENTER_LABYRINTH:
+ return ("labyrinth entrance");
+ case DNGN_ENTER_DIS:
+ return ("gateway to the Iron City of Dis");
+ case DNGN_ENTER_GEHENNA:
+ return ("gateway to Gehenna");
+ case DNGN_ENTER_COCYTUS:
+ return ("gateway to the freezing wastes of Cocytus");
+ case DNGN_ENTER_TARTARUS:
+ return ("gateway to the decaying netherworld of Tartarus");
+ case DNGN_ENTER_ABYSS:
+ return ("one-way gate to the infinite horrors of the Abyss");
+ case DNGN_EXIT_ABYSS:
+ return ("gateway leading out of the Abyss");
+ case DNGN_STONE_ARCH:
+ return ("empty arch of ancient stone");
+ case DNGN_ENTER_PANDEMONIUM:
+ return ("gate leading to the halls of Pandemonium");
+ case DNGN_EXIT_PANDEMONIUM:
+ return ("gate leading out of Pandemonium");
+ case DNGN_TRANSIT_PANDEMONIUM:
+ return ("gate leading to another region of Pandemonium");
+ case DNGN_ENTER_ORCISH_MINES:
+ return ("staircase to the Orcish Mines");
+ case DNGN_ENTER_HIVE:
+ return ("staircase to the Hive");
+ case DNGN_ENTER_LAIR:
+ return ("staircase to the Lair");
+ case DNGN_ENTER_SLIME_PITS:
+ return ("staircase to the Slime Pits");
+ case DNGN_ENTER_VAULTS:
+ return ("staircase to the Vaults");
+ case DNGN_ENTER_CRYPT:
+ return ("staircase to the Crypt");
+ case DNGN_ENTER_HALL_OF_BLADES:
+ return ("staircase to the Hall of Blades");
+ case DNGN_ENTER_ZOT:
+ return ("gate to the Realm of Zot");
+ case DNGN_ENTER_TEMPLE:
+ return ("staircase to the Ecumenical Temple");
+ case DNGN_ENTER_SNAKE_PIT:
+ return ("staircase to the Snake Pit");
+ case DNGN_ENTER_ELVEN_HALLS:
+ return ("staircase to the Elven Halls");
+ case DNGN_ENTER_TOMB:
+ return ("staircase to the Tomb");
+ case DNGN_ENTER_SWAMP:
+ return ("staircase to the Swamp");
+ case DNGN_ENTER_SHOALS:
+ return ("staircase to the Shoals");
+ case DNGN_ENTER_PORTAL_VAULT:
+ return ("gate leading to a distant place");
+ case DNGN_EXIT_PORTAL_VAULT:
+ return ("gate leading back to the Dungeon");
+ 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 ("staircase back to the Dungeon");
+ case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_SWAMP:
+ case DNGN_RETURN_FROM_SHOALS:
+ return ("staircase back to the Lair");
+ case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES:
+ return ("staircase back to the Vaults");
+ case DNGN_RETURN_FROM_ELVEN_HALLS:
+ return ("staircase back to the Mines");
+ case DNGN_RETURN_FROM_TOMB:
+ return ("staircase back to the Crypt");
+ case DNGN_RETURN_FROM_ZOT:
+ return ("gate leading back out of this place");
+ case DNGN_ALTAR_ZIN:
+ return ("glowing white marble altar of Zin");
+ case DNGN_ALTAR_SHINING_ONE:
+ return ("glowing golden altar of the Shining One");
+ case DNGN_ALTAR_KIKUBAAQUDGHA:
+ return ("ancient bone altar of Kikubaaqudgha");
+ case DNGN_ALTAR_YREDELEMNUL:
+ return ("basalt altar of Yredelemnul");
+ case DNGN_ALTAR_XOM:
+ return ("shimmering altar of Xom");
+ case DNGN_ALTAR_VEHUMET:
+ return ("shining altar of Vehumet");
+ case DNGN_ALTAR_OKAWARU:
+ return ("iron altar of Okawaru");
+ case DNGN_ALTAR_MAKHLEB:
+ return ("burning altar of Makhleb");
+ case DNGN_ALTAR_SIF_MUNA:
+ return ("deep blue altar of Sif Muna");
+ case DNGN_ALTAR_TROG:
+ return ("bloodstained altar of Trog");
+ case DNGN_ALTAR_NEMELEX_XOBEH:
+ return ("sparkling altar of Nemelex Xobeh");
+ case DNGN_ALTAR_ELYVILON:
+ return ("silver altar of Elyvilon");
+ case DNGN_ALTAR_LUGONU:
+ return ("corrupted altar of Lugonu");
+ case DNGN_ALTAR_BEOGH:
+ return ("roughly hewn altar of Beogh");
+ case DNGN_FOUNTAIN_BLUE:
+ return ("fountain of clear blue water");
+ case DNGN_FOUNTAIN_SPARKLING:
+ return ("fountain of sparkling water");
+ case DNGN_FOUNTAIN_BLOOD:
+ return ("fountain of blood");
+ case DNGN_DRY_FOUNTAIN_BLUE:
+ case DNGN_DRY_FOUNTAIN_SPARKLING:
+ case DNGN_DRY_FOUNTAIN_BLOOD:
+ case DNGN_PERMADRY_FOUNTAIN:
+ return ("dry fountain");
+ default:
+ return ("");
+ }
+}
+
+static std::string marker_feature_description(const coord_def &p)
+{
+ std::vector<map_marker*> markers = env.markers.get_markers_at(p);
+ for (int i = 0, size = markers.size(); i < size; ++i)
+ {
+ const std::string desc = markers[i]->feature_description();
+ if (!desc.empty())
+ return (desc);
+ }
+ return ("");
+}
+
+#ifndef DEBUG_DIAGNOSTICS
+// Is a feature interesting enough to 'v'iew it, even if a player normally
+// doesn't care about descriptions, i.e. does the description hold important
+// information? (Yes, this is entirely subjective. JPEG)
+
+static bool interesting_feature(dungeon_feature_type feat)
+{
+ return (get_feature_def(feat).flags & FFT_EXAMINE_HINT);
+}
+#endif
+
+std::string feature_description(int mx, int my, bool bloody,
+ description_level_type dtype, bool add_stop)
+{
+ dungeon_feature_type grid = grd[mx][my];
+ if ( grid == DNGN_SECRET_DOOR )
+ grid = grid_secret_door_appearance(mx, my);
+
+ if ( grid == DNGN_OPEN_DOOR || grid == DNGN_CLOSED_DOOR )
+ {
+ std::set<coord_def> all_door;
+ find_connected_identical(coord_def(mx, my), grd[mx][my], all_door);
+ const char *adj, *noun;
+ get_door_description(all_door.size(), &adj, &noun);
+
+ std::string desc = adj;
+ desc += (grid == DNGN_OPEN_DOOR) ? "open " : "closed ";
+ desc += noun;
+
+ if (bloody)
+ desc += ", spattered with blood";
+
+ return feature_do_grammar(dtype, add_stop, false, desc);
+ }
+
+ switch (grid)
+ {
+ case DNGN_TRAP_MECHANICAL:
+ case DNGN_TRAP_MAGICAL:
+ case DNGN_TRAP_NATURAL:
+ return (feature_description(grid, trap_type_at_xy(mx, my), bloody,
+ dtype, add_stop));
+ case DNGN_ENTER_SHOP:
+ return (shop_name(mx, my, add_stop));
+
+ case DNGN_ENTER_PORTAL_VAULT:
+ return (feature_do_grammar(
+ dtype, add_stop, false,
+ marker_feature_description(coord_def(mx, my))));
+ default:
+ return (feature_description(grid, NUM_TRAPS, bloody, dtype, add_stop));
+ }
+}
+
+static std::string describe_mons_enchantment(const monsters &mons,
+ const mon_enchant &ench,
+ bool paralysed)
+{
+ // Suppress silly-looking combinations, even if they're
+ // internally valid.
+ if (paralysed && (ench.ench == ENCH_SLOW || ench.ench == ENCH_HASTE))
+ return "";
+
+ if ((ench.ench == ENCH_HASTE || ench.ench == ENCH_BATTLE_FRENZY)
+ && mons.has_ench(ENCH_BERSERK))
+ {
+ return "";
+ }
+
+ switch (ench.ench)
+ {
+ case ENCH_POISON:
+ return "poisoned";
+ case ENCH_SICK:
+ return "sick";
+ case ENCH_ROT:
+ return "rotting away"; //jmf: "covered in sores"?
+ case ENCH_BACKLIGHT:
+ return "softly glowing";
+ case ENCH_SLOW:
+ return "moving slowly";
+ case ENCH_BERSERK:
+ return "berserk";
+ case ENCH_BATTLE_FRENZY:
+ return "consumed by blood-lust";
+ case ENCH_HASTE:
+ return "moving very quickly";
+ case ENCH_CONFUSION:
+ return "bewildered and confused";
+ case ENCH_INVIS:
+ return "slightly transparent";
+ case ENCH_CHARM:
+ return "in your thrall";
+ case ENCH_STICKY_FLAME:
+ return "covered in liquid flames";
+ case ENCH_HELD:
+ return "entangled in a net";
+ default:
+ return "";
+ } // end switch
+}
+
+static std::string describe_monster_weapon(const monsters *mons)
+{
+ std::string desc = "";
+ std::string name1, name2;
+ const item_def *weap = mons->mslot_item(MSLOT_WEAPON);
+ const item_def *alt = mons->mslot_item(MSLOT_ALT_WEAPON);
+
+ if (weap)
+ name1 = weap->name(DESC_NOCAP_A, false, false, true,
+ false, ISFLAG_KNOW_CURSE);
+ if (alt && (!weap || mons_wields_two_weapons(mons)))
+ name2 = alt->name(DESC_NOCAP_A, false, false, true,
+ false, ISFLAG_KNOW_CURSE);
+
+ if (name1.empty() && !name2.empty())
+ name1.swap(name2);
+
+ if (name1 == name2 && weap)
+ {
+ item_def dup = *weap;
+ ++dup.quantity;
+ name1 = dup.name(DESC_NOCAP_A, false, false, true, true,
+ ISFLAG_KNOW_CURSE);
+ name2.clear();
+ }
+
+ if (name1.empty())
+ return (desc);
+
+ desc += " wielding ";
+ desc += name1;
+
+ if (!name2.empty())
+ {
+ desc += " and ";
+ desc += name2;
+ }
+
+ return (desc);
+}
+
+#ifdef DEBUG_DIAGNOSTICS
+static std::string stair_destination_description(const coord_def &pos)
+{
+ if (LevelInfo *linf = travel_cache.find_level_info(level_id::current()))
+ {
+ const stair_info *si = linf->get_stair(pos);
+ if (si)
+ return (" " + si->describe());
+ else if (is_stair(grd(pos)))
+ return (" (unknown stair)");
+ }
+ return ("");
+}
+#endif
+
+static void describe_monster(const monsters *mon)
+{
+
+ // first print type and equipment
+ std::string text = get_monster_desc(mon);
+ text += ".";
+ print_formatted_paragraph(text, get_number_of_cols());
+
+ if (player_beheld_by(mon))
+ mpr("You are beheld by her song.", MSGCH_EXAMINE);
+
+ if (mon->type == MONS_HYDRA)
+ {
+ mprf(MSGCH_EXAMINE, "It has %d head%s.", mon->number,
+ (mon->number > 1? "s" : ""));
+ }
+
+ print_wounds(mon);
+
+ if (!mons_is_mimic(mon->type) && mons_behaviour_perceptible(mon))
+ {
+ if (mon->behaviour == BEH_SLEEP)
+ {
+ mprf(MSGCH_EXAMINE, "%s appears to be resting.",
+ mon->pronoun(PRONOUN_CAP).c_str());
+ }
+ // Applies to both friendlies and hostiles
+ else if (mon->behaviour == BEH_FLEE)
+ {
+ mprf(MSGCH_EXAMINE, "%s is retreating.",
+ mon->pronoun(PRONOUN_CAP).c_str());
+ }
+ // hostile with target != you
+ else if (!mons_friendly(mon) && !mons_neutral(mon) && mon->foe != MHITYOU)
+ {
+ // special case: batty monsters get set to BEH_WANDER as
+ // part of their special behaviour.
+ if (!testbits(mon->flags, MF_BATTY))
+ {
+ mprf(MSGCH_EXAMINE, "%s doesn't appear to have noticed you.",
+ mon->pronoun(PRONOUN_CAP).c_str());
+ }
+ }
+ }
+
+ if (mon->attitude == ATT_FRIENDLY)
+ {
+ mprf(MSGCH_EXAMINE, "%s is friendly.",
+ mon->pronoun(PRONOUN_CAP).c_str());
+ }
+ else if (mons_neutral(mon)) // don't differentiate between permanent or not
+ {
+ mprf(MSGCH_EXAMINE, "%s is indifferent to you.",
+ mon->pronoun(PRONOUN_CAP).c_str());
+ }
+
+ if (mon->haloed())
+ {
+ mprf(MSGCH_EXAMINE, "%s is illuminated by a halo.",
+ mon->pronoun(PRONOUN_CAP).c_str());
+ }
+
+ std::string desc = "";
+ std::string last_desc = "";
+ std::string tmp = "";
+
+ const bool paralysed = mons_is_paralysed(mon);
+ if (paralysed)
+ last_desc += "paralysed";
+
+ for (mon_enchant_list::const_iterator e = mon->enchantments.begin();
+ e != mon->enchantments.end(); ++e)
+ {
+ tmp = describe_mons_enchantment(*mon, e->second, paralysed);
+ if (!tmp.empty())
+ {
+ if (!desc.empty())
+ desc += ", ";
+ desc += last_desc;
+ last_desc = tmp;
+ }
+ }
+
+ if (!last_desc.empty())
+ {
+ if (!desc.empty())
+ desc += ", and ";
+ desc += last_desc;
+ }
+
+ if (!desc.empty())
+ {
+ text = mon->pronoun(PRONOUN_CAP);
+ text += " is ";
+ text += desc;
+ text += ".";
+ print_formatted_paragraph(text, get_number_of_cols());
+ }
+}
+
+std::string get_monster_desc(const monsters *mon, bool full_desc,
+ description_level_type mondtype)
+{
+ std::string desc = mon->name(mondtype);
+
+ const int mon_arm = mon->inv[MSLOT_ARMOUR];
+ std::string weap = "";
+
+ if (mon->type != MONS_DANCING_WEAPON)
+ weap = describe_monster_weapon(mon);
+
+ if (!weap.empty())
+ {
+ if (full_desc)
+ desc += ",";
+ desc += weap;
+ }
+
+ if (full_desc && mon_arm != NON_ITEM)
+ {
+ desc += ", ";
+ if (!weap.empty())
+ desc += "and ";
+ desc += "wearing ";
+ desc += mitm[mon_arm].name(DESC_NOCAP_A);
+ }
+
+ return desc;
+}
+
+static void describe_cell(int mx, int my)
+{
+ bool mimic_item = false;
+ bool monster_described = false;
+ bool cloud_described = false;
+ bool item_described = false;
+
+ if (mx == you.x_pos && my == you.y_pos)
+ mpr("You.", MSGCH_EXAMINE_FILTER);
+
+ if (mgrd[mx][my] != NON_MONSTER)
+ {
+ int i = mgrd[mx][my];
+
+ if (_mon_submerged_in_water(&menv[i]))
+ {
+ mpr("There is a strange disturbance in the water here.",
+ MSGCH_EXAMINE_FILTER);
+ }
+
+#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
+
+ describe_monster(&menv[i]);
+
+ if (mons_is_mimic( menv[i].type ))
+ {
+ mimic_item = true;
+ item_described = true;
+ }
+ else
+ monster_described = true;
+
+#if DEBUG_DIAGNOSTICS
+ stethoscope(i);
+#endif
+ if (Options.tutorial_left && tutorial_monster_interesting(&menv[i]))
+ {
+ std::string msg = "(Press <w>v<lightgray> for more information.)";
+ print_formatted_paragraph(msg, get_number_of_cols());
+ }
+ }
+
+#if (!DEBUG_DIAGNOSTICS)
+ // removing warning
+ look_clouds:
+#endif
+
+ if (is_sanctuary(mx, my))
+ mpr("This square lies inside a sanctuary.");
+
+ if (env.cgrid[mx][my] != EMPTY_CLOUD)
+ {
+ const int cloud_inspected = env.cgrid[mx][my];
+ const cloud_type ctype = (cloud_type) env.cloud[cloud_inspected].type;
+
+ mprf(MSGCH_EXAMINE, "There is a cloud of %s here.",
+ cloud_name(ctype).c_str());
+
+ cloud_described = true;
+ }
+
+ int targ_item = igrd[ mx ][ my ];
+
+ if (targ_item != NON_ITEM)
+ {
+ // If a mimic is on this square, we pretend it's the first item -- bwr
+ if (mimic_item)
+ mpr("There is something else lying underneath.", MSGCH_FLOOR_ITEMS);
+ else
+ {
+ if (mitm[ targ_item ].base_type == OBJ_GOLD)
+ mprf( MSGCH_FLOOR_ITEMS, "A pile of gold coins." );
+ else
+ {
+ mprf( MSGCH_FLOOR_ITEMS, "You see %s here.",
+ mitm[targ_item].name(DESC_NOCAP_A).c_str());
+ }
+
+ if (mitm[ targ_item ].link != NON_ITEM)
+ {
+ mprf( MSGCH_FLOOR_ITEMS,
+ "There is something else lying underneath.");
+ }
+ }
+ item_described = true;
+ }
+
+ bool bloody = false;
+ if (is_bloodcovered(mx, my))
+ bloody = true;
+
+ std::string feature_desc = feature_description(mx, my, bloody);
+#ifdef DEBUG_DIAGNOSTICS
+ std::string marker;
+ if (map_marker *mark = env.markers.find(coord_def(mx, my), MAT_ANY))
+ {
+ std::string desc = mark->debug_describe();
+ if (desc.empty())
+ desc = "?";
+ marker = " (" + desc + ")";
+ }
+ const std::string traveldest =
+ stair_destination_description(coord_def(mx, my));
+ const dungeon_feature_type feat = grd[mx][my];
+ mprf(MSGCH_DIAGNOSTICS, "(%d,%d): %s - %s (%d/%s)%s%s", mx, my,
+ stringize_glyph(get_screen_glyph(mx, my)).c_str(),
+ feature_desc.c_str(),
+ feat,
+ dungeon_feature_name(feat),
+ marker.c_str(),
+ traveldest.c_str());
+#else
+ if (Options.tutorial_left && tutorial_feat_interesting(grd[mx][my]))
+ {
+ feature_desc += " (Press <w>v<lightgray> for more information.)";
+
+ print_formatted_paragraph(feature_desc, get_number_of_cols());
+ }
+ else
+ {
+ const dungeon_feature_type feat = grd[mx][my];
+
+ if (interesting_feature(feat))
+ feature_desc += " (Press 'v' for more information.)";
+
+ // Suppress "Floor." if there's something on that square that we've
+ // already described.
+ if ((feat == DNGN_FLOOR || feat == DNGN_FLOOR_SPECIAL) && !bloody
+ && (monster_described || item_described || cloud_described))
+ {
+ return;
+ }
+
+ msg_channel_type channel = MSGCH_EXAMINE;
+ if (feat == DNGN_FLOOR
+ || feat == DNGN_FLOOR_SPECIAL
+ || feat == DNGN_SHALLOW_WATER
+ || feat == DNGN_DEEP_WATER)
+ {
+ channel = MSGCH_EXAMINE_FILTER;
+ }
+ mpr(feature_desc.c_str(), channel);
+ }
+#endif
+}
+
+///////////////////////////////////////////////////////////////////////////
+// targeting_behaviour
+
+targeting_behaviour::targeting_behaviour(bool look_around)
+ : just_looking(look_around), compass(false)
+{
+}
+
+targeting_behaviour::~targeting_behaviour()
+{
+}
+
+int targeting_behaviour::get_key()
+{
+#ifdef USE_TILE
+ mouse_control mc(MOUSE_MODE_TARGET_DIR);
+#endif
+
+ if (!crawl_state.is_replaying_keys())
+ flush_input_buffer(FLUSH_BEFORE_COMMAND);
+
+ return unmangle_direction_keys( getchm(KC_TARGETING), KC_TARGETING,
+ false, false);
+}
+
+command_type targeting_behaviour::get_command(int key)
+{
+ if (key == -1)
+ key = get_key();
+
+ switch ( key )
+ {
+ case ESCAPE:
+ case 'x': return CMD_TARGET_CANCEL;
+
+#ifdef USE_TILE
+ case CK_MOUSE_MOVE: return CMD_TARGET_MOUSE_MOVE;
+ case CK_MOUSE_CLICK: return CMD_TARGET_MOUSE_SELECT;
+#endif
+
+#ifdef WIZARD
+ case 'F': return CMD_TARGET_WIZARD_MAKE_FRIENDLY;
+ case 's': return CMD_TARGET_WIZARD_MAKE_SHOUT;
+ case 'g': return CMD_TARGET_WIZARD_GIVE_ITEM;
+#endif
+ case 'v': return CMD_TARGET_DESCRIBE;
+ case '?': return CMD_TARGET_HELP;
+ case ' ': return just_looking? CMD_TARGET_CANCEL : CMD_TARGET_SELECT;
+#ifdef WIZARD
+ case CONTROL('C'): return CMD_TARGET_CYCLE_BEAM;
+#endif
+ case ':': return CMD_TARGET_HIDE_BEAM;
+ case '\r': return CMD_TARGET_SELECT;
+ case '.': return CMD_TARGET_SELECT;
+ case '5': return CMD_TARGET_SELECT;
+ case '!': return CMD_TARGET_SELECT_ENDPOINT;
+
+ case '\\':
+ case '\t': return CMD_TARGET_FIND_PORTAL;
+ case '^': return CMD_TARGET_FIND_TRAP;
+ case '_': return CMD_TARGET_FIND_ALTAR;
+ case '<': return CMD_TARGET_FIND_UPSTAIR;
+ case '>': return CMD_TARGET_FIND_DOWNSTAIR;
+
+ case CONTROL('F'): return CMD_TARGET_CYCLE_TARGET_MODE;
+ case 'p': return CMD_TARGET_PREV_TARGET;
+ case 'f': return CMD_TARGET_MAYBE_PREV_TARGET;
+ case 't': return CMD_TARGET_MAYBE_PREV_TARGET; // for the 0.3.4 keys
+
+ case '-': return CMD_TARGET_CYCLE_BACK;
+ case '+':
+ case '=': return CMD_TARGET_CYCLE_FORWARD;
+ case ';':
+ case '/': return CMD_TARGET_OBJ_CYCLE_BACK;
+ case '*':
+ case '\'': return CMD_TARGET_OBJ_CYCLE_FORWARD;
+
+ case 'b': return CMD_TARGET_DOWN_LEFT;
+ case 'h': return CMD_TARGET_LEFT;
+ case 'j': return CMD_TARGET_DOWN;
+ case 'k': return CMD_TARGET_UP;
+ case 'l': return CMD_TARGET_RIGHT;
+ case 'n': return CMD_TARGET_DOWN_RIGHT;
+ case 'u': return CMD_TARGET_UP_RIGHT;
+ case 'y': return CMD_TARGET_UP_LEFT;
+
+ case 'B': return CMD_TARGET_DIR_DOWN_LEFT;
+ case 'H': return CMD_TARGET_DIR_LEFT;
+ case 'J': return CMD_TARGET_DIR_DOWN;
+ case 'K': return CMD_TARGET_DIR_UP;
+ case 'L': return CMD_TARGET_DIR_RIGHT;
+ case 'N': return CMD_TARGET_DIR_DOWN_RIGHT;
+ case 'U': return CMD_TARGET_DIR_UP_RIGHT;
+ case 'Y': return CMD_TARGET_DIR_UP_LEFT;
+
+ default: return CMD_NO_CMD;
+ }
+}
+
+bool targeting_behaviour::should_redraw()
+{
+ return (false);
+}