PageRenderTime 113ms CodeModel.GetById 19ms app.highlight 81ms RepoModel.GetById 1ms app.codeStats 0ms

/src/cmd-know.c

https://bitbucket.org/ekolis/jackband
C | 2116 lines | 1381 code | 437 blank | 298 comment | 312 complexity | 275e9eda16b12d12e87568f0eda25171 MD5 | raw file
   1/*
   2 * File: cmd-know.c
   3 * Purpose: Knowledge screen stuff.
   4 *
   5 * Copyright (c) 2000-2007 Eytan Zweig, Andrew Doull, Pete Mack.
   6 * (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#include "angband.h"
  20#include "object/tvalsval.h"
  21#include "ui.h"
  22#include "ui-menu.h"
  23#include "store.h"
  24
  25/* Flag value for missing array entry */
  26#define MISSING -17
  27
  28
  29typedef struct
  30{
  31	int maxnum;          /* Maximum possible item count for this class */
  32	bool easy_know;      /* Items don't need to be IDed to recognize membership */
  33
  34	const char *(*name)(int gid);               /* Name of this group */
  35
  36	/* Compare, in group and display order (optional if already sorted) */
  37	int (*gcomp)(const void *, const void *);   /* Compares gids of two oids */
  38	int (*group)(int oid);                      /* Returns gid for an oid */
  39
  40	/* Summary function for the "object" information. */
  41	void (*summary)(int gid, const int *object_list, int n, int top, int row, int col);
  42
  43} group_funcs;
  44
  45typedef struct
  46{
  47	/* Displays an entry at specified location, including kill-count and graphics */
  48	void (*display_member)(int col, int row, bool cursor, int oid);
  49
  50	void (*lore)(int oid);       /* Displays lore for an oid */
  51
  52
  53	/* Required only for objects with modifiable display attributes */
  54	/* Unknown 'flavors' return flavor attributes */
  55	char *(*xchar)(int oid);     /* Get character attr for OID (by address) */
  56	byte *(*xattr)(int oid);     /* Get color attr for OID (by address) */
  57
  58	const char *(*xtra_prompt)(int oid);  /* Returns optional extra prompt */
  59	void (*xtra_act)(char ch, int oid);   /* Handles optional extra actions */
  60
  61	bool is_visual;                       /* Does this kind have visual editing? */
  62
  63} member_funcs;
  64
  65
  66/* Helper class for generating joins */
  67typedef struct join
  68{
  69		int oid;
  70		int gid;
  71} join_t;
  72
  73/* A default group-by */
  74static join_t *default_join;
  75#if 0
  76static int default_join_cmp(const void *a, const void *b)
  77{
  78		join_t *ja = &default_join[*(int*)a];
  79		join_t *jb = &default_join[*(int*)b];
  80		int c = ja->gid - jb->gid;
  81		if (c) return c;
  82		return ja->oid - jb->oid;
  83}
  84#endif
  85static int default_group(int oid) { return default_join[oid].gid; }
  86
  87
  88static int *obj_group_order;
  89
  90/*
  91 * Description of each monster group.
  92 */
  93static struct
  94{
  95	cptr chars;
  96	cptr name;
  97} monster_group[] =
  98{
  99	{ (cptr)-1,   "Uniques" },
 100	{ "A",        "Angels" },
 101	{ "a",        "Ants" },
 102	{ "b",        "Bats" },
 103	{ "B",        "Birds" },
 104	{ "C",        "Canines" },
 105	{ "c",        "Centipedes" },
 106	{ "uU",       "Demons" },
 107	{ "dD",       "Dragons" },
 108	{ "vE",       "Elementals/Vortices" },
 109	{ "e",        "Eyes/Beholders" },
 110	{ "f",        "Felines" },
 111	{ "G",        "Ghosts" },
 112	{ "OP",       "Giants/Ogres" },
 113	{ "g",        "Golems" },
 114	{ "H",        "Harpies/Hybrids" },
 115	{ "h",        "Hominids (Elves, Dwarves)" },
 116	{ "M",        "Hydras" },
 117	{ "i",        "Icky Things" },
 118	{ "lFI",      "Insects" },
 119	{ "j",        "Jellies" },
 120	{ "K",        "Killer Beetles" },
 121	{ "k",        "Kobolds" },
 122	{ "L",        "Lichs" },
 123	{ "tp",		  "Men" },
 124	{ ".$!?=~_",  "Mimics" },
 125	{ "m",        "Molds" },
 126	{ ",",        "Mushroom Patches" },
 127	{ "n",        "Nagas" },
 128	{ "o",        "Orcs" },
 129	{ "q",        "Quadrupeds" },
 130	{ "Q",        "Quylthulgs" },
 131	{ "R",        "Reptiles/Amphibians" },
 132	{ "r",        "Rodents" },
 133	{ "S",        "Scorpions/Spiders" },
 134	{ "s",        "Skeletons/Drujs" },
 135	{ "J",        "Snakes" },
 136	{ "T",        "Trolls" },
 137	{ "V",        "Vampires" },
 138	{ "W",        "Wights/Wraiths" },
 139	{ "w",        "Worms/Worm Masses" },
 140	{ "X",        "Xorns/Xarens" },
 141	{ "y",        "Yeeks" },
 142	{ "Y",        "Yeti" },
 143	{ "Z",        "Zephyr Hounds" },
 144	{ "z",        "Zombies" },
 145	{ NULL,       NULL }
 146};
 147
 148/*
 149 * Description of each feature group.
 150 */
 151const char *feature_group_text[] =
 152{
 153	"Floors",
 154	"Traps",
 155	"Doors",
 156	"Stairs",
 157	"Walls",
 158	"Streamers",
 159	"Obstructions",
 160	"Stores",
 161	"Other",
 162	NULL
 163};
 164
 165
 166
 167/* Useful method declarations */
 168static void display_visual_list(int col, int row, int height, int width,
 169				byte attr_top, byte char_left);
 170
 171static bool visual_mode_command(ui_event_data ke, bool *visual_list_ptr,
 172				int height, int width,
 173				byte *attr_top_ptr, byte *char_left_ptr,
 174				byte *cur_attr_ptr, byte *cur_char_ptr,
 175				int col, int row, int *delay);
 176
 177static void place_visual_list_cursor(int col, int row, byte a,
 178				byte c, byte attr_top, byte char_left);
 179
 180/*
 181 * Clipboard variables for copy&paste in visual mode
 182 */
 183static byte attr_idx = 0;
 184static byte char_idx = 0;
 185
 186/*
 187 * Return a specific ordering for the features
 188 */
 189static int feat_order(int feat)
 190{
 191	feature_type *f_ptr = &f_info[feat];
 192
 193	switch (f_ptr->d_char)
 194	{
 195		case '.': 				return 0;
 196		case '^': 				return 1;
 197		case '\'': case '+': 	return 2;
 198		case '<': case '>':		return 3;
 199		case '#':				return 4;
 200		case '*': case '%' :	return 5;
 201		case ';': case ':' :	return 6;
 202
 203		default:
 204		{
 205			return 8;
 206		}
 207	}
 208}
 209
 210
 211/* Emit a 'graphical' symbol and a padding character if appropriate */
 212static void big_pad(int col, int row, byte a, byte c)
 213{
 214	Term_putch(col, row, a, c);
 215	if (!use_bigtile) return;
 216
 217	if (a & 0x80)
 218		Term_putch(col + 1, row, 255, -1);
 219	else
 220		Term_putch(col + 1, row, 1, ' ');
 221}
 222
 223/* Return the actual width of a symbol */
 224static int actual_width(int width)
 225{
 226#ifdef UNANGBAND
 227	if (use_trptile) width *= 3;
 228	else if (use_dbltile) width *= 2;
 229#endif
 230
 231	if (use_bigtile) width *= 2;
 232
 233	return width;
 234}
 235
 236/* Return the actual height of a symbol */
 237static int actual_height(int height)
 238{
 239#ifdef UNANGBAND
 240	if (use_trptile) height = height * 3 / 2;
 241	else if (use_dbltile) height *= 2;
 242#endif
 243
 244	if (use_bigtile) height *= 2;
 245
 246	return height;
 247}
 248
 249
 250/* From an actual width, return the logical width */
 251static int logical_width(int width)
 252{
 253	int divider = 1;
 254
 255#ifdef UNANGBAND
 256	if (use_trptile) divider = 3;
 257	else if (use_dbltile) divider = 2;
 258#endif
 259
 260	if (use_bigtile) divider *= 2;
 261
 262	return width / divider;
 263}
 264
 265/* From an actual height, return the logical height */
 266static int logical_height(int height)
 267{
 268	int divider = 1;
 269
 270#ifdef UNANGBAND
 271	if (use_trptile)
 272	{
 273		height *= 2;
 274		divider = 3;
 275	}
 276	else if (use_dbltile) divider = 2;
 277#endif
 278
 279	if (use_bigtile) divider *= 2;
 280
 281	return height / divider;
 282}
 283
 284
 285static void display_group_member(menu_type *menu, int oid,
 286						bool cursor, int row, int col, int wid)
 287{
 288	const member_funcs *o_funcs = menu->menu_data;
 289	byte attr = curs_attrs[CURS_KNOWN][cursor == oid];
 290
 291	(void)wid;
 292
 293	/* Print the interesting part */
 294	o_funcs->display_member(col, row, cursor, oid);
 295
 296#if 0 /* Debugging code */
 297	c_put_str(attr, format("%d", oid), row, 60);
 298#endif
 299
 300	/* Do visual mode */
 301	if (o_funcs->is_visual && o_funcs->xattr)
 302	{
 303		byte c = *o_funcs->xchar(oid);
 304		byte a = *o_funcs->xattr(oid);
 305
 306		c_put_str(attr, format((c & 0x80) ? "%02x/%02x" : "%02x/%d", a, c), row, 60);
 307	}
 308}
 309
 310static const char *recall_prompt(int oid)
 311{
 312	(void)oid;
 313	return ", 'r' to recall";
 314}
 315
 316#define swap(a, b) (swapspace = (void*)(a)), ((a) = (b)), ((b) = swapspace)
 317
 318/*
 319 * Interactive group by.
 320 * Recognises inscriptions, graphical symbols, lore
 321 */
 322static void display_knowledge(const char *title, int *obj_list, int o_count,
 323				group_funcs g_funcs, member_funcs o_funcs,
 324				const char *otherfields)
 325{
 326	/* maximum number of groups to display */
 327	int max_group = g_funcs.maxnum < o_count ? g_funcs.maxnum : o_count ;
 328
 329	/* This could (should?) be (void **) */
 330	int *g_list, *g_offset;
 331
 332	const char **g_names;
 333
 334	int g_name_len = 8;  /* group name length, minumum is 8 */
 335
 336	int grp_cnt = 0; /* total number groups */
 337
 338	int g_cur = 0, grp_old = -1; /* group list positions */
 339	int o_cur = 0;					/* object list positions */
 340	int g_o_count = 0;				 /* object count for group */
 341	int oid = -1;  				/* object identifiers */
 342
 343	region title_area = { 0, 0, 0, 4 };
 344	region group_region = { 0, 6, MISSING, -2 };
 345	region object_region = { MISSING, 6, 0, -2 };
 346
 347	/* display state variables */
 348	bool visual_list = FALSE;
 349	byte attr_top = 0;
 350	byte char_left = 0;
 351
 352	int delay = 0;
 353
 354	menu_type group_menu;
 355	menu_type object_menu;
 356	menu_iter object_iter;
 357
 358	/* Panel state */
 359	/* These are swapped in parallel whenever the actively browsing " */
 360	/* changes */
 361	int *active_cursor = &g_cur, *inactive_cursor = &o_cur;
 362	menu_type *active_menu = &group_menu, *inactive_menu = &object_menu;
 363	int panel = 0;
 364
 365	void *swapspace;
 366	bool do_swap = FALSE;
 367
 368	bool flag = FALSE;
 369	bool redraw = TRUE;
 370
 371	int browser_rows;
 372	int wid, hgt;
 373	int i;
 374	int prev_g = -1;
 375
 376	int omode = OPT(rogue_like_commands);
 377
 378
 379	/* Get size */
 380	Term_get_size(&wid, &hgt);
 381	browser_rows = hgt - 8;
 382
 383	/* Disable the roguelike commands for the duration */
 384	OPT(rogue_like_commands) = FALSE;
 385
 386
 387
 388	/* Do the group by. ang_sort only works on (void **) */
 389	/* Maybe should make this a precondition? */
 390	if (g_funcs.gcomp)
 391		qsort(obj_list, o_count, sizeof(*obj_list), g_funcs.gcomp);
 392
 393
 394	/* Sort everything into group order */
 395	g_list = C_ZNEW(max_group + 1, int);
 396	g_offset = C_ZNEW(max_group + 1, int);
 397
 398	for (i = 0; i < o_count; i++)
 399	{
 400		if (prev_g != g_funcs.group(obj_list[i]))
 401		{
 402			prev_g = g_funcs.group(obj_list[i]);
 403			g_offset[grp_cnt] = i;
 404			g_list[grp_cnt++] = prev_g;
 405		}
 406	}
 407
 408	g_offset[grp_cnt] = o_count;
 409	g_list[grp_cnt] = -1;
 410
 411
 412	/* The compact set of group names, in display order */
 413	g_names = C_ZNEW(grp_cnt, const char *);
 414
 415	for (i = 0; i < grp_cnt; i++)
 416	{
 417		int len;
 418		g_names[i] = g_funcs.name(g_list[i]);
 419		len = strlen(g_names[i]);
 420		if (len > g_name_len) g_name_len = len;
 421	}
 422
 423	/* Reasonable max group name len */
 424	if (g_name_len >= 20) g_name_len = 20;
 425
 426	object_region.col = g_name_len + 3;
 427	group_region.width = g_name_len;
 428
 429
 430	/* Leave room for the group summary information */
 431	if (g_funcs.summary) object_region.page_rows = -3;
 432
 433
 434	/* Set up the two menus */
 435	WIPE(&group_menu, menu_type);
 436	group_menu.count = grp_cnt;
 437	group_menu.cmd_keys = "\n\r6\x8C";  /* Ignore these as menu commands */
 438	group_menu.menu_data = g_names;
 439
 440	WIPE(&object_menu, menu_type);
 441	object_menu.menu_data = &o_funcs;
 442	WIPE(&object_iter, object_iter);
 443	object_iter.display_row = display_group_member;
 444
 445	o_funcs.is_visual = FALSE;
 446
 447	menu_init(&group_menu, MN_SKIN_SCROLL, find_menu_iter(MN_ITER_STRINGS), &group_region);
 448	menu_init(&object_menu, MN_SKIN_SCROLL, &object_iter, &object_region);
 449
 450
 451	/* This is the event loop for a multi-region panel */
 452	/* Panels are -- text panels, two menus, and visual browser */
 453	/* with "pop-up menu" for lore */
 454	while ((!flag) && (grp_cnt))
 455	{
 456		ui_event_data ke, ke0;
 457
 458		if (redraw)
 459		{
 460			/* Print the title bits */
 461			region_erase(&title_area);
 462			prt(format("Knowledge - %s", title), 2, 0);
 463			prt("Group", 4, 0);
 464			prt("Name", 4, g_name_len + 3);
 465
 466			if (otherfields)
 467				prt(otherfields, 4, 46);
 468
 469
 470			/* Print dividers: horizontal and vertical */
 471			for (i = 0; i < 79; i++)
 472				Term_putch(i, 5, TERM_WHITE, '=');
 473
 474			for (i = 0; i < browser_rows; i++)
 475				Term_putch(g_name_len + 1, 6 + i, TERM_WHITE, '|');
 476
 477
 478			/* Reset redraw flag */
 479			redraw = FALSE;
 480		}
 481
 482		if (g_cur != grp_old)
 483		{
 484			grp_old = g_cur;
 485			o_cur = 0;
 486			g_o_count = g_offset[g_cur+1] - g_offset[g_cur];
 487			menu_set_filter(&object_menu, obj_list + g_offset[g_cur], g_o_count);
 488			group_menu.cursor = g_cur;
 489			object_menu.cursor = 0;
 490		}
 491
 492		/* HACK ... */
 493		if (!visual_list)
 494		{
 495			/* ... The object menu may be browsing the entire group... */
 496			o_funcs.is_visual = FALSE;
 497			menu_set_filter(&object_menu, obj_list + g_offset[g_cur], g_o_count);
 498			object_menu.cursor = o_cur;
 499		}
 500		else
 501		{
 502			/* ... or just a single element in the group. */
 503			o_funcs.is_visual = TRUE;
 504			menu_set_filter(&object_menu, obj_list + o_cur + g_offset[g_cur], 1);
 505			object_menu.cursor = 0;
 506		}
 507
 508		oid = obj_list[g_offset[g_cur]+o_cur];
 509
 510		/* Print prompt */
 511		{
 512			const char *pedit = (!o_funcs.xattr) ? "" :
 513					(!(attr_idx|char_idx) ? ", 'c' to copy" : ", 'c', 'p' to paste");
 514			const char *xtra = o_funcs.xtra_prompt ? o_funcs.xtra_prompt(oid) : "";
 515			const char *pvs = "";
 516
 517			if (visual_list) pvs = ", ENTER to accept";
 518			else if (o_funcs.xattr) pvs = ", 'v' for visuals";
 519
 520
 521
 522			prt(format("<dir>%s%s%s, ESC", pvs, pedit, xtra), hgt - 1, 0);
 523		}
 524
 525		if (do_swap)
 526		{
 527			do_swap = FALSE;
 528			swap(active_menu, inactive_menu);
 529			swap(active_cursor, inactive_cursor);
 530			panel = 1 - panel;
 531		}
 532
 533		if (g_funcs.summary && !visual_list)
 534		{
 535			g_funcs.summary(g_cur, obj_list, g_o_count, g_offset[g_cur],
 536			                object_menu.boundary.row + object_menu.boundary.page_rows,
 537			                object_region.col);
 538		}
 539
 540		menu_refresh(inactive_menu);
 541		menu_refresh(active_menu);
 542
 543		handle_stuff();
 544
 545		if (visual_list)
 546		{
 547			display_visual_list(g_name_len + 3, 7, browser_rows-1,
 548			                             wid - (g_name_len + 3), attr_top, char_left);
 549			place_visual_list_cursor(g_name_len + 3, 7, *o_funcs.xattr(oid), 
 550										*o_funcs.xchar(oid), attr_top, char_left);
 551		}
 552
 553		if (delay)
 554		{
 555			/* Force screen update */
 556			Term_fresh();
 557
 558			/* Delay */
 559			Term_xtra(TERM_XTRA_DELAY, delay);
 560
 561			delay = 0;
 562		}
 563
 564		ke = inkey_ex();
 565
 566		/* Do visual mode command if needed */
 567		if (o_funcs.xattr && o_funcs.xchar &&
 568					visual_mode_command(ke, &visual_list,
 569					browser_rows-1, wid - (g_name_len + 3),
 570					&attr_top, &char_left,
 571					o_funcs.xattr(oid), (byte*)
 572					o_funcs.xchar(oid),
 573					g_name_len + 3, 7, &delay))
 574		{
 575			continue;
 576		}
 577
 578		if (ke.type == EVT_MOUSE)
 579		{
 580			/* Change active panels */
 581			if (region_inside(&inactive_menu->boundary, &ke))
 582			{
 583				swap(active_menu, inactive_menu);
 584				swap(active_cursor, inactive_cursor);
 585				panel = 1-panel;
 586			}
 587		}
 588
 589		ke0 = run_event_loop(&active_menu->target, &ke);
 590		if (ke0.type != EVT_AGAIN) ke = ke0;
 591
 592		switch (ke.type)
 593		{
 594			case EVT_KBRD:
 595			{
 596				break;
 597			}
 598
 599			case ESCAPE:
 600			{
 601				flag = TRUE;
 602				continue;
 603			}
 604
 605			case EVT_SELECT:
 606			{
 607				if (panel == 1 && oid >= 0 && o_cur == active_menu->cursor)
 608				{
 609					o_funcs.lore(oid);
 610					redraw = TRUE;
 611				}
 612			}
 613
 614			case EVT_MOVE:
 615			{
 616				*active_cursor = active_menu->cursor;
 617				continue;
 618			}
 619
 620			case EVT_BACK:
 621			{
 622				if (panel == 1)
 623					do_swap = TRUE;
 624			}
 625
 626			/* XXX Handle EVT_RESIZE */
 627
 628			default:
 629			{
 630				continue;
 631			}
 632		}
 633
 634		switch (ke.key)
 635		{
 636			case ESCAPE:
 637			{
 638				flag = TRUE;
 639				break;
 640			}
 641
 642			case 'R':
 643			case 'r':
 644			{
 645				/* Recall on screen */
 646				if (oid >= 0)
 647					o_funcs.lore(oid);
 648
 649				redraw = TRUE;
 650				break;
 651			}
 652
 653			/* Jump down a page */
 654			case '3':
 655			{
 656				*active_cursor += browser_rows;
 657
 658				if (g_cur >= grp_cnt) g_cur = grp_cnt - 1;
 659				else if (o_cur >= g_o_count) o_cur = g_o_count - 1;
 660
 661				break;
 662			}
 663
 664			/* Jump up a page */
 665			case '9':
 666			{
 667				*active_cursor -= browser_rows;
 668
 669				if (*active_cursor < 0) *active_cursor = 0;
 670
 671				break;
 672			}
 673
 674			default:
 675			{
 676				int d = target_dir(ke.key);
 677
 678				/* Handle key-driven motion between panels */
 679				if (ddx[d] && ((ddx[d] < 0) == (panel == 1)))
 680				{
 681					/* Silly hack -- diagonal arithmetic */
 682					*inactive_cursor += ddy[d];
 683					if (*inactive_cursor < 0) *inactive_cursor = 0;
 684					else if (g_cur >= grp_cnt) g_cur = grp_cnt - 1;
 685					else if (o_cur >= g_o_count) o_cur = g_o_count - 1;
 686					do_swap = TRUE;
 687				}
 688				else if (o_funcs.xtra_act)
 689				{
 690					o_funcs.xtra_act(ke.key, oid);
 691				}
 692
 693				break;
 694			}
 695		}
 696	}
 697
 698	/* Restore roguelike option */
 699	OPT(rogue_like_commands) = omode;
 700
 701	/* Prompt */
 702	if (!grp_cnt)
 703		prt(format("No %s known.", title), 15, 0);
 704
 705	FREE(g_names);
 706	FREE(g_offset);
 707	FREE(g_list);
 708}
 709
 710/*
 711 * Display visuals.
 712 */
 713static void display_visual_list(int col, int row, int height, int width, byte attr_top, byte char_left)
 714{
 715	int i, j;
 716
 717	/* Clear the display lines */
 718	for (i = 0; i < height; i++)
 719			Term_erase(col, row + i, width);
 720
 721	width = logical_width(width);
 722
 723	/* Display lines until done */
 724	for (i = 0; i < height; i++)
 725	{
 726		/* Display columns until done */
 727		for (j = 0; j < width; j++)
 728		{
 729			byte a;
 730			unsigned char c;
 731			int x = col + actual_width(j);
 732			int y = row + actual_width(i);
 733			int ia, ic;
 734
 735			ia = attr_top + i;
 736			ic = char_left + j;
 737
 738			a = (byte)ia;
 739			c = (unsigned char)ic;
 740
 741			/* Display symbol */
 742			big_pad(x, y, a, c);
 743		}
 744	}
 745}
 746
 747
 748/*
 749 * Place the cursor at the collect position for visual mode
 750 */
 751static void place_visual_list_cursor(int col, int row, byte a, byte c, byte attr_top, byte char_left)
 752{
 753	int i = a - attr_top;
 754	int j = c - char_left;
 755
 756	int x = col + actual_width(j);
 757	int y = row + actual_height(i);
 758
 759	/* Place the cursor */
 760	Term_gotoxy(x, y);
 761}
 762
 763
 764/*
 765 *  Do visual mode command -- Change symbols
 766 */
 767static bool visual_mode_command(ui_event_data ke, bool *visual_list_ptr,
 768				int height, int width,
 769				byte *attr_top_ptr, byte *char_left_ptr,
 770				byte *cur_attr_ptr, byte *cur_char_ptr,
 771				int col, int row, int *delay)
 772{
 773	static byte attr_old = 0;
 774	static char char_old = 0;
 775
 776	/* These are the distance we want to maintain between the
 777	 * cursor and borders.
 778	 */
 779	int frame_left = logical_width(10);
 780	int frame_right = logical_width(10);
 781	int frame_top = logical_height(4);
 782	int frame_bottom = logical_height(4);
 783
 784	switch (ke.key)
 785	{
 786		case ESCAPE:
 787		{
 788			if (*visual_list_ptr)
 789			{
 790				/* Cancel change */
 791				*cur_attr_ptr = attr_old;
 792				*cur_char_ptr = char_old;
 793				*visual_list_ptr = FALSE;
 794
 795				return TRUE;
 796			}
 797
 798			break;
 799		}
 800
 801		case '\n':
 802		case '\r':
 803		{
 804			if (*visual_list_ptr)
 805			{
 806				/* Accept change */
 807				*visual_list_ptr = FALSE;
 808				return TRUE;
 809			}
 810
 811			break;
 812		}
 813
 814		case 'V':
 815		case 'v':
 816		{
 817			if (!*visual_list_ptr)
 818			{
 819				*visual_list_ptr = TRUE;
 820
 821				*attr_top_ptr = (byte)MAX(0, (int)*cur_attr_ptr - frame_top);
 822				*char_left_ptr = (char)MAX(0, (int)*cur_char_ptr - frame_left);
 823
 824				attr_old = *cur_attr_ptr;
 825				char_old = *cur_char_ptr;
 826			}
 827			else
 828			{
 829				/* Cancel change */
 830				*cur_attr_ptr = attr_old;
 831				*cur_char_ptr = char_old;
 832				*visual_list_ptr = FALSE;
 833			}
 834
 835			return TRUE;
 836		}
 837
 838		case 'C':
 839		case 'c':
 840		{
 841			/* Set the visual */
 842			attr_idx = *cur_attr_ptr;
 843			char_idx = *cur_char_ptr;
 844
 845			return TRUE;
 846		}
 847
 848		case 'P':
 849		case 'p':
 850		{
 851			if (attr_idx)
 852			{
 853				/* Set the char */
 854				*cur_attr_ptr = attr_idx;
 855				*attr_top_ptr = (byte)MAX(0, (int)*cur_attr_ptr - frame_top);
 856			}
 857
 858			if (char_idx)
 859			{
 860				/* Set the char */
 861				*cur_char_ptr = char_idx;
 862				*char_left_ptr = (char)MAX(0, (int)*cur_char_ptr - frame_left);
 863			}
 864
 865			return TRUE;
 866		}
 867
 868		default:
 869		{
 870			if (*visual_list_ptr)
 871			{
 872				int eff_width = actual_width(width);
 873				int eff_height = actual_height(height);
 874				int d = target_dir(ke.key);
 875				byte a = *cur_attr_ptr;
 876				byte c = *cur_char_ptr;
 877
 878				/* Get mouse movement */
 879				if (ke.key == '\xff')
 880				{
 881					int my = ke.mousey - row;
 882					int mx = ke.mousex - col;
 883
 884					my = logical_height(my);
 885					mx = logical_width(mx);
 886
 887					if ((my >= 0) && (my < eff_height) && (mx >= 0) && (mx < eff_width)
 888						&& ((ke.index) || (a != *attr_top_ptr + my)
 889							|| (c != *char_left_ptr + mx)))
 890					{
 891						/* Set the visual */
 892						*cur_attr_ptr = a = *attr_top_ptr + my;
 893						*cur_char_ptr = c = *char_left_ptr + mx;
 894
 895						/* Move the frame */
 896						if (*char_left_ptr > MAX(0, (int)c - frame_left))
 897							(*char_left_ptr)--;
 898						if (*char_left_ptr + eff_width <= MIN(255, (int)c + frame_right))
 899							(*char_left_ptr)++;
 900						if (*attr_top_ptr > MAX(0, (int)a - frame_top))
 901							(*attr_top_ptr)--;
 902						if (*attr_top_ptr + eff_height <= MIN(255, (int)a + frame_bottom))
 903							(*attr_top_ptr)++;
 904
 905						/* Delay */
 906						*delay = 100;
 907
 908						/* Accept change */
 909						if (ke.index) *visual_list_ptr = FALSE;
 910
 911						return TRUE;
 912					}
 913
 914					/* Cancel change */
 915					else if (ke.index)
 916					{
 917						*cur_attr_ptr = attr_old;
 918						*cur_char_ptr = char_old;
 919						*visual_list_ptr = FALSE;
 920
 921						return TRUE;
 922					}
 923				}
 924				else
 925				{
 926					/* Restrict direction */
 927					if ((a == 0) && (ddy[d] < 0)) d = 0;
 928					if ((c == 0) && (ddx[d] < 0)) d = 0;
 929					if ((a == 255) && (ddy[d] > 0)) d = 0;
 930					if ((c == 255) && (ddx[d] > 0)) d = 0;
 931
 932					a += ddy[d];
 933					c += ddx[d];
 934
 935					/* Set the visual */
 936					*cur_attr_ptr = a;
 937					*cur_char_ptr = c;
 938
 939					/* Move the frame */
 940					if ((ddx[d] < 0) && *char_left_ptr > MAX(0, (int)c - frame_left))
 941						(*char_left_ptr)--;
 942					if ((ddx[d] > 0) && *char_left_ptr + eff_width <=
 943														MIN(255, (int)c + frame_right))
 944					(*char_left_ptr)++;
 945
 946					if ((ddy[d] < 0) && *attr_top_ptr > MAX(0, (int)a - frame_top))
 947						(*attr_top_ptr)--;
 948					if ((ddy[d] > 0) && *attr_top_ptr + eff_height <=
 949													MIN(255, (int)a + frame_bottom))
 950						(*attr_top_ptr)++;
 951
 952					/* We need to always eat the input even if it is clipped,
 953					 * otherwise it will be interpreted as a change object
 954					 * selection command with messy results.
 955					 */
 956					return TRUE;
 957				}
 958			}
 959		}
 960	}
 961
 962	/* Visual mode command is not used */
 963	return FALSE;
 964}
 965
 966
 967/* The following sections implement "subclasses" of the
 968 * abstract classes represented by member_funcs and group_funcs
 969 */
 970
 971/* =================== MONSTERS ==================================== */
 972/* Many-to-many grouping - use default auxiliary join */
 973
 974/*
 975 * Display a monster
 976 */
 977static void display_monster(int col, int row, bool cursor, int oid)
 978{
 979	/* HACK Get the race index. (Should be a wrapper function) */
 980	int r_idx = default_join[oid].oid;
 981
 982	/* Access the race */
 983	monster_race *r_ptr = &r_info[r_idx];
 984	monster_lore *l_ptr = &l_list[r_idx];
 985
 986	/* Choose colors */
 987	byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
 988	byte a = r_ptr->x_attr;
 989	byte c = r_ptr->x_char;
 990
 991	/* Display the name */
 992	c_prt(attr, r_name + r_ptr->name, row, col);
 993
 994#ifdef UNANGBAND
 995	if (use_dbltile || use_trptile)
 996		return;
 997#endif
 998
 999	/* Display symbol */
1000	big_pad(66, row, a, c);
1001
1002	/* Display kills */
1003	if (rf_has(r_ptr->flags, RF_UNIQUE))
1004		put_str(format("%s", (r_ptr->max_num == 0)?  " dead" : "alive"), row, 70);
1005	else
1006		put_str(format("%5d", l_ptr->pkills), row, 70);
1007}
1008
1009
1010static int m_cmp_race(const void *a, const void *b)
1011{
1012	const monster_race *r_a = &r_info[default_join[*(const int *)a].oid];
1013	const monster_race *r_b = &r_info[default_join[*(const int *)b].oid];
1014	int gid = default_join[*(const int *)a].gid;
1015
1016	/* Group by */
1017	int c = gid - default_join[*(const int *)b].gid;
1018	if (c) return c;
1019
1020	/* Order results */
1021	c = r_a->d_char - r_b->d_char;
1022	if (c && gid != 0)
1023	{
1024		/* UNIQUE group is ordered by level & name only */
1025		/* Others by order they appear in the group symbols */
1026		return strchr(monster_group[gid].chars, r_a->d_char)
1027			- strchr(monster_group[gid].chars, r_b->d_char);
1028	}
1029	c = r_a->level - r_b->level;
1030	if (c) return c;
1031
1032	return strcmp(r_name + r_a->name, r_name + r_b->name);
1033}
1034
1035static char *m_xchar(int oid) { return &r_info[default_join[oid].oid].x_char; }
1036static byte *m_xattr(int oid) { return &r_info[default_join[oid].oid].x_attr; }
1037static const char *race_name(int gid) { return monster_group[gid].name; }
1038
1039static void mon_lore(int oid)
1040{
1041	/* Update the monster recall window */
1042	monster_race_track(default_join[oid].oid);
1043	handle_stuff();
1044
1045	/* Save the screen */
1046	screen_save();
1047
1048	/* Describe */
1049	text_out_hook = text_out_to_screen;
1050
1051	/* Recall monster */
1052	roff_top(default_join[oid].oid);
1053	Term_gotoxy(0, 2);
1054	describe_monster(default_join[oid].oid, FALSE);
1055
1056	text_out_c(TERM_L_BLUE, "\n[Press any key to continue]\n");
1057	(void)anykey();
1058
1059	/* Load the screen */
1060	screen_load();
1061}
1062
1063static void mon_summary(int gid, const int *object_list, int n, int top, int row, int col)
1064{
1065	int i;
1066	int kills = 0;
1067
1068	/* Access the race */
1069	for (i = 0; i < n; i++)
1070	{
1071		int oid = default_join[object_list[i+top]].oid;
1072		kills += l_list[oid].pkills;
1073	}
1074
1075	/* Different display for the first item if we've got uniques to show */
1076	if (gid == 0 && rf_has((&r_info[default_join[object_list[0]].oid])->flags, RF_UNIQUE))
1077	{
1078		c_prt(TERM_L_BLUE, format("%d known uniques, %d slain.", n, kills),
1079					row, col);
1080	}
1081	else
1082	{
1083		int tkills = 0;
1084
1085		for (i = 0; i < z_info->r_max; i++)
1086			tkills += l_list[i].pkills;
1087
1088		c_prt(TERM_L_BLUE, format("Creatures slain: %d/%d (in group/in total)", kills, tkills), row, col);
1089	}
1090}
1091
1092static int count_known_monsters(void)
1093{
1094	int m_count = 0;
1095	int i;
1096	size_t j;
1097
1098	for (i = 0; i < z_info->r_max; i++)
1099	{
1100		monster_race *r_ptr = &r_info[i];
1101		if (!OPT(cheat_know) && !l_list[i].sights) continue;
1102		if (!r_ptr->name) continue;
1103
1104		if (rf_has(r_ptr->flags, RF_UNIQUE)) m_count++;
1105
1106		for (j = 1; j < N_ELEMENTS(monster_group) - 1; j++)
1107		{
1108			const char *pat = monster_group[j].chars;
1109			if (strchr(pat, r_ptr->d_char)) m_count++;
1110		}
1111	}
1112
1113	return m_count;
1114}
1115
1116/*
1117 * Display known monsters.
1118 */
1119static void do_cmd_knowledge_monsters(void *obj, const char *name)
1120{
1121	group_funcs r_funcs = {N_ELEMENTS(monster_group), FALSE, race_name,
1122							m_cmp_race, default_group, mon_summary};
1123
1124	member_funcs m_funcs = {display_monster, mon_lore, m_xchar, m_xattr, recall_prompt, 0, 0};
1125
1126	int *monsters;
1127	int m_count = 0;
1128	int i;
1129	size_t j;
1130
1131	(void)obj;
1132	(void)name;
1133
1134	for (i = 0; i < z_info->r_max; i++)
1135	{
1136		monster_race *r_ptr = &r_info[i];
1137		if (!OPT(cheat_know) && !l_list[i].sights) continue;
1138		if (!r_ptr->name) continue;
1139
1140		if (rf_has(r_ptr->flags, RF_UNIQUE)) m_count++;
1141
1142		for (j = 1; j < N_ELEMENTS(monster_group) - 1; j++)
1143		{
1144			const char *pat = monster_group[j].chars;
1145			if (strchr(pat, r_ptr->d_char)) m_count++;
1146		}
1147	}
1148
1149	default_join = C_ZNEW(m_count, join_t);
1150	monsters = C_ZNEW(m_count, int);
1151
1152	m_count = 0;
1153	for (i = 0; i < z_info->r_max; i++)
1154	{
1155		monster_race *r_ptr = &r_info[i];
1156		if (!OPT(cheat_know) && !l_list[i].sights) continue;
1157		if (!r_ptr->name) continue;
1158
1159		for (j = 0; j < N_ELEMENTS(monster_group)-1; j++)
1160		{
1161			const char *pat = monster_group[j].chars;
1162			if (j == 0 && !rf_has(r_ptr->flags, RF_UNIQUE))
1163				continue;
1164			else if (j > 0 && !strchr(pat, r_ptr->d_char))
1165				continue;
1166
1167			monsters[m_count] = m_count;
1168			default_join[m_count].oid = i;
1169			default_join[m_count++].gid = j;
1170		}
1171	}
1172
1173	display_knowledge("monsters", monsters, m_count, r_funcs, m_funcs,
1174			"                   Sym  Kills");
1175	FREE(default_join);
1176	FREE(monsters);
1177}
1178
1179/* =================== ARTIFACTS ==================================== */
1180/* Many-to-one grouping */
1181
1182static void get_artifact_display_name(char *o_name, size_t namelen, int a_idx)
1183{
1184	object_type object_type_body;
1185	object_type *o_ptr = &object_type_body;
1186
1187	/* Make fake artifact */
1188	o_ptr = &object_type_body;
1189	object_wipe(o_ptr);
1190	make_fake_artifact(o_ptr, a_idx);
1191
1192	/* Get its name */
1193	object_desc(o_name, namelen, o_ptr, ODESC_PREFIX | ODESC_BASE | ODESC_SPOIL);
1194}
1195
1196/*
1197 * Display an artifact label
1198 */
1199static void display_artifact(int col, int row, bool cursor, int oid)
1200{
1201	char o_name[80];
1202
1203	/* Choose a color */
1204	byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1205
1206	get_artifact_display_name(o_name, sizeof o_name, oid);
1207
1208	/* Display the name */
1209	c_prt(attr, o_name, row, col);
1210}
1211
1212/*
1213 * Show artifact lore
1214 */
1215static void desc_art_fake(int a_idx)
1216{
1217	object_type *o_ptr;
1218	object_type object_type_body;
1219	bool lost = TRUE, abil = FALSE;
1220	int i, j;
1221	oinfo_detail_t mode = OINFO_NONE;
1222
1223	/* Get local object */
1224	o_ptr = &object_type_body;
1225
1226	/* Wipe the object */
1227	object_wipe(o_ptr);
1228
1229	/* Look for the artifact, either in inventory, store or the object list */
1230	for (i = 0; i < z_info->o_max; i++)
1231	{
1232		if (o_list[i].name1 == a_idx)
1233		{
1234			o_ptr = &o_list[i];
1235			lost = FALSE;
1236			break;
1237		}
1238	}
1239
1240	if (lost)
1241	{
1242		for (i = 0; i < INVEN_TOTAL; i++)
1243		{
1244			if (inventory[i].name1 == a_idx)
1245			{
1246				o_ptr = &inventory[i];
1247				lost = FALSE;
1248				break;
1249			}
1250		}
1251	}
1252
1253	if (lost)
1254	{
1255		for (j = 1; j < (FEAT_SHOP_TAIL - FEAT_SHOP_HEAD + 1); j++)
1256		{
1257			for (i = 0; i < store[j].stock_size; i++)
1258			{
1259				if (store[j].stock[i].name1 == a_idx)
1260				{
1261					o_ptr = &store[j].stock[i];
1262					lost = FALSE;
1263					break;
1264				}
1265			}
1266			if (!lost) break;
1267		}
1268	}
1269
1270	/* If it's been lost, make a fake artifact for it */
1271	if (lost)
1272	{
1273		make_fake_artifact(o_ptr, a_idx);
1274		o_ptr->ident |= IDENT_NAME;
1275
1276		/* Check the history entry, to see if it was fully known before it
1277		 * was lost */
1278		if (history_is_artifact_known(a_idx))
1279			mode = OINFO_FULL;
1280	}
1281
1282	/* Hack -- Handle stuff */
1283	handle_stuff();
1284
1285	text_out_hook = text_out_to_screen;
1286	screen_save();
1287
1288	/* Print the artifact information */
1289	Term_gotoxy(0, 0);
1290	object_info_header(o_ptr);
1291	abil = object_info(o_ptr, mode);
1292	if (lost) text_out("\nThis artifact has been lost.");
1293	if (!abil) text_out("\n\nThis item does not seem to possess any special abilities.");
1294
1295	text_out_c(TERM_L_BLUE, "\n\n[Press any key to continue]\n");
1296	(void)anykey();
1297
1298	screen_load();
1299}
1300
1301static int a_cmp_tval(const void *a, const void *b)
1302{
1303	const artifact_type *a_a = &a_info[*(const int *)a];
1304	const artifact_type *a_b = &a_info[*(const int *)b];
1305
1306	/*group by */
1307	int ta = obj_group_order[a_a->tval];
1308	int tb = obj_group_order[a_b->tval];
1309	int c = ta - tb;
1310	if (c) return c;
1311
1312	/* order by */
1313	c = a_a->sval - a_b->sval;
1314	if (c) return c;
1315	return strcmp(a_name+a_a->name, a_name+a_b->name);
1316}
1317
1318static const char *kind_name(int gid) { return object_text_order[gid].name; }
1319static int art2gid(int oid) { return obj_group_order[a_info[oid].tval]; }
1320
1321/* Check if the given artifact idx is something we should "Know" about */
1322static bool artifact_is_known(int a_idx)
1323{
1324	int i;
1325
1326	if (p_ptr->wizard) return TRUE;
1327
1328	/* Artifact doesn't exist at all, or not created yet */
1329	if (!a_info[a_idx].name || a_info[a_idx].created == FALSE) return FALSE;
1330
1331	/* Check all objects to see if it exists but hasn't been IDed */
1332	for (i = 0; i < z_info->o_max; i++)
1333	{
1334		int a = o_list[i].name1;
1335
1336		/* If we haven't actually sensed the artifact yet */
1337		if (a && a == a_idx && !object_is_known_artifact(&o_list[i]))
1338		{
1339			return FALSE;
1340		}
1341	}
1342
1343    /* Check inventory for the same */
1344	for (i = 0; i < INVEN_TOTAL; i++)
1345	{
1346		object_type *o_ptr = &inventory[i];
1347
1348		/* Ignore non-objects */
1349		if (!o_ptr->k_idx) continue;
1350
1351		if (o_ptr->name1 && o_ptr->name1 == a_idx &&
1352		    !object_is_known_artifact(o_ptr))
1353		{
1354			return FALSE;
1355		}
1356	}
1357
1358	return TRUE;
1359}
1360
1361
1362/* If 'artifacts' is NULL, it counts the number of known artifacts, otherwise
1363   it collects the list of known artifacts into 'artifacts' as well. */
1364static int collect_known_artifacts(int *artifacts, size_t artifacts_len)
1365{
1366	int a_count = 0;
1367	int j;
1368
1369	if (artifacts)
1370		assert(artifacts_len >= z_info->a_max);
1371
1372	for (j = 0; j < z_info->a_max; j++)
1373	{
1374		/* Artifact doesn't exist */
1375		if (!a_info[j].name) continue;
1376
1377		if (OPT(cheat_xtra) || artifact_is_known(j))
1378		{
1379			if (artifacts)
1380				artifacts[a_count++] = j;
1381			else
1382				a_count++;
1383		}
1384	}
1385
1386	return a_count;
1387}
1388
1389/*
1390 * Display known artifacts
1391 */
1392static void do_cmd_knowledge_artifacts(void *obj, const char *name)
1393{
1394	/* HACK -- should be TV_MAX */
1395	group_funcs obj_f = {TV_GOLD, FALSE, kind_name, a_cmp_tval, art2gid, 0};
1396	member_funcs art_f = {display_artifact, desc_art_fake, 0, 0, recall_prompt, 0, 0};
1397
1398	int *artifacts;
1399	int a_count = 0;
1400
1401	(void)obj;
1402	(void)name;
1403
1404	artifacts = C_ZNEW(z_info->a_max, int);
1405
1406	/* Collect valid artifacts */
1407	a_count = collect_known_artifacts(artifacts, z_info->a_max);
1408
1409	display_knowledge("artifacts", artifacts, a_count, obj_f, art_f, NULL);
1410	FREE(artifacts);
1411}
1412
1413/* =================== EGO ITEMS  ==================================== */
1414/* Many-to-many grouping (uses default join) */
1415
1416/* static u16b *e_note(int oid) {return &e_info[default_join[oid].oid].note;} */
1417static const char *ego_grp_name(int gid) { return object_text_order[gid].name; }
1418
1419static void display_ego_item(int col, int row, bool cursor, int oid)
1420{
1421	/* HACK: Access the object */
1422	ego_item_type *e_ptr = &e_info[default_join[oid].oid];
1423
1424	/* Choose a color */
1425	byte attr = curs_attrs[0 != (int)e_ptr->everseen][0 != (int)cursor];
1426
1427	/* Display the name */
1428	c_prt(attr, e_name + e_ptr->name, row, col);
1429}
1430
1431/*
1432 * Describe fake ego item "lore"
1433 */
1434static void desc_ego_fake(int oid)
1435{
1436	/* Hack: dereference the join */
1437	const char *cursed[] = { "permanently cursed", "heavily cursed", "cursed" };
1438	const char *xtra[] = { "sustain", "higher resistance", "ability" };
1439	int i;
1440
1441	int e_idx = default_join[oid].oid;
1442	ego_item_type *e_ptr = &e_info[e_idx];
1443
1444	object_type dummy;
1445	WIPE(&dummy, dummy);
1446
1447	/* Save screen */
1448	screen_save();
1449
1450	/* Set text_out hook */
1451	text_out_hook = text_out_to_screen;
1452
1453	/* Dump the name */
1454	c_prt(TERM_L_BLUE, format("%s %s", ego_grp_name(default_group(oid)),
1455	                                   e_name + e_ptr->name), 0, 0);
1456
1457	/* Begin recall */
1458	Term_gotoxy(0, 1);
1459	text_out("\n");
1460
1461	if (e_ptr->text)
1462	{
1463		int x, y;
1464		text_out("%s", e_text + e_ptr->text);
1465		Term_locate(&x, &y);
1466		Term_gotoxy(0, y+1);
1467	}
1468
1469	/* List ego flags */
1470	dummy.name2 = e_idx;
1471	dummy.tval = e_ptr->tval[0];
1472	object_info(&dummy, OINFO_FULL | OINFO_DUMMY);
1473
1474	if (e_ptr->xtra)
1475		text_out(format("It provides one random %s.", xtra[e_ptr->xtra - 1]));
1476
1477	if (flags_test(e_ptr->flags, OF_SIZE, OF_CURSE_MASK, FLAG_END))
1478	{
1479		if (of_has(e_ptr->flags, OF_PERMA_CURSE))
1480			i = 0;
1481		else if (of_has(e_ptr->flags, OF_PERMA_CURSE))
1482			i = 1;
1483		else
1484			i = 2;
1485
1486		text_out_c(TERM_RED, format("It is %s.", cursed[i]));
1487	}
1488
1489	text_out_c(TERM_L_BLUE, "\n\n[Press any key to continue]\n");
1490	(void)anykey();
1491
1492	screen_load();
1493}
1494
1495/* TODO? Currently ego items will order by e_idx */
1496static int e_cmp_tval(const void *a, const void *b)
1497{
1498	const ego_item_type *ea = &e_info[default_join[*(const int *)a].oid];
1499	const ego_item_type *eb = &e_info[default_join[*(const int *)b].oid];
1500
1501	/* Group by */
1502	int c = default_join[*(const int *)a].gid - default_join[*(const int *)b].gid;
1503	if (c) return c;
1504
1505	/* Order by */
1506	return strcmp(e_name + ea->name, e_name + eb->name);
1507}
1508
1509/*
1510 * Display known ego_items
1511 */
1512static void do_cmd_knowledge_ego_items(void *obj, const char *name)
1513{
1514	group_funcs obj_f =
1515		{TV_GOLD, FALSE, ego_grp_name, e_cmp_tval, default_group, 0};
1516
1517	member_funcs ego_f = {display_ego_item, desc_ego_fake, 0, 0, recall_prompt, 0, 0};
1518
1519	int *egoitems;
1520	int e_count = 0;
1521	int i, j;
1522
1523	(void)obj;
1524	(void)name;
1525
1526	/* HACK: currently no more than 3 tvals for one ego type */
1527	egoitems = C_ZNEW(z_info->e_max * EGO_TVALS_MAX, int);
1528	default_join = C_ZNEW(z_info->e_max * EGO_TVALS_MAX, join_t);
1529
1530	for (i = 0; i < z_info->e_max; i++)
1531	{
1532		if (e_info[i].everseen || OPT(cheat_xtra))
1533		{
1534			for (j = 0; j < EGO_TVALS_MAX && e_info[i].tval[j]; j++)
1535			{
1536				int gid = obj_group_order[e_info[i].tval[j]];
1537
1538				/* Ignore duplicate gids */
1539				if (j > 0 && gid == default_join[e_count - 1].gid) continue;
1540
1541				egoitems[e_count] = e_count;
1542				default_join[e_count].oid = i;
1543				default_join[e_count++].gid = gid;
1544			}
1545		}
1546	}
1547
1548	display_knowledge("ego items", egoitems, e_count, obj_f, ego_f, NULL);
1549
1550	FREE(default_join);
1551	FREE(egoitems);
1552}
1553
1554/* =================== ORDINARY OBJECTS  ==================================== */
1555/* Many-to-one grouping */
1556
1557/*
1558 * Looks up an artifact idx given an object_kind *that's already known
1559 * to be an artifact*.  Behaviour is distinctly unfriendly if passed
1560 * flavours which don't correspond to an artifact.
1561 */
1562static int get_artifact_from_kind(object_kind *k_ptr)
1563{
1564	int i;
1565
1566	assert(of_has(k_ptr->flags, OF_INSTA_ART));
1567
1568	/* Look for the corresponding artifact */
1569	for (i = 0; i < z_info->a_max; i++)
1570	{
1571		if (k_ptr->tval == a_info[i].tval &&
1572		    k_ptr->sval == a_info[i].sval)
1573		{
1574			break;
1575		}
1576	}
1577
1578        assert(i < z_info->a_max);
1579	return i;
1580}
1581
1582/*
1583 * Display the objects in a group.
1584 */
1585static void display_object(int col, int row, bool cursor, int oid)
1586{
1587	int k_idx = oid;
1588
1589	object_kind *k_ptr = &k_info[k_idx];
1590	const char *inscrip = get_autoinscription(oid);
1591
1592	char o_name[80];
1593
1594	/* Choose a color */
1595	bool aware = (!k_ptr->flavor || k_ptr->aware);
1596	byte attr = curs_attrs[(int)aware][(int)cursor];
1597
1598	/* Find graphics bits -- versions of the object_char and object_attr defines */
1599	bool use_flavour = (k_ptr->flavor) && !(aware && k_ptr->tval == TV_SCROLL);
1600
1601	byte a = use_flavour ? flavor_info[k_ptr->flavor].x_attr : k_ptr->x_attr;
1602	byte c = use_flavour ? flavor_info[k_ptr->flavor].x_char : k_ptr->x_char;
1603
1604	/* Display known artifacts differently */
1605	if (of_has(k_ptr->flags, OF_INSTA_ART) && artifact_is_known(get_artifact_from_kind(k_ptr)))
1606	{
1607		get_artifact_display_name(o_name, sizeof(o_name), get_artifact_from_kind(k_ptr));
1608	}
1609	else
1610	{
1611 		object_kind_name(o_name, sizeof(o_name), k_idx, OPT(cheat_know));
1612	}
1613
1614	/* If the type is "tried", display that */
1615	if (k_ptr->tried && !aware)
1616		my_strcat(o_name, " {tried}", sizeof(o_name));
1617
1618	/* Display the name */
1619	c_prt(attr, o_name, row, col);
1620
1621	/* Show squelch status */
1622	if ((aware && kind_is_squelched_aware(k_ptr)) ||
1623		(!aware && kind_is_squelched_unaware(k_ptr)))
1624		c_put_str(attr, "Yes", row, 46);
1625	else if (aware && OPT(squelch_worthless) && !k_ptr->cost)
1626		c_put_str(attr, "Yes*", row, 46);
1627
1628
1629	/* Show autoinscription if around */
1630	if (aware && inscrip)
1631		c_put_str(TERM_YELLOW, inscrip, row, 55);
1632
1633#ifdef UNANGBAND
1634	/* Hack - don't use if double tile */
1635	if (use_dbltile || use_trptile)
1636		return;
1637#endif
1638
1639	/* Display symbol */
1640	big_pad(76, row, a, c);
1641}
1642
1643/*
1644 * Describe fake object
1645 */
1646static void desc_obj_fake(int k_idx)
1647{
1648	object_kind *k_ptr = &k_info[k_idx];
1649	object_type object_type_body;
1650	object_type *o_ptr = &object_type_body;
1651
1652	/* Check for known artifacts, display them as artifacts */
1653	if (of_has(k_ptr->flags, OF_INSTA_ART) && artifact_is_known(get_artifact_from_kind(k_ptr)))
1654	{
1655		desc_art_fake(get_artifact_from_kind(k_ptr));
1656		return;
1657	}
1658
1659	/* Update the object recall window */
1660	track_object_kind(k_idx);
1661	handle_stuff();
1662
1663	/* Wipe the object */
1664	object_wipe(o_ptr);
1665
1666	/* Create the artifact */
1667	object_prep(o_ptr, k_idx, 0, EXTREMIFY);
1668
1669	/* Hack -- its in the store */
1670	if (k_info[k_idx].aware) o_ptr->ident |= (IDENT_STORE);
1671
1672	/* It's fully know */
1673	if (!k_info[k_idx].flavor) object_notice_everything(o_ptr);
1674
1675	/* Hack -- Handle stuff */
1676	handle_stuff();
1677
1678	/* Describe */
1679	text_out_hook = text_out_to_screen;
1680	screen_save();
1681
1682	Term_gotoxy(0,0);
1683	object_info_header(o_ptr);
1684	if (!object_info(o_ptr, OINFO_NONE))
1685		text_out("\n\nThis item does not seem to possess any special abilities.");
1686
1687	text_out_c(TERM_L_BLUE, "\n\n[Press any key to continue]\n");
1688	(void)anykey();
1689
1690	screen_load();
1691}
1692
1693static int o_cmp_tval(const void *a, const void *b)
1694{
1695	const object_kind *k_a = &k_info[*(const int *)a];
1696	const object_kind *k_b = &k_info[*(const int *)b];
1697
1698	/* Group by */
1699	int ta = obj_group_order[k_a->tval];
1700	int tb = obj_group_order[k_b->tval];
1701	int c = ta - tb;
1702	if (c) return c;
1703
1704	/* Order by */
1705	c = k_a->aware - k_b->aware;
1706	if (c) return -c; /* aware has low sort weight */
1707
1708	switch (k_a->tval)
1709	{
1710		case TV_LIGHT:
1711		case TV_MAGIC_BOOK:
1712		case TV_PRAYER_BOOK:
1713		case TV_DRAG_ARMOR:
1714			/* leave sorted by sval */
1715			break;
1716
1717		default:
1718			if (k_a->aware)
1719				return strcmp(k_name + k_a->name, k_name + k_b->name);
1720
1721			/* Then in tried order */
1722			c = k_a->tried - k_b->tried;
1723			if (c) return -c;
1724
1725			return strcmp(flavor_text + flavor_info[k_a->flavor].text,
1726			              flavor_text + flavor_info[k_b->flavor].text);
1727	}
1728
1729	return k_a->sval - k_b->sval;
1730}
1731
1732static int obj2gid(int oid) { return obj_group_order[k_info[oid].tval]; }
1733
1734static char *o_xchar(int oid)
1735{
1736	object_kind *k_ptr = &k_info[oid];
1737
1738	if (!k_ptr->flavor || k_ptr->aware)
1739		return &k_ptr->x_char;
1740	else
1741		return &flavor_info[k_ptr->flavor].x_char;
1742}
1743
1744static byte *o_xattr(int oid)
1745{
1746	object_kind *k_ptr = &k_info[oid];
1747
1748	if (!k_ptr->flavor || k_ptr->aware)
1749		return &k_ptr->x_attr;
1750	else
1751		return &flavor_info[k_ptr->flavor].x_attr;
1752}
1753
1754/*
1755 * Display special prompt for object inscription.
1756 */
1757static const char *o_xtra_prompt(int oid)
1758{
1759	object_kind *k_ptr = &k_info[oid];
1760	s16b idx = get_autoinscription_index(oid);
1761
1762	const char *no_insc = ", 's' to toggle squelch, 'r'ecall, '{'";
1763	const char *with_insc = ", 's' to toggle squelch, 'r'ecall, '{', '}'";
1764
1765
1766	/* Forget it if we've never seen the thing */
1767	if (k_ptr->flavor && !k_ptr->aware)
1768		return "";
1769
1770	/* If it's already inscribed */
1771	if (idx != -1)
1772		return with_insc;
1773
1774	return no_insc;
1775}
1776
1777/*
1778 * Special key actions for object inscription.
1779 */
1780static void o_xtra_act(char ch, int oid)
1781{
1782	object_kind *k_ptr = &k_info[oid];
1783	s16b idx = get_autoinscription_index(oid);
1784
1785	/* Toggle squelch */
1786	if (squelch_tval(k_ptr->tval) && (ch == 's' || ch == 'S'))
1787	{
1788		if (k_ptr->aware)
1789		{
1790			if (kind_is_squelched_aware(k_ptr))
1791				kind_squelch_clear(k_ptr);
1792			else
1793				kind_squelch_when_aware(k_ptr);
1794		}
1795		else
1796		{
1797			if (kind_is_squelched_unaware(k_ptr))
1798				kind_squelch_clear(k_ptr);
1799			else
1800				kind_squelch_when_unaware(k_ptr);
1801		}
1802
1803		return;
1804	}
1805
1806	/* Forget it if we've never seen the thing */
1807	if (k_ptr->flavor && !k_ptr->aware)
1808		return;
1809
1810	/* Uninscribe */
1811	if (ch == '}')
1812	{
1813		if (idx != -1) remove_autoinscription(oid);
1814		return;
1815	}
1816
1817	/* Inscribe */
1818	else if (ch == '{')
1819	{
1820		char note_text[80] = "";
1821
1822		/* Avoid the prompt getting in the way */
1823		screen_save();
1824
1825		/* Prompt */
1826		prt("Inscribe with: ", 0, 0);
1827
1828		/* Default note */
1829		if (idx != -1)
1830			strnfmt(note_text, sizeof(note_text), "%s", get_autoinscription(oid));
1831
1832		/* Get an inscription */
1833		if (askfor_aux(note_text, sizeof(note_text), NULL))
1834		{
1835			/* Remove old inscription if existent */
1836			if (idx != -1)
1837				remove_autoinscription(oid);
1838
1839			/* Add the autoinscription */
1840			add_autoinscription(oid, note_text);
1841
1842			/* Notice stuff (later) */
1843			p_ptr->notice |= (PN_AUTOINSCRIBE);
1844			p_ptr->redraw |= (PR_INVEN | PR_EQUIP);
1845		}
1846
1847		/* Reload the screen */
1848		screen_load();
1849	}
1850}
1851
1852
1853
1854/*
1855 * Display known objects
1856 */
1857void do_cmd_knowledge_objects(void *obj, const char *name)
1858{
1859	group_funcs kind_f = {TV_GOLD, FALSE, kind_name, o_cmp_tval, obj2gid, 0};
1860	member_funcs obj_f = {display_object, desc_obj_fake, o_xchar, o_xattr, o_xtra_prompt, o_xtra_act, 0};
1861
1862	int *objects;
1863	int o_count = 0;
1864	int i;
1865
1866	(void)obj;
1867	(void)name;
1868
1869	objects = C_ZNEW(z_info->k_max, int);
1870
1871	for (i = 0; i < z_info->k_max; i++)
1872	{
1873		if ((k_info[i].everseen || k_info[i].flavor || OPT(cheat_xtra)) &&
1874				!of_has(k_info[i].flags, OF_INSTA_ART))
1875		{
1876			int c = obj_group_order[k_info[i].tval];
1877			if (c >= 0) objects[o_count++] = i;
1878		}
1879	}
1880
1881	display_knowledge("known objects", objects, o_count, kind_f, obj_f, "Squelch  Inscribed          Sym");
1882
1883	FREE(objects);
1884}
1885
1886/* =================== TERRAIN FEATURES ==================================== */
1887/* Many-to-one grouping */
1888
1889/*
1890 * Display the features in a group.
1891 */
1892static void display_feature(int col, int row, bool cursor, int oid )
1893{
1894	/* Get the feature index */
1895	int f_idx = oid;
1896
1897	/* Access the feature */
1898	feature_type *f_ptr = &f_info[f_idx];
1899
1900	/* Choose a color */
1901	byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1902
1903	/* Display the name */
1904	c_prt(attr, f_name + f_ptr->name, row, col);
1905
1906#ifdef UNANGBAND
1907	if (use_dbltile || use_trptile) return;
1908#endif
1909
1910	/* Display symbol */
1911	big_pad(68, row, f_ptr->x_attr, f_ptr->x_char);
1912
1913	/* ILLUMINATION AND DARKNESS GO HERE */
1914
1915}
1916
1917
1918static int f_cmp_fkind(const void *a, const void *b)
1919{
1920	const feature_type *fa = &f_info[*(const int *)a];
1921	const feature_type *fb = &f_info[*(const int *)b];
1922
1923	/* group by */
1924	int c = feat_order(*(const int *)a) - feat_order(*(const int *)b);
1925	if (c) return c;
1926
1927	/* order by feature name */
1928	return strcmp(f_name + fa->name, f_name + fb->name);
1929}
1930
1931static const char *fkind_name(int gid) { return feature_group_text[gid]; }
1932static byte *f_xattr(int oid) { return &f_info[oid].x_attr; }
1933static char *f_xchar(int oid) { return &f_info[oid].x_char; }
1934static void feat_lore(int oid) { (void)oid; /* noop */ }
1935
1936/*
1937 * Interact with feature visuals.
1938 */
1939static void do_cmd_knowledge_features(void *obj, const char *name)
1940{
1941	group_funcs fkind_f = {N_ELEMENTS(feature_group_text), FALSE,
1942							fkind_name, f_cmp_fkind, feat_order, 0};
1943
1944	member_funcs feat_f = {display_feature, feat_lore, f_xchar, f_xattr, 0, 0, 0};
1945
1946	int *features;
1947	int f_count = 0;
1948	int i;
1949
1950	(void)obj;
1951	(void)name;
1952
1953	features = C_ZNEW(z_info->f_max, int);
1954
1955	for (i = 0; i < z_info->f_max; i++)
1956	{
1957		/* Ignore non-features and mimics */
1958		if (f_info[i].name == 0 || f_info[i].mimic != i)
1959			continue;
1960
1961		features[f_count++] = i; /* Currently no filter for features */
1962	}
1963
1964	display_knowledge("features", features, f_count, fkind_f, feat_f,
1965		"                    Sym");
1966	FREE(features);
1967}
1968
1969
1970/* =================== END JOIN DEFINITIONS ================================ */
1971
1972static void do_cmd_knowledge_store(void *obj, const char *name)
1973{
1974	(void)name;
1975	store_knowledge = (int)obj;
1976	do_cmd_store_knowledge();
1977	store_knowledge = STORE_NONE;
1978}
1979
1980static void do_cmd_knowledge_scores(void *obj, const char *name)
1981{
1982	(void)obj;
1983	(void)name;
1984	show_scores();
1985}
1986
1987static void do_cmd_knowledge_history(void *obj, const char *name)
1988{
1989	(void)obj;
1990	(void)name;
1991	history_display();
1992}
1993
1994
1995
1996/*
1997 * Definition of the "player knowledge" menu.
1998 */
1999static menu_item knowledge_actions[] =
2000{
2001{ {0, "Display object knowledge",   	   do_cmd_knowledge_objects,   0}, 'a', 0 },
2002{ {0, "Display artifact knowledge", 	   do_cmd_knowledge_artifacts, 0}, 'b', 0 },
2003{ {0, "Display ego item knowledge", 	   do_cmd_knowledge_ego_items, 0}, 'c', 0 },
2004{ {0, "Display monster knowledge",  	   do_cmd_knowledge_monsters,  0}, 'd', 0 },
2005{ {0, "Display feature knowledge",  	   do_cmd_knowledge_features,  0}, 'e', 0 },
2006{ {0, "Display contents of general store", do_cmd_knowledge_store,
2007	(void*)STORE_GENERAL}, 'f', 0 },
2008{ {0, "Display contents of armourer",      do_cmd_knowledge_store,
2009	(void*)STORE_ARMOR}, 'g', 0 },
2010{ {0, "Display contents of weaponsmith",   do_cmd_knowledge_store,
2011	(void*)STORE_WEAPON}, 'h', 0 },
2012{ {0, "Display contents of temple",   	   do_cmd_knowledge_store,
2013	(void*)STORE_TEMPLE}, 'i', 0 },
2014{ {0, "Display contents of alchemist",     do_cmd_knowledge_store,
2015	(void*)STORE_ALCHEMY}, 'j', 0 },
2016{ {0, "Display contents of magic shop",    do_cmd_knowledge_store,
2017	(void*)STORE_MAGIC}, 'k', 0 },
2018{ {0, "Display contents of black market",  do_cmd_knowledge_store,
2019	(void*)STORE_B_MARKET}, 'l', 0 },
2020{ {0, "Display contents of home",   	   do_cmd_knowledge_store,
2021	(void*)STORE_HOME}, 'm', 0 },
2022{ {0, "Display hall of fame",       	   do_cmd_knowledge_scores,    0}, 'n', 0 },
2023{ {0, "Display character history",  	   do_cmd_knowledge_history,   0}, 'o', 0 },
2024};
2025
2026static menu_type knowledge_menu;
2027
2028
2029
2030
2031
2032
2033/* Keep macro counts happy. */
2034static void cleanup_cmds(void)
2035{
2036	FREE(obj_group_order);
2037}
2038
2039void init_cmd_know(void)
2040{
2041	/* Initialize the menus */
2042	menu_type *menu = &knowledge_menu;
2043	WIPE(menu, menu_type);
2044	menu->title = "Display current knowledge";
2045	menu->menu_data = knowledge_actions;
2046	menu->count = N_ELEMENTS(knowledge_actions),
2047	menu_init(menu, MN_SKIN_SCROLL, find_menu_iter(MN_ITER_ITEMS), &SCREEN_REGION);
2048
2049	/* initialize other static variables */
2050	if (!obj_group_order)
2051	{
2052		int i;
2053		int gid = -1;
2054
2055		obj_group_order = C_ZNEW(TV_GOLD + 1, int);
2056		atexit(cleanup_cmds);
2057
2058		/* Allow for missing values */
2059		for (i = 0; i <= TV_GOLD; i++)
2060			obj_group_order[i] = -1;
2061
2062		for (i = 0; 0 != object_text_order[i].tval; i++)
2063		{
2064			if (object_text_order[i].name) gid = i;
2065			obj_group_order[object_text_order[i].tval] = gid;
2066		}
2067	}
2068}
2069
2070
2071
2072
2073/*
2074 * Display the "player knowledge" menu.
2075 */
2076void do_cmd_knowledge(void)
2077{
2078	int cursor = 0;
2079	int i;
2080	ui_event_data c = EVENT_EMPTY;
2081	region knowledge_region = { 0, 0, -1, 18 };
2082
2083	/* Grey out menu items that won't display anything */
2084	if (collect_known_artifacts(NULL, 0) > 0)
2085		knowledge_actions[1].flags = 0;
2086	else
2087		knowledge_actions[1].flags = MN_GREYED;
2088
2089	knowledge_actions[2].flags = MN_GREYED;
2090	for (i = 0; i < z_info->e_max; i++)
2091	{
2092		if (e_info[i].everseen || OPT(cheat_xtra))
2093		{
2094			knowledge_actions[2].flags = 0;
2095			break;
2096		}
2097	}
2098
2099	if (count_known_monsters() > 0)
2100		knowledge_actions[3].flags = 0;
2101	else
2102		knowledge_actions[3].flags = MN_GREYED;
2103
2104	screen_save();
2105	menu_layout(&knowledge_menu, &knowledge_region);
2106
2107	while (c.key != ESCAPE)
2108	{
2109		clear_from(0);
2110		c = menu_select(&knowledge_menu, &cursor, 0);
2111	}
2112
2113	screen_load();
2114}
2115
2116