1 /**
2 * \file ui-entry.c
3 * \brief Definitions to link object/player properties to 2nd character screen
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 "init.h"
18 #include "object.h"
19 #include "obj-curse.h"
20 #include "obj-gear.h"
21 #include "obj-knowledge.h"
22 #include "obj-util.h"
23 #include "player.h"
24 #include "player-timed.h"
25 #include "ui-entry.h"
26 #include "ui-entry-combiner.h"
27 #include "ui-entry-init.h"
28 #include "ui-entry-renderers.h"
29 #include "z-util.h"
30 #include "z-virt.h"
31
32 /*
33 * This is the maximum number of characters stored for a label. Used to
34 * limit what's extracted from the configuration file.
35 */
36 #define MAX_ENTRY_LABEL (80)
37
38 struct ui_entry_iterator {
39 struct ui_entry **entries;
40 int n, i;
41 };
42
43 struct cached_object_data {
44 bitflag f[OF_SIZE];
45 };
46
47 struct cached_player_data {
48 bitflag untimed[OF_SIZE];
49 bitflag timed[OF_SIZE];
50 };
51
52 struct category_reference {
53 const char *name;
54 int priority;
55 bool priority_set;
56 };
57
58 struct bound_object_property {
59 int type;
60 int index;
61 int value;
62 bool have_value;
63 bool isaux;
64 };
65
66 struct bound_player_ability {
67 struct player_ability *ability;
68 int value;
69 bool have_value;
70 bool isaux;
71 };
72
73
74 enum {
75 ENTRY_FLAG_TIMED_AUX = 1,
76 /* Used internally; not set from within the configuration files. */
77 ENTRY_FLAG_TEMPLATE_ONLY = (1 << 20)
78 };
79 struct entry_flag {
80 const char *name;
81 int value;
82 };
83 static struct entry_flag entry_flags[] = {
84 { "TIMED_AS_AUX", ENTRY_FLAG_TIMED_AUX },
85 };
86
87 /*
88 * Set the maximum number of shortened versions of the label that will be
89 * accepted. The shortened versions will have lengths of one to MAX_SHORTENED
90 * characters, not including the terminating null.
91 */
92 #define MAX_SHORTENED (10)
93 struct ui_entry {
94 char *name;
95 struct category_reference *categories;
96 struct bound_object_property *obj_props;
97 struct bound_player_ability *p_abilities;
98 wchar_t *label;
99 wchar_t *shortened_labels[MAX_SHORTENED];
100 wchar_t shortened_buffer[MAX_SHORTENED + (MAX_SHORTENED * (MAX_SHORTENED + 1)) / 2];
101 int nshortened[MAX_SHORTENED];
102 int nlabel;
103 int renderer_index;
104 int combiner_index;
105 int default_priority;
106 int param_index;
107 int flags;
108 int n_category;
109 int nalloc_category;
110 int n_obj_prop;
111 int nalloc_obj_prop;
112 int n_p_ability;
113 int nalloc_p_ability;
114 };
115
116 static int ui_entry_search(const char *name, int *ind);
117 static int ui_entry_search_categories(const struct ui_entry *entry,
118 const char *name, int *ind);
119 static void modifier_to_skill(int modind, int *skillind, int *skill2mod_num,
120 int *skill2mod_den
121 );
122 static int get_timed_element_effect(const struct player *p, int ind);
123 static int get_timed_modifier_effect(const struct player *p, int ind);
124
125 struct ui_entry_name_parameter {
126 const char *name;
127 int (*count_func)(void);
128 const char *(*ith_name_func)(int i);
129 };
130
131 static int get_dummy_param_count(void);
132 static const char *get_dummy_param_name(int i);
133 static int get_element_count(void);
134 static const char *get_element_name(int i);
135 static int get_stat_count(void);
136 static const char *get_stat_name(int i);
137
138 static struct ui_entry_name_parameter name_parameters[] = {
139 { "", get_dummy_param_count, get_dummy_param_name },
140 { "element", get_element_count, get_element_name },
141 { "stat", get_stat_count, get_stat_name },
142 };
143
144 struct ui_entry_priority_scheme {
145 const char *name;
146 int (*priority)(int i);
147 };
148
149 static int get_dummy_priority(int i);
150 static int get_priority_from_index(int i);
151 static int get_priority_from_negative_index(int i);
152
153 static struct ui_entry_priority_scheme priority_schemes[] = {
154 { "", get_dummy_priority },
155 { "index", get_priority_from_index },
156 { "negative_index", get_priority_from_negative_index },
157 };
158
159 struct embryonic_category_reference {
160 const char *name;
161 int psource_index;
162 int priority;
163 bool priority_set;
164 };
165
166 struct embryonic_ui_entry {
167 struct ui_entry *entry;
168 struct embryonic_category_reference *categories;
169 int param_index;
170 int psource_index;
171 int last_category_index;
172 bool exists;
173 };
174
175 static int n_category = 0;
176 static int nalloc_category = 0;
177 static char **categories = NULL;
178
179 static int n_entry = 0;
180 static int nalloc_entry = 0;
181 static struct ui_entry **entries = NULL;
182
183
184 /**
185 * Binds an object property, given by type and index, to a user interface
186 * entry configured in ui_entry.txt. If name isn't configured in that file
187 * returns a nonzero value. Otherwise returns zero after binding the property.
188 * Currently, this is only used for some parts of the second character screen.
189 */
bind_object_property_to_ui_entry_by_name(const char * name,int type,int index,int value,bool have_value,bool isaux)190 int bind_object_property_to_ui_entry_by_name(const char *name, int type,
191 int index, int value, bool have_value, bool isaux)
192 {
193 int ind;
194
195 if (! ui_entry_search(name, &ind)) {
196 return 1;
197 }
198 if (entries[ind]->n_obj_prop == entries[ind]->nalloc_obj_prop) {
199 struct bound_object_property *nblk;
200
201 if (entries[ind]->nalloc_obj_prop > INT_MAX / 2) {
202 return 2;
203 }
204 entries[ind]->nalloc_obj_prop =
205 (entries[ind]->nalloc_obj_prop == 0) ?
206 4 : 2 * entries[ind]->nalloc_obj_prop;
207 nblk = mem_alloc(entries[ind]->nalloc_obj_prop *
208 sizeof(*nblk));
209 if (entries[ind]->n_obj_prop > 0) {
210 (void) memcpy(nblk, entries[ind]->obj_props,
211 entries[ind]->n_obj_prop * sizeof(*nblk));
212 }
213 mem_free(entries[ind]->obj_props);
214 entries[ind]->obj_props = nblk;
215 }
216 entries[ind]->obj_props[entries[ind]->n_obj_prop].type = type;
217 entries[ind]->obj_props[entries[ind]->n_obj_prop].index = index;
218 entries[ind]->obj_props[entries[ind]->n_obj_prop].value = value;
219 entries[ind]->obj_props[entries[ind]->n_obj_prop].have_value =
220 have_value;
221 entries[ind]->obj_props[entries[ind]->n_obj_prop].isaux = isaux;
222 ++entries[ind]->n_obj_prop;
223 return 0;
224 }
225
226
227 /**
228 * Binds a player ability to to a user interface entry configured in ui_entry.
229 * If name isn't configured in that file returns a nonzero value. Otherwise
230 * returns zero after binding the ability. Currently, this is only used for
231 * some parts of the second character screen.
232 */
bind_player_ability_to_ui_entry_by_name(const char * name,struct player_ability * ability,int value,bool have_value,bool isaux)233 int bind_player_ability_to_ui_entry_by_name(const char *name,
234 struct player_ability *ability, int value, bool have_value, bool isaux)
235 {
236 int ind;
237
238 if (! ui_entry_search(name, &ind)) {
239 return 1;
240 }
241 if (entries[ind]->n_p_ability ==
242 entries[ind]->nalloc_p_ability) {
243 struct bound_player_ability *abilities;
244
245 if (entries[ind]->nalloc_p_ability > INT_MAX / 2) {
246 return 2;
247 }
248 entries[ind]->nalloc_p_ability =
249 (entries[ind]->nalloc_p_ability == 0) ?
250 4 : 2 * entries[ind]->nalloc_p_ability;
251 abilities = mem_alloc(entries[ind]->nalloc_p_ability *
252 sizeof(*abilities));
253 if (entries[ind]->n_p_ability > 0) {
254 (void) memcpy(abilities, entries[ind]->p_abilities,
255 entries[ind]->n_p_ability *
256 sizeof(*abilities));
257 }
258 mem_free(entries[ind]->p_abilities);
259 entries[ind]->p_abilities = abilities;
260 }
261 entries[ind]->p_abilities[entries[ind]->n_p_ability].ability = ability;
262 entries[ind]->p_abilities[entries[ind]->n_p_ability].value = value;
263 entries[ind]->p_abilities[entries[ind]->n_p_ability].have_value =
264 have_value;
265 entries[ind]->p_abilities[entries[ind]->n_p_ability].isaux = isaux;
266 ++entries[ind]->n_p_ability;
267 return 0;
268 }
269
270
271 /**
272 * Returns true if the given user interface entry was configured in
273 * ui_entry.txt to be part of the category with the given name. Otherwise,
274 * returns false.
275 */
ui_entry_has_category(const struct ui_entry * entry,const char * name)276 bool ui_entry_has_category(const struct ui_entry *entry, const char *name)
277 {
278 int ind;
279
280 return ui_entry_search_categories(entry, name, &ind) != 0;
281 }
282
283
284 /**
285 * Fills label with length characters, including a terminating null, of a
286 * label for a user interface entry configured in ui_entry.txt. If the label
287 * is naturally shorter than the specified length, the label will be padded
288 * with spaces, either on the left if pad_left is true, or on the right if
289 * pad_left is false.
290 */
get_ui_entry_label(const struct ui_entry * entry,int length,bool pad_left,wchar_t * label)291 void get_ui_entry_label(const struct ui_entry *entry, int length,
292 bool pad_left, wchar_t *label)
293 {
294 static bool first_call = true;
295 static wchar_t spc[2];
296 const wchar_t *src;
297 int n;
298
299 if (first_call) {
300 size_t nw = text_mbstowcs(spc, " ", 2);
301
302 assert(nw != (size_t)-1);
303 first_call = false;
304 }
305
306 if (length <= 0) {
307 return;
308 }
309 if (length == 1) {
310 label[0] = spc[1];
311 return;
312 }
313 if (length <= MAX_SHORTENED + 1) {
314 src = entry->shortened_labels[length - 2];
315 n = entry->nshortened[length - 2];
316 } else {
317 src = entry->label;
318 n = entry->nlabel;
319 }
320 if (n < length - 1) {
321 int i;
322
323 if (pad_left) {
324 for (i = 0; i < length - 1 - n; ++i) {
325 label[i] = spc[0];
326 }
327 (void) memcpy(label + length - 1 - n, src,
328 n * sizeof(*label));
329 } else {
330 (void) memcpy(label, src, n * sizeof(*label));
331 for (i = n; i < length - 1; ++i) {
332 label[i] = spc[0];
333 }
334 }
335 } else {
336 (void) memcpy(label, src, (length - 1) * sizeof(*label));
337 }
338 label[length - 1] = spc[1];
339 }
340
341
342 static const char *category_for_cmp_desc_prio = NULL;
cmp_desc_prio(const void * left,const void * right)343 static int cmp_desc_prio(const void *left, const void *right)
344 {
345 const struct ui_entry *eleft =
346 *((const struct ui_entry* const *) left);
347 const struct ui_entry *eright =
348 *((const struct ui_entry* const *) right);
349 int left_ind = -1, right_ind = -1;
350 int result;
351
352 if (category_for_cmp_desc_prio) {
353 int ind;
354
355 if (ui_entry_search_categories(eleft,
356 category_for_cmp_desc_prio, &ind)) {
357 left_ind = ind;
358 }
359 if (ui_entry_search_categories(eright,
360 category_for_cmp_desc_prio, &ind)) {
361 right_ind = ind;
362 }
363 }
364 if (left_ind >= 0) {
365 if (right_ind >= 0) {
366 if (eleft->categories[left_ind].priority >
367 eright->categories[right_ind].priority) {
368 result = -1;
369 } else if (eleft->categories[left_ind].priority <
370 eright->categories[right_ind].priority) {
371 result = 1;
372 } else {
373 result = strcmp(eleft->name, eright->name);
374 }
375 } else {
376 /*
377 * right is not in the sort category so it should be
378 * pushed toward the end.
379 */
380 result = -1;
381 }
382 } else if (right_ind >= 0) {
383 /*
384 * left is not in the sort category so it should be pushed
385 * towards the end.
386 */
387 result = 1;
388 } else {
389 result = strcmp(eleft->name, eright->name);
390 }
391 return result;
392 }
393
394
395 /**
396 * Constructs an iterator to enumerate all the user interface elements for
397 * which the given predicate returns true. The iterator will present those
398 * elements in descending order of priority where the priority is that
399 * configured for the element in the category named sortcategory.
400 */
initialize_ui_entry_iterator(ui_entry_predicate predicate,void * closure,const char * sortcategory)401 struct ui_entry_iterator *initialize_ui_entry_iterator(
402 ui_entry_predicate predicate, void *closure, const char *sortcategory)
403 {
404 struct ui_entry_iterator *result = mem_alloc(sizeof(*result));
405 int i;
406
407 result->entries = mem_alloc(n_entry * sizeof(*result->entries));
408 result->n = 0;
409 result->i = 0;
410 for (i = 0; i < n_entry; ++i) {
411 if (! (entries[i]->flags & ENTRY_FLAG_TEMPLATE_ONLY) &&
412 (*predicate)(entries[i], closure)) {
413 result->entries[result->n] = entries[i];
414 ++result->n;
415 }
416 }
417 category_for_cmp_desc_prio = sortcategory;
418 sort(result->entries, result->n, sizeof(*result->entries),
419 cmp_desc_prio);
420 return result;
421 }
422
423
424 /**
425 * Releases the resources allocated by a prior call to
426 * initialize_ui_entry_iterator.
427 */
release_ui_entry_iterator(struct ui_entry_iterator * i)428 void release_ui_entry_iterator(struct ui_entry_iterator *i)
429 {
430 mem_free(i->entries);
431 mem_free(i);
432 }
433
434
435 /**
436 * Resets the given iterator to the position it ahd when returned by
437 * initialize_ui_entry_iterator.
438 */
reset_ui_entry_iterator(struct ui_entry_iterator * i)439 void reset_ui_entry_iterator(struct ui_entry_iterator *i)
440 {
441 i->i = 0;
442 }
443
444
445 /**
446 * Returns the number of elements remaining to be iterated for the given
447 * iterator.
448 */
count_ui_entry_iterator(struct ui_entry_iterator * i)449 int count_ui_entry_iterator(struct ui_entry_iterator *i)
450 {
451 return i->n - i->i;
452 }
453
454
455 /**
456 * Returns the user interface entry currently pointed to by the iterator and
457 * advances the iterator.
458 */
advance_ui_entry_iterator(struct ui_entry_iterator * i)459 struct ui_entry *advance_ui_entry_iterator(struct ui_entry_iterator *i)
460 {
461 struct ui_entry *result = i->entries[i->i];
462 ++i->i;
463 return result;
464 }
465
466
467 /**
468 * Returns the combiner index, suitable as the first argument to
469 * ui_entry_combiner_get_funcs(), for the given user interface element.
470 */
get_ui_entry_combiner_index(const struct ui_entry * entry)471 int get_ui_entry_combiner_index(const struct ui_entry *entry)
472 {
473 return entry->combiner_index;
474 }
475
476
477 /**
478 * Returns the renderer index, suitable as the first argument to
479 * ui_entry_renderer_apply(), for the given user interface element.
480 */
get_ui_entry_renderer_index(const struct ui_entry * entry)481 int get_ui_entry_renderer_index(const struct ui_entry *entry)
482 {
483 return entry->renderer_index;
484 }
485
486
487 /**
488 * Returns true if the properties/abilities bound to a user interface entry
489 * correspond to a known rune. Otherwise, returns false.
490 */
is_ui_entry_for_known_rune(const struct ui_entry * entry,const struct player * p)491 bool is_ui_entry_for_known_rune(const struct ui_entry *entry,
492 const struct player *p)
493 {
494 bool result = true;
495 int i;
496
497 /*
498 * Mark it as known if all of the properties/abilities bound to the
499 * entry are known.
500 */
501 for (i = 0; i < entry->n_obj_prop && result; ++i) {
502 int ind = entry->obj_props[i].index;
503
504 switch (entry->obj_props[i].type) {
505 case OBJ_PROPERTY_STAT:
506 case OBJ_PROPERTY_MOD:
507 if (p->obj_k->modifiers[ind] == 0) {
508 result = false;
509 }
510 break;
511
512 case OBJ_PROPERTY_FLAG:
513 if (! of_has(p->obj_k->flags, ind)) {
514 result = false;
515 }
516 break;
517
518 case OBJ_PROPERTY_IGNORE:
519 case OBJ_PROPERTY_RESIST:
520 case OBJ_PROPERTY_VULN:
521 case OBJ_PROPERTY_IMM:
522 if (p->obj_k->el_info[ind].res_level == 0) {
523 result = false;
524 }
525 break;
526
527 default:
528 result = false;
529 break;
530 }
531 }
532 for (i = 0; i < entry->n_p_ability && result; ++i) {
533 int ind = entry->p_abilities[i].ability->index;
534
535 if (streq(entry->p_abilities[i].ability->type, "player")) {
536 /*
537 * Not so easy to associate with a rune so don't let
538 * it change the result.
539 */
540 continue;
541 } else if (streq(entry->p_abilities[i].ability->type,
542 "object")) {
543 if (! of_has(p->obj_k->flags, ind)) {
544 result = false;
545 }
546 } else if (streq(entry->p_abilities[i].ability->type,
547 "element")) {
548 if (p->obj_k->el_info[ind].res_level == 0) {
549 result = false;
550 }
551 } else {
552 result = false;
553 }
554 }
555 return result;
556 }
557
558
compute_ui_entry_values_for_object(const struct ui_entry * entry,const struct object * obj,const struct player * p,struct cached_object_data ** cache,int * val,int * auxval)559 void compute_ui_entry_values_for_object(const struct ui_entry *entry,
560 const struct object *obj, const struct player *p,
561 struct cached_object_data **cache, int *val, int *auxval)
562 {
563 struct ui_entry_combiner_state cst = { 0, 0, 0 };
564 struct ui_entry_combiner_funcs combiner;
565 const struct curse_data *curse;
566 struct cached_object_data *cache2;
567 bool first, all_unknown, all_aux_unknown, any_aux, all_aux;
568 int curse_ind;
569
570 if (!obj || !entry->n_obj_prop) {
571 *val = UI_ENTRY_VALUE_NOT_PRESENT;
572 *auxval = UI_ENTRY_VALUE_NOT_PRESENT;
573 return;
574 }
575 if (*cache == NULL) {
576 *cache = mem_alloc(sizeof(**cache));
577 of_wipe((*cache)->f);
578 object_flags_known(obj, (*cache)->f);
579 }
580 first = true;
581 all_unknown = true;
582 all_aux_unknown = true;
583 any_aux = false;
584 all_aux = true;
585 if (ui_entry_combiner_get_funcs(entry->combiner_index, &combiner)) {
586 assert(0);
587 }
588 cache2 = *cache;
589 curse = obj->curses;
590 curse_ind = 0;
591 while (obj) {
592 int i;
593
594 for (i = 0; i < entry->n_obj_prop; ++i) {
595 int ind = entry->obj_props[i].index;
596
597 if (entry->obj_props[i].isaux) {
598 if (entry->flags & ENTRY_FLAG_TIMED_AUX) {
599 continue;
600 }
601 any_aux = true;
602 } else {
603 all_aux = false;
604 }
605
606 switch (entry->obj_props[i].type) {
607 case OBJ_PROPERTY_STAT:
608 case OBJ_PROPERTY_MOD:
609 if (p->obj_k->modifiers[ind] != 0 ||
610 obj->modifiers[ind] == 0) {
611 int v = obj->modifiers[ind];
612 int a = 0;
613
614 if (v && entry->obj_props[i].have_value) {
615 v = entry->obj_props[i].value;
616 }
617 if (entry->obj_props[i].isaux) {
618 int t = a;
619
620 a = v;
621 v = t;
622 all_aux_unknown = false;
623 } else {
624 all_unknown = false;
625 }
626 if (first) {
627 (*combiner.init_func)(v, a, &cst);
628 first = false;
629 } else {
630 (*combiner.accum_func)(v, a, &cst);
631 }
632 }
633 break;
634
635 case OBJ_PROPERTY_FLAG:
636 if (object_flag_is_known(obj, ind)) {
637 int v = of_has(cache2->f, ind) ? 1 : 0;
638 int a = 0;
639
640 if (v && entry->obj_props[i].have_value) {
641 v = entry->obj_props[i].value;
642 }
643 if (entry->obj_props[i].isaux) {
644 int t = a;
645
646 a = v;
647 v = t;
648 all_aux_unknown = false;
649 } else {
650 all_unknown = false;
651 }
652 if (first) {
653 (*combiner.init_func)(v, a, &cst);
654 first = false;
655 } else {
656 (*combiner.accum_func)(v, a, &cst);
657 }
658 }
659 break;
660
661 case OBJ_PROPERTY_IGNORE:
662 if (object_element_is_known(obj, ind)) {
663 int v = (obj->el_info[ind].flags &
664 EL_INFO_IGNORE) ? 1 : 0;
665 int a = 0;
666
667 if (v && entry->obj_props[i].have_value) {
668 v = entry->obj_props[i].value;
669 }
670 if (entry->obj_props[i].isaux) {
671 int t = a;
672
673 a = v;
674 v = t;
675 all_aux_unknown = false;
676 } else {
677 all_unknown = false;
678 }
679 if (first) {
680 (*combiner.init_func)(v, a, &cst);
681 first = false;
682 } else {
683 (*combiner.accum_func)(v, a, &cst);
684 }
685 }
686 break;
687
688 case OBJ_PROPERTY_RESIST:
689 case OBJ_PROPERTY_VULN:
690 case OBJ_PROPERTY_IMM:
691 if (object_element_is_known(obj, ind)) {
692 int v = obj->el_info[ind].res_level;
693 int a = 0;
694
695 if (v && entry->obj_props[i].have_value) {
696 v = entry->obj_props[i].value;
697 }
698 if (entry->obj_props[i].isaux) {
699 int t = a;
700
701 a = v;
702 v = t;
703 all_aux_unknown = false;
704 } else {
705 all_unknown = false;
706 }
707 if (first) {
708 (*combiner.init_func)(v, a, &cst);
709 first = false;
710 } else {
711 (*combiner.accum_func)(v, a, &cst);
712 }
713 }
714 break;
715
716 default:
717 break;
718 }
719 }
720
721 if (curse) {
722 /*
723 * Proceed to the next unprocessed curse object.
724 * Don't overwrite the cached data for the base.
725 */
726 obj = NULL;
727 if (curse_ind == 0) {
728 cache2 = mem_alloc(sizeof(*cache2));
729 }
730 ++curse_ind;
731 while (1) {
732 if (curse_ind >= z_info->curse_max) {
733 mem_free(cache2);
734 break;
735 }
736 if (curse[curse_ind].power) {
737 obj = curses[curse_ind].obj;
738 of_wipe(cache2->f);
739 object_flags_known(obj, cache2->f);
740 break;
741 }
742 ++curse_ind;
743 }
744 } else {
745 obj = NULL;
746 }
747 }
748 if (all_unknown && all_aux_unknown) {
749 *val = (all_aux) ? 0 : UI_ENTRY_UNKNOWN_VALUE;
750 *auxval = (any_aux) ? UI_ENTRY_UNKNOWN_VALUE : 0;
751 } else {
752 (*combiner.finish_func)(&cst);
753 if (all_unknown) {
754 *val = (all_aux) ? 0 : UI_ENTRY_UNKNOWN_VALUE;
755 } else {
756 *val = cst.accum;
757 }
758 if (all_aux_unknown) {
759 *auxval = (any_aux) ? UI_ENTRY_UNKNOWN_VALUE : 0;
760 } else {
761 *auxval = cst.accum_aux;
762 }
763 }
764 }
765
766
compute_ui_entry_values_for_player(const struct ui_entry * entry,struct player * p,struct cached_player_data ** cache,int * val,int * auxval)767 void compute_ui_entry_values_for_player(const struct ui_entry *entry,
768 struct player *p, struct cached_player_data **cache, int *val,
769 int *auxval)
770 {
771 struct ui_entry_combiner_state cst = { 0, 0, 0 };
772 struct ui_entry_combiner_funcs combiner;
773 bool first;
774 int i;
775
776 if (!p) {
777 *val = UI_ENTRY_VALUE_NOT_PRESENT;
778 *auxval = UI_ENTRY_VALUE_NOT_PRESENT;
779 return;
780 }
781 if (*cache == NULL) {
782 *cache = mem_alloc(sizeof(**cache));
783 player_flags(p, (*cache)->untimed);
784 of_wipe((*cache)->timed);
785 player_flags_timed(p, (*cache)->timed);
786 if (p->timed[TMD_TRAPSAFE]) {
787 of_on((*cache)->timed, OF_TRAP_IMMUNE);
788 }
789 }
790 first = true;
791 if (ui_entry_combiner_get_funcs(entry->combiner_index, &combiner)) {
792 assert(0);
793 }
794 for (i = 0; i < entry->n_p_ability; ++i) {
795 int ind = entry->p_abilities[i].ability->index;
796
797 if ((entry->flags & ENTRY_FLAG_TIMED_AUX) &&
798 entry->p_abilities[i].isaux) {
799 continue;
800 }
801 if (streq(entry->p_abilities[i].ability->type, "player")) {
802 if (! player_has(p, ind)) {
803 continue;
804 }
805 if (entry->p_abilities[i].have_value) {
806 int v = entry->p_abilities[i].value;
807 int a = UI_ENTRY_VALUE_NOT_PRESENT;
808
809 if (entry->p_abilities[i].isaux) {
810 int t = v;
811
812 v = a;
813 a = t;
814 }
815 if (first) {
816 (*combiner.init_func)(v, a, &cst);
817 first = false;
818 } else {
819 (*combiner.accum_func)(v, a, &cst);
820 }
821 } else {
822 int v, a;
823 struct object *launcher;
824
825 /*
826 * Handle player abilities that did not bind a
827 * value to the user interface element as
828 * special cases.
829 */
830 switch (ind) {
831 case PF_FAST_SHOT:
832 launcher = equipped_item_by_slot_name(
833 p, "shooting");
834 if (launcher && kf_has(launcher->kind->kind_flags,
835 KF_SHOOTS_ARROWS)) {
836 v = p->lev / 3;
837 a = 0;
838 } else {
839 v = 0;
840 a = 0;
841 }
842 if (entry->p_abilities[i].isaux) {
843 int t = v;
844
845 v = a;
846 a = t;
847 }
848 if (first) {
849 (*combiner.init_func)(v, a, &cst);
850 first = false;
851 } else {
852 (*combiner.accum_func)(v, a, &cst);
853 }
854 break;
855
856 case PF_BRAVERY_30:
857 /*
858 * player_flags() accounts for
859 * PF_BRAVERY_30 so this is only
860 * necessary in cases where
861 * OF_PROT_FEAR isn't also bound to
862 * the element.
863 */
864 v = (p->lev >= 30) ? 1 : 0;
865 a = 0;
866 if (entry->p_abilities[i].isaux) {
867 int t = v;
868
869 v = a;
870 a = t;
871 }
872 if (first) {
873 (*combiner.init_func)(v, a, &cst);
874 first = false;
875 } else {
876 (*combiner.accum_func)(v, a, &cst);
877 }
878 break;
879 }
880 }
881 } else if (streq(entry->p_abilities[i].ability->type,
882 "object")) {
883 int v = of_has((*cache)->untimed, ind) ? 1 : 0;
884 int a;
885
886 if (entry->flags & ENTRY_FLAG_TIMED_AUX) {
887 a = of_has((*cache)->timed, ind) ? 1 : 0;
888 } else {
889 a = 0;
890 }
891 if (entry->p_abilities[i].isaux) {
892 int t = v;
893
894 v = a;
895 a = t;
896 }
897 if (first) {
898 (*combiner.init_func)(v, a, &cst);
899 first = false;
900 } else {
901 (*combiner.accum_func)(v, a, &cst);
902 }
903
904 v = of_has(p->shape->flags, ind) ? 1 : 0;
905 a = 0;
906 if (v && of_has(p->obj_k->flags, ind)) {
907 if (entry->p_abilities[i].isaux) {
908 int t = v;
909
910 v = a;
911 a = t;
912 }
913 (*combiner.accum_func)(v, a, &cst);
914 }
915 } else if (streq(entry->p_abilities[i].ability->type,
916 "element")) {
917 int v = p->race->el_info[ind].res_level;
918 int a;
919
920 if (entry->flags & ENTRY_FLAG_TIMED_AUX) {
921 a = get_timed_element_effect(p, ind);
922 } else {
923 a = 0;
924 }
925 if (entry->p_abilities[i].isaux) {
926 int t = v;
927
928 v = a;
929 a = t;
930 }
931 if (first) {
932 (*combiner.init_func)(v, a, &cst);
933 first = false;
934 } else {
935 (*combiner.accum_func)(v, a, &cst);
936 }
937 v = p->shape->el_info[ind].res_level;
938 a = 0;
939 if (v != 0 && p->obj_k->el_info[ind].res_level) {
940 if (entry->p_abilities[i].isaux) {
941 int t = v;
942
943 v = a;
944 a = t;
945 }
946 (*combiner.accum_func)(v, a, &cst);
947 }
948 }
949 }
950 /*
951 * Since stats and modifiers aren't stored in the ability list, check
952 * if any object properties for those are bound to this element.
953 * Then lookup the player's intrinsic values for those.
954 */
955 for (i = 0; i < entry->n_obj_prop; ++i) {
956 int ind = entry->obj_props[i].index;
957 int skill_ind, skill_cnv_num, skill_cnv_den;
958 int v, a;
959
960 if (entry->obj_props[i].isaux &&
961 (entry->flags & ENTRY_FLAG_TIMED_AUX)) {
962 continue;
963 }
964 switch (entry->obj_props[i].type) {
965 case OBJ_PROPERTY_STAT:
966 case OBJ_PROPERTY_MOD:
967 v = p->shape->modifiers[ind];
968 if (entry->flags & ENTRY_FLAG_TIMED_AUX) {
969 a = get_timed_modifier_effect(p, ind);
970 } else {
971 a = 0;
972 }
973 if (entry->obj_props[i].isaux) {
974 int t = v;
975
976 v = a;
977 a = t;
978 }
979 if (first) {
980 (*combiner.init_func)(v, a, &cst);
981 first = false;
982 } else {
983 (*combiner.accum_func)(v, a, &cst);
984 }
985 /*
986 * Racial information doesn't store modifiers but does
987 * store skills. If applicable, extract the relevant
988 * value and convert.
989 */
990 modifier_to_skill(ind, &skill_ind, &skill_cnv_num,
991 &skill_cnv_den);
992 if (skill_ind >= 0) {
993 v = (p->race->r_skills[skill_ind] *
994 skill_cnv_num) / skill_cnv_den;
995 a = 0;
996 if (entry->obj_props[i].isaux) {
997 int t = v;
998
999 v = a;
1000 a = t;
1001 }
1002 (*combiner.accum_func)(v, a, &cst);
1003 }
1004 /*
1005 * Uggh, the player race handles infravision
1006 * separately.
1007 */
1008 if (ind == OBJ_MOD_INFRA) {
1009 v = p->race->infra;
1010 a = 0;
1011 if (entry->obj_props[i].isaux) {
1012 int t = v;
1013
1014 v = a;
1015 a = t;
1016 }
1017 (*combiner.accum_func)(v, a, &cst);
1018 }
1019 break;
1020 }
1021 }
1022 if (first) {
1023 *val = UI_ENTRY_VALUE_NOT_PRESENT;
1024 *auxval = UI_ENTRY_VALUE_NOT_PRESENT;
1025 } else {
1026 (*combiner.finish_func)(&cst);
1027 *val = cst.accum;
1028 *auxval = cst.accum_aux;
1029 }
1030 }
1031
1032
release_cached_object_data(struct cached_object_data * cache)1033 void release_cached_object_data(struct cached_object_data *cache)
1034 {
1035 mem_free(cache);
1036 }
1037
1038
release_cached_player_data(struct cached_player_data * cache)1039 void release_cached_player_data(struct cached_player_data *cache)
1040 {
1041 mem_free(cache);
1042 }
1043
1044
1045 /**
1046 * Lookup the entry by name. Return a nonzero value if present and set *ind to
1047 * its index. Otherwise return zero and set *ind to where it should be
1048 * inserted.
1049 */
ui_entry_search(const char * name,int * ind)1050 static int ui_entry_search(const char *name, int *ind)
1051 {
1052 /* They're sorted by name so use a binary search. */
1053 int ilow = 0, ihigh = n_entry;
1054
1055 while (1) {
1056 int imid, cmp;
1057
1058 if (ilow == ihigh) {
1059 *ind = ilow;
1060 return 0;
1061 }
1062 imid = (ilow + ihigh) / 2;
1063 cmp = strcmp(entries[imid]->name, name);
1064 if (cmp == 0) {
1065 *ind = imid;
1066 return 1;
1067 }
1068 if (cmp < 0) {
1069 ilow = imid + 1;
1070 } else {
1071 ihigh = imid;
1072 }
1073 }
1074 }
1075
1076
1077 /**
1078 * Lookup the entry by name. If present return its index + 1. Otherwise,
1079 * return 0.
1080 */
ui_entry_lookup(const char * name)1081 static int ui_entry_lookup(const char *name)
1082 {
1083 int ind;
1084
1085 return ui_entry_search(name, &ind) ? ind + 1 : 0;
1086 }
1087
1088
1089 /**
1090 * Insert the entry.
1091 */
ui_entry_insert(struct ui_entry * entry)1092 static void ui_entry_insert(struct ui_entry *entry)
1093 {
1094 int ind;
1095
1096 if (ui_entry_search(entry->name, &ind)) {
1097 quit("Attempted to insert ui_entry with same name");
1098 }
1099
1100 if (n_entry == nalloc_entry) {
1101 struct ui_entry **extended;
1102
1103 if (nalloc_entry > INT_MAX / 2) {
1104 quit("Too many ui_entry");
1105 }
1106 nalloc_entry = (nalloc_entry == 0) ? 8 : 2 * nalloc_entry;
1107 extended = mem_alloc(nalloc_entry * sizeof(*extended));
1108 if (ind > 0) {
1109 (void) memcpy(extended, entries,
1110 ind * sizeof(*entries));
1111 }
1112 extended[ind] = entry;
1113 if (ind < n_entry) {
1114 (void) memcpy(extended + ind + 1, entries + ind,
1115 (n_entry - ind) * sizeof(*entries));
1116 }
1117 mem_free(entries);
1118 entries = extended;
1119 } else {
1120 int i;
1121
1122 for (i = n_entry; i > ind; --i) {
1123 entries[i] = entries[i - 1];
1124 }
1125 entries[ind] = entry;
1126 }
1127 ++n_entry;
1128 }
1129
1130
1131 /**
1132 * Search for name in the categories associated with ui_entry. Return a
1133 * nonzero value if present and set *ind to its index. Otherwise, return zero
1134 * and set *ind to where it should be inserted.
1135 */
ui_entry_search_categories(const struct ui_entry * entry,const char * name,int * ind)1136 static int ui_entry_search_categories(const struct ui_entry *entry,
1137 const char *name, int *ind)
1138 {
1139 /* They're sorted by name so use a binary search. */
1140 int ilow = 0, ihigh = entry->n_category;
1141
1142 while (1) {
1143 int imid, cmp;
1144
1145 if (ilow == ihigh) {
1146 *ind = ilow;
1147 return 0;
1148 }
1149 imid = (ilow + ihigh) / 2;
1150 cmp = strcmp(entry->categories[imid].name, name);
1151 if (cmp == 0) {
1152 *ind = imid;
1153 return 1;
1154 }
1155 if (cmp < 0) {
1156 ilow = imid + 1;
1157 } else {
1158 ihigh = imid;
1159 }
1160 }
1161 }
1162
1163
modifier_to_skill(int modind,int * skillind,int * skill2mod_num,int * skill2mod_den)1164 static void modifier_to_skill(int modind, int *skillind, int *skill2mod_num,
1165 int *skill2mod_den
1166 )
1167 {
1168 switch (modind) {
1169 case OBJ_MOD_TUNNEL:
1170 *skillind = SKILL_DIGGING;
1171 *skill2mod_num = 1;
1172 *skill2mod_den = 20;
1173 break;
1174
1175 /*
1176 * Stealth and searching also have skills, but the calculations
1177 * formerly in ui-player.c didn't include the racial contribution from
1178 * those in what was shown in the second player screen. If that's
1179 * desirable, these are the conversion factors used in player-calcs.c
1180 */
1181 #if 0
1182 case OBJ_MOD_STEALTH:
1183 *skillind = SKILL_STEALTH;
1184 *skill2mod_num = 1;
1185 *skill2mod_den = 1;
1186 break;
1187
1188 case OBJ_MOD_SEARCH:
1189 *skillind = SKILL_SEARCH;
1190 *skill2mod_num = 1;
1191 *skill2mod_den = 5;
1192 break;
1193 #endif
1194
1195 default:
1196 *skillind = -1;
1197 *skill2mod_num = 1;
1198 *skill2mod_den = 1;
1199 break;
1200 }
1201 }
1202
1203
get_timed_element_effect(const struct player * p,int ind)1204 static int get_timed_element_effect(const struct player *p, int ind)
1205 {
1206 int result;
1207
1208 switch (ind) {
1209 case ELEM_ACID:
1210 result = p->timed[TMD_OPP_ACID] ? 1 : 0;
1211 break;
1212
1213 case ELEM_ELEC:
1214 result = p->timed[TMD_OPP_ELEC] ? 1 : 0;
1215 break;
1216
1217 case ELEM_FIRE:
1218 result = p->timed[TMD_OPP_FIRE] ? 1 : 0;
1219 break;
1220
1221 case ELEM_COLD:
1222 result = p->timed[TMD_OPP_COLD] ? 1 : 0;
1223 break;
1224
1225 case ELEM_POIS:
1226 result = p->timed[TMD_OPP_POIS] ? 1 : 0;
1227 break;
1228
1229 default:
1230 result = 0;
1231 break;
1232 }
1233 return result;
1234 }
1235
1236
get_timed_modifier_effect(const struct player * p,int ind)1237 static int get_timed_modifier_effect(const struct player *p, int ind)
1238 {
1239 int result;
1240
1241 /* Mimics calculations made in player-calcs.c. */
1242 switch (ind) {
1243 case OBJ_MOD_BLOWS:
1244 result = (p->timed[TMD_BLOODLUST]) ?
1245 p->timed[TMD_BLOODLUST] / 20 : 0;
1246 break;
1247
1248 case OBJ_MOD_INFRA:
1249 result = (p->timed[TMD_SINFRA]) ? 5 : 0;
1250 break;
1251
1252 case OBJ_MOD_SPEED:
1253 result = (p->timed[TMD_FAST] || p->timed[TMD_SPRINT]) ? 10 : 0;
1254 if (p->timed[TMD_STONESKIN]) {
1255 result -= 5;
1256 }
1257 if (p->timed[TMD_SLOW]) {
1258 result -= 10;
1259 }
1260 if (p->timed[TMD_TERROR]) {
1261 result += 10;
1262 }
1263 break;
1264
1265 case OBJ_MOD_STEALTH:
1266 result = (p->timed[TMD_STEALTH]) ? 10 : 0;
1267 break;
1268
1269 default:
1270 result = 0;
1271 break;
1272 }
1273 return result;
1274 }
1275
1276
1277 /**
1278 * Search for name in the categories associated with ui_entry being parsed.
1279 * Return a nonzero value if present and set *ind to its index. Otherwise,
1280 * return zero and set *ind to where it should be inserted.
1281 */
search_embryo_categories(const struct embryonic_ui_entry * embryo,const char * name,int * ind)1282 static int search_embryo_categories(const struct embryonic_ui_entry *embryo,
1283 const char *name, int *ind)
1284 {
1285 int ilow, ihigh;
1286
1287 if (embryo->exists) {
1288 return ui_entry_search_categories(embryo->entry, name, ind);
1289 }
1290
1291 /* They're sorted by name so use a binary search. */
1292 ilow = 0;
1293 ihigh = embryo->entry->n_category;
1294 while (1) {
1295 int imid, cmp;
1296
1297 if (ilow == ihigh) {
1298 *ind = ilow;
1299 return 0;
1300 }
1301 imid = (ilow + ihigh) / 2;
1302 cmp = strcmp(embryo->categories[imid].name, name);
1303 if (cmp == 0) {
1304 *ind = imid;
1305 return 1;
1306 }
1307 if (cmp < 0) {
1308 ilow = imid + 1;
1309 } else {
1310 ihigh = imid;
1311 }
1312 }
1313 }
1314
1315
1316 /**
1317 * Search the list of all categories seen so far for a name. Return a
1318 * nonzero value if found and set *ind to its index. Otherwise, return zero
1319 * and set *ind to where it should be inserted.
1320 */
search_categories(const char * name,int * ind)1321 static int search_categories(const char *name, int *ind)
1322 {
1323 int ilow = 0, ihigh = n_category;
1324
1325 while (1) {
1326 int imid, cmp;
1327
1328 if (ilow == ihigh) {
1329 *ind = ilow;
1330 return 0;
1331 }
1332 imid = (ilow + ihigh) / 2;
1333 cmp = strcmp(categories[imid], name);
1334 if (cmp == 0) {
1335 *ind = imid;
1336 return 1;
1337 }
1338 if (cmp < 0) {
1339 ilow = imid + 1;
1340 } else {
1341 ihigh = imid;
1342 }
1343 }
1344 }
1345
1346
1347 /**
1348 * Insert a category with (name, priority) into the ui_entry being parsed.
1349 * Use ind as the insertion point.
1350 */
insert_embryo_category(struct embryonic_ui_entry * embryo,const char * name,int psource_index,int priority,bool priority_set,int ind)1351 static void insert_embryo_category(struct embryonic_ui_entry *embryo,
1352 const char *name, int psource_index, int priority, bool priority_set,
1353 int ind)
1354 {
1355 int cind;
1356
1357 if (! search_categories(name, &cind)) {
1358 if (n_category == nalloc_category) {
1359 char **extended;
1360
1361 if (n_category > INT_MAX / 2) {
1362 quit("Too many categories");
1363 }
1364 nalloc_category = (nalloc_category == 0) ?
1365 8 : nalloc_category * 2;
1366 extended = mem_alloc(nalloc_category *
1367 sizeof(*extended));
1368 if (cind > 0) {
1369 memcpy(extended, categories,
1370 cind * sizeof(*extended));
1371 }
1372 extended[cind] = string_make(name);
1373 if (cind < n_category) {
1374 memcpy(extended + cind + 1, categories + cind,
1375 (n_category - cind) *
1376 sizeof(*extended));
1377 }
1378 mem_free(categories);
1379 categories = extended;
1380 } else {
1381 int i;
1382
1383 for (i = n_category; i > cind; --i) {
1384 categories[i] = categories[i - 1];
1385 }
1386 categories[cind] = string_make(name);
1387 }
1388 ++n_category;
1389 }
1390 if (embryo->entry->n_category == embryo->entry->nalloc_category) {
1391 if (embryo->entry->nalloc_category > INT_MAX / 2) {
1392 quit("Too many categories for an ui_entry");
1393 }
1394 embryo->entry->nalloc_category =
1395 (embryo->entry->nalloc_category == 0) ?
1396 4 : 2 * embryo->entry->nalloc_category;
1397 if (embryo->exists) {
1398 struct category_reference *extended =
1399 mem_alloc(embryo->entry->nalloc_category *
1400 sizeof(*extended));
1401
1402 if (ind > 0) {
1403 (void) memcpy(extended,
1404 embryo->entry->categories,
1405 ind * sizeof(*extended));
1406 }
1407 extended[ind].name = categories[cind];
1408 extended[ind].priority = priority;
1409 extended[ind].priority_set = priority_set;
1410 if (ind < embryo->entry->n_category) {
1411 (void) memcpy(extended + ind + 1,
1412 embryo->entry->categories + ind,
1413 (embryo->entry->n_category - ind) *
1414 sizeof(*extended));
1415 }
1416 mem_free(embryo->entry->categories);
1417 embryo->entry->categories = extended;
1418 } else {
1419 struct embryonic_category_reference *extended =
1420 mem_alloc(embryo->entry->nalloc_category *
1421 sizeof(*extended));
1422
1423 if (ind > 0) {
1424 (void) memcpy(extended,
1425 embryo->categories,
1426 ind * sizeof(*extended));
1427 }
1428 extended[ind].name = categories[cind];
1429 extended[ind].psource_index = psource_index;
1430 extended[ind].priority = priority;
1431 extended[ind].priority_set = priority_set;
1432 if (ind < embryo->entry->n_category) {
1433 (void) memcpy(extended + ind + 1,
1434 embryo->categories + ind,
1435 (embryo->entry->n_category - ind) *
1436 sizeof(*extended));
1437 }
1438 mem_free(embryo->categories);
1439 embryo->categories = extended;
1440 }
1441 } else {
1442 int i;
1443
1444 if (embryo->exists) {
1445 for (i = embryo->entry->n_category; i > ind; --i) {
1446 embryo->entry->categories[i] =
1447 embryo->entry->categories[i - 1];
1448 }
1449 embryo->entry->categories[ind].name = categories[cind];
1450 embryo->entry->categories[ind].priority = priority;
1451 embryo->entry->categories[ind].priority_set =
1452 priority_set;
1453 } else {
1454 for (i = embryo->entry->n_category; i > ind; --i) {
1455 embryo->categories[i] =
1456 embryo->categories[i - 1];
1457 }
1458 embryo->categories[ind].name = categories[cind];
1459 embryo->categories[ind].psource_index = psource_index;
1460 embryo->categories[ind].priority = priority;
1461 embryo->categories[ind].priority_set = priority_set;
1462 }
1463 }
1464 ++embryo->entry->n_category;
1465 }
1466
1467
1468 /* These are for handling unparameterized entry names. */
get_dummy_param_count(void)1469 static int get_dummy_param_count(void)
1470 {
1471 return 1;
1472 }
1473
1474
get_dummy_param_name(int i)1475 static const char *get_dummy_param_name(int i)
1476 {
1477 assert(i == 0);
1478 return "";
1479 }
1480
1481
1482 /* These are for handling of entries parameterized by the element name. */
1483 static const char *element_names[] = {
1484 #define ELEM(x) #x,
1485 #include "list-elements.h"
1486 #undef ELEM
1487 };
1488
1489
get_element_count(void)1490 static int get_element_count(void)
1491 {
1492 return N_ELEMENTS(element_names);
1493 }
1494
1495
get_element_name(int i)1496 static const char *get_element_name(int i)
1497 {
1498 assert(i >= 0 && i < get_element_count());
1499 return element_names[i];
1500 }
1501
1502
1503 /* These are for handling of entries parameterized by the stat name. */
1504 static const char *stat_names[] = {
1505 #define STAT(x) #x,
1506 #include "list-stats.h"
1507 #undef STAT
1508 };
1509
1510
get_stat_count(void)1511 static int get_stat_count(void)
1512 {
1513 return N_ELEMENTS(stat_names);
1514 }
1515
1516
get_stat_name(int i)1517 static const char *get_stat_name(int i)
1518 {
1519 assert(i >= 0 && i < get_stat_count());
1520 return stat_names[i];
1521 }
1522
1523
1524 /*
1525 * For handling of automatically setting the priority based on the parameter
1526 * indexing.
1527 */
get_dummy_priority(int i)1528 static int get_dummy_priority(int i)
1529 {
1530 return 0;
1531 }
1532
1533
get_priority_from_index(int i)1534 static int get_priority_from_index(int i)
1535 {
1536 return i;
1537 }
1538
1539
get_priority_from_negative_index(int i)1540 static int get_priority_from_negative_index(int i)
1541 {
1542 return -i;
1543 }
1544
1545
1546 /* Convert list of categories in the embryo to its final form. */
parameterize_category_list(const struct embryonic_category_reference * categories,int n,int ind,struct ui_entry * entry)1547 static void parameterize_category_list(
1548 const struct embryonic_category_reference *categories, int n, int ind,
1549 struct ui_entry *entry)
1550 {
1551 int i;
1552
1553 entry->categories = mem_alloc(n * sizeof(*entry->categories));
1554 entry->n_category = n;
1555 entry->nalloc_category = n;
1556
1557 for (i = 0; i < n; ++i) {
1558 entry->categories[i].name = categories[i].name;
1559 if (categories[i].priority_set) {
1560 entry->categories[i].priority =
1561 (categories[i].psource_index > 0) ?
1562 (*priority_schemes[categories[i].psource_index].priority)(ind) :
1563 categories[i].priority;
1564 entry->categories[i].priority_set = true;
1565 } else {
1566 entry->categories[i].priority = 0;
1567 entry->categories[i].priority_set = false;
1568 }
1569 }
1570 }
1571
1572
initialize_shortened(struct ui_entry * entry)1573 static void initialize_shortened(struct ui_entry *entry)
1574 {
1575 int i;
1576
1577 assert(MAX_SHORTENED > 0);
1578 entry->shortened_labels[0] = entry->shortened_buffer;
1579 for (i = 1; i < MAX_SHORTENED; ++i) {
1580 entry->shortened_labels[i] =
1581 entry->shortened_labels[i - 1] + i + 1;
1582 }
1583 for (i = 0; i < MAX_SHORTENED; ++i) {
1584 entry->nshortened[i] = 0;
1585 }
1586 }
1587
1588
copy_shortened_labels(struct ui_entry * dest,const struct ui_entry * src)1589 static void copy_shortened_labels(struct ui_entry *dest,
1590 const struct ui_entry *src)
1591 {
1592 int i;
1593
1594 for (i = 0; i < MAX_SHORTENED; ++i) {
1595 dest->shortened_labels[i] = (i == 0) ? dest->shortened_buffer :
1596 dest->shortened_labels[i - 1] + i + 1;
1597 dest->nshortened[i] = src->nshortened[i];
1598 if (src->nshortened[i] > 0) {
1599 (void) memcpy(dest->shortened_labels[i],
1600 src->shortened_labels[i],
1601 (src->nshortened[i] + 1) *
1602 sizeof(*dest->shortened_labels[i]));
1603 }
1604 }
1605 }
1606
1607
fill_out_shortened(struct ui_entry * entry)1608 static void fill_out_shortened(struct ui_entry *entry)
1609 {
1610 int i;
1611
1612 for (i = 0; i < MAX_SHORTENED; ++i) {
1613 int j;
1614 int n;
1615 const wchar_t *src;
1616
1617 if (entry->nshortened[i] != 0) {
1618 continue;
1619 }
1620 /*
1621 * Is there a longer abbreviated label already set? Use it
1622 * as the basis for this one. Otherwise, use the full label.
1623 */
1624 j = i + 1;
1625 while (1) {
1626 if (j >= MAX_SHORTENED) {
1627 n = entry->nlabel;
1628 src = entry->label;
1629 break;
1630 }
1631 if (entry->nshortened[j] != 0) {
1632 n = entry->nshortened[j];
1633 src = entry->shortened_labels[j];
1634 break;
1635 }
1636 ++j;
1637 }
1638 entry->nshortened[i] = (n < i + 1) ? n : i + 1;
1639 (void) memcpy(entry->shortened_labels[i], src,
1640 (entry->nshortened[i] + 1) *
1641 sizeof(*entry->shortened_labels[i]));
1642 }
1643 }
1644
1645
hatch_embryo(struct embryonic_ui_entry * embryo)1646 static int hatch_embryo(struct embryonic_ui_entry *embryo)
1647 {
1648 /* If was editing an existing entry, do not have to recommit it. */
1649 if (! embryo->exists) {
1650 int n, i;
1651
1652 /* Check that required fields are valid. */
1653 if (embryo->entry->combiner_index == 0) {
1654 string_free(embryo->entry->name);
1655 mem_free(embryo->entry->label);
1656 mem_free(embryo->entry);
1657 mem_free(embryo->categories);
1658 return 1;
1659 }
1660
1661 /* Parameterize the name generating multiple entries. */
1662 n = (*name_parameters[embryo->param_index].count_func)();
1663 for (i = 0; i < n - 1; ++i) {
1664 struct ui_entry *entry = mem_alloc(sizeof(*entry));
1665 const char *name = (*name_parameters[embryo->param_index].ith_name_func)(i);
1666 entry->name = string_make(
1667 format("%s<%s>", embryo->entry->name, name));
1668 if (embryo->psource_index != 0) {
1669 entry->default_priority =
1670 (*priority_schemes[embryo->psource_index].priority)(i);
1671 } else {
1672 entry->default_priority =
1673 embryo->entry->default_priority;
1674 }
1675 parameterize_category_list(
1676 embryo->categories, embryo->entry->n_category,
1677 i, entry);
1678 entry->obj_props = NULL;
1679 entry->p_abilities = NULL;
1680 if (embryo->entry->nlabel > 0) {
1681 size_t sz = (embryo->entry->nlabel + 1) *
1682 sizeof(*entry->label);
1683
1684 entry->label = mem_alloc(sz);
1685 (void) memcpy(entry->label,
1686 embryo->entry->label, sz);
1687 entry->nlabel = embryo->entry->nlabel;
1688 } else {
1689 size_t nw;
1690
1691 entry->label = mem_alloc(
1692 (MAX_ENTRY_LABEL + 1) *
1693 sizeof(*entry->label));
1694 nw = text_mbstowcs(entry->label, name,
1695 MAX_ENTRY_LABEL);
1696 /* Ensure null termination. */
1697 if (nw != (size_t)-1 &&
1698 text_mbstowcs(entry->label + nw, "", 1) != (size_t)-1) {
1699 entry->nlabel = nw;
1700 }
1701 }
1702 copy_shortened_labels(entry, embryo->entry);
1703 entry->renderer_index = embryo->entry->renderer_index;
1704 entry->combiner_index = embryo->entry->combiner_index;
1705 entry->param_index = i;
1706 entry->flags = embryo->entry->flags;
1707 entry->n_obj_prop = 0;
1708 entry->nalloc_obj_prop = 0;
1709 entry->n_p_ability = 0;
1710 entry->nalloc_p_ability = 0;
1711 ui_entry_insert(entry);
1712 }
1713 /*
1714 * For the last one, reuse the structure stored in the embryo
1715 * to save a bit of effort.
1716 */
1717 if (embryo->param_index == 0) {
1718 /* Not parameterized */
1719 embryo->entry->param_index = -1;
1720 } else {
1721 const char *name = (*name_parameters[embryo->param_index].ith_name_func)(n - 1);
1722 char *entnm = string_make(
1723 format("%s<%s>", embryo->entry->name, name));
1724
1725 string_free(embryo->entry->name);
1726 embryo->entry->name = entnm;
1727 if (embryo->entry->nlabel == 0) {
1728 size_t nw;
1729
1730 embryo->entry->label = mem_alloc(
1731 (MAX_ENTRY_LABEL + 1) *
1732 sizeof(*embryo->entry->label));
1733 nw = text_mbstowcs(embryo->entry->label, name,
1734 MAX_ENTRY_LABEL);
1735 /* Ensure null termination. */
1736 if (nw != (size_t)-1 &&
1737 text_mbstowcs(embryo->entry->label + nw, "", 1) != (size_t)-1) {
1738 embryo->entry->nlabel = nw;
1739 }
1740 }
1741 embryo->entry->param_index = n - 1;
1742 if (embryo->psource_index != 0) {
1743 embryo->entry->default_priority =
1744 (*priority_schemes[embryo->psource_index].priority)(n - 1);
1745 }
1746 }
1747 parameterize_category_list(embryo->categories,
1748 embryo->entry->n_category, n - 1, embryo->entry);
1749 ui_entry_insert(embryo->entry);
1750 }
1751 mem_free(embryo->categories);
1752 return 0;
1753 }
1754
1755
hatch_last_embryo(struct parser * p)1756 static int hatch_last_embryo(struct parser *p)
1757 {
1758 struct embryonic_ui_entry *embryo = parser_priv(p);
1759 int result = 0;
1760
1761 if (embryo) {
1762 if (hatch_embryo(embryo)) {
1763 result = 1;
1764 }
1765 mem_free(embryo);
1766 parser_setpriv(p, NULL);
1767 }
1768 return result;
1769 }
1770
1771
parse_entry_name(struct parser * p)1772 static enum parser_error parse_entry_name(struct parser *p)
1773 {
1774 const char *name = parser_getstr(p, "name");
1775 struct embryonic_ui_entry *embryo = parser_priv(p);
1776 int ind;
1777
1778 if (embryo) {
1779 if (streq(name, embryo->entry->name)) {
1780 /*
1781 * Strange case since the last record is the same as
1782 * the this one. Simply proceeed to modify the
1783 * previous record without a warning.
1784 */
1785 return PARSE_ERROR_NONE;
1786 }
1787 if (hatch_embryo(embryo)) {
1788 mem_free(embryo);
1789 parser_setpriv(p, NULL);
1790 return PARSE_ERROR_INVALID_VALUE;
1791 }
1792 } else {
1793 embryo = mem_alloc(sizeof(*embryo));
1794 }
1795
1796 parser_setpriv(p, embryo);
1797 ind = ui_entry_lookup(name);
1798 if (ind > 0) {
1799 /*
1800 * Modify an already existing record. Recall it, convert it
1801 * back to embryonic form, and mark it as already existing.
1802 */
1803 embryo->entry = entries[ind - 1];
1804 embryo->exists = true;
1805 } else {
1806 /* Make a completely new entry. */
1807 embryo->entry = mem_alloc(sizeof(*embryo->entry));
1808 embryo->entry->name = string_make(name);
1809 embryo->entry->categories = NULL;
1810 embryo->entry->obj_props = NULL;
1811 embryo->entry->p_abilities = NULL;
1812 embryo->entry->label = NULL;
1813 initialize_shortened(embryo->entry);
1814 embryo->entry->nlabel = 0;
1815 embryo->entry->renderer_index = 0;
1816 embryo->entry->combiner_index = 0;
1817 embryo->entry->default_priority = 0;
1818 embryo->entry->flags = 0;
1819 embryo->entry->n_category = 0;
1820 embryo->entry->nalloc_category = 0;
1821 embryo->entry->n_obj_prop = 0;
1822 embryo->entry->nalloc_obj_prop = 0;
1823 embryo->entry->n_p_ability = 0;
1824 embryo->entry->nalloc_p_ability = 0;
1825 embryo->exists = false;
1826 }
1827 embryo->categories = NULL;
1828 embryo->param_index = 0;
1829 embryo->psource_index = 0;
1830 embryo->last_category_index = -1;
1831
1832 return PARSE_ERROR_NONE;
1833 }
1834
1835
parse_entry_template(struct parser * p)1836 static enum parser_error parse_entry_template(struct parser *p)
1837 {
1838 struct embryonic_ui_entry *embryo = parser_priv(p);
1839 const char *name;
1840 const struct ui_entry *tentry;
1841 int template_ind, i;
1842
1843 if (!embryo) {
1844 return PARSE_ERROR_MISSING_RECORD_HEADER;
1845 }
1846 name = parser_getstr(p, "template");
1847 template_ind = ui_entry_lookup(name);
1848 if (template_ind == 0) {
1849 return PARSE_ERROR_INVALID_VALUE;
1850 }
1851 tentry = entries[template_ind - 1];
1852 /* Replace simple fields with what's in the template. */
1853 embryo->entry->renderer_index = tentry->renderer_index;
1854 embryo->entry->combiner_index = tentry->combiner_index;
1855 embryo->entry->default_priority = tentry->default_priority;
1856 embryo->entry->flags = tentry->flags & ~ENTRY_FLAG_TEMPLATE_ONLY;
1857 /* Add in categories not already present. */
1858 for (i = 0; i < tentry->n_category; ++i) {
1859 int ind;
1860
1861 if (! search_embryo_categories(embryo,
1862 tentry->categories[i].name, &ind)) {
1863 insert_embryo_category(embryo,
1864 tentry->categories[i].name, 0,
1865 tentry->categories[i].priority,
1866 tentry->categories[i].priority_set, ind);
1867 }
1868 }
1869 return PARSE_ERROR_NONE;
1870 }
1871
1872
parse_entry_parameter(struct parser * p)1873 static enum parser_error parse_entry_parameter(struct parser *p)
1874 {
1875 struct embryonic_ui_entry *embryo = parser_priv(p);
1876 const char *name;
1877 int n = N_ELEMENTS(name_parameters);
1878 int i;
1879
1880 if (!embryo) {
1881 return PARSE_ERROR_MISSING_RECORD_HEADER;
1882 }
1883 /*
1884 * Don't allow parameterization when editing an already existing entry.
1885 */
1886 if (embryo->exists) {
1887 return PARSE_ERROR_INVALID_OPTION;
1888 }
1889 name = parser_getstr(p, "parameter");
1890 i = 0;
1891 while (1) {
1892 if (i >= n) {
1893 return PARSE_ERROR_INVALID_VALUE;
1894 }
1895 if (streq(name, name_parameters[i].name)) {
1896 embryo->param_index = i;
1897 break;
1898 }
1899 ++i;
1900 }
1901 return PARSE_ERROR_NONE;
1902 }
1903
1904
parse_entry_renderer(struct parser * p)1905 static enum parser_error parse_entry_renderer(struct parser *p)
1906 {
1907 struct embryonic_ui_entry *embryo = parser_priv(p);
1908 const char *name;
1909
1910 if (!embryo) {
1911 return PARSE_ERROR_MISSING_RECORD_HEADER;
1912 }
1913 name = parser_getstr(p, "renderer");
1914 embryo->entry->renderer_index = ui_entry_renderer_lookup(name);
1915 return (embryo->entry->renderer_index) ?
1916 PARSE_ERROR_NONE : PARSE_ERROR_INVALID_VALUE;
1917 }
1918
1919
parse_entry_combine(struct parser * p)1920 static enum parser_error parse_entry_combine(struct parser *p)
1921 {
1922 struct embryonic_ui_entry *embryo = parser_priv(p);
1923 const char *name;
1924
1925 if (!embryo) {
1926 return PARSE_ERROR_MISSING_RECORD_HEADER;
1927 }
1928 name = parser_getstr(p, "combine");
1929 embryo->entry->combiner_index = ui_entry_combiner_lookup(name);
1930 return (embryo->entry->combiner_index) ?
1931 PARSE_ERROR_NONE : PARSE_ERROR_INVALID_VALUE;
1932 }
1933
1934
parse_entry_label(struct parser * p)1935 static enum parser_error parse_entry_label(struct parser *p)
1936 {
1937 struct embryonic_ui_entry *embryo = parser_priv(p);
1938 const char *name;
1939 size_t nw;
1940
1941 if (!embryo) {
1942 return PARSE_ERROR_MISSING_RECORD_HEADER;
1943 }
1944 name = parser_getstr(p, "label");
1945 mem_free(embryo->entry->label);
1946 embryo->entry->label = mem_alloc((MAX_ENTRY_LABEL + 1) *
1947 sizeof(*embryo->entry->label));
1948 nw = text_mbstowcs(embryo->entry->label, name, MAX_ENTRY_LABEL);
1949 if (nw != (size_t)-1) {
1950 /* Ensure null termination. */
1951 size_t nw2 = text_mbstowcs(embryo->entry->label + nw, "", 1);
1952
1953 if (nw2 != (size_t)-1) {
1954 embryo->entry->nlabel = nw;
1955 } else {
1956 return PARSE_ERROR_INVALID_VALUE;
1957 }
1958 } else {
1959 return PARSE_ERROR_INVALID_VALUE;
1960 }
1961 return PARSE_ERROR_NONE;
1962 }
1963
1964
parse_entry_shortened_label(struct parser * p)1965 static enum parser_error parse_entry_shortened_label(struct parser *p)
1966 {
1967 struct embryonic_ui_entry *embryo = parser_priv(p);
1968 int n = 1;
1969 const char *name;
1970 size_t nw;
1971
1972 if (!embryo) {
1973 return PARSE_ERROR_MISSING_RECORD_HEADER;
1974 }
1975 while (1) {
1976 if (n > MAX_SHORTENED) {
1977 return PARSE_ERROR_INTERNAL;
1978 }
1979 if (parser_hasval(p, format("label%d", n))) {
1980 break;
1981 }
1982 ++n;
1983 }
1984 name = parser_getstr(p, format("label%d", n));
1985 nw = text_mbstowcs(embryo->entry->shortened_labels[n - 1], name, n);
1986 if (nw != (size_t) -1) {
1987 /* Ensure null termination. */
1988 size_t nw2 = text_mbstowcs(
1989 embryo->entry->shortened_labels[n - 1] +
1990 ((int)nw < n ? (int)nw : n), "", 1);
1991
1992 if (nw2 != (size_t)-1) {
1993 embryo->entry->nshortened[n - 1] = nw;
1994 } else {
1995 return PARSE_ERROR_INVALID_VALUE;
1996 }
1997 } else {
1998 return PARSE_ERROR_INVALID_VALUE;
1999 }
2000 return PARSE_ERROR_NONE;
2001 }
2002
2003
parse_entry_category(struct parser * p)2004 static enum parser_error parse_entry_category(struct parser *p)
2005 {
2006 struct embryonic_ui_entry *embryo = parser_priv(p);
2007 const char *name;
2008 int ind;
2009
2010 if (!embryo) {
2011 return PARSE_ERROR_MISSING_RECORD_HEADER;
2012 }
2013 name = parser_getstr(p, "category");
2014 if (! search_embryo_categories(embryo, name, &ind)) {
2015 insert_embryo_category(embryo, name, embryo->psource_index,
2016 embryo->entry->default_priority, false, ind);
2017 }
2018 embryo->last_category_index = ind;
2019 return PARSE_ERROR_NONE;
2020 }
2021
2022
parse_entry_priority(struct parser * p)2023 static enum parser_error parse_entry_priority(struct parser *p)
2024 {
2025 struct embryonic_ui_entry *embryo = parser_priv(p);
2026 const char *name;
2027 int psource_index = 0;
2028 int priority = 0;
2029 int n = N_ELEMENTS(priority_schemes);
2030 int i;
2031
2032 if (!embryo) {
2033 return PARSE_ERROR_MISSING_RECORD_HEADER;
2034 }
2035 name = parser_getstr(p, "priority");
2036 i = 1;
2037 while (1) {
2038 if (i >= n) {
2039 char *end;
2040 long v;
2041
2042 v = strtol(name, &end, 10);
2043 if (! *name || *end) {
2044 return PARSE_ERROR_NOT_NUMBER;
2045 }
2046 priority = (v <= INT_MAX) ?
2047 ((v >= INT_MIN) ? v : INT_MIN) : INT_MAX;
2048 break;
2049 }
2050 if (streq(name, priority_schemes[i].name)) {
2051 if (embryo->exists) {
2052 priority = (*priority_schemes[i].priority)(
2053 embryo->entry->param_index);
2054 } else {
2055 psource_index = i;
2056 }
2057 break;
2058 }
2059 ++i;
2060 }
2061 if (embryo->last_category_index == -1) {
2062 embryo->psource_index = psource_index;
2063 embryo->entry->default_priority = priority;
2064 } else {
2065 if (embryo->exists) {
2066 embryo->entry->categories[embryo->last_category_index].priority = priority;
2067 embryo->entry->categories[embryo->last_category_index].priority_set = true;
2068 } else {
2069 embryo->categories[embryo->last_category_index].psource_index = psource_index;
2070 embryo->categories[embryo->last_category_index].priority = priority;
2071 embryo->categories[embryo->last_category_index].priority_set = true;
2072 }
2073 }
2074 return PARSE_ERROR_NONE;
2075 }
2076
2077
parse_entry_flags(struct parser * p)2078 static enum parser_error parse_entry_flags(struct parser *p)
2079 {
2080 struct embryonic_ui_entry *embryo = parser_priv(p);
2081 char *flags;
2082 char *s;
2083 int n;
2084
2085 if (!embryo) {
2086 return PARSE_ERROR_MISSING_RECORD_HEADER;
2087 }
2088 if (!parser_hasval(p, "flags")) {
2089 return PARSE_ERROR_NONE;
2090 }
2091 flags = string_make(parser_getstr(p, "flags"));
2092 n = N_ELEMENTS(entry_flags);
2093 s = strtok(flags, " |");
2094 while (s) {
2095 int i = 0;
2096
2097 while (1) {
2098 if (i >= n) {
2099 string_free(flags);
2100 return PARSE_ERROR_INVALID_FLAG;
2101 }
2102 if (streq(s, entry_flags[i].name)) {
2103 embryo->entry->flags |= entry_flags[i].value;
2104 break;
2105 }
2106 ++i;
2107 }
2108 s = strtok(NULL, " |");
2109 }
2110 string_free(flags);
2111 return PARSE_ERROR_NONE;
2112 }
2113
2114
parse_entry_desc(struct parser * p)2115 static enum parser_error parse_entry_desc(struct parser *p)
2116 {
2117 struct embryonic_ui_entry *embryo = parser_priv(p);
2118
2119 if (!embryo) {
2120 return PARSE_ERROR_MISSING_RECORD_HEADER;
2121 }
2122 /* Don't bother to store the description. */
2123 return PARSE_ERROR_NONE;
2124 }
2125
2126
init_parse_ui_entry(void)2127 static struct parser *init_parse_ui_entry(void)
2128 {
2129 struct parser *p = parser_new();
2130 int i;
2131
2132 parser_setpriv(p, NULL);
2133 parser_reg(p, "name str name", parse_entry_name);
2134 parser_reg(p, "template str template", parse_entry_template);
2135 parser_reg(p, "parameter str parameter", parse_entry_parameter);
2136 parser_reg(p, "renderer str renderer", parse_entry_renderer);
2137 parser_reg(p, "combine str combine", parse_entry_combine);
2138 parser_reg(p, "label str label", parse_entry_label);
2139 for (i = 1; i <= MAX_SHORTENED; ++i) {
2140 parser_reg(p, format("label%d str label%d", i, i),
2141 parse_entry_shortened_label);
2142 }
2143 parser_reg(p, "category str category", parse_entry_category);
2144 parser_reg(p, "priority str priority", parse_entry_priority);
2145 parser_reg(p, "flags ?str flags", parse_entry_flags);
2146 parser_reg(p, "desc str desc", parse_entry_desc);
2147 return p;
2148 }
2149
2150
run_parse_ui_entry(struct parser * p)2151 static errr run_parse_ui_entry(struct parser *p)
2152 {
2153 errr result = parse_file(p, "ui_entry_base");
2154 int i;
2155
2156 if (result != 0) {
2157 return result;
2158 }
2159 if (hatch_last_embryo(p)) {
2160 return 1;
2161 }
2162 /*
2163 * Mark those as templates only so they'll never be directly displayed.
2164 */
2165 for (i = 0; i < n_entry; ++i) {
2166 entries[i]->flags |= ENTRY_FLAG_TEMPLATE_ONLY;
2167 }
2168 return parse_file(p, "ui_entry");
2169 }
2170
2171
finish_parse_ui_entry(struct parser * p)2172 static errr finish_parse_ui_entry(struct parser *p)
2173 {
2174 errr result = 0;
2175 int i;
2176
2177 if (hatch_last_embryo(p)) {
2178 result = 1;
2179 }
2180 for (i = 0; i < n_entry; ++i) {
2181 int j;
2182
2183 /* Set labels not already configured. */
2184 if (entries[i]->nlabel == 0) {
2185 size_t n;
2186
2187 entries[i]->label = mem_alloc((MAX_ENTRY_LABEL + 1) *
2188 sizeof(*entries[i]->label));
2189 n = text_mbstowcs(entries[i]->label, entries[i]->name,
2190 MAX_ENTRY_LABEL);
2191 if (n != (size_t)-1) {
2192 /* Ensure null termination. */
2193 size_t n2 = text_mbstowcs(
2194 entries[i]->label + n, "", 1);
2195
2196 if (n2 != (size_t)-1) {
2197 entries[i]->nlabel = n;
2198 } else {
2199 result = 1;
2200 }
2201 } else {
2202 result = 1;
2203 }
2204 }
2205 fill_out_shortened(entries[i]);
2206
2207 /* Set priorities not already configured. */
2208 for (j = 0; j < entries[i]->n_category; ++j) {
2209 if (! entries[i]->categories[j].priority_set) {
2210 entries[i]->categories[j].priority =
2211 entries[i]->default_priority;
2212 entries[i]->categories[j].priority_set = true;
2213 }
2214 }
2215 }
2216
2217 parser_destroy(p);
2218 return result;
2219 }
2220
2221
cleanup_parse_ui_entry(void)2222 static void cleanup_parse_ui_entry(void)
2223 {
2224 int i;
2225
2226 for (i = 0; i < n_entry; ++i) {
2227 string_free(entries[i]->name);
2228 mem_free(entries[i]->categories);
2229 mem_free(entries[i]->obj_props);
2230 mem_free(entries[i]->p_abilities);
2231 mem_free(entries[i]->label);
2232 mem_free(entries[i]);
2233 }
2234 mem_free(entries);
2235 n_entry = 0;
2236 nalloc_entry = 0;
2237 entries = NULL;
2238
2239 for (i = 0; i < n_category; ++i) {
2240 string_free(categories[i]);
2241 }
2242 mem_free(categories);
2243 n_category = 0;
2244 nalloc_category = 0;
2245 categories = NULL;
2246 }
2247
2248
2249 struct file_parser ui_entry_parser = {
2250 "ui_entry",
2251 init_parse_ui_entry,
2252 run_parse_ui_entry,
2253 finish_parse_ui_entry,
2254 cleanup_parse_ui_entry
2255 };
2256