1 /**
2 * \file ui-knowledge.c
3 * \brief Player knowledge functions
4 *
5 * Copyright (c) 2000-2007 Eytan Zweig, Andrew Doull, Pete Mack.
6 * Copyright (c) 2010 Peter Denison, Chris Carr.
7 *
8 * This work is free software; you can redistribute it and/or modify it
9 * under the terms of either:
10 *
11 * a) the GNU General Public License as published by the Free Software
12 * Foundation, version 2, or
13 *
14 * b) the "Angband licence":
15 * This software may be copied and distributed for educational, research,
16 * and not for profit purposes provided that this copyright and statement
17 * are included in all such copies. Other copyrights may also apply.
18 */
19
20 #include "angband.h"
21 #include "cave.h"
22 #include "cmds.h"
23 #include "effects.h"
24 #include "effects-info.h"
25 #include "game-input.h"
26 #include "grafmode.h"
27 #include "init.h"
28 #include "mon-lore.h"
29 #include "mon-util.h"
30 #include "monster.h"
31 #include "obj-desc.h"
32 #include "obj-ignore.h"
33 #include "obj-knowledge.h"
34 #include "obj-info.h"
35 #include "obj-make.h"
36 #include "obj-pile.h"
37 #include "obj-tval.h"
38 #include "obj-util.h"
39 #include "object.h"
40 #include "player-calcs.h"
41 #include "player-history.h"
42 #include "player-util.h"
43 #include "project.h"
44 #include "store.h"
45 #include "target.h"
46 #include "trap.h"
47 #include "ui-context.h"
48 #include "ui-equip-cmp.h"
49 #include "ui-history.h"
50 #include "ui-menu.h"
51 #include "ui-mon-list.h"
52 #include "ui-mon-lore.h"
53 #include "ui-object.h"
54 #include "ui-obj-list.h"
55 #include "ui-options.h"
56 #include "ui-output.h"
57 #include "ui-prefs.h"
58 #include "ui-score.h"
59 #include "ui-store.h"
60 #include "ui-target.h"
61 #include "wizard.h"
62
63 /**
64 * The first part of this file contains the knowledge menus. Generic display
65 * routines are followed by sections which implement "subclasses" of the
66 * abstract classes represented by member_funcs and group_funcs.
67 *
68 * After the knowledge menus are various knowledge functions - message review;
69 * inventory, equipment, monster and object lists; symbol lookup; and the
70 * "locate" command which scrolls the screen around the current dungeon level.
71 */
72
73 typedef struct {
74 /* Name of this group */
75 const char *(*name)(int gid);
76
77 /* Compares gids of two oids */
78 int (*gcomp)(const void *, const void *);
79
80 /* Returns gid for an oid */
81 int (*group)(int oid);
82
83 /* Summary function for the "object" information. */
84 void (*summary)(int gid, const int *item_list, int n, int top, int row,
85 int col);
86
87 /* Maximum possible item count for this class */
88 int maxnum;
89
90 /* Items don't need to be IDed to recognize membership */
91 bool easy_know;
92
93 } group_funcs;
94
95 typedef struct {
96 /* Displays an entry at given location, including kill-count and graphics */
97 void (*display_member)(int col, int row, bool cursor, int oid);
98
99 /* Displays lore for an oid */
100 void (*lore)(int oid);
101
102
103 /* Required only for objects with modifiable display attributes
104 * Unknown 'flavors' return flavor attributes */
105
106 /* Get character attr for OID (by address) */
107 wchar_t *(*xchar)(int oid);
108
109 /* Get color attr for OID (by address) */
110 byte *(*xattr)(int oid);
111
112 /* Returns optional extra prompt */
113 const char *(*xtra_prompt)(int oid);
114
115 /* Handles optional extra actions */
116 void (*xtra_act)(struct keypress ch, int oid);
117
118 /* Does this kind have visual editing? */
119 bool is_visual;
120
121 } member_funcs;
122
123
124 /**
125 * Helper class for generating joins
126 */
127 typedef struct join {
128 int oid;
129 int gid;
130 } join_t;
131
132 /**
133 * A default group-by
134 */
135 static join_t *default_join;
136
137 /**
138 * Clipboard variables for copy & paste in visual mode
139 */
140 static byte attr_idx = 0;
141 static byte char_idx = 0;
142
143 /**
144 * ------------------------------------------------------------------------
145 * Knowledge menu utilities
146 * ------------------------------------------------------------------------ */
147
default_item_id(int oid)148 static int default_item_id(int oid)
149 {
150 return default_join[oid].oid;
151 }
152
default_group_id(int oid)153 static int default_group_id(int oid)
154 {
155 return default_join[oid].gid;
156 }
157
158 /**
159 * Return a specific ordering for the features
160 */
feat_order(int feat)161 static int feat_order(int feat)
162 {
163 struct feature *f = &f_info[feat];
164
165 switch (f->d_char)
166 {
167 case L'.': return 0;
168 case L'\'': case L'+': return 1;
169 case L'<': case L'>': return 2;
170 case L'#': return 3;
171 case L'*': case L'%' : return 4;
172 case L';': case L':' : return 5;
173 case L' ': return 7;
174 default:
175 {
176 return 6;
177 }
178 }
179 }
180
181
182 /**
183 * Return the actual width of a symbol
184 */
actual_width(int width)185 static int actual_width(int width)
186 {
187 return width * tile_width;
188 }
189
190 /**
191 * Return the actual height of a symbol
192 */
actual_height(int height)193 static int actual_height(int height)
194 {
195 return height * tile_height;
196 }
197
198
199 /**
200 * From an actual width, return the logical width
201 */
logical_width(int width)202 static int logical_width(int width)
203 {
204 return width / tile_width;
205 }
206
207 /**
208 * From an actual height, return the logical height
209 */
logical_height(int height)210 static int logical_height(int height)
211 {
212 return height / tile_height;
213 }
214
215
216 /**
217 * Display tiles.
218 */
display_tiles(int col,int row,int height,int width,byte attr_top,byte char_left)219 static void display_tiles(int col, int row, int height, int width,
220 byte attr_top, byte char_left)
221 {
222 int i, j;
223
224 /* Clear the display lines */
225 for (i = 0; i < height; i++)
226 Term_erase(col, row + i, width);
227
228 width = logical_width(width);
229 height = logical_height(height);
230
231 /* Display lines until done */
232 for (i = 0; i < height; i++) {
233 /* Display columns until done */
234 for (j = 0; j < width; j++) {
235 byte a;
236 unsigned char c;
237 int x = col + actual_width(j);
238 int y = row + actual_height(i);
239 int ia, ic;
240
241 ia = attr_top + i;
242 ic = char_left + j;
243
244 a = (byte)ia;
245 c = (unsigned char)ic;
246
247 /* Display symbol */
248 big_pad(x, y, a, c);
249 }
250 }
251 }
252
253
254 /**
255 * Place the cursor at the correct position for tile picking
256 */
place_tile_cursor(int col,int row,byte a,byte c,byte attr_top,byte char_left)257 static void place_tile_cursor(int col, int row, byte a, byte c, byte attr_top,
258 byte char_left)
259 {
260 int i = a - attr_top;
261 int j = c - char_left;
262
263 int x = col + actual_width(j);
264 int y = row + actual_height(i);
265
266 /* Place the cursor */
267 Term_gotoxy(x, y);
268 }
269
270
271 /**
272 * Remove the tile display and clear the screen
273 */
remove_tiles(int col,int row,bool * picker_ptr,int width,int height)274 static void remove_tiles(int col, int row, bool *picker_ptr, int width,
275 int height)
276 {
277 int i;
278
279 /* No more big cursor */
280 bigcurs = false;
281
282 /* Cancel visual list */
283 *picker_ptr = false;
284
285 /* Clear the display lines */
286 for (i = 0; i < height; i++)
287 Term_erase(col, row + i, width);
288
289 }
290
291 /**
292 * Do tile picker command -- Change tiles
293 */
tile_picker_command(ui_event ke,bool * tile_picker_ptr,int height,int width,byte * attr_top_ptr,byte * char_left_ptr,byte * cur_attr_ptr,byte * cur_char_ptr,int col,int row,int * delay)294 static bool tile_picker_command(ui_event ke, bool *tile_picker_ptr,
295 int height, int width, byte *attr_top_ptr,
296 byte *char_left_ptr, byte *cur_attr_ptr,
297 byte *cur_char_ptr, int col, int row,
298 int *delay)
299 {
300 static byte attr_old = 0;
301 static char char_old = 0;
302
303 /* These are the distance we want to maintain between the
304 * cursor and borders. */
305 int frame_left = logical_width(10);
306 int frame_right = logical_width(10);
307 int frame_top = logical_height(4);
308 int frame_bottom = logical_height(4);
309
310
311 /* Get mouse movement */
312 if (*tile_picker_ptr && (ke.type == EVT_MOUSE)) {
313 int eff_width = actual_width(width);
314 int eff_height = actual_height(height);
315 byte a = *cur_attr_ptr;
316 byte c = *cur_char_ptr;
317
318 int my = logical_height(ke.mouse.y - row);
319 int mx = logical_width(ke.mouse.x - col);
320
321 if ((my >= 0) && (my < eff_height) && (mx >= 0) && (mx < eff_width)
322 && ((ke.mouse.button == 1) || (a != *attr_top_ptr + my)
323 || (c != *char_left_ptr + mx))) {
324 /* Set the visual */
325 *cur_attr_ptr = a = *attr_top_ptr + my;
326 *cur_char_ptr = c = *char_left_ptr + mx;
327
328 /* Move the frame */
329 if (*char_left_ptr > MAX(0, (int)c - frame_left))
330 (*char_left_ptr)--;
331 if (*char_left_ptr + eff_width <= MIN(255, (int)c + frame_right))
332 (*char_left_ptr)++;
333 if (*attr_top_ptr > MAX(0, (int)a - frame_top))
334 (*attr_top_ptr)--;
335 if (*attr_top_ptr + eff_height <= MIN(255, (int)a + frame_bottom))
336 (*attr_top_ptr)++;
337
338 /* Delay */
339 *delay = 100;
340
341 /* Accept change */
342 if (ke.mouse.button)
343 remove_tiles(col, row, tile_picker_ptr, width, height);
344
345 return true;
346 } else if (ke.mouse.button == 2) {
347 /* Cancel change */
348 *cur_attr_ptr = attr_old;
349 *cur_char_ptr = char_old;
350 remove_tiles(col, row, tile_picker_ptr, width, height);
351
352 return true;
353 } else {
354 return false;
355 }
356 }
357
358 if (ke.type != EVT_KBRD)
359 return false;
360
361
362 switch (ke.key.code)
363 {
364 case ESCAPE:
365 {
366 if (*tile_picker_ptr) {
367 /* Cancel change */
368 *cur_attr_ptr = attr_old;
369 *cur_char_ptr = char_old;
370 remove_tiles(col, row, tile_picker_ptr, width, height);
371
372 return true;
373 }
374
375 break;
376 }
377
378 case KC_ENTER:
379 {
380 if (*tile_picker_ptr) {
381 /* Accept change */
382 remove_tiles(col, row, tile_picker_ptr, width, height);
383 return true;
384 }
385
386 break;
387 }
388
389 case 'V':
390 case 'v':
391 {
392 /* No visual mode without graphics, for now - NRM */
393 if (current_graphics_mode != NULL)
394 if (current_graphics_mode->grafID == 0)
395 break;
396
397 if (!*tile_picker_ptr) {
398 *tile_picker_ptr = true;
399 bigcurs = true;
400
401 *attr_top_ptr = (byte)MAX(0, (int)*cur_attr_ptr - frame_top);
402 *char_left_ptr = (char)MAX(0, (int)*cur_char_ptr - frame_left);
403
404 attr_old = *cur_attr_ptr;
405 char_old = *cur_char_ptr;
406 } else {
407 /* Cancel change */
408 *cur_attr_ptr = attr_old;
409 *cur_char_ptr = char_old;
410 remove_tiles(col, row, tile_picker_ptr, width, height);
411 }
412
413 return true;
414 }
415
416 case 'C':
417 case 'c':
418 {
419 /* Set the tile */
420 attr_idx = *cur_attr_ptr;
421 char_idx = *cur_char_ptr;
422
423 return true;
424 }
425
426 case 'P':
427 case 'p':
428 {
429 if (attr_idx) {
430 /* Set the char */
431 *cur_attr_ptr = attr_idx;
432 *attr_top_ptr = (byte)MAX(0, (int)*cur_attr_ptr - frame_top);
433 }
434
435 if (char_idx) {
436 /* Set the char */
437 *cur_char_ptr = char_idx;
438 *char_left_ptr = (char)MAX(0, (int)*cur_char_ptr - frame_left);
439 }
440
441 return true;
442 }
443
444 default:
445 {
446 int d = target_dir(ke.key);
447 byte a = *cur_attr_ptr;
448 byte c = *cur_char_ptr;
449
450 if (!*tile_picker_ptr)
451 break;
452
453 bigcurs = true;
454
455 /* Restrict direction */
456 if ((a == 0) && (ddy[d] < 0)) d = 0;
457 if ((c == 0) && (ddx[d] < 0)) d = 0;
458 if ((a == 255) && (ddy[d] > 0)) d = 0;
459 if ((c == 255) && (ddx[d] > 0)) d = 0;
460
461 a += ddy[d];
462 c += ddx[d];
463
464 /* Set the tile */
465 *cur_attr_ptr = a;
466 *cur_char_ptr = c;
467
468 /* Move the frame */
469 if (ddx[d] < 0 &&
470 *char_left_ptr > MAX(0, (int)c - frame_left))
471 (*char_left_ptr)--;
472 if ((ddx[d] > 0) &&
473 *char_left_ptr + (width / tile_width) <=
474 MIN(255, (int)c + frame_right))
475 (*char_left_ptr)++;
476
477 if (ddy[d] < 0 &&
478 *attr_top_ptr > MAX(0, (int)a - frame_top))
479 (*attr_top_ptr)--;
480 if (ddy[d] > 0 &&
481 *attr_top_ptr + (height / tile_height) <=
482 MIN(255, (int)a + frame_bottom))
483 (*attr_top_ptr)++;
484
485 /* We need to always eat the input even if it is clipped,
486 * otherwise it will be interpreted as a change object
487 * selection command with messy results.
488 */
489 return true;
490 }
491 }
492
493 /* Tile picker command is not used */
494 return false;
495 }
496
497
498 /**
499 * Display glyph and colours
500 */
display_glyphs(int col,int row,int height,int width,byte a,wchar_t c)501 static void display_glyphs(int col, int row, int height, int width, byte a,
502 wchar_t c)
503 {
504 int i;
505 int x, y;
506
507 /* Clear the display lines */
508 for (i = 0; i < height; i++)
509 Term_erase(col, row + i, width);
510
511 /* Prompt */
512 prt("Choose colour:", row + height/2, col);
513 Term_locate(&x, &y);
514 for (i = 0; i < MAX_COLORS; i++) big_pad(x + i, y, i, c);
515
516 /* Place the cursor */
517 Term_gotoxy(x + a, y);
518 }
519
520 /**
521 * Do glyph picker command -- Change glyphs
522 */
glyph_command(ui_event ke,bool * glyph_picker_ptr,int height,int width,byte * cur_attr_ptr,wchar_t * cur_char_ptr,int col,int row)523 static bool glyph_command(ui_event ke, bool *glyph_picker_ptr,
524 int height, int width, byte *cur_attr_ptr,
525 wchar_t *cur_char_ptr, int col, int row)
526 {
527 static byte attr_old = 0;
528 static char char_old = 0;
529
530 /* Get mouse movement */
531 if (*glyph_picker_ptr && (ke.type == EVT_MOUSE)) {
532 int mx = logical_width(ke.mouse.x - col);
533
534 if (ke.mouse.y != row + height / 2) return false;
535
536 if ((mx >= 0) && (mx < MAX_COLORS) && (ke.mouse.button == 1)) {
537 /* Set the visual */
538 *cur_attr_ptr = mx - 14;
539
540 /* Accept change */
541 remove_tiles(col, row, glyph_picker_ptr, width, height);
542
543 return true;
544 } else {
545 return false;
546 }
547 }
548
549 if (ke.type != EVT_KBRD)
550 return false;
551
552
553 switch (ke.key.code)
554 {
555 case ESCAPE:
556 {
557 if (*glyph_picker_ptr) {
558 /* Cancel change */
559 *cur_attr_ptr = attr_old;
560 *cur_char_ptr = char_old;
561 remove_tiles(col, row, glyph_picker_ptr, width, height);
562
563 return true;
564 }
565
566 break;
567 }
568
569 case KC_ENTER:
570 {
571 if (*glyph_picker_ptr) {
572 /* Accept change */
573 remove_tiles(col, row, glyph_picker_ptr, width, height);
574 return true;
575 }
576
577 break;
578 }
579
580 case 'V':
581 case 'v':
582 {
583 if (!*glyph_picker_ptr) {
584 *glyph_picker_ptr = true;
585
586 attr_old = *cur_attr_ptr;
587 char_old = *cur_char_ptr;
588 } else {
589 /* Cancel change */
590 *cur_attr_ptr = attr_old;
591 *cur_char_ptr = char_old;
592 remove_tiles(col, row, glyph_picker_ptr, width, height);
593 }
594
595 return true;
596 }
597
598 case 'C':
599 case 'c':
600 {
601 /* Set the tile */
602 attr_idx = *cur_attr_ptr;
603 char_idx = *cur_char_ptr;
604
605 return true;
606 }
607
608 case 'P':
609 case 'p':
610 {
611 if (attr_idx) {
612 /* Set the char */
613 *cur_attr_ptr = attr_idx;
614 }
615
616 if (char_idx) {
617 /* Set the char */
618 *cur_char_ptr = char_idx;
619 }
620
621 return true;
622 }
623
624 case 'i':
625 case 'I':
626 {
627 if (*glyph_picker_ptr) {
628 char code_point[6];
629 bool res = false;
630
631 /* Ask the user for a code point */
632 Term_gotoxy(col, row + height/2 + 2);
633 res = get_string("(up to 5 hex digits):", code_point, 5);
634
635 /* Process input */
636 if (res) {
637 unsigned long int point = strtoul(code_point,
638 (char **)NULL, 16);
639 *cur_char_ptr = (wchar_t) point;
640 return true;
641 }
642 }
643
644 break;
645
646
647 }
648
649 default:
650 {
651 int d = target_dir(ke.key);
652 byte a = *cur_attr_ptr;
653
654 if (!*glyph_picker_ptr)
655 break;
656
657 /* Horizontal only */
658 if (ddy[d] != 0) break;
659
660 /* Horizontal movement */
661 if (ddx[d] != 0) {
662 a += ddx[d] + BASIC_COLORS;
663 a = a % BASIC_COLORS;
664 *cur_attr_ptr = a;
665 }
666
667
668 /* We need to always eat the input even if it is clipped,
669 * otherwise it will be interpreted as a change object
670 * selection command with messy results.
671 */
672 return true;
673 }
674 }
675
676 /* Glyph picker command is not used */
677 return false;
678 }
679
display_group_member(struct menu * menu,int oid,bool cursor,int row,int col,int wid)680 static void display_group_member(struct menu *menu, int oid,
681 bool cursor, int row, int col, int wid)
682 {
683 const member_funcs *o_funcs = menu->menu_data;
684 byte attr = curs_attrs[CURS_KNOWN][cursor == oid];
685
686 (void)wid;
687
688 /* Print the interesting part */
689 o_funcs->display_member(col, row, cursor, oid);
690
691 #ifdef KNOWLEDGE_MENU_DEBUG
692 c_put_str(attr, format("%d", oid), row, 60);
693 #endif
694
695 /* Do visual mode */
696 if (o_funcs->is_visual && o_funcs->xattr) {
697 wchar_t c = *o_funcs->xchar(oid);
698 byte a = *o_funcs->xattr(oid);
699
700 c_put_str(attr, format("%d/%d", a, c), row, 60);
701 }
702 }
703
recall_prompt(int oid)704 static const char *recall_prompt(int oid)
705 {
706 (void)oid;
707 return ", 'r' to recall";
708 }
709
710 #define swap(a, b) (swapspace = (void*)(a)), ((a) = (b)), ((b) = swapspace)
711
712 /* Flag value for missing array entry */
713 #define MISSING -17
714
715 /**
716 * Interactive group by.
717 * Recognises inscriptions, graphical symbols, lore
718 */
display_knowledge(const char * title,int * obj_list,int o_count,group_funcs g_funcs,member_funcs o_funcs,const char * otherfields)719 static void display_knowledge(const char *title, int *obj_list, int o_count,
720 group_funcs g_funcs, member_funcs o_funcs,
721 const char *otherfields)
722 {
723 /* Maximum number of groups to display */
724 int max_group = g_funcs.maxnum < o_count ? g_funcs.maxnum : o_count ;
725
726 /* This could (should?) be (void **) */
727 int *g_list, *g_offset;
728
729 const char **g_names;
730
731 int g_name_len = 8; /* group name length, minumum is 8 */
732
733 int grp_cnt = 0; /* total number groups */
734
735 int g_cur = 0, grp_old = -1; /* group list positions */
736 int o_cur = 0; /* object list positions */
737 int g_o_count = 0; /* object count for group */
738 int oid; /* object identifiers */
739
740 region title_area = { 0, 0, 0, 4 };
741 region group_region = { 0, 6, MISSING, -2 };
742 region object_region = { MISSING, 6, 0, -2 };
743
744 /* display state variables */
745 bool tiles = (current_graphics_mode != NULL);
746 bool tile_picker = false;
747 bool glyph_picker = false;
748 byte attr_top = 0;
749 byte char_left = 0;
750
751 int delay = 0;
752
753 struct menu group_menu;
754 struct menu object_menu;
755 menu_iter object_iter = { NULL, NULL, display_group_member, NULL, NULL };
756
757 /* Panel state */
758 /* These are swapped in parallel whenever the actively browsing " */
759 /* changes */
760 int *active_cursor = &g_cur, *inactive_cursor = &o_cur;
761 struct menu *active_menu = &group_menu, *inactive_menu = &object_menu;
762 int panel = 0;
763
764 void *swapspace;
765 bool do_swap = false;
766
767 bool flag = false;
768 bool redraw = true;
769
770 int browser_rows;
771 int wid, hgt;
772 int i;
773 int prev_g = -1;
774
775 int omode = OPT(player, rogue_like_commands);
776 ui_event ke;
777
778 /* Get size */
779 Term_get_size(&wid, &hgt);
780 browser_rows = hgt - 8;
781
782 /* Disable the roguelike commands for the duration */
783 OPT(player, rogue_like_commands) = false;
784
785 /* Determine if using tiles or not */
786 if (tiles) tiles = (current_graphics_mode->grafID != 0);
787
788 if (g_funcs.gcomp)
789 sort(obj_list, o_count, sizeof(*obj_list), g_funcs.gcomp);
790
791 /* Sort everything into group order */
792 g_list = mem_zalloc((max_group + 1) * sizeof(int));
793 g_offset = mem_zalloc((max_group + 1) * sizeof(int));
794
795 for (i = 0; i < o_count; i++) {
796 if (prev_g != g_funcs.group(obj_list[i])) {
797 prev_g = g_funcs.group(obj_list[i]);
798 g_offset[grp_cnt] = i;
799 g_list[grp_cnt++] = prev_g;
800 }
801 }
802
803 g_offset[grp_cnt] = o_count;
804 g_list[grp_cnt] = -1;
805
806
807 /* The compact set of group names, in display order */
808 g_names = mem_zalloc(grp_cnt * sizeof(char*));
809
810 for (i = 0; i < grp_cnt; i++) {
811 int len;
812 g_names[i] = g_funcs.name(g_list[i]);
813 len = strlen(g_names[i]);
814 if (len > g_name_len) g_name_len = len;
815 }
816
817 /* Reasonable max group name len */
818 if (g_name_len >= 20) g_name_len = 20;
819
820 object_region.col = g_name_len + 3;
821 group_region.width = g_name_len;
822
823
824 /* Leave room for the group summary information */
825 if (g_funcs.summary) object_region.page_rows = -3;
826
827
828 /* Set up the two menus */
829 menu_init(&group_menu, MN_SKIN_SCROLL, menu_find_iter(MN_ITER_STRINGS));
830 menu_setpriv(&group_menu, grp_cnt, g_names);
831 menu_layout(&group_menu, &group_region);
832 group_menu.flags |= MN_DBL_TAP;
833
834 menu_init(&object_menu, MN_SKIN_SCROLL, &object_iter);
835 menu_setpriv(&object_menu, 0, &o_funcs);
836 menu_layout(&object_menu, &object_region);
837 object_menu.flags |= MN_DBL_TAP;
838
839 o_funcs.is_visual = false;
840
841 /* Save screen */
842 screen_save();
843 clear_from(0);
844
845 /* This is the event loop for a multi-region panel */
846 /* Panels are -- text panels, two menus, and visual browser */
847 /* with "pop-up menu" for lore */
848 while ((!flag) && (grp_cnt)) {
849 bool recall = false;
850
851 if (redraw) {
852 /* Print the title bits */
853 region_erase(&title_area);
854 prt(format("Knowledge - %s", title), 2, 0);
855 prt("Group", 4, 0);
856 prt("Name", 4, g_name_len + 3);
857
858 if (otherfields)
859 prt(otherfields, 4, 46);
860
861
862 /* Print dividers: horizontal and vertical */
863 for (i = 0; i < 79; i++)
864 Term_putch(i, 5, COLOUR_WHITE, L'=');
865
866 for (i = 0; i < browser_rows; i++)
867 Term_putch(g_name_len + 1, 6 + i, COLOUR_WHITE, L'|');
868
869
870 /* Reset redraw flag */
871 redraw = false;
872 }
873
874 if (g_cur != grp_old) {
875 grp_old = g_cur;
876 o_cur = 0;
877 g_o_count = g_offset[g_cur+1] - g_offset[g_cur];
878 menu_set_filter(&object_menu, obj_list + g_offset[g_cur],
879 g_o_count);
880 group_menu.cursor = g_cur;
881 object_menu.cursor = 0;
882 }
883
884 /* HACK ... */
885 if (!(tile_picker || glyph_picker)) {
886 /* ... The object menu may be browsing the entire group... */
887 o_funcs.is_visual = false;
888 menu_set_filter(&object_menu, obj_list + g_offset[g_cur],
889 g_o_count);
890 object_menu.cursor = o_cur;
891 } else {
892 /* ... or just a single element in the group. */
893 o_funcs.is_visual = true;
894 menu_set_filter(&object_menu, obj_list + o_cur + g_offset[g_cur],
895 1);
896 object_menu.cursor = 0;
897 }
898
899 oid = obj_list[g_offset[g_cur]+o_cur];
900
901 /* Print prompt */
902 {
903 const char *pedit = (!o_funcs.xattr) ? "" :
904 (!(attr_idx|char_idx) ?
905 ", 'c' to copy" : ", 'c', 'p' to paste");
906 const char *xtra = o_funcs.xtra_prompt ?
907 o_funcs.xtra_prompt(oid) : "";
908 const char *pvs = "";
909
910 if (tile_picker) pvs = ", ENTER to accept";
911 else if (glyph_picker) pvs = ", 'i' to insert, ENTER to accept";
912 else if (o_funcs.xattr) pvs = ", 'v' for visuals";
913
914 prt(format("<dir>%s%s%s, ESC", pvs, pedit, xtra), hgt - 1, 0);
915 }
916
917 if (do_swap) {
918 do_swap = false;
919 swap(active_menu, inactive_menu);
920 swap(active_cursor, inactive_cursor);
921 panel = 1 - panel;
922 }
923
924 if (g_funcs.summary && !tile_picker && !glyph_picker) {
925 g_funcs.summary(g_cur, obj_list, g_o_count, g_offset[g_cur],
926 object_menu.active.row +
927 object_menu.active.page_rows,
928 object_region.col);
929 }
930
931 menu_refresh(inactive_menu, false);
932 menu_refresh(active_menu, false);
933
934 handle_stuff(player);
935
936 if (tile_picker) {
937 bigcurs = true;
938 display_tiles(g_name_len + 3, 7, browser_rows - 1,
939 wid - (g_name_len + 3), attr_top,
940 char_left);
941 place_tile_cursor(g_name_len + 3, 7,
942 *o_funcs.xattr(oid),
943 (byte) *o_funcs.xchar(oid),
944 attr_top, char_left);
945 }
946
947 if (glyph_picker) {
948 display_glyphs(g_name_len + 3, 7, browser_rows - 1,
949 wid - (g_name_len + 3),
950 *o_funcs.xattr(oid),
951 *o_funcs.xchar(oid));
952 }
953
954 if (delay) {
955 /* Force screen update */
956 Term_fresh();
957
958 /* Delay */
959 Term_xtra(TERM_XTRA_DELAY, delay);
960
961 delay = 0;
962 }
963
964 ke = inkey_ex();
965 if (!tile_picker && !glyph_picker) {
966 ui_event ke0 = EVENT_EMPTY;
967
968 if (ke.type == EVT_MOUSE)
969 menu_handle_mouse(active_menu, &ke, &ke0);
970 else if (ke.type == EVT_KBRD)
971 menu_handle_keypress(active_menu, &ke, &ke0);
972
973 if (ke0.type != EVT_NONE)
974 ke = ke0;
975 }
976
977 /* XXX Do visual mode command if needed */
978 if (o_funcs.xattr && o_funcs.xchar) {
979 if (tiles) {
980 if (tile_picker_command(ke, &tile_picker,
981 browser_rows - 1,
982 wid - (g_name_len + 3),
983 &attr_top, &char_left,
984 o_funcs.xattr(oid),
985 (byte *) o_funcs.xchar(oid),
986 g_name_len + 3, 7, &delay))
987 continue;
988 } else {
989 if (glyph_command(ke, &glyph_picker,
990 browser_rows - 1, wid - (g_name_len + 3),
991 o_funcs.xattr(oid), o_funcs.xchar(oid),
992 g_name_len + 3, 7))
993 continue;
994 }
995 }
996
997 switch (ke.type)
998 {
999 case EVT_KBRD:
1000 {
1001 if (ke.key.code == 'r' || ke.key.code == 'R')
1002 recall = true;
1003 else if (o_funcs.xtra_act)
1004 o_funcs.xtra_act(ke.key, oid);
1005
1006 break;
1007 }
1008
1009 case EVT_MOUSE:
1010 {
1011 /* Change active panels */
1012 if (region_inside(&inactive_menu->active, &ke)) {
1013 swap(active_menu, inactive_menu);
1014 swap(active_cursor, inactive_cursor);
1015 panel = 1-panel;
1016 }
1017
1018 continue;
1019 }
1020
1021 case EVT_ESCAPE:
1022 {
1023 if (panel == 1)
1024 do_swap = true;
1025 else
1026 flag = true;
1027
1028 break;
1029 }
1030
1031 case EVT_SELECT:
1032 {
1033 if (panel == 0)
1034 do_swap = true;
1035 else if (panel == 1 && oid >= 0 && o_cur == active_menu->cursor)
1036 recall = true;
1037 break;
1038 }
1039
1040 case EVT_MOVE:
1041 {
1042 *active_cursor = active_menu->cursor;
1043 break;
1044 }
1045
1046 default:
1047 {
1048 break;
1049 }
1050 }
1051
1052 /* Recall on screen */
1053 if (recall) {
1054 if (oid >= 0)
1055 o_funcs.lore(oid);
1056
1057 redraw = true;
1058 }
1059 }
1060
1061 /* Restore roguelike option */
1062 OPT(player, rogue_like_commands) = omode;
1063
1064 /* Prompt */
1065 if (!grp_cnt)
1066 prt(format("No %s known.", title), 15, 0);
1067
1068 mem_free(g_names);
1069 mem_free(g_offset);
1070 mem_free(g_list);
1071
1072 screen_load();
1073 }
1074
1075 /**
1076 * ------------------------------------------------------------------------
1077 * MONSTERS
1078 * ------------------------------------------------------------------------ */
1079
1080 /**
1081 * Description of each monster group.
1082 */
1083 static struct
1084 {
1085 const wchar_t *chars;
1086 const char *name;
1087 } monster_group[] = {
1088 { (const wchar_t *)-1, "Uniques" },
1089 { L"A", "Ainur" },
1090 { L"a", "Ants" },
1091 { L"b", "Bats" },
1092 { L"B", "Birds" },
1093 { L"C", "Canines" },
1094 { L"c", "Centipedes" },
1095 { L"uU", "Demons" },
1096 { L"dD", "Dragons" },
1097 { L"vE", "Elementals/Vortices" },
1098 { L"e", "Eyes/Beholders" },
1099 { L"f", "Felines" },
1100 { L"G", "Ghosts" },
1101 { L"OP", "Giants/Ogres" },
1102 { L"g", "Golems" },
1103 { L"H", "Harpies/Hybrids" },
1104 { L"h", "Hominids (Elves, Dwarves)" },
1105 { L"M", "Hydras" },
1106 { L"i", "Icky Things" },
1107 { L"FI", "Insects" },
1108 { L"j", "Jellies" },
1109 { L"K", "Killer Beetles" },
1110 { L"k", "Kobolds" },
1111 { L"L", "Lichs" },
1112 { L"tp", "Men" },
1113 { L".$!?=~_", "Mimics" },
1114 { L"m", "Molds" },
1115 { L",", "Mushroom Patches" },
1116 { L"n", "Nagas" },
1117 { L"o", "Orcs" },
1118 { L"q", "Quadrupeds" },
1119 { L"Q", "Quylthulgs" },
1120 { L"R", "Reptiles/Amphibians" },
1121 { L"r", "Rodents" },
1122 { L"S", "Scorpions/Spiders" },
1123 { L"s", "Skeletons/Drujs" },
1124 { L"J", "Snakes" },
1125 { L"l", "Trees/Ents" },
1126 { L"T", "Trolls" },
1127 { L"V", "Vampires" },
1128 { L"W", "Wights/Wraiths" },
1129 { L"w", "Worms/Worm Masses" },
1130 { L"X", "Xorns/Xarens" },
1131 { L"y", "Yeeks" },
1132 { L"Y", "Yeti" },
1133 { L"Z", "Zephyr Hounds" },
1134 { L"z", "Zombies" },
1135 { NULL, NULL }
1136 };
1137
1138 /**
1139 * Display a monster
1140 */
display_monster(int col,int row,bool cursor,int oid)1141 static void display_monster(int col, int row, bool cursor, int oid)
1142 {
1143 /* HACK Get the race index. (Should be a wrapper function) */
1144 int r_idx = default_item_id(oid);
1145
1146 /* Access the race */
1147 struct monster_race *race = &r_info[r_idx];
1148 struct monster_lore *lore = &l_list[r_idx];
1149
1150 /* Choose colors */
1151 byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1152 byte a = monster_x_attr[race->ridx];
1153 wchar_t c = monster_x_char[race->ridx];
1154
1155 if ((tile_height != 1) && (a & 0x80)) {
1156 a = race->d_attr;
1157 c = race->d_char;
1158 /* If uniques are purple, make it so */
1159 if (OPT(player, purple_uniques) && rf_has(race->flags, RF_UNIQUE))
1160 a = COLOUR_VIOLET;
1161 }
1162 /* If uniques are purple, make it so */
1163 else if (OPT(player, purple_uniques) && !(a & 0x80) &&
1164 rf_has(race->flags, RF_UNIQUE))
1165 a = COLOUR_VIOLET;
1166
1167 /* Display the name */
1168 c_prt(attr, race->name, row, col);
1169
1170 /* Display symbol */
1171 big_pad(66, row, a, c);
1172
1173 /* Display kills */
1174 if (!race->rarity) {
1175 put_str(format("%s", "shape"), row, 70);
1176 } else if (rf_has(race->flags, RF_UNIQUE)) {
1177 put_str(format("%s", (race->max_num == 0)? " dead" : "alive"),
1178 row, 70);
1179 } else {
1180 put_str(format("%5d", lore->pkills), row, 70);
1181 }
1182 }
1183
m_cmp_race(const void * a,const void * b)1184 static int m_cmp_race(const void *a, const void *b)
1185 {
1186 const int a_val = *(const int *)a;
1187 const int b_val = *(const int *)b;
1188 const struct monster_race *r_a = &r_info[default_item_id(a_val)];
1189 const struct monster_race *r_b = &r_info[default_item_id(b_val)];
1190 int gid = default_group_id(a_val);
1191
1192 /* Group by */
1193 int c = gid - default_group_id(b_val);
1194 if (c)
1195 return c;
1196
1197 /* Order results */
1198 c = r_a->d_char - r_b->d_char;
1199 if (c && gid != 0) {
1200 /* UNIQUE group is ordered by level & name only */
1201 /* Others by order they appear in the group symbols */
1202 return wcschr(monster_group[gid].chars, r_a->d_char)
1203 - wcschr(monster_group[gid].chars, r_b->d_char);
1204 }
1205 c = r_a->level - r_b->level;
1206 if (c)
1207 return c;
1208
1209 return strcmp(r_a->name, r_b->name);
1210 }
1211
m_xchar(int oid)1212 static wchar_t *m_xchar(int oid)
1213 {
1214 return &monster_x_char[default_join[oid].oid];
1215 }
1216
m_xattr(int oid)1217 static byte *m_xattr(int oid)
1218 {
1219 return &monster_x_attr[default_join[oid].oid];
1220 }
1221
race_name(int gid)1222 static const char *race_name(int gid)
1223 {
1224 return monster_group[gid].name;
1225 }
1226
mon_lore(int oid)1227 static void mon_lore(int oid)
1228 {
1229 int r_idx;
1230 struct monster_race *race;
1231 const struct monster_lore *lore;
1232 textblock *tb;
1233
1234 r_idx = default_item_id(oid);
1235
1236 assert(r_idx);
1237 race = &r_info[r_idx];
1238 lore = get_lore(race);
1239
1240 /* Update the monster recall window */
1241 monster_race_track(player->upkeep, race);
1242 handle_stuff(player);
1243
1244 tb = textblock_new();
1245 lore_description(tb, race, lore, false);
1246 textui_textblock_show(tb, SCREEN_REGION, NULL);
1247 textblock_free(tb);
1248 }
1249
mon_summary(int gid,const int * item_list,int n,int top,int row,int col)1250 static void mon_summary(int gid, const int *item_list, int n, int top,
1251 int row, int col)
1252 {
1253 int i;
1254 int kills = 0;
1255
1256 /* Access the race */
1257 for (i = 0; i < n; i++) {
1258 int oid = default_join[item_list[i+top]].oid;
1259 kills += l_list[oid].pkills;
1260 }
1261
1262 /* Different display for the first item if we've got uniques to show */
1263 if (gid == 0 &&
1264 rf_has((&r_info[default_join[item_list[0]].oid])->flags, RF_UNIQUE)) {
1265 c_prt(COLOUR_L_BLUE, format("%d known uniques, %d slain.", n, kills),
1266 row, col);
1267 } else {
1268 int tkills = 0;
1269
1270 for (i = 0; i < z_info->r_max; i++)
1271 tkills += l_list[i].pkills;
1272
1273 c_prt(COLOUR_L_BLUE, format("Creatures slain: %d/%d (in group/in total)", kills, tkills), row, col);
1274 }
1275 }
1276
count_known_monsters(void)1277 static int count_known_monsters(void)
1278 {
1279 int m_count = 0;
1280 int i;
1281 size_t j;
1282
1283 for (i = 0; i < z_info->r_max; i++) {
1284 struct monster_race *race = &r_info[i];
1285 if (!l_list[i].all_known && !l_list[i].sights) {
1286 continue;
1287 }
1288
1289 if (!race->name) continue;
1290
1291 if (rf_has(race->flags, RF_UNIQUE)) m_count++;
1292
1293 for (j = 1; j < N_ELEMENTS(monster_group) - 1; j++) {
1294 const wchar_t *pat = monster_group[j].chars;
1295 if (wcschr(pat, race->d_char)) m_count++;
1296 }
1297 }
1298
1299 return m_count;
1300 }
1301
1302 /**
1303 * Display known monsters.
1304 */
do_cmd_knowledge_monsters(const char * name,int row)1305 static void do_cmd_knowledge_monsters(const char *name, int row)
1306 {
1307 group_funcs r_funcs = {race_name, m_cmp_race, default_group_id, mon_summary,
1308 N_ELEMENTS(monster_group), false};
1309
1310 member_funcs m_funcs = {display_monster, mon_lore, m_xchar, m_xattr,
1311 recall_prompt, 0, 0};
1312
1313 int *monsters;
1314 int m_count = 0;
1315 int i;
1316 size_t j;
1317
1318 for (i = 0; i < z_info->r_max; i++) {
1319 struct monster_race *race = &r_info[i];
1320 if (!l_list[i].all_known && !l_list[i].sights) {
1321 continue;
1322 }
1323
1324 if (!race->name) continue;
1325
1326 if (rf_has(race->flags, RF_UNIQUE)) m_count++;
1327
1328 for (j = 1; j < N_ELEMENTS(monster_group) - 1; j++) {
1329 const wchar_t *pat = monster_group[j].chars;
1330 if (wcschr(pat, race->d_char)) m_count++;
1331 }
1332 }
1333
1334 default_join = mem_zalloc(m_count * sizeof(join_t));
1335 monsters = mem_zalloc(m_count * sizeof(int));
1336
1337 m_count = 0;
1338 for (i = 0; i < z_info->r_max; i++) {
1339 struct monster_race *race = &r_info[i];
1340 if (!l_list[i].all_known && !l_list[i].sights) {
1341 continue;
1342 }
1343
1344 if (!race->name) continue;
1345
1346 for (j = 0; j < N_ELEMENTS(monster_group) - 1; j++) {
1347 const wchar_t *pat = monster_group[j].chars;
1348 if (j == 0 && !rf_has(race->flags, RF_UNIQUE)) continue;
1349 if (j > 0 && !wcschr(pat, race->d_char)) continue;
1350
1351 monsters[m_count] = m_count;
1352 default_join[m_count].oid = i;
1353 default_join[m_count++].gid = j;
1354 }
1355 }
1356
1357 display_knowledge("monsters", monsters, m_count, r_funcs, m_funcs,
1358 " Sym Kills");
1359 mem_free(default_join);
1360 mem_free(monsters);
1361 }
1362
1363 /**
1364 * ------------------------------------------------------------------------
1365 * ARTIFACTS
1366 * ------------------------------------------------------------------------ */
1367
1368 /**
1369 * These are used for all the object sections
1370 */
1371 static const grouper object_text_order[] =
1372 {
1373 {TV_RING, "Ring" },
1374 {TV_AMULET, "Amulet" },
1375 {TV_POTION, "Potion" },
1376 {TV_SCROLL, "Scroll" },
1377 {TV_WAND, "Wand" },
1378 {TV_STAFF, "Staff" },
1379 {TV_ROD, "Rod" },
1380 {TV_FOOD, "Food" },
1381 {TV_MUSHROOM, "Mushroom" },
1382 {TV_PRAYER_BOOK, "Priest Book" },
1383 {TV_MAGIC_BOOK, "Magic Book" },
1384 {TV_NATURE_BOOK, "Nature Book" },
1385 {TV_SHADOW_BOOK, "Shadow Book" },
1386 {TV_OTHER_BOOK, "Mystery Book" },
1387 {TV_LIGHT, "Light" },
1388 {TV_FLASK, "Flask" },
1389 {TV_SWORD, "Sword" },
1390 {TV_POLEARM, "Polearm" },
1391 {TV_HAFTED, "Hafted Weapon" },
1392 {TV_BOW, "Bow" },
1393 {TV_ARROW, "Ammunition" },
1394 {TV_BOLT, NULL },
1395 {TV_SHOT, NULL },
1396 {TV_SHIELD, "Shield" },
1397 {TV_CROWN, "Crown" },
1398 {TV_HELM, "Helm" },
1399 {TV_GLOVES, "Gloves" },
1400 {TV_BOOTS, "Boots" },
1401 {TV_CLOAK, "Cloak" },
1402 {TV_DRAG_ARMOR, "Dragon Scale Mail" },
1403 {TV_HARD_ARMOR, "Hard Armor" },
1404 {TV_SOFT_ARMOR, "Soft Armor" },
1405 {TV_DIGGING, "Digger" },
1406 {TV_GOLD, "Money" },
1407 {0, NULL }
1408 };
1409
1410 static int *obj_group_order = NULL;
1411
get_artifact_display_name(char * o_name,size_t namelen,int a_idx)1412 static void get_artifact_display_name(char *o_name, size_t namelen, int a_idx)
1413 {
1414 struct object body = OBJECT_NULL, known_body = OBJECT_NULL;
1415 struct object *obj = &body, *known_obj = &known_body;
1416
1417 make_fake_artifact(obj, &a_info[a_idx]);
1418 object_wipe(known_obj);
1419 object_copy(known_obj, obj);
1420 obj->known = known_obj;
1421 object_desc(o_name, namelen, obj, ODESC_PREFIX | ODESC_BASE | ODESC_SPOIL);
1422 object_wipe(known_obj);
1423 object_wipe(obj);
1424 }
1425
1426 /**
1427 * Display an artifact label
1428 */
display_artifact(int col,int row,bool cursor,int oid)1429 static void display_artifact(int col, int row, bool cursor, int oid)
1430 {
1431 byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1432 char o_name[80];
1433
1434 get_artifact_display_name(o_name, sizeof o_name, oid);
1435
1436 c_prt(attr, o_name, row, col);
1437 }
1438
1439 /**
1440 * Look for an artifact
1441 */
find_artifact(struct artifact * artifact)1442 static struct object *find_artifact(struct artifact *artifact)
1443 {
1444 int y, x, i;
1445 struct object *obj;
1446
1447 /* Ground objects */
1448 for (y = 1; y < cave->height; y++) {
1449 for (x = 1; x < cave->width; x++) {
1450 struct loc grid = loc(x, y);
1451 for (obj = square_object(cave, grid); obj; obj = obj->next) {
1452 if (obj->artifact == artifact) return obj;
1453 }
1454 }
1455 }
1456
1457 /* Player objects */
1458 for (obj = player->gear; obj; obj = obj->next) {
1459 if (obj->artifact == artifact) return obj;
1460 }
1461
1462 /* Monster objects */
1463 for (i = cave_monster_max(cave) - 1; i >= 1; i--) {
1464 struct monster *mon = cave_monster(cave, i);
1465 obj = mon ? mon->held_obj : NULL;
1466
1467 while (obj) {
1468 if (obj->artifact == artifact) return obj;
1469 obj = obj->next;
1470 }
1471 }
1472
1473 /* Store objects */
1474 for (i = 0; i < MAX_STORES; i++) {
1475 struct store *s = &stores[i];
1476 for (obj = s->stock; obj; obj = obj->next) {
1477 if (obj->artifact == artifact) return obj;
1478 }
1479 }
1480
1481 /* Stored chunk objects */
1482 for (i = 0; i < chunk_list_max; i++) {
1483 struct chunk *c = chunk_list[i];
1484 int j;
1485 if (strstr(c->name, "known")) continue;
1486
1487 /* Ground objects */
1488 for (y = 1; y < c->height; y++) {
1489 for (x = 1; x < c->width; x++) {
1490 struct loc grid = loc(x, y);
1491 for (obj = square_object(c, grid); obj; obj = obj->next) {
1492 if (obj->artifact == artifact) return obj;
1493 }
1494 }
1495 }
1496
1497 /* Monster objects */
1498 for (j = cave_monster_max(c) - 1; j >= 1; j--) {
1499 struct monster *mon = cave_monster(c, j);
1500 obj = mon ? mon->held_obj : NULL;
1501
1502 while (obj) {
1503 if (obj->artifact == artifact) return obj;
1504 obj = obj->next;
1505 }
1506 }
1507 }
1508
1509 return NULL;
1510 }
1511
1512 /**
1513 * Show artifact lore
1514 */
desc_art_fake(int a_idx)1515 static void desc_art_fake(int a_idx)
1516 {
1517 struct object *obj, *known_obj = NULL;
1518 struct object object_body = OBJECT_NULL, known_object_body = OBJECT_NULL;
1519 bool fake = false;
1520
1521 char header[120];
1522
1523 textblock *tb;
1524 region area = { 0, 0, 0, 0 };
1525
1526 obj = find_artifact(&a_info[a_idx]);
1527
1528 /* If it's been lost, make a fake artifact for it */
1529 if (!obj) {
1530 fake = true;
1531 obj = &object_body;
1532 known_obj = &known_object_body;
1533
1534 make_fake_artifact(obj, &a_info[a_idx]);
1535 obj->known = known_obj;
1536 known_obj->artifact = obj->artifact;
1537 known_obj->kind = obj->kind;
1538
1539 /* Check the history entry, to see if it was fully known before it
1540 * was lost */
1541 if (history_is_artifact_known(player, obj->artifact))
1542 /* Be very careful not to influence anything but this object */
1543 object_copy(known_obj, obj);
1544 }
1545
1546 /* Hack -- Handle stuff */
1547 handle_stuff(player);
1548
1549 tb = object_info(obj, OINFO_NONE);
1550 object_desc(header, sizeof(header), obj,
1551 ODESC_PREFIX | ODESC_FULL | ODESC_CAPITAL);
1552 if (fake) {
1553 object_wipe(known_obj);
1554 object_wipe(obj);
1555 }
1556
1557 textui_textblock_show(tb, area, header);
1558 textblock_free(tb);
1559 }
1560
a_cmp_tval(const void * a,const void * b)1561 static int a_cmp_tval(const void *a, const void *b)
1562 {
1563 const int a_val = *(const int *)a;
1564 const int b_val = *(const int *)b;
1565 const struct artifact *a_a = &a_info[a_val];
1566 const struct artifact *a_b = &a_info[b_val];
1567
1568 /* Group by */
1569 int ta = obj_group_order[a_a->tval];
1570 int tb = obj_group_order[a_b->tval];
1571 int c = ta - tb;
1572 if (c) return c;
1573
1574 /* Order by */
1575 c = a_a->sval - a_b->sval;
1576 if (c) return c;
1577 return strcmp(a_a->name, a_b->name);
1578 }
1579
kind_name(int gid)1580 static const char *kind_name(int gid)
1581 {
1582 return object_text_order[gid].name;
1583 }
1584
art2gid(int oid)1585 static int art2gid(int oid)
1586 {
1587 return obj_group_order[a_info[oid].tval];
1588 }
1589
1590 /**
1591 * Check if the given artifact idx is something we should "Know" about
1592 */
artifact_is_known(int a_idx)1593 static bool artifact_is_known(int a_idx)
1594 {
1595 struct object *obj;
1596
1597 if (!a_info[a_idx].name)
1598 return false;
1599
1600 if (player->wizard)
1601 return true;
1602
1603 if (!a_info[a_idx].created)
1604 return false;
1605
1606 /* Check all objects to see if it exists but hasn't been IDed */
1607 obj = find_artifact(&a_info[a_idx]);
1608 if (obj && !object_is_known_artifact(obj))
1609 return false;
1610
1611 return true;
1612 }
1613
1614
1615 /**
1616 * If 'artifacts' is NULL, it counts the number of known artifacts, otherwise
1617 * it collects the list of known artifacts into 'artifacts' as well.
1618 */
collect_known_artifacts(int * artifacts,size_t artifacts_len)1619 static int collect_known_artifacts(int *artifacts, size_t artifacts_len)
1620 {
1621 int a_count = 0;
1622 int j;
1623
1624 if (artifacts)
1625 assert(artifacts_len >= z_info->a_max);
1626
1627 for (j = 0; j < z_info->a_max; j++) {
1628 /* Artifact doesn't exist */
1629 if (!a_info[j].name) continue;
1630
1631 if (OPT(player, cheat_xtra) || artifact_is_known(j)) {
1632 if (artifacts)
1633 artifacts[a_count++] = j;
1634 else
1635 a_count++;
1636 }
1637 }
1638
1639 return a_count;
1640 }
1641
1642 /**
1643 * Display known artifacts
1644 */
do_cmd_knowledge_artifacts(const char * name,int row)1645 static void do_cmd_knowledge_artifacts(const char *name, int row)
1646 {
1647 /* HACK -- should be TV_MAX */
1648 group_funcs obj_f = {kind_name, a_cmp_tval, art2gid, 0, TV_MAX, false};
1649 member_funcs art_f = {display_artifact, desc_art_fake, 0, 0, recall_prompt,
1650 0, 0};
1651
1652 int *artifacts;
1653 int a_count = 0;
1654
1655 artifacts = mem_zalloc(z_info->a_max * sizeof(int));
1656
1657 /* Collect valid artifacts */
1658 a_count = collect_known_artifacts(artifacts, z_info->a_max);
1659
1660 display_knowledge("artifacts", artifacts, a_count, obj_f, art_f, NULL);
1661 mem_free(artifacts);
1662 }
1663
1664 /**
1665 * ------------------------------------------------------------------------
1666 * EGO ITEMS
1667 * ------------------------------------------------------------------------ */
1668
ego_grp_name(int gid)1669 static const char *ego_grp_name(int gid)
1670 {
1671 return object_text_order[gid].name;
1672 }
1673
display_ego_item(int col,int row,bool cursor,int oid)1674 static void display_ego_item(int col, int row, bool cursor, int oid)
1675 {
1676 /* Access the object */
1677 struct ego_item *ego = &e_info[default_item_id(oid)];
1678
1679 /* Choose a color */
1680 byte attr = curs_attrs[0 != (int)ego->everseen][0 != (int)cursor];
1681
1682 /* Display the name */
1683 c_prt(attr, ego->name, row, col);
1684 }
1685
1686 /**
1687 * Describe fake ego item "lore"
1688 */
desc_ego_fake(int oid)1689 static void desc_ego_fake(int oid)
1690 {
1691 int e_idx = default_item_id(oid);
1692 struct ego_item *ego = &e_info[e_idx];
1693
1694 textblock *tb;
1695 region area = { 0, 0, 0, 0 };
1696
1697 /* List ego flags */
1698 tb = object_info_ego(ego);
1699
1700 textui_textblock_show(tb, area, format("%s %s",
1701 ego_grp_name(default_group_id(oid)),
1702 ego->name));
1703 textblock_free(tb);
1704 }
1705
1706 /* TODO? Currently ego items will order by e_idx */
e_cmp_tval(const void * a,const void * b)1707 static int e_cmp_tval(const void *a, const void *b)
1708 {
1709 const int a_val = *(const int *)a;
1710 const int b_val = *(const int *)b;
1711 const struct ego_item *ea = &e_info[default_item_id(a_val)];
1712 const struct ego_item *eb = &e_info[default_item_id(b_val)];
1713
1714 /* Group by */
1715 int c = default_group_id(a_val) - default_group_id(b_val);
1716 if (c) return c;
1717
1718 /* Order by */
1719 return strcmp(ea->name, eb->name);
1720 }
1721
1722 /**
1723 * Display known ego_items
1724 */
do_cmd_knowledge_ego_items(const char * name,int row)1725 static void do_cmd_knowledge_ego_items(const char *name, int row)
1726 {
1727 group_funcs obj_f =
1728 {ego_grp_name, e_cmp_tval, default_group_id, 0, TV_MAX, false};
1729
1730 member_funcs ego_f =
1731 {display_ego_item, desc_ego_fake, 0, 0, recall_prompt, 0, 0};
1732
1733 int *egoitems;
1734 int e_count = 0;
1735 int i;
1736
1737 /* Overkill - NRM */
1738 int max_pairs = z_info->e_max * N_ELEMENTS(object_text_order);
1739 egoitems = mem_zalloc(max_pairs * sizeof(int));
1740 default_join = mem_zalloc(max_pairs * sizeof(join_t));
1741
1742 /* Look at all the ego items */
1743 for (i = 0; i < z_info->e_max; i++) {
1744 struct ego_item *ego = &e_info[i];
1745 if (ego->everseen || OPT(player, cheat_xtra)) {
1746 size_t j;
1747 int *tval = mem_zalloc(N_ELEMENTS(object_text_order) * sizeof(int));
1748 struct poss_item *poss;
1749
1750 /* Note the tvals which are possible for this ego */
1751 for (poss = ego->poss_items; poss; poss = poss->next) {
1752 struct object_kind *kind = &k_info[poss->kidx];
1753 tval[obj_group_order[kind->tval]]++;
1754 }
1755
1756 /* Count and put into the list */
1757 for (j = 0; j < TV_MAX; j++) {
1758 int gid = obj_group_order[j];
1759
1760 /* Ignore duplicates */
1761 if ((j > 0) && (gid == default_join[e_count - 1].gid)
1762 && (i == default_join[e_count - 1].oid))
1763 continue;
1764
1765 if (tval[obj_group_order[j]]) {
1766 egoitems[e_count] = e_count;
1767 default_join[e_count].oid = i;
1768 default_join[e_count++].gid = gid;
1769 }
1770 }
1771 mem_free(tval);
1772 }
1773 }
1774
1775 display_knowledge("ego items", egoitems, e_count, obj_f, ego_f, NULL);
1776
1777 mem_free(default_join);
1778 mem_free(egoitems);
1779 }
1780
1781 /**
1782 * ------------------------------------------------------------------------
1783 * ORDINARY OBJECTS
1784 * ------------------------------------------------------------------------ */
1785
1786 /**
1787 * Display the objects in a group.
1788 */
display_object(int col,int row,bool cursor,int oid)1789 static void display_object(int col, int row, bool cursor, int oid)
1790 {
1791 struct object_kind *kind = &k_info[oid];
1792 const char *inscrip = get_autoinscription(kind, kind->aware);
1793
1794 char o_name[80];
1795
1796 /* Choose a color */
1797 bool aware = (!kind->flavor || kind->aware);
1798 byte attr = curs_attrs[(int)aware][(int)cursor];
1799
1800 /* Graphics versions of the object_char and object_attr defines */
1801 byte a = object_kind_attr(kind);
1802 wchar_t c = object_kind_char(kind);
1803
1804 /* Don't display special artifacts */
1805 if (!kf_has(kind->kind_flags, KF_INSTA_ART))
1806 object_kind_name(o_name, sizeof(o_name), kind, OPT(player, cheat_xtra));
1807
1808 /* If the type is "tried", display that */
1809 if (kind->tried && !aware)
1810 my_strcat(o_name, " {tried}", sizeof(o_name));
1811
1812 /* Display the name */
1813 c_prt(attr, o_name, row, col);
1814
1815 /* Show ignore status */
1816 if ((aware && kind_is_ignored_aware(kind)) ||
1817 (!aware && kind_is_ignored_unaware(kind)))
1818 c_put_str(attr, "Yes", row, 46);
1819
1820
1821 /* Show autoinscription if around */
1822 if (inscrip)
1823 c_put_str(COLOUR_YELLOW, inscrip, row, 55);
1824
1825 if (tile_height == 1) {
1826 big_pad(76, row, a, c);
1827 }
1828 }
1829
1830 /**
1831 * Describe fake object
1832 */
desc_obj_fake(int k_idx)1833 static void desc_obj_fake(int k_idx)
1834 {
1835 struct object_kind *kind = &k_info[k_idx];
1836 struct object_kind *old_kind = player->upkeep->object_kind;
1837 struct object *old_obj = player->upkeep->object;
1838 struct object *obj = object_new(), *known_obj = object_new();
1839
1840 char header[120];
1841
1842 textblock *tb;
1843 region area = { 0, 0, 0, 0 };
1844
1845 /* Update the object recall window */
1846 track_object_kind(player->upkeep, kind);
1847 handle_stuff(player);
1848
1849 /* Create the artifact */
1850 object_prep(obj, kind, 0, EXTREMIFY);
1851
1852 /* It's fully known */
1853 if (kind->aware || !kind->flavor)
1854 object_copy(known_obj, obj);
1855 obj->known = known_obj;
1856
1857 /* Hack -- Handle stuff */
1858 handle_stuff(player);
1859
1860 tb = object_info(obj, OINFO_FAKE);
1861 object_desc(header, sizeof(header), obj,
1862 ODESC_PREFIX | ODESC_CAPITAL);
1863
1864 textui_textblock_show(tb, area, header);
1865 object_delete(&known_obj);
1866 object_delete(&obj);
1867 textblock_free(tb);
1868
1869 /* Restore the old trackee */
1870 if (old_kind)
1871 track_object_kind(player->upkeep, old_kind);
1872 else if (old_obj)
1873 track_object(player->upkeep, old_obj);
1874 else
1875 track_object_cancel(player->upkeep);
1876 }
1877
o_cmp_tval(const void * a,const void * b)1878 static int o_cmp_tval(const void *a, const void *b)
1879 {
1880 const int a_val = *(const int *)a;
1881 const int b_val = *(const int *)b;
1882 const struct object_kind *k_a = &k_info[a_val];
1883 const struct object_kind *k_b = &k_info[b_val];
1884
1885 /* Group by */
1886 int ta = obj_group_order[k_a->tval];
1887 int tb = obj_group_order[k_b->tval];
1888 int c = ta - tb;
1889 if (c) return c;
1890
1891 /* Order by */
1892 c = k_a->aware - k_b->aware;
1893 if (c) return -c; /* aware has low sort weight */
1894
1895 switch (k_a->tval)
1896 {
1897 case TV_LIGHT:
1898 case TV_MAGIC_BOOK:
1899 case TV_PRAYER_BOOK:
1900 case TV_NATURE_BOOK:
1901 case TV_SHADOW_BOOK:
1902 case TV_OTHER_BOOK:
1903 case TV_DRAG_ARMOR:
1904 /* leave sorted by sval */
1905 break;
1906
1907 default:
1908 if (k_a->aware)
1909 return strcmp(k_a->name, k_b->name);
1910
1911 /* Then in tried order */
1912 c = k_a->tried - k_b->tried;
1913 if (c) return -c;
1914
1915 return strcmp(k_a->flavor->text, k_b->flavor->text);
1916 }
1917
1918 return k_a->sval - k_b->sval;
1919 }
1920
obj2gid(int oid)1921 static int obj2gid(int oid)
1922 {
1923 return obj_group_order[k_info[oid].tval];
1924 }
1925
o_xchar(int oid)1926 static wchar_t *o_xchar(int oid)
1927 {
1928 struct object_kind *kind = objkind_byid(oid);
1929 if (!kind) return 0;
1930
1931 if (!kind->flavor || kind->aware)
1932 return &kind_x_char[kind->kidx];
1933 else
1934 return &flavor_x_char[kind->flavor->fidx];
1935 }
1936
o_xattr(int oid)1937 static byte *o_xattr(int oid)
1938 {
1939 struct object_kind *kind = objkind_byid(oid);
1940 if (!kind) return NULL;
1941
1942 if (!kind->flavor || kind->aware)
1943 return &kind_x_attr[kind->kidx];
1944 else
1945 return &flavor_x_attr[kind->flavor->fidx];
1946 }
1947
1948 /**
1949 * Display special prompt for object inscription.
1950 */
o_xtra_prompt(int oid)1951 static const char *o_xtra_prompt(int oid)
1952 {
1953 struct object_kind *kind = objkind_byid(oid);
1954
1955 const char *no_insc = ", 's' to toggle ignore, 'r'ecall, '{'";
1956 const char *with_insc = ", 's' to toggle ignore, 'r'ecall, '{', '}'";
1957
1958 if (!kind) return NULL;
1959
1960 /* Appropriate prompt */
1961 if (kind->aware)
1962 return kind->note_aware ? with_insc : no_insc;
1963 else
1964 return kind->note_unaware ? with_insc : no_insc;
1965 }
1966
1967 /**
1968 * Special key actions for object inscription.
1969 */
o_xtra_act(struct keypress ch,int oid)1970 static void o_xtra_act(struct keypress ch, int oid)
1971 {
1972 struct object_kind *k = objkind_byid(oid);
1973 if (!k) return;
1974
1975 /* Toggle ignore */
1976 if (ignore_tval(k->tval) && (ch.code == 's' || ch.code == 'S')) {
1977 if (k->aware) {
1978 if (kind_is_ignored_aware(k))
1979 kind_ignore_clear(k);
1980 else
1981 kind_ignore_when_aware(k);
1982 } else {
1983 if (kind_is_ignored_unaware(k))
1984 kind_ignore_clear(k);
1985 else
1986 kind_ignore_when_unaware(k);
1987 }
1988
1989 return;
1990 }
1991
1992 /* Uninscribe */
1993 if (ch.code == '}') {
1994 remove_autoinscription(oid);
1995 } else if (ch.code == '{') {
1996 /* Inscribe */
1997 char text[80] = "";
1998
1999 /* Avoid the prompt getting in the way */
2000 screen_save();
2001
2002 /* Prompt */
2003 prt("Inscribe with: ", 0, 0);
2004
2005 /* Default note */
2006 if (k->note_aware || k->note_unaware)
2007 strnfmt(text, sizeof(text), "%s", get_autoinscription(k, k->aware));
2008
2009 /* Get an inscription */
2010 if (askfor_aux(text, sizeof(text), NULL)) {
2011 /* Remove old inscription if existent */
2012 if (k->note_aware || k->note_unaware)
2013 remove_autoinscription(oid);
2014
2015 /* Add the autoinscription */
2016 add_autoinscription(oid, text, k->aware);
2017 cmdq_push(CMD_AUTOINSCRIBE);
2018
2019 /* Redraw gear */
2020 player->upkeep->redraw |= (PR_INVEN | PR_EQUIP);
2021 }
2022
2023 /* Reload the screen */
2024 screen_load();
2025 }
2026 }
2027
2028
2029
2030 /**
2031 * Display known objects
2032 */
textui_browse_object_knowledge(const char * name,int row)2033 void textui_browse_object_knowledge(const char *name, int row)
2034 {
2035 group_funcs kind_f = {kind_name, o_cmp_tval, obj2gid, 0, TV_MAX, false};
2036 member_funcs obj_f = {display_object, desc_obj_fake, o_xchar, o_xattr,
2037 o_xtra_prompt, o_xtra_act, 0};
2038
2039 int *objects;
2040 int o_count = 0;
2041 int i;
2042 struct object_kind *kind;
2043
2044 objects = mem_zalloc(z_info->k_max * sizeof(int));
2045
2046 for (i = 0; i < z_info->k_max; i++) {
2047 kind = &k_info[i];
2048 /* It's in the list if we've ever seen it, or it has a flavour,
2049 * and it's not one of the special artifacts. This way the flavour
2050 * appears in the list until it is found. */
2051 if ((kind->everseen || kind->flavor || OPT(player, cheat_xtra)) &&
2052 (!kf_has(kind->kind_flags, KF_INSTA_ART))) {
2053 int c = obj_group_order[k_info[i].tval];
2054 if (c >= 0) objects[o_count++] = i;
2055 }
2056 }
2057
2058 display_knowledge("known objects", objects, o_count, kind_f, obj_f,
2059 "Ignore Inscribed Sym");
2060
2061 mem_free(objects);
2062 }
2063
2064 /**
2065 * ------------------------------------------------------------------------
2066 * OBJECT RUNES
2067 * ------------------------------------------------------------------------ */
2068
2069 /**
2070 * Description of each rune group.
2071 */
2072 static const char *rune_group_text[] =
2073 {
2074 "Combat",
2075 "Modifiers",
2076 "Resists",
2077 "Brands",
2078 "Slays",
2079 "Curses",
2080 "Other",
2081 NULL
2082 };
2083
2084 /**
2085 * Display the runes in a group.
2086 */
display_rune(int col,int row,bool cursor,int oid)2087 static void display_rune(int col, int row, bool cursor, int oid )
2088 {
2089 byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
2090 const char *inscrip = quark_str(rune_note(oid));
2091
2092 c_prt(attr, rune_name(oid), row, col);
2093
2094 /* Show autoinscription if around */
2095 if (inscrip)
2096 c_put_str(COLOUR_YELLOW, inscrip, row, 47);
2097 }
2098
2099
rune_var_name(int gid)2100 static const char *rune_var_name(int gid)
2101 {
2102 return rune_group_text[gid];
2103 }
2104
rune_var(int oid)2105 static int rune_var(int oid)
2106 {
2107 return (int) rune_variety(oid);
2108 }
2109
rune_lore(int oid)2110 static void rune_lore(int oid)
2111 {
2112 textblock *tb = textblock_new();
2113 char *title = string_make(rune_name(oid));
2114
2115 my_strcap(title);
2116 textblock_append_c(tb, COLOUR_L_BLUE, "%s", title);
2117 textblock_append(tb, "\n");
2118 textblock_append(tb, "%s", rune_desc(oid));
2119 textblock_append(tb, "\n");
2120 textui_textblock_show(tb, SCREEN_REGION, NULL);
2121 textblock_free(tb);
2122
2123 string_free(title);
2124 }
2125
2126 /**
2127 * Display special prompt for rune inscription.
2128 */
rune_xtra_prompt(int oid)2129 static const char *rune_xtra_prompt(int oid)
2130 {
2131 const char *no_insc = ", 'r'ecall, '{'";
2132 const char *with_insc = ", 'r'ecall, '{', '}'";
2133
2134 /* Appropriate prompt */
2135 return rune_note(oid) ? with_insc : no_insc;
2136 }
2137
2138 /**
2139 * Special key actions for rune inscription.
2140 */
rune_xtra_act(struct keypress ch,int oid)2141 static void rune_xtra_act(struct keypress ch, int oid)
2142 {
2143 /* Uninscribe */
2144 if (ch.code == '}') {
2145 rune_set_note(oid, NULL);
2146 } else if (ch.code == '{') {
2147 /* Inscribe */
2148 char note_text[80] = "";
2149
2150 /* Avoid the prompt getting in the way */
2151 screen_save();
2152
2153 /* Prompt */
2154 prt("Inscribe with: ", 0, 0);
2155
2156 /* Default note */
2157 if (rune_note(oid))
2158 strnfmt(note_text, sizeof(note_text), "%s",
2159 quark_str(rune_note(oid)));
2160
2161 /* Get an inscription */
2162 if (askfor_aux(note_text, sizeof(note_text), NULL)) {
2163 /* Remove old inscription if existent */
2164 if (rune_note(oid))
2165 rune_set_note(oid, NULL);
2166
2167 /* Add the autoinscription */
2168 rune_set_note(oid, note_text);
2169 rune_autoinscribe(oid);
2170
2171 /* Redraw gear */
2172 player->upkeep->redraw |= (PR_INVEN | PR_EQUIP);
2173 }
2174
2175 /* Reload the screen */
2176 screen_load();
2177 }
2178 }
2179
2180
2181
2182 /**
2183 * Display rune knowledge.
2184 */
do_cmd_knowledge_runes(const char * name,int row)2185 static void do_cmd_knowledge_runes(const char *name, int row)
2186 {
2187 group_funcs rune_var_f = {rune_var_name, NULL, rune_var, 0,
2188 N_ELEMENTS(rune_group_text), false};
2189
2190 member_funcs rune_f = {display_rune, rune_lore, NULL, NULL,
2191 rune_xtra_prompt, rune_xtra_act, 0};
2192
2193 int *runes;
2194 int rune_max = max_runes();
2195 int count = 0;
2196 int i;
2197 char buf[30];
2198
2199 runes = mem_zalloc(rune_max * sizeof(int));
2200
2201 for (i = 0; i < rune_max; i++) {
2202 /* Ignore unknown runes */
2203 if (!player_knows_rune(player, i))
2204 continue;
2205
2206 runes[count++] = i;
2207 }
2208
2209 my_strcpy(buf, format("runes (%d unknown)", rune_max - count), sizeof(buf));
2210
2211 display_knowledge(buf, runes, count, rune_var_f, rune_f, "Inscribed");
2212 mem_free(runes);
2213 }
2214
2215 /**
2216 * ------------------------------------------------------------------------
2217 * TERRAIN FEATURES
2218 * ------------------------------------------------------------------------ */
2219
2220 /**
2221 * Description of each feature group.
2222 */
2223 static const char *feature_group_text[] =
2224 {
2225 "Floors",
2226 "Doors",
2227 "Stairs",
2228 "Walls",
2229 "Streamers",
2230 "Obstructions",
2231 "Stores",
2232 "Other",
2233 NULL
2234 };
2235
2236
2237 /**
2238 * Display the features in a group.
2239 */
display_feature(int col,int row,bool cursor,int oid)2240 static void display_feature(int col, int row, bool cursor, int oid )
2241 {
2242 struct feature *feat = &f_info[oid];
2243 byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
2244
2245 c_prt(attr, feat->name, row, col);
2246
2247 if (tile_height == 1) {
2248 /* Display symbols */
2249 col = 65;
2250 col += big_pad(col, row, feat_x_attr[LIGHTING_DARK][feat->fidx],
2251 feat_x_char[LIGHTING_DARK][feat->fidx]);
2252 col += big_pad(col, row, feat_x_attr[LIGHTING_LIT][feat->fidx],
2253 feat_x_char[LIGHTING_LIT][feat->fidx]);
2254 col += big_pad(col, row, feat_x_attr[LIGHTING_TORCH][feat->fidx],
2255 feat_x_char[LIGHTING_TORCH][feat->fidx]);
2256 (void) big_pad(col, row, feat_x_attr[LIGHTING_LOS][feat->fidx],
2257 feat_x_char[LIGHTING_LOS][feat->fidx]);
2258 }
2259 }
2260
2261
f_cmp_fkind(const void * a,const void * b)2262 static int f_cmp_fkind(const void *a, const void *b)
2263 {
2264 const int a_val = *(const int *)a;
2265 const int b_val = *(const int *)b;
2266 const struct feature *fa = &f_info[a_val];
2267 const struct feature *fb = &f_info[b_val];
2268
2269 /* Group by */
2270 int c = feat_order(a_val) - feat_order(b_val);
2271 if (c) return c;
2272
2273 /* Order by feature name */
2274 return strcmp(fa->name, fb->name);
2275 }
2276
fkind_name(int gid)2277 static const char *fkind_name(int gid)
2278 {
2279 return feature_group_text[gid];
2280 }
2281
2282
2283 /**
2284 * Disgusting hack to allow 4 in 1 editing of terrain visuals
2285 */
2286 static enum grid_light_level f_uik_lighting = LIGHTING_LIT;
2287
2288 /* XXX needs *better* retooling for multi-light terrain */
f_xattr(int oid)2289 static byte *f_xattr(int oid)
2290 {
2291 return &feat_x_attr[f_uik_lighting][oid];
2292 }
f_xchar(int oid)2293 static wchar_t *f_xchar(int oid)
2294 {
2295 return &feat_x_char[f_uik_lighting][oid];
2296 }
feat_lore(int oid)2297 static void feat_lore(int oid)
2298 {
2299 struct feature *feat = &f_info[oid];
2300 textblock *tb = textblock_new();
2301 char *title = string_make(feat->name);
2302
2303 if (feat->desc) {
2304 my_strcap(title);
2305 textblock_append_c(tb, COLOUR_L_BLUE, "%s", title);
2306 textblock_append(tb, "\n");
2307 textblock_append(tb, "%s", feat->desc);
2308 textblock_append(tb, "\n");
2309 textui_textblock_show(tb, SCREEN_REGION, NULL);
2310 textblock_free(tb);
2311 }
2312
2313 string_free(title);
2314 }
feat_prompt(int oid)2315 static const char *feat_prompt(int oid)
2316 {
2317 (void)oid;
2318 switch (f_uik_lighting) {
2319 case LIGHTING_LIT: return ", 'l/L' for lighting (lit)";
2320 case LIGHTING_TORCH: return ", 'l/L' for lighting (torch)";
2321 case LIGHTING_LOS: return ", 'l/L' for lighting (LOS)";
2322 default: return ", 'l/L' for lighting (dark)";
2323 }
2324 }
2325
2326 /**
2327 * Special key actions for cycling lighting
2328 */
f_xtra_act(struct keypress ch,int oid)2329 static void f_xtra_act(struct keypress ch, int oid)
2330 {
2331 /* XXX must be a better way to cycle this */
2332 if (ch.code == 'l') {
2333 switch (f_uik_lighting) {
2334 case LIGHTING_LIT: f_uik_lighting = LIGHTING_TORCH; break;
2335 case LIGHTING_TORCH: f_uik_lighting = LIGHTING_LOS; break;
2336 case LIGHTING_LOS: f_uik_lighting = LIGHTING_DARK; break;
2337 default: f_uik_lighting = LIGHTING_LIT; break;
2338 }
2339 } else if (ch.code == 'L') {
2340 switch (f_uik_lighting) {
2341 case LIGHTING_DARK: f_uik_lighting = LIGHTING_LOS; break;
2342 case LIGHTING_LOS: f_uik_lighting = LIGHTING_TORCH; break;
2343 case LIGHTING_LIT: f_uik_lighting = LIGHTING_DARK; break;
2344 default: f_uik_lighting = LIGHTING_LIT; break;
2345 }
2346 }
2347
2348 }
2349
2350
2351 /**
2352 * Interact with feature visuals.
2353 */
do_cmd_knowledge_features(const char * name,int row)2354 static void do_cmd_knowledge_features(const char *name, int row)
2355 {
2356 group_funcs fkind_f = {fkind_name, f_cmp_fkind, feat_order, 0,
2357 N_ELEMENTS(feature_group_text), false};
2358
2359 member_funcs feat_f = {display_feature, feat_lore, f_xchar, f_xattr,
2360 feat_prompt, f_xtra_act, 0};
2361
2362 int *features;
2363 int f_count = 0;
2364 int i;
2365
2366 features = mem_zalloc(z_info->f_max * sizeof(int));
2367
2368 for (i = 0; i < z_info->f_max; i++) {
2369 /* Ignore non-features and mimics */
2370 if (f_info[i].name == 0 || f_info[i].mimic)
2371 continue;
2372
2373 /* Currently no filter for features */
2374 features[f_count++] = i;
2375 }
2376
2377 display_knowledge("features", features, f_count, fkind_f, feat_f,
2378 " Sym");
2379 mem_free(features);
2380 }
2381
2382 /**
2383 * ------------------------------------------------------------------------
2384 * TRAPS
2385 * ------------------------------------------------------------------------ */
2386
2387 /**
2388 * Description of each feature group.
2389 */
2390 static const char *trap_group_text[] =
2391 {
2392 "Runes",
2393 "Locks",
2394 "Traps",
2395 "Other",
2396 NULL
2397 };
2398
2399
2400 /**
2401 * Display the features in a group.
2402 */
display_trap(int col,int row,bool cursor,int oid)2403 static void display_trap(int col, int row, bool cursor, int oid )
2404 {
2405 struct trap_kind *trap = &trap_info[oid];
2406 byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
2407
2408 c_prt(attr, trap->desc, row, col);
2409
2410 if (tile_height == 1) {
2411 /* Display symbols */
2412 col = 65;
2413 col += big_pad(col, row, trap_x_attr[LIGHTING_DARK][trap->tidx],
2414 trap_x_char[LIGHTING_DARK][trap->tidx]);
2415 col += big_pad(col, row, trap_x_attr[LIGHTING_LIT][trap->tidx],
2416 trap_x_char[LIGHTING_LIT][trap->tidx]);
2417 col += big_pad(col, row, trap_x_attr[LIGHTING_TORCH][trap->tidx],
2418 trap_x_char[LIGHTING_TORCH][trap->tidx]);
2419 (void) big_pad(col, row, trap_x_attr[LIGHTING_LOS][trap->tidx],
2420 trap_x_char[LIGHTING_LOS][trap->tidx]);
2421 }
2422 }
2423
trap_order(int trap)2424 static int trap_order(int trap)
2425 {
2426 const struct trap_kind *t = &trap_info[trap];
2427
2428 if (trf_has(t->flags, TRF_GLYPH))
2429 return 0;
2430 else if (trf_has(t->flags, TRF_LOCK))
2431 return 1;
2432 else if (trf_has(t->flags, TRF_TRAP))
2433 return 2;
2434 else
2435 return 3;
2436 }
2437
t_cmp_tkind(const void * a,const void * b)2438 static int t_cmp_tkind(const void *a, const void *b)
2439 {
2440 const int a_val = *(const int *)a;
2441 const int b_val = *(const int *)b;
2442 const struct trap_kind *ta = &trap_info[a_val];
2443 const struct trap_kind *tb = &trap_info[b_val];
2444
2445 /* Group by */
2446 int c = trap_order(a_val) - trap_order(b_val);
2447 if (c) return c;
2448
2449 /* Order by name */
2450 if (ta->name) {
2451 if (tb->name)
2452 return strcmp(ta->name, tb->name);
2453 else
2454 return 1;
2455 } else if (tb->name) {
2456 return -1;
2457 }
2458
2459 return 0;
2460 }
2461
tkind_name(int gid)2462 static const char *tkind_name(int gid)
2463 {
2464 return trap_group_text[gid];
2465 }
2466
2467
2468 /**
2469 * Disgusting hack to allow 4 in 1 editing of trap visuals
2470 */
2471 static enum grid_light_level t_uik_lighting = LIGHTING_LIT;
2472
2473 /* XXX needs *better* retooling for multi-light terrain */
t_xattr(int oid)2474 static byte *t_xattr(int oid)
2475 {
2476 return &trap_x_attr[t_uik_lighting][oid];
2477 }
t_xchar(int oid)2478 static wchar_t *t_xchar(int oid)
2479 {
2480 return &trap_x_char[t_uik_lighting][oid];
2481 }
trap_lore(int oid)2482 static void trap_lore(int oid)
2483 {
2484 struct trap_kind *trap = &trap_info[oid];
2485 textblock *tb = textblock_new();
2486 char *title = string_make(trap->desc);
2487
2488 if (trap->text) {
2489 my_strcap(title);
2490 textblock_append_c(tb, COLOUR_L_BLUE, "%s", title);
2491 textblock_append(tb, "\n");
2492 textblock_append(tb, "%s", trap->text);
2493 textblock_append(tb, "\n");
2494 textui_textblock_show(tb, SCREEN_REGION, NULL);
2495 textblock_free(tb);
2496 }
2497
2498 string_free(title);
2499 }
2500
trap_prompt(int oid)2501 static const char *trap_prompt(int oid)
2502 {
2503 (void)oid;
2504 return ", 'l' to cycle lighting";
2505 }
2506
2507 /**
2508 * Special key actions for cycling lighting
2509 */
t_xtra_act(struct keypress ch,int oid)2510 static void t_xtra_act(struct keypress ch, int oid)
2511 {
2512 /* XXX must be a better way to cycle this */
2513 if (ch.code == 'l') {
2514 switch (t_uik_lighting) {
2515 case LIGHTING_LIT: t_uik_lighting = LIGHTING_TORCH; break;
2516 case LIGHTING_TORCH: t_uik_lighting = LIGHTING_LOS; break;
2517 case LIGHTING_LOS: t_uik_lighting = LIGHTING_DARK; break;
2518 default: t_uik_lighting = LIGHTING_LIT; break;
2519 }
2520 } else if (ch.code == 'L') {
2521 switch (t_uik_lighting) {
2522 case LIGHTING_DARK: t_uik_lighting = LIGHTING_LOS; break;
2523 case LIGHTING_LOS: t_uik_lighting = LIGHTING_TORCH; break;
2524 case LIGHTING_LIT: t_uik_lighting = LIGHTING_DARK; break;
2525 default: t_uik_lighting = LIGHTING_LIT; break;
2526 }
2527 }
2528
2529 }
2530
2531
2532 /**
2533 * Interact with trap visuals.
2534 */
do_cmd_knowledge_traps(const char * name,int row)2535 static void do_cmd_knowledge_traps(const char *name, int row)
2536 {
2537 group_funcs tkind_f = {tkind_name, t_cmp_tkind, trap_order, 0,
2538 N_ELEMENTS(trap_group_text), false};
2539
2540 member_funcs trap_f = {display_trap, trap_lore, t_xchar, t_xattr,
2541 trap_prompt, t_xtra_act, 0};
2542
2543 int *traps;
2544 int t_count = 0;
2545 int i;
2546
2547 traps = mem_zalloc(z_info->trap_max * sizeof(int));
2548
2549 for (i = 0; i < z_info->trap_max; i++) {
2550 if (!trap_info[i].name) continue;
2551
2552 traps[t_count++] = i;
2553 }
2554
2555 display_knowledge("traps", traps, t_count, tkind_f, trap_f,
2556 " Sym");
2557 mem_free(traps);
2558 }
2559
2560
2561 /**
2562 * ------------------------------------------------------------------------
2563 * SHAPECHANGE
2564 * ------------------------------------------------------------------------ */
2565
2566 /**
2567 * Counts the number of interesting shapechanges and returns it.
2568 */
count_interesting_shapes(void)2569 static int count_interesting_shapes(void)
2570 {
2571 int count = 0;
2572 struct player_shape *s;
2573
2574 for (s = shapes; s; s = s->next) {
2575 if (! streq(s->name, "normal")) {
2576 ++count;
2577 }
2578 }
2579
2580 return count;
2581 }
2582
2583
2584 /**
2585 * Is a comparison function for an array of struct player_shape* which is
2586 * compatible with sort() and puts the elements in ascending alphabetical
2587 * order by name.
2588 */
compare_shape_names(const void * left,const void * right)2589 static int compare_shape_names(const void *left, const void *right)
2590 {
2591 const struct player_shape * const *sleft = left;
2592 const struct player_shape * const *sright = right;
2593
2594 return my_stricmp((*sleft)->name, (*sright)->name);
2595 }
2596
2597
shape_lore_helper_append_to_list(const char * item,const char *** list,int * p_nmax,int * p_n)2598 static void shape_lore_helper_append_to_list(const char* item,
2599 const char ***list, int* p_nmax, int *p_n)
2600 {
2601 if (*p_n >= *p_nmax) {
2602 if (*p_nmax == 0) {
2603 *p_nmax = 4;
2604 } else {
2605 assert(*p_nmax > 0);
2606 *p_nmax *= 2;
2607 }
2608 *list = mem_realloc(*list, *p_nmax * sizeof(**list));
2609 }
2610 (*list)[*p_n] = string_make(item);
2611 ++*p_n;
2612 }
2613
2614
skill_index_to_name(int i)2615 static const char *skill_index_to_name(int i)
2616 {
2617 const char *name;
2618
2619 switch (i) {
2620 case SKILL_DISARM_PHYS:
2621 name = "physical disarming";
2622 break;
2623
2624 case SKILL_DISARM_MAGIC:
2625 name = "magical disarming";
2626 break;
2627
2628 case SKILL_DEVICE:
2629 name = "magic devices";
2630 break;
2631
2632 case SKILL_SAVE:
2633 name = "saving throws";
2634 break;
2635
2636 case SKILL_SEARCH:
2637 name = "searching";
2638 break;
2639
2640 case SKILL_TO_HIT_MELEE:
2641 name = "melee to hit";
2642 break;
2643
2644 case SKILL_TO_HIT_BOW:
2645 name = "shooting to hit";
2646 break;
2647
2648 case SKILL_TO_HIT_THROW:
2649 name = "throwing to hit";
2650 break;
2651
2652 case SKILL_DIGGING:
2653 name = "digging";
2654 break;
2655
2656 default:
2657 name = "unknown skill";
2658 break;
2659 }
2660
2661 return name;
2662 }
2663
2664
shape_lore_append_list(textblock * tb,const char * const * list,int n)2665 static void shape_lore_append_list(textblock *tb,
2666 const char * const *list, int n)
2667 {
2668 int i;
2669
2670 if (n > 0) {
2671 textblock_append(tb, " %s", list[0]);
2672 }
2673 for (i = 1; i < n; ++i) {
2674 textblock_append(tb, "%s %s", (i < n - 1) ? "," : " and",
2675 list[i]);
2676 }
2677 }
2678
2679
shape_lore_append_basic_combat(textblock * tb,const struct player_shape * s)2680 static void shape_lore_append_basic_combat(textblock *tb,
2681 const struct player_shape *s)
2682 {
2683 char toa_msg[24];
2684 char toh_msg[24];
2685 char tod_msg[24];
2686 const char* msgs[3];
2687 int n = 0;
2688
2689 if (s->to_a != 0) {
2690 strnfmt(toa_msg, sizeof(toa_msg), "%+d to AC", s->to_a);
2691 msgs[n] = toa_msg;
2692 ++n;
2693 }
2694 if (s->to_h != 0) {
2695 strnfmt(toh_msg, sizeof(toh_msg), "%+d to hit", s->to_h);
2696 msgs[n] = toh_msg;
2697 ++n;
2698 }
2699 if (s->to_d != 0) {
2700 strnfmt(tod_msg, sizeof(tod_msg), "%+d to damage", s->to_d);
2701 msgs[n] = tod_msg;
2702 ++n;
2703 }
2704 if (n > 0) {
2705 textblock_append(tb, "Adds");
2706 shape_lore_append_list(tb, msgs, n);
2707 textblock_append(tb, ".\n");
2708 }
2709 }
2710
2711
shape_lore_append_skills(textblock * tb,const struct player_shape * s)2712 static void shape_lore_append_skills(textblock *tb,
2713 const struct player_shape *s)
2714 {
2715 const char **msgs = NULL;
2716 int nmax = 0, n = 0;
2717 int i;
2718
2719 for (i = 0; i < SKILL_MAX; ++i) {
2720 if (s->skills[i] != 0) {
2721 shape_lore_helper_append_to_list(
2722 format("%+d to %s", s->skills[i],
2723 skill_index_to_name(i)),
2724 &msgs, &nmax, &n);
2725 }
2726 }
2727
2728 if (n > 0) {
2729 textblock_append(tb, "Adds");
2730 shape_lore_append_list(tb, msgs, n);
2731 textblock_append(tb, ".\n");
2732 for (i = 0; i < n; ++i) {
2733 string_free((char*) msgs[i]);
2734 }
2735 }
2736
2737 mem_free(msgs);
2738 }
2739
2740
shape_lore_append_non_stat_modifiers(textblock * tb,const struct player_shape * s)2741 static void shape_lore_append_non_stat_modifiers(textblock *tb,
2742 const struct player_shape *s)
2743 {
2744 const char **msgs = NULL;
2745 int nmax = 0, n = 0;
2746 int i;
2747
2748 for (i = STAT_MAX; i < OBJ_MOD_MAX; ++i) {
2749 if (s->modifiers[i] != 0) {
2750 shape_lore_helper_append_to_list(
2751 format("%+d to %s",
2752 s->modifiers[i],
2753 lookup_obj_property(OBJ_PROPERTY_MOD, i)->name),
2754 &msgs, &nmax, &n);
2755 }
2756 }
2757
2758 if (n > 0) {
2759 textblock_append(tb, "Adds");
2760 shape_lore_append_list(tb, msgs, n);
2761 textblock_append(tb, ".\n");
2762 for (i = 0; i < n; ++i) {
2763 string_free((char*) msgs[i]);
2764 }
2765 }
2766
2767 mem_free(msgs);
2768 }
2769
2770
shape_lore_append_stat_modifiers(textblock * tb,const struct player_shape * s)2771 static void shape_lore_append_stat_modifiers(textblock *tb,
2772 const struct player_shape *s)
2773 {
2774 const char **msgs = NULL;
2775 int nmax = 0, n = 0;
2776 int i;
2777
2778 for (i = 0; i < STAT_MAX; ++i) {
2779 if (s->modifiers[i] != 0) {
2780 shape_lore_helper_append_to_list(
2781 format("%+d to %s",
2782 s->modifiers[i],
2783 lookup_obj_property(OBJ_PROPERTY_MOD, i)->name),
2784 &msgs, &nmax, &n);
2785 }
2786 }
2787
2788 if (n > 0) {
2789 textblock_append(tb, "Adds");
2790 shape_lore_append_list(tb, msgs, n);
2791 textblock_append(tb, ".\n");
2792 for (i = 0; i < n; ++i) {
2793 string_free((char*) msgs[i]);
2794 }
2795 }
2796
2797 mem_free(msgs);
2798 }
2799
2800
shape_lore_append_resistances(textblock * tb,const struct player_shape * s)2801 static void shape_lore_append_resistances(textblock *tb,
2802 const struct player_shape *s)
2803 {
2804 const char* vul[ELEM_MAX];
2805 const char* res[ELEM_MAX];
2806 const char* imm[ELEM_MAX];
2807 int nvul = 0, nres = 0, nimm = 0;
2808 int i;
2809
2810 for (i = 0; i < ELEM_MAX; ++i) {
2811 if (s->el_info[i].res_level < 0) {
2812 vul[nvul] = projections[i].name;
2813 ++nvul;
2814 } else if (s->el_info[i].res_level >= 3) {
2815 imm[nimm] = projections[i].name;
2816 ++nimm;
2817 } else if (s->el_info[i].res_level != 0) {
2818 res[nres] = projections[i].name;
2819 ++nres;
2820 }
2821 }
2822
2823 if (nvul != 0) {
2824 textblock_append(tb, "Makes you vulnerable to");
2825 shape_lore_append_list(tb, vul, nvul);
2826 textblock_append(tb, ".\n");
2827 }
2828
2829 if (nres != 0) {
2830 textblock_append(tb, "Makes you resistant to");
2831 shape_lore_append_list(tb, res, nres);
2832 textblock_append(tb, ".\n");
2833 }
2834
2835 if (nimm != 0) {
2836 textblock_append(tb, "Makes you immune to");
2837 shape_lore_append_list(tb, imm, nimm);
2838 textblock_append(tb, ".\n");
2839 }
2840 }
2841
2842
shape_lore_append_protection_flags(textblock * tb,const struct player_shape * s)2843 static void shape_lore_append_protection_flags(textblock *tb,
2844 const struct player_shape *s)
2845 {
2846 const char **msgs = NULL;
2847 int nmax = 0, n = 0;
2848 int i;
2849
2850 for (i = 1; i < OF_MAX; ++i) {
2851 struct obj_property *prop =
2852 lookup_obj_property(OBJ_PROPERTY_FLAG, i);
2853
2854 if (prop->subtype == OFT_PROT &&
2855 of_has(s->flags, prop->index)) {
2856 shape_lore_helper_append_to_list(
2857 prop->desc, &msgs, &nmax, &n);
2858 }
2859 }
2860
2861 if (n > 0) {
2862 textblock_append(tb, "Provides projection from");
2863 shape_lore_append_list(tb, msgs, n);
2864 textblock_append(tb, ".\n");
2865 for (i = 0; i < n; ++i) {
2866 string_free((char*) msgs[i]);
2867 }
2868 }
2869
2870 mem_free(msgs);
2871 }
2872
2873
shape_lore_append_sustains(textblock * tb,const struct player_shape * s)2874 static void shape_lore_append_sustains(textblock *tb,
2875 const struct player_shape *s)
2876 {
2877 const char **msgs = NULL;
2878 int nmax = 0, n = 0;
2879 int i;
2880
2881 for (i = 0; i < STAT_MAX; ++i) {
2882 struct obj_property *prop =
2883 lookup_obj_property(OBJ_PROPERTY_STAT, i);
2884
2885 if (of_has(s->flags, sustain_flag(prop->index))) {
2886 shape_lore_helper_append_to_list(
2887 prop->name, &msgs, &nmax, &n);
2888 }
2889 }
2890
2891 if (n > 0) {
2892 textblock_append(tb, "Sustains");
2893 shape_lore_append_list(tb, msgs, n);
2894 textblock_append(tb, ".\n");
2895 for (i = 0; i < n; ++i) {
2896 string_free((char*) msgs[i]);
2897 }
2898 }
2899
2900 mem_free(msgs);
2901 }
2902
2903
shape_lore_append_misc_flags(textblock * tb,const struct player_shape * s)2904 static void shape_lore_append_misc_flags(textblock *tb,
2905 const struct player_shape *s)
2906 {
2907 int n = 0;
2908 int i;
2909 struct player_ability *ability;
2910
2911 for (i = 1; i < OF_MAX; ++i) {
2912 struct obj_property *prop =
2913 lookup_obj_property(OBJ_PROPERTY_FLAG, i);
2914
2915 if ((prop->subtype == OFT_MISC || prop->subtype == OFT_MELEE ||
2916 prop->subtype == OFT_BAD) &&
2917 of_has(s->flags, prop->index)) {
2918 textblock_append(tb, "%s%s.", (n > 0) ? " " : "",
2919 prop->desc);
2920 ++n;
2921 }
2922 }
2923
2924 for (ability = player_abilities; ability; ability = ability->next) {
2925 if (streq(ability->type, "player") &&
2926 pf_has(s->pflags, ability->index)) {
2927 textblock_append(tb, "%s%s", (n > 0) ? " " : "",
2928 ability->desc);
2929 ++n;
2930 }
2931 }
2932
2933 if (n > 0) {
2934 textblock_append(tb, "\n");
2935 }
2936 }
2937
2938
shape_lore_append_change_effects(textblock * tb,const struct player_shape * s)2939 static void shape_lore_append_change_effects(textblock *tb,
2940 const struct player_shape *s)
2941 {
2942 textblock *tbe = effect_describe(s->effect, "Changing into the shape ",
2943 0, false);
2944
2945 if (tbe) {
2946 textblock_append_textblock(tb, tbe);
2947 textblock_free(tbe);
2948 textblock_append(tb, ".\n");
2949 }
2950 }
2951
2952
shape_lore_append_triggering_spells(textblock * tb,const struct player_shape * s)2953 static void shape_lore_append_triggering_spells(textblock *tb,
2954 const struct player_shape *s)
2955 {
2956 int n = 0;
2957 struct player_class *c;
2958
2959 for (c = classes; c; c = c->next) {
2960 int ibook;
2961
2962 for (ibook = 0; ibook < c->magic.num_books; ++ibook) {
2963 const struct class_book *book = c->magic.books + ibook;
2964 const struct object_kind *kind =
2965 lookup_kind(book->tval, book->sval);
2966 int ispell;
2967
2968 if (!kind || !kind->name) {
2969 continue;
2970 }
2971 for (ispell = 0; ispell < book->num_spells; ++ispell) {
2972 const struct class_spell *spell =
2973 book->spells + ispell;
2974 const struct effect *effect;
2975
2976 for (effect = spell->effect;
2977 effect;
2978 effect = effect->next) {
2979 if (effect->index == EF_SHAPECHANGE &&
2980 effect->subtype == s->sidx) {
2981 if (n == 0) {
2982 textblock_append(tb, "\n");
2983 }
2984 textblock_append(tb,
2985 "The %s spell, %s, from %s triggers the shapechange.",
2986 c->name,
2987 spell->name,
2988 kind->name
2989 );
2990 ++n;
2991 }
2992 }
2993 }
2994 }
2995 }
2996
2997 if (n > 0) {
2998 textblock_append(tb, "\n");
2999 }
3000 }
3001
3002
3003 /**
3004 * Display information about a shape change.
3005 */
shape_lore(const struct player_shape * s)3006 static void shape_lore(const struct player_shape *s)
3007 {
3008 textblock *tb = textblock_new();
3009
3010 textblock_append(tb, "%s", s->name);
3011 textblock_append(tb, "\nLike all shapes, the equipment at the time of "
3012 "the shapechange sets the base attributes, including damage "
3013 "per blow, number of blows and resistances.\n");
3014 shape_lore_append_basic_combat(tb, s);
3015 shape_lore_append_skills(tb, s);
3016 shape_lore_append_non_stat_modifiers(tb, s);
3017 shape_lore_append_stat_modifiers(tb, s);
3018 shape_lore_append_resistances(tb, s);
3019 shape_lore_append_protection_flags(tb, s);
3020 shape_lore_append_sustains(tb, s);
3021 shape_lore_append_misc_flags(tb, s);
3022 shape_lore_append_change_effects(tb, s);
3023 shape_lore_append_triggering_spells(tb, s);
3024
3025 textui_textblock_show(tb, SCREEN_REGION, NULL);
3026 textblock_free(tb);
3027 }
3028
3029
do_cmd_knowledge_shapechange(const char * name,int row)3030 static void do_cmd_knowledge_shapechange(const char *name, int row)
3031 {
3032 region header_region = { 0, 0, -1, 5 };
3033 region list_region = { 0, 6, -1, -2 };
3034 int count = count_interesting_shapes();
3035 struct menu* m;
3036 struct player_shape **sarray;
3037 const char **narray;
3038 int omode;
3039 int h, mark, mark_old;
3040 bool displaying, redraw;
3041 struct player_shape *s;
3042 int i;
3043
3044 if (!count) {
3045 return;
3046 }
3047
3048 m = menu_new(MN_SKIN_SCROLL, menu_find_iter(MN_ITER_STRINGS));
3049
3050 /* Set up an easily indexable list of the interesting shapes. */
3051 sarray = mem_alloc(count * sizeof(*sarray));
3052 for (s = shapes, i = 0; s; s = s->next) {
3053 if (streq(s->name, "normal")) {
3054 continue;
3055 }
3056 sarray[i] = s;
3057 ++i;
3058 }
3059
3060 /*
3061 * Sort them alphabetically by name and set up an array with just the
3062 * names.
3063 */
3064 sort(sarray, count, sizeof(sarray[0]), compare_shape_names);
3065 narray = mem_alloc(count * sizeof(*narray));
3066 for (i = 0; i < count; ++i) {
3067 narray[i] = sarray[i]->name;
3068 }
3069
3070 menu_setpriv(m, count, narray);
3071 menu_layout(m, &list_region);
3072 m->flags |= MN_DBL_TAP;
3073
3074 screen_save();
3075 clear_from(0);
3076
3077 /* Disable the roguelike commands for the duration */
3078 omode = OPT(player, rogue_like_commands);
3079 OPT(player, rogue_like_commands) = false;
3080
3081 h = 0;
3082 mark = 0;
3083 mark_old = -1;
3084 displaying = true;
3085 redraw = true;
3086 while (displaying) {
3087 bool recall = false;
3088 int wnew, hnew;
3089 ui_event ke0 = EVENT_EMPTY;
3090 ui_event ke;
3091
3092 Term_get_size(&wnew, &hnew);
3093 if (h != hnew) {
3094 h = hnew;
3095 redraw = true;
3096 }
3097
3098 if (redraw) {
3099 region_erase(&header_region);
3100 prt("Knowledge - shapes", 2, 0);
3101 prt("Name", 4, 0);
3102 for (i = 0; i < MIN(80, wnew); i++) {
3103 Term_putch(i, 5, COLOUR_WHITE, L'=');
3104 }
3105 prt("<dir>, 'r' to recall, ESC", h - 2, 0);
3106 redraw = false;
3107 }
3108
3109 if (mark_old != mark) {
3110 mark_old = mark;
3111 m->cursor = mark;
3112 }
3113
3114 menu_refresh(m, false);
3115
3116 handle_stuff(player);
3117
3118 ke = inkey_ex();
3119 if (ke.type == EVT_MOUSE) {
3120 menu_handle_mouse(m, &ke, &ke0);
3121 } else if (ke.type == EVT_KBRD) {
3122 menu_handle_keypress(m, &ke, &ke0);
3123 }
3124 if (ke0.type != EVT_NONE) {
3125 ke = ke0;
3126 }
3127
3128 switch (ke.type) {
3129 case EVT_KBRD:
3130 if (ke.key.code == 'r' || ke.key.code == 'R') {
3131 recall = true;
3132 }
3133 break;
3134
3135 case EVT_ESCAPE:
3136 displaying = false;
3137 break;
3138
3139 case EVT_SELECT:
3140 if (mark == m->cursor) {
3141 recall = true;
3142 }
3143 break;
3144
3145 case EVT_MOVE:
3146 mark = m->cursor;
3147 break;
3148
3149 default:
3150 break;
3151 }
3152
3153 if (recall) {
3154 assert(mark >= 0 && mark < count);
3155 shape_lore(sarray[mark]);
3156 }
3157 }
3158
3159 /* Restore roguelike option */
3160 OPT(player, rogue_like_commands) = omode;
3161
3162 screen_load();
3163
3164 mem_free(narray);
3165 mem_free(sarray);
3166 menu_free(m);
3167 }
3168
3169
3170 /**
3171 * ------------------------------------------------------------------------
3172 * Main knowledge menus
3173 * ------------------------------------------------------------------------ */
3174
3175 /* The first row of the knowledge_actions menu which does store knowledge */
3176 #define STORE_KNOWLEDGE_ROW 8
3177
do_cmd_knowledge_store(const char * name,int row)3178 static void do_cmd_knowledge_store(const char *name, int row)
3179 {
3180 textui_store_knowledge(row - STORE_KNOWLEDGE_ROW);
3181 }
3182
do_cmd_knowledge_scores(const char * name,int row)3183 static void do_cmd_knowledge_scores(const char *name, int row)
3184 {
3185 show_scores();
3186 }
3187
do_cmd_knowledge_history(const char * name,int row)3188 static void do_cmd_knowledge_history(const char *name, int row)
3189 {
3190 history_display();
3191 }
3192
do_cmd_knowledge_equip_cmp(const char * name,int row)3193 static void do_cmd_knowledge_equip_cmp(const char* name, int row)
3194 {
3195 equip_cmp_display();
3196 }
3197
handle_store_shortcuts(struct menu * m,const ui_event * ev,int oid)3198 static bool handle_store_shortcuts(struct menu *m, const ui_event *ev, int oid)
3199 {
3200 int i = 0;
3201
3202 assert(ev->type == EVT_KBRD);
3203 while (1) {
3204 if (! m->cmd_keys[i]) {
3205 return false;
3206 }
3207 if (ev->key.code == (unsigned) m->cmd_keys[i]) {
3208 menu_action *acts = menu_priv(m);
3209
3210 do_cmd_knowledge_store(
3211 acts[i + STORE_KNOWLEDGE_ROW].name,
3212 i + STORE_KNOWLEDGE_ROW);
3213 return true;
3214 }
3215 ++i;
3216 }
3217 }
3218
3219 /**
3220 * Definition of the "player knowledge" menu.
3221 */
3222 static menu_action knowledge_actions[] =
3223 {
3224 { 0, 0, "Display object knowledge", textui_browse_object_knowledge },
3225 { 0, 0, "Display rune knowledge", do_cmd_knowledge_runes },
3226 { 0, 0, "Display artifact knowledge", do_cmd_knowledge_artifacts },
3227 { 0, 0, "Display ego item knowledge", do_cmd_knowledge_ego_items },
3228 { 0, 0, "Display monster knowledge", do_cmd_knowledge_monsters },
3229 { 0, 0, "Display feature knowledge", do_cmd_knowledge_features },
3230 { 0, 0, "Display trap knowledge", do_cmd_knowledge_traps },
3231 { 0, 0, "Display shapechange effects", do_cmd_knowledge_shapechange },
3232 { 0, 0, "Display contents of general store (1)", do_cmd_knowledge_store },
3233 { 0, 0, "Display contents of armourer (2)", do_cmd_knowledge_store },
3234 { 0, 0, "Display contents of weaponsmith (3)", do_cmd_knowledge_store },
3235 { 0, 0, "Display contents of bookseller (4)", do_cmd_knowledge_store },
3236 { 0, 0, "Display contents of alchemist (5)", do_cmd_knowledge_store },
3237 { 0, 0, "Display contents of magic shop (6)", do_cmd_knowledge_store },
3238 { 0, 0, "Display contents of black market (7)", do_cmd_knowledge_store },
3239 { 0, 0, "Display contents of home (8)", do_cmd_knowledge_store },
3240 { 0, 0, "Display hall of fame", do_cmd_knowledge_scores },
3241 { 0, 0, "Display character history", do_cmd_knowledge_history },
3242 { 0, 0, "Display equippable comparison", do_cmd_knowledge_equip_cmp },
3243 };
3244
3245 static struct menu knowledge_menu;
3246
3247 /**
3248 * Keep macro counts happy.
3249 */
cleanup_cmds(void)3250 static void cleanup_cmds(void) {
3251 mem_free(obj_group_order);
3252 }
3253
textui_knowledge_init(void)3254 void textui_knowledge_init(void)
3255 {
3256 /* Initialize the menus */
3257 struct menu *menu = &knowledge_menu;
3258 menu_init(menu, MN_SKIN_SCROLL, menu_find_iter(MN_ITER_ACTIONS));
3259 menu_setpriv(menu, N_ELEMENTS(knowledge_actions), knowledge_actions);
3260
3261 menu->title = "Display current knowledge";
3262 menu->selections = lower_case;
3263 /* Shortcuts to get the contents of the stores by number; does prevent
3264 * the normal use of 4 and 6 to go to the previous or next menu */
3265 menu->cmd_keys = "12345678";
3266 menu->keys_hook = handle_store_shortcuts;
3267
3268 /* initialize other static variables */
3269 if (!obj_group_order) {
3270 int i;
3271 int gid = -1;
3272
3273 obj_group_order = mem_zalloc((TV_MAX + 1) * sizeof(int));
3274 atexit(cleanup_cmds);
3275
3276 /* Allow for missing values */
3277 for (i = 0; i < TV_MAX; i++)
3278 obj_group_order[i] = -1;
3279
3280 for (i = 0; 0 != object_text_order[i].tval; i++) {
3281 if (kb_info[object_text_order[i].tval].num_svals == 0) continue;
3282 if (object_text_order[i].name) gid = i;
3283 obj_group_order[object_text_order[i].tval] = gid;
3284 }
3285 }
3286 }
3287
3288
3289 /**
3290 * Display the "player knowledge" menu, greying out items that won't display
3291 * anything.
3292 */
textui_browse_knowledge(void)3293 void textui_browse_knowledge(void)
3294 {
3295 int i, rune_max = max_runes();
3296 region knowledge_region = { 0, 0, -1, 2 + (int)N_ELEMENTS(knowledge_actions) };
3297
3298 /* Runes */
3299 knowledge_actions[1].flags = MN_ACT_GRAYED;
3300 for (i = 0; i < rune_max; i++) {
3301 if (player_knows_rune(player, i) || OPT(player, cheat_xtra)) {
3302 knowledge_actions[1].flags = 0;
3303 break;
3304 }
3305 }
3306
3307 /* Artifacts */
3308 if (collect_known_artifacts(NULL, 0) > 0)
3309 knowledge_actions[2].flags = 0;
3310 else
3311 knowledge_actions[2].flags = MN_ACT_GRAYED;
3312
3313 /* Ego items */
3314 knowledge_actions[3].flags = MN_ACT_GRAYED;
3315 for (i = 0; i < z_info->e_max; i++) {
3316 if (e_info[i].everseen || OPT(player, cheat_xtra)) {
3317 knowledge_actions[3].flags = 0;
3318 break;
3319 }
3320 }
3321
3322 /* Monsters */
3323 if (count_known_monsters() > 0)
3324 knowledge_actions[4].flags = 0;
3325 else
3326 knowledge_actions[4].flags = MN_ACT_GRAYED;
3327
3328 /* Shapechanges */
3329 knowledge_actions[7].flags = (count_interesting_shapes() > 0) ?
3330 0 : MN_ACT_GRAYED;
3331
3332 screen_save();
3333 menu_layout(&knowledge_menu, &knowledge_region);
3334
3335 clear_from(0);
3336 menu_select(&knowledge_menu, 0, false);
3337
3338 screen_load();
3339 }
3340
3341
3342 /**
3343 * ------------------------------------------------------------------------
3344 * Other knowledge functions
3345 * ------------------------------------------------------------------------ */
3346
3347 /**
3348 * Recall the most recent message
3349 */
do_cmd_message_one(void)3350 void do_cmd_message_one(void)
3351 {
3352 /* Recall one message XXX XXX XXX */
3353 c_prt(message_color(0), format( "> %s", message_str(0)), 0, 0);
3354 }
3355
3356
3357 /**
3358 * Show previous messages to the user
3359 *
3360 * The screen format uses line 0 and 23 for headers and prompts,
3361 * skips line 1 and 22, and uses line 2 thru 21 for old messages.
3362 *
3363 * This command shows you which commands you are viewing, and allows
3364 * you to "search" for strings in the recall.
3365 *
3366 * Note that messages may be longer than 80 characters, but they are
3367 * displayed using "infinite" length, with a special sub-command to
3368 * "slide" the virtual display to the left or right.
3369 *
3370 * Attempt to only highlight the matching portions of the string.
3371 */
do_cmd_messages(void)3372 void do_cmd_messages(void)
3373 {
3374 ui_event ke;
3375
3376 bool more = true;
3377
3378 int i, j, n, q;
3379 int wid, hgt;
3380
3381 char shower[80] = "";
3382
3383 /* Total messages */
3384 n = messages_num();
3385
3386 /* Start on first message */
3387 i = 0;
3388
3389 /* Start at leftmost edge */
3390 q = 0;
3391
3392 /* Get size */
3393 Term_get_size(&wid, &hgt);
3394
3395 /* Save screen */
3396 screen_save();
3397
3398 /* Process requests until done */
3399 while (more) {
3400 /* Clear screen */
3401 Term_clear();
3402
3403 /* Dump messages */
3404 for (j = 0; (j < hgt - 4) && (i + j < n); j++) {
3405 const char *msg;
3406 const char *str = message_str(i + j);
3407 byte attr = message_color(i + j);
3408 u16b count = message_count(i + j);
3409
3410 if (count == 1)
3411 msg = str;
3412 else
3413 msg = format("%s <%dx>", str, count);
3414
3415 /* Apply horizontal scroll */
3416 msg = ((int)strlen(msg) >= q) ? (msg + q) : "";
3417
3418 /* Dump the messages, bottom to top */
3419 Term_putstr(0, hgt - 3 - j, -1, attr, msg);
3420
3421 /* Highlight "shower" */
3422 if (strlen(shower)) {
3423 str = msg;
3424
3425 /* Display matches */
3426 while ((str = my_stristr(str, shower)) != NULL) {
3427 int len = strlen(shower);
3428
3429 /* Display the match */
3430 Term_putstr(str-msg, hgt - 3 - j, len, COLOUR_YELLOW, str);
3431
3432 /* Advance */
3433 str += len;
3434 }
3435 }
3436 }
3437
3438 /* Display header */
3439 prt(format("Message recall (%d-%d of %d), offset %d",
3440 i, i + j - 1, n, q), 0, 0);
3441
3442 /* Display prompt (not very informative) */
3443 if (strlen(shower))
3444 prt("[Movement keys to navigate, '-' for next, '=' to find]",
3445 hgt - 1, 0);
3446 else
3447 prt("[Movement keys to navigate, '=' to find, or ESCAPE to exit]",
3448 hgt - 1, 0);
3449
3450 /* Get a command */
3451 ke = inkey_ex();
3452
3453 /* Scroll forwards or backwards using mouse clicks */
3454 if (ke.type == EVT_MOUSE) {
3455 if (ke.mouse.button == 1) {
3456 if (ke.mouse.y <= hgt / 2) {
3457 /* Go older if legal */
3458 if (i + 20 < n)
3459 i += 20;
3460 } else {
3461 /* Go newer */
3462 i = (i >= 20) ? (i - 20) : 0;
3463 }
3464 } else if (ke.mouse.button == 2) {
3465 more = false;
3466 }
3467 } else if (ke.type == EVT_KBRD) {
3468 switch (ke.key.code) {
3469 case ESCAPE:
3470 {
3471 more = false;
3472 break;
3473 }
3474
3475 case '=':
3476 {
3477 /* Get the string to find */
3478 prt("Find: ", hgt - 1, 0);
3479 if (!askfor_aux(shower, sizeof shower, NULL)) continue;
3480
3481 /* Set to find */
3482 ke.key.code = '-';
3483 break;
3484 }
3485
3486 case ARROW_LEFT:
3487 case '4':
3488 q = (q >= wid / 2) ? (q - wid / 2) : 0;
3489 break;
3490
3491 case ARROW_RIGHT:
3492 case '6':
3493 q = q + wid / 2;
3494 break;
3495
3496 case ARROW_UP:
3497 case '8':
3498 if (i + 1 < n) i += 1;
3499 break;
3500
3501 case ARROW_DOWN:
3502 case '2':
3503 case KC_ENTER:
3504 i = (i >= 1) ? (i - 1) : 0;
3505 break;
3506
3507 case KC_PGUP:
3508 case 'p':
3509 case ' ':
3510 if (i + 20 < n) i += 20;
3511 break;
3512
3513 case KC_PGDOWN:
3514 case 'n':
3515 i = (i >= 20) ? (i - 20) : 0;
3516 break;
3517 }
3518 }
3519
3520 /* Find the next item */
3521 if (ke.key.code == '-' && strlen(shower)) {
3522 s16b z;
3523
3524 /* Scan messages */
3525 for (z = i + 1; z < n; z++) {
3526 /* Search for it */
3527 if (my_stristr(message_str(z), shower)) {
3528 /* New location */
3529 i = z;
3530
3531 /* Done */
3532 break;
3533 }
3534 }
3535 }
3536 }
3537
3538 /* Load screen */
3539 screen_load();
3540 }
3541
3542
3543
3544 #define GET_ITEM_PARAMS \
3545 (USE_EQUIP | USE_INVEN | USE_QUIVER | USE_FLOOR | SHOW_QUIVER | SHOW_EMPTY | IS_HARMLESS)
3546
3547 /**
3548 * Display inventory
3549 */
do_cmd_inven(void)3550 void do_cmd_inven(void)
3551 {
3552 struct object *obj = NULL;
3553 int ret = 3;
3554
3555 if (player->upkeep->inven[0] == NULL) {
3556 msg("You have nothing in your inventory.");
3557 return;
3558 }
3559
3560 /* Start in "inventory" mode */
3561 player->upkeep->command_wrk = (USE_INVEN);
3562
3563 /* Loop this menu until an object context menu says differently */
3564 while (ret == 3) {
3565 /* Save screen */
3566 screen_save();
3567
3568 /* Get an item to use a context command on (Display the inventory) */
3569 if (get_item(&obj, "Select Item:", NULL, CMD_NULL, NULL,
3570 GET_ITEM_PARAMS)) {
3571 /* Load screen */
3572 screen_load();
3573
3574 if (obj && obj->kind) {
3575 /* Track the object */
3576 track_object(player->upkeep, obj);
3577
3578 if (!player_is_shapechanged(player)) {
3579 while ((ret = context_menu_object(obj)) == 2);
3580 }
3581 }
3582 } else {
3583 /* Load screen */
3584 screen_load();
3585
3586 ret = -1;
3587 }
3588 }
3589 }
3590
3591
3592 /**
3593 * Display equipment
3594 */
do_cmd_equip(void)3595 void do_cmd_equip(void)
3596 {
3597 struct object *obj = NULL;
3598 int ret = 3;
3599
3600 if (!player->upkeep->equip_cnt) {
3601 msg("You are not wielding or wearing anything.");
3602 return;
3603 }
3604
3605 /* Start in "equipment" mode */
3606 player->upkeep->command_wrk = (USE_EQUIP);
3607
3608 /* Loop this menu until an object context menu says differently */
3609 while (ret == 3) {
3610 /* Save screen */
3611 screen_save();
3612
3613 /* Get an item to use a context command on (Display the equipment) */
3614 if (get_item(&obj, "Select Item:", NULL, CMD_NULL, NULL,
3615 GET_ITEM_PARAMS)) {
3616 /* Load screen */
3617 screen_load();
3618
3619 if (obj && obj->kind) {
3620 /* Track the object */
3621 track_object(player->upkeep, obj);
3622
3623 if (!player_is_shapechanged(player)) {
3624 while ((ret = context_menu_object(obj)) == 2);
3625 }
3626
3627 /* Stay in "equipment" mode */
3628 player->upkeep->command_wrk = (USE_EQUIP);
3629 }
3630 } else {
3631 /* Load screen */
3632 screen_load();
3633
3634 ret = -1;
3635 }
3636 }
3637 }
3638
3639
3640 /**
3641 * Display equipment
3642 */
do_cmd_quiver(void)3643 void do_cmd_quiver(void)
3644 {
3645 struct object *obj = NULL;
3646 int ret = 3;
3647
3648 if (player->upkeep->quiver_cnt == 0) {
3649 msg("You have nothing in your quiver.");
3650 return;
3651 }
3652
3653 /* Start in "quiver" mode */
3654 player->upkeep->command_wrk = (USE_QUIVER);
3655
3656 /* Loop this menu until an object context menu says differently */
3657 while (ret == 3) {
3658 /* Save screen */
3659 screen_save();
3660
3661 /* Get an item to use a context command on (Display the quiver) */
3662 if (get_item(&obj, "Select Item:", NULL, CMD_NULL, NULL,
3663 GET_ITEM_PARAMS)) {
3664 /* Load screen */
3665 screen_load();
3666
3667 if (obj && obj->kind) {
3668 /* Track the object */
3669 track_object(player->upkeep, obj);
3670
3671 if (!player_is_shapechanged(player)) {
3672 while ((ret = context_menu_object(obj)) == 2);
3673 }
3674
3675 /* Stay in "quiver" mode */
3676 player->upkeep->command_wrk = (USE_QUIVER);
3677 }
3678 } else {
3679 /* Load screen */
3680 screen_load();
3681
3682 ret = -1;
3683 }
3684 }
3685 }
3686
3687
3688 /**
3689 * Look command
3690 */
do_cmd_look(void)3691 void do_cmd_look(void)
3692 {
3693 /* Look around */
3694 if (target_set_interactive(TARGET_LOOK, -1, -1))
3695 {
3696 msg("Target Selected.");
3697 }
3698 }
3699
3700
3701
3702 /**
3703 * Number of basic grids per panel, vertically and horizontally
3704 */
3705 #define PANEL_SIZE 11
3706
3707 /**
3708 * Allow the player to examine other sectors on the map
3709 */
do_cmd_locate(void)3710 void do_cmd_locate(void)
3711 {
3712 int y1, x1;
3713
3714 /* Start at current panel */
3715 y1 = Term->offset_y;
3716 x1 = Term->offset_x;
3717
3718 /* Show panels until done */
3719 while (1) {
3720 char tmp_val[80];
3721 char out_val[160];
3722
3723 /* Assume no direction */
3724 int dir = 0;
3725
3726 /* Get the current panel */
3727 int y2 = Term->offset_y;
3728 int x2 = Term->offset_x;
3729
3730 /* Adjust for tiles */
3731 int panel_hgt = (int)(PANEL_SIZE / tile_height);
3732 int panel_wid = (int)(PANEL_SIZE / tile_width);
3733
3734 /* Describe the location */
3735 if ((y2 == y1) && (x2 == x1)) {
3736 tmp_val[0] = '\0';
3737 } else {
3738 strnfmt(tmp_val, sizeof(tmp_val), "%s%s of",
3739 ((y2 < y1) ? " north" : (y2 > y1) ? " south" : ""),
3740 ((x2 < x1) ? " west" : (x2 > x1) ? " east" : ""));
3741 }
3742
3743 /* Prepare to ask which way to look */
3744 strnfmt(out_val, sizeof(out_val),
3745 "Map sector [%d,%d], which is%s your sector. Direction?",
3746 (y2 / panel_hgt), (x2 / panel_wid), tmp_val);
3747
3748 /* More detail */
3749 if (OPT(player, center_player)) {
3750 strnfmt(out_val, sizeof(out_val),
3751 "Map sector [%d(%02d),%d(%02d)], which is%s your sector. Direction?",
3752 (y2 / panel_hgt), (y2 % panel_hgt),
3753 (x2 / panel_wid), (x2 % panel_wid), tmp_val);
3754 }
3755
3756 /* Get a direction */
3757 while (!dir) {
3758 struct keypress command = KEYPRESS_NULL;
3759
3760 /* Get a command (or Cancel) */
3761 if (!get_com(out_val, (char *)&command.code)) break;
3762
3763 /* Extract direction */
3764 dir = target_dir(command);
3765
3766 /* Error */
3767 if (!dir) bell("Illegal direction for locate!");
3768 }
3769
3770 /* No direction */
3771 if (!dir) break;
3772
3773 /* Apply the motion */
3774 change_panel(dir);
3775
3776 /* Handle stuff */
3777 handle_stuff(player);
3778 }
3779
3780 /* Verify panel */
3781 verify_panel();
3782 }
3783
cmp_mexp(const void * a,const void * b)3784 static int cmp_mexp(const void *a, const void *b)
3785 {
3786 u16b ia = *(const u16b *)a;
3787 u16b ib = *(const u16b *)b;
3788 if (r_info[ia].mexp < r_info[ib].mexp)
3789 return -1;
3790 if (r_info[ia].mexp > r_info[ib].mexp)
3791 return 1;
3792 return (a < b ? -1 : (a > b ? 1 : 0));
3793 }
3794
cmp_level(const void * a,const void * b)3795 static int cmp_level(const void *a, const void *b)
3796 {
3797 u16b ia = *(const u16b *)a;
3798 u16b ib = *(const u16b *)b;
3799 if (r_info[ia].level < r_info[ib].level)
3800 return -1;
3801 if (r_info[ia].level > r_info[ib].level)
3802 return 1;
3803 return cmp_mexp(a, b);
3804 }
3805
cmp_tkill(const void * a,const void * b)3806 static int cmp_tkill(const void *a, const void *b)
3807 {
3808 u16b ia = *(const u16b *)a;
3809 u16b ib = *(const u16b *)b;
3810 if (l_list[ia].tkills < l_list[ib].tkills)
3811 return -1;
3812 if (l_list[ia].tkills > l_list[ib].tkills)
3813 return 1;
3814 return cmp_level(a, b);
3815 }
3816
cmp_pkill(const void * a,const void * b)3817 static int cmp_pkill(const void *a, const void *b)
3818 {
3819 u16b ia = *(const u16b *)a;
3820 u16b ib = *(const u16b *)b;
3821 if (l_list[ia].pkills < l_list[ib].pkills)
3822 return -1;
3823 if (l_list[ia].pkills > l_list[ib].pkills)
3824 return 1;
3825 return cmp_tkill(a, b);
3826 }
3827
cmp_monsters(const void * a,const void * b)3828 int cmp_monsters(const void *a, const void *b)
3829 {
3830 return cmp_level(a, b);
3831 }
3832
3833 /**
3834 * Search the monster, item, and feature types to find the
3835 * meaning for the given symbol.
3836 *
3837 * Note: We currently search items first, then features, then
3838 * monsters, and we return the first hit for a symbol.
3839 * This is to prevent mimics and lurkers from matching
3840 * a symbol instead of the item or feature it is mimicking.
3841 *
3842 * Todo: concatenate all matches into buf. This will be much
3843 * easier once we can loop through item tvals instead of items
3844 * (see note below.)
3845 *
3846 * Todo: Should this take the user's pref files into account?
3847 */
lookup_symbol(char sym,char * buf,size_t max)3848 static void lookup_symbol(char sym, char *buf, size_t max)
3849 {
3850 int i;
3851 struct monster_base *race;
3852
3853 /* Look through items */
3854 /* Note: We currently look through all items, and grab the tval when we
3855 * find a match.
3856 * It would make more sense to loop through tvals, but then we need to
3857 * associate a display character with each tval. */
3858 for (i = 0; i < z_info->k_max; i++) {
3859 if (char_matches_key(k_info[i].d_char, sym)) {
3860 strnfmt(buf, max, "%c - %s.", sym, tval_find_name(k_info[i].tval));
3861 return;
3862 }
3863 }
3864
3865 /* Look through features */
3866 /* Note: We need a better way of doing this. Currently '#' matches secret
3867 * door, and '^' matches trap door (instead of the more generic "trap"). */
3868 for (i = 1; i < z_info->f_max; i++) {
3869 if (char_matches_key(f_info[i].d_char, sym)) {
3870 strnfmt(buf, max, "%c - %s.", sym, f_info[i].name);
3871 return;
3872 }
3873 }
3874
3875 /* Look through monster templates */
3876 for (race = rb_info; race; race = race->next) {
3877 /* Slight hack - P appears twice */
3878 if (streq(race->name, "Morgoth")) continue;
3879 if (char_matches_key(race->d_char, sym)) {
3880 strnfmt(buf, max, "%c - %s.", sym, race->text);
3881 return;
3882 }
3883 }
3884
3885 /* No matches */
3886 if (isprint(sym)) {
3887 strnfmt(buf, max, "%c - Unknown Symbol.", sym);
3888 } else {
3889 strnfmt(buf, max, "? - Unknown Symbol.");
3890 }
3891
3892 return;
3893 }
3894
3895 /**
3896 * Identify a character, allow recall of monsters
3897 *
3898 * Several "special" responses recall "multiple" monsters:
3899 * ^A (all monsters)
3900 * ^U (all unique monsters)
3901 * ^N (all non-unique monsters)
3902 *
3903 * The responses may be sorted in several ways, see below.
3904 *
3905 * Note that the player ghosts are ignored, since they do not exist.
3906 */
do_cmd_query_symbol(void)3907 void do_cmd_query_symbol(void)
3908 {
3909 int idx, num;
3910 char buf[128];
3911
3912 char sym;
3913 struct keypress query;
3914
3915 bool all = false;
3916 bool uniq = false;
3917 bool norm = false;
3918
3919 bool recall = false;
3920
3921 u16b *who;
3922
3923 /* Get a character, or abort */
3924 if (!get_com("Enter character to be identified, or control+[ANU]: ", &sym))
3925 return;
3926
3927 /* Describe */
3928 if (sym == KTRL('A')) {
3929 all = true;
3930 my_strcpy(buf, "Full monster list.", sizeof(buf));
3931 } else if (sym == KTRL('U')) {
3932 all = uniq = true;
3933 my_strcpy(buf, "Unique monster list.", sizeof(buf));
3934 } else if (sym == KTRL('N')) {
3935 all = norm = true;
3936 my_strcpy(buf, "Non-unique monster list.", sizeof(buf));
3937 } else {
3938 lookup_symbol(sym, buf, sizeof(buf));
3939 }
3940
3941 /* Display the result */
3942 prt(buf, 0, 0);
3943
3944 /* Allocate the "who" array */
3945 who = mem_zalloc(z_info->r_max * sizeof(u16b));
3946
3947 /* Collect matching monsters */
3948 for (num = 0, idx = 1; idx < z_info->r_max - 1; idx++) {
3949 struct monster_race *race = &r_info[idx];
3950 struct monster_lore *lore = &l_list[idx];
3951
3952 /* Nothing to recall */
3953 if (!lore->all_known && !lore->sights)
3954 continue;
3955
3956 /* Require non-unique monsters if needed */
3957 if (norm && rf_has(race->flags, RF_UNIQUE)) continue;
3958
3959 /* Require unique monsters if needed */
3960 if (uniq && !rf_has(race->flags, RF_UNIQUE)) continue;
3961
3962 /* Collect "appropriate" monsters */
3963 if (all || char_matches_key(race->d_char, sym)) who[num++] = idx;
3964 }
3965
3966 /* No monsters to recall */
3967 if (!num) {
3968 /* Free the "who" array */
3969 mem_free(who);
3970 return;
3971 }
3972
3973 /* Prompt */
3974 put_str("Recall details? (y/k/n): ", 0, 40);
3975
3976 /* Query */
3977 query = inkey();
3978
3979 /* Restore */
3980 prt(buf, 0, 0);
3981
3982 /* Interpret the response */
3983 if (query.code == 'k') {
3984 /* Sort by kills (and level) */
3985 sort(who, num, sizeof(*who), cmp_pkill);
3986 } else if (query.code == 'y' || query.code == 'p') {
3987 /* Sort by level; accept 'p' as legacy */
3988 sort(who, num, sizeof(*who), cmp_level);
3989 } else {
3990 /* Any unsupported response is "nope, no history please" */
3991 mem_free(who);
3992 return;
3993 }
3994
3995 /* Start at the end, as the array is sorted lowest to highest */
3996 idx = num - 1;
3997
3998 /* Scan the monster memory */
3999 while (1) {
4000 textblock *tb;
4001
4002 /* Extract a race */
4003 int r_idx = who[idx];
4004 struct monster_race *race = &r_info[r_idx];
4005 struct monster_lore *lore = &l_list[r_idx];
4006
4007 /* Auto-recall */
4008 monster_race_track(player->upkeep, race);
4009
4010 /* Do any necessary updates or redraws */
4011 handle_stuff(player);
4012
4013 tb = textblock_new();
4014 lore_title(tb, race);
4015
4016 textblock_append(tb, " [(r)ecall, ESC]");
4017 textui_textblock_place(tb, SCREEN_REGION, NULL);
4018 textblock_free(tb);
4019
4020 /* Interact */
4021 while (1) {
4022 /* Ignore keys during recall presentation, otherwise, the 'r' key
4023 * acts like a toggle and instead of a one-off command */
4024 if (recall)
4025 lore_show_interactive(race, lore);
4026 else
4027 query = inkey();
4028
4029 /* Normal commands */
4030 if (query.code != 'r') break;
4031
4032 /* Toggle recall */
4033 recall = !recall;
4034 }
4035
4036 /* Stop scanning */
4037 if (query.code == ESCAPE) break;
4038
4039 /* Move to previous or next monster */
4040 if (query.code == '-') {
4041 /* Previous is a step forward in the array */
4042 idx++;
4043 /* Wrap if we're at the end of the array */
4044 if (idx == num) {
4045 idx = 0;
4046 }
4047 } else {
4048 /* Next is a step back in the array */
4049 idx--;
4050 /* Wrap if we're at the start of the array */
4051 if (idx < 0) {
4052 idx = num - 1;
4053 }
4054 }
4055 }
4056
4057 /* Re-display the identity */
4058 prt(buf, 0, 0);
4059
4060 /* Free the "who" array */
4061 mem_free(who);
4062 }
4063
4064 /**
4065 * Centers the map on the player
4066 */
do_cmd_center_map(void)4067 void do_cmd_center_map(void)
4068 {
4069 center_panel();
4070 }
4071
4072
4073
4074 /**
4075 * Display the main-screen monster list.
4076 */
do_cmd_monlist(void)4077 void do_cmd_monlist(void)
4078 {
4079 /* Save the screen and display the list */
4080 screen_save();
4081
4082 monster_list_show_interactive(Term->hgt, Term->wid);
4083
4084 /* Return */
4085 screen_load();
4086 }
4087
4088
4089 /**
4090 * Display the main-screen item list.
4091 */
do_cmd_itemlist(void)4092 void do_cmd_itemlist(void)
4093 {
4094 /* Save the screen and display the list */
4095 screen_save();
4096
4097 object_list_show_interactive(Term->hgt, Term->wid);
4098
4099 /* Return */
4100 screen_load();
4101 }
4102