1 | /* SCCS Id: @(#)explode.c 3.3 2000/07/07 */
2 | /* Copyright (C) 1990 by Ken Arromdee */
3 | /* NetHack may be freely redistributed. See license for details. */
4 |
5 | #include "hack.h"
6 |
7 | #ifdef OVL0
8 |
9 | /* Note: Arrays are column first, while the screen is row first */
10 | static int expl[3][3] = {
11 | { S_explode1, S_explode4, S_explode7 },
12 | { S_explode2, S_explode5, S_explode8 },
13 | { S_explode3, S_explode6, S_explode9 }
14 | };
15 |
16 | /* Note: I had to choose one of three possible kinds of "type" when writing
17 | * this function: a wand type (like in zap.c), an adtyp, or an object type.
18 | * Wand types get complex because they must be converted to adtyps for
19 | * determining such things as fire resistance. Adtyps get complex in that
20 | * they don't supply enough information--was it a player or a monster that
21 | * did it, and with a wand, spell, or breath weapon? Object types share both
22 | * these disadvantages....
23 | */
24 | void
25 | explode(x, y, type, dam, olet)
26 | int x, y;
27 | int type; /* the same as in zap.c */
28 | int dam;
29 | char olet;
30 | {
31 | int i, j, k, damu = dam;
32 | boolean starting = 1;
33 | boolean visible, any_shield;
34 | int uhurt = 0; /* 0=unhurt, 1=items damaged, 2=you and items damaged */
35 | const char *str;
36 | int idamres, idamnonres;
37 | struct monst *mtmp;
38 | uchar adtyp;
39 | int explmask[3][3];
40 | /* 0=normal explosion, 1=do shieldeff, 2=do nothing */
41 | boolean shopdamage = FALSE;
42 |
43 | if (olet == WAND_CLASS) /* retributive strike */
44 | switch (Role_switch) {
45 | case PM_PRIEST:
46 | case PM_MONK:
47 | case PM_WIZARD: damu /= 5;
48 | break;
49 | case PM_HEALER:
50 | case PM_KNIGHT: damu /= 2;
51 | break;
52 | default: break;
53 | }
54 |
55 | if (olet == MON_EXPLODE) {
56 | str = killer;
57 | killer = 0; /* set again later as needed */
58 | adtyp = AD_PHYS;
59 | } else
60 | switch (abs(type) % 10) {
61 | case 0: str = "magical blast";
62 | adtyp = AD_MAGM;
63 | break;
64 | case 1: str = olet == BURNING_OIL ? "burning oil" :
65 | olet == SCROLL_CLASS ? "tower of flame" :
66 | "fireball";
67 | adtyp = AD_FIRE;
68 | break;
69 | case 2: str = "ball of cold";
70 | adtyp = AD_COLD;
71 | break;
72 | case 4: str = (olet == WAND_CLASS) ? "death field" :
73 | "disintegration field";
74 | adtyp = AD_DISN;
75 | break;
76 | case 5: str = "ball of lightning";
77 | adtyp = AD_ELEC;
78 | break;
79 | case 6: str = "poison gas cloud";
80 | adtyp = AD_DRST;
81 | break;
82 | case 7: str = "splash of acid";
83 | adtyp = AD_ACID;
84 | break;
85 | default: impossible("explosion base type %d?", type); return;
86 | }
87 |
88 | any_shield = visible = FALSE;
89 | for (i=0; i<3; i++) for (j=0; j<3; j++) {
90 | if (!isok(i+x-1, j+y-1)) {
91 | explmask[i][j] = 2;
92 | continue;
93 | } else
94 | explmask[i][j] = 0;
95 |
96 | if (i+x-1 == u.ux && j+y-1 == u.uy) {
97 | switch(adtyp) {
98 | case AD_PHYS:
99 | explmask[i][j] = 0;
100 | break;
101 | case AD_MAGM:
102 | explmask[i][j] = !!Antimagic;
103 | break;
104 | case AD_FIRE:
105 | explmask[i][j] = !!Fire_resistance;
106 | break;
107 | case AD_COLD:
108 | explmask[i][j] = !!Cold_resistance;
109 | break;
110 | case AD_DISN:
111 | explmask[i][j] = (olet == WAND_CLASS) ?
112 | !!(nonliving(youmonst.data) || is_demon(youmonst.data)) :
113 | !!Disint_resistance;
114 | break;
115 | case AD_ELEC:
116 | explmask[i][j] = !!Shock_resistance;
117 | break;
118 | case AD_DRST:
119 | explmask[i][j] = !!Poison_resistance;
120 | break;
121 | case AD_ACID:
122 | explmask[i][j] = !!Acid_resistance;
123 | break;
124 | default:
125 | impossible("explosion type %d?", adtyp);
126 | break;
127 | }
128 | }
129 | /* can be both you and mtmp if you're swallowed */
130 | mtmp = m_at(i+x-1, j+y-1);
131 | #ifdef STEED
132 | if (!mtmp && i+x-1 == u.ux && j+y-1 == u.uy)
133 | mtmp = u.usteed;
134 | #endif
135 | if (mtmp) {
136 | if (mtmp->mhp < 1) explmask[i][j] = 2;
137 | else switch(adtyp) {
138 | case AD_PHYS:
139 | break;
140 | case AD_MAGM:
141 | explmask[i][j] |= resists_magm(mtmp);
142 | break;
143 | case AD_FIRE:
144 | explmask[i][j] |= resists_fire(mtmp);
145 | break;
146 | case AD_COLD:
147 | explmask[i][j] |= resists_cold(mtmp);
148 | break;
149 | case AD_DISN:
150 | explmask[i][j] |= (olet == WAND_CLASS) ?
151 | (nonliving(mtmp->data) || is_demon(mtmp->data)) :
152 | resists_disint(mtmp);
153 | break;
154 | case AD_ELEC:
155 | explmask[i][j] |= resists_elec(mtmp);
156 | break;
157 | case AD_DRST:
158 | explmask[i][j] |= resists_poison(mtmp);
159 | break;
160 | case AD_ACID:
161 | explmask[i][j] |= resists_acid(mtmp);
162 | break;
163 | default:
164 | impossible("explosion type %d?", adtyp);
165 | break;
166 | }
167 | }
168 | if (mtmp && cansee(i+x-1,j+y-1) && !canspotmon(mtmp))
169 | map_invisible(i+x-1, j+y-1);
170 | else if (!mtmp && glyph_is_invisible(levl[i+x-1][j+y-1].glyph)) {
171 | unmap_object(i+x-1, j+y-1);
172 | newsym(i+x-1, j+y-1);
173 | }
174 | if (cansee(i+x-1, j+y-1)) visible = TRUE;
175 | if (explmask[i][j] == 1) any_shield = TRUE;
176 | }
177 |
178 | if (visible) {
179 | /* Start the explosion */
180 | for (i=0; i<3; i++) for (j=0; j<3; j++) {
181 | if (explmask[i][j] == 2) continue;
182 | tmp_at(starting ? DISP_BEAM : DISP_CHANGE,
183 | cmap_to_glyph(expl[i][j]));
184 | tmp_at(i+x-1, j+y-1);
185 | starting = 0;
186 | }
187 | curs_on_u(); /* will flush screen and output */
188 |
189 | if (any_shield) { /* simulate a shield effect */
190 | for (k = 0; k < SHIELD_COUNT; k++) {
191 | for (i=0; i<3; i++) for (j=0; j<3; j++) {
192 | if (explmask[i][j] == 1)
193 | /*
194 | * Bypass tmp_at() and send the shield glyphs
195 | * directly to the buffered screen. tmp_at()
196 | * will clean up the location for us later.
197 | */
198 | show_glyph(i+x-1, j+y-1,
199 | cmap_to_glyph(shield_static[k]));
200 | }
201 | curs_on_u(); /* will flush screen and output */
202 | delay_output();
203 | }
204 |
205 | /* Cover last shield glyph with blast symbol. */
206 | for (i=0; i<3; i++) for (j=0; j<3; j++) {
207 | if (explmask[i][j] == 1)
208 | show_glyph(i+x-1,j+y-1,cmap_to_glyph(expl[i][j]));
209 | }
210 |
211 | } else { /* delay a little bit. */
212 | delay_output();
213 | delay_output();
214 | }
215 |
216 | tmp_at(DISP_END, 0); /* clear the explosion */
217 | } else {
218 | if (flags.soundok) You_hear("a blast.");
219 | }
220 |
221 | if (dam)
222 | for (i=0; i<3; i++) for (j=0; j<3; j++) {
223 | if (explmask[i][j] == 2) continue;
224 | if (i+x-1 == u.ux && j+y-1 == u.uy)
225 | uhurt = (explmask[i][j] == 1) ? 1 : 2;
226 | idamres = idamnonres = 0;
227 | if (type >= 0)
228 | (void)zap_over_floor((xchar)(i+x-1), (xchar)(j+y-1),
229 | type, &shopdamage);
230 |
231 | mtmp = m_at(i+x-1, j+y-1);
232 | #ifdef STEED
233 | if (!mtmp && i+x-1 == u.ux && j+y-1 == u.uy)
234 | mtmp = u.usteed;
235 | #endif
236 | if (!mtmp) continue;
237 | if (u.uswallow && mtmp == u.ustuck) {
238 | if (is_animal(u.ustuck->data))
239 | pline("%s gets %s!",
240 | Monnam(u.ustuck),
241 | (adtyp == AD_FIRE) ? "heartburn" :
242 | (adtyp == AD_COLD) ? "chilly" :
243 | (adtyp == AD_DISN) ? ((olet == WAND_CLASS) ?
244 | "irradiated by pure energy" : "perforated") :
245 | (adtyp == AD_ELEC) ? "shocked" :
246 | (adtyp == AD_DRST) ? "poisoned" :
247 | (adtyp == AD_ACID) ? "an upset stomach" :
248 | "fried");
249 | else
250 | pline("%s gets slightly %s!",
251 | Monnam(u.ustuck),
252 | (adtyp == AD_FIRE) ? "toasted" :
253 | (adtyp == AD_COLD) ? "chilly" :
254 | (adtyp == AD_DISN) ? ((olet == WAND_CLASS) ?
255 | "overwhelmed by pure energy" : "perforated") :
256 | (adtyp == AD_ELEC) ? "shocked" :
257 | (adtyp == AD_DRST) ? "intoxicated" :
258 | (adtyp == AD_ACID) ? "burned" :
259 | "fried");
260 | } else if (cansee(i+x-1, j+y-1))
261 | pline("%s is caught in the %s!", Monnam(mtmp), str);
262 |
263 | idamres += destroy_mitem(mtmp, SCROLL_CLASS, (int) adtyp);
264 | idamres += destroy_mitem(mtmp, SPBOOK_CLASS, (int) adtyp);
265 | idamnonres += destroy_mitem(mtmp, POTION_CLASS, (int) adtyp);
266 | idamnonres += destroy_mitem(mtmp, WAND_CLASS, (int) adtyp);
267 | idamnonres += destroy_mitem(mtmp, RING_CLASS, (int) adtyp);
268 |
269 | if (explmask[i][j] == 1) {
270 | golemeffects(mtmp, (int) adtyp, dam + idamres);
271 | mtmp->mhp -= idamnonres;
272 | } else {
273 | /* call resist with 0 and do damage manually so 1) we can
274 | * get out the message before doing the damage, and 2) we can
275 | * call mondied, not killed, if it's not your blast
276 | */
277 | int mdam = dam;
278 |
279 | if (resist(mtmp, olet, 0, FALSE)) {
280 | if (cansee(i+x-1,j+y-1))
281 | pline("%s resists the %s!", Monnam(mtmp), str);
282 | mdam = dam/2;
283 | }
284 | if (mtmp == u.ustuck)
285 | mdam *= 2;
286 | if (resists_cold(mtmp) && adtyp == AD_FIRE)
287 | mdam *= 2;
288 | else if (resists_fire(mtmp) && adtyp == AD_COLD)
289 | mdam *= 2;
290 | mtmp->mhp -= mdam;
291 | mtmp->mhp -= (idamres + idamnonres);
292 | }
293 | if (mtmp->mhp <= 0) {
294 | /* KMH -- Don't blame the player for pets killing gas spores */
295 | if (!flags.mon_moving) killed(mtmp);
296 | else monkilled(mtmp, "", (int)adtyp);
297 | }
298 | }
299 |
300 | /* Do your injury last */
301 | if (uhurt) {
302 | if ((type >= 0 || adtyp == AD_PHYS) && /* gas spores */
303 | flags.verbose && olet != SCROLL_CLASS)
304 | You("are caught in the %s!", str);
305 | /* do property damage first, in case we end up leaving bones */
306 | if (adtyp == AD_FIRE) burn_away_slime();
307 | if (Invulnerable) {
308 | damu = 0;
309 | You("are unharmed!");
310 | }
311 | if (adtyp == AD_FIRE) (void) burnarmor(&youmonst);
312 | destroy_item(SCROLL_CLASS, (int) adtyp);
313 | destroy_item(SPBOOK_CLASS, (int) adtyp);
314 | destroy_item(POTION_CLASS, (int) adtyp);
315 | destroy_item(RING_CLASS, (int) adtyp);
316 | destroy_item(WAND_CLASS, (int) adtyp);
317 |
318 | ugolemeffects((int) adtyp, damu);
319 | if (uhurt == 2) u.uhp -= damu, flags.botl = 1;
320 |
321 | if (u.uhp <= 0) {
322 | if (olet == MON_EXPLODE) {
323 | /* killer handled by caller */
324 | if (str != killer_buf)
325 | Strcpy(killer_buf, str);
326 | killer_format = KILLED_BY_AN;
327 | } else if (type >= 0 && olet != SCROLL_CLASS) {
328 | killer_format = NO_KILLER_PREFIX;
329 | Sprintf(killer_buf, "caught %sself in %s own %s",
330 | him[flags.female], his[flags.female], str);
331 | } else {
332 | killer_format = KILLED_BY;
333 | Strcpy(killer_buf, str);
334 | }
335 | killer = killer_buf;
336 | /* Known BUG: BURNING suppresses corpse in bones data,
337 | but done does not handle killer reason correctly */
338 | done((adtyp == AD_FIRE) ? BURNING : DIED);
339 | }
340 | exercise(A_STR, FALSE);
341 | }
342 |
343 | if (shopdamage) {
344 | pay_for_damage(adtyp == AD_FIRE ? "burn away" :
345 | adtyp == AD_COLD ? "shatter" :
346 | adtyp == AD_DISN ? "disintegrate" : "destroy");
347 | }
348 | }
349 | #endif /* OVL0 */
350 | #ifdef OVL1
351 |
352 | struct scatter_chain {
353 | struct scatter_chain *next; /* pointer to next scatter item */
354 | struct obj *obj; /* pointer to the object */
355 | xchar ox; /* location of */
356 | xchar oy; /* item */
357 | schar dx; /* direction of */
358 | schar dy; /* travel */
359 | int range; /* range of object */
360 | boolean stopped; /* flag for in-motion/stopped */
361 | };
362 |
363 | /*
364 | * scflags:
365 | * VIS_EFFECTS Add visual effects to display
366 | * MAY_HITMON Objects may hit monsters
367 | * MAY_HITYOU Objects may hit hero
368 | * MAY_HIT Objects may hit you or monsters
369 | * MAY_DESTROY Objects may be destroyed at random
370 | * MAY_FRACTURE Stone objects can be fractured (statues, boulders)
371 | */
372 |
373 | void
374 | scatter(sx,sy,blastforce,scflags, obj)
375 | int sx,sy; /* location of objects to scatter */
376 | int blastforce; /* force behind the scattering */
377 | unsigned int scflags;
378 | struct obj *obj; /* only scatter this obj */
379 | {
380 | register struct obj *otmp;
381 | register int tmp;
382 | int farthest = 0;
383 | uchar typ;
384 | long qtmp;
385 | boolean used_up;
386 | boolean split_up = FALSE;
387 | boolean individual_object = obj ? TRUE : FALSE;
388 | struct monst *mtmp;
389 | struct scatter_chain *stmp, *stmp2 = 0;
390 | struct scatter_chain *schain = (struct scatter_chain *)0;
391 |
392 | while ((otmp = individual_object ? obj : level.objects[sx][sy]) != 0) {
393 | if (otmp->quan > 1L) {
394 | qtmp = otmp->quan - 1;
395 | if (qtmp > LARGEST_INT) qtmp = LARGEST_INT;
396 | qtmp = (long)rnd((int)qtmp);
397 | (void) splitobj(otmp, qtmp);
398 | if (qtmp < otmp->quan)
399 | split_up = TRUE;
400 | else
401 | split_up = FALSE;
402 | }
403 | if (individual_object) {
404 | if (split_up) {
405 | if (otmp->where == OBJ_FLOOR)
406 | obj = otmp->nexthere;
407 | else
408 | obj = otmp->nobj;
409 | } else
410 | obj = (struct obj *)0;
411 | }
412 | obj_extract_self(otmp);
413 | used_up = FALSE;
414 |
415 | /* 9 in 10 chance of fracturing boulders or statues */
416 | if ((scflags & MAY_FRACTURE)
417 | && ((otmp->otyp == BOULDER) || (otmp->otyp == STATUE))
418 | && rn2(10)) {
419 | if (otmp->otyp == BOULDER) {
420 | pline("%s breaks apart.",The(xname(otmp)));
421 | fracture_rock(otmp);
422 | place_object(otmp, sx, sy); /* put fragments on floor */
423 | } else {
424 | struct trap *trap;
425 |
426 | if ((trap = t_at(sx,sy)) && trap->ttyp == STATUE_TRAP)
427 | deltrap(trap);
428 | pline("%s crumbles.",The(xname(otmp)));
429 | (void) break_statue(otmp);
430 | place_object(otmp, sx, sy); /* put fragments on floor */
431 | }
432 | used_up = TRUE;
433 |
434 | /* 1 in 10 chance of destruction of obj; glass, egg destruction */
435 | } else if ((scflags & MAY_DESTROY) && (!rn2(10)
436 | || (objects[otmp->otyp].oc_material == GLASS
437 | || otmp->otyp == EGG))) {
438 | if (breaks(otmp, (xchar)sx, (xchar)sy)) used_up = TRUE;
439 | }
440 |
441 | if (!used_up) {
442 | stmp = (struct scatter_chain *)
443 | alloc(sizeof(struct scatter_chain));
444 | stmp->next = (struct scatter_chain *)0;
445 | stmp->obj = otmp;
446 | stmp->ox = sx;
447 | stmp->oy = sy;
448 | tmp = rn2(8); /* get the direction */
449 | stmp->dx = xdir[tmp];
450 | stmp->dy = ydir[tmp];
451 | tmp = blastforce - (otmp->owt/40);
452 | if (tmp < 1) tmp = 1;
453 | stmp->range = rnd(tmp); /* anywhere up to that determ. by wt */
454 | if (farthest < stmp->range) farthest = stmp->range;
455 | stmp->stopped = FALSE;
456 | if (!schain)
457 | schain = stmp;
458 | else
459 | stmp2->next = stmp;
460 | stmp2 = stmp;
461 | }
462 | }
463 |
464 | while (farthest-- > 0) {
465 | for (stmp = schain; stmp; stmp = stmp->next) {
466 | if ((stmp->range-- > 0) && (!stmp->stopped)) {
467 | bhitpos.x = stmp->ox + stmp->dx;
468 | bhitpos.y = stmp->oy + stmp->dy;
469 | typ = levl[bhitpos.x][bhitpos.y].typ;
470 | if(!isok(bhitpos.x, bhitpos.y)) {
471 | bhitpos.x -= stmp->dx;
472 | bhitpos.y -= stmp->dy;
473 | stmp->stopped = TRUE;
474 | } else if(!ZAP_POS(typ) ||
475 | closed_door(bhitpos.x, bhitpos.y)) {
476 | bhitpos.x -= stmp->dx;
477 | bhitpos.y -= stmp->dy;
478 | stmp->stopped = TRUE;
479 | } else if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) {
480 | if (scflags & MAY_HITMON) {
481 | stmp->range--;
482 | if (ohitmon(mtmp, stmp->obj, 1, FALSE)) {
483 | stmp->obj = (struct obj *)0;
484 | stmp->stopped = TRUE;
485 | }
486 | }
487 | } else if (bhitpos.x==u.ux && bhitpos.y==u.uy) {
488 | if (scflags & MAY_HITYOU) {
489 | int hitvalu, hitu;
490 |
491 | if (multi) nomul(0);
492 | hitvalu = 8 + stmp->obj->spe;
493 | if (bigmonst(youmonst.data)) hitvalu++;
494 | hitu = thitu(hitvalu,
495 | dmgval(stmp->obj, &youmonst),
496 | stmp->obj, (char *)0);
497 | if (hitu) {
498 | stmp->range -= 3;
499 | stop_occupation();
500 | }
501 | }
502 | } else {
503 | if (scflags & VIS_EFFECTS) {
504 | /* tmp_at(bhitpos.x, bhitpos.y); */
505 | /* delay_output(); */
506 | }
507 | }
508 | stmp->ox = bhitpos.x;
509 | stmp->oy = bhitpos.y;
510 | }
511 | }
512 | }
513 | for (stmp = schain; stmp; stmp = stmp2) {
514 | int x,y;
515 |
516 | stmp2 = stmp->next;
517 | x = stmp->ox; y = stmp->oy;
518 | if (stmp->obj) {
519 | place_object(stmp->obj, x, y);
520 | stackobj(stmp->obj);
521 | }
522 | free((genericptr_t)stmp);
523 | newsym(x,y);
524 | }
525 | }
526 |
527 |
528 | /*
529 | * Splatter burning oil from x,y to the surrounding area.
530 | *
531 | * This routine should really take a how and direction parameters.
532 | * The how is how it was caused, e.g. kicked verses thrown. The
533 | * direction is which way to spread the flaming oil. Different
534 | * "how"s would give different dispersal patterns. For example,
535 | * kicking a burning flask will splatter differently from a thrown
536 | * flask hitting the ground.
537 | *
538 | * For now, just perform a "regular" explosion.
539 | */
540 | void
541 | splatter_burning_oil(x, y)
542 | int x, y;
543 | {
544 | /* ZT_SPELL(ZT_FIRE) = ZT_SPELL(AD_FIRE-1) = 10+(2-1) = 11 */
545 | #define ZT_SPELL_O_FIRE 11 /* value kludge, see zap.c */
546 | explode(x, y, ZT_SPELL_O_FIRE, d(4,4), BURNING_OIL);
547 | }
548 |
549 | #endif /* OVL1 */
550 |
551 | /*explode.c*/