1 /**
2 * \file ui-target.c
3 * \brief UI for targetting code
4 *
5 * Copyright (c) 1997-2014 Angband contributors
6 *
7 * This work is free software; you can redistribute it and/or modify it
8 * under the terms of either:
9 *
10 * a) the GNU General Public License as published by the Free Software
11 * Foundation, version 2, or
12 *
13 * b) the "Angband licence":
14 * This software may be copied and distributed for educational, research,
15 * and not for profit purposes provided that this copyright and statement
16 * are included in all such copies. Other copyrights may also apply.
17 */
18
19 #include "angband.h"
20 #include "cave.h"
21 #include "game-input.h"
22 #include "init.h"
23 #include "mon-desc.h"
24 #include "mon-lore.h"
25 #include "mon-predicate.h"
26 #include "monster.h"
27 #include "obj-desc.h"
28 #include "obj-pile.h"
29 #include "obj-util.h"
30 #include "player-attack.h"
31 #include "player-calcs.h"
32 #include "player-timed.h"
33 #include "project.h"
34 #include "target.h"
35 #include "trap.h"
36 #include "ui-display.h"
37 #include "ui-input.h"
38 #include "ui-keymap.h"
39 #include "ui-map.h"
40 #include "ui-mon-lore.h"
41 #include "ui-object.h"
42 #include "ui-output.h"
43 #include "ui-target.h"
44 #include "ui-term.h"
45
46 /**
47 * Extract a direction (or zero) from a character
48 */
target_dir(struct keypress ch)49 int target_dir(struct keypress ch)
50 {
51 return target_dir_allow(ch, false);
52 }
53
target_dir_allow(struct keypress ch,bool allow_5)54 int target_dir_allow(struct keypress ch, bool allow_5)
55 {
56 int d = 0;
57
58 /* Already a direction? */
59 if (isdigit((unsigned char)ch.code)) {
60 d = D2I(ch.code);
61 } else if (isarrow(ch.code)) {
62 switch (ch.code) {
63 case ARROW_DOWN: d = 2; break;
64 case ARROW_LEFT: d = 4; break;
65 case ARROW_RIGHT: d = 6; break;
66 case ARROW_UP: d = 8; break;
67 }
68 } else {
69 int mode;
70 const struct keypress *act;
71
72 if (OPT(player, rogue_like_commands))
73 mode = KEYMAP_MODE_ROGUE;
74 else
75 mode = KEYMAP_MODE_ORIG;
76
77 /* XXX see if this key has a digit in the keymap we can use */
78 act = keymap_find(mode, ch);
79 if (act) {
80 const struct keypress *cur;
81 for (cur = act; cur->type == EVT_KBRD; cur++) {
82 if (isdigit((unsigned char) cur->code))
83 d = D2I(cur->code);
84 }
85 }
86 }
87
88 /* Paranoia */
89 if (d == 5 && !allow_5) d = 0;
90
91 /* Return direction */
92 return (d);
93 }
94
95 /**
96 * Display targeting help at the bottom of the screen.
97 */
target_display_help(bool monster,bool free)98 void target_display_help(bool monster, bool free)
99 {
100 /* Determine help location */
101 int wid, hgt, help_loc;
102 Term_get_size(&wid, &hgt);
103 help_loc = hgt - HELP_HEIGHT;
104
105 /* Clear */
106 clear_from(help_loc);
107
108 /* Prepare help hooks */
109 text_out_hook = text_out_to_screen;
110 text_out_indent = 1;
111 Term_gotoxy(1, help_loc);
112
113 /* Display help */
114 text_out_c(COLOUR_L_GREEN, "<dir>");
115 text_out(" and ");
116 text_out_c(COLOUR_L_GREEN, "<click>");
117 text_out(" look around. '");
118 text_out_c(COLOUR_L_GREEN, "g");
119 text_out(" moves to the selection. '");
120 text_out_c(COLOUR_L_GREEN, "p");
121 text_out("' selects the player. '");
122 text_out_c(COLOUR_L_GREEN, "q");
123 text_out("' exits. '");
124 text_out_c(COLOUR_L_GREEN, "r");
125 text_out("' displays details. '");
126
127 if (free)
128 {
129 text_out_c(COLOUR_L_GREEN, "m");
130 text_out("' restricts to interesting places. ");
131 }
132 else
133 {
134 text_out_c(COLOUR_L_GREEN, "+");
135 text_out("' and '");
136 text_out_c(COLOUR_L_GREEN, "-");
137 text_out("' cycle through interesting places. '");
138 text_out_c(COLOUR_L_GREEN, "o");
139 text_out("' allows free selection. ");
140 }
141
142 if (monster || free)
143 {
144 text_out("'");
145 text_out_c(COLOUR_L_GREEN, "t");
146 text_out("' targets the current selection.");
147 }
148
149 /* Reset */
150 text_out_indent = 0;
151 }
152
153
154 /**
155 * Perform the minimum "whole panel" adjustment to ensure that the given
156 * location is contained inside the current panel, and return true if any
157 * such adjustment was performed. Optionally accounts for the targeting
158 * help window.
159 */
adjust_panel_help(int y,int x,bool help)160 static bool adjust_panel_help(int y, int x, bool help)
161 {
162 bool changed = false;
163
164 int j;
165
166 int screen_hgt_main = help ? (Term->hgt - ROW_MAP - 3)
167 : (Term->hgt - ROW_MAP - 1);
168
169 /* Scan windows */
170 for (j = 0; j < ANGBAND_TERM_MAX; j++)
171 {
172 int wx, wy;
173 int screen_hgt, screen_wid;
174
175 term *t = angband_term[j];
176
177 /* No window */
178 if (!t) continue;
179
180 /* No relevant flags */
181 if ((j > 0) && !(window_flag[j] & PW_MAPS)) continue;
182
183 wy = t->offset_y;
184 wx = t->offset_x;
185
186 screen_hgt = (j == 0) ? screen_hgt_main : t->hgt;
187 screen_wid = (j == 0) ? (Term->wid - COL_MAP - 1) : t->wid;
188
189 /* Bigtile panels need adjustment */
190 screen_wid = screen_wid / tile_width;
191 screen_hgt = screen_hgt / tile_height;
192
193 /* Adjust as needed */
194 while (y >= wy + screen_hgt) wy += screen_hgt / 2;
195 while (y < wy) wy -= screen_hgt / 2;
196
197 /* Adjust as needed */
198 while (x >= wx + screen_wid) wx += screen_wid / 2;
199 while (x < wx) wx -= screen_wid / 2;
200
201 /* Use "modify_panel" */
202 if (modify_panel(t, wy, wx)) changed = true;
203 }
204
205 return (changed);
206 }
207
208
209 /**
210 * Display the object name of the selected object and allow for full object
211 * recall. Returns an event that occurred display.
212 *
213 * This will only work for a single object on the ground and not a pile. This
214 * loop is similar to the monster recall loop in target_set_interactive_aux().
215 * The out_val array size needs to match the size that is passed in (since
216 * this code was extracted from there).
217 *
218 * \param obj is the object to describe.
219 * \param y is the cave row of the object.
220 * \param x is the cave column of the object.
221 * \param out_val is the string that holds the name of the object and is
222 * returned to the caller.
223 * \param s1 is part of the output string.
224 * \param s2 is part of the output string.
225 * \param s3 is part of the output string.
226 * \param coords is part of the output string
227 */
target_recall_loop_object(struct object * obj,int y,int x,char out_val[TARGET_OUT_VAL_SIZE],const char * s1,const char * s2,const char * s3,char * coords)228 static ui_event target_recall_loop_object(struct object *obj, int y, int x,
229 char out_val[TARGET_OUT_VAL_SIZE],
230 const char *s1, const char *s2,
231 const char *s3, char *coords)
232 {
233 bool recall = false;
234 ui_event press;
235
236 while (1) {
237 if (recall) {
238 display_object_recall_interactive(cave->objects[obj->oidx]);
239 press = inkey_m();
240 } else {
241 char o_name[80];
242
243 /* Obtain an object description */
244 object_desc(o_name, sizeof(o_name), cave->objects[obj->oidx],
245 ODESC_PREFIX | ODESC_FULL);
246
247 /* Describe the object */
248 if (player->wizard) {
249 strnfmt(out_val, TARGET_OUT_VAL_SIZE,
250 "%s%s%s%s, %s (%d:%d, noise=%d, scent=%d).", s1, s2, s3,
251 o_name, coords, y, x, (int)cave->noise.grids[y][x],
252 (int)cave->scent.grids[y][x]);
253 } else {
254 strnfmt(out_val, TARGET_OUT_VAL_SIZE,
255 "%s%s%s%s, %s.", s1, s2, s3, o_name, coords);
256 }
257
258 prt(out_val, 0, 0);
259 move_cursor_relative(y, x);
260 press = inkey_m();
261 }
262
263 if ((press.type == EVT_MOUSE) && (press.mouse.button == 1) &&
264 (KEY_GRID_X(press) == x) && (KEY_GRID_Y(press) == y))
265 recall = !recall;
266 else if ((press.type == EVT_KBRD) && (press.key.code == 'r'))
267 recall = !recall;
268 else
269 break;
270 }
271
272 return press;
273 }
274
275 /**
276 * Examine a grid, return a keypress.
277 *
278 * The "mode" argument contains the "TARGET_LOOK" bit flag, which
279 * indicates that the "space" key should scan through the contents
280 * of the grid, instead of simply returning immediately. This lets
281 * the "look" command get complete information, without making the
282 * "target" command annoying.
283 *
284 * The "info" argument contains the "commands" which should be shown
285 * inside the "[xxx]" text. This string must never be empty, or grids
286 * containing monsters will be displayed with an extra comma.
287 *
288 * Note that if a monster is in the grid, we update both the monster
289 * recall info and the health bar info to track that monster.
290 *
291 * This function correctly handles multiple objects per grid, and objects
292 * and terrain features in the same grid, though the latter never happens.
293 *
294 * This function must handle blindness/hallucination.
295 */
target_set_interactive_aux(int y,int x,int mode)296 static ui_event target_set_interactive_aux(int y, int x, int mode)
297 {
298 struct object *obj = NULL;
299
300 const char *s1, *s2, *s3;
301
302 bool boring;
303
304 int floor_max = z_info->floor_size;
305 struct object **floor_list = mem_zalloc(floor_max * sizeof(*floor_list));
306 int floor_num;
307
308 ui_event press;
309
310 char out_val[TARGET_OUT_VAL_SIZE];
311
312 char coords[20];
313
314 const char *name;
315
316 /* Describe the square location */
317 coords_desc(coords, sizeof(coords), y, x);
318
319 /* Repeat forever */
320 while (1) {
321 /* Make the default event to focus on the player */
322 press.type = EVT_KBRD;
323 press.key.code = 'p';
324 press.key.mods = 0;
325
326 /* Assume boring */
327 boring = true;
328
329 /* Default */
330 s1 = "You see ";
331 s2 = "";
332 s3 = "";
333
334 /* Bail if looking at a forbidden grid */
335 if (!square_in_bounds(cave, loc(x, y))) {
336 break;
337 }
338
339 /* The player */
340 if (square(cave, loc(x, y))->mon < 0) {
341 /* Description */
342 s1 = "You are ";
343
344 /* Preposition */
345 s2 = "on ";
346 }
347
348 /* Hallucination messes things up */
349 if (player->timed[TMD_IMAGE]) {
350 const char *name_strange = "something strange";
351
352 /* Display a message */
353 if (player->wizard)
354 strnfmt(out_val, sizeof(out_val),
355 "%s%s%s%s, %s (%d:%d, noise=%d, scent=%d).", s1, s2, s3,
356 name_strange, coords, y, x, (int)cave->noise.grids[y][x],
357 (int)cave->scent.grids[y][x]);
358 else
359 strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.",
360 s1, s2, s3, name_strange, coords);
361
362 prt(out_val, 0, 0);
363 move_cursor_relative(y, x);
364
365 press.key = inkey();
366
367 /* Stop on everything but "return" */
368 if (press.key.code == KC_ENTER)
369 continue;
370
371 mem_free(floor_list);
372 return press;
373 }
374
375 /* Actual monsters */
376 if (square(cave, loc(x, y))->mon > 0) {
377 struct monster *mon = square_monster(cave, loc(x, y));
378 const struct monster_lore *lore = get_lore(mon->race);
379
380 /* Visible */
381 if (monster_is_obvious(mon)) {
382 bool recall = false;
383
384 char m_name[80];
385
386 /* Not boring */
387 boring = false;
388
389 /* Get the monster name ("a kobold") */
390 monster_desc(m_name, sizeof(m_name), mon, MDESC_IND_VIS);
391
392 /* Hack -- track this monster race */
393 monster_race_track(player->upkeep, mon->race);
394
395 /* Hack -- health bar for this monster */
396 health_track(player->upkeep, mon);
397
398 /* Hack -- handle stuff */
399 handle_stuff(player);
400
401 /* Interact */
402 while (1) {
403 /* Recall or target */
404 if (recall) {
405 lore_show_interactive(mon->race, lore);
406 press = inkey_m();
407 } else {
408 char buf[80];
409
410 /* Describe the monster */
411 look_mon_desc(buf, sizeof(buf),
412 square(cave, loc(x, y))->mon);
413
414 /* Describe, and prompt for recall */
415 if (player->wizard) {
416 strnfmt(out_val, sizeof(out_val),
417 "%s%s%s%s (%s), %s (%d:%d, noise=%d, scent=%d).",
418 s1, s2, s3, m_name, buf, coords, y, x,
419 (int)cave->noise.grids[y][x],
420 (int)cave->scent.grids[y][x]);
421 } else {
422 strnfmt(out_val, sizeof(out_val),
423 "%s%s%s%s (%s), %s.",
424 s1, s2, s3, m_name, buf, coords);
425 }
426
427 prt(out_val, 0, 0);
428
429 /* Place cursor */
430 move_cursor_relative(y, x);
431
432 /* Command */
433 press = inkey_m();
434 }
435
436 /* Normal commands */
437 if ((press.type == EVT_MOUSE) && (press.mouse.button == 1)
438 && (KEY_GRID_X(press) == x) && (KEY_GRID_Y(press) == y))
439 recall = !recall;
440 else
441 if ((press.type == EVT_KBRD) && (press.key.code == 'r'))
442 recall = !recall;
443 else
444 break;
445 }
446
447 if (press.type == EVT_MOUSE) {
448 /* Stop on right click */
449 if (press.mouse.button == 2)
450 break;
451
452 /* Sometimes stop at "space" key */
453 if (press.mouse.button && !(mode & (TARGET_LOOK))) break;
454 } else {
455 /* Stop on everything but "return"/"space" */
456 if (press.key.code != KC_ENTER && press.key.code != ' ')
457 break;
458
459 /* Sometimes stop at "space" key */
460 if ((press.key.code == ' ') && !(mode & (TARGET_LOOK)))
461 break;
462 }
463
464 /* Take account of gender */
465 if (rf_has(mon->race->flags, RF_FEMALE)) s1 = "She is ";
466 else if (rf_has(mon->race->flags, RF_MALE)) s1 = "He is ";
467 else s1 = "It is ";
468
469 /* Describe carried objects (wizards only) */
470 if (player->wizard) {
471 /* Use a verb */
472 s2 = "carrying ";
473
474 /* Scan all objects being carried */
475 for (obj = mon->held_obj; obj; obj = obj->next) {
476 char o_name[80];
477
478 /* Obtain an object description */
479 object_desc(o_name, sizeof(o_name), obj,
480 ODESC_PREFIX | ODESC_FULL);
481
482 strnfmt(out_val, sizeof(out_val),
483 "%s%s%s%s, %s (%d:%d, noise=%d, scent=%d).",
484 s1, s2, s3, o_name, coords, y, x,
485 (int)cave->noise.grids[y][x],
486 (int)cave->scent.grids[y][x]);
487
488 prt(out_val, 0, 0);
489 move_cursor_relative(y, x);
490 press = inkey_m();
491
492 if (press.type == EVT_MOUSE) {
493 /* Stop on right click */
494 if (press.mouse.button == 2)
495 break;
496
497 /* Sometimes stop at "space" key */
498 if (press.mouse.button && !(mode & (TARGET_LOOK)))
499 break;
500 } else {
501 /* Stop on everything but "return"/"space" */
502 if ((press.key.code != KC_ENTER) &&
503 (press.key.code != ' '))
504 break;
505
506 /* Sometimes stop at "space" key */
507 if ((press.key.code == ' ') &&
508 !(mode & (TARGET_LOOK)))
509 break;
510 }
511
512 /* Change the intro */
513 s2 = "also carrying ";
514 }
515 }
516
517 /* Double break */
518 if (obj) break;
519
520 /* Use a preposition */
521 s2 = "on ";
522 }
523 }
524
525 /* A trap */
526 if (square_isvisibletrap(cave, loc(x, y))) {
527 struct trap *trap = square(cave, loc(x, y))->trap;
528
529 /* Not boring */
530 boring = false;
531
532 /* Interact */
533 while (1) {
534 /* Change the intro */
535 if (square(cave, loc(x, y))->mon < 0) {
536 s1 = "You are ";
537 s2 = "on ";
538 } else {
539 s1 = "You see ";
540 s2 = "";
541 }
542
543 /* Pick proper indefinite article */
544 s3 = (is_a_vowel(trap->kind->desc[0])) ? "an " : "a ";
545
546 /* Describe, and prompt for recall */
547 if (player->wizard) {
548 strnfmt(out_val, sizeof(out_val),
549 "%s%s%s%s, %s (%d:%d, noise=%d, scent=%d).", s1, s2,
550 s3, trap->kind->name, coords, y, x,
551 (int)cave->noise.grids[y][x],
552 (int)cave->scent.grids[y][x]);
553 } else {
554 strnfmt(out_val, sizeof(out_val), "%s%s%s%s, %s.",
555 s1, s2, s3, trap->kind->desc, coords);
556 }
557
558 prt(out_val, 0, 0);
559
560 /* Place cursor */
561 move_cursor_relative(y, x);
562
563 /* Command */
564 press = inkey_m();
565
566 /* Stop on everything but "return"/"space" */
567 if ((press.key.code != KC_ENTER) && (press.key.code != ' '))
568 break;
569
570 /* Sometimes stop at "space" key */
571 if ((press.key.code == ' ') && !(mode & (TARGET_LOOK)))
572 break;
573 }
574 }
575
576 /* Double break */
577 if (square_isvisibletrap(cave, loc(x, y)))
578 break;
579
580 /* Scan all sensed objects in the grid */
581 floor_num = scan_distant_floor(floor_list, floor_max, loc(x, y));
582 if ((floor_num > 0) &&
583 (!(player->timed[TMD_BLIND]) || loc_eq(loc(x, y), player->grid))) {
584 /* Not boring */
585 boring = false;
586
587 track_object(player->upkeep, floor_list[0]);
588 handle_stuff(player);
589
590 /* If there is more than one item... */
591 if (floor_num > 1)
592 while (1) {
593 /* Describe the pile */
594 if (player->wizard) {
595 strnfmt(out_val, sizeof(out_val),
596 "%s%s%sa pile of %d objects, %s (%d:%d, noise=%d, scent=%d).",
597 s1, s2, s3, floor_num, coords, y, x,
598 (int)cave->noise.grids[y][x],
599 (int)cave->scent.grids[y][x]);
600 } else {
601 strnfmt(out_val, sizeof(out_val),
602 "%s%s%sa pile of %d objects, %s.",
603 s1, s2, s3, floor_num, coords);
604 }
605
606 prt(out_val, 0, 0);
607 move_cursor_relative(y, x);
608 press = inkey_m();
609
610 /* Display objects */
611 if (((press.type == EVT_MOUSE) && (press.mouse.button == 1)
612 && (KEY_GRID_X(press) == x) &&
613 (KEY_GRID_Y(press) == y)) ||
614 ((press.type == EVT_KBRD) && (press.key.code == 'r'))) {
615 int rdone = 0;
616 int pos;
617 while (!rdone) {
618 /* Save screen */
619 screen_save();
620
621 /* Display */
622 show_floor(floor_list, floor_num,
623 (OLIST_WEIGHT | OLIST_GOLD), NULL);
624
625 /* Describe the pile */
626 prt(out_val, 0, 0);
627 press = inkey_m();
628
629 /* Load screen */
630 screen_load();
631
632 if (press.type == EVT_MOUSE) {
633 pos = press.mouse.y-1;
634 } else {
635 pos = press.key.code - 'a';
636 }
637 if (0 <= pos && pos < floor_num) {
638 track_object(player->upkeep, floor_list[pos]);
639 handle_stuff(player);
640 continue;
641 }
642 rdone = 1;
643 }
644
645 /* Now that the user's done with the display loop,
646 * let's do the outer loop over again */
647 continue;
648 }
649
650 /* Done */
651 break;
652 }
653 /* Only one object to display */
654 else {
655 /* Get the single object in the list */
656 struct object *obj_local = floor_list[0];
657
658 /* Allow user to recall an object */
659 press = target_recall_loop_object(obj_local, y, x, out_val, s1, s2,
660 s3, coords);
661
662 /* Stop on everything but "return"/"space" */
663 if ((press.key.code != KC_ENTER) && (press.key.code != ' '))
664 break;
665
666 /* Sometimes stop at "space" key */
667 if ((press.key.code == ' ') && !(mode & (TARGET_LOOK))) break;
668
669 /* Plurals */
670 s1 = VERB_AGREEMENT(obj_local->number, "It is ", "They are ");
671
672 /* Preposition */
673 s2 = "on ";
674 }
675
676 }
677
678 /* Double break */
679 if (obj) break;
680
681 name = square_apparent_name(cave, player, loc(x, y));
682
683 /* Terrain feature if needed */
684 if (boring || square_isinteresting(cave, loc(x, y))) {
685 /* Hack -- handle unknown grids */
686
687 /* Pick a preposition if needed */
688 if (*s2) s2 = square_apparent_look_in_preposition(cave, player, loc(x, y));
689
690 /* Pick prefix for the name */
691 s3 = square_apparent_look_prefix(cave, player, loc(x, y));
692
693 /* Display a message */
694 if (player->wizard) {
695 strnfmt(out_val, sizeof(out_val),
696 "%s%s%s%s, %s (%d:%d, noise=%d, scent=%d).", s1, s2, s3,
697 name, coords, y, x, (int)cave->noise.grids[y][x],
698 (int)cave->scent.grids[y][x]);
699 } else {
700 strnfmt(out_val, sizeof(out_val),
701 "%s%s%s%s, %s.", s1, s2, s3, name, coords);
702 }
703
704 prt(out_val, 0, 0);
705 move_cursor_relative(y, x);
706 press = inkey_m();
707
708 if (press.type == EVT_MOUSE) {
709 /* Stop on right click */
710 if (press.mouse.button == 2)
711 break;
712 } else {
713 /* Stop on everything but "return"/"space" */
714 if ((press.key.code != KC_ENTER) && (press.key.code != ' '))
715 break;
716 }
717 }
718
719 /* Stop on everything but "return" */
720 if (press.type == EVT_MOUSE) {
721 /* Stop on right click */
722 if (press.mouse.button != 2)
723 break;
724 } else {
725 if (press.key.code != KC_ENTER) break;
726 }
727 }
728
729 mem_free(floor_list);
730
731 /* Keep going */
732 return (press);
733 }
734
735 /**
736 * Target command
737 */
textui_target(void)738 void textui_target(void)
739 {
740 if (target_set_interactive(TARGET_KILL, -1, -1))
741 msg("Target Selected.");
742 else
743 msg("Target Aborted.");
744 }
745
746 /**
747 * Target closest monster.
748 *
749 * XXX: Move to using CMD_TARGET_CLOSEST at some point instead of invoking
750 * target_set_closest() directly.
751 */
textui_target_closest(void)752 void textui_target_closest(void)
753 {
754 if (target_set_closest(TARGET_KILL, NULL)) {
755 bool visibility;
756 struct loc target;
757
758 target_get(&target);
759
760 /* Visual cue */
761 Term_fresh();
762 Term_get_cursor(&visibility);
763 (void)Term_set_cursor(true);
764 move_cursor_relative(target.y, target.x);
765 Term_redraw_section(target.y, target.x, target.y, target.x);
766
767 /* TODO: what's an appropriate amount of time to spend highlighting */
768 Term_xtra(TERM_XTRA_DELAY, 150);
769 (void)Term_set_cursor(visibility);
770 }
771 }
772
773
774 /**
775 * Draw a visible path over the squares between (x1,y1) and (x2,y2).
776 *
777 * The path consists of "*", which are white except where there is a
778 * monster, object or feature in the grid.
779 *
780 * This routine has (at least) three weaknesses:
781 * - remembered objects/walls which are no longer present are not shown,
782 * - squares which (e.g.) the player has walked through in the dark are
783 * treated as unknown space.
784 * - walls which appear strange due to hallucination aren't treated correctly.
785 *
786 * The first two result from information being lost from the dungeon arrays,
787 * which requires changes elsewhere
788 */
draw_path(u16b path_n,struct loc * path_g,wchar_t * c,int * a,int y1,int x1)789 static int draw_path(u16b path_n, struct loc *path_g, wchar_t *c, int *a,
790 int y1, int x1)
791 {
792 int i;
793 bool on_screen;
794 bool pastknown = false;
795
796 /* No path, so do nothing. */
797 if (path_n < 1) return 0;
798
799 /* The starting square is never drawn, but notice if it is being
800 * displayed. In theory, it could be the last such square.
801 */
802 on_screen = panel_contains(y1, x1);
803
804 /* Draw the path. */
805 for (i = 0; i < path_n; i++) {
806 byte colour;
807
808 /* Find the co-ordinates on the level. */
809 struct loc grid = path_g[i];
810 struct monster *mon = square_monster(cave, grid);
811 struct object *obj = square_object(player->cave, grid);
812
813 /*
814 * As path[] is a straight line and the screen is oblong,
815 * there is only section of path[] on-screen.
816 * If the square being drawn is visible, this is part of it.
817 * If none of it has been drawn, continue until some of it
818 * is found or the last square is reached.
819 * If some of it has been drawn, finish now as there are no
820 * more visible squares to draw.
821 */
822 if (panel_contains(grid.y, grid.x)) on_screen = true;
823 else if (on_screen) break;
824 else continue;
825
826 /* Find the position on-screen */
827 move_cursor_relative(grid.y, grid.x);
828
829 /* This square is being overwritten, so save the original. */
830 Term_what(Term->scr->cx, Term->scr->cy, a + i, c + i);
831
832 /* Choose a colour. */
833 if (pastknown) {
834 /* Once we pass an unknown square, we no longer know
835 * if we will reach later squares */
836 colour = COLOUR_L_DARK;
837 } else if (mon && monster_is_visible(mon)) {
838 /* Mimics act as objects */
839 if (monster_is_camouflaged(mon))
840 colour = COLOUR_YELLOW;
841 else
842 /* Visible monsters are red. */
843 colour = COLOUR_L_RED;
844 } else if (obj)
845 /* Known objects are yellow. */
846 colour = COLOUR_YELLOW;
847
848 else if (!square_isprojectable(cave, grid) &&
849 (square_isknown(cave, grid) || square_isseen(cave, grid)))
850 /* Known walls are blue. */
851 colour = COLOUR_BLUE;
852
853 else if (!square_isknown(cave, grid) && !square_isseen(cave, grid)) {
854 /* Unknown squares are grey. */
855 pastknown = true;
856 colour = COLOUR_L_DARK;
857
858 } else
859 /* Unoccupied squares are white. */
860 colour = COLOUR_WHITE;
861
862 /* Draw the path segment */
863 (void)Term_addch(colour, L'*');
864 }
865 return i;
866 }
867
868
869 /**
870 * Load the attr/char at each point along "path" which is on screen from
871 * "a" and "c". This was saved in draw_path().
872 */
load_path(u16b path_n,struct loc * path_g,wchar_t * c,int * a)873 static void load_path(u16b path_n, struct loc *path_g, wchar_t *c, int *a)
874 {
875 int i;
876 for (i = 0; i < path_n; i++) {
877 int y = path_g[i].y;
878 int x = path_g[i].x;
879
880 if (!panel_contains(y, x)) continue;
881 move_cursor_relative(y, x);
882 Term_addch(a[i], c[i]);
883 }
884
885 Term_fresh();
886 }
887
888
889 /**
890 * Handle "target" and "look".
891 *
892 * Note that this code can be called from "get_aim_dir()".
893 *
894 * Currently, when "flag" is true, that is, when
895 * "interesting" grids are being used, and a directional key is used, we
896 * only scroll by a single panel, in the direction requested, and check
897 * for any interesting grids on that panel. The "correct" solution would
898 * actually involve scanning a larger set of grids, including ones in
899 * panels which are adjacent to the one currently scanned, but this is
900 * overkill for this function. XXX XXX
901 *
902 * Hack -- targetting/observing an "outer border grid" may induce
903 * problems, so this is not currently allowed.
904 *
905 * The player can use the direction keys to move among "interesting"
906 * grids in a heuristic manner, or the "space", "+", and "-" keys to
907 * move through the "interesting" grids in a sequential manner, or
908 * can enter "location" mode, and use the direction keys to move one
909 * grid at a time in any direction. The "t" (set target) command will
910 * only target a monster (as opposed to a location) if the monster is
911 * target_able and the "interesting" mode is being used.
912 *
913 * The current grid is described using the "look" method above, and
914 * a new command may be entered at any time, but note that if the
915 * "TARGET_LOOK" bit flag is set (or if we are in "location" mode,
916 * where "space" has no obvious meaning) then "space" will scan
917 * through the description of the current grid until done, instead
918 * of immediately jumping to the next "interesting" grid. This
919 * allows the "target" command to retain its old semantics.
920 *
921 * The "*", "+", and "-" keys may always be used to jump immediately
922 * to the next (or previous) interesting grid, in the proper mode.
923 *
924 * The "return" key may always be used to scan through a complete
925 * grid description (forever).
926 *
927 * This command will cancel any old target, even if used from
928 * inside the "look" command.
929 *
930 *
931 * 'mode' is one of TARGET_LOOK or TARGET_KILL.
932 * 'x' and 'y' are the initial position of the target to be highlighted,
933 * or -1 if no location is specified.
934 * Returns true if a target has been successfully set, false otherwise.
935 */
target_set_interactive(int mode,int x,int y)936 bool target_set_interactive(int mode, int x, int y)
937 {
938 int py = player->grid.y;
939 int px = player->grid.x;
940
941 int path_n;
942 struct loc path_g[256];
943
944 int i, d, m, t, bd;
945 int wid, hgt, help_prompt_loc;
946
947 bool done = false;
948 bool flag = true;
949 bool help = false;
950
951 ui_event press;
952
953 /* These are used for displaying the path to the target */
954 wchar_t *path_char = mem_zalloc(z_info->max_range * sizeof(wchar_t));
955 int *path_attr = mem_zalloc(z_info->max_range * sizeof(int));
956 struct point_set *targets;
957
958 /* If we haven't been given an initial location, start on the
959 player, otherwise honour it by going into "free targetting" mode. */
960 if (x == -1 || y == -1 || !square_in_bounds_fully(cave, loc(x, y))) {
961 x = player->grid.x;
962 y = player->grid.y;
963 } else {
964 flag = false;
965 }
966
967 /* Cancel target */
968 target_set_monster(0);
969
970 /* Prevent animations */
971 disallow_animations();
972
973 /* Calculate the window location for the help prompt */
974 Term_get_size(&wid, &hgt);
975 help_prompt_loc = hgt - 1;
976
977 /* Display the help prompt */
978 prt("Press '?' for help.", help_prompt_loc, 0);
979
980 /* Prepare the target set */
981 targets = target_get_monsters(mode, NULL);
982
983 /* Start near the player */
984 m = 0;
985
986 /* Interact */
987 while (!done) {
988 bool path_drawn = false;
989
990 /* Interesting grids if chosen and there are any, otherwise arbitrary */
991 if (flag && point_set_size(targets)) {
992 y = targets->pts[m].y;
993 x = targets->pts[m].x;
994
995 /* Adjust panel if needed */
996 if (adjust_panel_help(y, x, help)) handle_stuff(player);
997
998 /* Update help */
999 if (help) {
1000 bool good_target =
1001 target_able(square_monster(cave, targets->pts[m]));
1002 target_display_help(good_target,
1003 !(flag && point_set_size(targets)));
1004 }
1005
1006 /* Find the path. */
1007 path_n = project_path(path_g, z_info->max_range, loc(px, py),
1008 loc(x, y), PROJECT_THRU | PROJECT_INFO);
1009
1010 /* Draw the path in "target" mode. If there is one */
1011 if (mode & (TARGET_KILL))
1012 path_drawn = draw_path(path_n, path_g, path_char, path_attr,
1013 py, px);
1014
1015 /* Describe and Prompt */
1016 press = target_set_interactive_aux(y, x, mode);
1017
1018 /* Remove the path */
1019 if (path_drawn) load_path(path_n, path_g, path_char, path_attr);
1020
1021 /* Assume no "direction" */
1022 d = 0;
1023
1024
1025 /* Analyze */
1026 if (press.type == EVT_MOUSE) {
1027 if (press.mouse.button == 3) {
1028 /* give the target selection command */
1029 press.mouse.button = 2;
1030 press.mouse.mods = KC_MOD_CONTROL;
1031 }
1032 if (press.mouse.button == 2) {
1033 y = KEY_GRID_Y(press);
1034 x = KEY_GRID_X(press);
1035 if (press.mouse.mods & KC_MOD_CONTROL) {
1036 /* same as keyboard target selection command below */
1037 struct monster *m_local = square_monster(cave, loc(x, y));
1038
1039 if (target_able(m_local)) {
1040 /* Set up target information */
1041 monster_race_track(player->upkeep, m_local->race);
1042 health_track(player->upkeep, m_local);
1043 target_set_monster(m_local);
1044 done = true;
1045 } else {
1046 bell("Illegal target!");
1047 /*
1048 * So there's something
1049 * to work with in the
1050 * next pass through
1051 * the loop.
1052 */
1053 if (!square_in_bounds(cave, loc(x, y))) {
1054 x = player->grid.x;
1055 y = player->grid.y;
1056 }
1057 }
1058 } else if (press.mouse.mods & KC_MOD_ALT) {
1059 /* go to spot - same as 'g' command below */
1060 cmdq_push(CMD_PATHFIND);
1061 cmd_set_arg_point(cmdq_peek(), "point", loc(x, y));
1062 done = true;
1063 } else {
1064 /* cancel look mode */
1065 done = true;
1066 }
1067 } else {
1068 y = KEY_GRID_Y(press);
1069 x = KEY_GRID_X(press);
1070 if (square_monster(cave, loc(x, y)) ||
1071 square_object(cave, loc(x, y))) {
1072 /* reset the flag, to make sure we stay in this
1073 * mode if something is actually there */
1074 flag = false;
1075 /* scan the interesting list and see if there is
1076 * anything here */
1077 for (i = 0; i < point_set_size(targets); i++) {
1078 if ((y == targets->pts[i].y) &&
1079 (x == targets->pts[i].x)) {
1080 m = i;
1081 flag = true;
1082 break;
1083 }
1084 }
1085 } else {
1086 flag = false;
1087 if (! square_in_bounds(cave, loc(x, y))) {
1088 x = player->grid.x;
1089 y = player->grid.y;
1090 }
1091 }
1092 }
1093 } else
1094 switch (press.key.code)
1095 {
1096 case ESCAPE:
1097 case 'q':
1098 {
1099 done = true;
1100 break;
1101 }
1102
1103 case ' ':
1104 case '*':
1105 case '+':
1106 {
1107 if (++m == point_set_size(targets))
1108 m = 0;
1109
1110 break;
1111 }
1112
1113 case '-':
1114 {
1115 if (m-- == 0)
1116 m = point_set_size(targets) - 1;
1117
1118 break;
1119 }
1120
1121 case 'p':
1122 {
1123 /* Recenter around player */
1124 verify_panel();
1125
1126 /* Handle stuff */
1127 handle_stuff(player);
1128
1129 y = player->grid.y;
1130 x = player->grid.x;
1131 flag = false;
1132 break;
1133 }
1134
1135 case 'o':
1136 {
1137 flag = false;
1138 break;
1139 }
1140
1141 case 'm':
1142 {
1143 break;
1144 }
1145
1146 case 't':
1147 case '5':
1148 case '0':
1149 case '.':
1150 {
1151 struct monster *m_local = square_monster(cave, loc(x, y));
1152
1153 if (target_able(m_local)) {
1154 health_track(player->upkeep, m_local);
1155 target_set_monster(m_local);
1156 done = true;
1157 } else {
1158 bell("Illegal target!");
1159 }
1160 break;
1161 }
1162
1163 case 'g':
1164 {
1165 cmdq_push(CMD_PATHFIND);
1166 cmd_set_arg_point(cmdq_peek(), "point", loc(x, y));
1167 done = true;
1168 break;
1169 }
1170
1171 case '?':
1172 {
1173 help = !help;
1174
1175 /* Redraw main window */
1176 player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIP);
1177 Term_clear();
1178 handle_stuff(player);
1179 if (!help)
1180 prt("Press '?' for help.", help_prompt_loc, 0);
1181
1182 break;
1183 }
1184
1185 default:
1186 {
1187 /* Extract direction */
1188 d = target_dir(press.key);
1189
1190 /* Oops */
1191 if (!d) bell("Illegal command for target mode!");
1192
1193 break;
1194 }
1195 }
1196
1197 /* Hack -- move around */
1198 if (d) {
1199 int old_y = targets->pts[m].y;
1200 int old_x = targets->pts[m].x;
1201
1202 /* Find a new monster */
1203 i = target_pick(old_y, old_x, ddy[d], ddx[d], targets);
1204
1205 /* Scroll to find interesting grid */
1206 if (i < 0) {
1207 int old_wy = Term->offset_y;
1208 int old_wx = Term->offset_x;
1209
1210 /* Change if legal */
1211 if (change_panel(d)) {
1212 /* Recalculate interesting grids */
1213 point_set_dispose(targets);
1214 targets = target_get_monsters(mode, NULL);
1215
1216 /* Find a new monster */
1217 i = target_pick(old_y, old_x, ddy[d], ddx[d], targets);
1218
1219 /* Restore panel if needed */
1220 if ((i < 0) && modify_panel(Term, old_wy, old_wx)) {
1221 /* Recalculate interesting grids */
1222 point_set_dispose(targets);
1223 targets = target_get_monsters(mode, NULL);
1224 }
1225
1226 /* Handle stuff */
1227 handle_stuff(player);
1228 }
1229 }
1230
1231 /* Use interesting grid if found */
1232 if (i >= 0) m = i;
1233 }
1234 } else {
1235 /* Update help */
1236 if (help) {
1237 bool good_target = target_able(square_monster(cave, loc(x, y)));
1238 target_display_help(good_target,
1239 !(flag && point_set_size(targets)));
1240 }
1241
1242 /* Find the path. */
1243 path_n = project_path(path_g, z_info->max_range, loc(px, py),
1244 loc(x, y), PROJECT_THRU | PROJECT_INFO);
1245
1246 /* Draw the path in "target" mode. If there is one */
1247 if (mode & (TARGET_KILL))
1248 path_drawn = draw_path (path_n, path_g, path_char, path_attr,
1249 py, px);
1250
1251 /* Describe and Prompt (enable "TARGET_LOOK") */
1252 press = target_set_interactive_aux(y, x, mode | TARGET_LOOK);
1253
1254 /* Remove the path */
1255 if (path_drawn) load_path(path_n, path_g, path_char, path_attr);
1256
1257 /* Assume no direction */
1258 d = 0;
1259
1260 /* Analyze the keypress */
1261 if (press.type == EVT_MOUSE) {
1262 if (press.mouse.button == 3) {
1263 /* give the target selection command */
1264 press.mouse.button = 2;
1265 press.mouse.mods = KC_MOD_CONTROL;
1266 }
1267 if (press.mouse.button == 2) {
1268 if (mode & (TARGET_KILL)) {
1269 if ((y == KEY_GRID_Y(press))
1270 && (x == KEY_GRID_X(press))) {
1271 d = -1;
1272 }
1273 }
1274 y = KEY_GRID_Y(press);
1275 x = KEY_GRID_X(press);
1276 if (press.mouse.mods & KC_MOD_CONTROL) {
1277 /* same as keyboard target selection command below */
1278 target_set_location(y, x);
1279 done = true;
1280 } else if (press.mouse.mods & KC_MOD_ALT) {
1281 /* go to spot - same as 'g' command below */
1282 cmdq_push(CMD_PATHFIND);
1283 cmd_set_arg_point(cmdq_peek(), "point", loc(x, y));
1284 done = true;
1285 } else {
1286 /* cancel look mode */
1287 done = true;
1288 if (d == -1) {
1289 target_set_location(y, x);
1290 d = 0;
1291 }
1292 }
1293 } else {
1294 int dungeon_hgt = cave->height;
1295 int dungeon_wid = cave->width;
1296
1297 y = KEY_GRID_Y(press);
1298 x = KEY_GRID_X(press);
1299
1300 if (press.mouse.y <= 1) {
1301 /* move the screen north */
1302 y--;
1303 } else if (press.mouse.y >= (Term->hgt - 2)) {
1304 /* move the screen south */
1305 y++;
1306 } else if (press.mouse.x <= COL_MAP) {
1307 /* move the screen in west */
1308 x--;
1309 } else if (press.mouse.x >= (Term->wid - 2)) {
1310 /* move the screen east */
1311 x++;
1312 }
1313
1314 if (y < 0) y = 0;
1315 if (x < 0) x = 0;
1316 if (y >= dungeon_hgt-1) y = dungeon_hgt-1;
1317 if (x >= dungeon_wid-1) x = dungeon_wid-1;
1318
1319 /* Adjust panel if needed */
1320 if (adjust_panel_help(y, x, help)) {
1321 /* Handle stuff */
1322 handle_stuff(player);
1323
1324 /* Recalculate interesting grids */
1325 point_set_dispose(targets);
1326 targets = target_get_monsters(mode, NULL);
1327 }
1328
1329 if (square_monster(cave, loc(x, y)) ||
1330 square_object(cave, loc(x, y))) {
1331 /* scan the interesting list and see if there in
1332 * anything here */
1333 for (i = 0; i < point_set_size(targets); i++) {
1334 if ((y == targets->pts[i].y) &&
1335 (x == targets->pts[i].x)) {
1336 m = i;
1337 flag = true;
1338 break;
1339 }
1340 }
1341 } else {
1342 flag = false;
1343 }
1344 }
1345 } else
1346 switch (press.key.code)
1347 {
1348 case ESCAPE:
1349 case 'q':
1350 {
1351 done = true;
1352 break;
1353 }
1354
1355 case ' ':
1356 case '*':
1357 case '+':
1358 case '-':
1359 {
1360 break;
1361 }
1362
1363 case 'p':
1364 {
1365 /* Recenter around player */
1366 verify_panel();
1367
1368 /* Handle stuff */
1369 handle_stuff(player);
1370
1371 y = player->grid.y;
1372 x = player->grid.x;
1373 }
1374
1375 case 'o':
1376 {
1377 break;
1378 }
1379
1380 case 'm':
1381 {
1382 flag = true;
1383
1384 m = 0;
1385 bd = 999;
1386
1387 /* Pick a nearby monster */
1388 for (i = 0; i < point_set_size(targets); i++) {
1389 t = distance(loc(x, y), targets->pts[i]);
1390
1391 /* Pick closest */
1392 if (t < bd) {
1393 m = i;
1394 bd = t;
1395 }
1396 }
1397
1398 /* Nothing interesting */
1399 if (bd == 999) flag = false;
1400
1401 break;
1402 }
1403
1404 case 't':
1405 case '5':
1406 case '0':
1407 case '.':
1408 {
1409 target_set_location(y, x);
1410 done = true;
1411 break;
1412 }
1413
1414 case 'g':
1415 {
1416 cmdq_push(CMD_PATHFIND);
1417 cmd_set_arg_point(cmdq_peek(), "point", loc(x, y));
1418 done = true;
1419 break;
1420 }
1421
1422 case '?':
1423 {
1424 help = !help;
1425
1426 /* Redraw main window */
1427 player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIP);
1428 Term_clear();
1429 handle_stuff(player);
1430 if (!help)
1431 prt("Press '?' for help.", help_prompt_loc, 0);
1432
1433 break;
1434 }
1435
1436 default:
1437 {
1438 /* Extract a direction */
1439 d = target_dir(press.key);
1440
1441 /* Oops */
1442 if (!d) bell("Illegal command for target mode!");
1443
1444 break;
1445 }
1446 }
1447
1448 /* Handle "direction" */
1449 if (d) {
1450 int dungeon_hgt = cave->height;
1451 int dungeon_wid = cave->width;
1452
1453 /* Move */
1454 x += ddx[d];
1455 y += ddy[d];
1456
1457 /* Slide into legality */
1458 if (x >= dungeon_wid - 1) x--;
1459 else if (x <= 0) x++;
1460
1461 /* Slide into legality */
1462 if (y >= dungeon_hgt - 1) y--;
1463 else if (y <= 0) y++;
1464
1465 /* Adjust panel if needed */
1466 if (adjust_panel_help(y, x, help)) {
1467 /* Handle stuff */
1468 handle_stuff(player);
1469
1470 /* Recalculate interesting grids */
1471 point_set_dispose(targets);
1472 targets = target_get_monsters(mode, NULL);
1473 }
1474 }
1475 }
1476 }
1477
1478 /* Forget */
1479 point_set_dispose(targets);
1480
1481 /* Redraw as necessary */
1482 if (help) {
1483 player->upkeep->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIP);
1484 Term_clear();
1485 } else {
1486 prt("", 0, 0);
1487 prt("", help_prompt_loc, 0);
1488 player->upkeep->redraw |= (PR_DEPTH | PR_STATUS);
1489 }
1490
1491 /* Recenter around player */
1492 verify_panel();
1493
1494 /* Handle stuff */
1495 handle_stuff(player);
1496
1497 mem_free(path_attr);
1498 mem_free(path_char);
1499
1500 /* Allow animations again */
1501 allow_animations();
1502
1503 /* Failure to set target */
1504 if (!target_is_set()) return (false);
1505
1506 /* Success */
1507 return (true);
1508 }
1509
1510
1511