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