1 /**
2  * \file project-player.c
3  * \brief projection effects on the player
4  *
5  * Copyright (c) 1997 Ben Harrison, James E. Wilson, Robert A. Koeneke
6  *
7  * This work is free software; you can redistribute it and/or modify it
8  * under the terms of either:
9  *
10  * a) the GNU General Public License as published by the Free Software
11  *    Foundation, version 2, or
12  *
13  * b) the "Angband licence":
14  *    This software may be copied and distributed for educational, research,
15  *    and not for profit purposes provided that this copyright and statement
16  *    are included in all such copies.  Other copyrights may also apply.
17  */
18 
19 #include "angband.h"
20 #include "cave.h"
21 #include "effects.h"
22 #include "init.h"
23 #include "mon-desc.h"
24 #include "mon-predicate.h"
25 #include "mon-util.h"
26 #include "obj-desc.h"
27 #include "obj-gear.h"
28 #include "obj-knowledge.h"
29 #include "player-calcs.h"
30 #include "player-timed.h"
31 #include "player-util.h"
32 #include "project.h"
33 #include "source.h"
34 #include "trap.h"
35 
36 /**
37  * Adjust damage according to resistance or vulnerability.
38  *
39  * \param p is the player
40  * \param type is the attack type we are checking.
41  * \param dam is the unadjusted damage.
42  * \param dam_aspect is the calc we want (min, avg, max, random).
43  * \param resist is the degree of resistance (-1 = vuln, 3 = immune).
44  */
adjust_dam(struct player * p,int type,int dam,aspect dam_aspect,int resist,bool actual)45 int adjust_dam(struct player *p, int type, int dam, aspect dam_aspect,
46 			   int resist, bool actual)
47 {
48 	int i, denom = 0;
49 
50 	/* If an actual player exists, get their actual resist */
51 	if (p && p->race) {
52 		/* Ice is a special case */
53 		int res_type = (type == PROJ_ICE) ? PROJ_COLD: type;
54 		resist = res_type < ELEM_MAX ? p->state.el_info[res_type].res_level : 0;
55 
56 		/* Notice element stuff */
57 		if (actual) {
58 			equip_learn_element(p, res_type);
59 		}
60 	}
61 
62 	if (resist == 3) /* immune */
63 		return 0;
64 
65 	/* Hack - acid damage is halved by armour */
66 	if (type == PROJ_ACID && p && minus_ac(p))
67 		dam = (dam + 1) / 2;
68 
69 	if (resist == -1) /* vulnerable */
70 		return (dam * 4 / 3);
71 
72 	/* Variable resists vary the denominator, so we need to invert the logic
73 	 * of dam_aspect. (m_bonus is unused) */
74 	switch (dam_aspect) {
75 		case MINIMISE:
76 			denom = randcalc(projections[type].denominator, 0, MAXIMISE);
77 			break;
78 		case MAXIMISE:
79 			denom = randcalc(projections[type].denominator, 0, MINIMISE);
80 			break;
81 		case AVERAGE:
82 		case EXTREMIFY:
83 		case RANDOMISE:
84 			denom = randcalc(projections[type].denominator, 0, dam_aspect);
85 			break;
86 		default:
87 			assert(0);
88 	}
89 
90 	for (i = resist; i > 0; i--)
91 		if (denom)
92 			dam = dam * projections[type].numerator / denom;
93 
94 	return dam;
95 }
96 
97 
98 /**
99  * ------------------------------------------------------------------------
100  * Player handlers
101  * ------------------------------------------------------------------------ */
102 
103 /**
104  * Drain stats at random
105  *
106  * \param num is the number of points to drain
107  */
project_player_drain_stats(int num)108 static void project_player_drain_stats(int num)
109 {
110 	int i, k = 0;
111 	const char *act = NULL;
112 
113 	for (i = 0; i < num; i++) {
114 		switch (randint1(5)) {
115 			case 1: k = STAT_STR; act = "strong"; break;
116 			case 2: k = STAT_INT; act = "bright"; break;
117 			case 3: k = STAT_WIS; act = "wise"; break;
118 			case 4: k = STAT_DEX; act = "agile"; break;
119 			case 5: k = STAT_CON; act = "hale"; break;
120 		}
121 
122 		msg("You're not as %s as you used to be...", act);
123 		player_stat_dec(player, k, false);
124 	}
125 
126 	return;
127 }
128 
129 typedef struct project_player_handler_context_s {
130 	/* Input values */
131 	const struct source origin;
132 	const int r;
133 	const struct loc grid;
134 	int dam; /* May need adjustment */
135 	const int type;
136 	const int power;
137 
138 	/* Return values */
139 	bool obvious;
140 } project_player_handler_context_t;
141 
142 typedef int (*project_player_handler_f)(project_player_handler_context_t *);
143 
project_player_handler_ACID(project_player_handler_context_t * context)144 static int project_player_handler_ACID(project_player_handler_context_t *context)
145 {
146 	if (player_is_immune(player, ELEM_ACID)) return 0;
147 	inven_damage(player, PROJ_ACID, MIN(context->dam * 5, 300));
148 	return 0;
149 }
150 
project_player_handler_ELEC(project_player_handler_context_t * context)151 static int project_player_handler_ELEC(project_player_handler_context_t *context)
152 {
153 	if (player_is_immune(player, ELEM_ELEC)) return 0;
154 	inven_damage(player, PROJ_ELEC, MIN(context->dam * 5, 300));
155 	return 0;
156 }
157 
project_player_handler_FIRE(project_player_handler_context_t * context)158 static int project_player_handler_FIRE(project_player_handler_context_t *context)
159 {
160 	if (player_is_immune(player, ELEM_FIRE)) return 0;
161 	inven_damage(player, PROJ_FIRE, MIN(context->dam * 5, 300));
162 
163 	/* Occasional side-effects for powerful fire attacks */
164 	if (context->power >= 80) {
165 		if (randint0(context->dam) > 500) {
166 			msg("The intense heat saps you.");
167 			effect_simple(EF_DRAIN_STAT, source_none(), "0", STAT_STR, 0, 0, 0,
168 						  0, &context->obvious);
169 		}
170 		if (randint0(context->dam) > 500) {
171 			if (player_inc_timed(player, TMD_BLIND,
172 								 randint1(context->dam / 100), true, true)) {
173 				msg("Your eyes fill with smoke!");
174 			}
175 		}
176 		if (randint0(context->dam) > 500) {
177 			if (player_inc_timed(player, TMD_POISONED,
178 								 randint1(context->dam / 10), true, true)) {
179 				msg("You are assailed by poisonous fumes!");
180 			}
181 		}
182 	}
183 	return 0;
184 }
185 
project_player_handler_COLD(project_player_handler_context_t * context)186 static int project_player_handler_COLD(project_player_handler_context_t *context)
187 {
188 	if (player_is_immune(player, ELEM_COLD)) return 0;
189 	inven_damage(player, PROJ_COLD, MIN(context->dam * 5, 300));
190 
191 	/* Occasional side-effects for powerful cold attacks */
192 	if (context->power >= 80) {
193 		if (randint0(context->dam) > 500) {
194 			msg("The cold seeps into your bones.");
195 			effect_simple(EF_DRAIN_STAT, source_none(), "0", STAT_DEX, 0, 0, 0,
196 						  0, &context->obvious);
197 		}
198 		if (randint0(context->dam) > 500) {
199 			if (player_of_has(player, OF_HOLD_LIFE)) {
200 				equip_learn_flag(player, OF_HOLD_LIFE);
201 			} else {
202 				int drain = context->dam;
203 				msg("The cold withers your life force!");
204 				player_exp_lose(player, drain, false);
205 			}
206 		}
207 	}
208 	return 0;
209 }
210 
project_player_handler_POIS(project_player_handler_context_t * context)211 static int project_player_handler_POIS(project_player_handler_context_t *context)
212 {
213 	int xtra = 0;
214 
215 	if (!player_inc_timed(player, TMD_POISONED, 10 + randint1(context->dam),
216 						  true, true))
217 		msg("You resist the effect!");
218 
219 	/* Occasional side-effects for powerful poison attacks */
220 	if (context->power >= 60) {
221 		if (randint0(context->dam) > 200) {
222 			if (!player_is_immune(player, ELEM_ACID)) {
223 				int dam = context->dam / 5;
224 				msg("The venom stings your skin!");
225 				inven_damage(player, PROJ_ACID, dam);
226 				xtra += adjust_dam(player, PROJ_ACID, dam, RANDOMISE,
227 								 player->state.el_info[PROJ_ACID].res_level,
228 								 true);
229 			}
230 		}
231 		if (randint0(context->dam) > 200) {
232 			msg("The stench sickens you.");
233 			effect_simple(EF_DRAIN_STAT, source_none(), "0", STAT_CON, 0, 0, 0,
234 						  0, &context->obvious);
235 		}
236 	}
237 	return xtra;
238 }
239 
project_player_handler_LIGHT(project_player_handler_context_t * context)240 static int project_player_handler_LIGHT(project_player_handler_context_t *context)
241 {
242 	if (player_resists(player, ELEM_LIGHT)) {
243 		msg("You resist the effect!");
244 		return 0;
245 	}
246 
247 	(void)player_inc_timed(player, TMD_BLIND, 2 + randint1(5), true, true);
248 
249 	/* Confusion for strong unresisted light */
250 	if (context->dam > 300) {
251 		msg("You are dazzled!");
252 		(void)player_inc_timed(player, TMD_CONFUSED,
253 							   2 + randint1(context->dam / 100), true, true);
254 	}
255 	return 0;
256 }
257 
project_player_handler_DARK(project_player_handler_context_t * context)258 static int project_player_handler_DARK(project_player_handler_context_t *context)
259 {
260 	if (player_resists(player, ELEM_DARK)) {
261 		msg("You resist the effect!");
262 		return 0;
263 	}
264 
265 	(void)player_inc_timed(player, TMD_BLIND, 2 + randint1(5), true, true);
266 
267 	/* Unresisted dark from powerful monsters is bad news */
268 	if (context->power >= 70) {
269 		/* Life draining */
270 		if (randint0(context->dam) > 100) {
271 			if (player_of_has(player, OF_HOLD_LIFE)) {
272 				equip_learn_flag(player, OF_HOLD_LIFE);
273 			} else {
274 				int drain = context->dam;
275 				msg("The darkness steals your life force!");
276 				player_exp_lose(player, drain, false);
277 			}
278 		}
279 
280 		/* Slowing */
281 		if (randint0(context->dam) > 200) {
282 			msg("You feel unsure of yourself in the darkness.");
283 			(void)player_inc_timed(player, TMD_SLOW, context->dam / 100, true,
284 								   false);
285 		}
286 
287 		/* Amnesia */
288 		if (randint0(context->dam) > 300) {
289 			msg("Darkness penetrates your mind!");
290 			(void)player_inc_timed(player, TMD_AMNESIA, context->dam / 100,
291 								   true, false);
292 		}
293 	}
294 	return 0;
295 }
296 
project_player_handler_SOUND(project_player_handler_context_t * context)297 static int project_player_handler_SOUND(project_player_handler_context_t *context)
298 {
299 	if (player_resists(player, ELEM_SOUND)) {
300 		msg("You resist the effect!");
301 		return 0;
302 	}
303 
304 	/* Stun */
305 	if (!player_of_has(player, OF_PROT_STUN)) {
306 		int duration = 5 + randint1(context->dam / 3);
307 		if (duration > 35) duration = 35;
308 		(void)player_inc_timed(player, TMD_STUN, duration, true, true);
309 	} else {
310 		equip_learn_flag(player, OF_PROT_STUN);
311 	}
312 
313 	/* Confusion for strong unresisted sound */
314 	if (context->dam > 300) {
315 		msg("The noise disorients you.");
316 		(void)player_inc_timed(player, TMD_CONFUSED,
317 							   2 + randint1(context->dam / 100), true, true);
318 	}
319 	return 0;
320 }
321 
project_player_handler_SHARD(project_player_handler_context_t * context)322 static int project_player_handler_SHARD(project_player_handler_context_t *context)
323 {
324 	if (player_resists(player, ELEM_SHARD)) {
325 		msg("You resist the effect!");
326 		return 0;
327 	}
328 
329 	/* Cuts */
330 	(void)player_inc_timed(player, TMD_CUT, randint1(context->dam), true,
331 						   false);
332 	return 0;
333 }
334 
project_player_handler_NEXUS(project_player_handler_context_t * context)335 static int project_player_handler_NEXUS(project_player_handler_context_t *context)
336 {
337 	struct monster *mon = NULL;
338 	if (context->origin.what == SRC_MONSTER) {
339 		mon = cave_monster(cave, context->origin.which.monster);
340 	}
341 
342 	if (player_resists(player, ELEM_NEXUS)) {
343 		msg("You resist the effect!");
344 		return 0;
345 	}
346 
347 	/* Stat swap */
348 	if (randint0(100) < player->state.skills[SKILL_SAVE]) {
349 		msg("You avoid the effect!");
350 	} else {
351 		player_inc_timed(player, TMD_SCRAMBLE, randint0(20) + 20, true, true);
352 	}
353 
354 	if (one_in_(3) && mon) { /* Teleport to */
355 		effect_simple(EF_TELEPORT_TO, context->origin, "0", 0, 0, 0,
356 					  mon->grid.y, mon->grid.x, NULL);
357 	} else if (one_in_(4)) { /* Teleport level */
358 		if (randint0(100) < player->state.skills[SKILL_SAVE]) {
359 			msg("You avoid the effect!");
360 			return 0;
361 		}
362 		effect_simple(EF_TELEPORT_LEVEL, context->origin, "0", 0, 0, 0, 0, 0,
363 					  NULL);
364 	} else { /* Teleport */
365 		const char *miles = "200";
366 		effect_simple(EF_TELEPORT, context->origin, miles, 1, 0, 0, 0, 0, NULL);
367 	}
368 	return 0;
369 }
370 
project_player_handler_NETHER(project_player_handler_context_t * context)371 static int project_player_handler_NETHER(project_player_handler_context_t *context)
372 {
373 	int drain = 200 + (player->exp / 100) * z_info->life_drain_percent;
374 
375 	if (player_resists(player, ELEM_NETHER) ||
376 		player_of_has(player, OF_HOLD_LIFE)) {
377 		msg("You resist the effect!");
378 		equip_learn_flag(player, OF_HOLD_LIFE);
379 		return 0;
380 	}
381 
382 	/* Life draining */
383 	msg("You feel your life force draining away!");
384 	player_exp_lose(player, drain, false);
385 
386 	/* Powerful nether attacks have further side-effects */
387 	if (context->power >= 80) {
388 		/* Mana loss */
389 		if ((randint0(context->dam) > 100) && player->msp) {
390 			msg("Your mind is dulled.");
391 			player->csp -= MIN(player->csp, context->dam / 10);
392 			player->upkeep->redraw |= PR_MANA;
393 		}
394 
395 		/* Loss of energy */
396 		if (randint0(context->dam) > 200) {
397 			msg("Your energy is sapped!");
398 			player->energy = 0;
399 		}
400 	}
401 	return 0;
402 }
403 
project_player_handler_CHAOS(project_player_handler_context_t * context)404 static int project_player_handler_CHAOS(project_player_handler_context_t *context)
405 {
406 	if (player_resists(player, ELEM_CHAOS)) {
407 		msg("You resist the effect!");
408 		return 0;
409 	}
410 
411 	/* Hallucination */
412 	(void)player_inc_timed(player, TMD_IMAGE, randint1(10), true, false);
413 
414 	/* Confusion */
415 	(void)player_inc_timed(player, TMD_CONFUSED, 10 + randint0(20), true, true);
416 
417 	/* Life draining */
418 	if (!player_of_has(player, OF_HOLD_LIFE)) {
419 		int drain = ((player->exp * 3)/ (100 * 2)) * z_info->life_drain_percent;
420 		msg("You feel your life force draining away!");
421 		player_exp_lose(player, drain, false);
422 	} else {
423 		equip_learn_flag(player, OF_HOLD_LIFE);
424 	}
425 	return 0;
426 }
427 
project_player_handler_DISEN(project_player_handler_context_t * context)428 static int project_player_handler_DISEN(project_player_handler_context_t *context)
429 {
430 	if (player_resists(player, ELEM_DISEN)) {
431 		msg("You resist the effect!");
432 		return 0;
433 	}
434 
435 	/* Disenchant gear */
436 	effect_simple(EF_DISENCHANT, context->origin, "0", 0, 0, 0, 0, 0, NULL);
437 	return 0;
438 }
439 
project_player_handler_WATER(project_player_handler_context_t * context)440 static int project_player_handler_WATER(project_player_handler_context_t *context)
441 {
442 	/* Confusion */
443 	(void)player_inc_timed(player, TMD_CONFUSED, 5 + randint1(5), true, true);
444 
445 	/* Stun */
446 	(void)player_inc_timed(player, TMD_STUN, randint1(40), true, true);
447 	return 0;
448 }
449 
project_player_handler_ICE(project_player_handler_context_t * context)450 static int project_player_handler_ICE(project_player_handler_context_t *context)
451 {
452 	if (!player_is_immune(player, ELEM_COLD))
453 		inven_damage(player, PROJ_COLD, MIN(context->dam * 5, 300));
454 
455 	/* Cuts */
456 	if (!player_resists(player, ELEM_SHARD))
457 		(void)player_inc_timed(player, TMD_CUT, damroll(5, 8), true, false);
458 	else
459 		msg("You resist the effect!");
460 
461 	/* Stun */
462 	(void)player_inc_timed(player, TMD_STUN, randint1(15), true, true);
463 	return 0;
464 }
465 
project_player_handler_GRAVITY(project_player_handler_context_t * context)466 static int project_player_handler_GRAVITY(project_player_handler_context_t *context)
467 {
468 	msg("Gravity warps around you.");
469 
470 	/* Blink */
471 	if (randint1(127) > player->lev) {
472 		const char *five = "5";
473 		effect_simple(EF_TELEPORT, context->origin, five, 1, 0, 0, 0, 0, NULL);
474 	}
475 
476 	/* Slow */
477 	(void)player_inc_timed(player, TMD_SLOW, 4 + randint0(4), true, false);
478 
479 	/* Stun */
480 	if (!player_of_has(player, OF_PROT_STUN)) {
481 		int duration = 5 + randint1(context->dam / 3);
482 		if (duration > 35) duration = 35;
483 		(void)player_inc_timed(player, TMD_STUN, duration, true, true);
484 	} else {
485 		equip_learn_flag(player, OF_PROT_STUN);
486 	}
487 	return 0;
488 }
489 
project_player_handler_INERTIA(project_player_handler_context_t * context)490 static int project_player_handler_INERTIA(project_player_handler_context_t *context)
491 {
492 	/* Slow */
493 	(void)player_inc_timed(player, TMD_SLOW, 4 + randint0(4), true, false);
494 	return 0;
495 }
496 
project_player_handler_FORCE(project_player_handler_context_t * context)497 static int project_player_handler_FORCE(project_player_handler_context_t *context)
498 {
499 	struct loc centre = origin_get_loc(context->origin);
500 
501 	/* Player gets pushed in a random direction if on the trap */
502 	if (context->origin.what == SRC_TRAP &&	loc_eq(player->grid, centre)) {
503 		int d = randint0(8);
504 		centre = loc_sum(centre, ddgrid_ddd[d]);
505 	}
506 
507 	/* Stun */
508 	(void)player_inc_timed(player, TMD_STUN, randint1(20), true, true);
509 
510 	/* Thrust player away. */
511 	thrust_away(centre, context->grid, 3 + context->dam / 20);
512 	return 0;
513 }
514 
project_player_handler_TIME(project_player_handler_context_t * context)515 static int project_player_handler_TIME(project_player_handler_context_t *context)
516 {
517 	if (one_in_(2)) {
518 		/* Life draining */
519 		int drain = 100 + (player->exp / 100) * z_info->life_drain_percent;
520 		msg("You feel your life force draining away!");
521 		player_exp_lose(player, drain, false);
522 	} else if (!one_in_(5)) {
523 		/* Drain some stats */
524 		project_player_drain_stats(2);
525 	} else {
526 		/* Drain all stats */
527 		int i;
528 		msg("You're not as powerful as you used to be...");
529 
530 		for (i = 0; i < STAT_MAX; i++)
531 			player_stat_dec(player, i, false);
532 	}
533 	return 0;
534 }
535 
project_player_handler_PLASMA(project_player_handler_context_t * context)536 static int project_player_handler_PLASMA(project_player_handler_context_t *context)
537 {
538 	/* Stun */
539 	if (!player_of_has(player, OF_PROT_STUN)) {
540 		int duration = 5 + randint1(context->dam * 3 / 4);
541 		if (duration > 35) duration = 35;
542 		(void)player_inc_timed(player, TMD_STUN, duration, true, true);
543 	} else {
544 		equip_learn_flag(player, OF_PROT_STUN);
545 	}
546 	return 0;
547 }
548 
project_player_handler_METEOR(project_player_handler_context_t * context)549 static int project_player_handler_METEOR(project_player_handler_context_t *context)
550 {
551 	return 0;
552 }
553 
project_player_handler_MISSILE(project_player_handler_context_t * context)554 static int project_player_handler_MISSILE(project_player_handler_context_t *context)
555 {
556 	return 0;
557 }
558 
project_player_handler_MANA(project_player_handler_context_t * context)559 static int project_player_handler_MANA(project_player_handler_context_t *context)
560 {
561 	return 0;
562 }
563 
project_player_handler_HOLY_ORB(project_player_handler_context_t * context)564 static int project_player_handler_HOLY_ORB(project_player_handler_context_t *context)
565 {
566 	return 0;
567 }
568 
project_player_handler_ARROW(project_player_handler_context_t * context)569 static int project_player_handler_ARROW(project_player_handler_context_t *context)
570 {
571 	return 0;
572 }
573 
project_player_handler_LIGHT_WEAK(project_player_handler_context_t * context)574 static int project_player_handler_LIGHT_WEAK(project_player_handler_context_t *context)
575 {
576 	return 0;
577 }
578 
project_player_handler_DARK_WEAK(project_player_handler_context_t * context)579 static int project_player_handler_DARK_WEAK(project_player_handler_context_t *context)
580 {
581 	if (player_resists(player, ELEM_DARK)) {
582 		if (!player_has(player, PF_UNLIGHT)) {
583 			msg("You resist the effect!");
584 		}
585 		return 0;
586 	}
587 
588 	(void)player_inc_timed(player, TMD_BLIND, 3 + randint1(5), true, true);
589 	return 0;
590 }
591 
project_player_handler_KILL_WALL(project_player_handler_context_t * context)592 static int project_player_handler_KILL_WALL(project_player_handler_context_t *context)
593 {
594 	return 0;
595 }
596 
project_player_handler_KILL_DOOR(project_player_handler_context_t * context)597 static int project_player_handler_KILL_DOOR(project_player_handler_context_t *context)
598 {
599 	return 0;
600 }
601 
project_player_handler_KILL_TRAP(project_player_handler_context_t * context)602 static int project_player_handler_KILL_TRAP(project_player_handler_context_t *context)
603 {
604 	return 0;
605 }
606 
project_player_handler_MAKE_DOOR(project_player_handler_context_t * context)607 static int project_player_handler_MAKE_DOOR(project_player_handler_context_t *context)
608 {
609 	return 0;
610 }
611 
project_player_handler_MAKE_TRAP(project_player_handler_context_t * context)612 static int project_player_handler_MAKE_TRAP(project_player_handler_context_t *context)
613 {
614 	return 0;
615 }
616 
project_player_handler_AWAY_UNDEAD(project_player_handler_context_t * context)617 static int project_player_handler_AWAY_UNDEAD(project_player_handler_context_t *context)
618 {
619 	return 0;
620 }
621 
project_player_handler_AWAY_EVIL(project_player_handler_context_t * context)622 static int project_player_handler_AWAY_EVIL(project_player_handler_context_t *context)
623 {
624 	return 0;
625 }
626 
project_player_handler_AWAY_SPIRIT(project_player_handler_context_t * context)627 static int project_player_handler_AWAY_SPIRIT(project_player_handler_context_t *context)
628 {
629 	return 0;
630 }
631 
project_player_handler_AWAY_ALL(project_player_handler_context_t * context)632 static int project_player_handler_AWAY_ALL(project_player_handler_context_t *context)
633 {
634 	return 0;
635 }
636 
project_player_handler_TURN_UNDEAD(project_player_handler_context_t * context)637 static int project_player_handler_TURN_UNDEAD(project_player_handler_context_t *context)
638 {
639 	return 0;
640 }
641 
project_player_handler_TURN_EVIL(project_player_handler_context_t * context)642 static int project_player_handler_TURN_EVIL(project_player_handler_context_t *context)
643 {
644 	return 0;
645 }
646 
project_player_handler_TURN_LIVING(project_player_handler_context_t * context)647 static int project_player_handler_TURN_LIVING(project_player_handler_context_t *context)
648 {
649 	return 0;
650 }
651 
project_player_handler_TURN_ALL(project_player_handler_context_t * context)652 static int project_player_handler_TURN_ALL(project_player_handler_context_t *context)
653 {
654 	return 0;
655 }
656 
project_player_handler_DISP_UNDEAD(project_player_handler_context_t * context)657 static int project_player_handler_DISP_UNDEAD(project_player_handler_context_t *context)
658 {
659 	return 0;
660 }
661 
project_player_handler_DISP_EVIL(project_player_handler_context_t * context)662 static int project_player_handler_DISP_EVIL(project_player_handler_context_t *context)
663 {
664 	return 0;
665 }
666 
project_player_handler_DISP_ALL(project_player_handler_context_t * context)667 static int project_player_handler_DISP_ALL(project_player_handler_context_t *context)
668 {
669 	return 0;
670 }
671 
project_player_handler_SLEEP_UNDEAD(project_player_handler_context_t * context)672 static int project_player_handler_SLEEP_UNDEAD(project_player_handler_context_t *context)
673 {
674 	return 0;
675 }
676 
project_player_handler_SLEEP_EVIL(project_player_handler_context_t * context)677 static int project_player_handler_SLEEP_EVIL(project_player_handler_context_t *context)
678 {
679 	return 0;
680 }
681 
project_player_handler_SLEEP_ALL(project_player_handler_context_t * context)682 static int project_player_handler_SLEEP_ALL(project_player_handler_context_t *context)
683 {
684 	return 0;
685 }
686 
project_player_handler_MON_CLONE(project_player_handler_context_t * context)687 static int project_player_handler_MON_CLONE(project_player_handler_context_t *context)
688 {
689 	return 0;
690 }
691 
project_player_handler_MON_POLY(project_player_handler_context_t * context)692 static int project_player_handler_MON_POLY(project_player_handler_context_t *context)
693 {
694 	return 0;
695 }
696 
project_player_handler_MON_HEAL(project_player_handler_context_t * context)697 static int project_player_handler_MON_HEAL(project_player_handler_context_t *context)
698 {
699 	return 0;
700 }
701 
project_player_handler_MON_SPEED(project_player_handler_context_t * context)702 static int project_player_handler_MON_SPEED(project_player_handler_context_t *context)
703 {
704 	return 0;
705 }
706 
project_player_handler_MON_SLOW(project_player_handler_context_t * context)707 static int project_player_handler_MON_SLOW(project_player_handler_context_t *context)
708 {
709 	return 0;
710 }
711 
project_player_handler_MON_CONF(project_player_handler_context_t * context)712 static int project_player_handler_MON_CONF(project_player_handler_context_t *context)
713 {
714 	return 0;
715 }
716 
project_player_handler_MON_HOLD(project_player_handler_context_t * context)717 static int project_player_handler_MON_HOLD(project_player_handler_context_t *context)
718 {
719 	return 0;
720 }
721 
project_player_handler_MON_STUN(project_player_handler_context_t * context)722 static int project_player_handler_MON_STUN(project_player_handler_context_t *context)
723 {
724 	return 0;
725 }
726 
project_player_handler_MON_DRAIN(project_player_handler_context_t * context)727 static int project_player_handler_MON_DRAIN(project_player_handler_context_t *context)
728 {
729 	return 0;
730 }
731 
project_player_handler_MON_CRUSH(project_player_handler_context_t * context)732 static int project_player_handler_MON_CRUSH(project_player_handler_context_t *context)
733 {
734 	return 0;
735 }
736 
737 static const project_player_handler_f player_handlers[] = {
738 	#define ELEM(a) project_player_handler_##a,
739 	#include "list-elements.h"
740 	#undef ELEM
741 	#define PROJ(a) project_player_handler_##a,
742 	#include "list-projections.h"
743 	#undef PROJ
744 	NULL
745 };
746 
747 /**
748  * Called from project() to affect the player
749  *
750  * Called for projections with the PROJECT_PLAY flag set, which includes
751  * bolt, beam, ball and breath effects.
752  *
753  * \param src is the origin of the effect
754  * \param r is the distance from the centre of the effect
755  * \param y the coordinates of the grid being handled
756  * \param x the coordinates of the grid being handled
757  * \param dam is the "damage" from the effect at distance r from the centre
758  * \param typ is the projection (PROJ_) type
759  * \return whether the effects were obvious
760  *
761  * If "r" is non-zero, then the blast was centered elsewhere; the damage
762  * is reduced in project() before being passed in here.  This can happen if a
763  * monster breathes at the player and hits a wall instead.
764  *
765  * We assume the player is aware of some effect, and always return "true".
766  */
project_p(struct source origin,int r,struct loc grid,int dam,int typ,int power,bool self)767 bool project_p(struct source origin, int r, struct loc grid, int dam, int typ,
768 			   int power, bool self)
769 {
770 	bool blind = (player->timed[TMD_BLIND] ? true : false);
771 	bool seen = !blind;
772 	bool obvious = true;
773 
774 	/* Monster or trap name (for damage) */
775 	char killer[80];
776 
777 	project_player_handler_f player_handler = player_handlers[typ];
778 	project_player_handler_context_t context = {
779 		origin,
780 		r,
781 		grid,
782 		dam,
783 		typ,
784 		power,
785 		obvious
786 	};
787 	int res_level = typ < ELEM_MAX ? player->state.el_info[typ].res_level : 0;
788 
789 	/* Decoy has been hit */
790 	if (square_isdecoyed(cave, grid) && context.dam) {
791 		square_destroy_decoy(cave, grid);
792 	}
793 
794 	/* No player here */
795 	if (!square_isplayer(cave, grid)) {
796 		return false;
797 	}
798 
799 	switch (origin.what) {
800 		case SRC_PLAYER: {
801 			/* Don't affect projector unless explicitly allowed */
802 			if (!self) return false;
803 
804 			break;
805 		}
806 
807 		case SRC_MONSTER: {
808 			struct monster *mon = cave_monster(cave, origin.which.monster);
809 
810 			/* Check it is visible */
811 			if (!monster_is_visible(mon))
812 				seen = false;
813 
814 			/* Get the monster's real name */
815 			monster_desc(killer, sizeof(killer), mon, MDESC_DIED_FROM);
816 
817 			/* Monster sees what is going on */
818 			update_smart_learn(mon, player, 0, 0, typ);
819 
820 			break;
821 		}
822 
823 		case SRC_TRAP: {
824 			struct trap *trap = origin.which.trap;
825 
826 			/* Get the trap name */
827 			strnfmt(killer, sizeof(killer), "a %s", trap->kind->desc);
828 
829 			break;
830 		}
831 
832 		case SRC_OBJECT: {
833 			struct object *obj = origin.which.object;
834 			object_desc(killer, sizeof(killer), obj, ODESC_PREFIX | ODESC_BASE);
835 			break;
836 		}
837 
838 		case SRC_CHEST_TRAP: {
839 			struct chest_trap *trap = origin.which.chest_trap;
840 
841 			/* Get the trap name */
842 			strnfmt(killer, sizeof(killer), "%s", trap->msg_death);
843 
844 			break;
845 		}
846 
847 		case SRC_NONE: {
848 			/* Assume the caller has set the killer variable */
849 			break;
850 		}
851 	}
852 
853 	/* Let player know what is going on */
854 	if (!seen) {
855 		msg("You are hit by %s!", projections[typ].blind_desc);
856 	}
857 
858 	/* Adjust damage for resistance, immunity or vulnerability, and apply it */
859 	context.dam = adjust_dam(player,
860 							 typ,
861 							 context.dam,
862 							 RANDOMISE,
863 							 res_level,
864 							 true);
865 	if (context.dam) {
866 		/* Self-inflicted damage is scaled down */
867 		if (self) {
868 			context.dam /= 10;
869 		}
870 		take_hit(player, context.dam, killer);
871 	}
872 
873 	/* Handle side effects, possibly including extra damage */
874 	if (player_handler != NULL && player->is_dead == false) {
875 		int xtra = player_handler(&context);
876 		if (xtra) take_hit(player, xtra, killer);
877 	}
878 
879 	/* Disturb */
880 	disturb(player);
881 
882 	/* Return "Anything seen?" */
883 	return context.obvious;
884 }
885