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*/