1 /**
2 * \file player-timed.c
3 * \brief Timed effects handling
4 *
5 * Copyright (c) 1997 Ben Harrison
6 * Copyright (c) 2007 Andi Sidwell
7 *
8 * This work is free software; you can redistribute it and/or modify it
9 * under the terms of either:
10 *
11 * a) the GNU General Public License as published by the Free Software
12 * Foundation, version 2, or
13 *
14 * b) the "Angband licence":
15 * This software may be copied and distributed for educational, research,
16 * and not for profit purposes provided that this copyright and statement
17 * are included in all such copies. Other copyrights may also apply.
18 */
19
20 #include "angband.h"
21 #include "cave.h"
22 #include "datafile.h"
23 #include "init.h"
24 #include "mon-util.h"
25 #include "obj-gear.h"
26 #include "obj-knowledge.h"
27 #include "obj-util.h"
28 #include "player-calcs.h"
29 #include "player-timed.h"
30 #include "player-util.h"
31
32 int PY_FOOD_MAX;
33 int PY_FOOD_FULL;
34 int PY_FOOD_HUNGRY;
35 int PY_FOOD_WEAK;
36 int PY_FOOD_FAINT;
37 int PY_FOOD_STARVE;
38
39 /**
40 * ------------------------------------------------------------------------
41 * Parsing functions for player_timed.txt
42 * ------------------------------------------------------------------------ */
43
44 const char *list_player_flag_names[] = {
45 #define PF(a) #a,
46 #include "list-player-flags.h"
47 #undef PF
48 NULL
49 };
50
51 struct timed_effect_data timed_effects[TMD_MAX] = {
52 #define TMD(a, b, c) { #a, b, c },
53 #include "list-player-timed.h"
54 #undef TMD
55 };
56
timed_name_to_idx(const char * name)57 int timed_name_to_idx(const char *name)
58 {
59 for (size_t i = 0; i < N_ELEMENTS(timed_effects); i++) {
60 if (my_stricmp(name, timed_effects[i].name) == 0) {
61 return i;
62 }
63 }
64
65 return -1;
66 }
67
68 /**
69 * List of timed effect names
70 */
71 static const char *list_timed_effect_names[] = {
72 #define TMD(a, b, c) #a,
73 #include "list-player-timed.h"
74 #undef TMD
75 "MAX",
76 NULL
77 };
78
parse_player_timed_name(struct parser * p)79 static enum parser_error parse_player_timed_name(struct parser *p)
80 {
81 const char *name = parser_getstr(p, "name");
82 int index;
83
84 if (grab_name("timed effect",
85 name,
86 list_timed_effect_names,
87 N_ELEMENTS(list_timed_effect_names),
88 &index)) {
89 /* XXX not a desctiptive error */
90 return PARSE_ERROR_INVALID_SPELL_NAME;
91 }
92
93 struct timed_effect_data *t = &timed_effects[index];
94
95 t->index = index;
96 parser_setpriv(p, t);
97
98 return PARSE_ERROR_NONE;
99 }
100
parse_player_timed_desc(struct parser * p)101 static enum parser_error parse_player_timed_desc(struct parser *p)
102 {
103 struct timed_effect_data *t = parser_priv(p);
104 assert(t);
105
106 t->desc = string_append(t->desc, parser_getstr(p, "text"));
107 return PARSE_ERROR_NONE;
108 }
109
parse_player_timed_end_message(struct parser * p)110 static enum parser_error parse_player_timed_end_message(struct parser *p)
111 {
112 struct timed_effect_data *t = parser_priv(p);
113 assert(t);
114
115 t->on_end = string_append(t->on_end, parser_getstr(p, "text"));
116 return PARSE_ERROR_NONE;
117 }
118
parse_player_timed_increase_message(struct parser * p)119 static enum parser_error parse_player_timed_increase_message(struct parser *p)
120 {
121 struct timed_effect_data *t = parser_priv(p);
122 assert(t);
123
124 t->on_increase = string_append(t->on_increase, parser_getstr(p, "text"));
125 return PARSE_ERROR_NONE;
126 }
127
parse_player_timed_decrease_message(struct parser * p)128 static enum parser_error parse_player_timed_decrease_message(struct parser *p)
129 {
130 struct timed_effect_data *t = parser_priv(p);
131 assert(t);
132
133 t->on_decrease = string_append(t->on_decrease, parser_getstr(p, "text"));
134 return PARSE_ERROR_NONE;
135 }
136
parse_player_timed_message_type(struct parser * p)137 static enum parser_error parse_player_timed_message_type(struct parser *p)
138 {
139 struct timed_effect_data *t = parser_priv(p);
140 assert(t);
141
142 t->msgt = message_lookup_by_name(parser_getsym(p, "type"));
143
144 return t->msgt < 0 ?
145 PARSE_ERROR_INVALID_MESSAGE :
146 PARSE_ERROR_NONE;
147 }
148
parse_player_timed_fail(struct parser * p)149 static enum parser_error parse_player_timed_fail(struct parser *p)
150 {
151 struct timed_effect_data *t = parser_priv(p);
152 assert(t);
153
154 t->fail_code = parser_getuint(p, "code");
155
156 const char *name = parser_getstr(p, "flag");
157 if (t->fail_code == TMD_FAIL_FLAG_OBJECT) {
158 int flag = lookup_flag(list_obj_flag_names, name);
159 if (flag == FLAG_END)
160 return PARSE_ERROR_INVALID_FLAG;
161 else
162 t->fail = flag;
163 } else if (t->fail_code == TMD_FAIL_FLAG_PLAYER) {
164 int flag = lookup_flag(list_player_flag_names, name);
165 if (flag == FLAG_END)
166 return PARSE_ERROR_INVALID_FLAG;
167 else
168 t->fail = flag;
169 } else if ((t->fail_code == TMD_FAIL_FLAG_RESIST) ||
170 (t->fail_code == TMD_FAIL_FLAG_VULN)) {
171 size_t i = 0;
172 while (list_element_names[i] && !streq(list_element_names[i], name))
173 i++;
174
175 if (i == ELEM_MAX)
176 return PARSE_ERROR_INVALID_FLAG;
177 else
178 t->fail = i;
179 } else {
180 return PARSE_ERROR_INVALID_FLAG;
181 }
182
183 return PARSE_ERROR_NONE;
184 }
185
parse_player_timed_grade(struct parser * p)186 static enum parser_error parse_player_timed_grade(struct parser *p)
187 {
188 struct timed_effect_data *t = parser_priv(p);
189 struct timed_grade *current = t->grade;
190 struct timed_grade *l = mem_zalloc(sizeof(*l));
191 const char *color = parser_getsym(p, "color");
192 int attr = 0;
193 assert(t);
194
195 /* Make a zero grade structure if there isn't one */
196 if (!current) {
197 t->grade = mem_zalloc(sizeof(struct timed_grade));
198 current = t->grade;
199 }
200
201 /* Move to the highest grade so far */
202 while (current->next) {
203 current = current->next;
204 }
205
206 /* Add the new one */
207 current->next = l;
208 l->grade = current->grade + 1;
209
210 if (strlen(color) > 1) {
211 attr = color_text_to_attr(color);
212 } else {
213 attr = color_char_to_attr(color[0]);
214 }
215 if (attr < 0)
216 return PARSE_ERROR_INVALID_COLOR;
217 l->color = attr;
218
219 l->max = parser_getint(p, "max");
220 l->name = string_make(parser_getsym(p, "name"));
221
222 /* Name may be a dummy (eg hunger)*/
223 if (strlen(l->name) == 1) {
224 string_free(l->name);
225 l->name = NULL;
226 }
227
228 l->up_msg = string_make(parser_getsym(p, "up_msg"));
229
230 /* Message may be a dummy */
231 if (strlen(l->up_msg) == 1) {
232 string_free(l->up_msg);
233 l->up_msg = NULL;
234 }
235
236 if (parser_hasval(p, "down_msg")) {
237 l->down_msg = string_make(parser_getsym(p, "down_msg"));
238 }
239
240 /* Set food constants and deal with percentages */
241 if (streq(t->name, "FOOD")) {
242 l->max *= z_info->food_value;
243 if (streq(l->name, "Starving")) {
244 PY_FOOD_STARVE = l->max;
245 } else if (streq(l->name, "Faint")) {
246 PY_FOOD_FAINT = l->max;
247 } else if (streq(l->name, "Weak")) {
248 PY_FOOD_WEAK = l->max;
249 } else if (streq(l->name, "Hungry")) {
250 PY_FOOD_HUNGRY = l->max;
251 } else if (streq(l->name, "Fed")) {
252 PY_FOOD_FULL = l->max;
253 } else if (streq(l->name, "Full")) {
254 PY_FOOD_MAX = l->max;
255 }
256 }
257
258 return PARSE_ERROR_NONE;
259 }
260
init_parse_player_timed(void)261 static struct parser *init_parse_player_timed(void)
262 {
263 struct parser *p = parser_new();
264 parser_setpriv(p, NULL);
265 parser_reg(p, "name str name", parse_player_timed_name);
266 parser_reg(p, "desc str text", parse_player_timed_desc);
267 parser_reg(p, "on-end str text", parse_player_timed_end_message);
268 parser_reg(p, "on-increase str text", parse_player_timed_increase_message);
269 parser_reg(p, "on-decrease str text", parse_player_timed_decrease_message);
270 parser_reg(p, "msgt sym type", parse_player_timed_message_type);
271 parser_reg(p, "fail uint code str flag", parse_player_timed_fail);
272 parser_reg(p, "grade sym color int max sym name sym up_msg ?sym down_msg", parse_player_timed_grade);
273 return p;
274 }
275
run_parse_player_timed(struct parser * p)276 static errr run_parse_player_timed(struct parser *p)
277 {
278 return parse_file_quit_not_found(p, "player_timed");
279 }
280
finish_parse_player_timed(struct parser * p)281 static errr finish_parse_player_timed(struct parser *p)
282 {
283 parser_destroy(p);
284 return 0;
285 }
286
cleanup_player_timed(void)287 static void cleanup_player_timed(void)
288 {
289 for (size_t i = 0; i < TMD_MAX; i++) {
290 struct timed_effect_data *effect = &timed_effects[i];
291 struct timed_grade *grade = effect->grade;
292
293 while (grade) {
294 struct timed_grade *next = grade->next;
295 string_free(grade->name);
296 if (grade->up_msg) string_free(grade->up_msg);
297 if (grade->down_msg) string_free(grade->down_msg);
298 mem_free(grade);
299 grade = next;
300 }
301 effect->grade = NULL;
302
303 string_free(effect->desc);
304
305 if (effect->on_end)
306 string_free(effect->on_end);
307 if (effect->on_increase)
308 string_free(effect->on_increase);
309 if (effect->on_decrease)
310 string_free(effect->on_decrease);
311
312 effect->desc = NULL;
313 effect->on_end = NULL;
314 effect->on_increase = NULL;
315 effect->on_decrease = NULL;
316 }
317 }
318
319 struct file_parser player_timed_parser = {
320 "player timed effects",
321 init_parse_player_timed,
322 run_parse_player_timed,
323 finish_parse_player_timed,
324 cleanup_player_timed
325 };
326
327
328 /**
329 * ------------------------------------------------------------------------
330 * Utilities for more complex or anomolous effects
331 * ------------------------------------------------------------------------ */
332 /**
333 * Swap stats at random to temporarily scramble the player's stats.
334 */
player_scramble_stats(struct player * p)335 static void player_scramble_stats(struct player *p)
336 {
337 int max1, cur1, max2, cur2, i, j, swap;
338
339 /* Fisher-Yates shuffling algorithm */
340 for (i = STAT_MAX - 1; i > 0; --i) {
341 j = randint0(i);
342
343 max1 = p->stat_max[i];
344 cur1 = p->stat_cur[i];
345 max2 = p->stat_max[j];
346 cur2 = p->stat_cur[j];
347
348 p->stat_max[i] = max2;
349 p->stat_cur[i] = cur2;
350 p->stat_max[j] = max1;
351 p->stat_cur[j] = cur1;
352
353 /* Record what we did */
354 swap = p->stat_map[i];
355 p->stat_map[i] = p->stat_map[j];
356 p->stat_map[j] = swap;
357 }
358
359 return;
360 }
361
362 /**
363 * Undo scrambled stats when effect runs out.
364 */
player_fix_scramble(struct player * p)365 static void player_fix_scramble(struct player *p)
366 {
367 /* Figure out what stats should be */
368 int new_cur[STAT_MAX];
369 int new_max[STAT_MAX];
370
371 for (int i = 0; i < STAT_MAX; i++) {
372 new_cur[p->stat_map[i]] = p->stat_cur[i];
373 new_max[p->stat_map[i]] = p->stat_max[i];
374 }
375
376 /* Apply new stats and clear the stat_map */
377 for (int i = 0; i < STAT_MAX; i++) {
378 p->stat_cur[i] = new_cur[i];
379 p->stat_max[i] = new_max[i];
380 p->stat_map[i] = i;
381 }
382 }
383
384 /**
385 * Return true if the player timed effect matches the given string
386 */
player_timed_grade_eq(struct player * p,int idx,char * match)387 bool player_timed_grade_eq(struct player *p, int idx, char *match)
388 {
389 if (p->timed[idx]) {
390 struct timed_grade *grade = timed_effects[idx].grade;
391 while (p->timed[idx] > grade->max) {
392 grade = grade->next;
393 }
394 if (grade->name && streq(grade->name, match)) return true;
395 }
396
397 return false;
398 }
399
player_of_has_prot_conf(struct player * p)400 static bool player_of_has_prot_conf(struct player *p)
401 {
402 bitflag collect_f[OF_SIZE], f[OF_SIZE];
403 int i;
404
405 player_flags(p, collect_f);
406
407 for (i = 0; i < p->body.count; i++) {
408 struct object *obj = slot_object(p, i);
409
410 if (!obj) continue;
411 object_flags(obj, f);
412 of_union(collect_f, f);
413 }
414
415 return of_has(collect_f, OF_PROT_CONF);
416 }
417
418 /**
419 * ------------------------------------------------------------------------
420 * Setting, increasing, decreasing and clearing timed effects
421 * ------------------------------------------------------------------------ */
422 /**
423 * Set a timed effect.
424 */
player_set_timed(struct player * p,int idx,int v,bool notify)425 bool player_set_timed(struct player *p, int idx, int v, bool notify)
426 {
427 assert(idx >= 0);
428 assert(idx < TMD_MAX);
429
430 struct timed_effect_data *effect = &timed_effects[idx];
431 struct timed_grade *new_grade = effect->grade;
432 struct timed_grade *current_grade = effect->grade;
433 struct object *weapon = equipped_item_by_slot_name(p, "weapon");
434
435 /* Lower bound */
436 v = MAX(v, (idx == TMD_FOOD) ? 1 : 0);
437
438 /* No change */
439 if (p->timed[idx] == v) {
440 return false;
441 }
442
443 /* Find the grade we will be going to, and the current one */
444 while (v > new_grade->max) {
445 new_grade = new_grade->next;
446 if (!new_grade->next) break;
447 }
448 while (p->timed[idx] > current_grade->max) {
449 current_grade = current_grade->next;
450 if (!current_grade->next) break;
451 }
452
453 /* Upper bound */
454 v = MIN(v, new_grade->max);
455
456 /* Don't mention effects which already match the player state. */
457 if (idx == TMD_OPP_ACID && player_is_immune(p, ELEM_ACID)) {
458 notify = false;
459 } else if (idx == TMD_OPP_ELEC && player_is_immune(p, ELEM_ELEC)) {
460 notify = false;
461 } else if (idx == TMD_OPP_FIRE && player_is_immune(p, ELEM_FIRE)) {
462 notify = false;
463 } else if (idx == TMD_OPP_COLD && player_is_immune(p, ELEM_COLD)) {
464 notify = false;
465 } else if (idx == TMD_OPP_CONF && player_of_has_prot_conf(p)) {
466 notify = false;
467 }
468
469 /* Always mention going up a grade, otherwise on request */
470 if (new_grade->grade > current_grade->grade) {
471 print_custom_message(weapon, new_grade->up_msg, effect->msgt);
472 notify = true;
473 } else if ((new_grade->grade < current_grade->grade) &&
474 (new_grade->down_msg)) {
475 print_custom_message(weapon, new_grade->down_msg, effect->msgt);
476 notify = true;
477 } else if (notify) {
478 if (v == 0) {
479 /* Finishing */
480 print_custom_message(weapon, effect->on_end, MSG_RECOVER);
481 } else if (p->timed[idx] > v && effect->on_decrease) {
482 /* Decrementing */
483 print_custom_message(weapon, effect->on_decrease, effect->msgt);
484 } else if (v > p->timed[idx] && effect->on_increase) {
485 /* Incrementing */
486 print_custom_message(weapon, effect->on_increase, effect->msgt);
487 }
488 }
489
490 /* Handle stat swap */
491 if (idx == TMD_SCRAMBLE) {
492 if (p->timed[idx] == 0) {
493 player_scramble_stats(p);
494 } else if (v == 0) {
495 player_fix_scramble(p);
496 }
497 }
498
499 /* Use the value */
500 p->timed[idx] = v;
501
502 /* Sort out the sprint effect */
503 if (idx == TMD_SPRINT && v == 0) {
504 player_inc_timed(p, TMD_SLOW, 100, true, false);
505 }
506
507 if (notify) {
508 /* Disturb */
509 disturb(p);
510
511 /* Update the visuals, as appropriate. */
512 p->upkeep->update |= effect->flag_update;
513 p->upkeep->redraw |= (PR_STATUS | effect->flag_redraw);
514
515 /* Handle stuff */
516 handle_stuff(p);
517 }
518
519 return notify;
520 }
521
522 /**
523 * Check whether a timed effect will affect the player
524 */
player_inc_check(struct player * p,int idx,bool lore)525 bool player_inc_check(struct player *p, int idx, bool lore)
526 {
527 struct timed_effect_data *effect = &timed_effects[idx];
528
529 /* Check that @ can be affected by this effect */
530 if (!effect->fail_code) {
531 return true;
532 }
533
534 /* If we're only doing this for monster lore purposes */
535 if (lore) {
536 if (((effect->fail_code == TMD_FAIL_FLAG_OBJECT) &&
537 (of_has(p->known_state.flags, effect->fail))) ||
538 ((effect->fail_code == TMD_FAIL_FLAG_RESIST) &&
539 (p->known_state.el_info[effect->fail].res_level > 0)) ||
540 ((effect->fail_code == TMD_FAIL_FLAG_VULN) &&
541 (p->known_state.el_info[effect->fail].res_level < 0))) {
542 return false;
543 } else {
544 return true;
545 }
546 }
547
548 /* Determine whether an effect can be prevented by a flag */
549 if (effect->fail_code == TMD_FAIL_FLAG_OBJECT) {
550 /* If the effect is from a monster action, extra stuff happens */
551 struct monster *mon = cave->mon_current > 0 ?
552 cave_monster(cave, cave->mon_current) : NULL;
553
554 /* Effect is inhibited by an object flag */
555 equip_learn_flag(p, effect->fail);
556
557 if (mon) {
558 update_smart_learn(mon, player, effect->fail, 0, -1);
559 }
560
561 if (player_of_has(p, effect->fail)) {
562 if (mon) {
563 msg("You resist the effect!");
564 }
565 return false;
566 }
567 } else if (effect->fail_code == TMD_FAIL_FLAG_RESIST) {
568 /* Effect is inhibited by a resist */
569 equip_learn_element(p, effect->fail);
570 if (p->state.el_info[effect->fail].res_level > 0) {
571 return false;
572 }
573 } else if (effect->fail_code == TMD_FAIL_FLAG_VULN) {
574 /* Effect is inhibited by a vulnerability
575 * the asymmetry with resists is OK for now - NRM */
576 equip_learn_element(p, effect->fail);
577 if (p->state.el_info[effect->fail].res_level < 0) {
578 return false;
579 }
580 } else if (effect->fail_code == TMD_FAIL_FLAG_PLAYER) {
581 /* Effect is inhibited by a player flag */
582 if (player_has(p, effect->fail)) {
583 return false;
584 }
585 }
586
587 /* Special cases */
588 if (effect->index == TMD_POISONED && p->timed[TMD_OPP_POIS])
589 return false;
590
591 return true;
592 }
593
594 /**
595 * Increase the timed effect `idx` by `v`. Mention this if `notify` is true.
596 * Check for resistance to the effect if `check` is true.
597 */
player_inc_timed(struct player * p,int idx,int v,bool notify,bool check)598 bool player_inc_timed(struct player *p, int idx, int v, bool notify, bool check)
599 {
600 assert(idx >= 0);
601 assert(idx < TMD_MAX);
602
603 if (check == false || player_inc_check(p, idx, false) == true) {
604 /* Paralysis should be non-cumulative */
605 if (idx == TMD_PARALYZED && p->timed[TMD_PARALYZED] > 0) {
606 return false;
607 } else {
608 return player_set_timed(p,
609 idx,
610 p->timed[idx] + v,
611 notify);
612 }
613 }
614
615 return false;
616 }
617
618 /**
619 * Decrease the timed effect `idx` by `v`. Mention this if `notify` is true.
620 */
player_dec_timed(struct player * p,int idx,int v,bool notify)621 bool player_dec_timed(struct player *p, int idx, int v, bool notify)
622 {
623 int new_value;
624 assert(idx >= 0);
625 assert(idx < TMD_MAX);
626 new_value = p->timed[idx] - v;
627
628 /* Obey `notify` if not finishing; if finishing, always notify */
629 if (new_value > 0) {
630 return player_set_timed(p, idx, new_value, notify);
631 }
632 return player_set_timed(p, idx, new_value, true);
633 }
634
635 /**
636 * Clear the timed effect `idx`. Mention this if `notify` is true.
637 */
player_clear_timed(struct player * p,int idx,bool notify)638 bool player_clear_timed(struct player *p, int idx, bool notify)
639 {
640 assert(idx >= 0);
641 assert(idx < TMD_MAX);
642
643 return player_set_timed(p, idx, 0, notify);
644 }
645
646