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