diff options
Diffstat (limited to 'crawl-ref/source/direct.cc')
-rw-r--r-- | crawl-ref/source/direct.cc | 1503 |
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()); +} |