1 /**
2  *  \file project-mon.c
3  *  \brief projection effects on monsters
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 "generate.h"
23 #include "mon-desc.h"
24 #include "mon-lore.h"
25 #include "mon-make.h"
26 #include "mon-move.h"
27 #include "mon-msg.h"
28 #include "mon-predicate.h"
29 #include "mon-spell.h"
30 #include "mon-timed.h"
31 #include "mon-util.h"
32 #include "player-calcs.h"
33 #include "project.h"
34 #include "source.h"
35 
36 
37 /**
38  * Helper function -- return a "nearby" race for polymorphing
39  *
40  * Note that this function is one of the more "dangerous" ones...
41  */
poly_race(struct monster_race * race)42 static struct monster_race *poly_race(struct monster_race *race)
43 {
44 	int i, minlvl, maxlvl, goal;
45 
46 	assert(race && race->name);
47 
48 	/* Uniques never polymorph */
49 	if (rf_has(race->flags, RF_UNIQUE)) return race;
50 
51 	/* Allowable range of "levels" for resulting monster */
52 	goal = (player->depth + race->level) / 2 + 5;
53 	minlvl = MIN(race->level - 10, (race->level * 3) / 4);
54 	maxlvl = MAX(race->level + 10, (race->level * 5) / 4);
55 
56 	/* Small chance to allow something really strong */
57 	if (one_in_(100)) maxlvl = 100;
58 
59 	/* Try to pick a new, non-unique race within our level range */
60 	for (i = 0; i < 1000; i++) {
61 		struct monster_race *new_race = get_mon_num(goal);
62 
63 		if (!new_race || new_race == race) continue;
64 		if (rf_has(new_race->flags, RF_UNIQUE)) continue;
65 		if (new_race->level < minlvl || new_race->level > maxlvl) continue;
66 
67 		/* Avoid force-depth monsters, since it might cause a crash in project_m() */
68 		if (rf_has(new_race->flags, RF_FORCE_DEPTH) && player->depth < new_race->level) continue;
69 
70 		return new_race;
71 	}
72 
73 	/* If we get here, we weren't able to find a new race. */
74 	return race;
75 }
76 
77 /**
78  * Thrust the player or a monster away from the source of a projection.
79  *
80  * Monsters and players can be pushed past monsters or players weaker than
81  * they are.
82  */
thrust_away(struct loc centre,struct loc target,int grids_away)83 void thrust_away(struct loc centre, struct loc target, int grids_away)
84 {
85 	struct loc grid, next;
86 	int i, d, first_d;
87 	int angle;
88 
89 	/* Determine where target is in relation to caster, extend. */
90 	grid = loc_sum(loc_diff(target, centre), loc(20, 20));
91 
92 	/* Find the angle (/2) of the line from caster to target. */
93 	angle = get_angle_to_grid[grid.y][grid.x];
94 
95 	/* Start at the target grid. */
96 	grid = target;
97 
98 	/* Up to the number of grids requested, force the target away from the
99 	 * source of the projection, until it hits something it can't travel
100 	 * around. */
101 	for (i = 0; i < grids_away; i++) {
102 		/* Randomize initial direction. */
103 		first_d = randint0(8);
104 
105 		/* Look around (two possibilities for most angles). */
106 		for (d = first_d; d < 8 + first_d; d++) {
107 			/* Reject angles more than 44 degrees from desired direction. */
108 			if (d % 8 == 0) {	/* 135 */
109 				if ((angle > 157) || (angle < 114))
110 					continue;
111 			}
112 			if (d % 8 == 1) {	/* 45 */
113 				if ((angle > 66) || (angle < 23))
114 					continue;
115 			}
116 			if (d % 8 == 2) {	/* 0 */
117 				if ((angle > 21) && (angle < 159))
118 					continue;
119 			}
120 			if (d % 8 == 3) {	/* 90 */
121 				if ((angle > 112) || (angle < 68))
122 					continue;
123 			}
124 			if (d % 8 == 4) {	/* 158 */
125 				if ((angle > 179) || (angle < 136))
126 					continue;
127 			}
128 			if (d % 8 == 5) {	/* 113 */
129 				if ((angle > 134) || (angle < 91))
130 					continue;
131 			}
132 			if (d % 8 == 6) {	/* 22 */
133 				if ((angle > 44) || (angle < 1))
134 					continue;
135 			}
136 			if (d % 8 == 7) {	/* 67 */
137 				if ((angle > 89) || (angle < 46))
138 					continue;
139 			}
140 
141 			/* Extract adjacent location */
142 			next = loc_sum(grid, ddgrid_ddd[d % 8]);
143 
144 			/* There's someone there, try to switch places. */
145 			if (square(cave, next)->mon != 0) {
146 				/* A monster is trying to pass. */
147 				if (square(cave, grid)->mon > 0) {
148 					struct monster *mon = square_monster(cave, grid);
149 					if (square(cave, next)->mon > 0) {
150 						struct monster *mon1 = square_monster(cave, next);
151 
152 						/* Monsters cannot pass by stronger monsters. */
153 						if (mon1->race->mexp > mon->race->mexp)
154 							continue;
155 					} else {
156 						/* Monsters cannot pass by stronger characters. */
157 						if (player->lev * 2 > mon->race->level)
158 							continue;
159 					}
160 				}
161 
162 				/* The player is trying to pass. */
163 				if (square(cave, grid)->mon < 0) {
164 					if (square(cave, next)->mon > 0) {
165 						struct monster *mon1 = square_monster(cave, next);
166 
167 						/* Players cannot pass by stronger monsters. */
168 						if (mon1->race->level > player->lev * 2)
169 							continue;
170 					}
171 				}
172 			}
173 
174 			/* Check for obstruction. */
175 			if (!square_isprojectable(cave, next)) {
176 				/* Some features allow entrance, but not exit. */
177 				if (square_ispassable(cave, next)) {
178 					/* Travel down the path. */
179 					monster_swap(grid, next);
180 
181 					/* Jump to new location. */
182 					grid = next;
183 
184 					/* We can't travel any more. */
185 					i = grids_away;
186 
187 					/* Stop looking. */
188 					break;
189 				}
190 
191 				/* If there are walls everywhere, stop here. */
192 				else if (d == (8 + first_d - 1)) {
193 					/* Message for player. */
194 					if (square(cave, grid)->mon < 0)
195 						msg("You come to rest next to a wall.");
196 					i = grids_away;
197 				}
198 			} else {
199 				/* Travel down the path. */
200 				monster_swap(grid, next);
201 
202 				/* Jump to new location. */
203 				grid = next;
204 
205 				/* Stop looking at previous location. */
206 				break;
207 			}
208 		}
209 	}
210 
211 	/* Some special messages or effects for player or monster. */
212 	if (square_isfiery(cave, grid)) {
213 		if (square(cave, grid)->mon < 0) {
214 			msg("You are thrown into molten lava!");
215 		} else if (square(cave, grid)->mon > 0) {
216 			struct monster *mon = square_monster(cave, grid);
217 			monster_take_terrain_damage(mon);
218 		}
219 	}
220 
221 	/* Clear the projection mark. */
222 	sqinfo_off(square(cave, grid)->info, SQUARE_PROJECT);
223 }
224 
225 /**
226  * ------------------------------------------------------------------------
227  * Monster handlers
228  * ------------------------------------------------------------------------ */
229 
230 typedef struct project_monster_handler_context_s {
231 	const struct source origin;
232 	const int r;
233 	const struct loc grid;
234 	int dam;
235 	const int type;
236 	bool seen; /* Ideally, this would be const, but we can't with C89 initialization. */
237 	const bool id;
238 	struct monster *mon;
239 	struct monster_lore *lore;
240 	bool charm;
241 	bool obvious;
242 	bool skipped;
243 	u16b flag;
244 	int do_poly;
245 	int teleport_distance;
246 	enum mon_messages hurt_msg;
247 	enum mon_messages die_msg;
248 	int mon_timed[MON_TMD_MAX];
249 } project_monster_handler_context_t;
250 typedef void (*project_monster_handler_f)(project_monster_handler_context_t *);
251 
adjust_radius(project_monster_handler_context_t * context,int amount)252 static int adjust_radius(project_monster_handler_context_t *context, int amount)
253 {
254 	return (amount + context->r) / (context->r + 1);
255 }
256 
257 /**
258  * Resist an attack if the monster has the given elemental flag.
259  *
260  * If the effect is seen, we learn that the monster has a given flag.
261  * Resistance is divided by the factor.
262  *
263  * \param context is the project_m context.
264  * \param flag is the RF_ flag that the monster must have.
265  * \param factor is the divisor for the base damage.
266  */
project_monster_resist_element(project_monster_handler_context_t * context,int flag,int factor)267 static void project_monster_resist_element(project_monster_handler_context_t *context, int flag, int factor)
268 {
269 	if (context->seen) rf_on(context->lore->flags, flag);
270 	if (rf_has(context->mon->race->flags, flag)) {
271 		context->hurt_msg = MON_MSG_RESIST_A_LOT;
272 		context->dam /= factor;
273 	}
274 }
275 
276 /**
277  * Resist an attack if the monster has the given flag.
278  *
279  * If the effect is seen, we learn that the monster has a given flag.
280  * Resistance is multiplied by the factor and reduced by a small random amount
281  * (if reduce is set).
282  *
283  * \param context is the project_m context.
284  * \param flag is the RF_ flag that the monster must have.
285  * \param factor is the multiplier for the base damage.
286  * \param reduce should be true if the base damage * factor should be reduced,
287  * false if the base damage should be increased.
288  * \param msg is the message that should be displayed when the monster is hurt.
289  */
project_monster_resist_other(project_monster_handler_context_t * context,int flag,int factor,bool reduce,enum mon_messages msg)290 static void project_monster_resist_other(project_monster_handler_context_t *context, int flag, int factor, bool reduce, enum mon_messages msg)
291 {
292 	if (context->seen) rf_on(context->lore->flags, flag);
293 	if (rf_has(context->mon->race->flags, flag)) {
294 		context->hurt_msg = msg;
295 		context->dam *= factor;
296 
297 		if (reduce)
298 			context->dam /= randint1(6) + 6;
299 	}
300 }
301 
302 /**
303  * Resist an attack if the monster has the given flag or hurt the monster
304  * more if it has another flag.
305  *
306  * If the effect is seen, we learn the status of both flags. Resistance is
307  * divided by imm_factor while hurt is multiplied by hurt_factor.
308  *
309  * \param context is the project_m context.
310  * \param hurt_flag is the RF_ flag that the monster must have to use the hurt factor.
311  * \param imm_flag is the RF_ flag that the monster must have to use the resistance factor.
312  * \param hurt_factor is the hurt multiplier for the base damage.
313  * \param imm_factor is the resistance divisor for the base damage.
314  * \param hurt_msg is the message that should be displayed when the monster is hurt.
315  * \param die_msg is the message that should be displayed when the monster dies.
316  */
project_monster_hurt_immune(project_monster_handler_context_t * context,int hurt_flag,int imm_flag,int hurt_factor,int imm_factor,enum mon_messages hurt_msg,enum mon_messages die_msg)317 static void project_monster_hurt_immune(project_monster_handler_context_t *context, int hurt_flag, int imm_flag, int hurt_factor, int imm_factor, enum mon_messages hurt_msg, enum mon_messages die_msg)
318 {
319 	if (context->seen) {
320 		rf_on(context->lore->flags, imm_flag);
321 		rf_on(context->lore->flags, hurt_flag);
322 	}
323 
324 	if (rf_has(context->mon->race->flags, imm_flag)) {
325 		context->hurt_msg = MON_MSG_RESIST_A_LOT;
326 		context->dam /= imm_factor;
327 	}
328 	else if (rf_has(context->mon->race->flags, hurt_flag)) {
329 		context->hurt_msg = hurt_msg;
330 		context->die_msg = die_msg;
331 		context->dam *= hurt_factor;
332 	}
333 }
334 
335 /**
336  * Hurt the monster if it has a given flag or do no damage.
337  *
338  * If the effect is seen, we learn the status the flag. There is no damage
339  * multiplier.
340  *
341  * \param context is the project_m context.
342  * \param flag is the RF_ flag that the monster must have.
343  * \param hurt_msg is the message that should be displayed when the monster is hurt.
344  * \param die_msg is the message that should be displayed when the monster dies.
345  */
project_monster_hurt_only(project_monster_handler_context_t * context,int flag,enum mon_messages hurt_msg,enum mon_messages die_msg)346 static void project_monster_hurt_only(project_monster_handler_context_t *context, int flag, enum mon_messages hurt_msg, enum mon_messages die_msg)
347 {
348 	if (context->seen) rf_on(context->lore->flags, flag);
349 
350 	if (rf_has(context->mon->race->flags, flag)) {
351 		context->hurt_msg = hurt_msg;
352 		context->die_msg = die_msg;
353 	}
354 	else {
355 		context->dam = 0;
356 	}
357 }
358 
359 /**
360  * Resist an attack if the monster has the given spell flag.
361  *
362  * If the effect is seen, we learn that the monster has that spell (useful
363  * for breaths). Resistance is multiplied by the factor and reduced by
364  * a small random amount.
365  *
366  * \param context is the project_m context.
367  * \param flag is the RSF_ flag that the monster must have.
368  * \param factor is the multiplier for the base damage.
369  */
project_monster_breath(project_monster_handler_context_t * context,int flag,int factor)370 static void project_monster_breath(project_monster_handler_context_t *context, int flag, int factor)
371 {
372 	if (rsf_has(context->mon->race->spell_flags, flag)) {
373 		/* Learn about breathers through resistance */
374 		if (context->seen) rsf_on(context->lore->spell_flags, flag);
375 
376 		context->hurt_msg = MON_MSG_RESIST;
377 		context->dam *= factor;
378 		context->dam /= randint1(6) + 6;
379 	}
380 }
381 
382 /**
383  * Teleport away a monster that has a given flag.
384  *
385  * If the monster matches, it is teleported and the effect is obvious (if seen).
386  * The player learns monster lore on whether or not the monster matches the
387  * given flag if the effect is seen. Damage is not incurred by the monster.
388  *
389  * \param context is the project_m context.
390  * \param flag is the RF_ flag that the monster must have.
391  */
project_monster_teleport_away(project_monster_handler_context_t * context,int flag)392 static void project_monster_teleport_away(project_monster_handler_context_t *context, int flag)
393 {
394 	if (context->seen) rf_on(context->lore->flags, flag);
395 
396 	if (rf_has(context->mon->race->flags, flag)) {
397 		context->teleport_distance = context->dam;
398 		context->hurt_msg = MON_MSG_DISAPPEAR;
399 		monster_wake(context->mon, false, 100);
400 	} else {
401 		context->skipped = true;
402 	}
403 
404 	context->obvious = true;
405 	context->dam = 0;
406 }
407 
408 /**
409  * Scare a monster that has a given flag.
410  *
411  * If the monster matches, fear is applied and the effect is obvious (if seen).
412  * The player learns monster lore on whether or not the monster matches the
413  * given flag if the effect is seen. Damage is not incurred by the monster.
414  *
415  * \param context is the project_m context.
416  * \param flag is the RF_ flag that the monster must have.
417  */
project_monster_scare(project_monster_handler_context_t * context,int flag)418 static void project_monster_scare(project_monster_handler_context_t *context, int flag)
419 {
420     if (context->seen) rf_on(context->lore->flags, flag);
421 
422 	if (rf_has(context->mon->race->flags, flag)) {
423         context->mon_timed[MON_TMD_FEAR] = adjust_radius(context, context->dam);
424 		monster_wake(context->mon, false, 100);
425 	} else {
426 		context->skipped = true;
427 	}
428 
429 	context->obvious = true;
430 	context->dam = 0;
431 }
432 
433 /**
434  * Dispel a monster that has a given flag.
435  *
436  * If the monster matches, damage is applied and the effect is obvious
437  * (if seen). Otherwise, no damage is applied and the effect is not obvious.
438  * The player learns monster lore on whether or not the monster matches the
439  * given flag if the effect is seen.
440  *
441  * \param context is the project_m context.
442  * \param flag is the RF_ flag that the monster must have.
443  */
project_monster_dispel(project_monster_handler_context_t * context,int flag)444 static void project_monster_dispel(project_monster_handler_context_t *context, int flag)
445 {
446 	if (context->seen) rf_on(context->lore->flags, flag);
447 
448 	if (rf_has(context->mon->race->flags, flag)) {
449 		context->hurt_msg = MON_MSG_SHUDDER;
450 		context->die_msg = MON_MSG_DISSOLVE;
451 	} else {
452 		context->skipped = true;
453 		context->dam = 0;
454 	}
455 
456 	context->obvious = true;
457 }
458 
459 /**
460  * Sleep a monster that has a given flag.
461  *
462  * If the monster matches, an attempt is made to put the monster to sleep
463  * and the effect is obvious (if seen).
464  * Otherwise, no attempt is made and the effect is not obvious.
465  * The player learns monster lore on whether or not the monster matches the
466  * given flag if the effect is seen.
467  *
468  * \param context is the project_m context.
469  * \param flag is the RF_ flag that the monster must have.
470  */
project_monster_sleep(project_monster_handler_context_t * context,int flag)471 static void project_monster_sleep(project_monster_handler_context_t *context, int flag)
472 {
473 	if (context->seen && flag) rf_on(context->lore->flags, flag);
474 
475 	if (flag && !rf_has(context->mon->race->flags, flag)) {
476 		context->skipped = true;
477 		context->dam = 0;
478 	}
479 
480 	if (context->charm && rf_has(context->mon->race->flags, RF_ANIMAL)) {
481 		context->dam += context->dam / 2;
482 	}
483 	context->mon_timed[MON_TMD_SLEEP] = context->dam;
484 	context->dam = 0;
485 
486 	context->obvious = true;
487 }
488 
489 /* Acid */
project_monster_handler_ACID(project_monster_handler_context_t * context)490 static void project_monster_handler_ACID(project_monster_handler_context_t *context)
491 {
492 	project_monster_resist_element(context, RF_IM_ACID, 9);
493 }
494 
495 /* Electricity */
project_monster_handler_ELEC(project_monster_handler_context_t * context)496 static void project_monster_handler_ELEC(project_monster_handler_context_t *context)
497 {
498 	project_monster_resist_element(context, RF_IM_ELEC, 9);
499 }
500 
501 /* Fire damage */
project_monster_handler_FIRE(project_monster_handler_context_t * context)502 static void project_monster_handler_FIRE(project_monster_handler_context_t *context)
503 {
504 	project_monster_hurt_immune(context, RF_HURT_FIRE, RF_IM_FIRE, 2, 9, MON_MSG_CATCH_FIRE, MON_MSG_DISINTEGRATES);
505 }
506 
507 /* Cold */
project_monster_handler_COLD(project_monster_handler_context_t * context)508 static void project_monster_handler_COLD(project_monster_handler_context_t *context)
509 {
510 	project_monster_hurt_immune(context, RF_HURT_COLD, RF_IM_COLD, 2, 9, MON_MSG_BADLY_FROZEN, MON_MSG_FREEZE_SHATTER);
511 }
512 
513 /* Poison */
project_monster_handler_POIS(project_monster_handler_context_t * context)514 static void project_monster_handler_POIS(project_monster_handler_context_t *context)
515 {
516 	project_monster_resist_element(context, RF_IM_POIS, 9);
517 }
518 
519 /* Light -- opposite of Dark */
project_monster_handler_LIGHT(project_monster_handler_context_t * context)520 static void project_monster_handler_LIGHT(project_monster_handler_context_t *context)
521 {
522 	if (context->seen) rf_on(context->lore->flags, RF_HURT_LIGHT);
523 
524 	if (rsf_has(context->mon->race->spell_flags, RSF_BR_LIGHT)) {
525 		/* Learn about breathers through resistance */
526 		if (context->seen) rsf_on(context->lore->spell_flags, RSF_BR_LIGHT);
527 
528 		context->hurt_msg = MON_MSG_RESIST;
529 		context->dam *= 2;
530 		context->dam /= randint1(6) + 6;
531 	}
532 	else if (rf_has(context->mon->race->flags, RF_HURT_LIGHT)) {
533 		context->hurt_msg = MON_MSG_CRINGE_LIGHT;
534 		context->die_msg = MON_MSG_SHRIVEL_LIGHT;
535 		context->dam *= 2;
536 	}
537 }
538 
539 /* Dark -- opposite of Light */
project_monster_handler_DARK(project_monster_handler_context_t * context)540 static void project_monster_handler_DARK(project_monster_handler_context_t *context)
541 {
542 	project_monster_breath(context, RSF_BR_DARK, 2);
543 }
544 
545 /* Sound -- Sound breathers resist */
project_monster_handler_SOUND(project_monster_handler_context_t * context)546 static void project_monster_handler_SOUND(project_monster_handler_context_t *context)
547 {
548 	if (one_in_(3)) {
549 		context->mon_timed[MON_TMD_STUN] = adjust_radius(context, 5 + randint1(10));
550 	}
551 
552 	project_monster_breath(context, RSF_BR_SOUN, 2);
553 }
554 
555 /* Shards -- Shard breathers resist */
project_monster_handler_SHARD(project_monster_handler_context_t * context)556 static void project_monster_handler_SHARD(project_monster_handler_context_t *context)
557 {
558 	project_monster_breath(context, RSF_BR_SHAR, 3);
559 }
560 
561 /* Nexus */
project_monster_handler_NEXUS(project_monster_handler_context_t * context)562 static void project_monster_handler_NEXUS(project_monster_handler_context_t *context)
563 {
564 	project_monster_resist_other(context, RF_IM_NEXUS, 3, true, MON_MSG_RESIST);
565 
566 	if (one_in_(3)) {
567 		/* Blink */
568 		context->teleport_distance = 10;
569 	} else if (one_in_(4)) {
570 		/* Teleport */
571 		context->teleport_distance = 50;
572 	}
573 }
574 
575 /* Nether -- see above */
project_monster_handler_NETHER(project_monster_handler_context_t * context)576 static void project_monster_handler_NETHER(project_monster_handler_context_t *context)
577 {
578 	/* Update the lore */
579 	if (context->seen) {
580 		/* Acquire knowledge of undead type and nether resistance */
581 		rf_on(context->lore->flags, RF_UNDEAD);
582 		rf_on(context->lore->flags, RF_IM_NETHER);
583 
584 		/* If it isn't undead, acquire extra knowledge */
585 		if (!rf_has(context->mon->race->flags, RF_UNDEAD)) {
586 			/* Learn this creature breathes nether if true */
587 			if (rsf_has(context->mon->race->spell_flags, RSF_BR_NETH)) {
588 				rsf_on(context->lore->spell_flags, RSF_BR_NETH);
589 			}
590 
591 			/* Otherwise learn about evil type */
592 			else {
593 				rf_on(context->lore->flags, RF_EVIL);
594 			}
595 		}
596 	}
597 
598 	if (rf_has(context->mon->race->flags, RF_UNDEAD)) {
599 		context->hurt_msg = MON_MSG_IMMUNE;
600 		context->dam = 0;
601 	}
602 	else if (rf_has(context->mon->race->flags, RF_IM_NETHER)) {
603 		context->hurt_msg = MON_MSG_RESIST;
604 		context->dam *= 3;
605 		context->dam /= (randint1(6)+6);
606 	}
607 	else if (rf_has(context->mon->race->flags, RF_EVIL)) {
608 		context->dam /= 2;
609 		context->hurt_msg = MON_MSG_RESIST_SOMEWHAT;
610 	}
611 }
612 
613 /* Chaos -- Chaos breathers resist */
project_monster_handler_CHAOS(project_monster_handler_context_t * context)614 static void project_monster_handler_CHAOS(project_monster_handler_context_t *context)
615 {
616 	/* Prevent polymorph on chaos breathers. */
617 	if (rsf_has(context->mon->race->spell_flags, RSF_BR_CHAO))
618 		context->do_poly = 0;
619 	else
620 		context->do_poly = 1;
621 
622 	/* Hide resistance message (as assigned in project_monster_breath()). */
623 	context->mon_timed[MON_TMD_CONF] = adjust_radius(context, 10 + randint1(10));
624 	project_monster_breath(context, RSF_BR_CHAO, 3);
625 	context->hurt_msg = MON_MSG_NONE;
626 }
627 
628 /* Disenchantment */
project_monster_handler_DISEN(project_monster_handler_context_t * context)629 static void project_monster_handler_DISEN(project_monster_handler_context_t *context)
630 {
631 	project_monster_resist_other(context, RF_IM_DISEN, 3, true, MON_MSG_RESIST);
632 
633 	/* Affect monsters which don't resist, and have non-innate spells */
634 	if (!rf_has(context->mon->race->flags, RF_IM_DISEN) &&
635 		monster_has_non_innate_spells(context->mon)) {
636 		context->mon_timed[MON_TMD_DISEN] = adjust_radius(context,
637 														  5 + randint1(10));
638 	}
639 }
640 
641 /* Water damage */
project_monster_handler_WATER(project_monster_handler_context_t * context)642 static void project_monster_handler_WATER(project_monster_handler_context_t *context)
643 {
644 	/* Zero out the damage because this is an immunity flag. */
645 	project_monster_resist_other(context, RF_IM_WATER, 0, false, MON_MSG_IMMUNE);
646 }
647 
648 /* Ice -- Cold + Stun */
project_monster_handler_ICE(project_monster_handler_context_t * context)649 static void project_monster_handler_ICE(project_monster_handler_context_t *context)
650 {
651 	if (one_in_(3)) {
652 		context->mon_timed[MON_TMD_STUN] = adjust_radius(context, 5 + randint1(10));
653 	}
654 
655 	project_monster_hurt_immune(context, RF_HURT_COLD, RF_IM_COLD, 2, 9, MON_MSG_BADLY_FROZEN, MON_MSG_FREEZE_SHATTER);
656 }
657 
658 /* Gravity -- breathers resist */
project_monster_handler_GRAVITY(project_monster_handler_context_t * context)659 static void project_monster_handler_GRAVITY(project_monster_handler_context_t *context)
660 {
661 	/* Higher level monsters can resist the teleportation better */
662 	if (randint1(127) > context->mon->race->level)
663 		context->teleport_distance = 10;
664 
665 	/* Prevent displacement on gravity breathers. */
666 	if (rsf_has(context->mon->race->spell_flags, RSF_BR_GRAV))
667 		context->teleport_distance = 0;
668 
669 	project_monster_breath(context, RSF_BR_GRAV, 3);
670 }
671 
672 /* Inertia -- breathers resist */
project_monster_handler_INERTIA(project_monster_handler_context_t * context)673 static void project_monster_handler_INERTIA(project_monster_handler_context_t *context)
674 {
675 	project_monster_breath(context, RSF_BR_INER, 3);
676 }
677 
678 /* Force */
project_monster_handler_FORCE(project_monster_handler_context_t * context)679 static void project_monster_handler_FORCE(project_monster_handler_context_t *context)
680 {
681 	struct loc centre = origin_get_loc(context->origin);
682 
683 	if (one_in_(3)) {
684 		context->mon_timed[MON_TMD_STUN] = adjust_radius(context,
685 														 5 + randint1(10));
686 	}
687 
688 	project_monster_breath(context, RSF_BR_WALL, 3);
689 
690 	/* Prevent thrusting force breathers. */
691 	if (rsf_has(context->mon->race->spell_flags, RSF_BR_WALL))
692 		return;
693 
694 	/* Thrust monster away */
695 	thrust_away(centre, context->grid, 3 + context->dam / 20);
696 }
697 
698 /* Time -- breathers resist */
project_monster_handler_TIME(project_monster_handler_context_t * context)699 static void project_monster_handler_TIME(project_monster_handler_context_t *context)
700 {
701 	project_monster_breath(context, RSF_BR_TIME, 3);
702 }
703 
704 /* Plasma */
project_monster_handler_PLASMA(project_monster_handler_context_t * context)705 static void project_monster_handler_PLASMA(project_monster_handler_context_t *context)
706 {
707 	project_monster_resist_other(context, RF_IM_PLASMA, 3, true, MON_MSG_RESIST);
708 }
709 
project_monster_handler_METEOR(project_monster_handler_context_t * context)710 static void project_monster_handler_METEOR(project_monster_handler_context_t *context)
711 {
712 }
713 
project_monster_handler_MISSILE(project_monster_handler_context_t * context)714 static void project_monster_handler_MISSILE(project_monster_handler_context_t *context)
715 {
716 }
717 
project_monster_handler_MANA(project_monster_handler_context_t * context)718 static void project_monster_handler_MANA(project_monster_handler_context_t *context)
719 {
720 }
721 
722 /* Holy Orb -- hurts Evil */
project_monster_handler_HOLY_ORB(project_monster_handler_context_t * context)723 static void project_monster_handler_HOLY_ORB(project_monster_handler_context_t *context)
724 {
725 	project_monster_resist_other(context, RF_EVIL, 2, false, MON_MSG_HIT_HARD);
726 }
727 
project_monster_handler_ARROW(project_monster_handler_context_t * context)728 static void project_monster_handler_ARROW(project_monster_handler_context_t *context)
729 {
730 }
731 
732 /* Light, but only hurts susceptible creatures */
project_monster_handler_LIGHT_WEAK(project_monster_handler_context_t * context)733 static void project_monster_handler_LIGHT_WEAK(project_monster_handler_context_t *context)
734 {
735 	project_monster_hurt_only(context, RF_HURT_LIGHT, MON_MSG_CRINGE_LIGHT, MON_MSG_SHRIVEL_LIGHT);
736 }
737 
project_monster_handler_DARK_WEAK(project_monster_handler_context_t * context)738 static void project_monster_handler_DARK_WEAK(project_monster_handler_context_t *context)
739 {
740 	context->skipped = true;
741 	context->dam = 0;
742 }
743 
744 /* Stone to Mud */
project_monster_handler_KILL_WALL(project_monster_handler_context_t * context)745 static void project_monster_handler_KILL_WALL(project_monster_handler_context_t *context)
746 {
747 	project_monster_hurt_only(context, RF_HURT_ROCK, MON_MSG_LOSE_SKIN, MON_MSG_DISSOLVE);
748 }
749 
project_monster_handler_KILL_DOOR(project_monster_handler_context_t * context)750 static void project_monster_handler_KILL_DOOR(project_monster_handler_context_t *context)
751 {
752 	context->skipped = true;
753 	context->dam = 0;
754 }
755 
project_monster_handler_KILL_TRAP(project_monster_handler_context_t * context)756 static void project_monster_handler_KILL_TRAP(project_monster_handler_context_t *context)
757 {
758 	context->skipped = true;
759 	context->dam = 0;
760 }
761 
project_monster_handler_MAKE_DOOR(project_monster_handler_context_t * context)762 static void project_monster_handler_MAKE_DOOR(project_monster_handler_context_t *context)
763 {
764 	context->skipped = true;
765 	context->dam = 0;
766 }
767 
project_monster_handler_MAKE_TRAP(project_monster_handler_context_t * context)768 static void project_monster_handler_MAKE_TRAP(project_monster_handler_context_t *context)
769 {
770 	context->skipped = true;
771 	context->dam = 0;
772 }
773 
774 /* Teleport undead (Use "dam" as "power") */
project_monster_handler_AWAY_UNDEAD(project_monster_handler_context_t * context)775 static void project_monster_handler_AWAY_UNDEAD(project_monster_handler_context_t *context)
776 {
777 	project_monster_teleport_away(context, RF_UNDEAD);
778 }
779 
780 /* Teleport evil (Use "dam" as "power") */
project_monster_handler_AWAY_EVIL(project_monster_handler_context_t * context)781 static void project_monster_handler_AWAY_EVIL(project_monster_handler_context_t *context)
782 {
783 	project_monster_teleport_away(context, RF_EVIL);
784 }
785 
786 /* Teleport evil (Use "dam" as "power") */
project_monster_handler_AWAY_SPIRIT(project_monster_handler_context_t * context)787 static void project_monster_handler_AWAY_SPIRIT(project_monster_handler_context_t *context)
788 {
789 	project_monster_teleport_away(context, RF_SPIRIT);
790 }
791 
792 /* Teleport monster (Use "dam" as "power") */
project_monster_handler_AWAY_ALL(project_monster_handler_context_t * context)793 static void project_monster_handler_AWAY_ALL(project_monster_handler_context_t *context)
794 {
795 	/* Prepare to teleport */
796 	context->teleport_distance = context->dam;
797 
798 	/* No "real" damage */
799 	context->dam = 0;
800 	context->hurt_msg = MON_MSG_DISAPPEAR;
801 }
802 
803 /* Turn undead (Use "dam" as "power") */
project_monster_handler_TURN_UNDEAD(project_monster_handler_context_t * context)804 static void project_monster_handler_TURN_UNDEAD(project_monster_handler_context_t *context)
805 {
806 	project_monster_scare(context, RF_UNDEAD);
807 }
808 
809 /* Turn evil (Use "dam" as "power") */
project_monster_handler_TURN_EVIL(project_monster_handler_context_t * context)810 static void project_monster_handler_TURN_EVIL(project_monster_handler_context_t *context)
811 {
812 	project_monster_scare(context, RF_EVIL);
813 }
814 
815 /* Turn living (Use "dam" as "power") */
project_monster_handler_TURN_LIVING(project_monster_handler_context_t * context)816 static void project_monster_handler_TURN_LIVING(project_monster_handler_context_t *context)
817 {
818     if (context->seen) {
819 		rf_on(context->lore->flags, RF_NONLIVING);
820 		rf_on(context->lore->flags, RF_UNDEAD);
821 	}
822 
823 	if (monster_is_living(context->mon)) {
824         context->mon_timed[MON_TMD_FEAR] = adjust_radius(context, context->dam);
825 	} else {
826 		context->skipped = true;
827 	}
828 
829 	context->obvious = true;
830 	context->dam = 0;
831 }
832 
833 /* Turn monster (Use "dam" as "power") */
project_monster_handler_TURN_ALL(project_monster_handler_context_t * context)834 static void project_monster_handler_TURN_ALL(project_monster_handler_context_t *context)
835 {
836 	context->mon_timed[MON_TMD_FEAR] = context->dam;
837 	context->dam = 0;
838 }
839 
840 /* Dispel undead */
project_monster_handler_DISP_UNDEAD(project_monster_handler_context_t * context)841 static void project_monster_handler_DISP_UNDEAD(project_monster_handler_context_t *context)
842 {
843 	project_monster_dispel(context, RF_UNDEAD);
844 }
845 
846 /* Dispel evil */
project_monster_handler_DISP_EVIL(project_monster_handler_context_t * context)847 static void project_monster_handler_DISP_EVIL(project_monster_handler_context_t *context)
848 {
849 	project_monster_dispel(context, RF_EVIL);
850 }
851 
852 /* Dispel monster */
project_monster_handler_DISP_ALL(project_monster_handler_context_t * context)853 static void project_monster_handler_DISP_ALL(project_monster_handler_context_t *context)
854 {
855 	context->hurt_msg = MON_MSG_SHUDDER;
856 	context->die_msg = MON_MSG_DISSOLVE;
857 }
858 
859 /* Sleep (Use "dam" as "power") */
project_monster_handler_SLEEP_UNDEAD(project_monster_handler_context_t * context)860 static void project_monster_handler_SLEEP_UNDEAD(project_monster_handler_context_t *context)
861 {
862 	project_monster_sleep(context, RF_UNDEAD);
863 }
864 
865 /* Sleep (Use "dam" as "power") */
project_monster_handler_SLEEP_EVIL(project_monster_handler_context_t * context)866 static void project_monster_handler_SLEEP_EVIL(project_monster_handler_context_t *context)
867 {
868 	project_monster_sleep(context, RF_EVIL);
869 }
870 
871 /* Sleep (Use "dam" as "power") */
project_monster_handler_SLEEP_ALL(project_monster_handler_context_t * context)872 static void project_monster_handler_SLEEP_ALL(project_monster_handler_context_t *context)
873 {
874 	project_monster_sleep(context, RF_NONE);
875 }
876 
877 /* Clone monsters (Ignore "dam") */
project_monster_handler_MON_CLONE(project_monster_handler_context_t * context)878 static void project_monster_handler_MON_CLONE(project_monster_handler_context_t *context)
879 {
880 	/* Heal fully */
881 	context->mon->hp = context->mon->maxhp;
882 
883 	/* Speed up */
884 	mon_inc_timed(context->mon, MON_TMD_FAST, 50, MON_TMD_FLG_NOTIFY);
885 
886 	/* Attempt to clone. */
887 	if (multiply_monster(cave, context->mon))
888 		context->hurt_msg = MON_MSG_SPAWN;
889 
890 	/* No "real" damage */
891 	context->dam = 0;
892 
893 }
894 
895 /* Polymorph monster (Use "dam" as "power") */
project_monster_handler_MON_POLY(project_monster_handler_context_t * context)896 static void project_monster_handler_MON_POLY(project_monster_handler_context_t *context)
897 {
898 	if (context->charm && rf_has(context->mon->race->flags, RF_ANIMAL)) {
899 		context->dam += context->dam / 2;
900 	}
901 	/* Polymorph later */
902 	context->do_poly = context->dam;
903 
904 	/* No "real" damage */
905 	context->dam = 0;
906 }
907 
908 /* Heal Monster (use "dam" as amount of healing) */
project_monster_handler_MON_HEAL(project_monster_handler_context_t * context)909 static void project_monster_handler_MON_HEAL(project_monster_handler_context_t *context)
910 {
911 	/* Heal */
912 	context->mon->hp += context->dam;
913 
914 	/* No overflow */
915 	if (context->mon->hp > context->mon->maxhp)
916 		context->mon->hp = context->mon->maxhp;
917 
918 	/* Redraw (later) if needed */
919 	if (player->upkeep->health_who == context->mon)
920 		player->upkeep->redraw |= (PR_HEALTH);
921 
922 	/* Message */
923 	else context->hurt_msg = MON_MSG_HEALTHIER;
924 
925 	/* No "real" damage */
926 	context->dam = 0;
927 }
928 
929 /* Speed Monster (Ignore "dam") */
project_monster_handler_MON_SPEED(project_monster_handler_context_t * context)930 static void project_monster_handler_MON_SPEED(project_monster_handler_context_t *context)
931 {
932 	context->mon_timed[MON_TMD_FAST] = context->dam;
933 	context->dam = 0;
934 }
935 
936 /* Slow Monster (Use "dam" as "power") */
project_monster_handler_MON_SLOW(project_monster_handler_context_t * context)937 static void project_monster_handler_MON_SLOW(project_monster_handler_context_t *context)
938 {
939 	if (context->charm && rf_has(context->mon->race->flags, RF_ANIMAL)) {
940 		context->dam += context->dam / 2;
941 	}
942 	context->mon_timed[MON_TMD_SLOW] = context->dam;
943 	context->dam = 0;
944 }
945 
946 /* Confusion (Use "dam" as "power") */
project_monster_handler_MON_CONF(project_monster_handler_context_t * context)947 static void project_monster_handler_MON_CONF(project_monster_handler_context_t *context)
948 {
949 	if (context->charm && rf_has(context->mon->race->flags, RF_ANIMAL)) {
950 		context->dam += context->dam / 2;
951 	}
952 	context->mon_timed[MON_TMD_CONF] = context->dam;
953 	context->dam = 0;
954 }
955 
956 /* Hold (Use "dam" as "power") */
project_monster_handler_MON_HOLD(project_monster_handler_context_t * context)957 static void project_monster_handler_MON_HOLD(project_monster_handler_context_t *context)
958 {
959 	if (context->charm && rf_has(context->mon->race->flags, RF_ANIMAL)) {
960 		context->dam += context->dam / 2;
961 	}
962 	context->mon_timed[MON_TMD_HOLD] = context->dam;
963 	context->dam = 0;
964 }
965 
966 /* Stun (Use "dam" as "power") */
project_monster_handler_MON_STUN(project_monster_handler_context_t * context)967 static void project_monster_handler_MON_STUN(project_monster_handler_context_t *context)
968 {
969 	if (context->charm && rf_has(context->mon->race->flags, RF_ANIMAL)) {
970 		context->dam += context->dam / 2;
971 	}
972 	context->mon_timed[MON_TMD_STUN] = context->dam;
973 	context->dam = 0;
974 }
975 
976 /* Drain Life */
project_monster_handler_MON_DRAIN(project_monster_handler_context_t * context)977 static void project_monster_handler_MON_DRAIN(project_monster_handler_context_t *context)
978 {
979 	if (context->seen) context->obvious = true;
980 	if (context->seen) {
981 		rf_on(context->lore->flags, RF_UNDEAD);
982 	}
983 	if (monster_is_nonliving(context->mon)) {
984 		context->hurt_msg = MON_MSG_UNAFFECTED;
985 		context->obvious = false;
986 		context->dam = 0;
987 	}
988 }
989 
990 /* Crush */
project_monster_handler_MON_CRUSH(project_monster_handler_context_t * context)991 static void project_monster_handler_MON_CRUSH(project_monster_handler_context_t *context)
992 {
993 	if (context->seen) context->obvious = true;
994 	if (context->mon->hp >= context->dam) {
995 		context->hurt_msg = MON_MSG_UNAFFECTED;
996 		context->obvious = false;
997 		context->skipped = true;
998 		context->dam = 0;
999 	}
1000 }
1001 
1002 static const project_monster_handler_f monster_handlers[] = {
1003 	#define ELEM(a) project_monster_handler_##a,
1004 	#include "list-elements.h"
1005 	#undef ELEM
1006 	#define PROJ(a) project_monster_handler_##a,
1007 	#include "list-projections.h"
1008 	#undef PROJ
1009 	NULL
1010 };
1011 
1012 
1013 /**
1014  * Deal damage to a monster from another monster.
1015  *
1016  * This is a helper for project_m(). It is very similar to mon_take_hit(),
1017  * but eliminates the player-oriented stuff of that function. It isn't a type
1018  * handler, but we take a handler context since that has a lot of what we need.
1019  *
1020  * \param context is the project_m context.
1021  * \param m_idx is the cave monster index.
1022  * \return true if the monster died, false if it is still alive.
1023  */
project_m_monster_attack(project_monster_handler_context_t * context,int m_idx)1024 static bool project_m_monster_attack(project_monster_handler_context_t *context, int m_idx)
1025 {
1026 	bool mon_died = false;
1027 	bool seen = context->seen;
1028 	int dam = context->dam;
1029 	enum mon_messages die_msg = context->die_msg;
1030 	enum mon_messages hurt_msg = context->hurt_msg;
1031 	struct monster *mon = context->mon;
1032 
1033 	/* "Unique" monsters can only be "killed" by the player */
1034 	if (rf_has(mon->race->flags, RF_UNIQUE)) {
1035 		/* Reduce monster hp to zero, but don't kill it. */
1036 		if (dam > mon->hp) dam = mon->hp;
1037 	}
1038 
1039 	/* Redraw (later) if needed */
1040 	if (player->upkeep->health_who == mon)
1041 		player->upkeep->redraw |= (PR_HEALTH);
1042 
1043 	/* Wake the monster up, don't notice the player */
1044 	monster_wake(mon, false, 0);
1045 
1046 	/* Hurt the monster */
1047 	mon->hp -= dam;
1048 
1049 	/* Dead or damaged monster */
1050 	if (mon->hp < 0) {
1051 		/* Give detailed messages if destroyed */
1052 		if (!seen) die_msg = MON_MSG_MORIA_DEATH;
1053 
1054 		/* Death message */
1055 		add_monster_message(mon, die_msg, false);
1056 
1057 		/* Generate treasure, etc */
1058 		monster_death(mon, false);
1059 
1060 		/* Delete the monster */
1061 		delete_monster_idx(m_idx);
1062 
1063 		mon_died = true;
1064 	} else if (!monster_is_mimicking(mon)) {
1065 		/* Give detailed messages if visible or destroyed */
1066 		if ((hurt_msg != MON_MSG_NONE) && seen)
1067 			add_monster_message(mon, hurt_msg, false);
1068 
1069 		/* Hack -- Pain message */
1070 		else if (dam > 0)
1071 			message_pain(mon, dam);
1072 	}
1073 
1074 	return mon_died;
1075 }
1076 
1077 /**
1078  * Deal damage to a monster from a non-monster source (usually a player,
1079  * but could also be a trap)
1080  *
1081  * This is a helper for project_m(). It isn't a type handler, but we take a
1082  * handler context since that has a lot of what we need.
1083  *
1084  * \param context is the project_m context.
1085  * \return true if the monster died, false if it is still alive.
1086  */
project_m_player_attack(project_monster_handler_context_t * context)1087 static bool project_m_player_attack(project_monster_handler_context_t *context)
1088 {
1089 	bool fear = false;
1090 	bool mon_died = false;
1091 	bool seen = context->seen;
1092 	int dam = context->dam;
1093 	enum mon_messages die_msg = context->die_msg;
1094 	enum mon_messages hurt_msg = context->hurt_msg;
1095 	struct monster *mon = context->mon;
1096 
1097 	/* The monster is going to be killed, so display a specific death message.
1098 	 * If the monster is not visible to the player, use a generic message.
1099 	 *
1100 	 * Note that mon_take_hit() below is passed a zero-length string, which
1101 	 * ensures it doesn't print any death message and allows correct ordering
1102 	 * of messages. */
1103 	if (dam > mon->hp) {
1104 		if (!seen) die_msg = MON_MSG_MORIA_DEATH;
1105 		add_monster_message(mon, die_msg, false);
1106 	}
1107 
1108 	/* No damage is now going to mean the monster is not hit - and hence
1109 	 * is not woken or released from holding */
1110 	if (dam) {
1111 		mon_died = mon_take_hit(mon, dam, &fear, "");
1112 	}
1113 
1114 	/* If the monster didn't die, provide additional messages about how it was
1115 	 * hurt/damaged. If a specific message isn't provided, display a message
1116 	 * based on the amount of damage dealt. Also display a message
1117 	 * if the hit caused the monster to flee. */
1118 	if (!mon_died) {
1119 		if (seen && hurt_msg != MON_MSG_NONE)
1120 			add_monster_message(mon, hurt_msg, false);
1121 		else if (dam > 0)
1122 			message_pain(mon, dam);
1123 
1124 		if (seen && fear)
1125 			add_monster_message(mon, MON_MSG_FLEE_IN_TERROR, true);
1126 	}
1127 
1128 	return mon_died;
1129 }
1130 
1131 /**
1132  * Apply side effects from an attack onto a monster.
1133  *
1134  * This is a helper for project_m(). It isn't a type handler, but we take a
1135  * handler context since that has a lot of what we need.
1136  *
1137  * \param context is the project_m context.
1138  * \param m_idx is the cave monster index.
1139  */
project_m_apply_side_effects(project_monster_handler_context_t * context,int m_idx)1140 static void project_m_apply_side_effects(project_monster_handler_context_t *context, int m_idx)
1141 {
1142 	int typ = context->type;
1143 	struct monster *mon = context->mon;
1144 
1145 	/*
1146 	 * Handle side effects of an attack. First we check for polymorphing since
1147 	 * it may not make sense to apply status effects to a changed monster.
1148 	 * Right now, teleporting is also separate, but it could make sense in the
1149 	 * future to change it so that we can apply other effects AND teleport the
1150 	 * monster.
1151 	 */
1152 	if (context->do_poly) {
1153 		enum mon_messages hurt_msg = MON_MSG_UNAFFECTED;
1154 		const struct loc grid = context->grid;
1155 		int savelvl = 0;
1156 		struct monster_race *old;
1157 		struct monster_race *new;
1158 
1159 		/* Uniques cannot be polymorphed */
1160 		if (rf_has(mon->race->flags, RF_UNIQUE)) {
1161 			add_monster_message(mon, hurt_msg, false);
1162 			return;
1163 		}
1164 
1165 		if (context->seen) context->obvious = true;
1166 
1167 		/* Saving throws depend on damage for direct poly, random for chaos */
1168 		if (typ == PROJ_MON_POLY)
1169 			savelvl = randint1(MAX(1, context->do_poly - 10)) + 10;
1170 		else
1171 			savelvl = randint1(90);
1172 		if (mon->race->level > savelvl) {
1173 			if (typ == PROJ_MON_POLY) hurt_msg = MON_MSG_MAINTAIN_SHAPE;
1174 			add_monster_message(mon, hurt_msg, false);
1175 			return;
1176 		}
1177 
1178 		old = mon->race;
1179 		new = poly_race(old);
1180 
1181 		/* Handle polymorph */
1182 		if (new != old) {
1183 			struct monster_group_info info = {0, 0 };
1184 
1185 			/* Report the polymorph before changing the monster */
1186 			hurt_msg = MON_MSG_CHANGE;
1187 			add_monster_message(mon, hurt_msg, false);
1188 
1189 			/* Delete the old monster, and return a new one */
1190 			delete_monster_idx(m_idx);
1191 			place_new_monster(cave, grid, new, false, false, info,
1192 							  ORIGIN_DROP_POLY);
1193 			context->mon = square_monster(cave, grid);
1194 		} else {
1195 			add_monster_message(mon, hurt_msg, false);
1196 		}
1197 	} else if (context->teleport_distance > 0) {
1198 		char dice[5];
1199 		strnfmt(dice, sizeof(dice), "%d", context->teleport_distance);
1200 		effect_simple(EF_TELEPORT, context->origin, dice, 0, 0, 0,
1201 					  context->grid.y, context->grid.x, NULL);
1202 
1203 		/* Wake the monster up, don't notice the player */
1204 		monster_wake(mon, false, 0);
1205 	} else {
1206 		for (int i = 0; i < MON_TMD_MAX; i++) {
1207 			if (context->mon_timed[i] > 0) {
1208 				mon_inc_timed(mon,
1209 							  i,
1210 							  context->mon_timed[i],
1211 							  context->flag | MON_TMD_FLG_NOTIFY);
1212 				context->obvious = true;
1213 			}
1214 		}
1215 	}
1216 }
1217 
1218 /**
1219  * Called from project() to affect monsters
1220  *
1221  * Called for projections with the PROJECT_KILL flag set, which includes
1222  * bolt, beam, ball and breath effects.
1223  *
1224  * \param origin is the monster list index of the caster
1225  * \param r is the distance from the centre of the effect
1226  * \param y the coordinates of the grid being handled
1227  * \param x the coordinates of the grid being handled
1228  * \param dam is the "damage" from the effect at distance r from the centre
1229  * \param typ is the projection (PROJ_) type
1230  * \param flg consists of any relevant PROJECT_ flags
1231  * \return whether the effects were obvious
1232  *
1233  * Note that this routine can handle "no damage" attacks (like teleport) by
1234  * taking a zero damage, and can even take parameters to attacks (like
1235  * confuse) by accepting a "damage", using it to calculate the effect, and
1236  * then setting the damage to zero.  Note that actual damage should be already
1237  * adjusted for distance from the "epicenter" when passed in, but other effects
1238  * may be influenced by r.
1239  *
1240  * Note that "polymorph" is dangerous, since a failure in "place_monster()"'
1241  * may result in a dereference of an invalid pointer.  XXX XXX XXX
1242  *
1243  * Various messages are produced, and damage is applied.
1244  *
1245  * Just casting an element (e.g. plasma) does not make you immune, you must
1246  * actually be made of that substance, or breathe big balls of it.
1247  *
1248  * We assume that "Plasma" monsters, and "Plasma" breathers, are immune
1249  * to plasma.
1250  *
1251  * We assume "Nether" is an evil, necromantic force, so it doesn't hurt undead,
1252  * and hurts evil less.  If can breath nether, then it resists it as well.
1253  * This should actually be coded into monster records rather than aasumed - NRM
1254  *
1255  * Damage reductions use the following formulas:
1256  *   Note that "dam = dam * 6 / (randint1(6) + 6);"
1257  *     gives avg damage of .655, ranging from .858 to .500
1258  *   Note that "dam = dam * 5 / (randint1(6) + 6);"
1259  *     gives avg damage of .544, ranging from .714 to .417
1260  *   Note that "dam = dam * 4 / (randint1(6) + 6);"
1261  *     gives avg damage of .444, ranging from .556 to .333
1262  *   Note that "dam = dam * 3 / (randint1(6) + 6);"
1263  *     gives avg damage of .327, ranging from .427 to .250
1264  *   Note that "dam = dam * 2 / (randint1(6) + 6);"
1265  *     gives something simple.
1266  *
1267  * In this function, "result" messages are postponed until the end, where
1268  * the "note" string is appended to the monster name, if not NULL.  So,
1269  * to make a spell have no effect just set "note" to NULL.  You should
1270  * also set "notice" to false, or the player will learn what the spell does.
1271  *
1272  * Note that this function determines if the player can see anything that
1273  * happens by taking into account: blindness, line-of-sight, and illumination.
1274  *
1275  * Hack -- effects on grids which are memorized but not in view are also seen.
1276  */
project_m(struct source origin,int r,struct loc grid,int dam,int typ,int flg,bool * did_hit,bool * was_obvious)1277 void project_m(struct source origin, int r, struct loc grid, int dam, int typ,
1278 			   int flg, bool *did_hit, bool *was_obvious)
1279 {
1280 	struct monster *mon;
1281 	struct monster_lore *lore;
1282 
1283 	/* Is the monster "seen"? */
1284 	bool seen = false;
1285 	bool mon_died = false;
1286 
1287 	/* Is the effect obvious? */
1288 	bool obvious = (flg & PROJECT_AWARE ? true : false);
1289 
1290 	/* Are we trying to id the source of this effect? */
1291 	bool id = (origin.what == SRC_PLAYER) ? !obvious : false;
1292 
1293 	/* Is the source an extra charming player? */
1294 	bool charm = (origin.what == SRC_PLAYER) ?
1295 		player_has(player, PF_CHARM) : false;
1296 
1297 	int m_idx = square(cave, grid)->mon;
1298 
1299 	project_monster_handler_f monster_handler = monster_handlers[typ];
1300 	project_monster_handler_context_t context = {
1301 		origin,
1302 		r,
1303 		grid,
1304 		dam,
1305 		typ,
1306 		seen,
1307 		id,
1308 		NULL, /* mon */
1309 		NULL, /* lore */
1310 		charm,
1311 		obvious,
1312 		false, /* skipped */
1313 		0, /* flag */
1314 		0, /* do_poly */
1315 		0, /* teleport_distance */
1316 		MON_MSG_NONE, /* hurt_msg */
1317 		MON_MSG_DIE, /* die_msg */
1318 		{0, 0, 0, 0, 0, 0},
1319 	};
1320 
1321 	*did_hit = false;
1322 	*was_obvious = false;
1323 
1324 	/* Walls protect monsters */
1325 	if (!square_ispassable(cave, grid)) return;
1326 
1327 	/* No monster here */
1328 	if (!(m_idx > 0)) return;
1329 
1330 	/* Never affect projector */
1331 	if (origin.what == SRC_MONSTER && origin.which.monster == m_idx) return;
1332 
1333 	/* Obtain monster info */
1334 	mon = cave_monster(cave, m_idx);
1335 	lore = get_lore(mon->race);
1336 	context.mon = mon;
1337 	context.lore = lore;
1338 
1339 	/* See visible monsters */
1340 	if (monster_is_visible(mon)) {
1341 		seen = true;
1342 		context.seen = seen;
1343 	}
1344 
1345 	/* Breathers may not blast members of the same race. */
1346 	if (origin.what == SRC_MONSTER && (flg & PROJECT_SAFE)) {
1347 		/* Point to monster information of caster */
1348 		struct monster *caster = cave_monster(cave, origin.which.monster);
1349 		if (!caster) return;
1350 
1351 		/* Skip monsters with the same race */
1352 		if (caster->race == mon->race)
1353 			return;
1354 	}
1355 
1356 	/* Some monsters get "destroyed" */
1357 	if (monster_is_destroyed(mon))
1358 		context.die_msg = MON_MSG_DESTROYED;
1359 
1360 	/* Force obviousness for certain types if seen. */
1361 	if (projections[typ].obvious && context.seen)
1362 		context.obvious = true;
1363 
1364 	if (monster_handler != NULL)
1365 		monster_handler(&context);
1366 
1367 	/* Wake monster if required */
1368 	if (projections[typ].wake)
1369 		monster_wake(mon, false, 100);
1370 
1371 	/* Absolutely no effect */
1372 	if (context.skipped) return;
1373 
1374 	/* Apply damage to the monster, based on who did the damage. */
1375 	if (origin.what == SRC_MONSTER) {
1376 		mon_died = project_m_monster_attack(&context, m_idx);
1377 	} else {
1378 		mon_died = project_m_player_attack(&context);
1379 	}
1380 
1381 	if (!mon_died)
1382 		project_m_apply_side_effects(&context, m_idx);
1383 
1384 	/* Update locals, since the project_m_* functions can change some values. */
1385 	mon = context.mon;
1386 	obvious = context.obvious;
1387 
1388 	/* Check for NULL, since polymorph can occasionally return NULL. */
1389 	if (mon != NULL) {
1390 		/* Update the monster */
1391 		if (!mon_died)
1392 			update_mon(mon, cave, false);
1393 
1394 		/* Redraw the (possibly new) monster grid */
1395 		square_light_spot(cave, mon->grid);
1396 
1397 		/* Update monster recall window */
1398 		if (player->upkeep->monster_race == mon->race) {
1399 			/* Window stuff */
1400 			player->upkeep->redraw |= (PR_MONSTER);
1401 		}
1402 	}
1403 
1404 	/* Track it */
1405 	*did_hit = true;
1406 
1407 	/* Return "Anything seen?" */
1408 	*was_obvious = !!obvious;
1409 }
1410 
1411