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