make rank() static again
[NetHack.git] / src / explode.c
blob36c3aca59ea963e0f8a8215329fe75faff05608a
1 /* NetHack 3.7 explode.c $NHDT-Date: 1736530208 2025/01/10 09:30:08 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.122 $ */
2 /* Copyright (C) 1990 by Ken Arromdee */
3 /* NetHack may be freely redistributed. See license for details. */
5 #include "hack.h"
7 staticfn int explosionmask(struct monst *, uchar, char) NONNULLARG1;
8 staticfn void engulfer_explosion_msg(uchar, char);
10 /* Note: Arrays are column first, while the screen is row first */
11 static const int explosion[3][3] = {
12 { S_expl_tl, S_expl_ml, S_expl_bl },
13 { S_expl_tc, S_expl_mc, S_expl_bc },
14 { S_expl_tr, S_expl_mr, S_expl_br } };
16 /* what to do at [x+i][y+j] for i=-1,0,1 and j=-1,0,1 */
17 enum explode_action {
18 EXPL_NONE = 0, /* not specified yet or no shield effect needed */
19 EXPL_MON = 1, /* monster is affected */
20 EXPL_HERO = 2, /* hero is affected */
21 EXPL_SKIP = 4 /* don't apply shield effect (out of bounds) */
24 /* check if shield effects are needed for location affected by explosion */
25 staticfn int
26 explosionmask(
27 struct monst *m, /* target monster (might be youmonst) */
28 uchar adtyp, /* damage type */
29 char olet) /* object class (only matters for AD_DISN) */
31 int res = EXPL_NONE;
33 if (m == &gy.youmonst) {
34 switch (adtyp) {
35 case AD_PHYS:
36 /* leave 'res' with EXPL_NONE */
37 break;
38 case AD_MAGM:
39 if (Antimagic)
40 res = EXPL_HERO;
41 break;
42 case AD_FIRE:
43 if (Fire_resistance)
44 res = EXPL_HERO;
45 break;
46 case AD_COLD:
47 if (Cold_resistance)
48 res = EXPL_HERO;
49 break;
50 case AD_DISN:
51 if ((olet == WAND_CLASS)
52 ? (nonliving(m->data) || is_demon(m->data))
53 : Disint_resistance)
54 res = EXPL_HERO;
55 break;
56 case AD_ELEC:
57 if (Shock_resistance)
58 res = EXPL_HERO;
59 break;
60 case AD_DRST:
61 if (Poison_resistance)
62 res = EXPL_HERO;
63 break;
64 case AD_ACID:
65 if (Acid_resistance)
66 res = EXPL_HERO;
67 break;
68 default:
69 impossible("explosion type %d?", adtyp);
70 break;
73 } else {
74 /* 'm' is a monster */
75 switch (adtyp) {
76 case AD_PHYS:
77 break;
78 case AD_MAGM:
79 if (resists_magm(m))
80 res = EXPL_MON;
81 break;
82 case AD_FIRE:
83 if (resists_fire(m))
84 res = EXPL_MON;
85 break;
86 case AD_COLD:
87 if (resists_cold(m))
88 res = EXPL_MON;
89 break;
90 case AD_DISN:
91 if ((olet == WAND_CLASS)
92 ? (nonliving(m->data) || is_demon(m->data)
93 || is_vampshifter(m))
94 : !!resists_disint(m))
95 res = EXPL_MON;
96 break;
97 case AD_ELEC:
98 if (resists_elec(m))
99 res = EXPL_MON;
100 break;
101 case AD_DRST:
102 if (resists_poison(m))
103 res = EXPL_MON;
104 break;
105 case AD_ACID:
106 if (resists_acid(m))
107 res = EXPL_MON;
108 break;
109 default:
110 impossible("explosion type %d?", adtyp);
111 break;
114 return res;
117 staticfn void
118 engulfer_explosion_msg(uchar adtyp, char olet)
120 const char *adj = (char *) 0;
122 if (digests(u.ustuck->data)) {
123 switch (adtyp) {
124 case AD_FIRE:
125 adj = "heartburn";
126 break;
127 case AD_COLD:
128 adj = "chilly";
129 break;
130 case AD_DISN:
131 if (olet == WAND_CLASS)
132 adj = "irradiated by pure energy";
133 else
134 adj = "perforated";
135 break;
136 case AD_ELEC:
137 adj = "shocked";
138 break;
139 case AD_DRST:
140 adj = "poisoned";
141 break;
142 case AD_ACID:
143 adj = "an upset stomach";
144 break;
145 default:
146 adj = "fried";
147 break;
149 pline("%s gets %s!", Monnam(u.ustuck), adj);
150 } else {
151 switch (adtyp) {
152 case AD_FIRE:
153 adj = "toasted";
154 break;
155 case AD_COLD:
156 adj = "chilly";
157 break;
158 case AD_DISN:
159 if (olet == WAND_CLASS)
160 adj = "overwhelmed by pure energy";
161 else
162 adj = "perforated";
163 break;
164 case AD_ELEC:
165 adj = "shocked";
166 break;
167 case AD_DRST:
168 adj = "intoxicated";
169 break;
170 case AD_ACID:
171 adj = "burned";
172 break;
173 default:
174 adj = "fried";
175 break;
177 pline("%s gets slightly %s!", Monnam(u.ustuck), adj);
181 /* Note: I had to choose one of three possible kinds of "type" when writing
182 * this function: a wand type (like in zap.c), an adtyp, or an object type.
183 * Wand types get complex because they must be converted to adtyps for
184 * determining such things as fire resistance. Adtyps get complex in that
185 * they don't supply enough information--was it a player or a monster that
186 * did it, and with a wand, spell, or breath weapon? Object types share both
187 * these disadvantages....
189 * Note: anything with a AT_BOOM AD_PHYS attack uses PHYS_EXPL_TYPE for type.
191 * Important note about Half_physical_damage:
192 * Unlike losehp(), explode() makes the Half_physical_damage adjustments
193 * itself, so the caller should never have done that ahead of time.
194 * It has to be done this way because the damage value is applied to
195 * things beside the player. Care is taken within explode() to ensure
196 * that Half_physical_damage only affects the damage applied to the hero.
198 void
199 explode(
200 coordxy x, coordxy y, /* explosion's location;
201 * adjacent spots are also affected */
202 int type, /* same as in zap.c; -(wand typ) for some WAND_CLASS */
203 int dam, /* damage amount */
204 char olet, /* object class or BURNING_OIL or MON_EXPLODE */
205 int expltype) /* explosion type: controls color of explosion glyphs */
207 int i, j, k, damu = dam;
208 boolean starting = 1;
209 boolean visible, any_shield;
210 int uhurt = 0; /* 0=unhurt, 1=items damaged, 2=you and items damaged */
211 const char *str = (const char *) 0;
212 struct monst *mtmp, *mdef = 0;
213 uchar adtyp;
214 int explmask[3][3]; /* 0=normal explosion, 1=do shieldeff, 2=do nothing */
215 coordxy xx, yy;
216 boolean shopdamage = FALSE, generic = FALSE,
217 do_hallu = FALSE, inside_engulfer, grabbed, grabbing;
218 coord grabxy;
219 char hallu_buf[BUFSZ], killr_buf[BUFSZ];
220 short exploding_wand_typ = 0;
221 boolean you_exploding = (olet == MON_EXPLODE && type >= 0);
222 boolean didmsg = FALSE;
224 if (olet == WAND_CLASS) { /* retributive strike */
225 /* 'type' is passed as (wand's object type * -1); save
226 object type and convert 'type' itself to zap-type */
227 if (type < 0) {
228 type = -type;
229 exploding_wand_typ = (short) type;
230 /* most attack wands produce specific explosions;
231 other types produce a generic magical explosion */
232 if (objects[type].oc_dir == RAY
233 && type != WAN_DIGGING && type != WAN_SLEEP) {
234 type -= WAN_MAGIC_MISSILE;
235 if (type < 0 || type > 9) {
236 impossible("explode: wand has bad zap type (%d).", type);
237 type = 0;
239 } else
240 type = 0;
242 switch (Role_switch) {
243 case PM_CLERIC:
244 case PM_MONK:
245 case PM_WIZARD:
246 damu /= 5;
247 break;
248 case PM_HEALER:
249 case PM_KNIGHT:
250 damu /= 2;
251 break;
252 default:
253 break;
255 } else if (olet == BURNING_OIL) {
256 /* used to provide extra information to zap_over_floor() */
257 exploding_wand_typ = POT_OIL;
258 } else if (olet == SCROLL_CLASS) {
259 /* ditto */
260 exploding_wand_typ = SCR_FIRE;
261 } else if (olet == TRAP_EXPLODE) {
262 type = 0; /* hardcoded to generic magic explosion */
264 /* muse_unslime: SCR_FIRE */
265 if (expltype < 0) {
266 /* hero gets credit/blame for killing this monster, not others */
267 mdef = m_at(x, y);
268 expltype = -expltype;
270 /* if hero is engulfed and caused the explosion, only hero and
271 engulfer will be affected */
272 inside_engulfer = (u.uswallow && type >= 0);
273 /* held but not engulfed implies holder is reaching into second spot
274 so might get hit by double damage */
275 grabbed = grabbing = FALSE;
276 if (u.ustuck && !u.uswallow) {
277 if (Upolyd && sticks(gy.youmonst.data))
278 grabbing = TRUE;
279 else
280 grabbed = TRUE;
281 grabxy.x = u.ustuck->mx;
282 grabxy.y = u.ustuck->my;
283 } else
284 grabxy.x = grabxy.y = 0; /* lint suppression */
285 /* FIXME:
286 * It is possible for a grabber to be outside the explosion
287 * radius and reaching inside to hold the hero. If so, it ought
288 * to take damage (the extra half of double damage). It is also
289 * possible for poly'd hero to be outside the radius and reaching
290 * in to hold a monster. Hero should take damage in that situation.
292 * Probably the simplest way to handle this would be to expand
293 * the radius used when collecting targets but exclude everything
294 * beyond the regular radius which isn't reaching inside. Then
295 * skip harm to gear of any extended targets when inflicting damage.
298 if (olet == MON_EXPLODE && !you_exploding) {
299 /* when explode() is called recursively, svk.killer.name might change
300 so retain a copy of the current value for this explosion */
301 str = strcpy(killr_buf, svk.killer.name);
302 do_hallu = (Hallucination
303 && (strstri(str, "'s explosion")
304 || strstri(str, "s' explosion")));
306 if (type == PHYS_EXPL_TYPE) {
307 /* currently only gas spores */
308 adtyp = AD_PHYS;
309 } else {
310 /* If str is e.g. "flaming sphere's explosion" from above, we want to
311 * still assign adtyp appropriately, but not replace str. */
312 const char *adstr = NULL;
314 switch (abs(type) % 10) {
315 case 0:
316 adstr = "magical blast";
317 adtyp = AD_MAGM;
318 break;
319 case 1:
320 adstr = (olet == BURNING_OIL) ? "burning oil"
321 : (olet == SCROLL_CLASS) ? "tower of flame" : "fireball";
322 /* fire damage, not physical damage */
323 adtyp = AD_FIRE;
324 break;
325 case 2:
326 adstr = "ball of cold";
327 adtyp = AD_COLD;
328 break;
329 case 4:
330 adstr = (olet == WAND_CLASS) ? "death field"
331 : "disintegration field";
332 adtyp = AD_DISN;
333 break;
334 case 5:
335 adstr = "ball of lightning";
336 adtyp = AD_ELEC;
337 break;
338 case 6:
339 adstr = "poison gas cloud";
340 adtyp = AD_DRST;
341 break;
342 case 7:
343 adstr = "splash of acid";
344 adtyp = AD_ACID;
345 break;
346 default:
347 impossible("explosion base type %d?", type);
348 return;
350 if (!str)
351 str = adstr;
354 any_shield = visible = FALSE;
355 for (i = 0; i < 3; i++)
356 for (j = 0; j < 3; j++) {
357 xx = x + i - 1;
358 yy = y + j - 1;
359 if (!isok(xx, yy)) {
360 explmask[i][j] = EXPL_SKIP;
361 continue;
363 explmask[i][j] = EXPL_NONE;
365 if (u_at(xx, yy)) {
366 explmask[i][j] = explosionmask(&gy.youmonst, adtyp, olet);
368 /* can be both you and mtmp if you're swallowed or riding */
369 mtmp = m_at(xx, yy);
370 if (!mtmp && u_at(xx, yy))
371 mtmp = u.usteed;
372 if (mtmp && DEADMONSTER(mtmp))
373 mtmp = 0;
374 if (mtmp) {
375 explmask[i][j] |= explosionmask(mtmp, adtyp, olet);
378 if (mtmp && cansee(xx, yy) && !canspotmon(mtmp))
379 map_invisible(xx, yy);
380 else if (!mtmp)
381 (void) unmap_invisible(xx, yy);
382 if (cansee(xx, yy))
383 visible = TRUE;
384 if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0)
385 any_shield = TRUE;
388 if (visible) {
389 /* Start the explosion */
390 for (i = 0; i < 3; i++)
391 for (j = 0; j < 3; j++) {
392 if (explmask[i][j] == EXPL_SKIP)
393 continue;
394 xx = x + i - 1;
395 yy = y + j - 1;
396 tmp_at(starting ? DISP_BEAM : DISP_CHANGE,
397 explosion_to_glyph(expltype, explosion[i][j]));
398 tmp_at(xx, yy);
399 starting = 0;
401 curs_on_u(); /* will flush screen and output */
403 if (any_shield && flags.sparkle) { /* simulate shield effect */
404 for (k = 0; k < SHIELD_COUNT; k++) {
405 for (i = 0; i < 3; i++)
406 for (j = 0; j < 3; j++) {
407 xx = x + i - 1;
408 yy = y + j - 1;
409 if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0)
411 * Bypass tmp_at() and send the shield glyphs
412 * directly to the buffered screen. tmp_at()
413 * will clean up the location for us later.
415 show_glyph(xx, yy,
416 cmap_to_glyph(shield_static[k]));
418 curs_on_u(); /* will flush screen and output */
419 nh_delay_output();
422 /* Cover last shield glyph with blast symbol. */
423 for (i = 0; i < 3; i++)
424 for (j = 0; j < 3; j++) {
425 xx = x + i - 1;
426 yy = y + j - 1;
427 if ((explmask[i][j] & (EXPL_MON | EXPL_HERO)) != 0)
428 show_glyph(xx, yy,
429 explosion_to_glyph(expltype,
430 explosion[i][j]));
433 } else { /* delay a little bit. */
434 nh_delay_output();
435 nh_delay_output();
438 tmp_at(DISP_END, 0); /* clear the explosion */
439 } else {
440 if (olet == MON_EXPLODE || olet == TRAP_EXPLODE) {
441 str = "explosion";
442 generic = TRUE;
444 if (!Deaf && olet != SCROLL_CLASS) {
445 Soundeffect(se_blast, 75);
446 You_hear("a blast.");
447 didmsg = TRUE;
451 if (!Deaf && !didmsg)
452 pline("Boom!");
454 /* apply effects to monsters and floor objects first, in case the
455 damage to the hero is fatal and leaves bones */
456 if (dam) {
457 for (i = 0; i < 3; i++) {
458 for (j = 0; j < 3; j++) {
459 int itemdmg = 0;
461 if (explmask[i][j] == EXPL_SKIP)
462 continue;
463 xx = x + i - 1;
464 yy = y + j - 1;
465 if (u_at(xx, yy)) {
466 uhurt = ((explmask[i][j] & EXPL_HERO) != 0) ? 1 : 2;
467 /* If the player is attacking via polyself into something
468 * with an explosion attack, leave them (and their gear)
469 * unharmed, to avoid punishing them from using such
470 * polyforms creatively */
471 if (!svc.context.mon_moving && you_exploding)
472 uhurt = 0;
473 } else if (inside_engulfer) {
474 /* for inside_engulfer, only <u.ux,u.uy> is affected */
475 continue;
478 /* Affect the floor unless the player caused the explosion
479 * from inside their engulfer. */
480 if (!(u.uswallow && !svc.context.mon_moving))
481 (void) zap_over_floor(xx, yy, type,
482 &shopdamage, FALSE,
483 exploding_wand_typ);
485 mtmp = m_at(xx, yy);
486 if (!mtmp && u_at(xx, yy))
487 mtmp = u.usteed;
488 if (!mtmp)
489 continue;
490 if (do_hallu) {
491 int tryct = 0;
493 /* replace "gas spore" with a different description
494 for each target (we can't distinguish personal names
495 like "Barney" here in order to suppress "the" below,
496 so avoid any which begins with a capital letter) */
497 do {
498 Sprintf(hallu_buf, "%s explosion",
499 s_suffix(rndmonnam((char *) 0)));
500 } while (*hallu_buf != lowc(*hallu_buf) && ++tryct < 20);
501 str = hallu_buf;
503 if (engulfing_u(mtmp)) {
504 engulfer_explosion_msg(adtyp, olet);
505 } else if (cansee(xx, yy)) {
506 if (mtmp->m_ap_type)
507 seemimic(mtmp);
508 pline("%s is caught in the %s!", Monnam(mtmp), str);
511 itemdmg = destroy_items(mtmp, (int) adtyp, dam);
512 if (adtyp == AD_FIRE) {
513 (void) burnarmor(mtmp);
514 ignite_items(mtmp->minvent);
517 if ((explmask[i][j] & EXPL_MON) != 0) {
518 /* Damage from ring/wand explosion isn't itself
519 * electrical in nature, nor is damage from freezing
520 * potion really cold in nature, nor is damage from
521 * boiling potion or exploding oil; only burning items
522 * damage is the "same type" as the explosion. Because
523 * this is imperfect and marginal (burning items only
524 * deal 1 damage), ignore it for golemeffects(). */
525 golemeffects(mtmp, (int) adtyp, dam);
526 mtmp->mhp -= itemdmg; /* item destruction dmg */
527 } else {
528 /* Call resist with 0 and do damage manually so 1) we can
529 * get out the message before doing the damage, and 2) we
530 * can call mondied, not killed, if it's not your blast.
532 int mdam = dam;
534 if (resist(mtmp, olet, 0, FALSE)) {
535 /* inside_engulfer: <xx,yy> == <u.ux,u.uy> */
536 if (cansee(xx, yy) || inside_engulfer)
537 pline("%s resists the %s!", Monnam(mtmp), str);
538 mdam = (dam + 1) / 2;
540 /* if grabber is reaching into hero's spot and
541 hero's spot is within explosion radius, grabber
542 gets hit by double damage */
543 if (grabbed && mtmp == u.ustuck && next2u(x, y))
544 mdam *= 2;
545 /* being resistant to opposite type of damage makes
546 target more vulnerable to current type of damage
547 (when target is also resistant to current type,
548 we won't get here) */
549 if (resists_cold(mtmp) && adtyp == AD_FIRE)
550 mdam *= 2;
551 else if (resists_fire(mtmp) && adtyp == AD_COLD)
552 mdam *= 2;
553 mtmp->mhp -= mdam + itemdmg;
555 if (DEADMONSTER(mtmp)) {
556 int xkflg = ((adtyp == AD_FIRE
557 && completelyburns(mtmp->data))
558 ? XKILL_NOCORPSE : 0);
560 if (!svc.context.mon_moving) {
561 xkilled(mtmp, XKILL_GIVEMSG | xkflg);
562 } else if (mdef && mtmp == mdef) {
563 /* 'mdef' killed self trying to cure being turned
564 * into slime due to some action by the player.
565 * Hero gets the credit (experience) and most of
566 * the blame (possible loss of alignment and/or
567 * luck and/or telepathy depending on mtmp) but
568 * doesn't break pacifism. xkilled()'s message
569 * would be "you killed <mdef>" so give our own.
571 if (cansee(mtmp->mx, mtmp->my) || canspotmon(mtmp))
572 pline("%s is %s!", Monnam(mtmp),
573 xkflg ? "burned completely"
574 : nonliving(mtmp->data) ? "destroyed"
575 : "killed");
576 xkilled(mtmp, XKILL_NOMSG | XKILL_NOCONDUCT | xkflg);
577 } else {
578 if (xkflg)
579 adtyp = AD_RBRE; /* no corpse */
580 monkilled(mtmp, "", (int) adtyp);
582 } else if (!svc.context.mon_moving) {
583 /* all affected monsters, even if mdef is set */
584 setmangry(mtmp, TRUE);
590 /* Do your injury last */
591 if (uhurt) {
592 /* give message for any monster-induced explosion
593 or player-induced one other than scroll of fire */
594 if (flags.verbose && (type < 0 || olet != SCROLL_CLASS)) {
595 if (do_hallu) { /* (see explanation above) */
596 do {
597 Sprintf(hallu_buf, "%s explosion",
598 s_suffix(rndmonnam((char *) 0)));
599 } while (*hallu_buf != lowc(*hallu_buf));
600 str = hallu_buf;
602 You("are caught in the %s!", str);
603 iflags.last_msg = PLNMSG_CAUGHT_IN_EXPLOSION;
605 /* do property damage first, in case we end up leaving bones */
606 if (adtyp == AD_FIRE)
607 burn_away_slime();
608 if (Invulnerable) {
609 damu = 0;
610 You("are unharmed!");
611 } else if (adtyp == AD_PHYS || adtyp == AD_ACID)
612 damu = Maybe_Half_Phys(damu);
613 if (adtyp == AD_FIRE) {
614 (void) burnarmor(&gy.youmonst);
615 ignite_items(gi.invent);
617 (void) destroy_items(&gy.youmonst, (int) adtyp, dam);
619 ugolemeffects((int) adtyp, damu);
620 if (uhurt == 2) {
621 /* if poly'd hero is grabbing another victim, hero takes
622 double damage (note: don't rely on u.ustuck here because
623 that victim might have been killed when hit by the blast) */
624 if (grabbing && dist2((int) grabxy.x, (int) grabxy.y, x, y) <= 2)
625 damu *= 2;
626 /* hero does not get same fire-resistant vs cold and
627 cold-resistant vs fire double damage as monsters [why not?] */
628 if (Upolyd)
629 u.mh -= damu;
630 else
631 u.uhp -= damu;
632 disp.botl = TRUE;
635 /* You resisted the damage, lets not keep that to ourselves */
636 if (uhurt == 1)
637 monstseesu_ad(adtyp);
638 else
639 monstunseesu_ad(adtyp);
641 if (u.uhp <= 0 || (Upolyd && u.mh <= 0)) {
642 if (Upolyd) {
643 rehumanize();
644 } else {
645 if (olet == MON_EXPLODE) {
646 if (generic) /* explosion was unseen; str=="explosion", */
647 ; /* svk.killer.name=="gas spore's explosion". */
648 else if (str != svk.killer.name && str != hallu_buf)
649 Strcpy(svk.killer.name, str);
650 svk.killer.format = KILLED_BY_AN;
651 } else if (olet == TRAP_EXPLODE) {
652 svk.killer.format = NO_KILLER_PREFIX;
653 Snprintf(svk.killer.name, sizeof svk.killer.name,
654 "caught %sself in a %s", uhim(),
655 str);
656 } else if (type >= 0 && olet != SCROLL_CLASS) {
657 svk.killer.format = NO_KILLER_PREFIX;
658 Snprintf(svk.killer.name, sizeof svk.killer.name,
659 "caught %sself in %s own %s", uhim(),
660 uhis(), str);
661 } else {
662 svk.killer.format = (!strcmpi(str, "tower of flame")
663 || !strcmpi(str, "fireball"))
664 ? KILLED_BY_AN
665 : KILLED_BY;
666 Strcpy(svk.killer.name, str);
668 if (iflags.last_msg == PLNMSG_CAUGHT_IN_EXPLOSION
669 || iflags.last_msg == PLNMSG_TOWER_OF_FLAME) /*seffects()*/
670 pline("It is fatal.");
671 else
672 pline_The("%s is fatal.", str);
673 /* Known BUG: BURNING suppresses corpse in bones data,
674 but done does not handle killer reason correctly */
675 done((adtyp == AD_FIRE) ? BURNING : DIED);
678 exercise(A_STR, FALSE);
681 if (shopdamage) {
682 pay_for_damage((adtyp == AD_FIRE) ? "burn away"
683 : (adtyp == AD_COLD) ? "shatter"
684 : (adtyp == AD_DISN) ? "disintegrate"
685 : "destroy",
686 FALSE);
689 /* explosions are noisy */
690 i = dam * dam;
691 if (i < 50)
692 i = 50; /* in case random damage is very small */
693 if (inside_engulfer)
694 i = (i + 3) / 4;
695 wake_nearto(x, y, i);
698 struct scatter_chain {
699 struct scatter_chain *next; /* pointer to next scatter item */
700 struct obj *obj; /* pointer to the object */
701 coordxy ox; /* location of */
702 coordxy oy; /* item */
703 schar dx; /* direction of */
704 schar dy; /* travel */
705 int range; /* range of object */
706 boolean stopped; /* flag for in-motion/stopped */
710 * scflags:
711 * VIS_EFFECTS Add visual effects to display
712 * MAY_HITMON Objects may hit monsters
713 * MAY_HITYOU Objects may hit hero
714 * MAY_HIT Objects may hit you or monsters
715 * MAY_DESTROY Objects may be destroyed at random
716 * MAY_FRACTURE Stone objects can be fractured (statues, boulders)
719 /* returns number of scattered objects */
720 long
721 scatter(coordxy sx, coordxy sy, /* location of objects to scatter */
722 int blastforce, /* force behind the scattering */
723 unsigned int scflags,
724 struct obj *obj) /* only scatter this obj */
726 struct obj *otmp;
727 int tmp;
728 int farthest = 0;
729 uchar typ;
730 long qtmp;
731 boolean used_up;
732 boolean individual_object = obj ? TRUE : FALSE;
733 boolean shop_origin, lostgoods = FALSE;
734 struct monst *mtmp, *shkp = 0;
735 struct scatter_chain *stmp, *stmp2 = 0;
736 struct scatter_chain *schain = (struct scatter_chain *) 0;
737 long total = 0L;
739 if (individual_object && (obj->ox != sx || obj->oy != sy))
740 impossible("scattered object <%d,%d> not at scatter site <%d,%d>",
741 obj->ox, obj->oy, sx, sy);
743 shop_origin = ((shkp = shop_keeper(*in_rooms(sx, sy, SHOPBASE))) != 0
744 && costly_spot(sx, sy));
745 if (shop_origin)
746 credit_report(shkp, 0, TRUE); /* establish baseline, without msgs */
748 while ((otmp = (individual_object ? obj
749 : svl.level.objects[sx][sy])) != 0) {
750 if (otmp == uball || otmp == uchain) {
751 boolean waschain = (otmp == uchain);
753 Soundeffect(se_chain_shatters, 25);
754 pline_The("chain shatters!");
755 unpunish();
756 if (waschain)
757 continue;
759 if (otmp->quan > 1L) {
760 qtmp = otmp->quan - 1L;
761 if (qtmp > LARGEST_INT)
762 qtmp = LARGEST_INT;
763 qtmp = (long) rnd((int) qtmp);
764 otmp = splitobj(otmp, qtmp);
765 } else {
766 obj = (struct obj *) 0; /* all used */
768 obj_extract_self(otmp);
769 used_up = FALSE;
771 /* 9 in 10 chance of fracturing boulders or statues */
772 if ((scflags & MAY_FRACTURE) != 0
773 && (otmp->otyp == BOULDER || otmp->otyp == STATUE)
774 && rn2(10)) {
775 if (otmp->otyp == BOULDER) {
776 if (cansee(sx, sy)) {
777 pline("%s apart.", Tobjnam(otmp, "break"));
778 } else {
779 Soundeffect(se_stone_breaking, 100);
780 You_hear("stone breaking.");
782 fracture_rock(otmp);
783 place_object(otmp, sx, sy);
784 if ((otmp = sobj_at(BOULDER, sx, sy)) != 0) {
785 /* another boulder here, restack it to the top */
786 obj_extract_self(otmp);
787 place_object(otmp, sx, sy);
789 } else {
790 struct trap *trap;
792 if ((trap = t_at(sx, sy)) && trap->ttyp == STATUE_TRAP)
793 deltrap(trap);
794 if (cansee(sx, sy)) {
795 pline("%s.", Tobjnam(otmp, "crumble"));
796 } else {
797 Soundeffect(se_stone_crumbling, 100);
798 You_hear("stone crumbling.");
800 (void) break_statue(otmp);
801 place_object(otmp, sx, sy); /* put fragments on floor */
803 newsym(sx, sy); /* in case it's beyond radius of 'farthest' */
804 used_up = TRUE;
806 /* 1 in 10 chance of destruction of obj; glass, egg destruction */
807 } else if ((scflags & MAY_DESTROY) != 0
808 && (!rn2(10) || (objects[otmp->otyp].oc_material == GLASS
809 || otmp->otyp == EGG))) {
810 if (breaks(otmp, sx, sy))
811 used_up = TRUE;
814 if (!used_up) {
815 stmp = (struct scatter_chain *) alloc(sizeof *stmp);
816 stmp->next = (struct scatter_chain *) 0;
817 stmp->obj = otmp;
818 stmp->ox = sx;
819 stmp->oy = sy;
820 tmp = rn2(N_DIRS); /* get the direction */
821 stmp->dx = xdir[tmp];
822 stmp->dy = ydir[tmp];
823 tmp = blastforce - (otmp->owt / 40);
824 if (tmp < 1)
825 tmp = 1;
826 stmp->range = rnd(tmp); /* anywhere up to that determ. by wt */
827 if (farthest < stmp->range)
828 farthest = stmp->range;
829 stmp->stopped = FALSE;
830 if (!schain)
831 schain = stmp;
832 else
833 stmp2->next = stmp;
834 stmp2 = stmp;
838 while (farthest-- > 0) {
839 for (stmp = schain; stmp; stmp = stmp->next) {
840 if ((stmp->range-- > 0) && (!stmp->stopped)) {
841 gt.thrownobj = stmp->obj; /* mainly in case it kills hero */
842 gb.bhitpos.x = stmp->ox + stmp->dx;
843 gb.bhitpos.y = stmp->oy + stmp->dy;
844 if (isok(gb.bhitpos.x, gb.bhitpos.y))
845 typ = levl[gb.bhitpos.x][gb.bhitpos.y].typ;
846 else
847 typ = STONE;
848 if (!isok(gb.bhitpos.x, gb.bhitpos.y)) {
849 gb.bhitpos.x -= stmp->dx;
850 gb.bhitpos.y -= stmp->dy;
851 stmp->stopped = TRUE;
852 } else if (!ZAP_POS(typ)
853 || closed_door(gb.bhitpos.x, gb.bhitpos.y)) {
854 gb.bhitpos.x -= stmp->dx;
855 gb.bhitpos.y -= stmp->dy;
856 stmp->stopped = TRUE;
857 } else if ((mtmp = m_at(gb.bhitpos.x, gb.bhitpos.y)) != 0) {
858 if (scflags & MAY_HITMON) {
859 stmp->range--;
860 if (ohitmon(mtmp, stmp->obj, 1, FALSE)) {
861 stmp->obj = (struct obj *) 0;
862 stmp->stopped = TRUE;
865 } else if (u_at(gb.bhitpos.x, gb.bhitpos.y)) {
866 if (scflags & MAY_HITYOU) {
867 int hitvalu, hitu;
869 if (gm.multi)
870 nomul(0);
871 hitvalu = 8 + stmp->obj->spe;
872 if (bigmonst(gy.youmonst.data))
873 hitvalu++;
874 hitu = thitu(hitvalu, dmgval(stmp->obj, &gy.youmonst),
875 &stmp->obj, (char *) 0);
876 if (!stmp->obj)
877 stmp->stopped = TRUE;
878 if (hitu) {
879 stmp->range -= 3;
880 stop_occupation();
883 } else {
884 if (scflags & VIS_EFFECTS) {
885 /* tmp_at(gb.bhitpos.x, gb.bhitpos.y); */
886 /* nh_delay_output(); */
889 stmp->ox = gb.bhitpos.x;
890 stmp->oy = gb.bhitpos.y;
891 if (IS_SINK(levl[stmp->ox][stmp->oy].typ))
892 stmp->stopped = TRUE;
893 gt.thrownobj = (struct obj *) 0;
897 for (stmp = schain; stmp; stmp = stmp2) {
898 coordxy x, y;
899 boolean obj_left_shop = FALSE;
901 stmp2 = stmp->next;
902 x = stmp->ox;
903 y = stmp->oy;
904 if (stmp->obj) {
905 if (x != sx || y != sy) {
906 total += stmp->obj->quan;
907 obj_left_shop = (shop_origin && !costly_spot(x, y));
909 if (!flooreffects(stmp->obj, x, y, "land")) {
910 if (obj_left_shop
911 && strchr(u.urooms, *in_rooms(u.ux, u.uy, SHOPBASE))) {
912 /* At the moment this only takes on gold. While it is
913 simple enough to call addtobill for other items that
914 leave the shop due to scatter(), by default the hero
915 will get billed for the full shopkeeper asking-price
916 on the object's way out of shop. That can leave the
917 hero in a pickle. Even if the hero then manages to
918 retrieve the item and drop it back inside the shop,
919 the owed charges will only be reduced at that point
920 by the lesser shopkeeper buying-price.
921 The non-gold situation will likely get adjusted
922 further.
924 if (stmp->obj->otyp == GOLD_PIECE) {
925 addtobill(stmp->obj, FALSE, FALSE, TRUE);
926 lostgoods = TRUE;
929 place_object(stmp->obj, x, y);
930 stackobj(stmp->obj);
933 free((genericptr_t) stmp);
934 newsym(x, y);
936 newsym(sx, sy);
937 if (u_at(sx, sy) && u.uundetected && hides_under(gy.youmonst.data))
938 (void) hideunder(&gy.youmonst);
939 if (((mtmp = m_at(sx, sy)) != 0) && mtmp->mtrapped)
940 mtmp->mtrapped = 0;
941 maybe_unhide_at(sx, sy);
942 if (lostgoods) /* implies shop_origin and therefore shkp valid */
943 credit_report(shkp, 1, FALSE);
944 return total;
948 * Splatter burning oil from x,y to the surrounding area.
950 * This routine should really take a how and direction parameters.
951 * The how is how it was caused, e.g. kicked verses thrown. The
952 * direction is which way to spread the flaming oil. Different
953 * "how"s would give different dispersal patterns. For example,
954 * kicking a burning flask will splatter differently from a thrown
955 * flask hitting the ground.
957 * For now, just perform a "regular" explosion.
959 void
960 splatter_burning_oil(coordxy x, coordxy y, boolean diluted_oil)
962 int dmg = d(diluted_oil ? 3 : 4, 4);
964 /* ZT_SPELL(ZT_FIRE) = ZT_SPELL(AD_FIRE-1) = 10+(2-1) = 11 */
965 #define ZT_SPELL_O_FIRE 11 /* value kludge, see zap.c */
966 explode(x, y, ZT_SPELL_O_FIRE, dmg, BURNING_OIL, EXPL_FIERY);
969 /* lit potion of oil is exploding; extinguish it as a light source before
970 possibly killing the hero and attempting to save bones */
971 void
972 explode_oil(struct obj *obj, coordxy x, coordxy y)
974 boolean diluted_oil = obj->odiluted;
976 if (!obj->lamplit)
977 impossible("exploding unlit oil");
978 end_burn(obj, TRUE);
979 obj->how_lost = LOST_EXPLODING;
980 splatter_burning_oil(x, y, diluted_oil);
983 /* Convert a damage type into an explosion display type. */
985 adtyp_to_expltype(const int adtyp)
987 switch(adtyp) {
988 case AD_ELEC:
989 /* Electricity isn't magical, but there currently isn't an electric
990 * explosion type. Magical is the next best thing. */
991 case AD_SPEL:
992 case AD_DREN:
993 case AD_ENCH:
994 return EXPL_MAGICAL;
995 case AD_FIRE:
996 return EXPL_FIERY;
997 case AD_COLD:
998 return EXPL_FROSTY;
999 case AD_DRST:
1000 case AD_DRDX:
1001 case AD_DRCO:
1002 case AD_DISE:
1003 case AD_PEST:
1004 case AD_PHYS: /* gas spore */
1005 return EXPL_NOXIOUS;
1006 default:
1007 impossible("adtyp_to_expltype: bad explosion type %d", adtyp);
1008 return EXPL_FIERY;
1012 /* A monster explodes in a way that produces a real explosion (e.g. a sphere
1013 * or gas spore, not a yellow light or similar).
1014 * This is some common code between explmu() and explmm().
1016 void
1017 mon_explodes(
1018 struct monst *mon,
1019 struct attack *mattk)
1021 int dmg;
1022 int type;
1023 if (mattk->damn) {
1024 dmg = d((int) mattk->damn, (int) mattk->damd);
1026 else if (mattk->damd) {
1027 dmg = d((int) mon->data->mlevel + 1, (int) mattk->damd);
1029 else {
1030 dmg = 0;
1033 if (mattk->adtyp == AD_PHYS) {
1034 type = PHYS_EXPL_TYPE;
1036 else if (mattk->adtyp >= AD_MAGM && mattk->adtyp <= AD_SPC2) {
1037 /* The -1, +20, *-1 math is to set it up as a 'monster breath' type
1038 * for the explosions (it isn't, but this is the closest analogue). */
1039 /* FIXME: there are macros for kind of thing... */
1040 type = -((mattk->adtyp - 1) + 20);
1042 else {
1043 impossible("unknown type for mon_explode %d", mattk->adtyp);
1044 return;
1047 /* Kill it now so it won't appear to be caught in its own explosion.
1048 * Must check to see if already dead - which happens if this is called
1049 * from an AT_BOOM attack upon death. */
1050 if (!DEADMONSTER(mon)) {
1051 mondead(mon);
1054 /* This might end up killing you, too; you never know...
1055 * also, it is used in explode() messages */
1056 Sprintf(svk.killer.name, "%s explosion",
1057 s_suffix(pmname(mon->data, Mgender(mon))));
1058 svk.killer.format = KILLED_BY_AN;
1060 explode(mon->mx, mon->my, type, dmg, MON_EXPLODE,
1061 adtyp_to_expltype(mattk->adtyp));
1063 /* reset killer */
1064 svk.killer.name[0] = '\0';
1067 /*explode.c*/