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
explode(x,y,type,dam,olet)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
scatter(sx,sy,blastforce,scflags,obj)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
splatter_burning_oil(x,y)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*/
552