1 /**
2 * \file ui-equip-cmp.c
3 * \brief Supply a "resistance grid for home" in the knowledge menu
4 *
5 * This work is free software; you can redistribute it and/or modify it
6 * under the terms of either:
7 *
8 * a) the GNU General Public License as published by the Free Software
9 * Foundation, version 2, or
10 *
11 * b) the "Angband licence":
12 * This software may be copied and distributed for educational, research,
13 * and not for profit purposes provided that this copyright and statement
14 * are included in all such copies. Other copyrights may also apply.
15 */
16
17 #include "cave.h"
18 #include "game-input.h"
19 #include "init.h"
20 #include "object.h"
21 #include "obj-desc.h"
22 #include "obj-gear.h"
23 #include "obj-ignore.h"
24 #include "obj-info.h"
25 #include "obj-tval.h"
26 #include "player.h"
27 #include "store.h"
28 #include "ui-entry.h"
29 #include "ui-entry-renderers.h"
30 #include "ui-equip-cmp.h"
31 #include "ui-event.h"
32 #include "ui-input.h"
33 #include "ui-object.h"
34 #include "ui-output.h"
35 #include "ui-term.h"
36 #include "z-color.h"
37 #include "z-file.h"
38 #include "z-textblock.h"
39 #include "z-util.h"
40 #include "z-virt.h"
41
42 enum equippable_source {
43 EQUIP_SOURCE_WORN,
44 EQUIP_SOURCE_PACK,
45 EQUIP_SOURCE_FLOOR,
46 EQUIP_SOURCE_HOME,
47 EQUIP_SOURCE_STORE
48 };
49
50 enum equippable_quality {
51 EQUIP_QUAL_ARTIFACT,
52 EQUIP_QUAL_EGO,
53 EQUIP_QUAL_GOOD,
54 EQUIP_QUAL_AVERAGE,
55 EQUIP_QUAL_BAD
56 };
57
58 struct equippable {
59 char *short_name;
60 int *vals;
61 int *auxvals;
62 const struct object *obj;
63 enum equippable_source src;
64 enum equippable_quality qual;
65 int slot;
66 int nmlen;
67 wchar_t ch;
68 byte at;
69 };
70
71 union equippable_selfunc_extra {
72 enum equippable_source src;
73 enum equippable_quality qual;
74 int slot;
75 int propind;
76 };
77 typedef bool (*equippable_selfunc)(const struct equippable *eq,
78 const union equippable_selfunc_extra *ex);
79 struct equippable_selector {
80 equippable_selfunc func;
81 union equippable_selfunc_extra ex;
82 };
83 enum equippable_expr_class {
84 EQUIP_EXPR_SELECTOR,
85 EQUIP_EXPR_AND,
86 EQUIP_EXPR_OR,
87 EQUIP_EXPR_TERMINATOR
88 };
89 struct equippable_expr {
90 struct equippable_selector s;
91 enum equippable_expr_class c;
92 };
93 struct equippable_filter {
94 /*
95 * Has nv + 1 used elements with the last a sentinel (c ==
96 * EQUIP_EXPR_TERMINATOR).
97 */
98 struct equippable_expr *v;
99 /*
100 * Use EQUIP_EXPR_SELECTOR to indicate the simple evaluation is not
101 * possible. Use EQUIP_EXPR_TERMINATOR when nv is zero: there's no
102 * filtering. The others indicate the way the terms are combined
103 * during simple evaluation.
104 */
105 enum equippable_expr_class simple;
106 int nv;
107 int nalloc;
108 };
109
110 typedef int (*equippable_cmpfunc)(const struct equippable *left,
111 const struct equippable *right, int propind);
112 struct equippable_arranger {
113 equippable_cmpfunc func;
114 int propind;
115 };
116 struct equippable_sorter {
117 /* Has nv + 1 used elements with the last a sentinel (funct == 0). */
118 struct equippable_arranger *v;
119 int nv;
120 int nalloc;
121 };
122
123 enum store_inclusion {
124 EQUIPPABLE_NO_STORE,
125 EQUIPPABLE_ONLY_STORE,
126 EQUIPPABLE_YES_STORE,
127 EQUIPPABLE_ONLY_CARRIED,
128 };
129
130 struct prop_category {
131 struct ui_entry **entries;
132 wchar_t **labels;
133 wchar_t *label_buffer;
134 int n, off, nvw[3], ivw[3];
135 };
136
137 struct equippable_summary {
138 /* Has space for nalloc items; nitems are currently used */
139 struct equippable *items;
140 /*
141 * Has space for nalloc + 1 items; nfilt + 1 are currently used with
142 * the last a sentinel (-1).
143 */
144 int *sorted_indices;
145 int *p_and_eq_vals;
146 int *p_and_eq_auxvals;
147 const char *dlg_trans_msg;
148 struct prop_category propcats[5];
149 struct equippable_filter easy_filt;
150 /*
151 * Was intended to be an alternative to easy_filt where the player
152 * could configure a filter on more than one attribute. Backend
153 * support is, in principle there, but there's currently in place to
154 * allow the player to set it up. Leaving it here for now just in
155 * case. config_filt was to be what the player specified compiled to
156 * what the backend wants. config_mod_filt would be config_filt
157 * modified to handle the filtering of items from the stores as set
158 * by 'c'.
159 */
160 struct equippable_filter config_filt;
161 struct equippable_filter config_mod_filt;
162 struct equippable_sorter default_sort;
163 /*
164 * Was intended to be an alternative to the default sort that the
165 * player could configure. While backend support should mostly be
166 * there, there's nothing that allows the player to do the
167 * configuration. Leaving it here for now, just in case.
168 */
169 struct equippable_sorter config_sort;
170 enum store_inclusion stores;
171 /* Is the index, in sorted_indices, for first shown on page. */
172 int ifirst;
173 int indinc;
174 int iview;
175 /* Are indices into items for selection. */
176 int isel0, isel1;
177 /* Is the index, into sorted_indices, for choosing selection. */
178 int work_sel;
179 /* Is the number shown on current page. */
180 int npage;
181 int nfilt;
182 int nitems;
183 int nalloc;
184 int nprop;
185 /* Is the maximum number that can be shown on any page. */
186 int maxpage;
187 /* Is the maximum number of characters for a short object name. */
188 int nshortnm;
189 /* Is the number of character for propery labels. */
190 int nproplab;
191 /*
192 * Is the number of views to use for displaying the properties, either
193 * two or three.
194 */
195 int nview;
196 /*
197 * Is the row used for display of the combined player and current
198 * equipment properties.
199 */
200 int irow_combined_equip;
201 /* Is the column where the object names start. */
202 int icol_name;
203 /*
204 * These two are the terminal dimensions used when configuring the
205 * layout.
206 */
207 int term_ncol, term_nrow;
208 bool config_filt_is_on;
209 bool config_sort_is_on;
210 };
211
212 struct indirect_sort_data {
213 const struct equippable *items;
214 const struct equippable_arranger *arr;
215 };
216
217
218 /* These have to match up with the state array in equip_cmp_display(). */
219 enum {
220 EQUIP_CMP_MENU_DONE,
221 EQUIP_CMP_MENU_BAIL,
222 EQUIP_CMP_MENU_NEW_PAGE,
223 EQUIP_CMP_MENU_SAME_PAGE,
224 EQUIP_CMP_MENU_SEL0,
225 EQUIP_CMP_MENU_SEL1,
226 };
227 struct menu_display_state {
228 const char *prompt;
229 int (*keyfunc)(struct keypress ch, int istate,
230 struct equippable_summary *s, struct player *p);
231 bool clear;
232 bool refresh;
233 };
234
235
236 static int initialize_summary(struct player *p,
237 struct equippable_summary **s);
238 static void cleanup_summary(struct equippable_summary *s);
239 static void cleanup_summary_items(struct equippable_summary *s);
240 static int display_page(struct equippable_summary *s, const struct player *p,
241 bool allow_reconfig);
242 static void display_equip_cmp_help(void);
243 static void display_equip_cmp_sel_help(void);
244 static int get_expected_easy_filter_count(enum store_inclusion stores);
245 static int handle_key_bail(struct keypress ch, int istate,
246 struct equippable_summary *s, struct player *p);
247 static int handle_key_equip_cmp_general(struct keypress ch, int istate,
248 struct equippable_summary *s, struct player *p);
249 static int handle_key_equip_cmp_select(struct keypress ch, int istate,
250 struct equippable_summary *s, struct player *p);
251 static int prompt_for_easy_filter(struct equippable_summary *s, bool apply_not);
252 static void display_object_comparison(const struct equippable_summary *s);
253 static bool dump_to_file(const char *path);
254 static void append_to_file(ang_file *fff);
255 static void filter_items(struct equippable_summary *s);
256 /*
257 * Not used at the moment; left here in case more configurable filtering
258 * is implemented.
259 */
260 #if 0
261 static bool sel_better_than(const struct equippable *eq,
262 const union equippable_selfunc_extra *ex);
263 #endif
264 static bool sel_at_least_resists(const struct equippable *eq,
265 const union equippable_selfunc_extra *ex);
266 static bool sel_does_not_resist(const struct equippable *eq,
267 const union equippable_selfunc_extra *ex);
268 static bool sel_has_flag(const struct equippable *eq,
269 const union equippable_selfunc_extra *ex);
270 static bool sel_does_not_have_flag(const struct equippable *eq,
271 const union equippable_selfunc_extra *ex);
272 static bool sel_has_pos_mod(const struct equippable *eq,
273 const union equippable_selfunc_extra *ex);
274 static bool sel_has_nonpos_mod(const struct equippable *eq,
275 const union equippable_selfunc_extra *ex);
276 /*
277 * Not used at the moment; left here in case more configurable filtering
278 * is implemented.
279 */
280 #if 0
281 static bool sel_exclude_slot(const struct equippable *eq,
282 const union equippable_selfunc_extra *ex);
283 static bool sel_only_slot(const struct equippable *eq,
284 const union equippable_selfunc_extra *ex);
285 #endif
286 static bool sel_exclude_src(const struct equippable *eq,
287 const union equippable_selfunc_extra *ex);
288 static bool sel_only_src(const struct equippable *eq,
289 const union equippable_selfunc_extra *ex);
290 static void sort_items(struct equippable_summary *s);
291 static wchar_t source_to_char(enum equippable_source src);
292
293
294 static struct equippable_summary *the_summary = NULL;
295 /* Used by append_to_file() (and therefore for dump_to_file()) */
296 static struct equippable_summary *s_for_file = NULL;
297 static struct indirect_sort_data sort_dat;
298
299
300
equip_cmp_display(void)301 void equip_cmp_display(void)
302 {
303 const struct menu_display_state states[] = {
304 /* EQUIP_CMP_MENU_DONE */
305 { "", 0, false, false },
306 /* EQUIP_CMP_MENU_BAIL */
307 { "Sorry, could not display. Press any key.",
308 handle_key_bail, true, false },
309 /* EQUIP_CMP_MENU_NEW_PAGE */
310 { "[Up/Down arrow, p/PgUp, n/PgDn to move; ? for help; ESC to exit]", handle_key_equip_cmp_general, true, true },
311 /* EQUIP_CMP_MENU_SAME_PAGE */
312 { "[Up/Down arrow, p/PgUp, n/PgDn to move; ? for help; ESC to exit]", handle_key_equip_cmp_general, false, false },
313 /* EQUIP_CMP_MENU_SEL0 */
314 { "[Up/Down arrow, p/PgUp, n/PgDn to move; return to accept]", handle_key_equip_cmp_select, true, true },
315 /* EQUIP_CMP_MENU_SEL1 */
316 { "[Up/Down arrow, p/PgUp, n/PgDn to move; return to accept]", handle_key_equip_cmp_select, true, true },
317 };
318 int istate;
319
320 screen_save();
321
322 if (initialize_summary(player, &the_summary)) {
323 istate = EQUIP_CMP_MENU_BAIL;
324 } else {
325 istate = EQUIP_CMP_MENU_NEW_PAGE;
326 }
327
328 while (istate != EQUIP_CMP_MENU_DONE) {
329 struct keypress ch;
330 int wid, hgt;
331
332 assert(istate >= 0 && istate < (int)N_ELEMENTS(states));
333 if (states[istate].clear) {
334 Term_clear();
335 }
336 if (states[istate].refresh) {
337 if (display_page(the_summary, player, true)) {
338 istate = EQUIP_CMP_MENU_BAIL;
339 }
340 }
341
342 /*
343 * Use row 0 for state transition or other informational
344 * messages
345 */
346 if (the_summary->dlg_trans_msg != NULL &&
347 the_summary->dlg_trans_msg[0] != '\0') {
348 prt(the_summary->dlg_trans_msg, 0, 0);
349 the_summary->dlg_trans_msg = NULL;
350 } else {
351 if (the_summary->nfilt == 0) {
352 prt("No items; use q, !, c, or R to change filter", 0, 0);
353 } else if (! states[istate].clear) {
354 prt("", 0, 0);
355 }
356 }
357
358 /* Use last row for prompt. */
359 Term_get_size(&wid, &hgt);
360 prt(states[istate].prompt, hgt - 1, 0);
361
362 ch = inkey();
363 istate = (*states[istate].keyfunc)(ch, istate, the_summary,
364 player);
365 }
366
367 screen_load();
368 }
369
370
display_equip_cmp_help(void)371 static void display_equip_cmp_help(void)
372 {
373 int irow, wid, hgt;
374
375 Term_clear();
376 irow = 1;
377 prt("Movement/scrolling ---------------------------------", irow, 0);
378 ++irow;
379 prt("Down arrow one line down Up arrow one line up", irow, 0);
380 ++irow;
381 prt("n, PgDn one page down p, PgUp one page up", irow, 0);
382 ++irow;
383 prt("space one page down", irow, 0);
384 ++irow;
385 prt("Filtering/searching/sorting ------------------------", irow, 0);
386 ++irow;
387 prt("q quick filter ! use opposite quick", irow, 0);
388 ++irow;
389 prt("c cycle through sources of items", irow, 0);
390 ++irow;
391 prt("r reverse", irow, 0);
392 ++irow;
393 prt("Information ----------------------------------------", irow, 0);
394 ++irow;
395 prt("v cycle through attribute views", irow, 0);
396 ++irow;
397 prt("I, x select one or two items for details", irow, 0);
398 ++irow;
399 prt("Other ----------------------------------------------", irow, 0);
400 ++irow;
401 prt("d dump to file R reset display", irow, 0);
402 ++irow;
403 prt("ESC exit", irow, 0);
404 ++irow;
405
406 Term_get_size(&wid, &hgt);
407 prt("Press any key to continue", hgt - 1, 0);
408 (void) inkey();
409 }
410
411
get_expected_easy_filter_count(enum store_inclusion stores)412 static int get_expected_easy_filter_count(enum store_inclusion stores)
413 {
414 switch (stores) {
415 case EQUIPPABLE_NO_STORE:
416 case EQUIPPABLE_ONLY_STORE:
417 return 1;
418
419 case EQUIPPABLE_YES_STORE:
420 return 0;
421
422 case EQUIPPABLE_ONLY_CARRIED:
423 return 3;
424
425 default:
426 assert(0);
427 return 0;
428 }
429 }
430
431
handle_key_bail(struct keypress ch,int istate,struct equippable_summary * s,struct player * p)432 static int handle_key_bail(struct keypress ch, int istate,
433 struct equippable_summary *s, struct player *p)
434 {
435 return EQUIP_CMP_MENU_DONE;
436 }
437
438
handle_key_equip_cmp_general(struct keypress ch,int istate,struct equippable_summary * s,struct player * p)439 static int handle_key_equip_cmp_general(struct keypress ch, int istate,
440 struct equippable_summary *s, struct player *p)
441 {
442 static const char *trans_msg_unknown_key =
443 "Unknown key pressed; ? will list available keys";
444 static const char *trans_msg_view =
445 "Showing alternate attributes; press v to cycle";
446 static const char *trans_msg_onlystore =
447 "Only showing goods from stores; press c to change";
448 static const char *trans_msg_withstore =
449 "Showing possessions and goods from stores; press c to change";
450 static const char *trans_msg_carried =
451 "Only showing carried items; press c to change";
452 static const char *trans_msg_save_ok = "Successfully saved to file";
453 static const char *trans_msg_save_bad = "Failed to save to file!";
454 static const char *trans_msg_sel0 = "Select first item to examine";
455 int result;
456 int ilast;
457 int nfilt;
458
459 switch (ch.code) {
460 case 'n':
461 case ' ':
462 case KC_PGDOWN:
463 if (s->npage == s->maxpage) {
464 ilast = s->ifirst + 2 * s->indinc * s->npage;
465 if (ilast > s->nfilt) {
466 assert(s->indinc > 0);
467 s->ifirst = s->nfilt + 1 - s->npage;
468 --s->npage;
469 } else if (ilast < 0) {
470 assert(s->indinc < 0);
471 s->ifirst = s->npage - 2;
472 --s->npage;
473 } else {
474 s->ifirst += s->indinc * s->npage;
475 }
476 result = EQUIP_CMP_MENU_NEW_PAGE;
477 } else {
478 /* Page already includes the end, so don't move. */
479 result = EQUIP_CMP_MENU_SAME_PAGE;
480 }
481 break;
482
483 case 'p':
484 case KC_PGUP:
485 if (s->indinc > 0) {
486 if (s->ifirst > 0) {
487 s->ifirst = (s->ifirst < s->maxpage) ?
488 0 : s->ifirst - s->maxpage;
489 s->npage = (s->ifirst + s->maxpage <= s->nfilt) ?
490 s->maxpage : s->nfilt - s->ifirst;
491 result = EQUIP_CMP_MENU_NEW_PAGE;
492 } else {
493 result = EQUIP_CMP_MENU_SAME_PAGE;
494 }
495 } else {
496 if (s->ifirst < s->nfilt - 1) {
497 s->ifirst = (s->ifirst >= s->nfilt - s->maxpage) ?
498 s->nfilt - 1 : s->ifirst + s->maxpage;
499 s->npage = (s->ifirst - s->maxpage >= -1) ?
500 s->maxpage : s->ifirst + 1;
501 result = EQUIP_CMP_MENU_NEW_PAGE;
502 } else {
503 result = EQUIP_CMP_MENU_SAME_PAGE;
504 }
505 }
506 break;
507
508 case ARROW_DOWN:
509 if (s->npage == s->maxpage) {
510 s->ifirst += s->indinc;
511 ilast = s->ifirst + s->npage * s->indinc;
512 if (ilast < -1 || ilast > s->nfilt) {
513 --s->npage;
514 }
515 result = EQUIP_CMP_MENU_NEW_PAGE;
516 } else {
517 /* Page already includes the end, so don't move. */
518 result = EQUIP_CMP_MENU_SAME_PAGE;
519 }
520 break;
521
522 case ARROW_UP:
523 if (s->indinc > 0) {
524 if (s->ifirst > 0) {
525 --s->ifirst;
526 if (s->npage < s->maxpage &&
527 s->ifirst + s->npage < s->nfilt) {
528 ++s->npage;
529 }
530 result = EQUIP_CMP_MENU_NEW_PAGE;
531 } else {
532 result = EQUIP_CMP_MENU_SAME_PAGE;
533 }
534 } else {
535 if (s->ifirst < s->nfilt - 1) {
536 ++s->ifirst;
537 if (s->npage < s->maxpage &&
538 s->ifirst - s->npage > -1) {
539 ++s->npage;
540 }
541 result = EQUIP_CMP_MENU_NEW_PAGE;
542 } else {
543 result = EQUIP_CMP_MENU_SAME_PAGE;
544 }
545 }
546 break;
547
548 case 'c':
549 /*
550 * Cycle through no goods from stores (default), only goods
551 * from stores, both possessions and gopds from stores,
552 * and only what's carried (either equipped or in pack).
553 */
554 switch (s->stores) {
555 case EQUIPPABLE_NO_STORE:
556 assert(s->easy_filt.simple == EQUIP_EXPR_AND &&
557 (s->easy_filt.nv == 1 || s->easy_filt.nv == 2) &&
558 s->easy_filt.v[0].s.func == sel_exclude_src &&
559 s->easy_filt.v[0].s.ex.src == EQUIP_SOURCE_STORE);
560 s->easy_filt.v[0].s.func = sel_only_src;
561 s->stores = EQUIPPABLE_ONLY_STORE;
562 s->dlg_trans_msg = trans_msg_onlystore;
563 break;
564
565 case EQUIPPABLE_ONLY_STORE:
566 assert(s->easy_filt.simple == EQUIP_EXPR_AND &&
567 (s->easy_filt.nv == 1 || s->easy_filt.nv == 2) &&
568 s->easy_filt.v[0].s.func == sel_only_src &&
569 s->easy_filt.v[0].s.ex.src == EQUIP_SOURCE_STORE);
570 s->easy_filt.v[0] = s->easy_filt.v[1];
571 if (s->easy_filt.nv == 2) {
572 s->easy_filt.v[1] = s->easy_filt.v[2];
573 } else {
574 s->easy_filt.simple = EQUIP_EXPR_TERMINATOR;
575 }
576 --s->easy_filt.nv;
577 s->stores = EQUIPPABLE_YES_STORE;
578 s->dlg_trans_msg = trans_msg_withstore;
579 break;
580
581 case EQUIPPABLE_YES_STORE:
582 assert(s->easy_filt.nv == 0 || s->easy_filt.nv == 1);
583 s->easy_filt.v[3] = s->easy_filt.v[0];
584 s->easy_filt.simple = EQUIP_EXPR_AND;
585 s->easy_filt.v[0].s.func = sel_exclude_src;
586 s->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
587 s->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
588 s->easy_filt.v[1].s.func = sel_exclude_src;
589 s->easy_filt.v[1].s.ex.src = EQUIP_SOURCE_HOME;
590 s->easy_filt.v[1].c = EQUIP_EXPR_SELECTOR;
591 s->easy_filt.v[2].s.func = sel_exclude_src;
592 s->easy_filt.v[2].s.ex.src = EQUIP_SOURCE_FLOOR;
593 s->easy_filt.v[2].c = EQUIP_EXPR_SELECTOR;
594 s->easy_filt.nv += 3;
595 s->stores = EQUIPPABLE_ONLY_CARRIED;
596 s->dlg_trans_msg = trans_msg_carried;
597 break;
598
599 case EQUIPPABLE_ONLY_CARRIED:
600 assert(s->easy_filt.simple = EQUIP_EXPR_AND &&
601 (s->easy_filt.nv == 3 || s->easy_filt.nv == 4) &&
602 s->easy_filt.v[0].s.func == sel_exclude_src &&
603 s->easy_filt.v[0].s.ex.src == EQUIP_SOURCE_STORE &&
604 s->easy_filt.v[1].s.func == sel_exclude_src &&
605 s->easy_filt.v[1].s.ex.src == EQUIP_SOURCE_HOME &&
606 s->easy_filt.v[2].s.func == sel_exclude_src &&
607 s->easy_filt.v[2].s.ex.src == EQUIP_SOURCE_FLOOR);
608 s->easy_filt.v[1] = s->easy_filt.v[s->easy_filt.nv - 1];
609 s->easy_filt.v[0].s.func = sel_exclude_src;
610 s->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
611 s->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
612 s->easy_filt.v[2].c = EQUIP_EXPR_TERMINATOR;
613 s->easy_filt.v[3].c = EQUIP_EXPR_TERMINATOR;
614 s->easy_filt.nv -= 2;
615 s->stores = EQUIPPABLE_NO_STORE;
616 break;
617 }
618 filter_items(s);
619 sort_items(s);
620 result = EQUIP_CMP_MENU_NEW_PAGE;
621 break;
622
623 case 'd':
624 /* Dump to a file. */
625 {
626 char buf[1024];
627 char fname[80];
628
629 player_safe_name(fname, sizeof(fname),
630 p->full_name, false);
631 my_strcat(fname, "_equip.txt", sizeof(fname));
632 if (get_file(fname, buf, sizeof(buf))) {
633 s_for_file = s;
634 if (dump_to_file(buf)) {
635 s->dlg_trans_msg = trans_msg_save_ok;
636 } else {
637 s->dlg_trans_msg = trans_msg_save_bad;
638 }
639 }
640 }
641 result = EQUIP_CMP_MENU_NEW_PAGE;
642 break;
643
644 case 'q':
645 /* Choose a quick filter - one based on a single attribute. */
646 result = prompt_for_easy_filter(s, false);
647 break;
648
649 case 'r':
650 /* Reverse the order of display; keep in the same page. */
651 if (s->npage > 0) {
652 s->ifirst += (s->npage - 1) * s->indinc;
653 }
654 s->indinc *= -1;
655 result = EQUIP_CMP_MENU_NEW_PAGE;
656 break;
657
658 case 'v':
659 /* Cycle through which attributes are shown. */
660 ++s->iview;
661 if (s->iview >= s->nview) {
662 s->iview = 0;
663 } else {
664 s->dlg_trans_msg = trans_msg_view;
665 }
666 result = EQUIP_CMP_MENU_NEW_PAGE;
667 break;
668
669 case 'x':
670 case 'I':
671 /* Select an item or two to examine. */
672 s->work_sel = s->ifirst;
673 s->dlg_trans_msg = trans_msg_sel0;
674 result = EQUIP_CMP_MENU_SEL0;
675 break;
676
677 case 'R':
678 /* Reset view to defaults. */
679 s->indinc = 1;
680 s->iview = 0;
681 s->config_filt_is_on = false;
682 s->stores = EQUIPPABLE_NO_STORE;
683 s->easy_filt.simple = EQUIP_EXPR_AND;
684 s->easy_filt.nv = 1;
685 s->easy_filt.v[0].s.func = sel_exclude_src;
686 s->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
687 s->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
688 s->easy_filt.v[1].c = EQUIP_EXPR_TERMINATOR;
689 filter_items(s);
690 s->config_sort_is_on = false;
691 sort_items(s);
692 result = EQUIP_CMP_MENU_NEW_PAGE;
693 break;
694
695 case '!':
696 /*
697 * If using a quick filter, use not for the criteria.
698 * Otherwise, set up a quick filter where not is applied.
699 */
700 nfilt = get_expected_easy_filter_count(s->stores);
701 if (! s->config_filt_is_on && s->easy_filt.nv > nfilt) {
702 assert(s->easy_filt.v[nfilt].c == EQUIP_EXPR_SELECTOR);
703 if (s->easy_filt.v[nfilt].s.func ==
704 sel_at_least_resists) {
705 s->easy_filt.v[nfilt].s.func =
706 sel_does_not_resist;
707 } else if (s->easy_filt.v[nfilt].s.func ==
708 sel_has_flag) {
709 s->easy_filt.v[nfilt].s.func =
710 sel_does_not_have_flag;
711 } else if (s->easy_filt.v[nfilt].s.func ==
712 sel_has_pos_mod) {
713 s->easy_filt.v[nfilt].s.func =
714 sel_has_nonpos_mod;
715 } else if (s->easy_filt.v[nfilt].s.func ==
716 sel_does_not_resist) {
717 s->easy_filt.v[nfilt].s.func =
718 sel_at_least_resists;
719 } else if (s->easy_filt.v[nfilt].s.func ==
720 sel_does_not_have_flag) {
721 s->easy_filt.v[nfilt].s.func = sel_has_flag;
722 } else if (s->easy_filt.v[nfilt].s.func ==
723 sel_has_nonpos_mod) {
724 s->easy_filt.v[nfilt].s.func = sel_has_pos_mod;
725 } else {
726 assert(0);
727 }
728 filter_items(s);
729 sort_items(s);
730 result = EQUIP_CMP_MENU_NEW_PAGE;
731 } else {
732 result = prompt_for_easy_filter(s, true);
733 }
734 break;
735
736 case '?':
737 display_equip_cmp_help();
738 result = EQUIP_CMP_MENU_NEW_PAGE;
739 break;
740
741 case ESCAPE:
742 result = EQUIP_CMP_MENU_DONE;
743 break;
744
745 default:
746 s->dlg_trans_msg = trans_msg_unknown_key;
747 result = EQUIP_CMP_MENU_SAME_PAGE;
748 break;
749 }
750
751 return result;
752 }
753
754
display_equip_cmp_sel_help(void)755 static void display_equip_cmp_sel_help(void)
756 {
757 int irow, wid, hgt;
758
759 Term_clear();
760 irow = 1;
761 prt("Down arrow move selection one line down", irow, 0);
762 ++irow;
763 prt("Up arrow move selection one line up", irow, 0);
764 ++irow;
765 prt("n, PgDn move selection one page up", irow, 0);
766 ++irow;
767 prt("p, PgUp move selection one page up", irow, 0);
768 ++irow;
769 prt("x stop selection; if first item, escapes", irow, 0);
770 ++irow;
771 prt("return select current item", irow, 0);
772 ++irow;
773 prt("ESC leave selection process", irow, 0);
774 ++irow;
775
776 Term_get_size(&wid, &hgt);
777 prt("Press any key to continue", hgt - 1, 0);
778 (void) inkey();
779 }
780
781
handle_key_equip_cmp_select(struct keypress ch,int istate,struct equippable_summary * s,struct player * p)782 static int handle_key_equip_cmp_select(struct keypress ch, int istate,
783 struct equippable_summary *s, struct player *p)
784 {
785 static const char *trans_msg_unknown_key =
786 "Unknown key pressed; ? will list available keys";
787 static const char *trans_msg_sel1 = "Select second item; x to skip";
788 int result;
789 int ilast;
790
791 switch (ch.code) {
792 case 'n':
793 case KC_PGDOWN:
794 /* Move selection by one page. */
795 s->work_sel += s->indinc * s->npage;
796 if (s->work_sel < 0) {
797 s->work_sel = 0;
798 } else if (s->work_sel >= s->nfilt) {
799 s->work_sel = s->nfilt - 1;
800 }
801 /* Shift view. */
802 s->ifirst += s->indinc * s->npage;
803 if (s->indinc > 0) {
804 if (s->ifirst >= s->nfilt + 1 - s->maxpage) {
805 s->ifirst = s->nfilt + 1 - s->maxpage;
806 if (s->ifirst < 0) {
807 s->ifirst = 0;
808 s->npage = s->nfilt;
809 } else {
810 s->npage = s->maxpage - 1;
811 }
812 }
813 } else {
814 if (s->ifirst < s->maxpage - 2) {
815 s->ifirst = s->maxpage - 2;
816 if (s->ifirst >= s->nfilt) {
817 s->ifirst = s->nfilt - 1;
818 s->npage = s->nfilt;
819 } else {
820 s->npage = s->maxpage - 1;
821 }
822 --s->npage;
823 }
824 }
825 result = istate;
826 break;
827
828 case 'p':
829 case KC_PGUP:
830 /* Move selection by one page. */
831 s->work_sel -= s->indinc * s->npage;
832 if (s->work_sel < 0) {
833 s->work_sel = 0;
834 } else if (s->work_sel >= s->nfilt) {
835 s->work_sel = s->nfilt - 1;
836 }
837 /* Shift view. */
838 s->ifirst -= s->indinc * s->npage;
839 if (s->ifirst < 0) {
840 s->ifirst = 0;
841 } else if (s->ifirst >= s->nfilt) {
842 s->ifirst = s->nfilt - 1;
843 }
844 ilast = s->ifirst + s->indinc * s->maxpage;
845 if (s->indinc > 0) {
846 s->npage = (ilast <= s->nfilt) ?
847 s->maxpage : s->nfilt - s->ifirst;
848 } else {
849 s->npage = (ilast >= -1) ?
850 s->maxpage : s->ifirst + 1;
851 }
852 result = istate;
853 break;
854
855 case ARROW_DOWN:
856 /* Move selection by one line. */
857 s->work_sel += s->indinc;
858 if ((s->work_sel - s->ifirst) * s->indinc >= s->npage) {
859 if (s->work_sel < 0) {
860 s->work_sel = 0;
861 } else if (s->work_sel >= s->nfilt) {
862 s->work_sel = s->nfilt - 1;
863 } else {
864 /* Shift view. */
865 s->ifirst = s->work_sel - s->indinc;
866 if (s->indinc > 0) {
867 if (s->ifirst + s->npage > s->nfilt) {
868 s->ifirst = s->nfilt + 1 -
869 s->npage;
870 --s->npage;
871 }
872 } else {
873 if (s->ifirst - s->npage < -1) {
874 s->ifirst = s->npage - 2;
875 --s->npage;
876 }
877 }
878 }
879 }
880 result = istate;
881 break;
882
883 case ARROW_UP:
884 /* Move selection by one line. */
885 s->work_sel -= s->indinc;
886 if ((s->work_sel - s->ifirst) * s->indinc < 0) {
887 if (s->work_sel < 0) {
888 s->work_sel = 0;
889 } else if (s->work_sel >= s->nfilt) {
890 s->work_sel = s->nfilt - 1;
891 } else {
892 /* Shift view. */
893 s->ifirst = s->work_sel + s->indinc *
894 (2 - s->npage);
895 if (s->indinc > 0) {
896 if (s->ifirst < 0) {
897 s->ifirst = 0;
898 }
899 ilast = s->ifirst + s->maxpage;
900 if (ilast <= s->nfilt) {
901 s->npage = s->maxpage;
902 } else {
903 s->npage =
904 s->nfilt - s->ifirst;
905 }
906 } else {
907 if (s->ifirst >= s->nfilt) {
908 s->ifirst = s->nfilt - 1;
909 }
910 ilast = s->ifirst - s->maxpage;
911 if (ilast >= -1) {
912 s->npage = s->maxpage;
913 } else {
914 s->npage = s->ifirst + 1;
915 }
916 }
917 }
918 }
919 result = istate;
920 break;
921
922 case 'x':
923 /* Skip the selection. For the first, acts like ESC. */
924 if (istate == EQUIP_CMP_MENU_SEL1) {
925 display_object_comparison(s);
926 }
927 s->isel0 = -1;
928 s->isel1 = -1;
929 s->work_sel = -1;
930 result = EQUIP_CMP_MENU_NEW_PAGE;
931 break;
932
933 case KC_ENTER:
934 assert(s->work_sel >= 0 && s->work_sel < s->nfilt);
935 if (istate == EQUIP_CMP_MENU_SEL0) {
936 s->isel0 = s->sorted_indices[s->work_sel];
937 s->dlg_trans_msg = trans_msg_sel1;
938 result = EQUIP_CMP_MENU_SEL1;
939 } else {
940 s->isel1 = s->sorted_indices[s->work_sel];
941 display_object_comparison(s);
942 s->isel0 = -1;
943 s->isel1 = -1;
944 s->work_sel = -1;
945 result = EQUIP_CMP_MENU_NEW_PAGE;
946 }
947 break;
948
949 case '?':
950 display_equip_cmp_sel_help();
951 result = istate;
952 break;
953
954 case ESCAPE:
955 s->isel0 = -1;
956 s->isel1 = -1;
957 s->work_sel = -1;
958 result = EQUIP_CMP_MENU_NEW_PAGE;
959 break;
960
961 default:
962 s->dlg_trans_msg = trans_msg_unknown_key;
963 result = istate;
964 break;
965 }
966
967 return result;
968 }
969
970
prompt_for_easy_filter(struct equippable_summary * s,bool apply_not)971 static int prompt_for_easy_filter(struct equippable_summary *s, bool apply_not)
972 {
973 static const char *no_matching_attribute_msg =
974 "Did not find attribute with that name; filter unchanged";
975 char c[4] = "";
976 int itry;
977 bool threec;
978
979 if (! get_string("Enter 2 or 3 (for stat) character code and return or return to clear ", c,
980 N_ELEMENTS(c))) {
981 return EQUIP_CMP_MENU_NEW_PAGE;
982 }
983
984 /* Clear the current filter. */
985 if (c[0] == '\0') {
986 s->config_filt_is_on = false;
987 if (s->easy_filt.nv >
988 get_expected_easy_filter_count(s->stores)) {
989 s->easy_filt.v[s->easy_filt.nv - 1] =
990 s->easy_filt.v[s->easy_filt.nv];
991 --s->easy_filt.nv;
992 filter_items(s);
993 sort_items(s);
994 }
995 return EQUIP_CMP_MENU_NEW_PAGE;
996 }
997
998 /*
999 * Try different combinations of capitalization to match the
1000 * entered string to one of the column labels.
1001 */
1002 itry = 0;
1003 threec = false;
1004 while (1) {
1005 char ctry[4];
1006 wchar_t wc[4];
1007
1008 if (itry >= 4 || (threec && itry >= 3)) {
1009 s->dlg_trans_msg = no_matching_attribute_msg;
1010 break;
1011 }
1012 switch (itry) {
1013 case 0:
1014 ctry[0] = toupper(c[0]);
1015 if (c[1] != '\0') {
1016 ctry[1] = tolower(c[1]);
1017 if (c[2] != '\0') {
1018 ctry[2] = tolower(c[2]);
1019 ctry[3] = '\0';
1020 threec = true;
1021 } else {
1022 ctry[2] = '\0';
1023 }
1024 } else {
1025 ctry[1] = '\0';
1026 }
1027 break;
1028
1029 case 1:
1030 ctry[0] = toupper(c[0]);
1031 if (c[1] != '\0') {
1032 ctry[1] = toupper(c[1]);
1033 if (c[2] != '\0') {
1034 ctry[2] = toupper(c[2]);
1035 ctry[3] = '\0';
1036 threec = true;
1037 } else {
1038 ctry[2] = '\0';
1039 }
1040 } else {
1041 ctry[1] = '\0';
1042 }
1043 break;
1044
1045 case 2:
1046 ctry[0] = tolower(c[0]);
1047 if (c[1] != '\0') {
1048 ctry[1] = tolower(c[1]);
1049 if (c[2] != '\0') {
1050 ctry[2] = tolower(c[2]);
1051 ctry[3] = '\0';
1052 threec = true;
1053 } else {
1054 ctry[2] = '\0';
1055 }
1056 } else {
1057 ctry[1] = '\0';
1058 }
1059 break;
1060
1061 case 3:
1062 ctry[0] = tolower(c[0]);
1063 if (c[1] != '\0') {
1064 ctry[1] = toupper(c[1]);
1065 ctry[2] = '\0';
1066 } else {
1067 ctry[1] = '\0';
1068 }
1069 break;
1070 }
1071
1072 if (text_mbstowcs(wc, ctry, N_ELEMENTS(wc)) != (size_t)-1) {
1073 int j = 0, k = 0;
1074 bool search = true;
1075
1076 while (search) {
1077 if (j < (int)N_ELEMENTS(s->propcats)) {
1078 if (k < s->propcats[j].n) {
1079 if (threec) {
1080 wchar_t wce[4];
1081
1082 get_ui_entry_label(s->propcats[j].entries[k], 4, false, wce);
1083 if (wc[0] == wce[0] &&
1084 wc[1] == wce[1] &&
1085 wc[2] == wce[2]) {
1086 search = false;
1087 } else {
1088 ++k;
1089 }
1090 } else if (wc[0] == s->propcats[j].labels[k][0] &&
1091 wc[1] == s->propcats[j].labels[k][1]) {
1092 search = false;
1093 } else {
1094 ++k;
1095 }
1096 } else {
1097 k = 0;
1098 ++j;
1099 }
1100 } else {
1101 search = false;
1102 }
1103 }
1104
1105 /* Configure the new filter and apply it. */
1106 if (j < (int)N_ELEMENTS(s->propcats)) {
1107 int ind;
1108
1109 s->config_filt_is_on = false;
1110 if (s->easy_filt.nv ==
1111 get_expected_easy_filter_count(s->stores)) {
1112 s->easy_filt.v[s->easy_filt.nv].c =
1113 EQUIP_EXPR_SELECTOR;
1114 ++s->easy_filt.nv;
1115 }
1116 ind = s->easy_filt.nv - 1;
1117 s->easy_filt.v[ind].s.ex.propind =
1118 s->propcats[j].off + k;
1119 switch (j) {
1120 case 0:
1121 /* Resistance */
1122 s->easy_filt.v[ind].s.func =
1123 (apply_not) ?
1124 sel_does_not_resist :
1125 sel_at_least_resists;
1126 break;
1127
1128 case 1:
1129 /*
1130 * A boolean flag where on is usually
1131 * preferred.
1132 */
1133 s->easy_filt.v[ind].s.func =
1134 (apply_not) ?
1135 sel_does_not_have_flag :
1136 sel_has_flag;
1137 break;
1138
1139 case 2:
1140 /*
1141 * A boolean flag where off is usually
1142 * preferred.
1143 */
1144 s->easy_filt.v[ind].s.func =
1145 (apply_not) ?
1146 sel_has_flag :
1147 sel_does_not_have_flag;
1148 break;
1149
1150 case 3:
1151 case 4:
1152 /* Integer modifier. */
1153 s->easy_filt.v[ind].s.func =
1154 (apply_not) ?
1155 sel_has_nonpos_mod :
1156 sel_has_pos_mod;
1157 break;
1158
1159 default:
1160 assert(0);
1161 }
1162
1163 filter_items(s);
1164 sort_items(s);
1165 break;
1166 }
1167 }
1168
1169 ++itry;
1170 }
1171 return EQUIP_CMP_MENU_NEW_PAGE;
1172 }
1173
1174
display_object_comparison(const struct equippable_summary * s)1175 static void display_object_comparison(const struct equippable_summary *s)
1176 {
1177 char hbuf[120];
1178 textblock *tb0;
1179 region local_area = { 0, 0, 0, 0 };
1180
1181 assert(s->isel0 >= 0 && s->isel0 < s->nitems);
1182 tb0 = object_info(s->items[s->isel0].obj, OINFO_NONE);
1183 object_desc(hbuf, sizeof(hbuf), s->items[s->isel0].obj,
1184 ODESC_PREFIX | ODESC_FULL | ODESC_CAPITAL);
1185 if (s->isel1 != -1 && s->isel1 != s->isel0) {
1186 textblock *tb1 = textblock_new();
1187 textblock *tb2;
1188
1189 assert(s->isel1 >= 0 && s->isel1 < s->nitems);
1190 textblock_append(tb1, "%s\n", hbuf);
1191 textblock_append_textblock(tb1, tb0);
1192 object_desc(hbuf, sizeof(hbuf), s->items[s->isel1].obj,
1193 ODESC_PREFIX | ODESC_FULL | ODESC_CAPITAL);
1194 textblock_append(tb1, "\n%s\n", hbuf);
1195 tb2 = object_info(s->items[s->isel1].obj, OINFO_NONE);
1196 textblock_append_textblock(tb1, tb2);
1197 textblock_free(tb2);
1198
1199 textui_textblock_show(tb1, local_area, "Object comparison");
1200 textblock_free(tb1);
1201 } else {
1202 textui_textblock_show(tb0, local_area, hbuf);
1203 }
1204 textblock_free(tb0);
1205 }
1206
1207
dump_to_file(const char * path)1208 static bool dump_to_file(const char *path)
1209 {
1210 return (text_lines_to_file(path, append_to_file)) ? false : true;
1211 }
1212
1213
1214 /**
1215 * Write all the pages with the current selection of items and properties to
1216 * display.
1217 */
append_to_file(ang_file * fff)1218 static void append_to_file(ang_file *fff)
1219 {
1220 int cached_ifirst, cached_npage;
1221 int wid, hgt, y;
1222 char *buf;
1223
1224 if (!s_for_file) {
1225 if (initialize_summary(player, &the_summary)) {
1226 return;
1227 }
1228 s_for_file = the_summary;
1229 }
1230
1231 cached_ifirst = s_for_file->ifirst;
1232 cached_npage = s_for_file->npage;
1233 s_for_file->ifirst =
1234 (s_for_file->indinc > 0 || s_for_file->nfilt == 0) ?
1235 0 : s_for_file->nfilt - 1;
1236 s_for_file->npage =
1237 (s_for_file->maxpage <= s_for_file->nfilt) ?
1238 s_for_file->maxpage : s_for_file->nfilt;
1239
1240 Term_get_size(&wid, &hgt);
1241 buf = mem_alloc(text_wcsz() * wid + 1);
1242
1243 (void) display_page(s_for_file, player, false);
1244
1245 /* Dump part of the screen. */
1246 for (y = s_for_file->irow_combined_equip - s_for_file->nproplab;
1247 y < s_for_file->irow_combined_equip + 1 + s_for_file->npage;
1248 ++y) {
1249 char *p = buf;
1250 int x, a, n;
1251 wchar_t c;
1252
1253 /* Dump a row. */
1254 for (x = 0; x < wid; ++x) {
1255 (void) Term_what(x, y, &a, &c);
1256 n = text_wctomb(p, c);
1257 if (n > 0) {
1258 p += n;
1259 } else {
1260 *p++ = ' ';
1261 }
1262 }
1263 /* Back up over spaces */
1264 while ((p > buf) && (p[-1] == ' ')) {
1265 --p;
1266 }
1267 /* Terminate */
1268 *p = '\0';
1269
1270 file_putf(fff, "%s\n", buf);
1271 }
1272
1273 while (1) {
1274 s_for_file->ifirst += s_for_file->npage * s_for_file->indinc;
1275 if (s_for_file->indinc > 0) {
1276 if (s_for_file->ifirst >= s_for_file->nfilt) {
1277 break;
1278 }
1279 if (s_for_file->ifirst + s_for_file->npage >
1280 s_for_file->nfilt) {
1281 s_for_file->npage = s_for_file->nfilt -
1282 s_for_file->ifirst;
1283 }
1284 } else {
1285 if (s_for_file->ifirst < 0) {
1286 break;
1287 }
1288 if (s_for_file->ifirst - s_for_file->npage < -1) {
1289 s_for_file->npage = s_for_file->ifirst + 1;
1290 }
1291 }
1292
1293 (void) display_page(s_for_file, player, false);
1294
1295 /*
1296 * Dump part of the screen. Exclude the header that appeared
1297 * on the first page.
1298 */
1299 for (y = s_for_file->irow_combined_equip + 1;
1300 y < s_for_file->irow_combined_equip + 1 +
1301 s_for_file->npage; ++y) {
1302 char *p = buf;
1303 int x, a, n;
1304 wchar_t c;
1305
1306 /* Dump a row. */
1307 for (x = 0; x < wid; ++x) {
1308 (void) Term_what(x, y, &a, &c);
1309 n = text_wctomb(p, c);
1310 if (n > 0) {
1311 p += n;
1312 } else {
1313 *p++ = ' ';
1314 }
1315 }
1316 /* Back up over spaces */
1317 while ((p > buf) && (p[-1] == ' ')) {
1318 --p;
1319 }
1320 /* Terminate */
1321 *p = '\0';
1322
1323 file_putf(fff, "%s\n", buf);
1324 }
1325 }
1326
1327 mem_free(buf);
1328
1329 s_for_file->ifirst = cached_ifirst;
1330 s_for_file->npage = cached_npage;
1331 s_for_file = NULL;
1332 }
1333
1334
set_short_name(const struct object * obj,size_t length)1335 static char *set_short_name(const struct object *obj, size_t length)
1336 {
1337 char buf[80];
1338 const char *nmsrc;
1339 size_t nmlen;
1340 bool tail;
1341 char *result;
1342
1343 if (obj->known && obj->known->artifact) {
1344 nmsrc = obj->known->artifact->name;
1345 tail = true;
1346 } else if (obj->known && obj->known->ego) {
1347 nmsrc = obj->known->ego->name;
1348 tail = true;
1349 } else {
1350 object_desc(buf, N_ELEMENTS(buf), obj, ODESC_COMBAT |
1351 ODESC_SINGULAR | ODESC_TERSE);
1352 nmsrc = buf;
1353 tail = false;
1354 }
1355 nmlen = strlen(nmsrc);
1356 if (nmlen <= length) {
1357 result = string_make(nmsrc);
1358 } else if (tail) {
1359 result = string_make(nmsrc + nmlen - length);
1360 } else {
1361 result = string_make(format("%.*s", (int) length, nmsrc));
1362 }
1363 return result;
1364 }
1365
1366
1367 #if 0
1368 static bool sel_better_than(const struct equippable *eq,
1369 const union equippable_selfunc_extra *ex)
1370 {
1371 return eq->qual < ex->qual;
1372 }
1373 #endif
1374
1375
sel_at_least_resists(const struct equippable * eq,const union equippable_selfunc_extra * ex)1376 static bool sel_at_least_resists(const struct equippable *eq,
1377 const union equippable_selfunc_extra *ex)
1378 {
1379 return eq->vals[ex->propind] >= 1;
1380 }
1381
1382
sel_does_not_resist(const struct equippable * eq,const union equippable_selfunc_extra * ex)1383 static bool sel_does_not_resist(const struct equippable *eq,
1384 const union equippable_selfunc_extra *ex)
1385 {
1386 return eq->vals[ex->propind] < 1;
1387 }
1388
1389
sel_has_flag(const struct equippable * eq,const union equippable_selfunc_extra * ex)1390 static bool sel_has_flag(const struct equippable *eq,
1391 const union equippable_selfunc_extra *ex)
1392 {
1393 return eq->vals[ex->propind] != 0;
1394 }
1395
1396
sel_does_not_have_flag(const struct equippable * eq,const union equippable_selfunc_extra * ex)1397 static bool sel_does_not_have_flag(const struct equippable *eq,
1398 const union equippable_selfunc_extra *ex)
1399 {
1400 return eq->vals[ex->propind] == 0;
1401 }
1402
1403
sel_has_pos_mod(const struct equippable * eq,const union equippable_selfunc_extra * ex)1404 static bool sel_has_pos_mod(const struct equippable *eq,
1405 const union equippable_selfunc_extra *ex)
1406 {
1407 return eq->vals[ex->propind] > 0;
1408 }
1409
1410
sel_has_nonpos_mod(const struct equippable * eq,const union equippable_selfunc_extra * ex)1411 static bool sel_has_nonpos_mod(const struct equippable *eq,
1412 const union equippable_selfunc_extra *ex)
1413 {
1414 return eq->vals[ex->propind] <= 0;
1415 }
1416
1417
1418 #if 0
1419 static bool sel_exclude_slot(const struct equippable *eq,
1420 const union equippable_selfunc_extra *ex)
1421 {
1422 return eq->slot != ex->slot;
1423 }
1424
1425
1426 static bool sel_only_slot(const struct equippable *eq,
1427 const union equippable_selfunc_extra *ex)
1428 {
1429 return eq->slot == ex->slot;
1430 }
1431 #endif
1432
1433
sel_exclude_src(const struct equippable * eq,const union equippable_selfunc_extra * ex)1434 static bool sel_exclude_src(const struct equippable *eq,
1435 const union equippable_selfunc_extra *ex)
1436 {
1437 return eq->src != ex->src;
1438 }
1439
1440
sel_only_src(const struct equippable * eq,const union equippable_selfunc_extra * ex)1441 static bool sel_only_src(const struct equippable *eq,
1442 const union equippable_selfunc_extra *ex)
1443 {
1444 return eq->src == ex->src;
1445 }
1446
1447
apply_simple_filter(const struct equippable_filter * f,struct equippable_summary * s)1448 static void apply_simple_filter(const struct equippable_filter *f,
1449 struct equippable_summary *s)
1450 {
1451 int i;
1452
1453 s->nfilt = 0;
1454 switch (f->simple) {
1455 case EQUIP_EXPR_AND:
1456 for (i = 0; i < s->nitems; ++i) {
1457 int j = 0;
1458
1459 while (1) {
1460 if (j >= f->nv) {
1461 assert(s->nfilt < s->nitems &&
1462 s->nfilt < s->nalloc);
1463 s->sorted_indices[s->nfilt] = i;
1464 ++s->nfilt;
1465 break;
1466 }
1467 assert(f->v[j].c == EQUIP_EXPR_SELECTOR);
1468 if (! (*f->v[j].s.func)(s->items + i,
1469 &f->v[j].s.ex)) {
1470 break;
1471 }
1472 ++j;
1473 }
1474 }
1475 break;
1476
1477 case EQUIP_EXPR_OR:
1478 for (i = 0; i < s->nitems; ++i) {
1479 int j = 0;
1480
1481 while (1) {
1482 if (j >= f->nv) {
1483 break;
1484 }
1485 assert(f->v[j].c == EQUIP_EXPR_SELECTOR);
1486 if ((*f->v[j].s.func)(s->items + i,
1487 &f->v[j].s.ex)) {
1488 assert(s->nfilt < s->nitems &&
1489 s->nfilt < s->nalloc);
1490 s->sorted_indices[s->nfilt] = i;
1491 ++s->nfilt;
1492 break;
1493 }
1494 ++j;
1495 }
1496 }
1497 break;
1498
1499 default:
1500 assert(0);
1501 }
1502
1503 s->sorted_indices[s->nfilt] = -1;
1504 }
1505
1506
apply_complex_filter(const struct equippable_filter * f,struct equippable_summary * s)1507 static void apply_complex_filter(const struct equippable_filter *f,
1508 struct equippable_summary *s)
1509 {
1510 bool *stack;
1511 int nst, i;
1512
1513 assert(f->nv > 0);
1514 stack = mem_alloc(f->nv * sizeof(*stack));
1515 nst = 0;
1516 s->nfilt = 0;
1517 for (i = 0; i < s->nitems; ++i) {
1518 int j = 0;
1519
1520 while (1) {
1521 assert(j <= f->nv);
1522
1523 if (f->v[j].c == EQUIP_EXPR_TERMINATOR) {
1524 assert(nst == 1);
1525 if (stack[0]) {
1526 assert(s->nfilt < s->nitems &&
1527 s->nfilt < s->nalloc);
1528 s->sorted_indices[s->nfilt] = i;
1529 ++s->nfilt;
1530 }
1531 break;
1532 }
1533
1534 switch (f->v[j].c) {
1535 case EQUIP_EXPR_SELECTOR:
1536 assert(nst < f->nv);
1537 stack[nst] = (*f->v[j].s.func)(s->items + i,
1538 &f->v[j].s.ex);
1539 ++nst;
1540 break;
1541
1542 case EQUIP_EXPR_AND:
1543 assert(nst >= 2);
1544 stack[nst - 2] =
1545 (stack[nst - 1] && stack[nst - 2]);
1546 --nst;
1547 break;
1548
1549 case EQUIP_EXPR_OR:
1550 assert(nst >= 2);
1551 stack[nst - 2] =
1552 (stack[nst - 1] || stack[nst - 2]);
1553 --nst;
1554 break;
1555
1556 case EQUIP_EXPR_TERMINATOR:
1557 assert(0);
1558 }
1559 ++j;
1560 }
1561 }
1562
1563 s->sorted_indices[s->nfilt] = -1;
1564
1565 mem_free(stack);
1566 }
1567
1568
filter_items(struct equippable_summary * s)1569 static void filter_items(struct equippable_summary *s)
1570 {
1571 if (s->config_filt_is_on) {
1572 if (s->config_mod_filt.simple == EQUIP_EXPR_SELECTOR) {
1573 apply_complex_filter(&s->config_mod_filt, s);
1574 } else {
1575 apply_simple_filter(&s->config_mod_filt, s);
1576 }
1577 } else if (s->easy_filt.simple == EQUIP_EXPR_SELECTOR) {
1578 apply_complex_filter(&s->easy_filt, s);
1579 } else if (s->easy_filt.simple != EQUIP_EXPR_TERMINATOR) {
1580 apply_simple_filter(&s->easy_filt, s);
1581 } else {
1582 /*
1583 * Tnere's no filtering; set up the sorted indices to include
1584 * everything.
1585 */
1586 int i;
1587
1588 s->nfilt = s->nitems;
1589 for (i = 0; i < s->nitems; ++i) {
1590 s->sorted_indices[i] = i;
1591 }
1592 s->sorted_indices[s->nitems] = -1;
1593 }
1594
1595 /* Reset the first item shown and the number on that page. */
1596 s->ifirst = (s->indinc > 0) ? 0 : s->nfilt - 1;
1597 s->npage = (s->maxpage <= s->nfilt) ? s->maxpage : s->nfilt;
1598 }
1599
1600
cmp_by_location(const struct equippable * left,const struct equippable * right,int propind)1601 static int cmp_by_location(const struct equippable *left,
1602 const struct equippable *right, int propind)
1603 {
1604 return (left->src < right->src) ?
1605 -1 : ((left->src > right->src) ? 1 : 0);
1606 }
1607
1608
cmp_by_quality(const struct equippable * left,const struct equippable * right,int propind)1609 static int cmp_by_quality(const struct equippable *left,
1610 const struct equippable *right, int propind)
1611 {
1612 return (left->qual < right->qual) ?
1613 -1 : ((left->qual > right->qual) ? 1 : 0);
1614 }
1615
1616
cmp_by_short_name(const struct equippable * left,const struct equippable * right,int propind)1617 static int cmp_by_short_name(const struct equippable *left,
1618 const struct equippable *right, int propind)
1619 {
1620
1621 return strcmp(left->short_name, right->short_name);
1622 }
1623
1624
cmp_by_slot(const struct equippable * left,const struct equippable * right,int propind)1625 static int cmp_by_slot(const struct equippable *left,
1626 const struct equippable *right, int propind)
1627 {
1628 return (left->slot < right->slot) ?
1629 -1 : ((left->slot > right->slot) ? 1 : 0);
1630 }
1631
1632
cmp_for_sort_items(const void * left,const void * right)1633 static int cmp_for_sort_items(const void *left, const void *right)
1634 {
1635 int ileft = *((int*) left);
1636 int iright = *((int*) right);
1637 int i = 0;
1638
1639 while (1) {
1640 int cres;
1641
1642 if (sort_dat.arr[i].func == 0) {
1643 return 0;
1644 }
1645 cres = (*sort_dat.arr[i].func)(sort_dat.items + ileft,
1646 sort_dat.items + iright, sort_dat.arr[i].propind);
1647 if (cres != 0) {
1648 return cres;
1649 }
1650 ++i;
1651 }
1652 }
1653
1654
sort_items(struct equippable_summary * s)1655 static void sort_items(struct equippable_summary *s)
1656 {
1657 sort_dat.items = s->items;
1658 sort_dat.arr = (s->config_sort_is_on) ?
1659 s->config_sort.v : s->default_sort.v;
1660 sort(s->sorted_indices, s->nfilt, sizeof(*s->sorted_indices),
1661 cmp_for_sort_items);
1662 }
1663
1664
1665 typedef bool (*objselfunc)(const struct object *obj, const void *closure);
1666 typedef void (*objusefunc)(const struct object *obj, void *closure);
1667 struct obj_visitor_data {
1668 objselfunc selfunc;
1669 objusefunc usefunc;
1670 const void *selfunc_closure;
1671 void *usefunc_closure;
1672 };
1673
1674
1675 /**
1676 * Intended for use with apply_visitor_to_pile() or
1677 * apply_visitor_to_equipped().
1678 */
select_any(const struct object * obj,const void * closure)1679 static bool select_any(const struct object *obj, const void *closure)
1680 {
1681 return true;
1682 }
1683
1684
1685 /**
1686 * Test for wearable objects in the pack. Intended for use with
1687 * apply_visitor_to_pile(). Assumes obj is in the player's gear.
1688 */
select_nonequipped_wearable(const struct object * obj,const void * closure)1689 static bool select_nonequipped_wearable(const struct object *obj,
1690 const void *closure)
1691 {
1692 const struct player *p = closure;
1693
1694 return tval_is_wearable(obj) && !object_is_equipped(p->body, obj);
1695 }
1696
1697
1698 /**
1699 * Test for wearable objects. Intended for use with apply_visitor_to_pile().
1700 */
select_wearable(const struct object * obj,const void * closure)1701 static bool select_wearable(const struct object *obj, const void *closure)
1702 {
1703 return tval_is_wearable(obj);
1704 }
1705
1706
1707 /**
1708 * Test for wearable objects that are not ignored and have been seen (have a
1709 * known object). Intended for use with apply_visitor_to_pile().
1710 */
select_seen_wearable(const struct object * obj,const void * closure)1711 static bool select_seen_wearable(const struct object *obj, const void *closure)
1712 {
1713 return tval_is_wearable(obj) && obj->known && !ignore_item_ok(obj);
1714 }
1715
1716
1717 /**
1718 * Increment a counter; intended for use with apply_visitor_to_pile() or
1719 * apply_visitor_to_equipped().
1720 */
count_objects(const struct object * obj,void * closure)1721 static void count_objects(const struct object *obj, void *closure)
1722 {
1723 int *n = closure;
1724
1725 ++*n;
1726 }
1727
1728
1729 /**
1730 * Add an object to the summary of equippable items; intended for use with
1731 * apply_visitor_to_pile() or apply_visitor_to_equipped().
1732 */
1733 struct add_obj_to_summary_closure {
1734 const struct player *p;
1735 struct equippable_summary *summary;
1736 enum equippable_source src;
1737 };
add_obj_to_summary(const struct object * obj,void * closure)1738 static void add_obj_to_summary(const struct object *obj, void *closure)
1739 {
1740 struct add_obj_to_summary_closure *c = closure;
1741 struct equippable *e;
1742 struct cached_object_data *cache;
1743 int i;
1744
1745 assert(c->summary->nitems < c->summary->nalloc);
1746 e = c->summary->items + c->summary->nitems;
1747 ++c->summary->nitems;
1748
1749 if (!e->vals) {
1750 e->vals = mem_alloc(c->summary->nprop * sizeof(*e->vals));
1751 }
1752 if (!e->auxvals) {
1753 e->auxvals = mem_alloc(c->summary->nprop *
1754 sizeof(*e->auxvals));
1755 }
1756 cache = NULL;
1757 for (i = 0; i < (int)N_ELEMENTS(c->summary->propcats); ++i) {
1758 int j;
1759
1760 for (j = 0; j < c->summary->propcats[i].n; ++j) {
1761 compute_ui_entry_values_for_object(
1762 c->summary->propcats[i].entries[j], obj,
1763 c->p, &cache,
1764 e->vals + j + c->summary->propcats[i].off,
1765 e->auxvals + j + c->summary->propcats[i].off);
1766 }
1767 }
1768 release_cached_object_data(cache);
1769
1770 if (c->summary->nshortnm > 0) {
1771 string_free(e->short_name);
1772 e->short_name = set_short_name(obj, c->summary->nshortnm);
1773 e->nmlen = (int)strlen(e->short_name);
1774 }
1775
1776 e->obj = obj;
1777 e->src = c->src;
1778
1779 switch (ignore_level_of(obj)) {
1780 case IGNORE_GOOD:
1781 e->qual = EQUIP_QUAL_GOOD;
1782 break;
1783
1784 case IGNORE_AVERAGE:
1785 e->qual = EQUIP_QUAL_AVERAGE;
1786 break;
1787
1788 case IGNORE_BAD:
1789 e->qual = EQUIP_QUAL_BAD;
1790 break;
1791
1792 default:
1793 /* Try to get some finer distinctions. */
1794 if (obj->known && obj->known->artifact) {
1795 e->qual = EQUIP_QUAL_ARTIFACT;
1796 } else if (obj->known && obj->known->ego) {
1797 e->qual = EQUIP_QUAL_EGO;
1798 } else {
1799 /* Treat unknown items as average. */
1800 e->qual = EQUIP_QUAL_AVERAGE;
1801 }
1802 break;
1803 }
1804
1805 e->slot = wield_slot(obj);
1806 e->ch = object_char(obj);
1807 e->at = object_attr(obj);
1808 }
1809
1810
1811 /**
1812 * Given the function pointers in visitor, apply them to each object in the
1813 * pile pointed to by obj. Assumes that the functions do not modify the
1814 * structure of the pile.
1815 */
apply_visitor_to_pile(const struct object * obj,struct obj_visitor_data * visitor)1816 static void apply_visitor_to_pile(const struct object *obj,
1817 struct obj_visitor_data *visitor)
1818 {
1819 while (obj) {
1820 if ((*visitor->selfunc)(obj, visitor->selfunc_closure)) {
1821 (*visitor->usefunc)(obj, visitor->usefunc_closure);
1822 }
1823 obj = obj->next;
1824 }
1825 }
1826
1827
1828 /**
1829 * Given the function pointers in visitor, apply them to each object in the
1830 * given player's inventory.
1831 */
apply_visitor_to_equipped(struct player * p,struct obj_visitor_data * visitor)1832 static void apply_visitor_to_equipped(struct player *p,
1833 struct obj_visitor_data *visitor)
1834 {
1835 int i;
1836
1837 for (i = 0; i < p->body.count; ++i) {
1838 const struct object *obj = slot_object(p, i);
1839
1840 if (obj && (*visitor->selfunc)(obj,
1841 visitor->selfunc_closure)) {
1842 (*visitor->usefunc)(obj, visitor->usefunc_closure);
1843 }
1844 }
1845 }
1846
1847
reconfigure_for_term_if_necessary(bool update_names,struct equippable_summary * s)1848 static int reconfigure_for_term_if_necessary(bool update_names,
1849 struct equippable_summary *s)
1850 {
1851 int result = 0;
1852 int min_length = 16;
1853 int ncol, nrow, length, i;
1854
1855 Term_get_size(&ncol, &nrow);
1856 if (s->term_ncol == ncol && s->term_nrow == nrow) {
1857 return result;
1858 }
1859
1860 /*
1861 * Have s->irow_combined_equip + 1 rows of a header and one row, for
1862 * prompts, of a footer.
1863 */
1864 s->maxpage = nrow - s->irow_combined_equip - 2;
1865 if (s->maxpage < 1) {
1866 result = 1;
1867 } else if (s->npage > s->maxpage) {
1868 s->npage = s->maxpage;
1869 if (s->work_sel != -1 &&
1870 (s->work_sel - s->ifirst) * s->indinc >= s->npage) {
1871 s->work_sel = s->ifirst + (s->npage - 1) * s->indinc;
1872 }
1873 }
1874
1875 /*
1876 * Leave a space between the name and the properties. Don't include
1877 * the core stat modifiers in the first view.
1878 */
1879 length = ncol - s->nprop + s->propcats[N_ELEMENTS(s->propcats) - 1].n -
1880 1 - s->icol_name;
1881 if (length < min_length) {
1882 /* Try shifting the other modifiers to the second view. */
1883 length += s->propcats[N_ELEMENTS(s->propcats) - 2].n;
1884 if (length < min_length) {
1885 /* Try a three view layout. */
1886 length += s->propcats[N_ELEMENTS(s->propcats) - 3].n;
1887 if (length < min_length) {
1888 /* Give up. */
1889 result = 1;
1890 }
1891 s->nview = 3;
1892 for (i = 0; i < (int)N_ELEMENTS(s->propcats) - 3; ++i) {
1893 s->propcats[i].nvw[0] = s->propcats[i].n;
1894 s->propcats[i].nvw[1] = 0;
1895 s->propcats[i].nvw[2] = 0;
1896 s->propcats[i].ivw[0] = 0;
1897 s->propcats[i].ivw[1] = 0;
1898 s->propcats[i].ivw[2] = 0;
1899 }
1900 for (i = (int)N_ELEMENTS(s->propcats) - 3;
1901 i < (int)N_ELEMENTS(s->propcats) - 1; ++i) {
1902 s->propcats[i].nvw[0] = 0;
1903 s->propcats[i].nvw[1] = s->propcats[i].n;
1904 s->propcats[i].nvw[2] = 0;
1905 s->propcats[i].ivw[0] = 0;
1906 s->propcats[i].ivw[1] = 0;
1907 s->propcats[i].ivw[2] = 0;
1908 }
1909 s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[0] = 0;
1910 s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[1] = 0;
1911 s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[2] =
1912 s->propcats[N_ELEMENTS(s->propcats) - 1].n;
1913 s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[0] = 0;
1914 s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[1] = 0;
1915 s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[2] = 0;
1916 } else {
1917 s->nview = 2;
1918 for (i = 0; i < (int)N_ELEMENTS(s->propcats) - 2; ++i) {
1919 s->propcats[i].nvw[0] = s->propcats[i].n;
1920 s->propcats[i].nvw[1] = 0;
1921 s->propcats[i].nvw[2] = 0;
1922 s->propcats[i].ivw[0] = 0;
1923 s->propcats[i].ivw[1] = 0;
1924 s->propcats[i].ivw[2] = 0;
1925 }
1926 for (i = (int)N_ELEMENTS(s->propcats) - 2;
1927 i < (int)N_ELEMENTS(s->propcats); ++i) {
1928 s->propcats[i].nvw[0] = 0;
1929 s->propcats[i].nvw[1] = s->propcats[i].n;
1930 s->propcats[i].nvw[2] = 0;
1931 s->propcats[i].ivw[0] = 0;
1932 s->propcats[i].ivw[1] = 0;
1933 s->propcats[i].ivw[2] = 0;
1934 }
1935 }
1936 } else {
1937 s->nview = 2;
1938 for (i = 0; i < (int)N_ELEMENTS(s->propcats) - 1; ++i) {
1939 s->propcats[i].nvw[0] = s->propcats[i].n;
1940 s->propcats[i].nvw[1] = 0;
1941 s->propcats[i].nvw[2] = 0;
1942 s->propcats[i].ivw[0] = 0;
1943 s->propcats[i].ivw[1] = 0;
1944 s->propcats[i].ivw[2] = 0;
1945 }
1946 s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[0] = 0;
1947 s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[1] =
1948 s->propcats[N_ELEMENTS(s->propcats) - 1].n;
1949 s->propcats[N_ELEMENTS(s->propcats) - 1].nvw[2] = 0;
1950 s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[0] = 0;
1951 s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[1] = 0;
1952 s->propcats[N_ELEMENTS(s->propcats) - 1].ivw[2] = 0;
1953 }
1954 if (s->iview >= s->nview) {
1955 s->iview = s->nview - 1;
1956 }
1957 if (length > 20) {
1958 length = 20;
1959 }
1960 if (length != s->nshortnm && update_names) {
1961 for (i = 0; i < s->nitems; ++i) {
1962 string_free(s->items[i].short_name);
1963 s->items[i].short_name =
1964 set_short_name(s->items[i].obj, length);
1965 s->items[i].nmlen =
1966 (int)strlen(s->items[i].short_name);
1967 }
1968 }
1969 s->nshortnm = length;
1970
1971 s->term_ncol = ncol;
1972 s->term_nrow = nrow;
1973
1974 return result;
1975 }
1976
1977
compute_player_and_equipment_values(struct player * p,struct equippable_summary * s)1978 static void compute_player_and_equipment_values(struct player *p,
1979 struct equippable_summary *s)
1980 {
1981 struct cached_player_data *pcache;
1982 struct ui_entry_combiner_state *cstates;
1983 struct ui_entry_combiner_funcs cfuncs;
1984 int i;
1985
1986 if (! s->p_and_eq_vals) {
1987 s->p_and_eq_vals = mem_alloc(s->nprop *
1988 sizeof(*s->p_and_eq_vals));
1989 }
1990 if (! s->p_and_eq_auxvals) {
1991 s->p_and_eq_auxvals = mem_alloc(s->nprop *
1992 sizeof(*s->p_and_eq_auxvals));
1993 }
1994
1995 pcache = NULL;
1996 cstates = mem_alloc(s->nprop * sizeof(*cstates));
1997 for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
1998 int j;
1999
2000 for (j = 0; j < s->propcats[i].n; ++j) {
2001 const struct ui_entry *entry =
2002 s->propcats[i].entries[j];
2003 int rendind = get_ui_entry_renderer_index(entry);
2004 int combind =
2005 ui_entry_renderer_query_combiner(rendind);
2006 int v, a;
2007
2008 assert(combind > 0);
2009 (void) ui_entry_combiner_get_funcs(combind, &cfuncs);
2010 compute_ui_entry_values_for_player(
2011 entry, p, &pcache, &v, &a);
2012 (*cfuncs.init_func)(v, a,
2013 cstates + j + s->propcats[i].off);
2014 }
2015 }
2016 release_cached_player_data(pcache);
2017
2018 /* Combine with the values from the equipment. */
2019 for (i = 0; i < p->body.count; ++i) {
2020 const struct object *obj = slot_object(p, i);
2021 struct cached_object_data *cache = NULL;
2022 int j;
2023
2024 if (!obj) {
2025 continue;
2026 }
2027 for (j = 0; j < (int)N_ELEMENTS(s->propcats); ++j) {
2028 int k;
2029
2030 for (k = 0; k < s->propcats[j].n; ++k) {
2031 const struct ui_entry *entry =
2032 s->propcats[j].entries[k];
2033 int rendind = get_ui_entry_renderer_index(entry);
2034 int combind = ui_entry_renderer_query_combiner(rendind);
2035 int v, a;
2036
2037 assert(combind > 0);
2038 (void) ui_entry_combiner_get_funcs(
2039 combind, &cfuncs);
2040 compute_ui_entry_values_for_object(
2041 s->propcats[j].entries[k], obj, p,
2042 &cache, &v, &a);
2043 (*cfuncs.accum_func)(v, a,
2044 cstates + k + s->propcats[j].off);
2045 }
2046 }
2047 release_cached_object_data(cache);
2048 }
2049
2050 for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
2051 int j;
2052
2053 for (j = 0; j < s->propcats[i].n; ++j) {
2054 const struct ui_entry *entry =
2055 s->propcats[i].entries[j];
2056 int rendind = get_ui_entry_renderer_index(entry);
2057 int combind =
2058 ui_entry_renderer_query_combiner(rendind);
2059
2060 assert(combind > 0);
2061 (void) ui_entry_combiner_get_funcs(combind, &cfuncs);
2062 (*cfuncs.finish_func)(
2063 cstates + j + s->propcats[i].off);
2064 s->p_and_eq_vals[j + s->propcats[i].off] =
2065 cstates[j + s->propcats[i].off].accum;
2066 s->p_and_eq_auxvals[j + s->propcats[i].off] =
2067 cstates[j + s->propcats[i].off].accum_aux;
2068 }
2069 }
2070
2071 mem_free(cstates);
2072 }
2073
2074
check_for_two_categories(const struct ui_entry * entry,void * closure)2075 static bool check_for_two_categories(const struct ui_entry *entry,
2076 void *closure)
2077 {
2078 char **categories = closure;
2079
2080 return ui_entry_has_category(entry, categories[0]) &&
2081 ui_entry_has_category(entry, categories[1]);
2082 }
2083
2084
initialize_summary(struct player * p,struct equippable_summary ** s)2085 static int initialize_summary(struct player *p,
2086 struct equippable_summary **s)
2087 {
2088 struct obj_visitor_data visitor;
2089 struct add_obj_to_summary_closure add_obj_data;
2090 int count, i;
2091
2092 if (*s == NULL) {
2093 char *categories[] = {
2094 "resistances",
2095 "abilities",
2096 "hindrances",
2097 "modifiers",
2098 "stat_modifiers"
2099 };
2100 char *test_categories[2];
2101
2102 *s = mem_alloc(sizeof(**s));
2103 (*s)->items = NULL;
2104 (*s)->sorted_indices = NULL;
2105 (*s)->p_and_eq_vals = NULL;
2106 (*s)->p_and_eq_auxvals = NULL;
2107 (*s)->dlg_trans_msg = NULL;
2108 (*s)->nitems = 0;
2109 (*s)->nalloc = 0;
2110 (*s)->stores = EQUIPPABLE_NO_STORE;
2111 (*s)->ifirst = 0;
2112 (*s)->indinc = 1;
2113 (*s)->iview = 0;
2114 (*s)->npage = 0;
2115 (*s)->nshortnm = 0;
2116 (*s)->term_ncol = -1;
2117 (*s)->term_nrow = -1;
2118
2119 /* These are currently hardwired layout choices. */
2120 (*s)->nproplab = 2;
2121 (*s)->irow_combined_equip = (*s)->nproplab + 1;
2122 /*
2123 * Leave room for a character (item character), a space,
2124 * a character (item location code), and a space.
2125 */
2126 (*s)->icol_name = 4;
2127
2128 /*
2129 * These need to be done once after the game configuration is
2130 * read.
2131 */
2132 (*s)->nprop = 0;
2133 test_categories[0] = "EQUIPCMP_SCREEN";
2134 for (i = 0; i < (int)N_ELEMENTS((*s)->propcats); ++i) {
2135 struct ui_entry_iterator *ui_iter;
2136 int n, j;
2137
2138 test_categories[1] = categories[i];
2139 ui_iter = initialize_ui_entry_iterator(
2140 check_for_two_categories, test_categories,
2141 test_categories[1]);
2142 n = count_ui_entry_iterator(ui_iter);
2143 (*s)->nprop += n;
2144 (*s)->propcats[i].n = n;
2145 (*s)->propcats[i].off = (i == 0) ?
2146 0 : (*s)->propcats[i - 1].off +
2147 (*s)->propcats[i - 1].n;
2148 (*s)->propcats[i].entries = mem_alloc(n *
2149 sizeof(*(*s)->propcats[i].entries));
2150 (*s)->propcats[i].labels = mem_alloc(n *
2151 sizeof(*(*s)->propcats[i].labels));
2152 (*s)->propcats[i].label_buffer = mem_alloc(n *
2153 ((*s)->nproplab + 1) *
2154 sizeof(*(*s)->propcats[i].label_buffer));
2155 for (j = 0; j < n; ++j) {
2156 struct ui_entry *entry =
2157 advance_ui_entry_iterator(ui_iter);
2158
2159 (*s)->propcats[i].entries[j] = entry;
2160 (*s)->propcats[i].labels[j] =
2161 (*s)->propcats[i].label_buffer +
2162 j * ((*s)->nproplab + 1);
2163 get_ui_entry_label(entry,
2164 (*s)->nproplab + 1, true,
2165 (*s)->propcats[i].labels[j]);
2166 }
2167 release_ui_entry_iterator(ui_iter);
2168 }
2169
2170 /*
2171 * Start with nothing for the easy filter but set up space
2172 * so it is trivial to add one term filtering on an attribute
2173 * and up to three others that can filter on the source of the
2174 * item.
2175 */
2176 (*s)->easy_filt.nalloc = 5;
2177 (*s)->easy_filt.v = mem_alloc((*s)->easy_filt.nalloc *
2178 sizeof(*(*s)->easy_filt.v));
2179 switch ((*s)->stores) {
2180 case EQUIPPABLE_NO_STORE:
2181 (*s)->easy_filt.simple = EQUIP_EXPR_AND;
2182 (*s)->easy_filt.nv = 1;
2183 (*s)->easy_filt.v[0].s.func = sel_exclude_src;
2184 (*s)->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
2185 (*s)->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
2186 break;
2187
2188 case EQUIPPABLE_ONLY_STORE:
2189 (*s)->easy_filt.simple = EQUIP_EXPR_AND;
2190 (*s)->easy_filt.nv = 1;
2191 (*s)->easy_filt.v[0].s.func = sel_only_src;
2192 (*s)->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
2193 (*s)->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
2194 break;
2195
2196 case EQUIPPABLE_YES_STORE:
2197 /* There's no filtering to be done. */
2198 (*s)->easy_filt.simple = EQUIP_EXPR_TERMINATOR;
2199 (*s)->easy_filt.nv = 0;
2200 break;
2201
2202 case EQUIPPABLE_ONLY_CARRIED:
2203 (*s)->easy_filt.simple = EQUIP_EXPR_AND;
2204 (*s)->easy_filt.nv = 3;
2205 (*s)->easy_filt.v[0].s.func = sel_exclude_src;
2206 (*s)->easy_filt.v[0].s.ex.src = EQUIP_SOURCE_STORE;
2207 (*s)->easy_filt.v[0].c = EQUIP_EXPR_SELECTOR;
2208 (*s)->easy_filt.v[1].s.func = sel_exclude_src;
2209 (*s)->easy_filt.v[1].s.ex.src = EQUIP_SOURCE_HOME;
2210 (*s)->easy_filt.v[1].c = EQUIP_EXPR_SELECTOR;
2211 (*s)->easy_filt.v[2].s.func = sel_exclude_src;
2212 (*s)->easy_filt.v[2].s.ex.src = EQUIP_SOURCE_FLOOR;
2213 (*s)->easy_filt.v[2].c = EQUIP_EXPR_SELECTOR;
2214 }
2215
2216 for (i = (*s)->easy_filt.nv; i < (*s)->easy_filt.nalloc; ++i) {
2217 (*s)->easy_filt.v[i].c = EQUIP_EXPR_TERMINATOR;
2218 }
2219
2220 /* Start with nothing for the specially configured filter. */
2221 (*s)->config_filt.v = NULL;
2222 (*s)->config_filt.simple = EQUIP_EXPR_TERMINATOR;
2223 (*s)->config_filt.nv = 0;
2224 (*s)->config_filt.nalloc = 0;
2225 (*s)->config_mod_filt.v = NULL;
2226 (*s)->config_mod_filt.simple = EQUIP_EXPR_TERMINATOR;
2227 (*s)->config_mod_filt.nv = 0;
2228 (*s)->config_mod_filt.nalloc = 0;
2229 (*s)->config_filt_is_on = false;
2230
2231 /*
2232 * The default sort is by equipment slot. Any ties are
2233 * resolved first by the location of the object, rough object
2234 * quality, and then alphabetical order.
2235 */
2236 (*s)->default_sort.nalloc = 5;
2237 (*s)->default_sort.v = mem_alloc((*s)->default_sort.nalloc *
2238 sizeof(*(*s)->default_sort.v));
2239 (*s)->default_sort.v[0].func = cmp_by_slot;
2240 (*s)->default_sort.v[0].propind = 0;
2241 (*s)->default_sort.v[1].func = cmp_by_location;
2242 (*s)->default_sort.v[1].propind = 0;
2243 (*s)->default_sort.v[2].func = cmp_by_quality;
2244 (*s)->default_sort.v[2].propind = 0;
2245 (*s)->default_sort.v[3].func = cmp_by_short_name;
2246 (*s)->default_sort.v[3].propind = 0;
2247 (*s)->default_sort.v[4].func = 0;
2248 (*s)->default_sort.v[4].propind = 0;
2249 (*s)->default_sort.nv = 4;
2250
2251 /* Start with nothing for the specially configured sort. */
2252 (*s)->config_sort.v = NULL;
2253 (*s)->config_sort.nv = 0;
2254 (*s)->config_sort.nalloc = 0;
2255 (*s)->config_sort_is_on = false;
2256 }
2257
2258 /* These need to be redone on a change to the terminal size. */
2259 if (reconfigure_for_term_if_necessary(false, *s)) {
2260 return 1;
2261 }
2262
2263 /*
2264 * These need to be redone for any change in the equipped items, pack,
2265 * home, or stores.
2266 */
2267
2268 /* Count the available items to include. */
2269 count = 0;
2270 visitor.usefunc = count_objects;
2271 visitor.usefunc_closure = &count;
2272 visitor.selfunc = select_any;
2273 visitor.selfunc_closure = NULL;
2274 apply_visitor_to_equipped(p, &visitor);
2275 visitor.selfunc = select_nonequipped_wearable;
2276 visitor.selfunc_closure = p;
2277 apply_visitor_to_pile(p->gear, &visitor);
2278 if (cave) {
2279 visitor.selfunc = select_seen_wearable;
2280 visitor.selfunc_closure = NULL;
2281 apply_visitor_to_pile(square_object(cave, p->grid), &visitor);
2282 }
2283 visitor.selfunc = select_wearable;
2284 visitor.selfunc_closure = NULL;
2285 apply_visitor_to_pile(stores[STORE_HOME].stock, &visitor);
2286 for (i = 0; i < MAX_STORES; ++i) {
2287 if (i == STORE_HOME) {
2288 continue;
2289 }
2290 apply_visitor_to_pile(stores[i].stock, &visitor);
2291 }
2292
2293 /* Allocate storage and add the available items. */
2294 if (count > (*s)->nalloc) {
2295 mem_free((*s)->sorted_indices);
2296 cleanup_summary_items(*s);
2297 mem_free((*s)->items);
2298 (*s)->items = mem_zalloc(count * sizeof(*(*s)->items));
2299 (*s)->sorted_indices =
2300 mem_alloc((count + 1) * sizeof(*(*s)->sorted_indices));
2301 (*s)->nalloc = count;
2302 }
2303 (*s)->nitems = 0;
2304 visitor.usefunc = add_obj_to_summary;
2305 visitor.usefunc_closure = &add_obj_data;
2306 add_obj_data.p = p;
2307 add_obj_data.summary = *s;
2308 add_obj_data.src = EQUIP_SOURCE_WORN;
2309 visitor.selfunc = select_any;
2310 visitor.selfunc_closure = NULL;
2311 apply_visitor_to_equipped(p, &visitor);
2312 add_obj_data.src = EQUIP_SOURCE_PACK;
2313 visitor.selfunc = select_nonequipped_wearable;
2314 visitor.selfunc_closure = p;
2315 apply_visitor_to_pile(p->gear, &visitor);
2316 if (cave) {
2317 add_obj_data.src = EQUIP_SOURCE_FLOOR;
2318 visitor.selfunc = select_seen_wearable;
2319 visitor.selfunc_closure = NULL;
2320 apply_visitor_to_pile(square_object(cave, p->grid), &visitor);
2321 }
2322 add_obj_data.src = EQUIP_SOURCE_HOME;
2323 visitor.selfunc = select_wearable;
2324 visitor.selfunc_closure = NULL;
2325 apply_visitor_to_pile(stores[STORE_HOME].stock, &visitor);
2326 add_obj_data.src = EQUIP_SOURCE_STORE;
2327 for (i = 0; i < MAX_STORES; ++i) {
2328 if (i == STORE_HOME) {
2329 continue;
2330 }
2331 apply_visitor_to_pile(stores[i].stock, &visitor);
2332 }
2333
2334 compute_player_and_equipment_values(p, *s);
2335
2336 /*
2337 * Do an initial filtering and sorting of the items so they're ready
2338 * for display.
2339 */
2340 filter_items(*s);
2341 sort_items(*s);
2342
2343 /* Reset selection state. */
2344 (*s)->isel0 = -1;
2345 (*s)->isel1 = -1;
2346 (*s)->work_sel = -1;
2347
2348 return 0;
2349 }
2350
2351
cleanup_summary(struct equippable_summary * s)2352 static void cleanup_summary(struct equippable_summary *s)
2353 {
2354 int i;
2355
2356 if (s == NULL) {
2357 return;
2358 }
2359 cleanup_summary_items(s);
2360 mem_free(s->p_and_eq_auxvals);
2361 mem_free(s->p_and_eq_vals);
2362 mem_free(s->sorted_indices);
2363 mem_free(s->items);
2364 mem_free(s->config_sort.v);
2365 mem_free(s->default_sort.v);
2366 mem_free(s->config_mod_filt.v);
2367 mem_free(s->config_filt.v);
2368 mem_free(s->easy_filt.v);
2369 for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
2370 mem_free(s->propcats[i].label_buffer);
2371 mem_free(s->propcats[i].labels);
2372 mem_free(s->propcats[i].entries);
2373 }
2374 mem_free(s);
2375 }
2376
2377
cleanup_summary_items(struct equippable_summary * s)2378 static void cleanup_summary_items(struct equippable_summary *s)
2379 {
2380 int i;
2381
2382 for (i = 0; i < s->nalloc; ++i) {
2383 string_free(s->items[i].short_name);
2384 mem_free(s->items[i].auxvals);
2385 mem_free(s->items[i].vals);
2386 }
2387 }
2388
2389
source_to_char(enum equippable_source src)2390 static wchar_t source_to_char(enum equippable_source src)
2391 {
2392 static bool first_call = true;
2393 static wchar_t wchars[7];
2394 wchar_t result;
2395
2396 if (first_call) {
2397 if (text_mbstowcs(wchars, "epfhs ", N_ELEMENTS(wchars)) == (size_t)-1) {
2398 quit("Invalid encoding for item location codes");
2399 }
2400 first_call = false;
2401 }
2402
2403 switch (src) {
2404 case EQUIP_SOURCE_WORN:
2405 result = wchars[0];
2406 break;
2407
2408 case EQUIP_SOURCE_PACK:
2409 result = wchars[1];
2410 break;
2411
2412 case EQUIP_SOURCE_FLOOR:
2413 result = wchars[2];
2414 break;
2415
2416 case EQUIP_SOURCE_HOME:
2417 result = wchars[3];
2418 break;
2419
2420 case EQUIP_SOURCE_STORE:
2421 result = wchars[4];
2422 break;
2423
2424 default:
2425 result = wchars[5];
2426 break;
2427 }
2428
2429 return result;
2430 }
2431
2432
display_page(struct equippable_summary * s,const struct player * p,bool allow_reconfig)2433 static int display_page(struct equippable_summary *s, const struct player *p,
2434 bool allow_reconfig)
2435 {
2436 struct ui_entry_details rdetails;
2437 int color = (COLOUR_WHITE);
2438 int i;
2439
2440 /* Try to handle terminal size changes while displaying the summary. */
2441 if (allow_reconfig) {
2442 if (reconfigure_for_term_if_necessary(true, s)) {
2443 return 1;
2444 }
2445 }
2446
2447 /*
2448 * Display the column labels and the combined values for @ and the
2449 * the current equipment. The display of the labels bypasses what's
2450 * done by ui_entry_renderer_apply() so the color of the label can
2451 * alternate between columns.
2452 */
2453 rdetails.label_position.x = s->icol_name + s->nshortnm + 1;
2454 rdetails.label_position.y = s->irow_combined_equip - s->nproplab;
2455 rdetails.value_position.x = rdetails.label_position.x;
2456 rdetails.value_position.y = s->irow_combined_equip;
2457 rdetails.position_step = loc(1, 0);
2458 rdetails.combined_position = loc(0, 0);
2459 rdetails.vertical_label = false;
2460 rdetails.alternate_color_first = false;
2461 rdetails.show_combined = false;
2462 Term_putch(s->icol_name - 4, rdetails.value_position.y, color, L'@');
2463 for (i = 0; i < (int)N_ELEMENTS(s->propcats); ++i) {
2464 int j;
2465
2466 if (!s->propcats[i].nvw[s->iview]) {
2467 continue;
2468 }
2469 for (j = 0; j < s->propcats[i].nvw[s->iview]; ++j) {
2470 int joff = j + s->propcats[i].ivw[s->iview];
2471 /*
2472 * As a hack, label colors are hardwired; it would be
2473 * better if they configurable so they'd be consistent
2474 * with the scheme for the symbol colors.
2475 */
2476 int label_color = (j % 2 == 0) ?
2477 COLOUR_WHITE : COLOUR_L_WHITE;
2478 int k;
2479
2480 for (k = 0; k < s->nproplab; ++k) {
2481 Term_putch(rdetails.label_position.x,
2482 rdetails.label_position.y + k,
2483 label_color,
2484 s->propcats[i].labels[joff][k]);
2485 }
2486 rdetails.known_rune = is_ui_entry_for_known_rune(
2487 s->propcats[i].entries[joff], p);
2488 ui_entry_renderer_apply(get_ui_entry_renderer_index(
2489 s->propcats[i].entries[joff]), NULL, 0,
2490 s->p_and_eq_vals + joff + s->propcats[i].off,
2491 s->p_and_eq_auxvals + joff +
2492 s->propcats[i].off, 1, &rdetails);
2493 ++rdetails.label_position.x;
2494 ++rdetails.value_position.x;
2495 rdetails.alternate_color_first =
2496 !rdetails.alternate_color_first;
2497 }
2498 }
2499
2500 /* Display the items available on the current page. */
2501 ++rdetails.value_position.y;
2502 for (i = 0; i < s->npage; ++i) {
2503 const struct equippable *e;
2504 int icnv, isort, j, nmcolor;
2505
2506 icnv = s->ifirst + s->indinc * i;
2507 assert(icnv >= 0 && icnv < s->nfilt);
2508 isort = s->sorted_indices[icnv];
2509 assert(isort >= 0 && isort < s->nitems);
2510 e = s->items + isort;
2511
2512 Term_putch(s->icol_name - 4, rdetails.value_position.y,
2513 e->at, e->ch);
2514 Term_putch(s->icol_name - 2, rdetails.value_position.y, color,
2515 source_to_char(e->src));
2516 if (isort == s->isel0 || isort == s->isel1 ||
2517 icnv == s->work_sel) {
2518 nmcolor = (COLOUR_L_BLUE);
2519 } else {
2520 nmcolor = color;
2521 }
2522 Term_putstr(s->icol_name, rdetails.value_position.y,
2523 e->nmlen, nmcolor, e->short_name);
2524 rdetails.value_position.x = s->icol_name + s->nshortnm + 1;
2525 rdetails.alternate_color_first = false;
2526 for (j = 0; j < (int)N_ELEMENTS(s->propcats); ++j) {
2527 int k;
2528
2529 if (!s->propcats[j].nvw[s->iview]) {
2530 continue;
2531 }
2532 for (k = 0; k < s->propcats[j].nvw[s->iview]; ++k) {
2533 int koff = k + s->propcats[j].ivw[s->iview];
2534
2535 ui_entry_renderer_apply(
2536 get_ui_entry_renderer_index(
2537 s->propcats[j].entries[koff]), NULL, 0,
2538 e->vals + koff + s->propcats[j].off,
2539 e->auxvals + koff + s->propcats[j].off,
2540 1, &rdetails);
2541 ++rdetails.value_position.x;
2542 rdetails.alternate_color_first =
2543 !rdetails.alternate_color_first;
2544 }
2545 }
2546 ++rdetails.value_position.y;
2547 }
2548
2549 return 0;
2550 }
2551
2552
init_ui_equip_cmp(void)2553 static void init_ui_equip_cmp(void)
2554 {
2555 /* There's nothing to do; initialize lazily. */
2556 }
2557
2558
cleanup_ui_equip_cmp(void)2559 static void cleanup_ui_equip_cmp(void)
2560 {
2561 cleanup_summary(the_summary);
2562 the_summary = NULL;
2563 }
2564
2565
2566 struct init_module ui_equip_cmp_module = {
2567 .name = "ui-equip-cmp",
2568 .init = init_ui_equip_cmp,
2569 .cleanup = cleanup_ui_equip_cmp
2570 };
2571