root/trunk/src/cmd4.c

Revision 169, 88.6 kB (checked in by takkaria, 3 years ago)

Fix some menu bugs (#128):

  • Options submenus should use a, b, c etc.
  • Macro menu doesn't have the right indentation.
  • Birthscreen menu for race/class has cursor in wrong place (should be on the grid of the selected menu item).
Line 
1 /*
2  * File: cmd4.c
3  * Purpose: Various kinds of browsing functions.
4  *
5  * Copyright (c) 1997-2007 Robert A. Koeneke, James E. Wilson, Ben Harrison,
6  * Eytan Zweig, Andrew Doull, Pete Mack.
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 "ui.h"
21
22
23 /*
24  * Code cleanup -- Pete Mack 02/2007
25  * Use proper function tables and database methodology.
26  * Tables are now tables, not multiline conditionals.
27  * Joins are now relational, not ad hoc.
28  * Function tables are used for iteration where reasonable. (C-style class model)
29  */
30
31
32 /* Flag value for missing array entry */
33 #define MISSING -17
34
35 #define APP_MACRO       101
36 #define ASK_MACRO       103
37 #define DEL_MACRO       104
38 #define NEW_MACRO       105
39 #define APP_KEYMAP      106
40 #define ASK_KEYMAP      107
41 #define DEL_KEYMAP      108
42 #define NEW_KEYMAP      109
43 #define ENTER_ACT       110
44 #define LOAD_PREF       111
45 #define DUMP_MON        112
46 #define DUMP_OBJ        113
47 #define DUMP_FEAT       114
48 #define DUMP_FLAV       115
49 #define MOD_MON         116
50 #define MOD_OBJ         117
51 #define MOD_FEAT        118
52 #define MOD_FLAV        119
53 #define DUMP_COL        120
54 #define MOD_COL         121
55 #define RESET_VIS       122
56
57 #define OPTION_MENU 140
58 #define VISUAL_MENU 141
59 #define COLOR_MENU      142
60 #define KNOWLEDGE_MENU 143
61 #define MACRO_MENU      144
62
63
64 #define INFO_SCREENS 2 /* Number of screens in character info mode */
65
66
67
68 typedef struct
69 {
70                 int maxnum; /* Maximum possible item count for this class */
71                 bool easy_know; /* Items don't need to be IDed to recognize membership */
72
73                 const char *(*name)(int gid); /* name of this group */
74
75                 /* Compare, in group and display order */
76                 /* Optional, if already sorted */
77                 int (*gcomp)(const void *, const void *); /* Compare GroupIDs of two oids */
78                 int (*group)(int oid); /* Group ID for of an oid */
79                 bool (*aware)(object_type *obj); /* Object is known sufficiently for group */
80
81                 /* summary function for the "object" information. */
82                 void (*summary)(int gid, const int *object_list, int n, int top, int row, int col);
83 } group_funcs;
84
85 typedef struct
86 {
87
88                 /* Print a tabular-formatted description for an oid */
89                 /* This includes things like kill-count, the current graphic, */
90                 /* any tile illumination info. Item id for Wizard mode and */
91                 /* autoinscription are handled by the caller */
92                 void (*display_member)(int col, int row, bool cursor, int oid);
93
94                 void (*lore)(int oid); /* Dump known lore to screen*/
95
96
97                 /* Required only for objects with modifiable display attributes */
98                 /* Unknown 'flavors' return flavor attributes */
99                 char *(*xchar)(int oid); /* get character attr for OID (by address) */
100                 byte *(*xattr)(int oid); /* get color attr for OID (by address) */
101
102                 /* Required only for manipulable (ordinary) objects */
103                 /* Address of inscription.  Unknown 'flavors' return null  */
104                 u16b *(*note)(int oid);
105
106                 /* extra context for display of members */
107                 bool is_visual;
108
109
110 } member_funcs;
111
112
113 /* Helper class for generating joins */
114 typedef struct join
115 {
116                 int oid;
117                 int gid;
118 } join_t;
119
120 /* A default group-by */
121 static join_t *default_join;
122 static int default_join_cmp(const void *a, const void *b)
123 {
124                 join_t *ja = &default_join[*(int*)a];
125                 join_t *jb = &default_join[*(int*)b];
126                 int c = ja->gid - jb->gid;
127                 if (c) return c;
128                 return ja->oid - jb->oid;
129 }
130 static int default_group(int oid) { return default_join[oid].gid; }
131
132
133 static int *obj_group_order;
134 /*
135  * Description of each monster group.
136  */
137 static struct
138 {
139                 cptr chars;
140                 cptr name;
141 } monster_group[] =
142 {
143         { (cptr)-1,     "Uniques" },
144         { "A",          "Angels" },
145         { "a",          "Ants" },
146         { "b",          "Bats" },
147         { "B",          "Birds" },
148         { "C",          "Canines" },
149         { "c",          "Centipedes" },
150         { "uU",         "Demons" },
151         { "dD",         "Dragons" },
152         { "vE",         "Elementals/Vortices" },
153         { "e",          "Eyes/Beholders" },
154         { "f",          "Felines" },
155         { "G",          "Ghosts" },
156         { "OP",         "Giants/Ogres" },
157         { "g",          "Golems" },
158         { "H",          "Harpies/Hybrids" },
159         { "h",          "Hominids (Elves, Dwarves)" },
160         { "H",          "Hydras" },
161         { "i",          "Icky Things" },
162         { "FI",         "Insects" },
163         { "j",          "Jellies" },
164         { "K",          "Killer Beetles" },
165         { "k",          "Kobolds" },
166         { "L",          "Lichs" },
167         { "tp",         "Men" },
168         { "$?!_",       "Mimics" },
169         { "m",          "Molds" },
170         { ",",          "Mushroom Patches" },
171         { "n",          "Nagas" },
172         { "o",          "Orcs" },
173         { "q",          "Quadrupeds" },
174         { "Q",          "Quylthulgs" },
175         { "R",          "Reptiles/Amphibians" },
176         { "r",          "Rodents" },
177         { "S",          "Scorpions/Spiders" },
178         { "s",          "Skeletons/Drujs" },
179         { "J",          "Snakes" },
180         { "T",          "Trolls" },
181         { "V",          "Vampires" },
182         { "W",          "Wights/Wraiths" },
183         { "w",          "Worms/Worm Masses" },
184         { "X",          "Xorns/Xarens" },
185         { "Y",          "Yeti" },
186         { "Z",          "Zephyr Hounds" },
187         { "z",          "Zombies" },
188         { NULL,         NULL }
189 };
190
191 /*
192  * Description of each feature group.
193  */
194 const char *feature_group_text[] =
195 {
196         "Floors",
197         "Traps",
198         "Doors",
199         "Stairs",
200         "Walls",
201         "Streamers",
202         "Obstructions",
203         "Stores",
204         "Other",
205         NULL
206 };
207
208
209
210 /* Useful method declarations */
211 static void display_visual_list(int col, int row, int height, int width,
212                                 byte attr_top, char char_left);
213
214 static bool visual_mode_command(event_type ke, bool *visual_list_ptr,
215                                 int height, int width,
216                                 byte *attr_top_ptr, char *char_left_ptr,
217                                 byte *cur_attr_ptr, char *cur_char_ptr,
218                                 int col, int row, int *delay);
219
220 static void place_visual_list_cursor(int col, int row, byte a,
221                                 byte c, byte attr_top, byte char_left);
222
223 static void dump_pref_file(void (*dump)(FILE*), const char *title);
224
225 /*
226  *  Clipboard variables for copy&paste in visual mode
227  */
228 static byte attr_idx = 0;
229 static byte char_idx = 0;
230
231 /*
232  * Return a specific ordering for the features
233  */
234 int feat_order(int feat)
235 {
236         feature_type *f_ptr = &f_info[feat];
237         switch(f_ptr->d_char)
238         {
239                 case '.':                               return 0;
240                 case '^':                               return 1;
241                 case '\'': case '+':    return 2;
242                 case '<': case '>':             return 3;
243                 case '#':                               return 4;
244                 case '*': case '%' :    return 5;
245                 case ';': case ':' :    return 6;
246
247                 default:
248                         if (isdigit(f_ptr->d_char)) return 7;
249                         return 8;
250         }
251 }
252
253
254 /* HACK */
255 static const int use_dbltile = 0;
256 static const int use_trptile = 0;
257
258 /* Emit a 'graphical' symbol and a padding character if appropriate */
259 static void big_pad(int col, int row, byte a, byte c)
260 {
261         Term_putch(col, row, a, c);
262         if (!use_bigtile) return;
263         if (a &0x80) Term_putch(col+1, row, 255, -1);
264         else Term_putch(col+1, row, 1, ' ');
265 }
266
267 static int actual_width(int width) {
268         if (use_trptile) width = width * 3;
269         else if (use_dbltile) width *= 2;
270         if (use_bigtile) width *= 2;
271                 return width;
272 }
273
274 static int actual_height(int height) {
275         if (use_bigtile) height *= 2;
276         if (use_trptile) height = height * 3 / 2;
277         else if (use_dbltile) height *= 2;
278         return height;
279 }
280
281 static int logical_width(int width)
282 {
283         int div = 1;
284         if (use_trptile) div = 3;
285         else if (use_dbltile) div *= 2;
286         if (use_bigtile) div *= 2;
287         return width / div;
288 }
289
290 static int logical_height(int height)
291 {
292         int div = 1;
293         if (use_trptile)
294         {
295                 height *= 2;
296                 div = 3;
297         }
298         else if (use_dbltile) div = 2;
299         if (use_bigtile) div *= 2;
300         return height / div;
301 }
302
303 /*
304  * Interact with inscriptions.
305  * Create a copy of an existing quark, except if the quark has '=x' in it,
306  * If an quark has '=x' in it remove it from the copied string, otherwise append it where 'x' is ch.
307  * Return the new quark location.
308  */
309 static int auto_note_modify(int note, char ch)
310 {
311         char tmp[80];
312
313         cptr s;
314
315         /* Paranoia */
316         if (!ch) return(note);
317
318         /* Null length string to start */
319         tmp[0] = '\0';
320
321         /* Inscription */
322         if (note)
323         {
324
325                 /* Get the inscription */
326                 s = quark_str(note);
327
328                 /* Temporary copy */
329                 my_strcpy(tmp,s,80);
330
331                 /* Process inscription */
332                 while (s)
333                 {
334
335                         /* Auto-pickup on "=g" */
336                         if (s[1] == ch)
337                         {
338
339                                 /* Truncate string */
340                                 tmp[strlen(tmp)-strlen(s)] = '\0';
341
342                                 /* Overwrite shorter string */
343                                 my_strcat(tmp,s+2,80);
344
345                                 /* Create quark */
346                                 return(quark_add(tmp));
347                         }
348
349                         /* Find another '=' */
350                         s = strchr(s + 1, '=');
351                 }
352         }
353
354         /* Append note */
355         my_strcat(tmp,format("=%c",ch),80);
356
357         /* Create quark */
358         return(quark_add(tmp));
359 }
360
361
362 static void display_group_member(menu_type *menu, int oid,
363                                                 bool cursor, int row, int col, int wid)
364 {
365         member_funcs *o_funcs = (member_funcs*) menu->menu_data;
366         byte attr = curs_attrs[CURS_KNOWN][cursor == oid];
367
368         /* Show inscription, if applicable, aware and existing */
369         if (o_funcs->note && o_funcs->note(oid) && *o_funcs->note(oid))
370         {
371                 c_put_str(TERM_YELLOW,quark_str(*o_funcs->note(oid)), row, 65);
372         }
373         /* Print the interesting part */
374         o_funcs->display_member(col, row, cursor, oid);
375
376         if (p_ptr->wizard) c_put_str(attr, format("%d", oid), row, 60);
377
378         /* Do visual mode */
379         if (o_funcs->is_visual && o_funcs->xattr)
380         {
381                 char c = *o_funcs->xchar(oid);
382                 byte a = *o_funcs->xattr(oid);
383                 c_put_str(attr, format((c & 0x80) ? "%02x/%02x" : "%02x/%d", a, c), row, 60);
384         }
385 }
386
387
388 #define swap(a, b) (swapspace = (void*)(a)), ((a) = (b)), ((b) = swapspace)
389
390 /*
391  * Interactive group by.
392  * Recognises inscriptions, graphical symbols, lore
393  */
394 static void display_knowledge(const char *title, int *obj_list, int o_count,
395                                 group_funcs g_funcs, member_funcs o_funcs,
396                                 const char *otherfields)
397 {
398         /* maximum number of groups to display */
399         int max_group = g_funcs.maxnum < o_count ? g_funcs.maxnum : o_count ;
400
401         /* This could (should?) be (void **) */
402         int *g_list, *g_offset;
403
404         const char **g_names;
405
406         int g_name_len = 8;  /* group name length, minumum is 8 */
407
408         int grp_cnt = 0; /* total number groups */
409
410         int g_cur = 0, grp_old = -1; /* group list positions */
411         int o_cur = 0;                                  /* object list positions */
412         int g_o_count = 0;                               /* object count for group */
413         int oid = -1;                           /* object identifiers */
414
415         region title_area = { 0, 0, 0, 4 };
416         region group_region = { 0, 6, MISSING, -2 };
417         region object_region = { MISSING, 6, 0, -2 };
418
419         /* display state variables */
420         bool visual_list = FALSE;
421         byte attr_top = 0;
422         char char_left = 0;
423         int note_idx = 0;
424
425         int delay = 0;
426
427         menu_type group_menu;
428         menu_type object_menu;
429         menu_iter object_iter;
430
431         /* Panel state */
432         /* These are swapped in parallel whenever the actively browsing " */
433         /* changes */
434         int *active_cursor = &g_cur, *inactive_cursor = &o_cur;
435         menu_type *active_menu = &group_menu, *inactive_menu = &object_menu;
436         int panel = 0;
437
438         void *swapspace;
439         bool do_swap = FALSE;
440
441         bool flag = FALSE;
442         bool redraw = TRUE;
443
444         int browser_rows;
445         int wid, hgt;
446         int i;
447         int prev_g = -1;
448
449         int omode = rogue_like_commands;
450
451         /* Get size */
452         Term_get_size(&wid, &hgt);
453         browser_rows = hgt - 8;
454
455         /* Do the group by. ang_sort only works on (void **) */
456         /* Maybe should make this a precondition? */
457         if (g_funcs.gcomp)
458                         qsort(obj_list, o_count, sizeof(*obj_list), g_funcs.gcomp);
459
460         C_MAKE(g_list, max_group+1, int);
461         C_MAKE(g_offset, max_group+1, int);
462
463         for (i = 0; i < o_count; i++)
464         {
465                 if (prev_g != g_funcs.group(obj_list[i]))
466                 {
467                         prev_g = g_funcs.group(obj_list[i]);
468                         g_offset[grp_cnt] = i;
469                         g_list[grp_cnt++] = prev_g;
470                 }
471         }
472         g_offset[grp_cnt] = o_count;
473         g_list[grp_cnt] = -1;
474
475         /* The compact set of group names, in display order */
476         C_MAKE(g_names, grp_cnt, const char **);
477         for (i = 0; i < grp_cnt; i++)
478         {
479                 int len;
480                 g_names[i] = g_funcs.name(g_list[i]);
481                 len = strlen(g_names[i]);
482                 if (len > g_name_len) g_name_len = len;
483         }
484         /* Reasonable max group name len */
485         if (g_name_len >= 20) g_name_len = 20;
486
487         object_region.col = g_name_len+3;
488         group_region.width = g_name_len;
489
490         /* Disable the roguelike commands for the duration */
491         rogue_like_commands = FALSE;
492
493         /* Leave room for the group summary information */
494         if (g_funcs.summary) object_region.page_rows = -3;
495
496         WIPE(&group_menu, menu_type);
497         group_menu.count = grp_cnt;
498         group_menu.cmd_keys = "\n\r6\x8C"/* Ignore these as menu commands */
499         group_menu.menu_data = g_names;
500
501         WIPE(&object_menu, menu_type);
502         object_menu.menu_data = &o_funcs;
503         WIPE(&object_iter, object_iter);
504         object_iter.display_row = display_group_member;
505
506         o_funcs.is_visual = FALSE;
507
508         menu_init(&group_menu, MN_SCROLL, MN_STRING, &group_region);
509         menu_init2(&object_menu, find_menu_skin(MN_SCROLL), &object_iter, &object_region);
510
511
512         /* This is the event loop for a multi-region panel */
513         /* Panels are -- text panels, two menus, and visual browser */
514         /* with "pop-up menu" for lore */
515         while ((!flag) && (grp_cnt))
516         {
517                 event_type ke, ke0;
518                 if (redraw)
519                 {
520                         region_erase(&title_area);
521                         prt(format("Knowledge - %s", title), 2, 0);
522                         prt( "Group", 4, 0);
523                         prt("Name", 4, g_name_len + 3);
524                         Term_gotoxy(65, 4);
525                         if (o_funcs.note)
526                                 Term_addstr(-1, TERM_WHITE, "Inscribed ");
527                         if (otherfields)
528                                 Term_addstr(-1, TERM_WHITE, otherfields);
529                         for (i = 0; i < 78; i++)
530                                 Term_putch(i, 5, TERM_WHITE, '=');
531                         for (i = 0; i < browser_rows; i++)
532                                 Term_putch(g_name_len + 1, 6 + i, TERM_WHITE, '|');
533
534                         redraw = FALSE;
535                 }
536
537                 if (g_cur != grp_old)
538                 {
539                         grp_old = g_cur;
540                         o_cur = 0;
541                         g_o_count = g_offset[g_cur+1] - g_offset[g_cur];
542                         menu_set_filter(&object_menu, obj_list + g_offset[g_cur], g_o_count);
543                         group_menu.cursor = g_cur;
544                         object_menu.cursor = 0;
545                 }
546                 /* HACK ... */
547                 if (!visual_list)
548                 {
549                         /* ... The object menu may be browsing the entire group... */
550                         o_funcs.is_visual = FALSE;
551                         menu_set_filter(&object_menu, obj_list + g_offset[g_cur], g_o_count);
552                         object_menu.cursor = o_cur;
553                 }
554                 else
555                 {
556                         /* ... or just a single element in the group. */
557                         o_funcs.is_visual = TRUE;
558                         menu_set_filter(&object_menu, obj_list + o_cur +g_offset[g_cur], 1);
559                         object_menu.cursor = 0;
560                 }
561                 oid = obj_list[g_offset[g_cur]+o_cur];
562                 /* Prompt */
563                 {
564                         const char *pedit = (!o_funcs.xattr) ? "" :
565                                         (!(attr_idx|char_idx) ? ", 'c' to copy" : ", 'c', 'p' to paste");
566
567                         const char *pnote = (!o_funcs.note || !o_funcs.note(oid)) ?
568                                                         "" : ((note_idx) ? ", '\\' to re-inscribe"  :  "");
569
570                         const char *pnote1 = (!o_funcs.note || !o_funcs.note(oid)) ?
571                                                                         "" : ", '{', '}', 'k', 'g', ...";
572
573                         const char *pvs = (!o_funcs.xattr) ? "" : ", 'v' for visuals";
574
575                         if (visual_list)
576                                 prt(format("<dir>, 'r' to recall, ENTER to accept%s, ESC", pedit), hgt-1, 0);
577                         else
578                                 prt(format("<dir>, 'r' to recall%s ESC%s%s%s",
579                                                                                 pvs, pedit, pnote, pnote1), hgt-1, 0);
580                 }
581                 if (do_swap)
582                 {
583                         do_swap = FALSE;
584                         swap(active_menu, inactive_menu);
585                         swap(active_cursor, inactive_cursor);
586                         panel = 1-panel;
587                 }
588
589                 if (g_funcs.summary && !visual_list)
590                         g_funcs.summary(g_cur, obj_list, g_o_count, g_offset[g_cur],
591                         object_menu.boundary.row + object_menu.boundary.page_rows, object_region.col);
592                 menu_refresh(inactive_menu);
593                 menu_refresh(active_menu);
594                 handle_stuff();
595
596                 if (visual_list)
597                 {
598        
599                         display_visual_list(g_name_len + 3, 7, browser_rows-1,
600                                    wid - (g_name_len + 3), attr_top, char_left);
601                         place_visual_list_cursor(g_name_len + 3, 7, *o_funcs.xattr(oid),
602                                                                                 *o_funcs.xchar(oid), attr_top, char_left);
603                 }
604
605                 if (delay)
606                 {
607                         /* Force screen update */
608                         Term_fresh();
609
610                         /* Delay */
611                         Term_xtra(TERM_XTRA_DELAY, delay);
612
613                         delay = 0;
614                 }
615
616                 ke = inkey_ex();
617                 /* Do visual mode command if needed */
618                 if (o_funcs.xattr && o_funcs.xchar &&
619                                         visual_mode_command(ke, &visual_list,
620                                         browser_rows-1, wid - (g_name_len + 3),
621                                         &attr_top, &char_left,
622                                         o_funcs.xattr(oid), o_funcs.xchar(oid),
623                                         g_name_len + 3, 7, &delay))
624                 {
625                         continue;
626                 }
627
628                 if (ke.type == EVT_MOUSE)
629                 {
630                         /* Change active panels */
631                         if (region_inside(&inactive_menu->boundary, &ke))
632                         {
633                                 swap(active_menu, inactive_menu);
634                                 swap(active_cursor, inactive_cursor);
635                                 panel = 1-panel;
636                         }
637                 }
638                 ke0 = run_event_loop(&active_menu->target, 0, &ke);
639                 if (ke0.type != EVT_AGAIN) ke = ke0;
640                 switch(ke.type) {
641                         case EVT_KBRD:
642                                 break;
643                         case ESCAPE:
644                                 flag = TRUE;
645                                 continue;
646                         case EVT_SELECT:
647                                 if (panel == 1 && oid >= 0 && o_cur == active_menu->cursor)
648                                 {
649                                         o_funcs.lore(oid);
650                                         redraw = TRUE;
651                                 }
652                         case EVT_MOVE:
653                                 *active_cursor = active_menu->cursor;
654                                 continue;
655                         case EVT_BACK:
656                                 if (panel == 1)
657                                         do_swap = TRUE;
658                         default:
659                                 continue;
660                 }
661                 switch (ke.key)
662                 {
663
664                         case ESCAPE:
665                         {
666                                 flag = TRUE;
667                                 break;
668                         }
669
670                         case 'R':
671                         case 'r':
672                         {
673                                 /* Recall on screen */
674                                 if (oid >= 0)
675                                         o_funcs.lore(oid);
676
677                                 redraw = TRUE;
678                                 break;
679                         }
680
681                         /* HACK: make this a function.  Replace g_funcs.aware() */
682                         case '{':
683                         case '}':
684                         case '\\':
685                                 /* precondition -- valid object */
686                                 if (o_funcs.note == NULL || o_funcs.note(oid) == 0)
687                                                 break;
688                         {
689                                 char note_text[80] = "";
690                                 u16b *note = o_funcs.note(oid);
691                                 u16b old_note = *note;
692
693                                 if (ke.key == '{')
694                                 {
695                                         /* Prompt */
696                                         prt("Inscribe with: ", hgt, 0);
697
698                                         /* Default note */
699                                         if (old_note)
700                                                 strnfmt(note_text, sizeof note_text, "%s", quark_str(old_note));
701
702                                         /* Get a filename */
703                                         if (!askfor_aux(note_text, sizeof note_text, NULL)) continue;
704
705                                                 /* Set the inscription */
706                                                 *note = quark_add(note_text);
707
708                                         /* Set the current default note */
709                                         note_idx = *note;
710                                 }
711                                 else if (ke.key == '}')
712                                 {
713                                         *note = 0;
714                                 }
715                                 else
716                                 {
717                                         /* '\\' */
718                                         *note = note_idx;
719                                 }
720
721                                 /* Process existing objects */
722                                 for (i = 1; i < o_max; i++)
723                                 {
724                                         /* Get the object */
725                                         object_type *i_ptr = &o_list[i];
726
727                                         /* Skip dead or differently sourced items */
728                                         if (!i_ptr->k_idx || i_ptr->note != old_note) continue;
729
730                                         /* Not matching item */
731                                         if (!g_funcs.group(oid) != i_ptr->tval) continue;
732
733                                         /* Auto-inscribe */
734                                         if (g_funcs.aware(i_ptr) || cheat_peek)
735                                                 i_ptr->note = note_idx;
736                                 }
737
738                                 break;
739                         }
740
741                         default:
742                         {
743                                 int d = target_dir(ke.key);
744                                 /* Handle key-driven motion between panels */
745                                 if (ddx[d] && ((ddx[d] < 0) == (panel == 1)))
746                                 {
747                                         /* Silly hack -- diagonal arithmetic */
748                                         *inactive_cursor += ddy[d];
749                                         if (*inactive_cursor < 0) *inactive_cursor = 0;
750                                         else if (g_cur >= grp_cnt) g_cur = grp_cnt -1;
751                                         else if (o_cur >= g_o_count) o_cur = g_o_count-1;
752                                         do_swap = TRUE;
753                                 }
754                                 else if (o_funcs.note && o_funcs.note(oid))
755                                 {
756                                         note_idx = auto_note_modify(*o_funcs.note(oid), ke.key);
757                                         *o_funcs.note(oid) = note_idx;
758                                 }
759                                 break;
760                         }
761                 }
762         }
763
764         rogue_like_commands = omode;
765
766         /* Prompt */
767         if (!grp_cnt)
768                 prt(format("No %s known.", title), 15, 0);
769
770         FREE(g_names);
771         FREE(g_offset);
772         FREE(g_list);
773 }
774
775 /*
776  * Display visuals.
777  */
778 static void display_visual_list(int col, int row, int height, int width, byte attr_top, char char_left)
779 {
780                 int i, j;
781
782                 /* Clear the display lines */
783                 for (i = 0; i < height; i++)
784                 {
785                                 Term_erase(col, row + i, width);
786                 }
787
788                 width = logical_width(width);
789
790                 /* Display lines until done */
791                 for (i = 0; i < height; i++)
792                 {
793                                 /* Display columns until done */
794                                 for (j = 0; j < width; j++)
795                                 {
796                                                 byte a;
797                                                 char c;
798                                                 int x = col + actual_width(j);
799                                                 int y = row + actual_width(i);
800                                                 int ia, ic;
801
802                                                 ia = attr_top + i;
803                                                 ic = char_left + j;
804
805                                                 a = (byte)ia;
806                                                 c = (char)ic;
807
808                                                 /* Display symbol */
809                                                 big_pad(x, y, a, c);
810                                 }
811                 }
812 }
813
814
815 /*
816  * Place the cursor at the collect position for visual mode
817  */
818 static void place_visual_list_cursor(int col, int row, byte a, byte c, byte attr_top, byte char_left)
819 {
820         int i = a - attr_top;
821         int j = c - char_left;
822
823         int x = col + actual_width(j);
824         int y = row + actual_height(i);
825
826         /* Place the cursor */
827         Term_gotoxy(x, y);
828 }
829
830
831 /*
832  *  Do visual mode command -- Change symbols
833  */
834 static bool visual_mode_command(event_type ke, bool *visual_list_ptr,
835                                 int height, int width,
836                                 byte *attr_top_ptr, char *char_left_ptr,
837                                 byte *cur_attr_ptr, char *cur_char_ptr,
838                                 int col, int row, int *delay)
839 {
840         static byte attr_old = 0;
841         static char char_old = 0;
842
843         int frame_left = logical_width(10);
844         int frame_right = logical_width(10);
845         int frame_top = logical_height(4);
846         int frame_bottom = logical_height(4);
847
848         switch (ke.key)
849         {
850                 case ESCAPE:
851                         if (*visual_list_ptr)
852                         {
853                                 /* Cancel change */
854                                 *cur_attr_ptr = attr_old;
855                                 *cur_char_ptr = char_old;
856                                 *visual_list_ptr = FALSE;
857
858                                 return TRUE;
859                         }
860                         break;
861
862                 case '\n':
863                 case '\r':
864                         if (*visual_list_ptr)
865                         {
866                                 /* Accept change */
867                                 *visual_list_ptr = FALSE;
868
869                                 return TRUE;
870                         }
871                         break;
872
873                 case 'V':
874                 case 'v':
875                         if (!*visual_list_ptr)
876                         {
877                                 *visual_list_ptr = TRUE;
878
879                                 *attr_top_ptr = (byte)MAX(0, (int)*cur_attr_ptr - frame_top);
880                                 *char_left_ptr = (char)MAX(-128, (int)*cur_char_ptr - frame_left);
881
882                                 attr_old = *cur_attr_ptr;
883                                 char_old = *cur_char_ptr;
884
885                                 return TRUE;
886                         }
887                         else
888                         {
889                                 /* Cancel change */
890                                 *cur_attr_ptr = attr_old;
891                                 *cur_char_ptr = char_old;
892                                 *visual_list_ptr = FALSE;
893
894                                 return TRUE;
895                         }
896                         break;
897
898                 case 'C':
899                 case 'c':
900                         /* Set the visual */
901                         attr_idx = *cur_attr_ptr;
902                         char_idx = *cur_char_ptr;
903
904                         return TRUE;
905
906                 case 'P':
907                 case 'p':
908                         if (attr_idx)
909                         {
910                                 /* Set the char */
911                                 *cur_attr_ptr = attr_idx;
912                                 *attr_top_ptr = (byte)MAX(0, (int)*cur_attr_ptr - frame_top);
913                         }
914
915                         if (char_idx)
916                         {
917                                 /* Set the char */
918                                 *cur_char_ptr = char_idx;
919                                 *char_left_ptr = (char)MAX(-128, (int)*cur_char_ptr - frame_left);
920                         }
921
922                         return TRUE;
923
924                 default:
925                 {
926                         if (*visual_list_ptr)
927                         {
928                                 int eff_width = actual_width(width);
929                                 int eff_height = actual_height(height);
930                                 int d = target_dir(ke.key);
931                                 byte a = *cur_attr_ptr;
932                                 char c = *cur_char_ptr;
933
934                                 /* Get mouse movement */
935                                 if (ke.key == '\xff')
936                                 {
937                                         int my = ke.mousey - row;
938                                         int mx = ke.mousex - col;
939
940                                         my = logical_height(my);
941                                         mx = logical_width(mx);
942
943                                         if ((my >= 0) && (my < eff_height) && (mx >= 0) && (mx < eff_width)
944                                                 && ((ke.index) || (a != *attr_top_ptr + my)
945                                                         || (c != *char_left_ptr + mx)) )
946                                         {
947                                                 /* Set the visual */
948                                                 *cur_attr_ptr = a = *attr_top_ptr + my;
949                                                 *cur_char_ptr = c = *char_left_ptr + mx;
950
951                                                 /* Move the frame */
952                                                 if (*char_left_ptr > MAX(-128, (int)c - frame_left))
953                                                         (*char_left_ptr)--;
954                                                 if (*char_left_ptr + eff_width < MIN(127, (int)c + frame_right))
955                                                         (*char_left_ptr)++;
956                                                 if (*attr_top_ptr > MAX(0, (int)a - frame_top))
957                                                         (*attr_top_ptr)--;
958                                                 if (*attr_top_ptr + eff_height < MIN(255, (int)a + frame_bottom))
959                                                         (*attr_top_ptr)++;
960
961                                                 /* Delay */
962                                                 *delay = 100;
963
964                                                 /* Accept change */
965                                                 if (ke.index) *visual_list_ptr = FALSE;
966
967                                                 return TRUE;
968                                         }
969                                         /* Cancel change */
970                                         else if (ke.index)
971                                         {
972                                                 *cur_attr_ptr = attr_old;
973                                                 *cur_char_ptr = char_old;
974                                                 *visual_list_ptr = FALSE;
975
976                                                 return TRUE;
977                                         }
978                                 }
979                                 else
980                                 {
981                                         /* Restrict direction */
982                                         if ((a == 0) && (ddy[d] < 0)) d = 0;
983                                         if ((c == (char) -128) && (ddx[d] < 0)) d = 0;
984                                         if ((a == 255) && (ddy[d] > 0)) d = 0;
985                                         if ((c == 127) && (ddx[d] > 0)) d = 0;
986
987                                         a += ddy[d];
988                                         c += ddx[d];
989
990                                         /* Set the visual */
991                                         *cur_attr_ptr = a;
992                                         *cur_char_ptr = c;
993
994                                         /* Move the frame */
995                                         if ((ddx[d] < 0) && *char_left_ptr > MAX(-128, (int)c - frame_left))
996                                                 (*char_left_ptr)--;
997                                         if ((ddx[d] > 0) && *char_left_ptr + eff_width <
998                                                                                                                 MIN(127, (int)c + frame_right))
999                                         (*char_left_ptr)++;
1000
1001                                         if ((ddy[d] < 0) && *attr_top_ptr > MAX(0, (int)a - frame_top))
1002                                                 (*attr_top_ptr)--;
1003                                         if ((ddy[d] > 0) && *attr_top_ptr + eff_height <
1004                                                                                                         MIN(255, (int)a + frame_bottom))
1005                                                 (*attr_top_ptr)++;
1006
1007                                         if (d != 0) return TRUE;
1008                                 }
1009                         }
1010                 }
1011         }
1012
1013         /* Visual mode command is not used */
1014         return FALSE;
1015 }
1016
1017
1018 /* The following sections implement "subclasses" of the
1019  * abstract classes represented by member_funcs and group_funcs
1020  */
1021
1022 /* =================== MONSTERS ==================================== */
1023 /* Many-to-many grouping - use default auxiliary join */
1024
1025 /*
1026  * Display a monster
1027  */
1028 static void display_monster(int col, int row, bool cursor, int oid)
1029 {
1030         /* HACK Get the race index. (Should be a wrapper function) */
1031         int r_idx = default_join[oid].oid;
1032
1033         /* Access the race */
1034         monster_race *r_ptr = &r_info[r_idx];
1035         monster_lore *l_ptr = &l_list[r_idx];
1036
1037         /* Choose colors */
1038         byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1039         byte a = r_ptr->x_attr;
1040         byte c = r_ptr->x_char;
1041
1042         /* Display the name */
1043         c_prt(attr, r_name + r_ptr->name, row, col);
1044
1045         if (use_dbltile || use_trptile)
1046                 return;
1047
1048         /* Display symbol */
1049         big_pad(66, row, a, c);
1050
1051         /* Display kills */
1052         if (r_ptr->flags1 & (RF1_UNIQUE))
1053                 put_str(format("%s", (r_ptr->max_num == 0)?  " dead" : "alive"), row, 70);
1054         else put_str(format("%5d", l_ptr->pkills), row, 70);
1055 }
1056
1057
1058 static int m_cmp_race(const void *a, const void *b) {
1059         monster_race *r_a = &r_info[default_join[*(int*)a].oid];
1060         monster_race *r_b = &r_info[default_join[*(int*)b].oid];
1061         int gid = default_join[*(int*)a].gid;
1062         /* group by */
1063         int c = gid - default_join[*(int*)b].gid;
1064         if (c) return c;
1065         /* order results */
1066         c = r_a->d_char - r_b->d_char;
1067         if (c && gid != 0)
1068         {
1069                 /* UNIQUE group is ordered by level & name only */
1070                 /* Others by order they appear in the group symbols */
1071                 return strchr(monster_group[gid].chars, r_a->d_char)
1072                         - strchr(monster_group[gid].chars, r_b->d_char);
1073         }
1074         c = r_a->level - r_b->level;
1075         if (c) return c;
1076         return strcmp(r_name + r_a->name, r_name + r_b->name);
1077 }
1078
1079 static char *m_xchar(int oid) { return &r_info[default_join[oid].oid].x_char; }
1080 static byte *m_xattr(int oid) { return &r_info[default_join[oid].oid].x_attr; }
1081 static const char *race_name(int gid) { return monster_group[gid].name; }
1082 static void mon_lore(int oid) { screen_roff(default_join[oid].oid); inkey_ex(); }
1083
1084 static void mon_summary(int gid, const int *object_list, int n, int top, int row, int col)
1085 {
1086         int i;
1087         int kills = 0;
1088
1089         for (i = 0; i < n; i++)
1090         {
1091                 int oid = default_join[object_list[i+top]].oid;
1092                 kills += l_list[oid].pkills;
1093         }
1094         if (gid == 0)
1095         {
1096                 c_prt(TERM_L_BLUE, format("Known Uniques: %d, Slain Uniques: %d.", n, kills),
1097                                         row, col);
1098         }
1099         else
1100         {
1101                 int tkills = 0;
1102                 for (i = 0; i < z_info->r_max; i++)
1103                         tkills += l_list[i].pkills;
1104                 c_prt(TERM_L_BLUE, format("Creatures Slain: %d/%d (in group/in total)", kills, tkills), row, col);
1105         }
1106 }
1107
1108 /*
1109  * Display known monsters.
1110  */
1111 static void do_cmd_knowledge_monsters(void)
1112 {
1113         group_funcs r_funcs = {N_ELEMENTS(monster_group), FALSE, race_name,
1114                                                         m_cmp_race, default_group, 0, mon_summary};
1115
1116         member_funcs m_funcs = {display_monster, mon_lore, m_xchar, m_xattr, 0, 0};
1117
1118        
1119         int *monsters;
1120         int m_count = 0;
1121         int i;
1122         size_t j;
1123
1124         for (i = 0; i < z_info->r_max; i++)
1125         {
1126                 monster_race *r_ptr = &r_info[i];
1127                 if (!cheat_know && !l_list[i].sights) continue;
1128                 if (!r_ptr->name) continue;
1129
1130                 if (r_ptr->flags1 & RF1_UNIQUE) m_count++;
1131                 for (j = 1; j < N_ELEMENTS(monster_group)-1; j++)
1132                 {
1133                         const char *pat = monster_group[j].chars;
1134                         if (strchr(pat, r_ptr->d_char)) m_count++;
1135                 }
1136         }
1137
1138         C_MAKE(default_join, m_count, join_t);
1139         C_MAKE(monsters, m_count, int);
1140
1141         m_count = 0;
1142         for (i = 0; i < z_info->r_max; i++)
1143         {
1144                 monster_race *r_ptr = &r_info[i];
1145                 if (!cheat_know && !l_list[i].sights) continue;
1146                 if (!r_ptr->name) continue;
1147        
1148                 for (j = 0; j < N_ELEMENTS(monster_group)-1; j++)
1149                 {
1150                         const char *pat = monster_group[j].chars;
1151                         if (j == 0 && !(r_ptr->flags1 & RF1_UNIQUE))
1152                                 continue;
1153                         else if (j > 0 && !strchr(pat, r_ptr->d_char))
1154                                 continue;
1155
1156                         monsters[m_count] = m_count;
1157                         default_join[m_count].oid = i;
1158                         default_join[m_count++].gid = j;
1159                 }
1160         }
1161
1162         display_knowledge("monsters", monsters, m_count, r_funcs, m_funcs,
1163                                                 "Sym  Kills");
1164         FREE(monsters);
1165         FREE(default_join);
1166         default_join = 0;
1167 }
1168
1169 /* =================== ARTIFACTS ==================================== */
1170 /* Many-to-one grouping */
1171
1172 /*
1173  * Display an artifact label
1174  */
1175 static void display_artifact(int col, int row, bool cursor, int oid)
1176 {
1177         char o_name[80];
1178         object_type object_type_body;
1179         object_type *o_ptr = &object_type_body;
1180
1181         /* Choose a color */
1182         byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1183
1184         /* Get local object */
1185         o_ptr = &object_type_body;
1186
1187         /* Wipe the object */
1188         object_wipe(o_ptr);
1189
1190         /* Make fake artifact */
1191         make_fake_artifact(o_ptr, oid);
1192
1193         /* Get its name */
1194         object_desc_spoil(o_name, sizeof(o_name), o_ptr, TRUE, 0);
1195
1196         /* Display the name */
1197         c_prt(attr, o_name, row, col);
1198 }
1199
1200 /*
1201  * Show artifact lore
1202  */
1203 static void desc_art_fake(int a_idx)
1204 {
1205         object_type *o_ptr;
1206         object_type object_type_body;
1207
1208         /* Get local object */
1209         o_ptr = &object_type_body;
1210
1211         /* Wipe the object */
1212         object_wipe(o_ptr);
1213
1214         /* Make fake artifact */
1215         make_fake_artifact(o_ptr, a_idx);
1216         o_ptr->ident |= IDENT_STORE | IDENT_KNOWN;
1217         if (cheat_xtra) o_ptr->ident |= IDENT_MENTAL;
1218
1219         /* Hack -- Handle stuff */
1220         handle_stuff();
1221
1222         /* Save the screen */
1223         screen_save();
1224
1225         object_info_screen(o_ptr);
1226
1227         /* Load the screen */
1228         screen_load();
1229 }
1230
1231 static int a_cmp_tval(const void *a, const void *b)
1232 {
1233         artifact_type *a_a = &a_info[*(int*)a];
1234         artifact_type *a_b = &a_info[*(int*)b];
1235         /*group by */
1236         int ta = obj_group_order[a_a->tval];
1237         int tb = obj_group_order[a_b->tval];
1238         int c = ta - tb;
1239         if (c) return c;
1240
1241         /* order by */
1242         c = a_a->sval - a_b->sval;
1243         if (c) return c;
1244         return strcmp(a_name+a_a->name, a_name+a_b->name);
1245 }
1246
1247 static const char *kind_name(int gid)
1248 { return object_text_order[gid].name; }
1249 static int art2tval(int oid) { return a_info[oid].tval; }
1250
1251 /*
1252  * Display known artifacts
1253  */
1254 static void do_cmd_knowledge_artifacts(void)
1255 {
1256         /* HACK -- should be TV_MAX */
1257         group_funcs obj_f = {TV_GOLD, FALSE, kind_name, a_cmp_tval, art2tval, 0, 0};
1258         member_funcs art_f = {display_artifact, desc_art_fake, 0, 0, 0, 0};
1259
1260
1261         int *artifacts;
1262         int a_count = 0;
1263         int i, j;
1264
1265         C_MAKE(artifacts, z_info->a_max, int);
1266        
1267         /* Collect valid artifacts */
1268         for (i = 0; i < z_info->a_max; i++)
1269         {
1270                 if ((cheat_xtra || a_info[i].cur_num) && a_info[i].name)
1271                         artifacts[a_count++] = i;
1272         }
1273         for (i = 0; !cheat_xtra && i < z_info->o_max; i++)
1274         {
1275                 int a = o_list[i].name1;
1276                 if (a && !object_known_p(&o_list[i]))
1277                 {
1278                         for (j = 0; j < a_count && a != artifacts[j]; j++);
1279                         a_count -= 1;
1280                         for (; j < a_count; j++)
1281                                 artifacts[j] = artifacts[j+1];
1282                 }
1283         }
1284
1285         display_knowledge("artifacts", artifacts, a_count, obj_f, art_f, 0);
1286         FREE(artifacts);
1287 }
1288
1289 /* =================== EGO ITEMS  ==================================== */
1290 /* Many-to-many grouping (uses default join) */
1291
1292 /* static u16b *e_note(int oid) {return &e_info[default_join[oid].oid].note;} */
1293 static const char *ego_grp_name(int gid) { return object_text_order[gid].name; }
1294
1295 static void display_ego_item(int col, int row, bool cursor, int oid)
1296 {
1297         /* HACK: Access the object */
1298         ego_item_type *e_ptr = &e_info[default_join[oid].oid];
1299
1300         /* Choose a color */
1301         byte attr = curs_attrs[0 != (int)e_ptr->everseen][0 != (int)cursor];
1302
1303         /* Display the name */
1304         c_prt(attr, e_name + e_ptr->name, row, col);
1305 }
1306
1307 /*
1308  * Describe fake ego item "lore"
1309  */
1310 static void desc_ego_fake(int oid)
1311 {
1312         /* Hack: dereference the join */
1313         const char *cursed [] = {"permanently cursed", "heavily cursed", "cursed"};
1314         const char *xtra [] = {"sustain", "higher resistance", "ability"};
1315         int f3, i;
1316
1317         int e_idx = default_join[oid].oid;
1318         ego_item_type *e_ptr = &e_info[e_idx];
1319
1320         object_type dummy;
1321         WIPE(&dummy, dummy);
1322
1323         /* Save screen */
1324         screen_save();
1325
1326         /* Set text_out hook */
1327         text_out_hook = text_out_to_screen;
1328
1329         /* Dump the name */
1330         c_prt(TERM_L_BLUE, format("%s %s", ego_grp_name(default_group(oid)),
1331                                                                                 e_name+e_ptr->name), 0, 0);
1332
1333         /* Begin recall */
1334         Term_gotoxy(0, 1);
1335         if (e_ptr->text)
1336         {
1337                 int x, y;
1338                 text_out(e_text + e_ptr->text);
1339                 Term_locate(&x, &y);
1340                 Term_gotoxy(0, y+1);
1341         }
1342
1343         /* List ego flags */
1344         dummy.name2 = e_idx;
1345         object_info_out_flags = object_flags;
1346         object_info_out(&dummy);
1347
1348         if (e_ptr->xtra)
1349         {
1350                 text_out(format("It provides one random %s.", xtra[e_ptr->xtra - 1]));
1351         }
1352
1353         for (i = 0, f3 = TR3_PERMA_CURSE; i < 3 ; f3 >>= 1, i++)
1354         {
1355                 if (e_ptr->flags3 & f3)
1356                 {
1357                         text_out_c(TERM_RED, format("It is %s.", cursed[i]));
1358                         break;
1359                 }
1360         }
1361
1362         Term_flush();
1363
1364         (void)inkey_ex();
1365
1366         screen_load();
1367 }
1368
1369 /* TODO? Currently ego items will order by e_idx */
1370 static int e_cmp_tval(const void *a, const void *b)
1371 {
1372         ego_item_type *ea = &e_info[default_join[*(int*)a].oid];
1373         ego_item_type *eb = &e_info[default_join[*(int*)b].oid];
1374         /*group by */
1375         int c = default_join[*(int*)a].gid - default_join[*(int*)b].gid;
1376         if (c) return c;
1377         /* order by */
1378         return strcmp(e_name + ea->name, e_name + eb->name);
1379 }
1380
1381 /*
1382  * Display known ego_items
1383  */
1384 static void do_cmd_knowledge_ego_items(void)
1385 {
1386         group_funcs obj_f =
1387                 {TV_GOLD, FALSE, ego_grp_name, e_cmp_tval, default_group, 0, 0};
1388
1389         member_funcs ego_f = {display_ego_item, desc_ego_fake, 0, 0, 0, 0/*e_note */ };
1390
1391         int *egoitems;
1392         int e_count = 0;
1393         int i, j;
1394
1395         /* HACK: currently no more than 3 tvals for one ego type */
1396         C_MAKE(egoitems, z_info->e_max*EGO_TVALS_MAX, int);
1397         C_MAKE(default_join, z_info->e_max*EGO_TVALS_MAX, join_t);
1398         for (i = 0; i < z_info->e_max; i++)
1399         {
1400                 if (e_info[i].everseen || cheat_xtra)
1401                 {
1402                         for (j = 0; j < EGO_TVALS_MAX && e_info[i].tval[j]; j++)
1403                         {
1404                                 int gid = obj_group_order[e_info[i].tval[j]];
1405                                 /* Ignore duplicate gids */
1406                                 if (j > 0 && gid == default_join[e_count-1].gid)
1407                                         continue;
1408                                 egoitems[e_count] = e_count;
1409                                 default_join[e_count].oid = i;
1410                                 default_join[e_count++].gid = gid;
1411                         }
1412                 }
1413         }
1414
1415         display_knowledge("ego items", egoitems, e_count, obj_f, ego_f, "");
1416         FREE(default_join);
1417         default_join = 0;
1418         FREE(egoitems);
1419 }
1420
1421 /* =================== ORDINARY OBJECTS  ==================================== */
1422 /* Many-to-one grouping */
1423
1424 /*
1425  * Display the objects in a group.
1426  */
1427 static void display_object(int col, int row, bool cursor, int oid)
1428 {
1429         int k_idx = oid;
1430        
1431         /* Access the object */
1432         object_kind *k_ptr = &k_info[k_idx];
1433
1434         char o_name[80];
1435
1436         /* Choose a color */
1437         bool aware = (k_ptr->flavor == 0) || (k_ptr->aware);
1438         byte a = (aware && k_ptr->x_attr) ?
1439                                 k_ptr->x_attr : flavor_info[k_ptr->flavor].x_attr;
1440         byte c = aware ? k_ptr->x_char : flavor_info[k_ptr->flavor].x_char;
1441         byte attr = curs_attrs[(int)k_ptr->flavor == 0 || k_ptr->aware][(int)cursor];
1442
1443         /* Symbol is unknown.  This should never happen.*/     
1444         if (!k_ptr->aware && !k_ptr->flavor && !p_ptr->wizard)
1445         {
1446                 assert(0);
1447                 c = ' ';
1448                 a = TERM_DARK;
1449         }
1450
1451         /* Tidy name */
1452         strip_name(o_name, k_idx, cheat_know);
1453
1454         /* Display the name */
1455         c_prt(attr, o_name, row, col);
1456
1457
1458         /* Hack - don't use if double tile */
1459         if (use_dbltile || use_trptile)
1460                 return;
1461
1462         /* Display symbol */
1463         big_pad(76, row, a, c);
1464 }
1465
1466 /*
1467  * Describe fake object
1468  */
1469 static void desc_obj_fake(int k_idx)
1470 {
1471         object_type object_type_body;
1472         object_type *o_ptr = &object_type_body;
1473
1474         /* Wipe the object */
1475         object_wipe(o_ptr);
1476
1477         /* Create the artifact */
1478         object_prep(o_ptr, k_idx);
1479
1480         /* Hack -- its in the store */
1481         if (k_info[k_idx].aware) o_ptr->ident |= (IDENT_STORE);
1482
1483         /* It's fully know */
1484         if (!k_info[k_idx].flavor) object_known(o_ptr);
1485
1486         /* Hack -- Handle stuff */
1487         handle_stuff();
1488
1489         /* Save the screen */
1490         screen_save();
1491
1492         /* Describe */
1493         Term_gotoxy(0,0);
1494         object_info_screen(o_ptr);
1495
1496         /* Load the screen */
1497         screen_load();
1498 }
1499
1500 static int o_cmp_tval(const void *a, const void *b)
1501 {
1502         object_kind *k_a = &k_info[*(int*)a];
1503         object_kind *k_b = &k_info[*(int*)b];
1504         /*group by */
1505         int ta = obj_group_order[k_a->tval];
1506         int tb = obj_group_order[k_b->tval];
1507         int c = ta - tb;
1508         if (c) return c;
1509         /* order by */
1510         c = k_a->aware - k_b->aware;
1511         if (c) return -c; /* aware has low sort weight */
1512         if (!k_a->aware)
1513         {
1514                 return strcmp(flavor_text + flavor_info[k_a->flavor].text,
1515                                                                         flavor_text +flavor_info[k_b->flavor].text);
1516         }
1517         c = k_a->cost - k_b->cost;
1518         if (c) return c;
1519         return strcmp(k_name + k_a->name, k_name + k_b->name);
1520 }
1521 static int obj2gid(int oid) { return obj_group_order[k_info[oid].tval]; }
1522 static char *o_xchar(int oid) {
1523         object_kind *k_ptr = &k_info[oid];
1524         if (!k_ptr->flavor || k_ptr->aware) return &k_ptr->x_char;
1525         else return &flavor_info[k_ptr->flavor].x_char;
1526 }
1527 static byte *o_xattr(int oid) {
1528         object_kind *k_ptr = &k_info[oid];
1529         if (!k_ptr->flavor || k_ptr->aware) return &k_ptr->x_attr;
1530         else return &flavor_info[k_ptr->flavor].x_attr;
1531 }
1532
1533 static u16b *o_note(int oid) {
1534         object_kind *k_ptr = &k_info[oid];
1535         int ind = get_autoinscription_index(oid);
1536         if (!k_ptr->flavor || k_ptr->aware) return (u16b*) &inscriptions[ind].inscription_idx;
1537         else return 0;
1538 }
1539
1540 /*
1541  * Display known objects
1542  */
1543 static void do_cmd_knowledge_objects(void)
1544 {
1545         group_funcs kind_f = {TV_GOLD, FALSE, kind_name, o_cmp_tval, obj2gid, 0, 0};
1546         member_funcs obj_f = {display_object, desc_obj_fake, o_xchar, o_xattr,0 /* o_note*/};
1547
1548         int *objects;
1549         int o_count = 0;
1550         int i;
1551
1552         C_MAKE(objects, z_info->k_max, int);
1553
1554         for (i = 0; i < z_info->k_max; i++)
1555         {
1556                 if (k_info[i].everseen || k_info[i].flavor || cheat_xtra)
1557                 {
1558                         int c = obj_group_order[k_info[i].tval];
1559                         if (c >= 0) objects[o_count++] = i;
1560                 }
1561         }
1562         display_knowledge("known objects", objects, o_count, kind_f, obj_f, "         Sym");
1563         FREE(objects);
1564 }
1565
1566 /* =================== TERRAIN FEATURES ==================================== */
1567 /* Many-to-one grouping */
1568
1569 /*
1570  * Display the features in a group.
1571  */
1572 static void display_feature(int col, int row, bool cursor, int oid )
1573 {
1574         /* Get the feature index */
1575         int f_idx = oid;
1576
1577         /* Access the feature */
1578         feature_type *f_ptr = &f_info[f_idx];
1579
1580         /* Choose a color */
1581         byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
1582
1583         /* Display the name */
1584         c_prt(attr, f_name + f_ptr->name, row, col);
1585
1586
1587         if (use_dbltile || use_trptile) return;
1588
1589         /* Display symbol */
1590         big_pad(68, row, f_ptr->x_attr, f_ptr->x_char);
1591
1592         /* ILLUMINATION AND DARKNESS GO HERE */
1593
1594 }
1595
1596
1597 static int f_cmp_fkind(const void *a, const void *b) {
1598         feature_type *fa = &f_info[*(int*)a];
1599         feature_type *fb = &f_info[*(int*)b];
1600         /* group by */
1601         int c = feat_order(*(int*)a) - feat_order(*(int*)b);
1602         if (c) return c;
1603         /* order by feature name */
1604         return strcmp(f_name + fa->name, f_name + fb->name);
1605 }
1606
1607 static const char *fkind_name(int gid) { return feature_group_text[gid]; }
1608 static byte *f_xattr(int oid) { return &f_info[oid].x_attr; }
1609 static char *f_xchar(int oid) { return &f_info[oid].x_char; }
1610 static void feat_lore(int oid) { /* noop */ }
1611
1612 /*
1613  * Interact with feature visuals.
1614  */
1615 static void do_cmd_knowledge_features(void)
1616 {
1617         group_funcs fkind_f = {N_ELEMENTS(feature_group_text), FALSE,
1618                                                         fkind_name, f_cmp_fkind, feat_order, 0, 0};
1619
1620         member_funcs feat_f = {display_feature, feat_lore, f_xchar, f_xattr, 0};
1621
1622         int *features;
1623         int f_count = 0;
1624         int i;
1625         C_MAKE(features, z_info->f_max, int);
1626
1627         for (i = 0; i < z_info->f_max; i++)
1628         {
1629                 if (f_info[i].name == 0) continue;
1630                 features[f_count++] = i; /* Currently no filter for features */
1631         }
1632
1633         display_knowledge("features", features, f_count, fkind_f, feat_f, " Sym");
1634         FREE(features);
1635 }
1636
1637 /* =================== HOMES AND STORES ==================================== */
1638
1639
1640
1641 void do_cmd_knowledge_home() 
1642 {
1643         /* TODO */
1644 }
1645
1646
1647 /* =================== END JOIN DEFINITIONS ================================ */
1648
1649
1650 /*
1651  * Hack -- redraw the screen
1652  *
1653  * This command performs various low level updates, clears all the "extra"
1654  * windows, does a total redraw of the main window, and requests all of the
1655  * interesting updates and redraws that I can think of.
1656  *
1657  * This command is also used to "instantiate" the results of the user
1658  * selecting various things, such as graphics mode, so it must call
1659  * the "TERM_XTRA_REACT" hook before redrawing the windows.
1660  *
1661  */
1662 void do_cmd_redraw(void)
1663 {
1664         int j;
1665
1666         term *old = Term;
1667
1668
1669         /* Low level flush */
1670         Term_flush();
1671
1672         /* Reset "inkey()" */
1673         flush();
1674        
1675         if (0 != character_dungeon)
1676                 verify_panel();
1677
1678
1679         /* Hack -- React to changes */
1680         Term_xtra(TERM_XTRA_REACT, 0);
1681
1682
1683         /* Combine and Reorder the pack (later) */
1684         p_ptr->notice |= (PN_COMBINE | PN_REORDER);
1685
1686
1687         /* Update torch */
1688         p_ptr->update |= (PU_TORCH);
1689
1690         /* Update stuff */
1691         p_ptr->update |= (PU_BONUS | PU_HP | PU_MANA | PU_SPELLS);
1692
1693         /* Fully update the visuals */
1694         p_ptr->update |= (PU_FORGET_VIEW | PU_UPDATE_VIEW | PU_MONSTERS);
1695
1696         /* Redraw everything */
1697         p_ptr->redraw |= (PR_BASIC | PR_EXTRA | PR_MAP | PR_EQUIPPY);
1698
1699         /* Window stuff */
1700         p_ptr->window |= (PW_INVEN | PW_EQUIP | PW_PLAYER_0 | PW_PLAYER_1 |
1701                           PW_MESSAGE | PW_OVERHEAD | PW_MONSTER | PW_OBJECT |
1702                           PW_MAP | PW_MONLIST);
1703
1704         /* Clear screen */
1705         Term_clear();
1706
1707         /* Hack -- update */
1708         handle_stuff();
1709
1710         /* Place the cursor on the player */
1711         if (0 != character_dungeon)
1712                 move_cursor_relative(p_ptr->px, p_ptr->py);
1713
1714
1715         /* Redraw every window */
1716         for (j = 0; j < ANGBAND_TERM_MAX; j++)
1717         {
1718                 /* Dead window */
1719                 if (!angband_term[j]) continue;
1720
1721                 /* Activate */
1722                 Term_activate(angband_term[j]);
1723
1724                 /* Redraw */
1725                 Term_redraw();
1726
1727                 /* Refresh */
1728                 Term_fresh();
1729
1730                 /* Restore */
1731                 Term_activate(old);
1732         }
1733 }
1734
1735
1736 /*
1737  * Hack -- change name
1738  */
1739 void do_cmd_change_name(void)
1740 {
1741         event_type ke;
1742
1743         int mode = 0;
1744
1745         cptr p;
1746
1747         /* Prompt */
1748         p = "['c' to change name, 'f' to file, 'h' to change mode, or ESC]";
1749
1750         /* Save screen */
1751         screen_save();
1752
1753         /* Forever */
1754         while (1)
1755         {
1756                 /* Display the player */
1757                 display_player(mode);
1758
1759                 /* Prompt */
1760                 Term_putstr(2, 23, -1, TERM_WHITE, p);
1761
1762                 /* Query */
1763                 ke = inkey_ex();
1764
1765                 /* Exit */
1766                 if (ke.key == ESCAPE) break;
1767
1768                 /* Change name */
1769                 if ((ke.key == 'c') || ((ke.key == '\xff') && (ke.mousey == 2) && (ke.mousex < 26)))
1770                 {
1771                         get_name(FALSE);
1772                 }
1773
1774                 /* File dump */
1775                 else if (ke.key == 'f')
1776                 {
1777                         char ftmp[80];
1778
1779                         strnfmt(ftmp, sizeof ftmp, "%s.txt", op_ptr->base_name);
1780
1781                         if (get_string("File name: ", ftmp, 80))
1782                         {
1783                                 if (ftmp[0] && (ftmp[0] != ' '))
1784                                 {
1785                                         if (file_character(ftmp, FALSE))
1786                                                 msg_print("Character dump failed!");
1787                                         else
1788                                                 msg_print("Character dump successful.");
1789                                 }
1790                         }
1791                 }
1792
1793                 /* Toggle mode */
1794                 else if ((ke.key == 'h') || (ke.key == '\xff') ||
1795                          (ke.key == ARROW_LEFT) || (ke.key == ' '))
1796                 {
1797                         mode = (mode + 1) % INFO_SCREENS;
1798                 }
1799
1800                 /* Toggle mode */
1801                 else if ((ke.key == 'l') || ke.key == ARROW_RIGHT)
1802                 {
1803                         mode = (mode - 1) % INFO_SCREENS;
1804                 }
1805
1806
1807                 /* Oops */
1808                 else
1809                 {
1810                         bell(NULL);
1811                 }
1812
1813                 /* Flush messages */
1814                 message_flush();
1815         }
1816
1817         /* Load screen */
1818         screen_load();
1819 }
1820
1821
1822 /*
1823  * Recall the most recent message
1824  */
1825 void do_cmd_message_one(void)
1826 {
1827         /* Recall one message XXX XXX XXX */
1828         c_prt(message_color(0), format( "> %s", message_str(0)), 0, 0);
1829 }
1830
1831
1832 /*
1833  * Show previous messages to the user
1834  *
1835  * The screen format uses line 0 and 23 for headers and prompts,
1836  * skips line 1 and 22, and uses line 2 thru 21 for old messages.
1837  *
1838  * This command shows you which commands you are viewing, and allows
1839  * you to "search" for strings in the recall.
1840  *
1841  * Note that messages may be longer than 80 characters, but they are
1842  * displayed using "infinite" length, with a special sub-command to
1843  * "slide" the virtual display to the left or right.
1844  *
1845  * Attempt to only hilite the matching portions of the string.
1846  */
1847 void do_cmd_messages(void)
1848 {
1849         event_type ke;
1850
1851         int i, j, n, q;
1852         int wid, hgt;
1853
1854         char shower[80];
1855         char finder[80];
1856
1857
1858         /* Wipe finder */
1859         my_strcpy(finder, "", sizeof(shower));
1860
1861         /* Wipe shower */
1862         my_strcpy(shower, "", sizeof(finder));
1863
1864
1865         /* Total messages */
1866         n = message_num();
1867
1868         /* Start on first message */
1869         i = 0;
1870
1871         /* Start at leftmost edge */
1872         q = 0;
1873
1874         /* Get size */
1875         Term_get_size(&wid, &hgt);
1876
1877         /* Save screen */
1878         screen_save();
1879
1880         /* Process requests until done */
1881         while (1)
1882         {
1883                 /* Clear screen */
1884                 Term_clear();
1885
1886                 /* Dump messages */
1887                 for (j = 0; (j < hgt - 4) && (i + j < n); j++)
1888                 {
1889                         cptr msg = message_str((s16b)(i+j));
1890                         byte attr = message_color((s16b)(i+j));
1891
1892                         /* Apply horizontal scroll */
1893                         msg = ((int)strlen(msg) >= q) ? (msg + q) : "";
1894
1895                         /* Dump the messages, bottom to top */
1896                         Term_putstr(0, hgt - 3 - j, -1, attr, msg);
1897
1898                         /* Hilite "shower" */
1899                         if (shower[0])
1900                         {
1901                                 cptr str = msg;
1902
1903                                 /* Display matches */
1904                                 while ((str = strstr(str, shower)) != NULL)
1905                                 {
1906                                         int len = strlen(shower);
1907
1908                                         /* Display the match */
1909                                         Term_putstr(str-msg, hgt - 3 - j, len, TERM_YELLOW, shower);
1910
1911                                         /* Advance */
1912                                         str += len;
1913                                 }
1914                         }
1915                 }
1916
1917                 /* Display header XXX XXX XXX */
1918                 prt(format("Message Recall (%d-%d of %d), Offset %d",
1919                            i, i + j - 1, n, q), 0, 0);
1920
1921                 /* Display prompt (not very informative) */
1922                 prt("[Press 'p' for older, 'n' for newer, ..., or ESCAPE]", hgt - 1, 0);
1923
1924                 /* Get a command */
1925                 ke = inkey_ex();
1926
1927                 /* Exit on Escape */
1928                 if (ke.key == ESCAPE) break;
1929
1930                 /* Hack -- Save the old index */
1931                 j = i;
1932
1933                 /* Horizontal scroll */
1934                 if (ke.key == '4')
1935                 {
1936                         /* Scroll left */
1937                         q = (q >= wid / 2) ? (q - wid / 2) : 0;
1938
1939                         /* Success */
1940                         continue;
1941                 }
1942
1943                 /* Horizontal scroll */
1944                 if (ke.key == '6')
1945                 {
1946                         /* Scroll right */
1947                         q = q + wid / 2;
1948
1949                         /* Success */
1950                         continue;
1951                 }
1952
1953                 /* Hack -- handle show */
1954                 if (ke.key == '=')
1955                 {
1956                         /* Prompt */
1957                         prt("Show: ", hgt - 1, 0);
1958
1959                         /* Get a "shower" string, or continue */
1960                         if (!askfor_aux(shower, sizeof shower, NULL)) continue;
1961
1962                         /* Okay */
1963                         continue;
1964                 }
1965
1966                 /* Hack -- handle find */
1967                 if (ke.key == '/')
1968                 {
1969                         s16b z;
1970
1971                         /* Prompt */
1972                         prt("Find: ", hgt - 1, 0);
1973
1974                         /* Get a "finder" string, or continue */
1975                         if (!askfor_aux(finder, sizeof finder, NULL)) continue;
1976
1977                         /* Show it */
1978                         my_strcpy(shower, finder, sizeof(shower));
1979
1980                         /* Scan messages */
1981                         for (z = i + 1; z < n; z++)
1982                         {
1983                                 cptr msg = message_str(z);
1984
1985                                 /* Search for it */
1986                                 if (strstr(msg, finder))
1987                                 {
1988                                         /* New location */
1989                                         i = z;
1990
1991                                         /* Done */
1992                                         break;
1993                                 }
1994                         }
1995                 }
1996
1997                 /* Recall 20 older messages */
1998                 if ((ke.key == 'p') || (ke.key == KTRL('P')) || (ke.key == ' '))
1999                 {
2000                         /* Go older if legal */
2001                         if (i + 20 < n) i += 20;
2002                 }
2003
2004                 /* Recall 10 older messages */
2005                 if (ke.key == '+')
2006                 {
2007                         /* Go older if legal */
2008                         if (i + 10 < n) i += 10;
2009                 }
2010
2011                 /* Recall 1 older message */
2012                 if ((ke.key == '8') || (ke.key == '\n') || (ke.key == '\r'))
2013                 {
2014                         /* Go older if legal */
2015                         if (i + 1 < n) i += 1;
2016                 }
2017
2018                 /* Recall 20 newer messages */
2019                 if ((ke.key == 'n') || (ke.key == KTRL('N')))
2020                 {
2021                         /* Go newer (if able) */
2022                         i = (i >= 20) ? (i - 20) : 0;
2023                 }
2024
2025                 /* Recall 10 newer messages */
2026                 if (ke.key == '-')
2027                 {
2028                         /* Go newer (if able) */
2029                         i = (i >= 10) ? (i - 10) : 0;
2030                 }
2031
2032                 /* Recall 1 newer messages */
2033                 if (ke.key == '2')
2034                 {
2035                         /* Go newer (if able) */
2036                         i = (i >= 1) ? (i - 1) : 0;
2037                 }
2038
2039                 /* Scroll forwards or backwards using mouse clicks */
2040                 if (ke.key == '\xff')
2041                 {
2042                         if (ke.index)
2043                         {
2044                                 if (ke.mousey <= hgt / 2)
2045                                 {
2046                                         /* Go older if legal */
2047                                         if (i + 20 < n) i += 20;
2048                                 }
2049                                 else
2050                                 {
2051                                         /* Go newer (if able) */
2052                                         i = (i >= 20) ? (i - 20) : 0;
2053                                 }
2054                         }
2055                 }
2056
2057                 /* Hack -- Error of some kind */
2058                 if (i == j) bell(NULL);
2059         }
2060
2061         /* Load screen */
2062         screen_load();
2063 }
2064
2065
2066
2067 /*
2068  * Ask for a "user pref line" and process it
2069  */
2070 void do_cmd_pref(void)
2071 {
2072         char tmp[80];
2073
2074         /* Default */
2075         my_strcpy(tmp, "", sizeof(tmp));
2076
2077         /* Ask for a "user pref command" */
2078         if (!get_string("Pref: ", tmp, 80)) return;
2079
2080         /* Process that pref command */
2081         (void)process_pref_file_command(tmp);
2082 }
2083
2084
2085 /*
2086  * Ask for a "user pref file" and process it.
2087  *
2088  * This function should only be used by standard interaction commands,
2089  * in which a standard "Command:" prompt is present on the given row.
2090  *
2091  * Allow absolute file names?  XXX XXX XXX
2092  */
2093 static void do_cmd_pref_file_hack(long row)
2094 {
2095         char ftmp[80];
2096
2097         /* Prompt */
2098         prt("Command: Load a user pref file", row, 0);
2099
2100         /* Prompt */
2101         prt("File: ", row + 2, 0);
2102
2103         /* Default filename */
2104         strnfmt(ftmp, sizeof ftmp, "%s.prf", op_ptr->base_name);
2105
2106         /* Ask for a file (or cancel) */
2107         if (!askfor_aux(ftmp, sizeof ftmp, NULL)) return;
2108
2109         /* Process the given filename */
2110         if (process_pref_file(ftmp))
2111         {
2112                 /* Mention failure */
2113                 msg_format("Failed to load '%s'!", ftmp);
2114         }
2115         else
2116         {
2117                 /* Mention success */
2118                 msg_format("Loaded '%s'.", ftmp);
2119         }
2120         inkey_ex();
2121 }
2122
2123
2124 static void display_option(menu_type *menu, int oid,
2125                                                         bool cursor, int row, int col, int width)
2126 {
2127         byte attr = curs_attrs[CURS_KNOWN][(int)cursor];
2128         c_prt(attr, format("%-45s: %s  (%s)",
2129                                                 option_desc[oid],
2130                                                 op_ptr->opt[oid] ? "yes" : "no ",
2131                                                 option_text[oid]),
2132                         row, col);
2133 }
2134
2135 static bool update_option(char key, void *pgdb, int oid)
2136 {
2137         switch(toupper(key))
2138         {
2139         case 'Y': case '6':
2140                 op_ptr->opt[oid] = TRUE;
2141                 break;
2142         case 'N': case '4':
2143                 op_ptr->opt[oid] = FALSE;
2144                 break;
2145         case 'T': case '5': case '\xff':
2146                 op_ptr->opt[oid] = !op_ptr->opt[oid];
2147                 break;
2148         case '?':
2149                 show_file(format("option.txt#%s", option_text[oid]), NULL, 0, 0);
2150                 break;
2151         }
2152         return TRUE;
2153 }
2154
2155 static const menu_iter options_iter = {
2156         0,
2157         NULL,
2158         NULL,
2159         display_option,         /* label */
2160         update_option           /* updater */
2161 };
2162
2163 static menu_type option_toggle_menu;
2164
2165
2166 /*
2167  * Interact with some options
2168  */
2169 static void do_cmd_options_aux(void *vpage, cptr info)
2170 {
2171         int page = (int)vpage;
2172         int opt[OPT_PAGE_PER];
2173         int i, n = 0;
2174         int cursor_pos = 0;
2175
2176         menu_type *menu = &option_toggle_menu;
2177         menu->title = info;
2178         menu_layout(menu, &SCREEN_REGION);
2179
2180         screen_save();
2181         clear_from(0);
2182
2183         /* Filter the options for this page */
2184         for (i = 0; i < OPT_PAGE_PER; i++)
2185         {
2186                 if (option_page[page][i] != OPT_NONE)
2187                 {
2188                         opt[n++] = option_page[page][i];
2189                 }
2190         }
2191         menu_set_filter(menu, opt, n);
2192         menu->menu_data = vpage;
2193
2194         menu_layout(menu, &SCREEN_REGION);
2195
2196         for (;;)
2197         {
2198                 event_type cx;
2199                 cx = menu_select(menu, &cursor_pos, EVT_MOVE);
2200                 if (cx.type == EVT_BACK || ESCAPE == cx.key) break;
2201                 if (cx.type == EVT_MOVE) cursor_pos = cx.index;
2202                 if (cx.type == EVT_SELECT && strchr("YN", toupper(cx.key)))
2203                         cursor_pos++;
2204                 cursor_pos = (cursor_pos+n)%n;
2205         }
2206
2207         /* Hack -- Notice use of any "cheat" options */
2208         for (i = OPT_CHEAT; i < OPT_ADULT; i++)
2209         {
2210                 if (op_ptr->opt[i])
2211                 {
2212                         /* Set score option */
2213                         op_ptr->opt[OPT_SCORE + (i - OPT_CHEAT)] = TRUE;
2214                 }
2215         }
2216
2217         screen_load();
2218 }
2219
2220
2221 /*
2222  * Modify the "window" options
2223  */
2224 static void do_cmd_options_win(void)
2225 {
2226         int i, j, d;
2227
2228         int y = 0;
2229         int x = 0;
2230
2231         event_type ke;
2232
2233         u32b old_flag[ANGBAND_TERM_MAX];
2234
2235
2236         /* Memorize old flags */
2237         for (j = 0; j < ANGBAND_TERM_MAX; j++)
2238         {
2239                 old_flag[j] = op_ptr->window_flag[j];
2240         }
2241
2242
2243         /* Clear screen */
2244         clear_from(0);
2245
2246         /* Interact */
2247         while (1)
2248         {
2249                 /* Prompt */
2250                 prt("Window flags (<dir> to move, 't' to toggle, or ESC)", 0, 0);
2251
2252                 /* Display the windows */
2253                 for (j = 0; j < ANGBAND_TERM_MAX; j++)
2254                 {
2255                         byte a = TERM_WHITE;
2256
2257                         cptr s = angband_term_name[j];
2258
2259                         /* Use color */
2260                         if (j == x) a = TERM_L_BLUE;
2261
2262                         /* Window name, staggered, centered */
2263                         Term_putstr(35 + j * 5 - strlen(s) / 2, 2 + j % 2, -1, a, s);
2264                 }
2265
2266                 /* Display the options */
2267                 for (i = 0; i < PW_MAX_FLAGS; i++)
2268                 {
2269                         byte a = TERM_WHITE;
2270
2271                         cptr str = window_flag_desc[i];
2272
2273                         /* Use color */
2274                         if (i == y) a = TERM_L_BLUE;
2275
2276                         /* Unused option */
2277                         if (!str) str = "(Unused option)";
2278
2279                         /* Flag name */
2280                         Term_putstr(0, i + 5, -1, a, str);
2281
2282                         /* Display the windows */
2283                         for (j = 0; j < ANGBAND_TERM_MAX; j++)
2284                         {
2285                                 byte a = TERM_WHITE;
2286
2287                                 char c = '.';
2288
2289                                 /* Use color */
2290                                 if ((i == y) && (j == x)) a = TERM_L_BLUE;
2291
2292                                 /* Active flag */
2293                                 if (op_ptr->window_flag[j] & (1L << i)) c = 'X';
2294
2295                                 /* Flag value */
2296                                 Term_putch(35 + j * 5, i + 5, a, c);
2297                         }
2298                 }
2299
2300                 /* Place Cursor */
2301                 Term_gotoxy(35 + x * 5, y + 5);
2302
2303                 /* Get key */
2304                 ke = inkey_ex();
2305
2306                 /* Allow escape */
2307                 if ((ke.key == ESCAPE) || (ke.key == 'q')) break;
2308
2309                 /* Mouse interaction */
2310                 if (ke.key == '\xff')
2311                 {
2312                         int choicey = ke.mousey - 5;
2313                         int choicex = (ke.mousex - 35)/5;
2314
2315                         if ((choicey >= 0) && (choicey < PW_MAX_FLAGS)
2316                                 && (choicex > 0) && (choicex < ANGBAND_TERM_MAX)
2317                                 && !(ke.mousex % 5))
2318                         {
2319                                 y = choicey;
2320                                 x = (ke.mousex - 35)/5;
2321                         }
2322
2323                         /* Toggle using mousebutton later */
2324                         if (!ke.index) continue;
2325                 }
2326
2327                 /* Toggle */
2328                 if ((ke.key == '5') || (ke.key == 't') || ((ke.key == '\xff') && (ke.index)))
2329                 {
2330                         /* Hack -- ignore the main window */
2331                         if (x == 0)
2332                         {
2333                                 bell("Cannot set main window flags!");
2334                         }
2335
2336                         /* Toggle flag (off) */
2337                         else if (op_ptr->window_flag[x] & (1L << y))
2338                         {
2339                                 op_ptr->window_flag[x] &= ~(1L << y);
2340                         }
2341
2342                         /* Toggle flag (on) */
2343                         else
2344                         {
2345                                 op_ptr->window_flag[x] |= (1L << y);
2346                         }
2347
2348                         /* Continue */
2349                         continue;
2350                 }
2351
2352                 /* Extract direction */
2353                 d = target_dir(ke.key);
2354
2355                 /* Move */
2356                 if (d != 0)
2357                 {
2358                         x = (x + ddx[d] + 8) % ANGBAND_TERM_MAX;
2359                         y = (y + ddy[d] + 16) % PW_MAX_FLAGS;
2360                 }
2361
2362                 /* Oops */
2363                 else
2364                 {
2365                         bell("Illegal command for window options!");
2366                 }
2367         }
2368
2369         /* Notice changes */
2370         for (j = 0; j < ANGBAND_TERM_MAX; j++)
2371         {
2372                 term *old = Term;
2373
2374                 /* Dead window */
2375                 if (!angband_term[j]) continue;
2376
2377                 /* Ignore non-changes */
2378                 if (op_ptr->window_flag[j] == old_flag[j]) continue;
2379
2380                 /* Activate */
2381                 Term_activate(angband_term[j]);
2382
2383                 /* Erase */
2384                 Term_clear();
2385
2386                 /* Refresh */
2387                 Term_fresh();
2388
2389                 /* Restore */
2390                 Term_activate(old);
2391         }
2392 }
2393
2394
2395 /*
2396  *  Header and footer marker string for pref file dumps
2397  */
2398 static cptr dump_separator = "#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#=#";
2399
2400
2401 /*
2402  * Remove old lines from pref files
2403  */
2404 static void remove_old_dump(cptr orig_file, cptr mark)
2405 {
2406                 FILE *tmp_fff, *orig_fff;
2407
2408                 char tmp_file[1024];
2409                 char buf[1024];
2410                 bool between_marks = FALSE;
2411                 bool changed = FALSE;
2412                 char expected_line[1024];
2413
2414                 /* Open an old dump file in read-only mode */
2415                 orig_fff = my_fopen(orig_file, "r");
2416
2417                 /* If original file does not exist, nothing to do */
2418                 if (!orig_fff) return;
2419
2420                 /* Open a new temporary file */
2421                 tmp_fff = my_fopen_temp(tmp_file, sizeof(tmp_file));
2422
2423                 if (!tmp_fff)
2424                 {
2425                                 msg_format("Failed to create temporary file %s.", tmp_file);
2426                                 msg_print(NULL);
2427                                 return;
2428                 }
2429
2430                 strnfmt(expected_line, sizeof(expected_line),
2431                                                 "%s begin %s", dump_separator, mark);
2432
2433                 /* Loop for every line */
2434                 while (TRUE)
2435                 {
2436                                 /* Read a line */
2437                                 if (my_fgets(orig_fff, buf, sizeof(buf)))
2438                                 {
2439                                                 /* End of file but no end marker */
2440                                                 if (between_marks) changed = FALSE;
2441
2442                                                 break;
2443                                 }
2444
2445                                 /* Is this line a header/footer? */
2446                 if (strncmp(buf, dump_separator, strlen(dump_separator)) == 0)
2447                 {
2448                         /* Found the expected line? */
2449                         if (strcmp(buf, expected_line) == 0)
2450                         {
2451                                 if (!between_marks)
2452                                 {
2453                                         /* Expect the footer next */
2454                                         strnfmt(expected_line, sizeof(expected_line),
2455                                                 "%s end %s", dump_separator, mark);
2456
2457                                         between_marks = TRUE;
2458
2459                                         /* There are some changes */
2460                                         changed = TRUE;
2461                                 }
2462                                 else
2463                                 {
2464                                         /* Expect a header next - XXX shouldn't happen */
2465                                         strnfmt(expected_line, sizeof(expected_line),
2466                                                 "%s begin %s", dump_separator, mark);
2467
2468                                         between_marks = FALSE;
2469
2470                                         /* Next line */
2471                                         continue;
2472                                 }
2473                         }
2474                         /* Found a different line */
2475                         else
2476                         {
2477                                 /* Expected a footer and got something different? */
2478                                 if (between_marks)
2479                                 {
2480                                         /* Abort */
2481                                         changed = FALSE;
2482                                         break;
2483                                 }
2484                         }
2485                 }
2486
2487                 if (!between_marks)
2488                 {
2489                         /* Copy orginal line */
2490                         fprintf(tmp_fff, "%s\n", buf);
2491                 }
2492         }
2493
2494         /* Close files */
2495         my_fclose(orig_fff);
2496         my_fclose(tmp_fff);
2497
2498         /* If there are changes, overwrite the original file with the new one */
2499         if (changed)
2500         {
2501                 /* Copy contents of temporary file */
2502                 tmp_fff = my_fopen(tmp_file, "r");
2503                 orig_fff = my_fopen(orig_file, "w");
2504
2505                 while (!my_fgets(tmp_fff, buf, sizeof(buf)))
2506                 {
2507                         fprintf(orig_fff, "%s\n", buf);
2508                 }
2509
2510                 my_fclose(orig_fff);
2511                 my_fclose(tmp_fff);
2512         }
2513
2514         /* Kill the temporary file */
2515         fd_kill(tmp_file);
2516 }
2517
2518
2519 /*
2520  * Output the header of a pref-file dump
2521  */
2522 static void pref_header(FILE *fff, cptr mark)
2523 {
2524         /* Start of dump */
2525         fprintf(fff, "%s begin %s\n", dump_separator, mark);
2526
2527         fprintf(fff, "# *Warning!*  The lines below are an automatic dump.\n");
2528         fprintf(fff, "# Don't edit them; changes will be deleted and replaced automatically.\n");
2529 }
2530
2531 /*
2532  * Output the footer of a pref-file dump
2533  */
2534 static void pref_footer(FILE *fff, cptr mark)
2535 {
2536         fprintf(fff, "# *Warning!*  The lines above are an automatic dump.\n");
2537         fprintf(fff, "# Don't edit them; changes will be deleted and replaced automatically.\n");
2538
2539         /* End of dump */
2540         fprintf(fff, "%s end %s\n", dump_separator, mark);
2541 }
2542
2543
2544 /*
2545  * Interactively dump preferences to a file.
2546  *
2547  * - Title must have the form "Dump <pref-type>"
2548  * - dump(FILE*) needs to emit only the raw data for the dump.
2549  *   Comments are generated automatically
2550  */
2551 static void dump_pref_file(void (*dump)(FILE*), const char *title)
2552 {
2553         char ftmp[80];
2554         char buf[1025];
2555         FILE *fff;
2556
2557         int row = 15;
2558
2559         /* Prompt */
2560         prt(format("%s to a pref file", title), row, 0);
2561
2562         /* Prompt */
2563         prt("File: ", row + 2, 0);
2564
2565         /* Default filename */
2566         strnfmt(ftmp, sizeof ftmp, "%s.prf", op_ptr->base_name);
2567
2568         /* Get a filename */
2569         if (!askfor_aux(ftmp, sizeof ftmp, NULL)) return;
2570
2571         /* Build the filename */
2572         path_build(buf, 1024, ANGBAND_DIR_USER, ftmp);
2573
2574         FILE_TYPE(FILE_TYPE_TEXT);
2575
2576         /* Remove old macros */
2577         remove_old_dump(buf, title);
2578
2579         /* Append to the file */
2580         fff = my_fopen(buf, "a");
2581
2582         /* Failure */
2583         if (!fff)
2584         {
2585                 msg_print("Failed");
2586                 return;
2587         }
2588
2589         /* Output header */
2590         pref_header(fff, title);
2591
2592         /* Skip some lines */
2593         fprintf(fff, "\n\n");
2594
2595         /* Start dumping */
2596         fprintf(fff, "# %s definitions\n\n", strstr(title, " "));
2597        
2598         dump(fff);
2599
2600         /* All done */
2601         fprintf(fff, "\n\n\n");
2602
2603         /* Output footer */
2604         pref_footer(fff, title);
2605
2606         /* Close */
2607         my_fclose(fff);
2608
2609         /* Message */
2610         msg_print(format("Dumped %s", strstr(title, " ")+1));
2611 }
2612
2613 /*
2614  * Write all current options to a user preference file.
2615  */
2616 static void option_dump(FILE *fff)
2617 {
2618         int i, j;
2619
2620         /* Dump options (skip cheat, adult, score) */
2621         for (i = 0; i < OPT_CHEAT; i++)
2622         {
2623                 /* Require a real option */
2624                 if (!option_text[i]) continue;
2625
2626                 /* Comment */
2627                 fprintf(fff, "# Option '%s'\n", option_desc[i]);
2628
2629                 /* Dump the option */
2630                 if (op_ptr->opt[i])
2631                 {
2632                         fprintf(fff, "Y:%s\n", option_text[i]);
2633                 }
2634                 else
2635                 {
2636                         fprintf(fff, "X:%s\n", option_text[i]);
2637                 }
2638
2639                 /* Skip a line */
2640                 fprintf(fff, "\n");
2641         }
2642
2643         /* Dump window flags */
2644         for (i = 1; i < ANGBAND_TERM_MAX; i++)
2645         {
2646                 /* Require a real window */
2647                 if (!angband_term[i]) continue;
2648
2649                 /* Check each flag */
2650                 for (j = 0; j < (int)N_ELEMENTS(window_flag_desc); j++)
2651                 {
2652                         /* Require a real flag */
2653                         if (!window_flag_desc[j]) continue;
2654
2655                         /* Comment */
2656                         fprintf(fff, "# Window '%s', Flag '%s'\n",
2657                                 angband_term_name[i], window_flag_desc[j]);
2658
2659                         /* Dump the flag */
2660                         if (op_ptr->window_flag[i] & (1L << j))
2661                         {
2662                                 fprintf(fff, "W:%d:%d:1\n", i, j);
2663                         }
2664                         else
2665                         {
2666                                 fprintf(fff, "W:%d:%d:0\n", i, j);
2667                         }
2668
2669                         /* Skip a line */
2670                         fprintf(fff, "\n");
2671                 }
2672         }
2673 }
2674
2675 /* Hack -- Base Delay Factor */
2676 void do_cmd_delay(void)
2677 {
2678         /* Prompt */
2679         prt("Command: Base Delay Factor", 20, 0);
2680
2681         /* Get a new value */
2682         while (1)
2683         {
2684                 char cx;
2685                 int msec = op_ptr->delay_factor * op_ptr->delay_factor;
2686                 prt(format("Current base delay factor: %d (%d msec)",
2687                                            op_ptr->delay_factor, msec), 22, 0);
2688                 prt("New base delay factor (0-9 or ESC to accept): ", 21, 0);
2689
2690                 cx = inkey();
2691                 if (cx == ESCAPE) break;
2692                 if (isdigit(cx)) op_ptr->delay_factor = D2I(cx);
2693                 else bell("Illegal delay factor!");
2694         }
2695 }
2696
2697 /* Hack -- hitpoint warning factor */
2698 void do_cmd_hp_warn(void)
2699 {
2700         /* Prompt */
2701         prt("Command: Hitpoint Warning", 20, 0);
2702
2703         /* Get a new value */
2704         while (1)
2705         {
2706                 char cx;
2707                 prt(format("Current hitpoint warning: %2d%%",
2708                            op_ptr->hitpoint_warn * 10), 22, 0);
2709                 prt("New hitpoint warning (0-9 or ESC to accept): ", 21, 0);
2710
2711                 cx = inkey();
2712                 if (cx == ESCAPE) break;
2713                 if (isdigit(cx)) op_ptr->hitpoint_warn = D2I(cx);
2714                 else bell("Illegal hitpoint warning!");
2715         }
2716 }
2717
2718
2719 #ifdef ALLOW_MACROS
2720
2721 /*
2722  * append all current macros to the given file
2723  */
2724 static void macro_dump(FILE *fff)
2725 {
2726         int i;
2727         char buf[1024];
2728
2729         /* Dump them */
2730         for (i = 0; i < macro__num; i++)
2731         {
2732                 /* Start the macro */
2733                 fprintf(fff, "# Macro '%d'\n\n", i);
2734
2735                 /* Extract the macro action */
2736                 ascii_to_text(buf, sizeof(buf), macro__act[i]);
2737
2738                 /* Dump the macro action */
2739                 fprintf(fff, "A:%s\n", buf);
2740
2741                 /* Extract the macro pattern */
2742                 ascii_to_text(buf, sizeof(buf), macro__pat[i]);
2743
2744                 /* Dump the macro pattern */
2745                 fprintf(fff, "P:%s\n", buf);
2746
2747                 /* End the macro */
2748                 fprintf(fff, "\n\n");
2749         }
2750 }
2751
2752
2753 /*
2754  * Hack -- ask for a "trigger" (see below)
2755  *
2756  * Note the complex use of the "inkey()" function from "util.c".
2757  *
2758  * Note that both "flush()" calls are extremely important.  This may
2759  * no longer be true, since "util.c" is much simpler now.  XXX XXX XXX
2760  */
2761 static void do_cmd_macro_aux(char *buf)
2762 {
2763         char ch;
2764
2765         int n = 0;
2766
2767         char tmp[1024];
2768
2769
2770         /* Flush */
2771         flush();
2772
2773
2774         /* Do not process macros */
2775         inkey_base = TRUE;
2776
2777         /* First key */
2778         ch = inkey();
2779
2780         text_out_hook = text_out_to_screen;
2781
2782         /* Read the pattern */
2783         while (ch != ESCAPE && ch != '\xff')
2784         {
2785                 /* Save the key */
2786                 buf[n++] = ch;
2787                 buf[n] = 0;
2788
2789                 /* echo */
2790                 ascii_to_text(tmp, sizeof(tmp), buf+n-1);
2791                 text_out(tmp);
2792                 flush();
2793
2794
2795                 /* Do not process macros */
2796                 inkey_base = TRUE;
2797
2798                 /* Do not wait for keys */
2799                 inkey_scan = TRUE;
2800
2801                 /* Attempt to read a key */
2802                 ch = inkey();
2803         }
2804
2805         /* Convert the trigger */
2806         ascii_to_text(tmp, sizeof(tmp), buf);
2807 }
2808
2809
2810 /*
2811  * Hack -- ask for a keymap "trigger" (see below)
2812  *
2813  * Note that both "flush()" calls are extremely important.  This may
2814  * no longer be true, since "util.c" is much simpler now.  XXX XXX XXX
2815  */
2816 static void do_cmd_macro_aux_keymap(char *buf)
2817 {
2818         char tmp[1024];
2819
2820
2821         /* Flush */
2822         flush();
2823
2824
2825         /* Get a key */
2826         buf[0] = inkey();
2827         buf[1] = '\0';
2828
2829
2830         /* Convert to ascii */
2831         ascii_to_text(tmp, sizeof(tmp), buf);
2832
2833         /* Hack -- display the trigger */
2834         Term_addstr(-1, TERM_WHITE, tmp);
2835
2836
2837         /* Flush */
2838         flush();
2839 }
2840
2841
2842 /*
2843  * Hack -- Append all keymaps to the given file.
2844  *
2845  * Hack -- We only append the keymaps for the "active" mode.
2846  */
2847 static void keymap_dump(FILE *fff)
2848 {
2849         int i;
2850         int mode;
2851         char buf[1024];
2852
2853         /* Roguelike */
2854         if (rogue_like_commands)
2855         {
2856                 mode = KEYMAP_MODE_ROGUE;
2857         }
2858
2859         /* Original */
2860         else
2861         {
2862                 mode = KEYMAP_MODE_ORIG;
2863         }
2864
2865         for (i = 0; i < (int)N_ELEMENTS(keymap_act[mode]); i++)
2866         {
2867                 char key[2] = "?";
2868
2869                 cptr act;
2870
2871                 /* Loop up the keymap */
2872                 act = keymap_act[mode][i];
2873
2874                 /* Skip empty keymaps */
2875                 if (!act) continue;
2876
2877                 /* Encode the action */
2878                 ascii_to_text(buf, sizeof(buf), act);
2879
2880                 /* Dump the keymap action */
2881                 fprintf(fff, "A:%s\n", buf);
2882
2883                 /* Convert the key into a string */
2884                 key[0] = i;
2885
2886                 /* Encode the key */
2887                 ascii_to_text(buf, sizeof(buf), key);
2888
2889                 /* Dump the keymap pattern */
2890                 fprintf(fff, "C:%d:%s\n", mode, buf);
2891
2892                 /* Skip a line */
2893                 fprintf(fff, "\n");
2894         }
2895
2896 }
2897
2898 #endif
2899
2900
2901 /*
2902  * Interact with "macros"
2903  *
2904  * Could use some helpful instructions on this page.  XXX XXX XXX
2905  * CLEANUP
2906  */
2907
2908 static event_action macro_actions[] =
2909 {
2910         {LOAD_PREF, "Load a user pref file", 0},
2911 #ifdef ALLOW_MACROS
2912         {APP_MACRO, "Append macros to a file", 0},
2913         {ASK_MACRO, "Query a macro", 0},
2914         {NEW_MACRO, "Create a macro", 0},
2915         {DEL_MACRO, "Remove a macro", 0},
2916         {APP_KEYMAP, "Append keymaps to a file", 0},
2917         {ASK_KEYMAP, "Query a keymap", 0},
2918         {NEW_KEYMAP, "Create a keymap", 0},
2919         {DEL_KEYMAP, "Remove a keymap", 0},
2920         {ENTER_ACT, "Enter a new action", 0}
2921 #endif /* ALLOW_MACROS */
2922 };
2923
2924 static menu_type macro_menu;
2925
2926
2927 void do_cmd_macros(void)
2928 {
2929
2930         char tmp[1024];
2931
2932         char pat[1024];
2933
2934         int mode;
2935         int cursor = 0;
2936
2937         region loc = {0, 0, 0, 12};
2938
2939         /* Roguelike */
2940         if (rogue_like_commands)
2941         {
2942                 mode = KEYMAP_MODE_ROGUE;
2943         }
2944
2945         /* Original */
2946         else
2947         {
2948                 mode = KEYMAP_MODE_ORIG;
2949         }
2950
2951
2952         /* File type is "TEXT" */
2953         FILE_TYPE(FILE_TYPE_TEXT);
2954
2955         screen_save();
2956
2957         menu_layout(&macro_menu, &loc);
2958
2959         /* Process requests until done */
2960         while (1)
2961         {
2962                 event_type c;
2963                 int evt;
2964
2965                 /* Clear screen */
2966                 clear_from(0);
2967
2968                 /* Describe current action */
2969                 prt("Current action (if any) shown below:", 13, 0);
2970
2971                 /* Analyze the current action */
2972                 ascii_to_text(tmp, sizeof(tmp), macro_buffer);
2973
2974                 /* Display the current action */
2975                 prt(tmp, 14, 0);
2976                 c = menu_select(&macro_menu, &cursor, EVT_CMD);
2977
2978
2979                 if (ESCAPE == c.key) break;
2980                 evt = macro_actions[cursor].id;
2981
2982                 switch(evt)
2983                 {
2984                 case LOAD_PREF:
2985                 {
2986                         do_cmd_pref_file_hack(16);
2987                         break;
2988                 }
2989
2990 #ifdef ALLOW_MACROS
2991                 case APP_MACRO:
2992                 {
2993                         /* Dump the macros */
2994                         (void)dump_pref_file(macro_dump, "Dump Macros");
2995
2996                         break;
2997                 }
2998
2999                 case ASK_MACRO:
3000                 {
3001                         int k;
3002
3003                         /* Prompt */
3004                         prt("Command: Query a macro", 16, 0);
3005
3006                         /* Prompt */
3007                         prt("Trigger: ", 18, 0);
3008
3009                         /* Get a macro trigger */
3010                         do_cmd_macro_aux(pat);
3011
3012                         /* Get the action */
3013                         k = macro_find_exact(pat);
3014
3015                         /* Nothing found */
3016                         if (k < 0)
3017                         {
3018                                 /* Prompt */
3019                                 msg_print("Found no macro.");
3020                         }
3021
3022                         /* Found one */
3023                         else
3024                         {
3025                                 /* Obtain the action */
3026                                 my_strcpy(macro_buffer, macro__act[k], sizeof(macro_buffer));
3027
3028                                 /* Analyze the current action */
3029                                 ascii_to_text(tmp, sizeof(tmp), macro_buffer);
3030
3031                                 /* Display the current action */
3032                                 prt(tmp, 22, 0);
3033
3034                                 /* Prompt */
3035                                 msg_print("Found a macro.");
3036                         }
3037                         break;
3038                 }
3039
3040                 case NEW_MACRO:
3041                 {
3042                         /* Prompt */
3043                         prt("Command: Create a macro", 16, 0);
3044
3045                         /* Prompt */
3046                         prt("Trigger: ", 18, 0);
3047
3048                         /* Get a macro trigger */
3049                         do_cmd_macro_aux(pat);
3050
3051                         /* Clear */
3052                         clear_from(20);
3053
3054                         /* Prompt */
3055                         prt("Action: ", 20, 0);
3056
3057                         /* Convert to text */
3058                         ascii_to_text(tmp, sizeof(tmp), macro_buffer);
3059
3060                         /* Get an encoded action */
3061                         if (askfor_aux(tmp, sizeof tmp, NULL))
3062                         {
3063                                 /* Convert to ascii */
3064                                 text_to_ascii(macro_buffer, sizeof(macro_buffer), tmp);
3065
3066                                 /* Link the macro */
3067                                 macro_add(pat, macro_buffer);
3068
3069                                 /* Prompt */
3070                                 msg_print("Added a macro.");
3071                         }
3072                         break;
3073                 }
3074
3075                 case DEL_MACRO:
3076                 {
3077                         /* Prompt */
3078                         prt("Command: Remove a macro", 16, 0);
3079
3080                         /* Prompt */
3081                         prt("Trigger: ", 18, 0);
3082
3083                         /* Get a macro trigger */
3084                         do_cmd_macro_aux(pat);
3085
3086                         /* Link the macro */
3087                         macro_add(pat, pat);
3088
3089                         /* Prompt */
3090                         msg_print("Removed a macro.");
3091                         break;
3092                 }
3093                 case APP_KEYMAP:
3094                 {
3095                         /* Dump the keymaps */
3096                         (void)dump_pref_file(keymap_dump, "Dump Keymaps");
3097                         break;
3098                 }
3099                 case ASK_KEYMAP:
3100                 {
3101                         cptr act;
3102
3103                         /* Prompt */
3104                         prt("Command: Query a keymap", 16, 0);
3105
3106                         /* Prompt */
3107                         prt("Keypress: ", 18, 0);
3108
3109                         /* Get a keymap trigger */
3110                         do_cmd_macro_aux_keymap(pat);
3111
3112                         /* Look up the keymap */
3113                         act = keymap_act[mode][(byte)(pat[0])];
3114
3115                         /* Nothing found */
3116                         if (!act)
3117                         {
3118                                 /* Prompt */
3119                                 msg_print("Found no keymap.");
3120                         }
3121
3122                         /* Found one */
3123                         else
3124                         {
3125                                 /* Obtain the action */
3126                                 my_strcpy(macro_buffer, act, sizeof(macro_buffer));
3127
3128                                 /* Analyze the current action */
3129                                 ascii_to_text(tmp, sizeof(tmp), macro_buffer);
3130
3131                                 /* Display the current action */
3132                                 prt(tmp, 22, 0);
3133
3134                                 /* Prompt */
3135                                 msg_print("Found a keymap.");
3136                         }
3137                         break;
3138                 }
3139                 case NEW_KEYMAP:
3140                 {
3141                         /* Prompt */
3142                         prt("Command: Create a keymap", 16, 0);
3143
3144                         /* Prompt */
3145                         prt("Keypress: ", 18, 0);
3146
3147                         /* Get a keymap trigger */
3148                         do_cmd_macro_aux_keymap(pat);
3149
3150                         /* Clear */
3151                         clear_from(20);
3152
3153                         /* Prompt */
3154                         prt("Action: ", 20, 0);
3155
3156                         /* Convert to text */
3157                         ascii_to_text(tmp, sizeof(tmp), macro_buffer);
3158
3159                         /* Get an encoded action */
3160                         if (askfor_aux(tmp, sizeof tmp, NULL))
3161                         {
3162                                 /* Convert to ascii */
3163                                 text_to_ascii(macro_buffer, sizeof(macro_buffer), tmp);
3164
3165                                 /* Free old keymap */
3166                                 string_free(keymap_act[mode][(byte)(pat[0])]);
3167
3168                                 /* Make new keymap */
3169                                 keymap_act[mode][(byte)(pat[0])] = string_make(macro_buffer);
3170
3171                                 /* Prompt */
3172                                 msg_print("Added a keymap.");
3173                         }
3174                         break;
3175                 }
3176                 case DEL_KEYMAP:
3177                 {
3178                         /* Prompt */
3179                         prt("Command: Remove a keymap", 16, 0);
3180
3181                         /* Prompt */
3182                         prt("Keypress: ", 18, 0);
3183
3184                         /* Get a keymap trigger */
3185                         do_cmd_macro_aux_keymap(pat);
3186
3187                         /* Free old keymap */
3188                         string_free(keymap_act[mode][(byte)(pat[0])]);
3189
3190                         /* Make new keymap */
3191                         keymap_act[mode][(byte)(pat[0])] = NULL;
3192
3193                         /* Prompt */
3194                         msg_print("Removed a keymap.");
3195                         break;
3196                 }
3197                 case ENTER_ACT: /* Enter a new action */
3198                 {
3199                         /* Prompt */
3200                         prt("Command: Enter a new action", 16, 0);
3201
3202                         /* Go to the correct location */
3203                         Term_gotoxy(0, 22);
3204
3205                         /* Analyze the current action */
3206                         ascii_to_text(tmp, sizeof(tmp), macro_buffer);
3207
3208                         /* Get an encoded action */
3209                         if (askfor_aux(tmp, sizeof tmp, NULL))
3210                         {
3211                                 /* Extract an action */
3212                                 text_to_ascii(macro_buffer, sizeof(macro_buffer), tmp);
3213                         }
3214                         break;
3215                 }
3216 #endif /* ALLOW_MACROS */
3217                 }
3218
3219                 /* Flush messages */
3220                 message_flush();
3221         }
3222
3223         /* Load screen */
3224         screen_load();
3225 }
3226
3227
3228 /* Dump monsters */
3229 static void dump_monsters(FILE *fff)
3230 {
3231         int i;
3232         for (i = 0; i < z_info->r_max; i++)
3233         {
3234                 monster_race *r_ptr = &r_info[i];
3235
3236                 /* Skip non-entries */
3237                 if (!r_ptr->name) continue;
3238
3239                 /* Dump a comment */
3240                 fprintf(fff, "# %s\n", (r_name + r_ptr->name));
3241
3242                 /* Dump the monster attr/char info */
3243                 fprintf(fff, "R:%d:0x%02X:0x%02X\n\n", i,
3244                         (byte)(r_ptr->x_attr), (byte)(r_ptr->x_char));
3245         }
3246 }
3247
3248 /* Dump objects */
3249 static void dump_objects(FILE *fff)
3250 {
3251         int i;
3252         for (i = 0; i < z_info->k_max; i++)
3253         {
3254                 object_kind *k_ptr = &k_info[i];
3255
3256                 /* Skip non-entries */
3257                 if (!k_ptr->name) continue;
3258
3259                 /* Dump a comment */
3260                 fprintf(fff, "# %s\n", (k_name + k_ptr->name));
3261
3262                 /* Dump the object attr/char info */
3263                 fprintf(fff, "K:%d:0x%02X:0x%02X\n\n", i,
3264                                         (byte)(k_ptr->x_attr), (byte)(k_ptr->x_char));
3265         }
3266 }
3267
3268 /* Dump features */
3269 static void dump_features(FILE *fff)
3270 {
3271         int i;
3272         for (i = 0; i < z_info->f_max; i++)
3273         {
3274                 feature_type *f_ptr = &f_info[i];
3275
3276                 /* Skip non-entries */
3277                 if (!f_ptr->name) continue;
3278
3279                 /* Skip mimic entries -- except invisible trap */
3280                 if ((f_ptr->mimic != i) && (i != FEAT_INVIS)) continue;
3281
3282                 /* Dump a comment */
3283                 fprintf(fff, "# %s\n", (f_name + f_ptr->name));
3284
3285                 /* Dump the feature attr/char info */
3286                 /* Dump the feature attr/char info */
3287                 fprintf(fff, "F:%d:0x%02X:0x%02X\n\n", i,
3288                                                 (byte)(f_ptr->x_attr), (byte)(f_ptr->x_char));
3289
3290
3291         }
3292 }
3293
3294 /* Dump flavors */
3295 static void dump_flavors(FILE *fff)
3296 {
3297         int i;
3298         for (i = 0; i < z_info->flavor_max; i++)
3299         {
3300                 flavor_type *x_ptr = &flavor_info[i];
3301
3302                 /* Dump a comment */
3303                 fprintf(fff, "# %s\n", (flavor_text + x_ptr->text));
3304
3305                 /* Dump the flavor attr/char info */
3306                 fprintf(fff, "L:%d:0x%02X:0x%02X\n\n", i,
3307                         (byte)(x_ptr->x_attr), (byte)(x_ptr->x_char));
3308         }
3309 }
3310
3311 /* Dump colors */
3312 static void dump_colors(FILE *fff)
3313 {
3314         int i;
3315         for (i = 0; i < MAX_COLORS; i++)
3316         {
3317                 int kv = angband_color_table[i][0];
3318                 int rv = angband_color_table[i][1];
3319                 int gv = angband_color_table[i][2];
3320                 int bv = angband_color_table[i][3];
3321
3322                 cptr name = "unknown";
3323
3324                 /* Skip non-entries */
3325                 if (!kv && !rv && !gv && !bv) continue;
3326
3327                 /* Extract the color name */
3328                 if (i < BASIC_COLORS) name = color_names[i];
3329
3330                 /* Dump a comment */
3331                 fprintf(fff, "# Color '%s'\n", name);
3332
3333         }
3334 }
3335
3336
3337 int modify_attribute(const char *clazz, int oid, const char *name,
3338                                                         byte da, char dc, byte *pca, char *pcc)
3339 {
3340         int cx;
3341         const char *empty_symbol = "<< ? >>";
3342         const char *empty_symbol2 = "\0";
3343         const char *empty_symbol3 = "\0";
3344
3345         byte ca = (byte)*pca;
3346         byte cc = (byte)*pcc;
3347
3348         int linec = (use_trptile ? 22: (use_dbltile ? 21 : 20));
3349
3350         if (use_trptile && use_bigtile)
3351         {
3352                 empty_symbol = "// ?????? \\\\";
3353                 empty_symbol2 = "   ??????   ";
3354                 empty_symbol3 = "\\\\ ?????? //";
3355         }
3356         else if (use_dbltile && use_bigtile)
3357         {
3358                 empty_symbol = "// ???? \\\\";
3359                 empty_symbol2 = "\\\\ ???? //";
3360         }
3361         else if (use_trptile)
3362         {
3363                 empty_symbol = "// ??? \\\\";
3364                 empty_symbol2 = "   ???   ";
3365                 empty_symbol3 = "\\\\ ??? //";
3366         }
3367         else if (use_dbltile)
3368         {
3369                 empty_symbol = "// ?? \\\\";