1 /* NetHack 3.6 trap.c $NHDT-Date: 1576638501 2019/12/18 03:08:21 $ $NHDT-Branch: NetHack-3.6 $:$NHDT-Revision: 1.329 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2013. */
4 /* NetHack may be freely redistributed. See license for details. */
5
6 #include "hack.h"
7
8 extern const char *const destroy_strings[][3]; /* from zap.c */
9
10 STATIC_DCL boolean FDECL(keep_saddle_with_steedcorpse, (unsigned, struct obj *,
11 struct obj *));
12 STATIC_DCL struct obj *FDECL(t_missile, (int, struct trap *));
13 STATIC_DCL char *FDECL(trapnote, (struct trap *, BOOLEAN_P));
14 STATIC_DCL int FDECL(steedintrap, (struct trap *, struct obj *));
15 STATIC_DCL void FDECL(launch_drop_spot, (struct obj *, XCHAR_P, XCHAR_P));
16 STATIC_DCL int FDECL(mkroll_launch, (struct trap *, XCHAR_P, XCHAR_P,
17 SHORT_P, long));
18 STATIC_DCL boolean FDECL(isclearpath, (coord *, int, SCHAR_P, SCHAR_P));
19 STATIC_DCL void FDECL(dofiretrap, (struct obj *));
20 STATIC_DCL void NDECL(domagictrap);
21 STATIC_DCL boolean FDECL(emergency_disrobe, (boolean *));
22 STATIC_DCL int FDECL(untrap_prob, (struct trap *));
23 STATIC_DCL void FDECL(move_into_trap, (struct trap *));
24 STATIC_DCL int FDECL(try_disarm, (struct trap *, BOOLEAN_P));
25 STATIC_DCL void FDECL(reward_untrap, (struct trap *, struct monst *));
26 STATIC_DCL int FDECL(disarm_holdingtrap, (struct trap *));
27 STATIC_DCL int FDECL(disarm_landmine, (struct trap *));
28 STATIC_DCL int FDECL(disarm_squeaky_board, (struct trap *));
29 STATIC_DCL int FDECL(disarm_shooting_trap, (struct trap *, int));
30 STATIC_DCL void FDECL(clear_conjoined_pits, (struct trap *));
31 STATIC_DCL boolean FDECL(adj_nonconjoined_pit, (struct trap *));
32 STATIC_DCL int FDECL(try_lift, (struct monst *, struct trap *, int,
33 BOOLEAN_P));
34 STATIC_DCL int FDECL(help_monster_out, (struct monst *, struct trap *));
35 #if 0
36 STATIC_DCL void FDECL(join_adjacent_pits, (struct trap *));
37 #endif
38 STATIC_DCL boolean FDECL(thitm, (int, struct monst *, struct obj *, int,
39 BOOLEAN_P));
40 STATIC_DCL void NDECL(maybe_finish_sokoban);
41
42 /* mintrap() should take a flags argument, but for time being we use this */
43 STATIC_VAR int force_mintrap = 0;
44
45 STATIC_VAR const char *const a_your[2] = { "a", "your" };
46 STATIC_VAR const char *const A_Your[2] = { "A", "Your" };
47 STATIC_VAR const char tower_of_flame[] = "tower of flame";
48 STATIC_VAR const char *const A_gush_of_water_hits = "A gush of water hits";
49 STATIC_VAR const char *const blindgas[6] = { "humid", "odorless",
50 "pungent", "chilling",
51 "acrid", "biting" };
52
53 /* called when you're hit by fire (dofiretrap,buzz,zapyourself,explode);
54 returns TRUE if hit on torso */
55 boolean
burnarmor(victim)56 burnarmor(victim)
57 struct monst *victim;
58 {
59 struct obj *item;
60 char buf[BUFSZ];
61 int mat_idx, oldspe;
62 boolean hitting_u;
63
64 if (!victim)
65 return 0;
66 hitting_u = (victim == &youmonst);
67
68 /* burning damage may dry wet towel */
69 item = hitting_u ? carrying(TOWEL) : m_carrying(victim, TOWEL);
70 while (item) {
71 if (is_wet_towel(item)) {
72 oldspe = item->spe;
73 dry_a_towel(item, rn2(oldspe + 1), TRUE);
74 if (item->spe != oldspe)
75 break; /* stop once one towel has been affected */
76 }
77 item = item->nobj;
78 }
79
80 #define burn_dmg(obj, descr) erode_obj(obj, descr, ERODE_BURN, EF_GREASE)
81 while (1) {
82 switch (rn2(5)) {
83 case 0:
84 item = hitting_u ? uarmh : which_armor(victim, W_ARMH);
85 if (item) {
86 mat_idx = objects[item->otyp].oc_material;
87 Sprintf(buf, "%s %s", materialnm[mat_idx],
88 helm_simple_name(item));
89 }
90 if (!burn_dmg(item, item ? buf : "helmet"))
91 continue;
92 break;
93 case 1:
94 item = hitting_u ? uarmc : which_armor(victim, W_ARMC);
95 if (item) {
96 (void) burn_dmg(item, cloak_simple_name(item));
97 return TRUE;
98 }
99 item = hitting_u ? uarm : which_armor(victim, W_ARM);
100 if (item) {
101 (void) burn_dmg(item, xname(item));
102 return TRUE;
103 }
104 item = hitting_u ? uarmu : which_armor(victim, W_ARMU);
105 if (item)
106 (void) burn_dmg(item, "shirt");
107 return TRUE;
108 case 2:
109 item = hitting_u ? uarms : which_armor(victim, W_ARMS);
110 if (!burn_dmg(item, "wooden shield"))
111 continue;
112 break;
113 case 3:
114 item = hitting_u ? uarmg : which_armor(victim, W_ARMG);
115 if (!burn_dmg(item, "gloves"))
116 continue;
117 break;
118 case 4:
119 item = hitting_u ? uarmf : which_armor(victim, W_ARMF);
120 if (!burn_dmg(item, "boots"))
121 continue;
122 break;
123 }
124 break; /* Out of while loop */
125 }
126 #undef burn_dmg
127
128 return FALSE;
129 }
130
131 /* Generic erode-item function.
132 * "ostr", if non-null, is an alternate string to print instead of the
133 * object's name.
134 * "type" is an ERODE_* value for the erosion type
135 * "flags" is an or-ed list of EF_* flags
136 *
137 * Returns an erosion return value (ER_*)
138 */
139 int
erode_obj(otmp,ostr,type,ef_flags)140 erode_obj(otmp, ostr, type, ef_flags)
141 register struct obj *otmp;
142 const char *ostr;
143 int type;
144 int ef_flags;
145 {
146 static NEARDATA const char
147 *const action[] = { "smoulder", "rust", "rot", "corrode" },
148 *const msg[] = { "burnt", "rusted", "rotten", "corroded" },
149 *const bythe[] = { "heat", "oxidation", "decay", "corrosion" };
150 boolean vulnerable = FALSE, is_primary = TRUE,
151 check_grease = (ef_flags & EF_GREASE) ? TRUE : FALSE,
152 print = (ef_flags & EF_VERBOSE) ? TRUE : FALSE,
153 uvictim, vismon, visobj;
154 int erosion, cost_type;
155 struct monst *victim;
156
157 if (!otmp)
158 return ER_NOTHING;
159
160 victim = carried(otmp) ? &youmonst : mcarried(otmp) ? otmp->ocarry : NULL;
161 uvictim = (victim == &youmonst);
162 vismon = victim && (victim != &youmonst) && canseemon(victim);
163 /* Is bhitpos correct here? Ugh. */
164 visobj = !victim && cansee(bhitpos.x, bhitpos.y);
165
166 switch (type) {
167 case ERODE_BURN:
168 vulnerable = is_flammable(otmp);
169 check_grease = FALSE;
170 cost_type = COST_BURN;
171 break;
172 case ERODE_RUST:
173 vulnerable = is_rustprone(otmp);
174 cost_type = COST_RUST;
175 break;
176 case ERODE_ROT:
177 vulnerable = is_rottable(otmp);
178 check_grease = FALSE;
179 is_primary = FALSE;
180 cost_type = COST_ROT;
181 break;
182 case ERODE_CORRODE:
183 vulnerable = is_corrodeable(otmp);
184 is_primary = FALSE;
185 cost_type = COST_CORRODE;
186 break;
187 default:
188 impossible("Invalid erosion type in erode_obj");
189 return ER_NOTHING;
190 }
191 erosion = is_primary ? otmp->oeroded : otmp->oeroded2;
192
193 if (!ostr)
194 ostr = cxname(otmp);
195 /* 'visobj' messages insert "the"; probably ought to switch to the() */
196 if (visobj && !(uvictim || vismon) && !strncmpi(ostr, "the ", 4))
197 ostr += 4;
198
199 if (check_grease && otmp->greased) {
200 grease_protect(otmp, ostr, victim);
201 return ER_GREASED;
202 } else if (!erosion_matters(otmp)) {
203 return ER_NOTHING;
204 } else if (!vulnerable || (otmp->oerodeproof && otmp->rknown)) {
205 if (flags.verbose && print && (uvictim || vismon))
206 pline("%s %s %s not affected by %s.",
207 uvictim ? "Your" : s_suffix(Monnam(victim)),
208 ostr, vtense(ostr, "are"), bythe[type]);
209 return ER_NOTHING;
210 } else if (otmp->oerodeproof || (otmp->blessed && !rnl(4))) {
211 if (flags.verbose && (print || otmp->oerodeproof)
212 && (uvictim || vismon || visobj))
213 pline("Somehow, %s %s %s not affected by the %s.",
214 uvictim ? "your"
215 : !vismon ? "the" /* visobj */
216 : s_suffix(mon_nam(victim)),
217 ostr, vtense(ostr, "are"), bythe[type]);
218 /* We assume here that if the object is protected because it
219 * is blessed, it still shows some minor signs of wear, and
220 * the hero can distinguish this from an object that is
221 * actually proof against damage.
222 */
223 if (otmp->oerodeproof) {
224 otmp->rknown = TRUE;
225 if (victim == &youmonst)
226 update_inventory();
227 }
228
229 return ER_NOTHING;
230 } else if (erosion < MAX_ERODE) {
231 const char *adverb = (erosion + 1 == MAX_ERODE)
232 ? " completely"
233 : erosion ? " further" : "";
234
235 if (uvictim || vismon || visobj)
236 pline("%s %s %s%s!",
237 uvictim ? "Your"
238 : !vismon ? "The" /* visobj */
239 : s_suffix(Monnam(victim)),
240 ostr, vtense(ostr, action[type]), adverb);
241
242 if (ef_flags & EF_PAY)
243 costly_alteration(otmp, cost_type);
244
245 if (is_primary)
246 otmp->oeroded++;
247 else
248 otmp->oeroded2++;
249
250 if (victim == &youmonst)
251 update_inventory();
252
253 return ER_DAMAGED;
254 } else if (ef_flags & EF_DESTROY) {
255 if (uvictim || vismon || visobj)
256 pline("%s %s %s away!",
257 uvictim ? "Your"
258 : !vismon ? "The" /* visobj */
259 : s_suffix(Monnam(victim)),
260 ostr, vtense(ostr, action[type]));
261
262 if (ef_flags & EF_PAY)
263 costly_alteration(otmp, cost_type);
264
265 setnotworn(otmp);
266 delobj(otmp);
267 return ER_DESTROYED;
268 } else {
269 if (flags.verbose && print) {
270 if (uvictim)
271 Your("%s %s completely %s.",
272 ostr, vtense(ostr, Blind ? "feel" : "look"), msg[type]);
273 else if (vismon || visobj)
274 pline("%s %s %s completely %s.",
275 !vismon ? "The" : s_suffix(Monnam(victim)),
276 ostr, vtense(ostr, "look"), msg[type]);
277 }
278 return ER_NOTHING;
279 }
280 }
281
282 /* Protect an item from erosion with grease. Returns TRUE if the grease
283 * wears off.
284 */
285 boolean
grease_protect(otmp,ostr,victim)286 grease_protect(otmp, ostr, victim)
287 register struct obj *otmp;
288 const char *ostr;
289 struct monst *victim;
290 {
291 static const char txt[] = "protected by the layer of grease!";
292 boolean vismon = victim && (victim != &youmonst) && canseemon(victim);
293
294 if (ostr) {
295 if (victim == &youmonst)
296 Your("%s %s %s", ostr, vtense(ostr, "are"), txt);
297 else if (vismon)
298 pline("%s's %s %s %s", Monnam(victim),
299 ostr, vtense(ostr, "are"), txt);
300 } else if (victim == &youmonst || vismon) {
301 pline("%s %s", Yobjnam2(otmp, "are"), txt);
302 }
303 if (!rn2(2)) {
304 otmp->greased = 0;
305 if (carried(otmp)) {
306 pline_The("grease dissolves.");
307 update_inventory();
308 }
309 return TRUE;
310 }
311 return FALSE;
312 }
313
314 struct trap *
maketrap(x,y,typ)315 maketrap(x, y, typ)
316 int x, y, typ;
317 {
318 static union vlaunchinfo zero_vl;
319 boolean oldplace;
320 struct trap *ttmp;
321 struct rm *lev = &levl[x][y];
322
323 if ((ttmp = t_at(x, y)) != 0) {
324 if (ttmp->ttyp == MAGIC_PORTAL || ttmp->ttyp == VIBRATING_SQUARE)
325 return (struct trap *) 0;
326 oldplace = TRUE;
327 if (u.utrap && x == u.ux && y == u.uy
328 && ((u.utraptype == TT_BEARTRAP && typ != BEAR_TRAP)
329 || (u.utraptype == TT_WEB && typ != WEB)
330 || (u.utraptype == TT_PIT && !is_pit(typ))))
331 u.utrap = 0;
332 /* old <tx,ty> remain valid */
333 } else if (IS_FURNITURE(lev->typ)
334 && (!IS_GRAVE(lev->typ) || (typ != PIT && typ != HOLE))) {
335 /* no trap on top of furniture (caller usually screens the
336 location to inhibit this, but wizard mode wishing doesn't) */
337 return (struct trap *) 0;
338 } else {
339 oldplace = FALSE;
340 ttmp = newtrap();
341 (void) memset((genericptr_t)ttmp, 0, sizeof(struct trap));
342 ttmp->ntrap = 0;
343 ttmp->tx = x;
344 ttmp->ty = y;
345 }
346 /* [re-]initialize all fields except ntrap (handled below) and <tx,ty> */
347 ttmp->vl = zero_vl;
348 ttmp->launch.x = ttmp->launch.y = -1; /* force error if used before set */
349 ttmp->dst.dnum = ttmp->dst.dlevel = -1;
350 ttmp->madeby_u = 0;
351 ttmp->once = 0;
352 ttmp->tseen = (typ == HOLE); /* hide non-holes */
353 ttmp->ttyp = typ;
354
355 switch (typ) {
356 case SQKY_BOARD: {
357 int tavail[12], tpick[12], tcnt = 0, k;
358 struct trap *t;
359
360 for (k = 0; k < 12; ++k)
361 tavail[k] = tpick[k] = 0;
362 for (t = ftrap; t; t = t->ntrap)
363 if (t->ttyp == SQKY_BOARD && t != ttmp)
364 tavail[t->tnote] = 1;
365 /* now populate tpick[] with the available indices */
366 for (k = 0; k < 12; ++k)
367 if (tavail[k] == 0)
368 tpick[tcnt++] = k;
369 /* choose an unused note; if all are in use, pick a random one */
370 ttmp->tnote = (short) ((tcnt > 0) ? tpick[rn2(tcnt)] : rn2(12));
371 break;
372 }
373 case STATUE_TRAP: { /* create a "living" statue */
374 struct monst *mtmp;
375 struct obj *otmp, *statue;
376 struct permonst *mptr;
377 int trycount = 10;
378
379 do { /* avoid ultimately hostile co-aligned unicorn */
380 mptr = &mons[rndmonnum()];
381 } while (--trycount > 0 && is_unicorn(mptr)
382 && sgn(u.ualign.type) == sgn(mptr->maligntyp));
383 statue = mkcorpstat(STATUE, (struct monst *) 0, mptr, x, y,
384 CORPSTAT_NONE);
385 mtmp = makemon(&mons[statue->corpsenm], 0, 0, MM_NOCOUNTBIRTH);
386 if (!mtmp)
387 break; /* should never happen */
388 while (mtmp->minvent) {
389 otmp = mtmp->minvent;
390 otmp->owornmask = 0;
391 obj_extract_self(otmp);
392 (void) add_to_container(statue, otmp);
393 }
394 statue->owt = weight(statue);
395 mongone(mtmp);
396 break;
397 }
398 case ROLLING_BOULDER_TRAP: /* boulder will roll towards trigger */
399 (void) mkroll_launch(ttmp, x, y, BOULDER, 1L);
400 break;
401 case PIT:
402 case SPIKED_PIT:
403 ttmp->conjoined = 0;
404 /*FALLTHRU*/
405 case HOLE:
406 case TRAPDOOR:
407 if (*in_rooms(x, y, SHOPBASE)
408 && (is_hole(typ) || IS_DOOR(lev->typ) || IS_WALL(lev->typ)))
409 add_damage(x, y, /* schedule repair */
410 ((IS_DOOR(lev->typ) || IS_WALL(lev->typ))
411 && !context.mon_moving)
412 ? SHOP_HOLE_COST
413 : 0L);
414 lev->doormask = 0; /* subsumes altarmask, icedpool... */
415 if (IS_ROOM(lev->typ)) /* && !IS_AIR(lev->typ) */
416 lev->typ = ROOM;
417 /*
418 * some cases which can happen when digging
419 * down while phazing thru solid areas
420 */
421 else if (lev->typ == STONE || lev->typ == SCORR)
422 lev->typ = CORR;
423 else if (IS_WALL(lev->typ) || lev->typ == SDOOR)
424 lev->typ = level.flags.is_maze_lev
425 ? ROOM
426 : level.flags.is_cavernous_lev ? CORR : DOOR;
427
428 unearth_objs(x, y);
429 break;
430 }
431
432 if (!oldplace) {
433 ttmp->ntrap = ftrap;
434 ftrap = ttmp;
435 } else {
436 /* oldplace;
437 it shouldn't be possible to override a sokoban pit or hole
438 with some other trap, but we'll check just to be safe */
439 if (Sokoban)
440 maybe_finish_sokoban();
441 }
442 return ttmp;
443 }
444
445 void
fall_through(td,ftflags)446 fall_through(td, ftflags)
447 boolean td; /* td == TRUE : trap door or hole */
448 unsigned ftflags;
449 {
450 d_level dtmp;
451 char msgbuf[BUFSZ];
452 const char *dont_fall = 0;
453 int newlevel, bottom;
454 struct trap *t = (struct trap *) 0;
455
456 /* we'll fall even while levitating in Sokoban; otherwise, if we
457 won't fall and won't be told that we aren't falling, give up now */
458 if (Blind && Levitation && !Sokoban)
459 return;
460
461 bottom = dunlevs_in_dungeon(&u.uz);
462 /* when in the upper half of the quest, don't fall past the
463 middle "quest locate" level if hero hasn't been there yet */
464 if (In_quest(&u.uz)) {
465 int qlocate_depth = qlocate_level.dlevel;
466
467 /* deepest reached < qlocate implies current < qlocate */
468 if (dunlev_reached(&u.uz) < qlocate_depth)
469 bottom = qlocate_depth; /* early cut-off */
470 }
471 newlevel = dunlev(&u.uz); /* current level */
472 do {
473 newlevel++;
474 } while (!rn2(4) && newlevel < bottom);
475
476 if (td) {
477 t = t_at(u.ux, u.uy);
478 feeltrap(t);
479 if (!Sokoban && !(ftflags & TOOKPLUNGE)) {
480 if (t->ttyp == TRAPDOOR)
481 pline("A trap door opens up under you!");
482 else
483 pline("There's a gaping hole under you!");
484 }
485 } else
486 pline_The("%s opens up under you!", surface(u.ux, u.uy));
487
488 if (Sokoban && Can_fall_thru(&u.uz))
489 ; /* KMH -- You can't escape the Sokoban level traps */
490 else if (Levitation || u.ustuck
491 || (!Can_fall_thru(&u.uz) && !levl[u.ux][u.uy].candig)
492 || ((Flying || is_clinger(youmonst.data)
493 || (ceiling_hider(youmonst.data) && u.uundetected))
494 && !(ftflags & TOOKPLUNGE))
495 || (Inhell && !u.uevent.invoked && newlevel == bottom)) {
496 dont_fall = "don't fall in.";
497 } else if (youmonst.data->msize >= MZ_HUGE) {
498 dont_fall = "don't fit through.";
499 } else if (!next_to_u()) {
500 dont_fall = "are jerked back by your pet!";
501 }
502 if (dont_fall) {
503 You1(dont_fall);
504 /* hero didn't fall through, but any objects here might */
505 impact_drop((struct obj *) 0, u.ux, u.uy, 0);
506 if (!td) {
507 display_nhwindow(WIN_MESSAGE, FALSE);
508 pline_The("opening under you closes up.");
509 }
510 return;
511 }
512 if ((Flying || is_clinger(youmonst.data))
513 && (ftflags & TOOKPLUNGE) && td && t)
514 You("%s down %s!",
515 Flying ? "swoop" : "deliberately drop",
516 (t->ttyp == TRAPDOOR)
517 ? "through the trap door"
518 : "into the gaping hole");
519
520 if (*u.ushops)
521 shopdig(1);
522 if (Is_stronghold(&u.uz)) {
523 find_hell(&dtmp);
524 } else {
525 int dist = newlevel - dunlev(&u.uz);
526 dtmp.dnum = u.uz.dnum;
527 dtmp.dlevel = newlevel;
528 if (dist > 1)
529 You("fall down a %s%sshaft!", dist > 3 ? "very " : "",
530 dist > 2 ? "deep " : "");
531 }
532 if (!td)
533 Sprintf(msgbuf, "The hole in the %s above you closes up.",
534 ceiling(u.ux, u.uy));
535
536 schedule_goto(&dtmp, FALSE, TRUE, 0, (char *) 0,
537 !td ? msgbuf : (char *) 0);
538 }
539
540 /*
541 * Animate the given statue. May have been via shatter attempt, trap,
542 * or stone to flesh spell. Return a monster if successfully animated.
543 * If the monster is animated, the object is deleted. If fail_reason
544 * is non-null, then fill in the reason for failure (or success).
545 *
546 * The cause of animation is:
547 *
548 * ANIMATE_NORMAL - hero "finds" the monster
549 * ANIMATE_SHATTER - hero tries to destroy the statue
550 * ANIMATE_SPELL - stone to flesh spell hits the statue
551 *
552 * Perhaps x, y is not needed if we can use get_obj_location() to find
553 * the statue's location... ???
554 *
555 * Sequencing matters:
556 * create monster; if it fails, give up with statue intact;
557 * give "statue comes to life" message;
558 * if statue belongs to shop, have shk give "you owe" message;
559 * transfer statue contents to monster (after stolen_value());
560 * delete statue.
561 * [This ordering means that if the statue ends up wearing a cloak of
562 * invisibility or a mummy wrapping, the visibility checks might be
563 * wrong, but to avoid that we'd have to clone the statue contents
564 * first in order to give them to the monster before checking their
565 * shop status--it's not worth the hassle.]
566 */
567 struct monst *
animate_statue(statue,x,y,cause,fail_reason)568 animate_statue(statue, x, y, cause, fail_reason)
569 struct obj *statue;
570 xchar x, y;
571 int cause;
572 int *fail_reason;
573 {
574 int mnum = statue->corpsenm;
575 struct permonst *mptr = &mons[mnum];
576 struct monst *mon = 0, *shkp;
577 struct obj *item;
578 coord cc;
579 boolean historic = (Role_if(PM_ARCHEOLOGIST)
580 && (statue->spe & STATUE_HISTORIC) != 0),
581 golem_xform = FALSE, use_saved_traits;
582 const char *comes_to_life;
583 char statuename[BUFSZ], tmpbuf[BUFSZ];
584 static const char historic_statue_is_gone[] =
585 "that the historic statue is now gone";
586
587 if (cant_revive(&mnum, TRUE, statue)) {
588 /* mnum has changed; we won't be animating this statue as itself */
589 if (mnum != PM_DOPPELGANGER)
590 mptr = &mons[mnum];
591 use_saved_traits = FALSE;
592 } else if (is_golem(mptr) && cause == ANIMATE_SPELL) {
593 /* statue of any golem hit by stone-to-flesh becomes flesh golem */
594 golem_xform = (mptr != &mons[PM_FLESH_GOLEM]);
595 mnum = PM_FLESH_GOLEM;
596 mptr = &mons[PM_FLESH_GOLEM];
597 use_saved_traits = (has_omonst(statue) && !golem_xform);
598 } else {
599 use_saved_traits = has_omonst(statue);
600 }
601
602 if (use_saved_traits) {
603 /* restore a petrified monster */
604 cc.x = x, cc.y = y;
605 mon = montraits(statue, &cc, (cause == ANIMATE_SPELL));
606 if (mon && mon->mtame && !mon->isminion)
607 wary_dog(mon, TRUE);
608 } else {
609 /* statues of unique monsters from bones or wishing end
610 up here (cant_revive() sets mnum to be doppelganger;
611 mptr reflects the original form for use by newcham()) */
612 if ((mnum == PM_DOPPELGANGER && mptr != &mons[PM_DOPPELGANGER])
613 /* block quest guards from other roles */
614 || (mptr->msound == MS_GUARDIAN
615 && quest_info(MS_GUARDIAN) != mnum)) {
616 mon = makemon(&mons[PM_DOPPELGANGER], x, y,
617 NO_MINVENT | MM_NOCOUNTBIRTH | MM_ADJACENTOK);
618 /* if hero has protection from shape changers, cham field will
619 be NON_PM; otherwise, set form to match the statue */
620 if (mon && mon->cham >= LOW_PM)
621 (void) newcham(mon, mptr, FALSE, FALSE);
622 } else
623 mon = makemon(mptr, x, y, (cause == ANIMATE_SPELL)
624 ? (NO_MINVENT | MM_ADJACENTOK)
625 : NO_MINVENT);
626 }
627
628 if (!mon) {
629 if (fail_reason)
630 *fail_reason = unique_corpstat(&mons[statue->corpsenm])
631 ? AS_MON_IS_UNIQUE
632 : AS_NO_MON;
633 return (struct monst *) 0;
634 }
635
636 /* a non-montraits() statue might specify gender */
637 if (statue->spe & STATUE_MALE)
638 mon->female = FALSE;
639 else if (statue->spe & STATUE_FEMALE)
640 mon->female = TRUE;
641 /* if statue has been named, give same name to the monster */
642 if (has_oname(statue) && !unique_corpstat(mon->data))
643 mon = christen_monst(mon, ONAME(statue));
644 /* mimic statue becomes seen mimic; other hiders won't be hidden */
645 if (M_AP_TYPE(mon))
646 seemimic(mon);
647 else
648 mon->mundetected = FALSE;
649 mon->msleeping = 0;
650 if (cause == ANIMATE_NORMAL || cause == ANIMATE_SHATTER) {
651 /* trap always releases hostile monster */
652 mon->mtame = 0; /* (might be petrified pet tossed onto trap) */
653 mon->mpeaceful = 0;
654 set_malign(mon);
655 }
656
657 comes_to_life = !canspotmon(mon)
658 ? "disappears"
659 : golem_xform
660 ? "turns into flesh"
661 : (nonliving(mon->data) || is_vampshifter(mon))
662 ? "moves"
663 : "comes to life";
664 if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) {
665 /* "the|your|Manlobbi's statue [of a wombat]" */
666 shkp = shop_keeper(*in_rooms(mon->mx, mon->my, SHOPBASE));
667 Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue),
668 (cause == ANIMATE_SPELL
669 /* avoid "of a shopkeeper" if it's Manlobbi himself
670 (if carried, it can't be unpaid--hence won't be
671 described as "Manlobbi's statue"--because there
672 wasn't any living shk when statue was picked up) */
673 && (mon != shkp || carried(statue)))
674 ? xname(statue)
675 : "statue");
676 pline("%s %s!", upstart(statuename), comes_to_life);
677 } else if (Hallucination) { /* They don't know it's a statue */
678 pline_The("%s suddenly seems more animated.", rndmonnam((char *) 0));
679 } else if (cause == ANIMATE_SHATTER) {
680 if (cansee(x, y))
681 Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue),
682 xname(statue));
683 else
684 Strcpy(statuename, "a statue");
685 pline("Instead of shattering, %s suddenly %s!", statuename,
686 comes_to_life);
687 } else { /* cause == ANIMATE_NORMAL */
688 You("find %s posing as a statue.",
689 canspotmon(mon) ? a_monnam(mon) : something);
690 if (!canspotmon(mon) && Blind)
691 map_invisible(x, y);
692 stop_occupation();
693 }
694
695 /* if this isn't caused by a monster using a wand of striking,
696 there might be consequences for the hero */
697 if (!context.mon_moving) {
698 /* if statue is owned by a shop, hero will have to pay for it;
699 stolen_value gives a message (about debt or use of credit)
700 which refers to "it" so needs to follow a message describing
701 the object ("the statue comes to life" one above) */
702 if (cause != ANIMATE_NORMAL && costly_spot(x, y)
703 && (carried(statue) ? statue->unpaid : !statue->no_charge)
704 && (shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0
705 /* avoid charging for Manlobbi's statue of Manlobbi
706 if stone-to-flesh is used on petrified shopkeep */
707 && mon != shkp)
708 (void) stolen_value(statue, x, y, (boolean) shkp->mpeaceful,
709 FALSE);
710
711 if (historic) {
712 You_feel("guilty %s.", historic_statue_is_gone);
713 adjalign(-1);
714 }
715 } else {
716 if (historic && cansee(x, y))
717 You_feel("regret %s.", historic_statue_is_gone);
718 /* no alignment penalty */
719 }
720
721 /* transfer any statue contents to monster's inventory */
722 while ((item = statue->cobj) != 0) {
723 obj_extract_self(item);
724 (void) mpickobj(mon, item);
725 }
726 m_dowear(mon, TRUE);
727 /* in case statue is wielded and hero zaps stone-to-flesh at self */
728 if (statue->owornmask)
729 remove_worn_item(statue, TRUE);
730 /* statue no longer exists */
731 delobj(statue);
732
733 /* avoid hiding under nothing */
734 if (x == u.ux && y == u.uy && Upolyd && hides_under(youmonst.data)
735 && !OBJ_AT(x, y))
736 u.uundetected = 0;
737
738 if (fail_reason)
739 *fail_reason = AS_OK;
740 return mon;
741 }
742
743 /*
744 * You've either stepped onto a statue trap's location or you've triggered a
745 * statue trap by searching next to it or by trying to break it with a wand
746 * or pick-axe.
747 */
748 struct monst *
activate_statue_trap(trap,x,y,shatter)749 activate_statue_trap(trap, x, y, shatter)
750 struct trap *trap;
751 xchar x, y;
752 boolean shatter;
753 {
754 struct monst *mtmp = (struct monst *) 0;
755 struct obj *otmp = sobj_at(STATUE, x, y);
756 int fail_reason;
757
758 /*
759 * Try to animate the first valid statue. Stop the loop when we
760 * actually create something or the failure cause is not because
761 * the mon was unique.
762 */
763 deltrap(trap);
764 while (otmp) {
765 mtmp = animate_statue(otmp, x, y,
766 shatter ? ANIMATE_SHATTER : ANIMATE_NORMAL,
767 &fail_reason);
768 if (mtmp || fail_reason != AS_MON_IS_UNIQUE)
769 break;
770
771 otmp = nxtobj(otmp, STATUE, TRUE);
772 }
773
774 feel_newsym(x, y);
775 return mtmp;
776 }
777
778 STATIC_OVL boolean
keep_saddle_with_steedcorpse(steed_mid,objchn,saddle)779 keep_saddle_with_steedcorpse(steed_mid, objchn, saddle)
780 unsigned steed_mid;
781 struct obj *objchn, *saddle;
782 {
783 if (!saddle)
784 return FALSE;
785 while (objchn) {
786 if (objchn->otyp == CORPSE && has_omonst(objchn)) {
787 struct monst *mtmp = OMONST(objchn);
788
789 if (mtmp->m_id == steed_mid) {
790 /* move saddle */
791 xchar x, y;
792 if (get_obj_location(objchn, &x, &y, 0)) {
793 obj_extract_self(saddle);
794 place_object(saddle, x, y);
795 stackobj(saddle);
796 }
797 return TRUE;
798 }
799 }
800 if (Has_contents(objchn)
801 && keep_saddle_with_steedcorpse(steed_mid, objchn->cobj, saddle))
802 return TRUE;
803 objchn = objchn->nobj;
804 }
805 return FALSE;
806 }
807
808 /* monster or you go through and possibly destroy a web.
809 return TRUE if could go through. */
810 boolean
mu_maybe_destroy_web(mtmp,domsg,trap)811 mu_maybe_destroy_web(mtmp, domsg, trap)
812 struct monst *mtmp;
813 boolean domsg;
814 struct trap *trap;
815 {
816 boolean isyou = (mtmp == &youmonst);
817 struct permonst *mptr = mtmp->data;
818
819 if (amorphous(mptr) || is_whirly(mptr) || flaming(mptr)
820 || unsolid(mptr) || mptr == &mons[PM_GELATINOUS_CUBE]) {
821 xchar x = trap->tx;
822 xchar y = trap->ty;
823
824 if (flaming(mptr) || acidic(mptr)) {
825 if (domsg) {
826 if (isyou)
827 You("%s %s spider web!",
828 (flaming(mptr)) ? "burn" : "dissolve",
829 a_your[trap->madeby_u]);
830 else
831 pline("%s %s %s spider web!", Monnam(mtmp),
832 (flaming(mptr)) ? "burns" : "dissolves",
833 a_your[trap->madeby_u]);
834 }
835 deltrap(trap);
836 newsym(x, y);
837 return TRUE;
838 }
839 if (domsg) {
840 if (isyou) {
841 You("flow through %s spider web.", a_your[trap->madeby_u]);
842 } else {
843 pline("%s flows through %s spider web.", Monnam(mtmp),
844 a_your[trap->madeby_u]);
845 seetrap(trap);
846 }
847 }
848 return TRUE;
849 }
850 return FALSE;
851 }
852
853 /* make a single arrow/dart/rock for a trap to shoot or drop */
854 STATIC_OVL struct obj *
t_missile(otyp,trap)855 t_missile(otyp, trap)
856 int otyp;
857 struct trap *trap;
858 {
859 struct obj *otmp = mksobj(otyp, TRUE, FALSE);
860
861 otmp->quan = 1L;
862 otmp->owt = weight(otmp);
863 otmp->opoisoned = 0;
864 otmp->ox = trap->tx, otmp->oy = trap->ty;
865 return otmp;
866 }
867
868 void
set_utrap(tim,typ)869 set_utrap(tim, typ)
870 unsigned tim, typ;
871 {
872 u.utrap = tim;
873 /* FIXME:
874 * utraptype==0 is bear trap rather than 'none'; we probably ought
875 * to change that but can't do so until save file compatability is
876 * able to be broken.
877 */
878 u.utraptype = tim ? typ : 0;
879
880 float_vs_flight(); /* maybe block Lev and/or Fly */
881 }
882
883 void
reset_utrap(msg)884 reset_utrap(msg)
885 boolean msg;
886 {
887 boolean was_Lev = (Levitation != 0), was_Fly = (Flying != 0);
888
889 set_utrap(0, 0);
890
891 if (msg) {
892 if (!was_Lev && Levitation)
893 float_up();
894 if (!was_Fly && Flying)
895 You("can fly.");
896 }
897 }
898
899 void
dotrap(trap,trflags)900 dotrap(trap, trflags)
901 register struct trap *trap;
902 unsigned trflags;
903 {
904 register int ttype = trap->ttyp;
905 struct obj *otmp;
906 boolean already_seen = trap->tseen,
907 forcetrap = ((trflags & FORCETRAP) != 0
908 || (trflags & FAILEDUNTRAP) != 0),
909 webmsgok = (trflags & NOWEBMSG) == 0,
910 forcebungle = (trflags & FORCEBUNGLE) != 0,
911 plunged = (trflags & TOOKPLUNGE) != 0,
912 viasitting = (trflags & VIASITTING) != 0,
913 conj_pit = conjoined_pits(trap, t_at(u.ux0, u.uy0), TRUE),
914 adj_pit = adj_nonconjoined_pit(trap);
915 int oldumort;
916 int steed_article = ARTICLE_THE;
917
918 nomul(0);
919
920 /* KMH -- You can't escape the Sokoban level traps */
921 if (Sokoban && (is_pit(ttype) || is_hole(ttype))) {
922 /* The "air currents" message is still appropriate -- even when
923 * the hero isn't flying or levitating -- because it conveys the
924 * reason why the player cannot escape the trap with a dexterity
925 * check, clinging to the ceiling, etc.
926 */
927 pline("Air currents pull you down into %s %s!",
928 a_your[trap->madeby_u],
929 defsyms[trap_to_defsym(ttype)].explanation);
930 /* then proceed to normal trap effect */
931 } else if (already_seen && !forcetrap) {
932 if ((Levitation || (Flying && !plunged))
933 && (is_pit(ttype) || ttype == HOLE || ttype == BEAR_TRAP)) {
934 You("%s over %s %s.", Levitation ? "float" : "fly",
935 a_your[trap->madeby_u],
936 defsyms[trap_to_defsym(ttype)].explanation);
937 return;
938 }
939 if (!Fumbling && ttype != MAGIC_PORTAL && ttype != VIBRATING_SQUARE
940 && ttype != ANTI_MAGIC && !forcebungle && !plunged
941 && !conj_pit && !adj_pit
942 && (!rn2(5) || (is_pit(ttype)
943 && is_clinger(youmonst.data)))) {
944 You("escape %s %s.", (ttype == ARROW_TRAP && !trap->madeby_u)
945 ? "an"
946 : a_your[trap->madeby_u],
947 defsyms[trap_to_defsym(ttype)].explanation);
948 return;
949 }
950 }
951
952 if (u.usteed) {
953 u.usteed->mtrapseen |= (1 << (ttype - 1));
954 /* suppress article in various steed messages when using its
955 name (which won't occur when hallucinating) */
956 if (has_mname(u.usteed) && !Hallucination)
957 steed_article = ARTICLE_NONE;
958 }
959
960 switch (ttype) {
961 case ARROW_TRAP:
962 if (trap->once && trap->tseen && !rn2(15)) {
963 You_hear("a loud click!");
964 deltrap(trap);
965 newsym(u.ux, u.uy);
966 break;
967 }
968 trap->once = 1;
969 seetrap(trap);
970 pline("An arrow shoots out at you!");
971 otmp = t_missile(ARROW, trap);
972 if (u.usteed && !rn2(2) && steedintrap(trap, otmp)) {
973 ; /* nothing */
974 } else if (thitu(8, dmgval(otmp, &youmonst), &otmp, "arrow")) {
975 if (otmp)
976 obfree(otmp, (struct obj *) 0);
977 } else {
978 place_object(otmp, u.ux, u.uy);
979 if (!Blind)
980 otmp->dknown = 1;
981 stackobj(otmp);
982 newsym(u.ux, u.uy);
983 }
984 break;
985
986 case DART_TRAP:
987 if (trap->once && trap->tseen && !rn2(15)) {
988 You_hear("a soft click.");
989 deltrap(trap);
990 newsym(u.ux, u.uy);
991 break;
992 }
993 trap->once = 1;
994 seetrap(trap);
995 pline("A little dart shoots out at you!");
996 otmp = t_missile(DART, trap);
997 if (!rn2(6))
998 otmp->opoisoned = 1;
999 oldumort = u.umortality;
1000 if (u.usteed && !rn2(2) && steedintrap(trap, otmp)) {
1001 ; /* nothing */
1002 } else if (thitu(7, dmgval(otmp, &youmonst), &otmp, "little dart")) {
1003 if (otmp) {
1004 if (otmp->opoisoned)
1005 poisoned("dart", A_CON, "little dart",
1006 /* if damage triggered life-saving,
1007 poison is limited to attrib loss */
1008 (u.umortality > oldumort) ? 0 : 10, TRUE);
1009 obfree(otmp, (struct obj *) 0);
1010 }
1011 } else {
1012 place_object(otmp, u.ux, u.uy);
1013 if (!Blind)
1014 otmp->dknown = 1;
1015 stackobj(otmp);
1016 newsym(u.ux, u.uy);
1017 }
1018 break;
1019
1020 case ROCKTRAP:
1021 if (trap->once && trap->tseen && !rn2(15)) {
1022 pline("A trap door in %s opens, but nothing falls out!",
1023 the(ceiling(u.ux, u.uy)));
1024 deltrap(trap);
1025 newsym(u.ux, u.uy);
1026 } else {
1027 int dmg = d(2, 6); /* should be std ROCK dmg? */
1028
1029 trap->once = 1;
1030 feeltrap(trap);
1031 otmp = t_missile(ROCK, trap);
1032 place_object(otmp, u.ux, u.uy);
1033
1034 pline("A trap door in %s opens and %s falls on your %s!",
1035 the(ceiling(u.ux, u.uy)), an(xname(otmp)), body_part(HEAD));
1036 if (uarmh) {
1037 if (is_metallic(uarmh)) {
1038 pline("Fortunately, you are wearing a hard helmet.");
1039 dmg = 2;
1040 } else if (flags.verbose) {
1041 pline("%s does not protect you.", Yname2(uarmh));
1042 }
1043 }
1044 if (!Blind)
1045 otmp->dknown = 1;
1046 stackobj(otmp);
1047 newsym(u.ux, u.uy); /* map the rock */
1048
1049 losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN);
1050 exercise(A_STR, FALSE);
1051 }
1052 break;
1053
1054 case SQKY_BOARD: /* stepped on a squeaky board */
1055 if ((Levitation || Flying) && !forcetrap) {
1056 if (!Blind) {
1057 seetrap(trap);
1058 if (Hallucination)
1059 You("notice a crease in the linoleum.");
1060 else
1061 You("notice a loose board below you.");
1062 }
1063 } else {
1064 seetrap(trap);
1065 pline("A board beneath you %s%s%s.",
1066 Deaf ? "vibrates" : "squeaks ",
1067 Deaf ? "" : trapnote(trap, 0), Deaf ? "" : " loudly");
1068 wake_nearby();
1069 }
1070 break;
1071
1072 case BEAR_TRAP: {
1073 int dmg = d(2, 4);
1074
1075 if ((Levitation || Flying) && !forcetrap)
1076 break;
1077 feeltrap(trap);
1078 if (amorphous(youmonst.data) || is_whirly(youmonst.data)
1079 || unsolid(youmonst.data)) {
1080 pline("%s bear trap closes harmlessly through you.",
1081 A_Your[trap->madeby_u]);
1082 break;
1083 }
1084 if (!u.usteed && youmonst.data->msize <= MZ_SMALL) {
1085 pline("%s bear trap closes harmlessly over you.",
1086 A_Your[trap->madeby_u]);
1087 break;
1088 }
1089 set_utrap((unsigned) rn1(4, 4), TT_BEARTRAP);
1090 if (u.usteed) {
1091 pline("%s bear trap closes on %s %s!", A_Your[trap->madeby_u],
1092 s_suffix(mon_nam(u.usteed)), mbodypart(u.usteed, FOOT));
1093 if (thitm(0, u.usteed, (struct obj *) 0, dmg, FALSE))
1094 reset_utrap(TRUE); /* steed died, hero not trapped */
1095 } else {
1096 pline("%s bear trap closes on your %s!", A_Your[trap->madeby_u],
1097 body_part(FOOT));
1098 set_wounded_legs(rn2(2) ? RIGHT_SIDE : LEFT_SIDE, rn1(10, 10));
1099 if (u.umonnum == PM_OWLBEAR || u.umonnum == PM_BUGBEAR)
1100 You("howl in anger!");
1101 losehp(Maybe_Half_Phys(dmg), "bear trap", KILLED_BY_AN);
1102 }
1103 exercise(A_DEX, FALSE);
1104 break;
1105 }
1106
1107 case SLP_GAS_TRAP:
1108 seetrap(trap);
1109 if (Sleep_resistance || breathless(youmonst.data)) {
1110 You("are enveloped in a cloud of gas!");
1111 } else {
1112 pline("A cloud of gas puts you to sleep!");
1113 fall_asleep(-rnd(25), TRUE);
1114 }
1115 (void) steedintrap(trap, (struct obj *) 0);
1116 break;
1117
1118 case RUST_TRAP:
1119 seetrap(trap);
1120
1121 /* Unlike monsters, traps cannot aim their rust attacks at
1122 * you, so instead of looping through and taking either the
1123 * first rustable one or the body, we take whatever we get,
1124 * even if it is not rustable.
1125 */
1126 switch (rn2(5)) {
1127 case 0:
1128 pline("%s you on the %s!", A_gush_of_water_hits, body_part(HEAD));
1129 (void) water_damage(uarmh, helm_simple_name(uarmh), TRUE);
1130 break;
1131 case 1:
1132 pline("%s your left %s!", A_gush_of_water_hits, body_part(ARM));
1133 if (water_damage(uarms, "shield", TRUE) != ER_NOTHING)
1134 break;
1135 if (u.twoweap || (uwep && bimanual(uwep)))
1136 (void) water_damage(u.twoweap ? uswapwep : uwep, 0, TRUE);
1137 glovecheck:
1138 (void) water_damage(uarmg, "gauntlets", TRUE);
1139 /* Not "metal gauntlets" since it gets called
1140 * even if it's leather for the message
1141 */
1142 break;
1143 case 2:
1144 pline("%s your right %s!", A_gush_of_water_hits, body_part(ARM));
1145 (void) water_damage(uwep, 0, TRUE);
1146 goto glovecheck;
1147 default:
1148 pline("%s you!", A_gush_of_water_hits);
1149 for (otmp = invent; otmp; otmp = otmp->nobj)
1150 if (otmp->lamplit && otmp != uwep
1151 && (otmp != uswapwep || !u.twoweap))
1152 (void) snuff_lit(otmp);
1153 if (uarmc)
1154 (void) water_damage(uarmc, cloak_simple_name(uarmc), TRUE);
1155 else if (uarm)
1156 (void) water_damage(uarm, suit_simple_name(uarm), TRUE);
1157 else if (uarmu)
1158 (void) water_damage(uarmu, "shirt", TRUE);
1159 }
1160 update_inventory();
1161
1162 if (u.umonnum == PM_IRON_GOLEM) {
1163 int dam = u.mhmax;
1164
1165 You("are covered with rust!");
1166 losehp(Maybe_Half_Phys(dam), "rusting away", KILLED_BY);
1167 } else if (u.umonnum == PM_GREMLIN && rn2(3)) {
1168 (void) split_mon(&youmonst, (struct monst *) 0);
1169 }
1170
1171 break;
1172
1173 case FIRE_TRAP:
1174 seetrap(trap);
1175 dofiretrap((struct obj *) 0);
1176 break;
1177
1178 case PIT:
1179 case SPIKED_PIT:
1180 /* KMH -- You can't escape the Sokoban level traps */
1181 if (!Sokoban && (Levitation || (Flying && !plunged)))
1182 break;
1183 feeltrap(trap);
1184 if (!Sokoban && is_clinger(youmonst.data) && !plunged) {
1185 if (trap->tseen) {
1186 You_see("%s %spit below you.", a_your[trap->madeby_u],
1187 ttype == SPIKED_PIT ? "spiked " : "");
1188 } else {
1189 pline("%s pit %sopens up under you!", A_Your[trap->madeby_u],
1190 ttype == SPIKED_PIT ? "full of spikes " : "");
1191 You("don't fall in!");
1192 }
1193 break;
1194 }
1195 if (!Sokoban) {
1196 char verbbuf[BUFSZ];
1197
1198 *verbbuf = '\0';
1199 if (u.usteed) {
1200 if ((trflags & RECURSIVETRAP) != 0)
1201 Sprintf(verbbuf, "and %s fall",
1202 x_monnam(u.usteed, steed_article, (char *) 0,
1203 SUPPRESS_SADDLE, FALSE));
1204 else
1205 Sprintf(verbbuf, "lead %s",
1206 x_monnam(u.usteed, steed_article, "poor",
1207 SUPPRESS_SADDLE, FALSE));
1208 } else if (conj_pit) {
1209 You("move into an adjacent pit.");
1210 } else if (adj_pit) {
1211 You("stumble over debris%s.",
1212 !rn2(5) ? " between the pits" : "");
1213 } else {
1214 Strcpy(verbbuf,
1215 !plunged ? "fall" : (Flying ? "dive" : "plunge"));
1216 }
1217 if (*verbbuf)
1218 You("%s into %s pit!", verbbuf, a_your[trap->madeby_u]);
1219 }
1220 /* wumpus reference */
1221 if (Role_if(PM_RANGER) && !trap->madeby_u && !trap->once
1222 && In_quest(&u.uz) && Is_qlocate(&u.uz)) {
1223 pline("Fortunately it has a bottom after all...");
1224 trap->once = 1;
1225 } else if (u.umonnum == PM_PIT_VIPER || u.umonnum == PM_PIT_FIEND) {
1226 pline("How pitiful. Isn't that the pits?");
1227 }
1228 if (ttype == SPIKED_PIT) {
1229 const char *predicament = "on a set of sharp iron spikes";
1230
1231 if (u.usteed) {
1232 pline("%s %s %s!",
1233 upstart(x_monnam(u.usteed, steed_article, "poor",
1234 SUPPRESS_SADDLE, FALSE)),
1235 conj_pit ? "steps" : "lands", predicament);
1236 } else
1237 You("%s %s!", conj_pit ? "step" : "land", predicament);
1238 }
1239 /* FIXME:
1240 * if hero gets killed here, setting u.utrap in advance will
1241 * show "you were trapped in a pit" during disclosure's display
1242 * of enlightenment, but hero is dying *before* becoming trapped.
1243 */
1244 set_utrap((unsigned) rn1(6, 2), TT_PIT);
1245 if (!steedintrap(trap, (struct obj *) 0)) {
1246 if (ttype == SPIKED_PIT) {
1247 oldumort = u.umortality;
1248 losehp(Maybe_Half_Phys(rnd(conj_pit ? 4 : adj_pit ? 6 : 10)),
1249 /* note: these don't need locomotion() handling;
1250 if fatal while poly'd and Unchanging, the
1251 death reason will be overridden with
1252 "killed while stuck in creature form" */
1253 plunged
1254 ? "deliberately plunged into a pit of iron spikes"
1255 : conj_pit
1256 ? "stepped into a pit of iron spikes"
1257 : adj_pit
1258 ? "stumbled into a pit of iron spikes"
1259 : "fell into a pit of iron spikes",
1260 NO_KILLER_PREFIX);
1261 if (!rn2(6))
1262 poisoned("spikes", A_STR,
1263 (conj_pit || adj_pit)
1264 ? "stepping on poison spikes"
1265 : "fall onto poison spikes",
1266 /* if damage triggered life-saving,
1267 poison is limited to attrib loss */
1268 (u.umortality > oldumort) ? 0 : 8, FALSE);
1269 } else {
1270 /* plunging flyers take spike damage but not pit damage */
1271 if (!conj_pit
1272 && !(plunged && (Flying || is_clinger(youmonst.data))))
1273 losehp(Maybe_Half_Phys(rnd(adj_pit ? 3 : 6)),
1274 plunged ? "deliberately plunged into a pit"
1275 : "fell into a pit",
1276 NO_KILLER_PREFIX);
1277 }
1278 if (Punished && !carried(uball)) {
1279 unplacebc();
1280 ballfall();
1281 placebc();
1282 }
1283 if (!conj_pit)
1284 selftouch("Falling, you");
1285 vision_full_recalc = 1; /* vision limits change */
1286 exercise(A_STR, FALSE);
1287 exercise(A_DEX, FALSE);
1288 }
1289 break;
1290
1291 case HOLE:
1292 case TRAPDOOR:
1293 if (!Can_fall_thru(&u.uz)) {
1294 seetrap(trap); /* normally done in fall_through */
1295 impossible("dotrap: %ss cannot exist on this level.",
1296 defsyms[trap_to_defsym(ttype)].explanation);
1297 break; /* don't activate it after all */
1298 }
1299 fall_through(TRUE, (trflags & TOOKPLUNGE));
1300 break;
1301
1302 case TELEP_TRAP:
1303 seetrap(trap);
1304 tele_trap(trap);
1305 break;
1306
1307 case LEVEL_TELEP:
1308 seetrap(trap);
1309 level_tele_trap(trap, trflags);
1310 break;
1311
1312 case WEB: /* Our luckless player has stumbled into a web. */
1313 feeltrap(trap);
1314 if (mu_maybe_destroy_web(&youmonst, webmsgok, trap))
1315 break;
1316 if (webmaker(youmonst.data)) {
1317 if (webmsgok)
1318 pline(trap->madeby_u ? "You take a walk on your web."
1319 : "There is a spider web here.");
1320 break;
1321 }
1322 if (webmsgok) {
1323 char verbbuf[BUFSZ];
1324
1325 if (forcetrap || viasitting) {
1326 Strcpy(verbbuf, "are caught by");
1327 } else if (u.usteed) {
1328 Sprintf(verbbuf, "lead %s into",
1329 x_monnam(u.usteed, steed_article, "poor",
1330 SUPPRESS_SADDLE, FALSE));
1331 } else {
1332 Sprintf(verbbuf, "%s into",
1333 Levitation ? (const char *) "float"
1334 : locomotion(youmonst.data, "stumble"));
1335 }
1336 You("%s %s spider web!", verbbuf, a_your[trap->madeby_u]);
1337 }
1338
1339 /* time will be adjusted below */
1340 set_utrap(1, TT_WEB);
1341
1342 /* Time stuck in the web depends on your/steed strength. */
1343 {
1344 int tim, str = ACURR(A_STR);
1345
1346 /* If mounted, the steed gets trapped. Use mintrap
1347 * to do all the work. If mtrapped is set as a result,
1348 * unset it and set utrap instead. In the case of a
1349 * strongmonst and mintrap said it's trapped, use a
1350 * short but non-zero trap time. Otherwise, monsters
1351 * have no specific strength, so use player strength.
1352 * This gets skipped for webmsgok, which implies that
1353 * the steed isn't a factor.
1354 */
1355 if (u.usteed && webmsgok) {
1356 /* mtmp location might not be up to date */
1357 u.usteed->mx = u.ux;
1358 u.usteed->my = u.uy;
1359
1360 /* mintrap currently does not return 2(died) for webs */
1361 if (mintrap(u.usteed)) {
1362 u.usteed->mtrapped = 0;
1363 if (strongmonst(u.usteed->data))
1364 str = 17;
1365 } else {
1366 reset_utrap(FALSE);
1367 break;
1368 }
1369
1370 webmsgok = FALSE; /* mintrap printed the messages */
1371 }
1372 if (str <= 3)
1373 tim = rn1(6, 6);
1374 else if (str < 6)
1375 tim = rn1(6, 4);
1376 else if (str < 9)
1377 tim = rn1(4, 4);
1378 else if (str < 12)
1379 tim = rn1(4, 2);
1380 else if (str < 15)
1381 tim = rn1(2, 2);
1382 else if (str < 18)
1383 tim = rnd(2);
1384 else if (str < 69)
1385 tim = 1;
1386 else {
1387 tim = 0;
1388 if (webmsgok)
1389 You("tear through %s web!", a_your[trap->madeby_u]);
1390 deltrap(trap);
1391 newsym(u.ux, u.uy); /* get rid of trap symbol */
1392 }
1393 set_utrap((unsigned) tim, TT_WEB);
1394 }
1395 break;
1396
1397 case STATUE_TRAP:
1398 (void) activate_statue_trap(trap, u.ux, u.uy, FALSE);
1399 break;
1400
1401 case MAGIC_TRAP: /* A magic trap. */
1402 seetrap(trap);
1403 if (!rn2(30)) {
1404 deltrap(trap);
1405 newsym(u.ux, u.uy); /* update position */
1406 You("are caught in a magical explosion!");
1407 losehp(rnd(10), "magical explosion", KILLED_BY_AN);
1408 Your("body absorbs some of the magical energy!");
1409 u.uen = (u.uenmax += 2);
1410 break;
1411 } else {
1412 domagictrap();
1413 }
1414 (void) steedintrap(trap, (struct obj *) 0);
1415 break;
1416
1417 case ANTI_MAGIC:
1418 seetrap(trap);
1419 /* hero without magic resistance loses spell energy,
1420 hero with magic resistance takes damage instead;
1421 possibly non-intuitive but useful for play balance */
1422 if (!Antimagic) {
1423 drain_en(rnd(u.ulevel) + 1);
1424 } else {
1425 int dmgval2 = rnd(4), hp = Upolyd ? u.mh : u.uhp;
1426
1427 /* Half_XXX_damage has opposite its usual effect (approx)
1428 but isn't cumulative if hero has more than one */
1429 if (Half_physical_damage || Half_spell_damage)
1430 dmgval2 += rnd(4);
1431 /* give Magicbane wielder dose of own medicine */
1432 if (uwep && uwep->oartifact == ART_MAGICBANE)
1433 dmgval2 += rnd(4);
1434 /* having an artifact--other than own quest one--which
1435 confers magic resistance simply by being carried
1436 also increases the effect */
1437 for (otmp = invent; otmp; otmp = otmp->nobj)
1438 if (otmp->oartifact && !is_quest_artifact(otmp)
1439 && defends_when_carried(AD_MAGM, otmp))
1440 break;
1441 if (otmp)
1442 dmgval2 += rnd(4);
1443 if (Passes_walls)
1444 dmgval2 = (dmgval2 + 3) / 4;
1445
1446 You_feel((dmgval2 >= hp) ? "unbearably torpid!"
1447 : (dmgval2 >= hp / 4) ? "very lethargic."
1448 : "sluggish.");
1449 /* opposite of magical explosion */
1450 losehp(dmgval2, "anti-magic implosion", KILLED_BY_AN);
1451 }
1452 break;
1453
1454 case POLY_TRAP: {
1455 char verbbuf[BUFSZ];
1456
1457 seetrap(trap);
1458 if (viasitting)
1459 Strcpy(verbbuf, "trigger"); /* follows "You sit down." */
1460 else if (u.usteed)
1461 Sprintf(verbbuf, "lead %s onto",
1462 x_monnam(u.usteed, steed_article, (char *) 0,
1463 SUPPRESS_SADDLE, FALSE));
1464 else
1465 Sprintf(verbbuf, "%s onto",
1466 Levitation ? (const char *) "float"
1467 : locomotion(youmonst.data, "step"));
1468 You("%s a polymorph trap!", verbbuf);
1469 if (Antimagic || Unchanging) {
1470 shieldeff(u.ux, u.uy);
1471 You_feel("momentarily different.");
1472 /* Trap did nothing; don't remove it --KAA */
1473 } else {
1474 (void) steedintrap(trap, (struct obj *) 0);
1475 deltrap(trap); /* delete trap before polymorph */
1476 newsym(u.ux, u.uy); /* get rid of trap symbol */
1477 You_feel("a change coming over you.");
1478 polyself(0);
1479 }
1480 break;
1481 }
1482 case LANDMINE: {
1483 unsigned steed_mid = 0;
1484 struct obj *saddle = 0;
1485
1486 if ((Levitation || Flying) && !forcetrap) {
1487 if (!already_seen && rn2(3))
1488 break;
1489 feeltrap(trap);
1490 pline("%s %s in a pile of soil below you.",
1491 already_seen ? "There is" : "You discover",
1492 trap->madeby_u ? "the trigger of your mine" : "a trigger");
1493 if (already_seen && rn2(3))
1494 break;
1495 pline("KAABLAMM!!! %s %s%s off!",
1496 forcebungle ? "Your inept attempt sets"
1497 : "The air currents set",
1498 already_seen ? a_your[trap->madeby_u] : "",
1499 already_seen ? " land mine" : "it");
1500 } else {
1501 /* prevent landmine from killing steed, throwing you to
1502 * the ground, and you being affected again by the same
1503 * mine because it hasn't been deleted yet
1504 */
1505 static boolean recursive_mine = FALSE;
1506
1507 if (recursive_mine)
1508 break;
1509 feeltrap(trap);
1510 pline("KAABLAMM!!! You triggered %s land mine!",
1511 a_your[trap->madeby_u]);
1512 if (u.usteed)
1513 steed_mid = u.usteed->m_id;
1514 recursive_mine = TRUE;
1515 (void) steedintrap(trap, (struct obj *) 0);
1516 recursive_mine = FALSE;
1517 saddle = sobj_at(SADDLE, u.ux, u.uy);
1518 set_wounded_legs(LEFT_SIDE, rn1(35, 41));
1519 set_wounded_legs(RIGHT_SIDE, rn1(35, 41));
1520 exercise(A_DEX, FALSE);
1521 }
1522 blow_up_landmine(trap);
1523 if (steed_mid && saddle && !u.usteed)
1524 (void) keep_saddle_with_steedcorpse(steed_mid, fobj, saddle);
1525 newsym(u.ux, u.uy); /* update trap symbol */
1526 losehp(Maybe_Half_Phys(rnd(16)), "land mine", KILLED_BY_AN);
1527 /* fall recursively into the pit... */
1528 if ((trap = t_at(u.ux, u.uy)) != 0)
1529 dotrap(trap, RECURSIVETRAP);
1530 fill_pit(u.ux, u.uy);
1531 break;
1532 }
1533
1534 case ROLLING_BOULDER_TRAP: {
1535 int style = ROLL | (trap->tseen ? LAUNCH_KNOWN : 0);
1536
1537 feeltrap(trap);
1538 pline("Click! You trigger a rolling boulder trap!");
1539 if (!launch_obj(BOULDER, trap->launch.x, trap->launch.y,
1540 trap->launch2.x, trap->launch2.y, style)) {
1541 deltrap(trap);
1542 newsym(u.ux, u.uy); /* get rid of trap symbol */
1543 pline("Fortunately for you, no boulder was released.");
1544 }
1545 break;
1546 }
1547
1548 case MAGIC_PORTAL:
1549 feeltrap(trap);
1550 domagicportal(trap);
1551 break;
1552
1553 case VIBRATING_SQUARE:
1554 feeltrap(trap);
1555 /* messages handled elsewhere; the trap symbol is merely to mark the
1556 * square for future reference */
1557 break;
1558
1559 default:
1560 feeltrap(trap);
1561 impossible("You hit a trap of type %u", trap->ttyp);
1562 }
1563 }
1564
1565 STATIC_OVL char *
trapnote(trap,noprefix)1566 trapnote(trap, noprefix)
1567 struct trap *trap;
1568 boolean noprefix;
1569 {
1570 static char tnbuf[12];
1571 const char *tn,
1572 *tnnames[12] = { "C note", "D flat", "D note", "E flat",
1573 "E note", "F note", "F sharp", "G note",
1574 "G sharp", "A note", "B flat", "B note" };
1575
1576 tnbuf[0] = '\0';
1577 tn = tnnames[trap->tnote];
1578 if (!noprefix)
1579 Sprintf(tnbuf, "%s ",
1580 (*tn == 'A' || *tn == 'E' || *tn == 'F') ? "an" : "a");
1581 Sprintf(eos(tnbuf), "%s", tn);
1582 return tnbuf;
1583 }
1584
1585 STATIC_OVL int
steedintrap(trap,otmp)1586 steedintrap(trap, otmp)
1587 struct trap *trap;
1588 struct obj *otmp;
1589 {
1590 struct monst *steed = u.usteed;
1591 int tt;
1592 boolean trapkilled, steedhit;
1593
1594 if (!steed || !trap)
1595 return 0;
1596 tt = trap->ttyp;
1597 steed->mx = u.ux;
1598 steed->my = u.uy;
1599 trapkilled = steedhit = FALSE;
1600
1601 switch (tt) {
1602 case ARROW_TRAP:
1603 if (!otmp) {
1604 impossible("steed hit by non-existent arrow?");
1605 return 0;
1606 }
1607 trapkilled = thitm(8, steed, otmp, 0, FALSE);
1608 steedhit = TRUE;
1609 break;
1610 case DART_TRAP:
1611 if (!otmp) {
1612 impossible("steed hit by non-existent dart?");
1613 return 0;
1614 }
1615 trapkilled = thitm(7, steed, otmp, 0, FALSE);
1616 steedhit = TRUE;
1617 break;
1618 case SLP_GAS_TRAP:
1619 if (!resists_sleep(steed) && !breathless(steed->data)
1620 && !steed->msleeping && steed->mcanmove) {
1621 if (sleep_monst(steed, rnd(25), -1))
1622 /* no in_sight check here; you can feel it even if blind */
1623 pline("%s suddenly falls asleep!", Monnam(steed));
1624 }
1625 steedhit = TRUE;
1626 break;
1627 case LANDMINE:
1628 trapkilled = thitm(0, steed, (struct obj *) 0, rnd(16), FALSE);
1629 steedhit = TRUE;
1630 break;
1631 case PIT:
1632 case SPIKED_PIT:
1633 trapkilled = (DEADMONSTER(steed)
1634 || thitm(0, steed, (struct obj *) 0,
1635 rnd((tt == PIT) ? 6 : 10), FALSE));
1636 steedhit = TRUE;
1637 break;
1638 case POLY_TRAP:
1639 if (!resists_magm(steed) && !resist(steed, WAND_CLASS, 0, NOTELL)) {
1640 struct permonst *mdat = steed->data;
1641
1642 (void) newcham(steed, (struct permonst *) 0, FALSE, FALSE);
1643 if (!can_saddle(steed) || !can_ride(steed)) {
1644 dismount_steed(DISMOUNT_POLY);
1645 } else {
1646 char buf[BUFSZ];
1647
1648 Strcpy(buf, x_monnam(steed, ARTICLE_YOUR, (char *) 0,
1649 SUPPRESS_SADDLE, FALSE));
1650 if (mdat != steed->data)
1651 (void) strsubst(buf, "your ", "your new ");
1652 You("adjust yourself in the saddle on %s.", buf);
1653 }
1654 }
1655 steedhit = TRUE;
1656 break;
1657 default:
1658 break;
1659 }
1660
1661 if (trapkilled) {
1662 dismount_steed(DISMOUNT_POLY);
1663 return 2;
1664 }
1665 return steedhit ? 1 : 0;
1666 }
1667
1668 /* some actions common to both player and monsters for triggered landmine */
1669 void
blow_up_landmine(trap)1670 blow_up_landmine(trap)
1671 struct trap *trap;
1672 {
1673 int x = trap->tx, y = trap->ty, dbx, dby;
1674 struct rm *lev = &levl[x][y];
1675
1676 (void) scatter(x, y, 4,
1677 MAY_DESTROY | MAY_HIT | MAY_FRACTURE | VIS_EFFECTS,
1678 (struct obj *) 0);
1679 del_engr_at(x, y);
1680 wake_nearto(x, y, 400);
1681 if (IS_DOOR(lev->typ))
1682 lev->doormask = D_BROKEN;
1683 /* destroy drawbridge if present */
1684 if (lev->typ == DRAWBRIDGE_DOWN || is_drawbridge_wall(x, y) >= 0) {
1685 dbx = x, dby = y;
1686 /* if under the portcullis, the bridge is adjacent */
1687 if (find_drawbridge(&dbx, &dby))
1688 destroy_drawbridge(dbx, dby);
1689 trap = t_at(x, y); /* expected to be null after destruction */
1690 }
1691 /* convert landmine into pit */
1692 if (trap) {
1693 if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) {
1694 /* no pits here */
1695 deltrap(trap);
1696 } else {
1697 trap->ttyp = PIT; /* explosion creates a pit */
1698 trap->madeby_u = FALSE; /* resulting pit isn't yours */
1699 seetrap(trap); /* and it isn't concealed */
1700 }
1701 }
1702 }
1703
1704 /*
1705 * The following are used to track launched objects to
1706 * prevent them from vanishing if you are killed. They
1707 * will reappear at the launchplace in bones files.
1708 */
1709 static struct {
1710 struct obj *obj;
1711 xchar x, y;
1712 } launchplace;
1713
1714 STATIC_OVL void
launch_drop_spot(obj,x,y)1715 launch_drop_spot(obj, x, y)
1716 struct obj *obj;
1717 xchar x, y;
1718 {
1719 if (!obj) {
1720 launchplace.obj = (struct obj *) 0;
1721 launchplace.x = 0;
1722 launchplace.y = 0;
1723 } else {
1724 launchplace.obj = obj;
1725 launchplace.x = x;
1726 launchplace.y = y;
1727 }
1728 }
1729
1730 boolean
launch_in_progress()1731 launch_in_progress()
1732 {
1733 if (launchplace.obj)
1734 return TRUE;
1735 return FALSE;
1736 }
1737
1738 void
force_launch_placement()1739 force_launch_placement()
1740 {
1741 if (launchplace.obj) {
1742 launchplace.obj->otrapped = 0;
1743 place_object(launchplace.obj, launchplace.x, launchplace.y);
1744 }
1745 }
1746
1747 /*
1748 * Move obj from (x1,y1) to (x2,y2)
1749 *
1750 * Return 0 if no object was launched.
1751 * 1 if an object was launched and placed somewhere.
1752 * 2 if an object was launched, but used up.
1753 */
1754 int
launch_obj(otyp,x1,y1,x2,y2,style)1755 launch_obj(otyp, x1, y1, x2, y2, style)
1756 short otyp;
1757 register int x1, y1, x2, y2;
1758 int style;
1759 {
1760 register struct monst *mtmp;
1761 register struct obj *otmp, *otmp2;
1762 register int dx, dy;
1763 struct obj *singleobj;
1764 boolean used_up = FALSE;
1765 boolean otherside = FALSE;
1766 int dist;
1767 int tmp;
1768 int delaycnt = 0;
1769
1770 otmp = sobj_at(otyp, x1, y1);
1771 /* Try the other side too, for rolling boulder traps */
1772 if (!otmp && otyp == BOULDER) {
1773 otherside = TRUE;
1774 otmp = sobj_at(otyp, x2, y2);
1775 }
1776 if (!otmp)
1777 return 0;
1778 if (otherside) { /* swap 'em */
1779 int tx, ty;
1780
1781 tx = x1;
1782 ty = y1;
1783 x1 = x2;
1784 y1 = y2;
1785 x2 = tx;
1786 y2 = ty;
1787 }
1788
1789 if (otmp->quan == 1L) {
1790 obj_extract_self(otmp);
1791 singleobj = otmp;
1792 otmp = (struct obj *) 0;
1793 } else {
1794 singleobj = splitobj(otmp, 1L);
1795 obj_extract_self(singleobj);
1796 }
1797 newsym(x1, y1);
1798 /* in case you're using a pick-axe to chop the boulder that's being
1799 launched (perhaps a monster triggered it), destroy context so that
1800 next dig attempt never thinks you're resuming previous effort */
1801 if ((otyp == BOULDER || otyp == STATUE)
1802 && singleobj->ox == context.digging.pos.x
1803 && singleobj->oy == context.digging.pos.y)
1804 (void) memset((genericptr_t) &context.digging, 0,
1805 sizeof(struct dig_info));
1806
1807 dist = distmin(x1, y1, x2, y2);
1808 bhitpos.x = x1;
1809 bhitpos.y = y1;
1810 dx = sgn(x2 - x1);
1811 dy = sgn(y2 - y1);
1812 switch (style) {
1813 case ROLL | LAUNCH_UNSEEN:
1814 if (otyp == BOULDER) {
1815 You_hear(Hallucination ? "someone bowling."
1816 : "rumbling in the distance.");
1817 }
1818 style &= ~LAUNCH_UNSEEN;
1819 goto roll;
1820 case ROLL | LAUNCH_KNOWN:
1821 /* use otrapped as a flag to ohitmon */
1822 singleobj->otrapped = 1;
1823 style &= ~LAUNCH_KNOWN;
1824 /* fall through */
1825 roll:
1826 case ROLL:
1827 delaycnt = 2;
1828 /* fall through */
1829 default:
1830 if (!delaycnt)
1831 delaycnt = 1;
1832 if (!cansee(bhitpos.x, bhitpos.y))
1833 curs_on_u();
1834 tmp_at(DISP_FLASH, obj_to_glyph(singleobj, rn2_on_display_rng));
1835 tmp_at(bhitpos.x, bhitpos.y);
1836 }
1837 /* Mark a spot to place object in bones files to prevent
1838 * loss of object. Use the starting spot to ensure that
1839 * a rolling boulder will still launch, which it wouldn't
1840 * do if left midstream. Unfortunately we can't use the
1841 * target resting spot, because there are some things/situations
1842 * that would prevent it from ever getting there (bars), and we
1843 * can't tell that yet.
1844 */
1845 launch_drop_spot(singleobj, bhitpos.x, bhitpos.y);
1846
1847 /* Set the object in motion */
1848 while (dist-- > 0 && !used_up) {
1849 struct trap *t;
1850 tmp_at(bhitpos.x, bhitpos.y);
1851 tmp = delaycnt;
1852
1853 /* dstage@u.washington.edu -- Delay only if hero sees it */
1854 if (cansee(bhitpos.x, bhitpos.y))
1855 while (tmp-- > 0)
1856 delay_output();
1857
1858 bhitpos.x += dx;
1859 bhitpos.y += dy;
1860
1861 if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) {
1862 if (otyp == BOULDER && throws_rocks(mtmp->data)) {
1863 if (rn2(3)) {
1864 if (cansee(bhitpos.x, bhitpos.y))
1865 pline("%s snatches the boulder.", Monnam(mtmp));
1866 singleobj->otrapped = 0;
1867 (void) mpickobj(mtmp, singleobj);
1868 used_up = TRUE;
1869 launch_drop_spot((struct obj *) 0, 0, 0);
1870 break;
1871 }
1872 }
1873 if (ohitmon(mtmp, singleobj, (style == ROLL) ? -1 : dist,
1874 FALSE)) {
1875 used_up = TRUE;
1876 launch_drop_spot((struct obj *) 0, 0, 0);
1877 break;
1878 }
1879 } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) {
1880 if (multi)
1881 nomul(0);
1882 if (thitu(9 + singleobj->spe, dmgval(singleobj, &youmonst),
1883 &singleobj, (char *) 0))
1884 stop_occupation();
1885 }
1886 if (style == ROLL) {
1887 if (down_gate(bhitpos.x, bhitpos.y) != -1) {
1888 if (ship_object(singleobj, bhitpos.x, bhitpos.y, FALSE)) {
1889 used_up = TRUE;
1890 launch_drop_spot((struct obj *) 0, 0, 0);
1891 break;
1892 }
1893 }
1894 if ((t = t_at(bhitpos.x, bhitpos.y)) != 0 && otyp == BOULDER) {
1895 switch (t->ttyp) {
1896 case LANDMINE:
1897 if (rn2(10) > 2) {
1898 pline(
1899 "KAABLAMM!!!%s",
1900 cansee(bhitpos.x, bhitpos.y)
1901 ? " The rolling boulder triggers a land mine."
1902 : "");
1903 deltrap(t);
1904 del_engr_at(bhitpos.x, bhitpos.y);
1905 place_object(singleobj, bhitpos.x, bhitpos.y);
1906 singleobj->otrapped = 0;
1907 fracture_rock(singleobj);
1908 (void) scatter(bhitpos.x, bhitpos.y, 4,
1909 MAY_DESTROY | MAY_HIT | MAY_FRACTURE
1910 | VIS_EFFECTS,
1911 (struct obj *) 0);
1912 if (cansee(bhitpos.x, bhitpos.y))
1913 newsym(bhitpos.x, bhitpos.y);
1914 used_up = TRUE;
1915 launch_drop_spot((struct obj *) 0, 0, 0);
1916 }
1917 break;
1918 case LEVEL_TELEP:
1919 case TELEP_TRAP:
1920 if (cansee(bhitpos.x, bhitpos.y))
1921 pline("Suddenly the rolling boulder disappears!");
1922 else
1923 You_hear("a rumbling stop abruptly.");
1924 singleobj->otrapped = 0;
1925 if (t->ttyp == TELEP_TRAP)
1926 (void) rloco(singleobj);
1927 else {
1928 int newlev = random_teleport_level();
1929 d_level dest;
1930
1931 if (newlev == depth(&u.uz) || In_endgame(&u.uz))
1932 continue;
1933 add_to_migration(singleobj);
1934 get_level(&dest, newlev);
1935 singleobj->ox = dest.dnum;
1936 singleobj->oy = dest.dlevel;
1937 singleobj->owornmask = (long) MIGR_RANDOM;
1938 }
1939 seetrap(t);
1940 used_up = TRUE;
1941 launch_drop_spot((struct obj *) 0, 0, 0);
1942 break;
1943 case PIT:
1944 case SPIKED_PIT:
1945 case HOLE:
1946 case TRAPDOOR:
1947 /* the boulder won't be used up if there is a
1948 monster in the trap; stop rolling anyway */
1949 x2 = bhitpos.x, y2 = bhitpos.y; /* stops here */
1950 if (flooreffects(singleobj, x2, y2, "fall")) {
1951 used_up = TRUE;
1952 launch_drop_spot((struct obj *) 0, 0, 0);
1953 }
1954 dist = -1; /* stop rolling immediately */
1955 break;
1956 }
1957 if (used_up || dist == -1)
1958 break;
1959 }
1960 if (flooreffects(singleobj, bhitpos.x, bhitpos.y, "fall")) {
1961 used_up = TRUE;
1962 launch_drop_spot((struct obj *) 0, 0, 0);
1963 break;
1964 }
1965 if (otyp == BOULDER
1966 && (otmp2 = sobj_at(BOULDER, bhitpos.x, bhitpos.y)) != 0) {
1967 const char *bmsg = " as one boulder sets another in motion";
1968
1969 if (!isok(bhitpos.x + dx, bhitpos.y + dy) || !dist
1970 || IS_ROCK(levl[bhitpos.x + dx][bhitpos.y + dy].typ))
1971 bmsg = " as one boulder hits another";
1972
1973 You_hear("a loud crash%s!",
1974 cansee(bhitpos.x, bhitpos.y) ? bmsg : "");
1975 obj_extract_self(otmp2);
1976 /* pass off the otrapped flag to the next boulder */
1977 otmp2->otrapped = singleobj->otrapped;
1978 singleobj->otrapped = 0;
1979 place_object(singleobj, bhitpos.x, bhitpos.y);
1980 singleobj = otmp2;
1981 otmp2 = (struct obj *) 0;
1982 wake_nearto(bhitpos.x, bhitpos.y, 10 * 10);
1983 }
1984 }
1985 if (otyp == BOULDER && closed_door(bhitpos.x, bhitpos.y)) {
1986 if (cansee(bhitpos.x, bhitpos.y))
1987 pline_The("boulder crashes through a door.");
1988 levl[bhitpos.x][bhitpos.y].doormask = D_BROKEN;
1989 if (dist)
1990 unblock_point(bhitpos.x, bhitpos.y);
1991 }
1992
1993 /* if about to hit iron bars, do so now */
1994 if (dist > 0 && isok(bhitpos.x + dx, bhitpos.y + dy)
1995 && levl[bhitpos.x + dx][bhitpos.y + dy].typ == IRONBARS) {
1996 x2 = bhitpos.x, y2 = bhitpos.y; /* object stops here */
1997 if (hits_bars(&singleobj,
1998 x2, y2, x2+dx, y2+dy,
1999 !rn2(20), 0)) {
2000 if (!singleobj) {
2001 used_up = TRUE;
2002 launch_drop_spot((struct obj *) 0, 0, 0);
2003 }
2004 break;
2005 }
2006 }
2007 }
2008 tmp_at(DISP_END, 0);
2009 launch_drop_spot((struct obj *) 0, 0, 0);
2010 if (!used_up) {
2011 singleobj->otrapped = 0;
2012 place_object(singleobj, x2, y2);
2013 newsym(x2, y2);
2014 return 1;
2015 } else
2016 return 2;
2017 }
2018
2019 void
seetrap(trap)2020 seetrap(trap)
2021 struct trap *trap;
2022 {
2023 if (!trap->tseen) {
2024 trap->tseen = 1;
2025 newsym(trap->tx, trap->ty);
2026 }
2027 }
2028
2029 /* like seetrap() but overrides vision */
2030 void
feeltrap(trap)2031 feeltrap(trap)
2032 struct trap *trap;
2033 {
2034 trap->tseen = 1;
2035 map_trap(trap, 1);
2036 /* in case it's beneath something, redisplay the something */
2037 newsym(trap->tx, trap->ty);
2038 }
2039
2040 STATIC_OVL int
mkroll_launch(ttmp,x,y,otyp,ocount)2041 mkroll_launch(ttmp, x, y, otyp, ocount)
2042 struct trap *ttmp;
2043 xchar x, y;
2044 short otyp;
2045 long ocount;
2046 {
2047 struct obj *otmp;
2048 register int tmp;
2049 schar dx, dy;
2050 int distance;
2051 coord cc;
2052 coord bcc;
2053 int trycount = 0;
2054 boolean success = FALSE;
2055 int mindist = 4;
2056
2057 if (ttmp->ttyp == ROLLING_BOULDER_TRAP)
2058 mindist = 2;
2059 distance = rn1(5, 4); /* 4..8 away */
2060 tmp = rn2(8); /* randomly pick a direction to try first */
2061 while (distance >= mindist) {
2062 dx = xdir[tmp];
2063 dy = ydir[tmp];
2064 cc.x = x;
2065 cc.y = y;
2066 /* Prevent boulder from being placed on water */
2067 if (ttmp->ttyp == ROLLING_BOULDER_TRAP
2068 && is_pool_or_lava(x + distance * dx, y + distance * dy))
2069 success = FALSE;
2070 else
2071 success = isclearpath(&cc, distance, dx, dy);
2072 if (ttmp->ttyp == ROLLING_BOULDER_TRAP) {
2073 boolean success_otherway;
2074
2075 bcc.x = x;
2076 bcc.y = y;
2077 success_otherway = isclearpath(&bcc, distance, -(dx), -(dy));
2078 if (!success_otherway)
2079 success = FALSE;
2080 }
2081 if (success)
2082 break;
2083 if (++tmp > 7)
2084 tmp = 0;
2085 if ((++trycount % 8) == 0)
2086 --distance;
2087 }
2088 if (!success) {
2089 /* create the trap without any ammo, launch pt at trap location */
2090 cc.x = bcc.x = x;
2091 cc.y = bcc.y = y;
2092 } else {
2093 otmp = mksobj(otyp, TRUE, FALSE);
2094 otmp->quan = ocount;
2095 otmp->owt = weight(otmp);
2096 place_object(otmp, cc.x, cc.y);
2097 stackobj(otmp);
2098 }
2099 ttmp->launch.x = cc.x;
2100 ttmp->launch.y = cc.y;
2101 if (ttmp->ttyp == ROLLING_BOULDER_TRAP) {
2102 ttmp->launch2.x = bcc.x;
2103 ttmp->launch2.y = bcc.y;
2104 } else
2105 ttmp->launch_otyp = otyp;
2106 newsym(ttmp->launch.x, ttmp->launch.y);
2107 return 1;
2108 }
2109
2110 STATIC_OVL boolean
isclearpath(cc,distance,dx,dy)2111 isclearpath(cc, distance, dx, dy)
2112 coord *cc;
2113 int distance;
2114 schar dx, dy;
2115 {
2116 uchar typ;
2117 xchar x, y;
2118
2119 x = cc->x;
2120 y = cc->y;
2121 while (distance-- > 0) {
2122 x += dx;
2123 y += dy;
2124 typ = levl[x][y].typ;
2125 if (!isok(x, y) || !ZAP_POS(typ) || closed_door(x, y))
2126 return FALSE;
2127 }
2128 cc->x = x;
2129 cc->y = y;
2130 return TRUE;
2131 }
2132
2133 int
mintrap(mtmp)2134 mintrap(mtmp)
2135 register struct monst *mtmp;
2136 {
2137 register struct trap *trap = t_at(mtmp->mx, mtmp->my);
2138 boolean trapkilled = FALSE;
2139 struct permonst *mptr = mtmp->data;
2140 struct obj *otmp;
2141
2142 if (!trap) {
2143 mtmp->mtrapped = 0; /* perhaps teleported? */
2144 } else if (mtmp->mtrapped) { /* is currently in the trap */
2145 if (!trap->tseen && cansee(mtmp->mx, mtmp->my) && canseemon(mtmp)
2146 && (is_pit(trap->ttyp) || trap->ttyp == BEAR_TRAP
2147 || trap->ttyp == HOLE
2148 || trap->ttyp == WEB)) {
2149 /* If you come upon an obviously trapped monster, then
2150 * you must be able to see the trap it's in too.
2151 */
2152 seetrap(trap);
2153 }
2154
2155 if (!rn2(40)) {
2156 if (sobj_at(BOULDER, mtmp->mx, mtmp->my)
2157 && is_pit(trap->ttyp)) {
2158 if (!rn2(2)) {
2159 mtmp->mtrapped = 0;
2160 if (canseemon(mtmp))
2161 pline("%s pulls free...", Monnam(mtmp));
2162 fill_pit(mtmp->mx, mtmp->my);
2163 }
2164 } else {
2165 mtmp->mtrapped = 0;
2166 }
2167 } else if (metallivorous(mptr)) {
2168 if (trap->ttyp == BEAR_TRAP) {
2169 if (canseemon(mtmp))
2170 pline("%s eats a bear trap!", Monnam(mtmp));
2171 deltrap(trap);
2172 mtmp->meating = 5;
2173 mtmp->mtrapped = 0;
2174 } else if (trap->ttyp == SPIKED_PIT) {
2175 if (canseemon(mtmp))
2176 pline("%s munches on some spikes!", Monnam(mtmp));
2177 trap->ttyp = PIT;
2178 mtmp->meating = 5;
2179 }
2180 }
2181 } else {
2182 register int tt = trap->ttyp;
2183 boolean in_sight, tear_web, see_it,
2184 inescapable = force_mintrap || ((tt == HOLE || tt == PIT)
2185 && Sokoban && !trap->madeby_u);
2186 const char *fallverb;
2187 xchar tx = trap->tx, ty = trap->ty;
2188
2189 /* true when called from dotrap, inescapable is not an option */
2190 if (mtmp == u.usteed)
2191 inescapable = TRUE;
2192 if (!inescapable && ((mtmp->mtrapseen & (1 << (tt - 1))) != 0
2193 || (tt == HOLE && !mindless(mptr)))) {
2194 /* it has been in such a trap - perhaps it escapes */
2195 if (rn2(4))
2196 return 0;
2197 } else {
2198 mtmp->mtrapseen |= (1 << (tt - 1));
2199 }
2200 /* Monster is aggravated by being trapped by you.
2201 Recognizing who made the trap isn't completely
2202 unreasonable; everybody has their own style. */
2203 if (trap->madeby_u && rnl(5))
2204 setmangry(mtmp, TRUE);
2205
2206 in_sight = canseemon(mtmp);
2207 see_it = cansee(mtmp->mx, mtmp->my);
2208 /* assume hero can tell what's going on for the steed */
2209 if (mtmp == u.usteed)
2210 in_sight = TRUE;
2211 switch (tt) {
2212 case ARROW_TRAP:
2213 if (trap->once && trap->tseen && !rn2(15)) {
2214 if (in_sight && see_it)
2215 pline("%s triggers a trap but nothing happens.",
2216 Monnam(mtmp));
2217 deltrap(trap);
2218 newsym(mtmp->mx, mtmp->my);
2219 break;
2220 }
2221 trap->once = 1;
2222 otmp = t_missile(ARROW, trap);
2223 if (in_sight)
2224 seetrap(trap);
2225 if (thitm(8, mtmp, otmp, 0, FALSE))
2226 trapkilled = TRUE;
2227 break;
2228 case DART_TRAP:
2229 if (trap->once && trap->tseen && !rn2(15)) {
2230 if (in_sight && see_it)
2231 pline("%s triggers a trap but nothing happens.",
2232 Monnam(mtmp));
2233 deltrap(trap);
2234 newsym(mtmp->mx, mtmp->my);
2235 break;
2236 }
2237 trap->once = 1;
2238 otmp = t_missile(DART, trap);
2239 if (!rn2(6))
2240 otmp->opoisoned = 1;
2241 if (in_sight)
2242 seetrap(trap);
2243 if (thitm(7, mtmp, otmp, 0, FALSE))
2244 trapkilled = TRUE;
2245 break;
2246 case ROCKTRAP:
2247 if (trap->once && trap->tseen && !rn2(15)) {
2248 if (in_sight && see_it)
2249 pline(
2250 "A trap door above %s opens, but nothing falls out!",
2251 mon_nam(mtmp));
2252 deltrap(trap);
2253 newsym(mtmp->mx, mtmp->my);
2254 break;
2255 }
2256 trap->once = 1;
2257 otmp = t_missile(ROCK, trap);
2258 if (in_sight)
2259 seetrap(trap);
2260 if (thitm(0, mtmp, otmp, d(2, 6), FALSE))
2261 trapkilled = TRUE;
2262 break;
2263 case SQKY_BOARD:
2264 if (is_flyer(mptr))
2265 break;
2266 /* stepped on a squeaky board */
2267 if (in_sight) {
2268 if (!Deaf) {
2269 pline("A board beneath %s squeaks %s loudly.",
2270 mon_nam(mtmp), trapnote(trap, 0));
2271 seetrap(trap);
2272 } else {
2273 pline("%s stops momentarily and appears to cringe.",
2274 Monnam(mtmp));
2275 }
2276 } else {
2277 /* same near/far threshold as mzapmsg() */
2278 int range = couldsee(mtmp->mx, mtmp->my) /* 9 or 5 */
2279 ? (BOLT_LIM + 1) : (BOLT_LIM - 3);
2280
2281 You_hear("a %s squeak %s.", trapnote(trap, 1),
2282 (distu(mtmp->mx, mtmp->my) <= range * range)
2283 ? "nearby" : "in the distance");
2284 }
2285 /* wake up nearby monsters */
2286 wake_nearto(mtmp->mx, mtmp->my, 40);
2287 break;
2288 case BEAR_TRAP:
2289 if (mptr->msize > MZ_SMALL && !amorphous(mptr) && !is_flyer(mptr)
2290 && !is_whirly(mptr) && !unsolid(mptr)) {
2291 mtmp->mtrapped = 1;
2292 if (in_sight) {
2293 pline("%s is caught in %s bear trap!", Monnam(mtmp),
2294 a_your[trap->madeby_u]);
2295 seetrap(trap);
2296 } else {
2297 if (mptr == &mons[PM_OWLBEAR]
2298 || mptr == &mons[PM_BUGBEAR])
2299 You_hear("the roaring of an angry bear!");
2300 }
2301 } else if (force_mintrap) {
2302 if (in_sight) {
2303 pline("%s evades %s bear trap!", Monnam(mtmp),
2304 a_your[trap->madeby_u]);
2305 seetrap(trap);
2306 }
2307 }
2308 if (mtmp->mtrapped)
2309 trapkilled = thitm(0, mtmp, (struct obj *) 0, d(2, 4), FALSE);
2310 break;
2311 case SLP_GAS_TRAP:
2312 if (!resists_sleep(mtmp) && !breathless(mptr) && !mtmp->msleeping
2313 && mtmp->mcanmove) {
2314 if (sleep_monst(mtmp, rnd(25), -1) && in_sight) {
2315 pline("%s suddenly falls asleep!", Monnam(mtmp));
2316 seetrap(trap);
2317 }
2318 }
2319 break;
2320 case RUST_TRAP: {
2321 struct obj *target;
2322
2323 if (in_sight)
2324 seetrap(trap);
2325 switch (rn2(5)) {
2326 case 0:
2327 if (in_sight)
2328 pline("%s %s on the %s!", A_gush_of_water_hits,
2329 mon_nam(mtmp), mbodypart(mtmp, HEAD));
2330 target = which_armor(mtmp, W_ARMH);
2331 (void) water_damage(target, helm_simple_name(target), TRUE);
2332 break;
2333 case 1:
2334 if (in_sight)
2335 pline("%s %s's left %s!", A_gush_of_water_hits,
2336 mon_nam(mtmp), mbodypart(mtmp, ARM));
2337 target = which_armor(mtmp, W_ARMS);
2338 if (water_damage(target, "shield", TRUE) != ER_NOTHING)
2339 break;
2340 target = MON_WEP(mtmp);
2341 if (target && bimanual(target))
2342 (void) water_damage(target, 0, TRUE);
2343 glovecheck:
2344 target = which_armor(mtmp, W_ARMG);
2345 (void) water_damage(target, "gauntlets", TRUE);
2346 break;
2347 case 2:
2348 if (in_sight)
2349 pline("%s %s's right %s!", A_gush_of_water_hits,
2350 mon_nam(mtmp), mbodypart(mtmp, ARM));
2351 (void) water_damage(MON_WEP(mtmp), 0, TRUE);
2352 goto glovecheck;
2353 default:
2354 if (in_sight)
2355 pline("%s %s!", A_gush_of_water_hits, mon_nam(mtmp));
2356 for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
2357 if (otmp->lamplit
2358 && (otmp->owornmask & (W_WEP | W_SWAPWEP)) == 0)
2359 (void) snuff_lit(otmp);
2360 if ((target = which_armor(mtmp, W_ARMC)) != 0)
2361 (void) water_damage(target, cloak_simple_name(target),
2362 TRUE);
2363 else if ((target = which_armor(mtmp, W_ARM)) != 0)
2364 (void) water_damage(target, suit_simple_name(target),
2365 TRUE);
2366 else if ((target = which_armor(mtmp, W_ARMU)) != 0)
2367 (void) water_damage(target, "shirt", TRUE);
2368 }
2369
2370 if (mptr == &mons[PM_IRON_GOLEM]) {
2371 if (in_sight)
2372 pline("%s falls to pieces!", Monnam(mtmp));
2373 else if (mtmp->mtame)
2374 pline("May %s rust in peace.", mon_nam(mtmp));
2375 mondied(mtmp);
2376 if (DEADMONSTER(mtmp))
2377 trapkilled = TRUE;
2378 } else if (mptr == &mons[PM_GREMLIN] && rn2(3)) {
2379 (void) split_mon(mtmp, (struct monst *) 0);
2380 }
2381 break;
2382 } /* RUST_TRAP */
2383 case FIRE_TRAP:
2384 mfiretrap:
2385 if (in_sight)
2386 pline("A %s erupts from the %s under %s!", tower_of_flame,
2387 surface(mtmp->mx, mtmp->my), mon_nam(mtmp));
2388 else if (see_it) /* evidently `mtmp' is invisible */
2389 You_see("a %s erupt from the %s!", tower_of_flame,
2390 surface(mtmp->mx, mtmp->my));
2391
2392 if (resists_fire(mtmp)) {
2393 if (in_sight) {
2394 shieldeff(mtmp->mx, mtmp->my);
2395 pline("%s is uninjured.", Monnam(mtmp));
2396 }
2397 } else {
2398 int num = d(2, 4), alt;
2399 boolean immolate = FALSE;
2400
2401 /* paper burns very fast, assume straw is tightly
2402 * packed and burns a bit slower */
2403 switch (monsndx(mptr)) {
2404 case PM_PAPER_GOLEM:
2405 immolate = TRUE;
2406 alt = mtmp->mhpmax;
2407 break;
2408 case PM_STRAW_GOLEM:
2409 alt = mtmp->mhpmax / 2;
2410 break;
2411 case PM_WOOD_GOLEM:
2412 alt = mtmp->mhpmax / 4;
2413 break;
2414 case PM_LEATHER_GOLEM:
2415 alt = mtmp->mhpmax / 8;
2416 break;
2417 default:
2418 alt = 0;
2419 break;
2420 }
2421 if (alt > num)
2422 num = alt;
2423
2424 if (thitm(0, mtmp, (struct obj *) 0, num, immolate))
2425 trapkilled = TRUE;
2426 else
2427 /* we know mhp is at least `num' below mhpmax,
2428 so no (mhp > mhpmax) check is needed here */
2429 mtmp->mhpmax -= rn2(num + 1);
2430 }
2431 if (burnarmor(mtmp) || rn2(3)) {
2432 (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE);
2433 (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE);
2434 (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE);
2435 }
2436 if (burn_floor_objects(mtmp->mx, mtmp->my, see_it, FALSE)
2437 && !see_it && distu(mtmp->mx, mtmp->my) <= 3 * 3)
2438 You("smell smoke.");
2439 if (is_ice(mtmp->mx, mtmp->my))
2440 melt_ice(mtmp->mx, mtmp->my, (char *) 0);
2441 if (see_it && t_at(mtmp->mx, mtmp->my))
2442 seetrap(trap);
2443 break;
2444 case PIT:
2445 case SPIKED_PIT:
2446 fallverb = "falls";
2447 if (is_flyer(mptr) || is_floater(mptr)
2448 || (mtmp->wormno && count_wsegs(mtmp) > 5)
2449 || is_clinger(mptr)) {
2450 if (force_mintrap && !Sokoban) {
2451 /* openfallingtrap; not inescapable here */
2452 if (in_sight) {
2453 seetrap(trap);
2454 pline("%s doesn't fall into the pit.", Monnam(mtmp));
2455 }
2456 break; /* inescapable = FALSE; */
2457 }
2458 if (!inescapable)
2459 break; /* avoids trap */
2460 fallverb = "is dragged"; /* sokoban pit */
2461 }
2462 if (!passes_walls(mptr))
2463 mtmp->mtrapped = 1;
2464 if (in_sight) {
2465 pline("%s %s into %s pit!", Monnam(mtmp), fallverb,
2466 a_your[trap->madeby_u]);
2467 if (mptr == &mons[PM_PIT_VIPER]
2468 || mptr == &mons[PM_PIT_FIEND])
2469 pline("How pitiful. Isn't that the pits?");
2470 seetrap(trap);
2471 }
2472 mselftouch(mtmp, "Falling, ", FALSE);
2473 if (DEADMONSTER(mtmp) || thitm(0, mtmp, (struct obj *) 0,
2474 rnd((tt == PIT) ? 6 : 10), FALSE))
2475 trapkilled = TRUE;
2476 break;
2477 case HOLE:
2478 case TRAPDOOR:
2479 if (!Can_fall_thru(&u.uz)) {
2480 impossible("mintrap: %ss cannot exist on this level.",
2481 defsyms[trap_to_defsym(tt)].explanation);
2482 break; /* don't activate it after all */
2483 }
2484 if (is_flyer(mptr) || is_floater(mptr) || mptr == &mons[PM_WUMPUS]
2485 || (mtmp->wormno && count_wsegs(mtmp) > 5)
2486 || mptr->msize >= MZ_HUGE) {
2487 if (force_mintrap && !Sokoban) {
2488 /* openfallingtrap; not inescapable here */
2489 if (in_sight) {
2490 seetrap(trap);
2491 if (tt == TRAPDOOR)
2492 pline(
2493 "A trap door opens, but %s doesn't fall through.",
2494 mon_nam(mtmp));
2495 else /* (tt == HOLE) */
2496 pline("%s doesn't fall through the hole.",
2497 Monnam(mtmp));
2498 }
2499 break; /* inescapable = FALSE; */
2500 }
2501 if (inescapable) { /* sokoban hole */
2502 if (in_sight) {
2503 pline("%s seems to be yanked down!", Monnam(mtmp));
2504 /* suppress message in mlevel_tele_trap() */
2505 in_sight = FALSE;
2506 seetrap(trap);
2507 }
2508 } else
2509 break;
2510 }
2511 /*FALLTHRU*/
2512 case LEVEL_TELEP:
2513 case MAGIC_PORTAL: {
2514 int mlev_res;
2515
2516 mlev_res = mlevel_tele_trap(mtmp, trap, inescapable, in_sight);
2517 if (mlev_res)
2518 return mlev_res;
2519 break;
2520 }
2521 case TELEP_TRAP:
2522 mtele_trap(mtmp, trap, in_sight);
2523 break;
2524 case WEB:
2525 /* Monster in a web. */
2526 if (webmaker(mptr))
2527 break;
2528 if (mu_maybe_destroy_web(mtmp, in_sight, trap))
2529 break;
2530 tear_web = FALSE;
2531 switch (monsndx(mptr)) {
2532 case PM_OWLBEAR: /* Eric Backus */
2533 case PM_BUGBEAR:
2534 if (!in_sight) {
2535 You_hear("the roaring of a confused bear!");
2536 mtmp->mtrapped = 1;
2537 break;
2538 }
2539 /*FALLTHRU*/
2540 default:
2541 if (mptr->mlet == S_GIANT
2542 /* exclude baby dragons and relatively short worms */
2543 || (mptr->mlet == S_DRAGON && extra_nasty(mptr))
2544 || (mtmp->wormno && count_wsegs(mtmp) > 5)) {
2545 tear_web = TRUE;
2546 } else if (in_sight) {
2547 pline("%s is caught in %s spider web.", Monnam(mtmp),
2548 a_your[trap->madeby_u]);
2549 seetrap(trap);
2550 }
2551 mtmp->mtrapped = tear_web ? 0 : 1;
2552 break;
2553 /* this list is fairly arbitrary; it deliberately
2554 excludes wumpus & giant/ettin zombies/mummies */
2555 case PM_TITANOTHERE:
2556 case PM_BALUCHITHERIUM:
2557 case PM_PURPLE_WORM:
2558 case PM_JABBERWOCK:
2559 case PM_IRON_GOLEM:
2560 case PM_BALROG:
2561 case PM_KRAKEN:
2562 case PM_MASTODON:
2563 case PM_ORION:
2564 case PM_NORN:
2565 case PM_CYCLOPS:
2566 case PM_LORD_SURTUR:
2567 tear_web = TRUE;
2568 break;
2569 }
2570 if (tear_web) {
2571 if (in_sight)
2572 pline("%s tears through %s spider web!", Monnam(mtmp),
2573 a_your[trap->madeby_u]);
2574 deltrap(trap);
2575 newsym(mtmp->mx, mtmp->my);
2576 } else if (force_mintrap && !mtmp->mtrapped) {
2577 if (in_sight) {
2578 pline("%s avoids %s spider web!", Monnam(mtmp),
2579 a_your[trap->madeby_u]);
2580 seetrap(trap);
2581 }
2582 }
2583 break;
2584 case STATUE_TRAP:
2585 break;
2586 case MAGIC_TRAP:
2587 /* A magic trap. Monsters usually immune. */
2588 if (!rn2(21))
2589 goto mfiretrap;
2590 break;
2591 case ANTI_MAGIC:
2592 /* similar to hero's case, more or less */
2593 if (!resists_magm(mtmp)) { /* lose spell energy */
2594 if (!mtmp->mcan && (attacktype(mptr, AT_MAGC)
2595 || attacktype(mptr, AT_BREA))) {
2596 mtmp->mspec_used += d(2, 2);
2597 if (in_sight) {
2598 seetrap(trap);
2599 pline("%s seems lethargic.", Monnam(mtmp));
2600 }
2601 }
2602 } else { /* take some damage */
2603 int dmgval2 = rnd(4);
2604
2605 if ((otmp = MON_WEP(mtmp)) != 0
2606 && otmp->oartifact == ART_MAGICBANE)
2607 dmgval2 += rnd(4);
2608 for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
2609 if (otmp->oartifact
2610 && defends_when_carried(AD_MAGM, otmp))
2611 break;
2612 if (otmp)
2613 dmgval2 += rnd(4);
2614 if (passes_walls(mptr))
2615 dmgval2 = (dmgval2 + 3) / 4;
2616
2617 if (in_sight)
2618 seetrap(trap);
2619 mtmp->mhp -= dmgval2;
2620 if (DEADMONSTER(mtmp))
2621 monkilled(mtmp,
2622 in_sight
2623 ? "compression from an anti-magic field"
2624 : (const char *) 0,
2625 -AD_MAGM);
2626 if (DEADMONSTER(mtmp))
2627 trapkilled = TRUE;
2628 if (see_it)
2629 newsym(trap->tx, trap->ty);
2630 }
2631 break;
2632 case LANDMINE:
2633 if (rn2(3))
2634 break; /* monsters usually don't set it off */
2635 if (is_flyer(mptr)) {
2636 boolean already_seen = trap->tseen;
2637
2638 if (in_sight && !already_seen) {
2639 pline("A trigger appears in a pile of soil below %s.",
2640 mon_nam(mtmp));
2641 seetrap(trap);
2642 }
2643 if (rn2(3))
2644 break;
2645 if (in_sight) {
2646 newsym(mtmp->mx, mtmp->my);
2647 pline_The("air currents set %s off!",
2648 already_seen ? "a land mine" : "it");
2649 }
2650 } else if (in_sight) {
2651 newsym(mtmp->mx, mtmp->my);
2652 pline("%s%s triggers %s land mine!",
2653 !Deaf ? "KAABLAMM!!! " : "", Monnam(mtmp),
2654 a_your[trap->madeby_u]);
2655 }
2656 if (!in_sight && !Deaf)
2657 pline("Kaablamm! %s an explosion in the distance!",
2658 "You hear"); /* Deaf-aware */
2659 blow_up_landmine(trap);
2660 /* explosion might have destroyed a drawbridge; don't
2661 dish out more damage if monster is already dead */
2662 if (DEADMONSTER(mtmp)
2663 || thitm(0, mtmp, (struct obj *) 0, rnd(16), FALSE)) {
2664 trapkilled = TRUE;
2665 } else {
2666 /* monsters recursively fall into new pit */
2667 if (mintrap(mtmp) == 2)
2668 trapkilled = TRUE;
2669 }
2670 /* a boulder may fill the new pit, crushing monster */
2671 fill_pit(tx, ty); /* thitm may have already destroyed the trap */
2672 if (DEADMONSTER(mtmp))
2673 trapkilled = TRUE;
2674 if (unconscious()) {
2675 multi = -1;
2676 nomovemsg = "The explosion awakens you!";
2677 }
2678 break;
2679 case POLY_TRAP:
2680 if (resists_magm(mtmp)) {
2681 shieldeff(mtmp->mx, mtmp->my);
2682 } else if (!resist(mtmp, WAND_CLASS, 0, NOTELL)) {
2683 if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE))
2684 /* we're done with mptr but keep it up to date */
2685 mptr = mtmp->data;
2686 if (in_sight)
2687 seetrap(trap);
2688 }
2689 break;
2690 case ROLLING_BOULDER_TRAP:
2691 if (!is_flyer(mptr)) {
2692 int style = ROLL | (in_sight ? 0 : LAUNCH_UNSEEN);
2693
2694 newsym(mtmp->mx, mtmp->my);
2695 if (in_sight)
2696 pline("Click! %s triggers %s.", Monnam(mtmp),
2697 trap->tseen ? "a rolling boulder trap" : something);
2698 if (launch_obj(BOULDER, trap->launch.x, trap->launch.y,
2699 trap->launch2.x, trap->launch2.y, style)) {
2700 if (in_sight)
2701 trap->tseen = TRUE;
2702 if (DEADMONSTER(mtmp))
2703 trapkilled = TRUE;
2704 } else {
2705 deltrap(trap);
2706 newsym(mtmp->mx, mtmp->my);
2707 }
2708 }
2709 break;
2710 case VIBRATING_SQUARE:
2711 if (see_it && !Blind) {
2712 seetrap(trap); /* before messages */
2713 if (in_sight) {
2714 char buf[BUFSZ], *p, *monnm = mon_nam(mtmp);
2715
2716 if (nolimbs(mtmp->data)
2717 || is_floater(mtmp->data) || is_flyer(mtmp->data)) {
2718 /* just "beneath <mon>" */
2719 Strcpy(buf, monnm);
2720 } else {
2721 Strcpy(buf, s_suffix(monnm));
2722 p = eos(strcat(buf, " "));
2723 Strcpy(p, makeplural(mbodypart(mtmp, FOOT)));
2724 /* avoid "beneath 'rear paws'" or 'rear hooves' */
2725 (void) strsubst(p, "rear ", "");
2726 }
2727 You_see("a strange vibration beneath %s.", buf);
2728 } else {
2729 /* notice something (hearing uses a larger threshold
2730 for 'nearby') */
2731 You_see("the ground vibrate %s.",
2732 (distu(mtmp->mx, mtmp->my) <= 2 * 2)
2733 ? "nearby" : "in the distance");
2734 }
2735 }
2736 break;
2737 default:
2738 impossible("Some monster encountered a strange trap of type %d.",
2739 tt);
2740 }
2741 }
2742 if (trapkilled)
2743 return 2;
2744 return mtmp->mtrapped;
2745 }
2746
2747 /* Combine cockatrice checks into single functions to avoid repeating code. */
2748 void
instapetrify(str)2749 instapetrify(str)
2750 const char *str;
2751 {
2752 if (Stone_resistance)
2753 return;
2754 if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))
2755 return;
2756 You("turn to stone...");
2757 killer.format = KILLED_BY;
2758 if (str != killer.name)
2759 Strcpy(killer.name, str ? str : "");
2760 done(STONING);
2761 }
2762
2763 void
minstapetrify(mon,byplayer)2764 minstapetrify(mon, byplayer)
2765 struct monst *mon;
2766 boolean byplayer;
2767 {
2768 if (resists_ston(mon))
2769 return;
2770 if (poly_when_stoned(mon->data)) {
2771 mon_to_stone(mon);
2772 return;
2773 }
2774 if (!vamp_stone(mon))
2775 return;
2776
2777 /* give a "<mon> is slowing down" message and also remove
2778 intrinsic speed (comparable to similar effect on the hero) */
2779 mon_adjust_speed(mon, -3, (struct obj *) 0);
2780
2781 if (cansee(mon->mx, mon->my))
2782 pline("%s turns to stone.", Monnam(mon));
2783 if (byplayer) {
2784 stoned = TRUE;
2785 xkilled(mon, XKILL_NOMSG);
2786 } else
2787 monstone(mon);
2788 }
2789
2790 void
selftouch(arg)2791 selftouch(arg)
2792 const char *arg;
2793 {
2794 char kbuf[BUFSZ];
2795
2796 if (uwep && uwep->otyp == CORPSE && touch_petrifies(&mons[uwep->corpsenm])
2797 && !Stone_resistance) {
2798 pline("%s touch the %s corpse.", arg, mons[uwep->corpsenm].mname);
2799 Sprintf(kbuf, "%s corpse", an(mons[uwep->corpsenm].mname));
2800 instapetrify(kbuf);
2801 /* life-saved; unwield the corpse if we can't handle it */
2802 if (!uarmg && !Stone_resistance)
2803 uwepgone();
2804 }
2805 /* Or your secondary weapon, if wielded [hypothetical; we don't
2806 allow two-weapon combat when either weapon is a corpse] */
2807 if (u.twoweap && uswapwep && uswapwep->otyp == CORPSE
2808 && touch_petrifies(&mons[uswapwep->corpsenm]) && !Stone_resistance) {
2809 pline("%s touch the %s corpse.", arg, mons[uswapwep->corpsenm].mname);
2810 Sprintf(kbuf, "%s corpse", an(mons[uswapwep->corpsenm].mname));
2811 instapetrify(kbuf);
2812 /* life-saved; unwield the corpse */
2813 if (!uarmg && !Stone_resistance)
2814 uswapwepgone();
2815 }
2816 }
2817
2818 void
mselftouch(mon,arg,byplayer)2819 mselftouch(mon, arg, byplayer)
2820 struct monst *mon;
2821 const char *arg;
2822 boolean byplayer;
2823 {
2824 struct obj *mwep = MON_WEP(mon);
2825
2826 if (mwep && mwep->otyp == CORPSE && touch_petrifies(&mons[mwep->corpsenm])
2827 && !resists_ston(mon)) {
2828 if (cansee(mon->mx, mon->my)) {
2829 pline("%s%s touches %s.", arg ? arg : "",
2830 arg ? mon_nam(mon) : Monnam(mon),
2831 corpse_xname(mwep, (const char *) 0, CXN_PFX_THE));
2832 }
2833 minstapetrify(mon, byplayer);
2834 /* if life-saved, might not be able to continue wielding */
2835 if (!DEADMONSTER(mon) && !which_armor(mon, W_ARMG) && !resists_ston(mon))
2836 mwepgone(mon);
2837 }
2838 }
2839
2840 /* start levitating */
2841 void
float_up()2842 float_up()
2843 {
2844 context.botl = TRUE;
2845 if (u.utrap) {
2846 if (u.utraptype == TT_PIT) {
2847 reset_utrap(FALSE);
2848 You("float up, out of the pit!");
2849 vision_full_recalc = 1; /* vision limits change */
2850 fill_pit(u.ux, u.uy);
2851 } else if (u.utraptype == TT_LAVA /* molten lava */
2852 || u.utraptype == TT_INFLOOR) { /* solidified lava */
2853 Your("body pulls upward, but your %s are still stuck.",
2854 makeplural(body_part(LEG)));
2855 } else if (u.utraptype == TT_BURIEDBALL) { /* tethered */
2856 coord cc;
2857
2858 cc.x = u.ux, cc.y = u.uy;
2859 /* caveat: this finds the first buried iron ball within
2860 one step of the specified location, not necessarily the
2861 buried [former] uball at the original anchor point */
2862 (void) buried_ball(&cc);
2863 /* being chained to the floor blocks levitation from floating
2864 above that floor but not from enhancing carrying capacity */
2865 You("feel lighter, but your %s is still chained to the %s.",
2866 body_part(LEG),
2867 IS_ROOM(levl[cc.x][cc.y].typ) ? "floor" : "ground");
2868 } else if (u.utraptype == WEB) {
2869 You("float up slightly, but you are still stuck in the web.");
2870 } else { /* bear trap */
2871 You("float up slightly, but your %s is still stuck.",
2872 body_part(LEG));
2873 }
2874 /* when still trapped, float_vs_flight() below will block levitation */
2875 #if 0
2876 } else if (Is_waterlevel(&u.uz)) {
2877 pline("It feels as though you've lost some weight.");
2878 #endif
2879 } else if (u.uinwater) {
2880 spoteffects(TRUE);
2881 } else if (u.uswallow) {
2882 You(is_animal(u.ustuck->data) ? "float away from the %s."
2883 : "spiral up into %s.",
2884 is_animal(u.ustuck->data) ? surface(u.ux, u.uy)
2885 : mon_nam(u.ustuck));
2886 } else if (Hallucination) {
2887 pline("Up, up, and awaaaay! You're walking on air!");
2888 } else if (Is_airlevel(&u.uz)) {
2889 You("gain control over your movements.");
2890 } else {
2891 You("start to float in the air!");
2892 }
2893 if (u.usteed && !is_floater(u.usteed->data) && !is_flyer(u.usteed->data)) {
2894 if (Lev_at_will) {
2895 pline("%s magically floats up!", Monnam(u.usteed));
2896 } else {
2897 You("cannot stay on %s.", mon_nam(u.usteed));
2898 dismount_steed(DISMOUNT_GENERIC);
2899 }
2900 }
2901 if (Flying)
2902 You("are no longer able to control your flight.");
2903 float_vs_flight(); /* set BFlying, also BLevitation if still trapped */
2904 /* levitation gives maximum carrying capacity, so encumbrance
2905 state might be reduced */
2906 (void) encumber_msg();
2907 return;
2908 }
2909
2910 void
fill_pit(x,y)2911 fill_pit(x, y)
2912 int x, y;
2913 {
2914 struct obj *otmp;
2915 struct trap *t;
2916
2917 if ((t = t_at(x, y)) && is_pit(t->ttyp)
2918 && (otmp = sobj_at(BOULDER, x, y))) {
2919 obj_extract_self(otmp);
2920 (void) flooreffects(otmp, x, y, "settle");
2921 }
2922 }
2923
2924 /* stop levitating */
2925 int
float_down(hmask,emask)2926 float_down(hmask, emask)
2927 long hmask, emask; /* might cancel timeout */
2928 {
2929 register struct trap *trap = (struct trap *) 0;
2930 d_level current_dungeon_level;
2931 boolean no_msg = FALSE;
2932
2933 HLevitation &= ~hmask;
2934 ELevitation &= ~emask;
2935 if (Levitation)
2936 return 0; /* maybe another ring/potion/boots */
2937 if (BLevitation) {
2938 /* if blocked by terrain, we haven't actually been levitating so
2939 we don't give any end-of-levitation feedback or side-effects,
2940 but if blocking is solely due to being trapped in/on floor,
2941 do give some feedback but skip other float_down() effects */
2942 boolean trapped = (BLevitation == I_SPECIAL);
2943
2944 float_vs_flight();
2945 if (trapped && u.utrap) /* u.utrap => paranoia */
2946 You("are no longer trying to float up from the %s.",
2947 (u.utraptype == TT_BEARTRAP) ? "trap's jaws"
2948 : (u.utraptype == TT_WEB) ? "web"
2949 : (u.utraptype == TT_BURIEDBALL) ? "chain"
2950 : (u.utraptype == TT_LAVA) ? "lava"
2951 : "ground"); /* TT_INFLOOR */
2952 (void) encumber_msg(); /* carrying capacity might have changed */
2953 return 0;
2954 }
2955 context.botl = TRUE;
2956 nomul(0); /* stop running or resting */
2957 if (BFlying) {
2958 /* controlled flight no longer overridden by levitation */
2959 float_vs_flight(); /* clears BFlying & I_SPECIAL
2960 * unless hero is stuck in floor */
2961 if (Flying) {
2962 You("have stopped levitating and are now flying.");
2963 (void) encumber_msg(); /* carrying capacity might have changed */
2964 return 1;
2965 }
2966 }
2967 if (u.uswallow) {
2968 You("float down, but you are still %s.",
2969 is_animal(u.ustuck->data) ? "swallowed" : "engulfed");
2970 (void) encumber_msg();
2971 return 1;
2972 }
2973
2974 if (Punished && !carried(uball)
2975 && (is_pool(uball->ox, uball->oy)
2976 || ((trap = t_at(uball->ox, uball->oy))
2977 && (is_pit(trap->ttyp) || is_hole(trap->ttyp))))) {
2978 u.ux0 = u.ux;
2979 u.uy0 = u.uy;
2980 u.ux = uball->ox;
2981 u.uy = uball->oy;
2982 movobj(uchain, uball->ox, uball->oy);
2983 newsym(u.ux0, u.uy0);
2984 vision_full_recalc = 1; /* in case the hero moved. */
2985 }
2986 /* check for falling into pool - added by GAN 10/20/86 */
2987 if (!Flying) {
2988 if (!u.uswallow && u.ustuck) {
2989 if (sticks(youmonst.data))
2990 You("aren't able to maintain your hold on %s.",
2991 mon_nam(u.ustuck));
2992 else
2993 pline("Startled, %s can no longer hold you!",
2994 mon_nam(u.ustuck));
2995 u.ustuck = 0;
2996 }
2997 /* kludge alert:
2998 * drown() and lava_effects() print various messages almost
2999 * every time they're called which conflict with the "fall
3000 * into" message below. Thus, we want to avoid printing
3001 * confusing, duplicate or out-of-order messages.
3002 * Use knowledge of the two routines as a hack -- this
3003 * should really be handled differently -dlc
3004 */
3005 if (is_pool(u.ux, u.uy) && !Wwalking && !Swimming && !u.uinwater)
3006 no_msg = drown();
3007
3008 if (is_lava(u.ux, u.uy)) {
3009 (void) lava_effects();
3010 no_msg = TRUE;
3011 }
3012 }
3013 if (!trap) {
3014 trap = t_at(u.ux, u.uy);
3015 if (Is_airlevel(&u.uz)) {
3016 You("begin to tumble in place.");
3017 } else if (Is_waterlevel(&u.uz) && !no_msg) {
3018 You_feel("heavier.");
3019 /* u.uinwater msgs already in spoteffects()/drown() */
3020 } else if (!u.uinwater && !no_msg) {
3021 if (!(emask & W_SADDLE)) {
3022 if (Sokoban && trap) {
3023 /* Justification elsewhere for Sokoban traps is based
3024 * on air currents. This is consistent with that.
3025 * The unexpected additional force of the air currents
3026 * once levitation ceases knocks you off your feet.
3027 */
3028 if (Hallucination)
3029 pline("Bummer! You've crashed.");
3030 else
3031 You("fall over.");
3032 losehp(rnd(2), "dangerous winds", KILLED_BY);
3033 if (u.usteed)
3034 dismount_steed(DISMOUNT_FELL);
3035 selftouch("As you fall, you");
3036 } else if (u.usteed && (is_floater(u.usteed->data)
3037 || is_flyer(u.usteed->data))) {
3038 You("settle more firmly in the saddle.");
3039 } else if (Hallucination) {
3040 pline("Bummer! You've %s.",
3041 is_pool(u.ux, u.uy)
3042 ? "splashed down"
3043 : "hit the ground");
3044 } else {
3045 You("float gently to the %s.", surface(u.ux, u.uy));
3046 }
3047 }
3048 }
3049 }
3050
3051 /* levitation gives maximum carrying capacity, so having it end
3052 potentially triggers greater encumbrance; do this after
3053 'come down' messages, before trap activation or autopickup */
3054 (void) encumber_msg();
3055
3056 /* can't rely on u.uz0 for detecting trap door-induced level change;
3057 it gets changed to reflect the new level before we can check it */
3058 assign_level(¤t_dungeon_level, &u.uz);
3059 if (trap) {
3060 switch (trap->ttyp) {
3061 case STATUE_TRAP:
3062 break;
3063 case HOLE:
3064 case TRAPDOOR:
3065 if (!Can_fall_thru(&u.uz) || u.ustuck)
3066 break;
3067 /*FALLTHRU*/
3068 default:
3069 if (!u.utrap) /* not already in the trap */
3070 dotrap(trap, 0);
3071 }
3072 }
3073 if (!Is_airlevel(&u.uz) && !Is_waterlevel(&u.uz) && !u.uswallow
3074 /* falling through trap door calls goto_level,
3075 and goto_level does its own pickup() call */
3076 && on_level(&u.uz, ¤t_dungeon_level))
3077 (void) pickup(1);
3078 return 1;
3079 }
3080
3081 /* shared code for climbing out of a pit */
3082 void
climb_pit()3083 climb_pit()
3084 {
3085 if (!u.utrap || u.utraptype != TT_PIT)
3086 return;
3087
3088 if (Passes_walls) {
3089 /* marked as trapped so they can pick things up */
3090 You("ascend from the pit.");
3091 reset_utrap(FALSE);
3092 fill_pit(u.ux, u.uy);
3093 vision_full_recalc = 1; /* vision limits change */
3094 } else if (!rn2(2) && sobj_at(BOULDER, u.ux, u.uy)) {
3095 Your("%s gets stuck in a crevice.", body_part(LEG));
3096 display_nhwindow(WIN_MESSAGE, FALSE);
3097 clear_nhwindow(WIN_MESSAGE);
3098 You("free your %s.", body_part(LEG));
3099 } else if ((Flying || is_clinger(youmonst.data)) && !Sokoban) {
3100 /* eg fell in pit, then poly'd to a flying monster;
3101 or used '>' to deliberately enter it */
3102 You("%s from the pit.", Flying ? "fly" : "climb");
3103 reset_utrap(FALSE);
3104 fill_pit(u.ux, u.uy);
3105 vision_full_recalc = 1; /* vision limits change */
3106 } else if (!(--u.utrap)) {
3107 reset_utrap(FALSE);
3108 You("%s to the edge of the pit.",
3109 (Sokoban && Levitation)
3110 ? "struggle against the air currents and float"
3111 : u.usteed ? "ride" : "crawl");
3112 fill_pit(u.ux, u.uy);
3113 vision_full_recalc = 1; /* vision limits change */
3114 } else if (u.dz || flags.verbose) {
3115 if (u.usteed)
3116 Norep("%s is still in a pit.", upstart(y_monnam(u.usteed)));
3117 else
3118 Norep((Hallucination && !rn2(5))
3119 ? "You've fallen, and you can't get up."
3120 : "You are still in a pit.");
3121 }
3122 }
3123
3124 STATIC_OVL void
dofiretrap(box)3125 dofiretrap(box)
3126 struct obj *box; /* null for floor trap */
3127 {
3128 boolean see_it = !Blind;
3129 int num, alt;
3130
3131 /* Bug: for box case, the equivalent of burn_floor_objects() ought
3132 * to be done upon its contents.
3133 */
3134
3135 if ((box && !carried(box)) ? is_pool(box->ox, box->oy) : Underwater) {
3136 pline("A cascade of steamy bubbles erupts from %s!",
3137 the(box ? xname(box) : surface(u.ux, u.uy)));
3138 if (Fire_resistance)
3139 You("are uninjured.");
3140 else
3141 losehp(rnd(3), "boiling water", KILLED_BY);
3142 return;
3143 }
3144 pline("A %s %s from %s!", tower_of_flame, box ? "bursts" : "erupts",
3145 the(box ? xname(box) : surface(u.ux, u.uy)));
3146 if (Fire_resistance) {
3147 shieldeff(u.ux, u.uy);
3148 num = rn2(2);
3149 } else if (Upolyd) {
3150 num = d(2, 4);
3151 switch (u.umonnum) {
3152 case PM_PAPER_GOLEM:
3153 alt = u.mhmax;
3154 break;
3155 case PM_STRAW_GOLEM:
3156 alt = u.mhmax / 2;
3157 break;
3158 case PM_WOOD_GOLEM:
3159 alt = u.mhmax / 4;
3160 break;
3161 case PM_LEATHER_GOLEM:
3162 alt = u.mhmax / 8;
3163 break;
3164 default:
3165 alt = 0;
3166 break;
3167 }
3168 if (alt > num)
3169 num = alt;
3170 if (u.mhmax > mons[u.umonnum].mlevel)
3171 u.mhmax -= rn2(min(u.mhmax, num + 1)), context.botl = 1;
3172 } else {
3173 num = d(2, 4);
3174 if (u.uhpmax > u.ulevel)
3175 u.uhpmax -= rn2(min(u.uhpmax, num + 1)), context.botl = 1;
3176 }
3177 if (!num)
3178 You("are uninjured.");
3179 else
3180 losehp(num, tower_of_flame, KILLED_BY_AN); /* fire damage */
3181 burn_away_slime();
3182
3183 if (burnarmor(&youmonst) || rn2(3)) {
3184 destroy_item(SCROLL_CLASS, AD_FIRE);
3185 destroy_item(SPBOOK_CLASS, AD_FIRE);
3186 destroy_item(POTION_CLASS, AD_FIRE);
3187 }
3188 if (!box && burn_floor_objects(u.ux, u.uy, see_it, TRUE) && !see_it)
3189 You("smell paper burning.");
3190 if (is_ice(u.ux, u.uy))
3191 melt_ice(u.ux, u.uy, (char *) 0);
3192 }
3193
3194 STATIC_OVL void
domagictrap()3195 domagictrap()
3196 {
3197 register int fate = rnd(20);
3198
3199 /* What happened to the poor sucker? */
3200
3201 if (fate < 10) {
3202 /* Most of the time, it creates some monsters. */
3203 int cnt = rnd(4);
3204
3205 /* blindness effects */
3206 if (!resists_blnd(&youmonst)) {
3207 You("are momentarily blinded by a flash of light!");
3208 make_blinded((long) rn1(5, 10), FALSE);
3209 if (!Blind)
3210 Your1(vision_clears);
3211 } else if (!Blind) {
3212 You_see("a flash of light!");
3213 }
3214
3215 /* deafness effects */
3216 if (!Deaf) {
3217 You_hear("a deafening roar!");
3218 incr_itimeout(&HDeaf, rn1(20, 30));
3219 context.botl = TRUE;
3220 } else {
3221 /* magic vibrations still hit you */
3222 You_feel("rankled.");
3223 incr_itimeout(&HDeaf, rn1(5, 15));
3224 context.botl = TRUE;
3225 }
3226 while (cnt--)
3227 (void) makemon((struct permonst *) 0, u.ux, u.uy, NO_MM_FLAGS);
3228 /* roar: wake monsters in vicinity, after placing trap-created ones */
3229 wake_nearto(u.ux, u.uy, 7 * 7);
3230 /* [flash: should probably also hit nearby gremlins with light] */
3231 } else
3232 switch (fate) {
3233 case 10:
3234 case 11:
3235 /* sometimes nothing happens */
3236 break;
3237 case 12: /* a flash of fire */
3238 dofiretrap((struct obj *) 0);
3239 break;
3240
3241 /* odd feelings */
3242 case 13:
3243 pline("A shiver runs up and down your %s!", body_part(SPINE));
3244 break;
3245 case 14:
3246 You_hear(Hallucination ? "the moon howling at you."
3247 : "distant howling.");
3248 break;
3249 case 15:
3250 if (on_level(&u.uz, &qstart_level))
3251 You_feel(
3252 "%slike the prodigal son.",
3253 (flags.female || (Upolyd && is_neuter(youmonst.data)))
3254 ? "oddly "
3255 : "");
3256 else
3257 You("suddenly yearn for %s.",
3258 Hallucination
3259 ? "Cleveland"
3260 : (In_quest(&u.uz) || at_dgn_entrance("The Quest"))
3261 ? "your nearby homeland"
3262 : "your distant homeland");
3263 break;
3264 case 16:
3265 Your("pack shakes violently!");
3266 break;
3267 case 17:
3268 You(Hallucination ? "smell hamburgers." : "smell charred flesh.");
3269 break;
3270 case 18:
3271 You_feel("tired.");
3272 break;
3273
3274 /* very occasionally something nice happens. */
3275 case 19: { /* tame nearby monsters */
3276 int i, j;
3277 struct monst *mtmp;
3278
3279 (void) adjattrib(A_CHA, 1, FALSE);
3280 for (i = -1; i <= 1; i++)
3281 for (j = -1; j <= 1; j++) {
3282 if (!isok(u.ux + i, u.uy + j))
3283 continue;
3284 mtmp = m_at(u.ux + i, u.uy + j);
3285 if (mtmp)
3286 (void) tamedog(mtmp, (struct obj *) 0);
3287 }
3288 break;
3289 }
3290 case 20: { /* uncurse stuff */
3291 struct obj pseudo;
3292 long save_conf = HConfusion;
3293
3294 pseudo = zeroobj; /* neither cursed nor blessed,
3295 and zero out oextra */
3296 pseudo.otyp = SCR_REMOVE_CURSE;
3297 HConfusion = 0L;
3298 (void) seffects(&pseudo);
3299 HConfusion = save_conf;
3300 break;
3301 }
3302 default:
3303 break;
3304 }
3305 }
3306
3307 /* Set an item on fire.
3308 * "force" means not to roll a luck-based protection check for the
3309 * item.
3310 * "x" and "y" are the coordinates to dump the contents of a
3311 * container, if it burns up.
3312 *
3313 * Return whether the object was destroyed.
3314 */
3315 boolean
fire_damage(obj,force,x,y)3316 fire_damage(obj, force, x, y)
3317 struct obj *obj;
3318 boolean force;
3319 xchar x, y;
3320 {
3321 int chance;
3322 struct obj *otmp, *ncobj;
3323 int in_sight = !Blind && couldsee(x, y); /* Don't care if it's lit */
3324 int dindx;
3325
3326 /* object might light in a controlled manner */
3327 if (catch_lit(obj))
3328 return FALSE;
3329
3330 if (Is_container(obj)) {
3331 switch (obj->otyp) {
3332 case ICE_BOX:
3333 return FALSE; /* Immune */
3334 case CHEST:
3335 chance = 40;
3336 break;
3337 case LARGE_BOX:
3338 chance = 30;
3339 break;
3340 default:
3341 chance = 20;
3342 break;
3343 }
3344 if ((!force && (Luck + 5) > rn2(chance))
3345 || (is_flammable(obj) && obj->oerodeproof))
3346 return FALSE;
3347 /* Container is burnt up - dump contents out */
3348 if (in_sight)
3349 pline("%s catches fire and burns.", Yname2(obj));
3350 if (Has_contents(obj)) {
3351 if (in_sight)
3352 pline("Its contents fall out.");
3353 for (otmp = obj->cobj; otmp; otmp = ncobj) {
3354 ncobj = otmp->nobj;
3355 obj_extract_self(otmp);
3356 if (!flooreffects(otmp, x, y, ""))
3357 place_object(otmp, x, y);
3358 }
3359 }
3360 setnotworn(obj);
3361 delobj(obj);
3362 return TRUE;
3363 } else if (!force && (Luck + 5) > rn2(20)) {
3364 /* chance per item of sustaining damage:
3365 * max luck (Luck==13): 10%
3366 * avg luck (Luck==0): 75%
3367 * awful luck (Luck<-4): 100%
3368 */
3369 return FALSE;
3370 } else if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS) {
3371 if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
3372 return FALSE;
3373 if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
3374 if (in_sight)
3375 pline("Smoke rises from %s.", the(xname(obj)));
3376 return FALSE;
3377 }
3378 dindx = (obj->oclass == SCROLL_CLASS) ? 3 : 4;
3379 if (in_sight)
3380 pline("%s %s.", Yname2(obj),
3381 destroy_strings[dindx][(obj->quan > 1L)]);
3382 setnotworn(obj);
3383 delobj(obj);
3384 return TRUE;
3385 } else if (obj->oclass == POTION_CLASS) {
3386 dindx = (obj->otyp != POT_OIL) ? 1 : 2;
3387 if (in_sight)
3388 pline("%s %s.", Yname2(obj),
3389 destroy_strings[dindx][(obj->quan > 1L)]);
3390 setnotworn(obj);
3391 delobj(obj);
3392 return TRUE;
3393 } else if (erode_obj(obj, (char *) 0, ERODE_BURN, EF_DESTROY)
3394 == ER_DESTROYED) {
3395 return TRUE;
3396 }
3397 return FALSE;
3398 }
3399
3400 /*
3401 * Apply fire_damage() to an entire chain.
3402 *
3403 * Return number of objects destroyed. --ALI
3404 */
3405 int
fire_damage_chain(chain,force,here,x,y)3406 fire_damage_chain(chain, force, here, x, y)
3407 struct obj *chain;
3408 boolean force, here;
3409 xchar x, y;
3410 {
3411 struct obj *obj, *nobj;
3412 int num = 0;
3413
3414 for (obj = chain; obj; obj = nobj) {
3415 nobj = here ? obj->nexthere : obj->nobj;
3416 if (fire_damage(obj, force, x, y))
3417 ++num;
3418 }
3419
3420 if (num && (Blind && !couldsee(x, y)))
3421 You("smell smoke.");
3422 return num;
3423 }
3424
3425 /* obj has been thrown or dropped into lava; damage is worse than mere fire */
3426 boolean
lava_damage(obj,x,y)3427 lava_damage(obj, x, y)
3428 struct obj *obj;
3429 xchar x, y;
3430 {
3431 int otyp = obj->otyp, ocls = obj->oclass;
3432
3433 /* the Amulet, invocation items, and Rider corpses are never destroyed
3434 (let Book of the Dead fall through to fire_damage() to get feedback) */
3435 if (obj_resists(obj, 0, 0) && otyp != SPE_BOOK_OF_THE_DEAD)
3436 return FALSE;
3437 /* destroy liquid (venom), wax, veggy, flesh, paper (except for scrolls
3438 and books--let fire damage deal with them), cloth, leather, wood, bone
3439 unless it's inherently or explicitly fireproof or contains something;
3440 note: potions are glass so fall through to fire_damage() and boil */
3441 if (objects[otyp].oc_material < DRAGON_HIDE
3442 && ocls != SCROLL_CLASS && ocls != SPBOOK_CLASS
3443 && objects[otyp].oc_oprop != FIRE_RES
3444 && otyp != WAN_FIRE && otyp != FIRE_HORN
3445 /* assumes oerodeproof isn't overloaded for some other purpose on
3446 non-eroding items */
3447 && !obj->oerodeproof
3448 /* fire_damage() knows how to deal with containers and contents */
3449 && !Has_contents(obj)) {
3450 if (cansee(x, y)) {
3451 /* this feedback is pretty clunky and can become very verbose
3452 when former contents of a burned container get here via
3453 flooreffects() */
3454 if (obj == thrownobj || obj == kickedobj)
3455 pline("%s %s up!", is_plural(obj) ? "They" : "It",
3456 otense(obj, "burn"));
3457 else
3458 You_see("%s hit lava and burn up!", doname(obj));
3459 }
3460 if (carried(obj)) { /* shouldn't happen */
3461 remove_worn_item(obj, TRUE);
3462 useupall(obj);
3463 } else
3464 delobj(obj);
3465 return TRUE;
3466 }
3467 return fire_damage(obj, TRUE, x, y);
3468 }
3469
3470 void
acid_damage(obj)3471 acid_damage(obj)
3472 struct obj *obj;
3473 {
3474 /* Scrolls but not spellbooks can be erased by acid. */
3475 struct monst *victim;
3476 boolean vismon;
3477
3478 if (!obj)
3479 return;
3480
3481 victim = carried(obj) ? &youmonst : mcarried(obj) ? obj->ocarry : NULL;
3482 vismon = victim && (victim != &youmonst) && canseemon(victim);
3483
3484 if (obj->greased) {
3485 grease_protect(obj, (char *) 0, victim);
3486 } else if (obj->oclass == SCROLL_CLASS && obj->otyp != SCR_BLANK_PAPER) {
3487 if (obj->otyp != SCR_BLANK_PAPER
3488 #ifdef MAIL
3489 && obj->otyp != SCR_MAIL
3490 #endif
3491 ) {
3492 if (!Blind) {
3493 if (victim == &youmonst)
3494 pline("Your %s.", aobjnam(obj, "fade"));
3495 else if (vismon)
3496 pline("%s %s.", s_suffix(Monnam(victim)),
3497 aobjnam(obj, "fade"));
3498 }
3499 }
3500 obj->otyp = SCR_BLANK_PAPER;
3501 obj->spe = 0;
3502 obj->dknown = 0;
3503 } else
3504 erode_obj(obj, (char *) 0, ERODE_CORRODE, EF_GREASE | EF_VERBOSE);
3505 }
3506
3507 /* context for water_damage(), managed by water_damage_chain();
3508 when more than one stack of potions of acid explode while processing
3509 a chain of objects, use alternate phrasing after the first message */
3510 static struct h2o_ctx {
3511 int dkn_boom, unk_boom; /* track dknown, !dknown separately */
3512 boolean ctx_valid;
3513 } acid_ctx = { 0, 0, FALSE };
3514
3515 /* Get an object wet and damage it appropriately.
3516 * "ostr", if present, is used instead of the object name in some
3517 * messages.
3518 * "force" means not to roll luck to protect some objects.
3519 * Returns an erosion return value (ER_*)
3520 */
3521 int
water_damage(obj,ostr,force)3522 water_damage(obj, ostr, force)
3523 struct obj *obj;
3524 const char *ostr;
3525 boolean force;
3526 {
3527 if (!obj)
3528 return ER_NOTHING;
3529
3530 if (snuff_lit(obj))
3531 return ER_DAMAGED;
3532
3533 if (!ostr)
3534 ostr = cxname(obj);
3535
3536 if (obj->otyp == CAN_OF_GREASE && obj->spe > 0) {
3537 return ER_NOTHING;
3538 } else if (obj->otyp == TOWEL && obj->spe < 7) {
3539 wet_a_towel(obj, rnd(7), TRUE);
3540 return ER_NOTHING;
3541 } else if (obj->greased) {
3542 if (!rn2(2))
3543 obj->greased = 0;
3544 if (carried(obj))
3545 update_inventory();
3546 return ER_GREASED;
3547 } else if (Is_container(obj) && !Is_box(obj)
3548 && (obj->otyp != OILSKIN_SACK || (obj->cursed && !rn2(3)))) {
3549 if (carried(obj))
3550 pline("Water gets into your %s!", ostr);
3551
3552 water_damage_chain(obj->cobj, FALSE);
3553 return ER_DAMAGED; /* contents were damaged */
3554 } else if (obj->otyp == OILSKIN_SACK) {
3555 if (carried(obj))
3556 pline("Some water slides right off your %s.", ostr);
3557 makeknown(OILSKIN_SACK);
3558 /* not actually damaged, but because we /didn't/ get the "water
3559 gets into!" message, the player now has more information and
3560 thus we need to waste any potion they may have used (also,
3561 flavourwise the water is now on the floor) */
3562 return ER_DAMAGED;
3563 } else if (!force && (Luck + 5) > rn2(20)) {
3564 /* chance per item of sustaining damage:
3565 * max luck: 10%
3566 * avg luck (Luck==0): 75%
3567 * awful luck (Luck<-4): 100%
3568 */
3569 return ER_NOTHING;
3570 } else if (obj->oclass == SCROLL_CLASS) {
3571 if (obj->otyp == SCR_BLANK_PAPER
3572 #ifdef MAIL
3573 || obj->otyp == SCR_MAIL
3574 #endif
3575 ) return 0;
3576 if (carried(obj))
3577 pline("Your %s %s.", ostr, vtense(ostr, "fade"));
3578
3579 obj->otyp = SCR_BLANK_PAPER;
3580 obj->dknown = 0;
3581 obj->spe = 0;
3582 if (carried(obj))
3583 update_inventory();
3584 return ER_DAMAGED;
3585 } else if (obj->oclass == SPBOOK_CLASS) {
3586 if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
3587 pline("Steam rises from %s.", the(xname(obj)));
3588 return 0;
3589 } else if (obj->otyp == SPE_BLANK_PAPER) {
3590 return 0;
3591 }
3592 if (carried(obj))
3593 pline("Your %s %s.", ostr, vtense(ostr, "fade"));
3594
3595 if (obj->otyp == SPE_NOVEL) {
3596 obj->novelidx = 0;
3597 free_oname(obj);
3598 }
3599
3600 obj->otyp = SPE_BLANK_PAPER;
3601 obj->dknown = 0;
3602 if (carried(obj))
3603 update_inventory();
3604 return ER_DAMAGED;
3605 } else if (obj->oclass == POTION_CLASS) {
3606 if (obj->otyp == POT_ACID) {
3607 char *bufp;
3608 boolean one = (obj->quan == 1L), update = carried(obj),
3609 exploded = FALSE;
3610
3611 if (Blind && !carried(obj))
3612 obj->dknown = 0;
3613 if (acid_ctx.ctx_valid)
3614 exploded = ((obj->dknown ? acid_ctx.dkn_boom
3615 : acid_ctx.unk_boom) > 0);
3616 /* First message is
3617 * "a [potion|<color> potion|potion of acid] explodes"
3618 * depending on obj->dknown (potion has been seen) and
3619 * objects[POT_ACID].oc_name_known (fully discovered),
3620 * or "some {plural version} explode" when relevant.
3621 * Second and subsequent messages for same chain and
3622 * matching dknown status are
3623 * "another [potion|<color> &c] explodes" or plural
3624 * variant.
3625 */
3626 bufp = simpleonames(obj);
3627 pline("%s %s %s!", /* "A potion explodes!" */
3628 !exploded ? (one ? "A" : "Some")
3629 : (one ? "Another" : "More"),
3630 bufp, vtense(bufp, "explode"));
3631 if (acid_ctx.ctx_valid) {
3632 if (obj->dknown)
3633 acid_ctx.dkn_boom++;
3634 else
3635 acid_ctx.unk_boom++;
3636 }
3637 setnotworn(obj);
3638 delobj(obj);
3639 if (update)
3640 update_inventory();
3641 return ER_DESTROYED;
3642 } else if (obj->odiluted) {
3643 if (carried(obj))
3644 pline("Your %s %s further.", ostr, vtense(ostr, "dilute"));
3645
3646 obj->otyp = POT_WATER;
3647 obj->dknown = 0;
3648 obj->blessed = obj->cursed = 0;
3649 obj->odiluted = 0;
3650 if (carried(obj))
3651 update_inventory();
3652 return ER_DAMAGED;
3653 } else if (obj->otyp != POT_WATER) {
3654 if (carried(obj))
3655 pline("Your %s %s.", ostr, vtense(ostr, "dilute"));
3656
3657 obj->odiluted++;
3658 if (carried(obj))
3659 update_inventory();
3660 return ER_DAMAGED;
3661 }
3662 } else {
3663 return erode_obj(obj, ostr, ERODE_RUST, EF_NONE);
3664 }
3665 return ER_NOTHING;
3666 }
3667
3668 void
water_damage_chain(obj,here)3669 water_damage_chain(obj, here)
3670 struct obj *obj;
3671 boolean here;
3672 {
3673 struct obj *otmp;
3674
3675 /* initialize acid context: so far, neither seen (dknown) potions of
3676 acid nor unseen have exploded during this water damage sequence */
3677 acid_ctx.dkn_boom = acid_ctx.unk_boom = 0;
3678 acid_ctx.ctx_valid = TRUE;
3679
3680 for (; obj; obj = otmp) {
3681 otmp = here ? obj->nexthere : obj->nobj;
3682 water_damage(obj, (char *) 0, FALSE);
3683 }
3684
3685 /* reset acid context */
3686 acid_ctx.dkn_boom = acid_ctx.unk_boom = 0;
3687 acid_ctx.ctx_valid = FALSE;
3688 }
3689
3690 /*
3691 * This function is potentially expensive - rolling
3692 * inventory list multiple times. Luckily it's seldom needed.
3693 * Returns TRUE if disrobing made player unencumbered enough to
3694 * crawl out of the current predicament.
3695 */
3696 STATIC_OVL boolean
emergency_disrobe(lostsome)3697 emergency_disrobe(lostsome)
3698 boolean *lostsome;
3699 {
3700 int invc = inv_cnt(TRUE);
3701
3702 while (near_capacity() > (Punished ? UNENCUMBERED : SLT_ENCUMBER)) {
3703 register struct obj *obj, *otmp = (struct obj *) 0;
3704 register int i;
3705
3706 /* Pick a random object */
3707 if (invc > 0) {
3708 i = rn2(invc);
3709 for (obj = invent; obj; obj = obj->nobj) {
3710 /*
3711 * Undroppables are: body armor, boots, gloves,
3712 * amulets, and rings because of the time and effort
3713 * in removing them + loadstone and other cursed stuff
3714 * for obvious reasons.
3715 */
3716 if (!((obj->otyp == LOADSTONE && obj->cursed) || obj == uamul
3717 || obj == uleft || obj == uright || obj == ublindf
3718 || obj == uarm || obj == uarmc || obj == uarmg
3719 || obj == uarmf || obj == uarmu
3720 || (obj->cursed && (obj == uarmh || obj == uarms))
3721 || welded(obj)))
3722 otmp = obj;
3723 /* reached the mark and found some stuff to drop? */
3724 if (--i < 0 && otmp)
3725 break;
3726
3727 /* else continue */
3728 }
3729 }
3730 if (!otmp)
3731 return FALSE; /* nothing to drop! */
3732 if (otmp->owornmask)
3733 remove_worn_item(otmp, FALSE);
3734 *lostsome = TRUE;
3735 dropx(otmp);
3736 invc--;
3737 }
3738 return TRUE;
3739 }
3740
3741
3742 /* return TRUE iff player relocated */
3743 boolean
drown()3744 drown()
3745 {
3746 const char *pool_of_water;
3747 boolean inpool_ok = FALSE, crawl_ok;
3748 int i, x, y;
3749
3750 feel_newsym(u.ux, u.uy); /* in case Blind, map the water here */
3751 /* happily wading in the same contiguous pool */
3752 if (u.uinwater && is_pool(u.ux - u.dx, u.uy - u.dy)
3753 && (Swimming || Amphibious)) {
3754 /* water effects on objects every now and then */
3755 if (!rn2(5))
3756 inpool_ok = TRUE;
3757 else
3758 return FALSE;
3759 }
3760
3761 if (!u.uinwater) {
3762 You("%s into the %s%c", Is_waterlevel(&u.uz) ? "plunge" : "fall",
3763 hliquid("water"),
3764 Amphibious || Swimming ? '.' : '!');
3765 if (!Swimming && !Is_waterlevel(&u.uz))
3766 You("sink like %s.", Hallucination ? "the Titanic" : "a rock");
3767 }
3768
3769 water_damage_chain(invent, FALSE);
3770
3771 if (u.umonnum == PM_GREMLIN && rn2(3))
3772 (void) split_mon(&youmonst, (struct monst *) 0);
3773 else if (u.umonnum == PM_IRON_GOLEM) {
3774 You("rust!");
3775 i = Maybe_Half_Phys(d(2, 6));
3776 if (u.mhmax > i)
3777 u.mhmax -= i;
3778 losehp(i, "rusting away", KILLED_BY);
3779 }
3780 if (inpool_ok)
3781 return FALSE;
3782
3783 if ((i = number_leashed()) > 0) {
3784 pline_The("leash%s slip%s loose.", (i > 1) ? "es" : "",
3785 (i > 1) ? "" : "s");
3786 unleash_all();
3787 }
3788
3789 if (Amphibious || Swimming) {
3790 if (Amphibious) {
3791 if (flags.verbose)
3792 pline("But you aren't drowning.");
3793 if (!Is_waterlevel(&u.uz)) {
3794 if (Hallucination)
3795 Your("keel hits the bottom.");
3796 else
3797 You("touch bottom.");
3798 }
3799 }
3800 if (Punished) {
3801 unplacebc();
3802 placebc();
3803 }
3804 vision_recalc(2); /* unsee old position */
3805 u.uinwater = 1;
3806 under_water(1);
3807 vision_full_recalc = 1;
3808 return FALSE;
3809 }
3810 if ((Teleportation || can_teleport(youmonst.data)) && !Unaware
3811 && (Teleport_control || rn2(3) < Luck + 2)) {
3812 You("attempt a teleport spell."); /* utcsri!carroll */
3813 if (!level.flags.noteleport) {
3814 (void) dotele(FALSE);
3815 if (!is_pool(u.ux, u.uy))
3816 return TRUE;
3817 } else
3818 pline_The("attempted teleport spell fails.");
3819 }
3820 if (u.usteed) {
3821 dismount_steed(DISMOUNT_GENERIC);
3822 if (!is_pool(u.ux, u.uy))
3823 return TRUE;
3824 }
3825 crawl_ok = FALSE;
3826 x = y = 0; /* lint suppression */
3827 /* if sleeping, wake up now so that we don't crawl out of water
3828 while still asleep; we can't do that the same way that waking
3829 due to combat is handled; note unmul() clears u.usleep */
3830 if (u.usleep)
3831 unmul("Suddenly you wake up!");
3832 /* being doused will revive from fainting */
3833 if (is_fainted())
3834 reset_faint();
3835 /* can't crawl if unable to move (crawl_ok flag stays false) */
3836 if (multi < 0 || (Upolyd && !youmonst.data->mmove))
3837 goto crawl;
3838 /* look around for a place to crawl to */
3839 for (i = 0; i < 100; i++) {
3840 x = rn1(3, u.ux - 1);
3841 y = rn1(3, u.uy - 1);
3842 if (crawl_destination(x, y)) {
3843 crawl_ok = TRUE;
3844 goto crawl;
3845 }
3846 }
3847 /* one more scan */
3848 for (x = u.ux - 1; x <= u.ux + 1; x++)
3849 for (y = u.uy - 1; y <= u.uy + 1; y++)
3850 if (crawl_destination(x, y)) {
3851 crawl_ok = TRUE;
3852 goto crawl;
3853 }
3854 crawl:
3855 if (crawl_ok) {
3856 boolean lost = FALSE;
3857 /* time to do some strip-tease... */
3858 boolean succ = Is_waterlevel(&u.uz) ? TRUE : emergency_disrobe(&lost);
3859
3860 You("try to crawl out of the %s.", hliquid("water"));
3861 if (lost)
3862 You("dump some of your gear to lose weight...");
3863 if (succ) {
3864 pline("Pheew! That was close.");
3865 teleds(x, y, TRUE);
3866 return TRUE;
3867 }
3868 /* still too much weight */
3869 pline("But in vain.");
3870 }
3871 u.uinwater = 1;
3872 You("drown.");
3873 for (i = 0; i < 5; i++) { /* arbitrary number of loops */
3874 /* killer format and name are reconstructed every iteration
3875 because lifesaving resets them */
3876 pool_of_water = waterbody_name(u.ux, u.uy);
3877 killer.format = KILLED_BY_AN;
3878 /* avoid "drowned in [a] water" */
3879 if (!strcmp(pool_of_water, "water"))
3880 pool_of_water = "deep water", killer.format = KILLED_BY;
3881 Strcpy(killer.name, pool_of_water);
3882 done(DROWNING);
3883 /* oops, we're still alive. better get out of the water. */
3884 if (safe_teleds(TRUE))
3885 break; /* successful life-save */
3886 /* nowhere safe to land; repeat drowning loop... */
3887 pline("You're still drowning.");
3888 }
3889 if (u.uinwater) {
3890 u.uinwater = 0;
3891 You("find yourself back %s.",
3892 Is_waterlevel(&u.uz) ? "in an air bubble" : "on land");
3893 }
3894 return TRUE;
3895 }
3896
3897 void
drain_en(n)3898 drain_en(n)
3899 int n;
3900 {
3901 if (!u.uenmax) {
3902 /* energy is completely gone */
3903 You_feel("momentarily lethargic.");
3904 } else {
3905 /* throttle further loss a bit when there's not much left to lose */
3906 if (n > u.uenmax || n > u.ulevel)
3907 n = rnd(n);
3908
3909 You_feel("your magical energy drain away%c", (n > u.uen) ? '!' : '.');
3910 u.uen -= n;
3911 if (u.uen < 0) {
3912 u.uenmax -= rnd(-u.uen);
3913 if (u.uenmax < 0)
3914 u.uenmax = 0;
3915 u.uen = 0;
3916 }
3917 context.botl = 1;
3918 }
3919 }
3920
3921 /* disarm a trap */
3922 int
dountrap()3923 dountrap()
3924 {
3925 if (near_capacity() >= HVY_ENCUMBER) {
3926 pline("You're too strained to do that.");
3927 return 0;
3928 }
3929 if ((nohands(youmonst.data) && !webmaker(youmonst.data))
3930 || !youmonst.data->mmove) {
3931 pline("And just how do you expect to do that?");
3932 return 0;
3933 } else if (u.ustuck && sticks(youmonst.data)) {
3934 pline("You'll have to let go of %s first.", mon_nam(u.ustuck));
3935 return 0;
3936 }
3937 if (u.ustuck || (welded(uwep) && bimanual(uwep))) {
3938 Your("%s seem to be too busy for that.", makeplural(body_part(HAND)));
3939 return 0;
3940 }
3941 return untrap(FALSE);
3942 }
3943
3944 /* Probability of disabling a trap. Helge Hafting */
3945 STATIC_OVL int
untrap_prob(ttmp)3946 untrap_prob(ttmp)
3947 struct trap *ttmp;
3948 {
3949 int chance = 3;
3950
3951 /* Only spiders know how to deal with webs reliably */
3952 if (ttmp->ttyp == WEB && !webmaker(youmonst.data))
3953 chance = 30;
3954 if (Confusion || Hallucination)
3955 chance++;
3956 if (Blind)
3957 chance++;
3958 if (Stunned)
3959 chance += 2;
3960 if (Fumbling)
3961 chance *= 2;
3962 /* Your own traps are better known than others. */
3963 if (ttmp && ttmp->madeby_u)
3964 chance--;
3965 if (Role_if(PM_ROGUE)) {
3966 if (rn2(2 * MAXULEV) < u.ulevel)
3967 chance--;
3968 if (u.uhave.questart && chance > 1)
3969 chance--;
3970 } else if (Role_if(PM_RANGER) && chance > 1)
3971 chance--;
3972 return rn2(chance);
3973 }
3974
3975 /* Replace trap with object(s). Helge Hafting */
3976 void
cnv_trap_obj(otyp,cnt,ttmp,bury_it)3977 cnv_trap_obj(otyp, cnt, ttmp, bury_it)
3978 int otyp;
3979 int cnt;
3980 struct trap *ttmp;
3981 boolean bury_it;
3982 {
3983 struct obj *otmp = mksobj(otyp, TRUE, FALSE);
3984
3985 otmp->quan = cnt;
3986 otmp->owt = weight(otmp);
3987 /* Only dart traps are capable of being poisonous */
3988 if (otyp != DART)
3989 otmp->opoisoned = 0;
3990 place_object(otmp, ttmp->tx, ttmp->ty);
3991 if (bury_it) {
3992 /* magical digging first disarms this trap, then will unearth it */
3993 (void) bury_an_obj(otmp, (boolean *) 0);
3994 } else {
3995 /* Sell your own traps only... */
3996 if (ttmp->madeby_u)
3997 sellobj(otmp, ttmp->tx, ttmp->ty);
3998 stackobj(otmp);
3999 }
4000 newsym(ttmp->tx, ttmp->ty);
4001 if (u.utrap && ttmp->tx == u.ux && ttmp->ty == u.uy)
4002 reset_utrap(TRUE);
4003 deltrap(ttmp);
4004 }
4005
4006 /* while attempting to disarm an adjacent trap, we've fallen into it */
4007 STATIC_OVL void
move_into_trap(ttmp)4008 move_into_trap(ttmp)
4009 struct trap *ttmp;
4010 {
4011 int bc = 0;
4012 xchar x = ttmp->tx, y = ttmp->ty, bx, by, cx, cy;
4013 boolean unused;
4014
4015 bx = by = cx = cy = 0; /* lint suppression */
4016 /* we know there's no monster in the way, and we're not trapped */
4017 if (!Punished
4018 || drag_ball(x, y, &bc, &bx, &by, &cx, &cy, &unused, TRUE)) {
4019 u.ux0 = u.ux, u.uy0 = u.uy;
4020 u.ux = x, u.uy = y;
4021 u.umoved = TRUE;
4022 newsym(u.ux0, u.uy0);
4023 vision_recalc(1);
4024 check_leash(u.ux0, u.uy0);
4025 if (Punished)
4026 move_bc(0, bc, bx, by, cx, cy);
4027 /* marking the trap unseen forces dotrap() to treat it like a new
4028 discovery and prevents pickup() -> look_here() -> check_here()
4029 from giving a redundant "there is a <trap> here" message when
4030 there are objects covering this trap */
4031 ttmp->tseen = 0; /* hack for check_here() */
4032 /* trigger the trap */
4033 iflags.failing_untrap++; /* spoteffects() -> dotrap(,FAILEDUNTRAP) */
4034 spoteffects(TRUE); /* pickup() + dotrap() */
4035 iflags.failing_untrap--;
4036 /* this should no longer be necessary; before the failing_untrap
4037 hack, Flying hero would not trigger an unseen bear trap and
4038 setting it not-yet-seen above resulted in leaving it hidden */
4039 if ((ttmp = t_at(u.ux, u.uy)) != 0)
4040 ttmp->tseen = 1;
4041 exercise(A_WIS, FALSE);
4042 }
4043 }
4044
4045 /* 0: doesn't even try
4046 * 1: tries and fails
4047 * 2: succeeds
4048 */
4049 STATIC_OVL int
try_disarm(ttmp,force_failure)4050 try_disarm(ttmp, force_failure)
4051 struct trap *ttmp;
4052 boolean force_failure;
4053 {
4054 struct monst *mtmp = m_at(ttmp->tx, ttmp->ty);
4055 int ttype = ttmp->ttyp;
4056 boolean under_u = (!u.dx && !u.dy);
4057 boolean holdingtrap = (ttype == BEAR_TRAP || ttype == WEB);
4058
4059 /* Test for monster first, monsters are displayed instead of trap. */
4060 if (mtmp && (!mtmp->mtrapped || !holdingtrap)) {
4061 pline("%s is in the way.", Monnam(mtmp));
4062 return 0;
4063 }
4064 /* We might be forced to move onto the trap's location. */
4065 if (sobj_at(BOULDER, ttmp->tx, ttmp->ty) && !Passes_walls && !under_u) {
4066 There("is a boulder in your way.");
4067 return 0;
4068 }
4069 /* duplicate tight-space checks from test_move */
4070 if (u.dx && u.dy && bad_rock(youmonst.data, u.ux, ttmp->ty)
4071 && bad_rock(youmonst.data, ttmp->tx, u.uy)) {
4072 if ((invent && (inv_weight() + weight_cap() > 600))
4073 || bigmonst(youmonst.data)) {
4074 /* don't allow untrap if they can't get thru to it */
4075 You("are unable to reach the %s!",
4076 defsyms[trap_to_defsym(ttype)].explanation);
4077 return 0;
4078 }
4079 }
4080 /* untrappable traps are located on the ground. */
4081 if (!can_reach_floor(under_u)) {
4082 if (u.usteed && P_SKILL(P_RIDING) < P_BASIC)
4083 rider_cant_reach();
4084 else
4085 You("are unable to reach the %s!",
4086 defsyms[trap_to_defsym(ttype)].explanation);
4087 return 0;
4088 }
4089
4090 /* Will our hero succeed? */
4091 if (force_failure || untrap_prob(ttmp)) {
4092 if (rnl(5)) {
4093 pline("Whoops...");
4094 if (mtmp) { /* must be a trap that holds monsters */
4095 if (ttype == BEAR_TRAP) {
4096 if (mtmp->mtame)
4097 abuse_dog(mtmp);
4098 mtmp->mhp -= rnd(4);
4099 if (DEADMONSTER(mtmp))
4100 killed(mtmp);
4101 } else if (ttype == WEB) {
4102 if (!webmaker(youmonst.data)) {
4103 struct trap *ttmp2 = maketrap(u.ux, u.uy, WEB);
4104
4105 if (ttmp2) {
4106 pline_The(
4107 "webbing sticks to you. You're caught too!");
4108 dotrap(ttmp2, NOWEBMSG);
4109 if (u.usteed && u.utrap) {
4110 /* you, not steed, are trapped */
4111 dismount_steed(DISMOUNT_FELL);
4112 }
4113 }
4114 } else
4115 pline("%s remains entangled.", Monnam(mtmp));
4116 }
4117 } else if (under_u) {
4118 /* [don't need the iflags.failing_untrap hack here] */
4119 dotrap(ttmp, FAILEDUNTRAP);
4120 } else {
4121 move_into_trap(ttmp);
4122 }
4123 } else {
4124 pline("%s %s is difficult to %s.",
4125 ttmp->madeby_u ? "Your" : under_u ? "This" : "That",
4126 defsyms[trap_to_defsym(ttype)].explanation,
4127 (ttype == WEB) ? "remove" : "disarm");
4128 }
4129 return 1;
4130 }
4131 return 2;
4132 }
4133
4134 STATIC_OVL void
reward_untrap(ttmp,mtmp)4135 reward_untrap(ttmp, mtmp)
4136 struct trap *ttmp;
4137 struct monst *mtmp;
4138 {
4139 if (!ttmp->madeby_u) {
4140 if (rnl(10) < 8 && !mtmp->mpeaceful && !mtmp->msleeping
4141 && !mtmp->mfrozen && !mindless(mtmp->data)
4142 && mtmp->data->mlet != S_HUMAN) {
4143 mtmp->mpeaceful = 1;
4144 set_malign(mtmp); /* reset alignment */
4145 pline("%s is grateful.", Monnam(mtmp));
4146 }
4147 /* Helping someone out of a trap is a nice thing to do,
4148 * A lawful may be rewarded, but not too often. */
4149 if (!rn2(3) && !rnl(8) && u.ualign.type == A_LAWFUL) {
4150 adjalign(1);
4151 You_feel("that you did the right thing.");
4152 }
4153 }
4154 }
4155
4156 STATIC_OVL int
disarm_holdingtrap(ttmp)4157 disarm_holdingtrap(ttmp) /* Helge Hafting */
4158 struct trap *ttmp;
4159 {
4160 struct monst *mtmp;
4161 int fails = try_disarm(ttmp, FALSE);
4162
4163 if (fails < 2)
4164 return fails;
4165
4166 /* ok, disarm it. */
4167
4168 /* untrap the monster, if any.
4169 There's no need for a cockatrice test, only the trap is touched */
4170 if ((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) {
4171 mtmp->mtrapped = 0;
4172 You("remove %s %s from %s.", the_your[ttmp->madeby_u],
4173 (ttmp->ttyp == BEAR_TRAP) ? "bear trap" : "webbing",
4174 mon_nam(mtmp));
4175 reward_untrap(ttmp, mtmp);
4176 } else {
4177 if (ttmp->ttyp == BEAR_TRAP) {
4178 You("disarm %s bear trap.", the_your[ttmp->madeby_u]);
4179 cnv_trap_obj(BEARTRAP, 1, ttmp, FALSE);
4180 } else /* if (ttmp->ttyp == WEB) */ {
4181 You("succeed in removing %s web.", the_your[ttmp->madeby_u]);
4182 deltrap(ttmp);
4183 }
4184 }
4185 newsym(u.ux + u.dx, u.uy + u.dy);
4186 return 1;
4187 }
4188
4189 STATIC_OVL int
disarm_landmine(ttmp)4190 disarm_landmine(ttmp) /* Helge Hafting */
4191 struct trap *ttmp;
4192 {
4193 int fails = try_disarm(ttmp, FALSE);
4194
4195 if (fails < 2)
4196 return fails;
4197 You("disarm %s land mine.", the_your[ttmp->madeby_u]);
4198 cnv_trap_obj(LAND_MINE, 1, ttmp, FALSE);
4199 return 1;
4200 }
4201
4202 /* getobj will filter down to cans of grease and known potions of oil */
4203 static NEARDATA const char oil[] = { ALL_CLASSES, TOOL_CLASS, POTION_CLASS,
4204 0 };
4205
4206 /* it may not make much sense to use grease on floor boards, but so what? */
4207 STATIC_OVL int
disarm_squeaky_board(ttmp)4208 disarm_squeaky_board(ttmp)
4209 struct trap *ttmp;
4210 {
4211 struct obj *obj;
4212 boolean bad_tool;
4213 int fails;
4214
4215 obj = getobj(oil, "untrap with");
4216 if (!obj)
4217 return 0;
4218
4219 bad_tool = (obj->cursed
4220 || ((obj->otyp != POT_OIL || obj->lamplit)
4221 && (obj->otyp != CAN_OF_GREASE || !obj->spe)));
4222 fails = try_disarm(ttmp, bad_tool);
4223 if (fails < 2)
4224 return fails;
4225
4226 /* successfully used oil or grease to fix squeaky board */
4227 if (obj->otyp == CAN_OF_GREASE) {
4228 consume_obj_charge(obj, TRUE);
4229 } else {
4230 useup(obj); /* oil */
4231 makeknown(POT_OIL);
4232 }
4233 You("repair the squeaky board."); /* no madeby_u */
4234 deltrap(ttmp);
4235 newsym(u.ux + u.dx, u.uy + u.dy);
4236 more_experienced(1, 5);
4237 newexplevel();
4238 return 1;
4239 }
4240
4241 /* removes traps that shoot arrows, darts, etc. */
4242 STATIC_OVL int
disarm_shooting_trap(ttmp,otyp)4243 disarm_shooting_trap(ttmp, otyp)
4244 struct trap *ttmp;
4245 int otyp;
4246 {
4247 int fails = try_disarm(ttmp, FALSE);
4248
4249 if (fails < 2)
4250 return fails;
4251 You("disarm %s trap.", the_your[ttmp->madeby_u]);
4252 cnv_trap_obj(otyp, 50 - rnl(50), ttmp, FALSE);
4253 return 1;
4254 }
4255
4256 /* Is the weight too heavy?
4257 * Formula as in near_capacity() & check_capacity() */
4258 STATIC_OVL int
try_lift(mtmp,ttmp,wt,stuff)4259 try_lift(mtmp, ttmp, wt, stuff)
4260 struct monst *mtmp;
4261 struct trap *ttmp;
4262 int wt;
4263 boolean stuff;
4264 {
4265 int wc = weight_cap();
4266
4267 if (((wt * 2) / wc) >= HVY_ENCUMBER) {
4268 pline("%s is %s for you to lift.", Monnam(mtmp),
4269 stuff ? "carrying too much" : "too heavy");
4270 if (!ttmp->madeby_u && !mtmp->mpeaceful && mtmp->mcanmove
4271 && !mindless(mtmp->data) && mtmp->data->mlet != S_HUMAN
4272 && rnl(10) < 3) {
4273 mtmp->mpeaceful = 1;
4274 set_malign(mtmp); /* reset alignment */
4275 pline("%s thinks it was nice of you to try.", Monnam(mtmp));
4276 }
4277 return 0;
4278 }
4279 return 1;
4280 }
4281
4282 /* Help trapped monster (out of a (spiked) pit) */
4283 STATIC_OVL int
help_monster_out(mtmp,ttmp)4284 help_monster_out(mtmp, ttmp)
4285 struct monst *mtmp;
4286 struct trap *ttmp;
4287 {
4288 int wt;
4289 struct obj *otmp;
4290 boolean uprob;
4291
4292 /*
4293 * This works when levitating too -- consistent with the ability
4294 * to hit monsters while levitating.
4295 *
4296 * Should perhaps check that our hero has arms/hands at the
4297 * moment. Helping can also be done by engulfing...
4298 *
4299 * Test the monster first - monsters are displayed before traps.
4300 */
4301 if (!mtmp->mtrapped) {
4302 pline("%s isn't trapped.", Monnam(mtmp));
4303 return 0;
4304 }
4305 /* Do you have the necessary capacity to lift anything? */
4306 if (check_capacity((char *) 0))
4307 return 1;
4308
4309 /* Will our hero succeed? */
4310 if ((uprob = untrap_prob(ttmp)) && !mtmp->msleeping && mtmp->mcanmove) {
4311 You("try to reach out your %s, but %s backs away skeptically.",
4312 makeplural(body_part(ARM)), mon_nam(mtmp));
4313 return 1;
4314 }
4315
4316 /* is it a cockatrice?... */
4317 if (touch_petrifies(mtmp->data) && !uarmg && !Stone_resistance) {
4318 You("grab the trapped %s using your bare %s.", mtmp->data->mname,
4319 makeplural(body_part(HAND)));
4320
4321 if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) {
4322 display_nhwindow(WIN_MESSAGE, FALSE);
4323 } else {
4324 char kbuf[BUFSZ];
4325
4326 Sprintf(kbuf, "trying to help %s out of a pit",
4327 an(mtmp->data->mname));
4328 instapetrify(kbuf);
4329 return 1;
4330 }
4331 }
4332 /* need to do cockatrice check first if sleeping or paralyzed */
4333 if (uprob) {
4334 You("try to grab %s, but cannot get a firm grasp.", mon_nam(mtmp));
4335 if (mtmp->msleeping) {
4336 mtmp->msleeping = 0;
4337 pline("%s awakens.", Monnam(mtmp));
4338 }
4339 return 1;
4340 }
4341
4342 You("reach out your %s and grab %s.", makeplural(body_part(ARM)),
4343 mon_nam(mtmp));
4344
4345 if (mtmp->msleeping) {
4346 mtmp->msleeping = 0;
4347 pline("%s awakens.", Monnam(mtmp));
4348 } else if (mtmp->mfrozen && !rn2(mtmp->mfrozen)) {
4349 /* After such manhandling, perhaps the effect wears off */
4350 mtmp->mcanmove = 1;
4351 mtmp->mfrozen = 0;
4352 pline("%s stirs.", Monnam(mtmp));
4353 }
4354
4355 /* is the monster too heavy? */
4356 wt = inv_weight() + mtmp->data->cwt;
4357 if (!try_lift(mtmp, ttmp, wt, FALSE))
4358 return 1;
4359
4360 /* is the monster with inventory too heavy? */
4361 for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
4362 wt += otmp->owt;
4363 if (!try_lift(mtmp, ttmp, wt, TRUE))
4364 return 1;
4365
4366 You("pull %s out of the pit.", mon_nam(mtmp));
4367 mtmp->mtrapped = 0;
4368 reward_untrap(ttmp, mtmp);
4369 fill_pit(mtmp->mx, mtmp->my);
4370 return 1;
4371 }
4372
4373 int
untrap(force)4374 untrap(force)
4375 boolean force;
4376 {
4377 register struct obj *otmp;
4378 register int x, y;
4379 int ch;
4380 struct trap *ttmp;
4381 struct monst *mtmp;
4382 const char *trapdescr;
4383 boolean here, useplural, deal_with_floor_trap,
4384 confused = (Confusion || Hallucination),
4385 trap_skipped = FALSE;
4386 int boxcnt = 0;
4387 char the_trap[BUFSZ], qbuf[QBUFSZ];
4388
4389 if (!getdir((char *) 0))
4390 return 0;
4391 x = u.ux + u.dx;
4392 y = u.uy + u.dy;
4393 if (!isok(x, y)) {
4394 pline_The("perils lurking there are beyond your grasp.");
4395 return 0;
4396 }
4397 /* 'force' is true for #invoke; make it be true for #untrap if
4398 carrying MKoT */
4399 if (!force && has_magic_key(&youmonst))
4400 force = TRUE;
4401
4402 ttmp = t_at(x, y);
4403 if (ttmp && !ttmp->tseen)
4404 ttmp = 0;
4405 trapdescr = ttmp ? defsyms[trap_to_defsym(ttmp->ttyp)].explanation : 0;
4406 here = (x == u.ux && y == u.uy); /* !u.dx && !u.dy */
4407
4408 if (here) /* are there are one or more containers here? */
4409 for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere)
4410 if (Is_box(otmp)) {
4411 if (++boxcnt > 1)
4412 break;
4413 }
4414
4415 deal_with_floor_trap = can_reach_floor(FALSE);
4416 if (!deal_with_floor_trap) {
4417 *the_trap = '\0';
4418 if (ttmp)
4419 Strcat(the_trap, an(trapdescr));
4420 if (ttmp && boxcnt)
4421 Strcat(the_trap, " and ");
4422 if (boxcnt)
4423 Strcat(the_trap, (boxcnt == 1) ? "a container" : "containers");
4424 useplural = ((ttmp && boxcnt > 0) || boxcnt > 1);
4425 /* note: boxcnt and useplural will always be 0 for !here case */
4426 if (ttmp || boxcnt)
4427 There("%s %s %s but you can't reach %s%s.",
4428 useplural ? "are" : "is", the_trap, here ? "here" : "there",
4429 useplural ? "them" : "it",
4430 u.usteed ? " while mounted" : "");
4431 trap_skipped = (ttmp != 0);
4432 } else { /* deal_with_floor_trap */
4433
4434 if (ttmp) {
4435 Strcpy(the_trap, the(trapdescr));
4436 if (boxcnt) {
4437 if (is_pit(ttmp->ttyp)) {
4438 You_cant("do much about %s%s.", the_trap,
4439 u.utrap ? " that you're stuck in"
4440 : " while standing on the edge of it");
4441 trap_skipped = TRUE;
4442 deal_with_floor_trap = FALSE;
4443 } else {
4444 Sprintf(
4445 qbuf, "There %s and %s here. %s %s?",
4446 (boxcnt == 1) ? "is a container" : "are containers",
4447 an(trapdescr),
4448 (ttmp->ttyp == WEB) ? "Remove" : "Disarm", the_trap);
4449 switch (ynq(qbuf)) {
4450 case 'q':
4451 return 0;
4452 case 'n':
4453 trap_skipped = TRUE;
4454 deal_with_floor_trap = FALSE;
4455 break;
4456 }
4457 }
4458 }
4459 if (deal_with_floor_trap) {
4460 if (u.utrap) {
4461 You("cannot deal with %s while trapped%s!", the_trap,
4462 (x == u.ux && y == u.uy) ? " in it" : "");
4463 return 1;
4464 }
4465 if ((mtmp = m_at(x, y)) != 0
4466 && (M_AP_TYPE(mtmp) == M_AP_FURNITURE
4467 || M_AP_TYPE(mtmp) == M_AP_OBJECT)) {
4468 stumble_onto_mimic(mtmp);
4469 return 1;
4470 }
4471 switch (ttmp->ttyp) {
4472 case BEAR_TRAP:
4473 case WEB:
4474 return disarm_holdingtrap(ttmp);
4475 case LANDMINE:
4476 return disarm_landmine(ttmp);
4477 case SQKY_BOARD:
4478 return disarm_squeaky_board(ttmp);
4479 case DART_TRAP:
4480 return disarm_shooting_trap(ttmp, DART);
4481 case ARROW_TRAP:
4482 return disarm_shooting_trap(ttmp, ARROW);
4483 case PIT:
4484 case SPIKED_PIT:
4485 if (here) {
4486 You("are already on the edge of the pit.");
4487 return 0;
4488 }
4489 if (!mtmp) {
4490 pline("Try filling the pit instead.");
4491 return 0;
4492 }
4493 return help_monster_out(mtmp, ttmp);
4494 default:
4495 You("cannot disable %s trap.", !here ? "that" : "this");
4496 return 0;
4497 }
4498 }
4499 } /* end if */
4500
4501 if (boxcnt) {
4502 for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere)
4503 if (Is_box(otmp)) {
4504 (void) safe_qbuf(qbuf, "There is ",
4505 " here. Check it for traps?", otmp,
4506 doname, ansimpleoname, "a box");
4507 switch (ynq(qbuf)) {
4508 case 'q':
4509 return 0;
4510 case 'n':
4511 continue;
4512 }
4513
4514 if ((otmp->otrapped
4515 && (force || (!confused
4516 && rn2(MAXULEV + 1 - u.ulevel) < 10)))
4517 || (!force && confused && !rn2(3))) {
4518 You("find a trap on %s!", the(xname(otmp)));
4519 if (!confused)
4520 exercise(A_WIS, TRUE);
4521
4522 switch (ynq("Disarm it?")) {
4523 case 'q':
4524 return 1;
4525 case 'n':
4526 trap_skipped = TRUE;
4527 continue;
4528 }
4529
4530 if (otmp->otrapped) {
4531 exercise(A_DEX, TRUE);
4532 ch = ACURR(A_DEX) + u.ulevel;
4533 if (Role_if(PM_ROGUE))
4534 ch *= 2;
4535 if (!force && (confused || Fumbling
4536 || rnd(75 + level_difficulty() / 2)
4537 > ch)) {
4538 (void) chest_trap(otmp, FINGER, TRUE);
4539 } else {
4540 You("disarm it!");
4541 otmp->otrapped = 0;
4542 }
4543 } else
4544 pline("That %s was not trapped.", xname(otmp));
4545 return 1;
4546 } else {
4547 You("find no traps on %s.", the(xname(otmp)));
4548 return 1;
4549 }
4550 }
4551
4552 You(trap_skipped ? "find no other traps here."
4553 : "know of no traps here.");
4554 return 0;
4555 }
4556
4557 if (stumble_on_door_mimic(x, y))
4558 return 1;
4559
4560 } /* deal_with_floor_trap */
4561 /* doors can be manipulated even while levitating/unskilled riding */
4562
4563 if (!IS_DOOR(levl[x][y].typ)) {
4564 if (!trap_skipped)
4565 You("know of no traps there.");
4566 return 0;
4567 }
4568
4569 switch (levl[x][y].doormask) {
4570 case D_NODOOR:
4571 You("%s no door there.", Blind ? "feel" : "see");
4572 return 0;
4573 case D_ISOPEN:
4574 pline("This door is safely open.");
4575 return 0;
4576 case D_BROKEN:
4577 pline("This door is broken.");
4578 return 0;
4579 }
4580
4581 if (((levl[x][y].doormask & D_TRAPPED) != 0
4582 && (force || (!confused && rn2(MAXULEV - u.ulevel + 11) < 10)))
4583 || (!force && confused && !rn2(3))) {
4584 You("find a trap on the door!");
4585 exercise(A_WIS, TRUE);
4586 if (ynq("Disarm it?") != 'y')
4587 return 1;
4588 if (levl[x][y].doormask & D_TRAPPED) {
4589 ch = 15 + (Role_if(PM_ROGUE) ? u.ulevel * 3 : u.ulevel);
4590 exercise(A_DEX, TRUE);
4591 if (!force && (confused || Fumbling
4592 || rnd(75 + level_difficulty() / 2) > ch)) {
4593 You("set it off!");
4594 b_trapped("door", FINGER);
4595 levl[x][y].doormask = D_NODOOR;
4596 unblock_point(x, y);
4597 newsym(x, y);
4598 /* (probably ought to charge for this damage...) */
4599 if (*in_rooms(x, y, SHOPBASE))
4600 add_damage(x, y, 0L);
4601 } else {
4602 You("disarm it!");
4603 levl[x][y].doormask &= ~D_TRAPPED;
4604 }
4605 } else
4606 pline("This door was not trapped.");
4607 return 1;
4608 } else {
4609 You("find no traps on the door.");
4610 return 1;
4611 }
4612 }
4613
4614 /* for magic unlocking; returns true if targetted monster (which might
4615 be hero) gets untrapped; the trap remains intact */
4616 boolean
openholdingtrap(mon,noticed)4617 openholdingtrap(mon, noticed)
4618 struct monst *mon;
4619 boolean *noticed; /* set to true iff hero notices the effect; */
4620 { /* otherwise left with its previous value intact */
4621 struct trap *t;
4622 char buf[BUFSZ], whichbuf[20];
4623 const char *trapdescr = 0, *which = 0;
4624 boolean ishero = (mon == &youmonst);
4625
4626 if (!mon)
4627 return FALSE;
4628 if (mon == u.usteed)
4629 ishero = TRUE;
4630
4631 t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my);
4632
4633 if (ishero && u.utrap) { /* all u.utraptype values are holding traps */
4634 which = the_your[(!t || !t->tseen || !t->madeby_u) ? 0 : 1];
4635 switch (u.utraptype) {
4636 case TT_LAVA:
4637 trapdescr = "molten lava";
4638 break;
4639 case TT_INFLOOR:
4640 /* solidified lava, so not "floor" even if within a room */
4641 trapdescr = "ground";
4642 break;
4643 case TT_BURIEDBALL:
4644 trapdescr = "your anchor";
4645 which = "";
4646 break;
4647 case TT_BEARTRAP:
4648 case TT_PIT:
4649 case TT_WEB:
4650 trapdescr = 0; /* use defsyms[].explanation */
4651 break;
4652 default:
4653 /* lint suppression in case 't' is unexpectedly Null
4654 or u.utraptype has new value we don't know about yet */
4655 trapdescr = "trap";
4656 break;
4657 }
4658 } else {
4659 /* if no trap here or it's not a holding trap, we're done */
4660 if (!t || (t->ttyp != BEAR_TRAP && t->ttyp != WEB))
4661 return FALSE;
4662 }
4663
4664 if (!trapdescr)
4665 trapdescr = defsyms[trap_to_defsym(t->ttyp)].explanation;
4666 if (!which)
4667 which = t->tseen ? the_your[t->madeby_u]
4668 : index(vowels, *trapdescr) ? "an" : "a";
4669 if (*which)
4670 which = strcat(strcpy(whichbuf, which), " ");
4671
4672 if (ishero) {
4673 if (!u.utrap)
4674 return FALSE;
4675 *noticed = TRUE;
4676 if (u.usteed)
4677 Sprintf(buf, "%s is", noit_Monnam(u.usteed));
4678 else
4679 Strcpy(buf, "You are");
4680 reset_utrap(TRUE);
4681 vision_full_recalc = 1; /* vision limits can change (pit escape) */
4682 pline("%s released from %s%s.", buf, which, trapdescr);
4683 } else {
4684 if (!mon->mtrapped)
4685 return FALSE;
4686 mon->mtrapped = 0;
4687 if (canspotmon(mon)) {
4688 *noticed = TRUE;
4689 pline("%s is released from %s%s.", Monnam(mon), which,
4690 trapdescr);
4691 } else if (cansee(t->tx, t->ty) && t->tseen) {
4692 *noticed = TRUE;
4693 if (t->ttyp == WEB)
4694 pline("%s is released from %s%s.", Something, which,
4695 trapdescr);
4696 else /* BEAR_TRAP */
4697 pline("%s%s opens.", upstart(strcpy(buf, which)), trapdescr);
4698 }
4699 /* might pacify monster if adjacent */
4700 if (rn2(2) && distu(mon->mx, mon->my) <= 2)
4701 reward_untrap(t, mon);
4702 }
4703 return TRUE;
4704 }
4705
4706 /* for magic locking; returns true if targetted monster (which might
4707 be hero) gets hit by a trap (might avoid actually becoming trapped) */
4708 boolean
closeholdingtrap(mon,noticed)4709 closeholdingtrap(mon, noticed)
4710 struct monst *mon;
4711 boolean *noticed; /* set to true iff hero notices the effect; */
4712 { /* otherwise left with its previous value intact */
4713 struct trap *t;
4714 unsigned dotrapflags;
4715 boolean ishero = (mon == &youmonst), result;
4716
4717 if (!mon)
4718 return FALSE;
4719 if (mon == u.usteed)
4720 ishero = TRUE;
4721 t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my);
4722 /* if no trap here or it's not a holding trap, we're done */
4723 if (!t || (t->ttyp != BEAR_TRAP && t->ttyp != WEB))
4724 return FALSE;
4725
4726 if (ishero) {
4727 if (u.utrap)
4728 return FALSE; /* already trapped */
4729 *noticed = TRUE;
4730 dotrapflags = FORCETRAP;
4731 /* dotrap calls mintrap when mounted hero encounters a web */
4732 if (u.usteed)
4733 dotrapflags |= NOWEBMSG;
4734 ++force_mintrap;
4735 dotrap(t, dotrapflags);
4736 --force_mintrap;
4737 result = (u.utrap != 0);
4738 } else {
4739 if (mon->mtrapped)
4740 return FALSE; /* already trapped */
4741 /* you notice it if you see the trap close/tremble/whatever
4742 or if you sense the monster who becomes trapped */
4743 *noticed = cansee(t->tx, t->ty) || canspotmon(mon);
4744 ++force_mintrap;
4745 result = (mintrap(mon) != 0);
4746 --force_mintrap;
4747 }
4748 return result;
4749 }
4750
4751 /* for magic unlocking; returns true if targetted monster (which might
4752 be hero) gets hit by a trap (target might avoid its effect) */
4753 boolean
openfallingtrap(mon,trapdoor_only,noticed)4754 openfallingtrap(mon, trapdoor_only, noticed)
4755 struct monst *mon;
4756 boolean trapdoor_only;
4757 boolean *noticed; /* set to true iff hero notices the effect; */
4758 { /* otherwise left with its previous value intact */
4759 struct trap *t;
4760 boolean ishero = (mon == &youmonst), result;
4761
4762 if (!mon)
4763 return FALSE;
4764 if (mon == u.usteed)
4765 ishero = TRUE;
4766 t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my);
4767 /* if no trap here or it's not a falling trap, we're done
4768 (note: falling rock traps have a trapdoor in the ceiling) */
4769 if (!t || ((t->ttyp != TRAPDOOR && t->ttyp != ROCKTRAP)
4770 && (trapdoor_only || (t->ttyp != HOLE && !is_pit(t->ttyp)))))
4771 return FALSE;
4772
4773 if (ishero) {
4774 if (u.utrap)
4775 return FALSE; /* already trapped */
4776 *noticed = TRUE;
4777 dotrap(t, FORCETRAP);
4778 result = (u.utrap != 0);
4779 } else {
4780 if (mon->mtrapped)
4781 return FALSE; /* already trapped */
4782 /* you notice it if you see the trap close/tremble/whatever
4783 or if you sense the monster who becomes trapped */
4784 *noticed = cansee(t->tx, t->ty) || canspotmon(mon);
4785 /* monster will be angered; mintrap doesn't handle that */
4786 wakeup(mon, TRUE);
4787 ++force_mintrap;
4788 result = (mintrap(mon) != 0);
4789 --force_mintrap;
4790 /* mon might now be on the migrating monsters list */
4791 }
4792 return result;
4793 }
4794
4795 /* only called when the player is doing something to the chest directly */
4796 boolean
chest_trap(obj,bodypart,disarm)4797 chest_trap(obj, bodypart, disarm)
4798 register struct obj *obj;
4799 register int bodypart;
4800 boolean disarm;
4801 {
4802 register struct obj *otmp = obj, *otmp2;
4803 char buf[80];
4804 const char *msg;
4805 coord cc;
4806
4807 if (get_obj_location(obj, &cc.x, &cc.y, 0)) /* might be carried */
4808 obj->ox = cc.x, obj->oy = cc.y;
4809
4810 otmp->otrapped = 0; /* trap is one-shot; clear flag first in case
4811 chest kills you and ends up in bones file */
4812 You(disarm ? "set it off!" : "trigger a trap!");
4813 display_nhwindow(WIN_MESSAGE, FALSE);
4814 if (Luck > -13 && rn2(13 + Luck) > 7) { /* saved by luck */
4815 /* trap went off, but good luck prevents damage */
4816 switch (rn2(13)) {
4817 case 12:
4818 case 11:
4819 msg = "explosive charge is a dud";
4820 break;
4821 case 10:
4822 case 9:
4823 msg = "electric charge is grounded";
4824 break;
4825 case 8:
4826 case 7:
4827 msg = "flame fizzles out";
4828 break;
4829 case 6:
4830 case 5:
4831 case 4:
4832 msg = "poisoned needle misses";
4833 break;
4834 case 3:
4835 case 2:
4836 case 1:
4837 case 0:
4838 msg = "gas cloud blows away";
4839 break;
4840 default:
4841 impossible("chest disarm bug");
4842 msg = (char *) 0;
4843 break;
4844 }
4845 if (msg)
4846 pline("But luckily the %s!", msg);
4847 } else {
4848 switch (rn2(20) ? ((Luck >= 13) ? 0 : rn2(13 - Luck)) : rn2(26)) {
4849 case 25:
4850 case 24:
4851 case 23:
4852 case 22:
4853 case 21: {
4854 struct monst *shkp = 0;
4855 long loss = 0L;
4856 boolean costly, insider;
4857 register xchar ox = obj->ox, oy = obj->oy;
4858
4859 /* the obj location need not be that of player */
4860 costly = (costly_spot(ox, oy)
4861 && (shkp = shop_keeper(*in_rooms(ox, oy, SHOPBASE)))
4862 != (struct monst *) 0);
4863 insider = (*u.ushops && inside_shop(u.ux, u.uy)
4864 && *in_rooms(ox, oy, SHOPBASE) == *u.ushops);
4865
4866 pline("%s!", Tobjnam(obj, "explode"));
4867 Sprintf(buf, "exploding %s", xname(obj));
4868
4869 if (costly)
4870 loss += stolen_value(obj, ox, oy, (boolean) shkp->mpeaceful,
4871 TRUE);
4872 delete_contents(obj);
4873 /* unpunish() in advance if either ball or chain (or both)
4874 is going to be destroyed */
4875 if (Punished && ((uchain->ox == u.ux && uchain->oy == u.uy)
4876 || (uball->where == OBJ_FLOOR
4877 && uball->ox == u.ux && uball->oy == u.uy)))
4878 unpunish();
4879
4880 for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp2) {
4881 otmp2 = otmp->nexthere;
4882 if (costly)
4883 loss += stolen_value(otmp, otmp->ox, otmp->oy,
4884 (boolean) shkp->mpeaceful, TRUE);
4885 delobj(otmp);
4886 }
4887 wake_nearby();
4888 losehp(Maybe_Half_Phys(d(6, 6)), buf, KILLED_BY_AN);
4889 exercise(A_STR, FALSE);
4890 if (costly && loss) {
4891 if (insider)
4892 You("owe %ld %s for objects destroyed.", loss,
4893 currency(loss));
4894 else {
4895 You("caused %ld %s worth of damage!", loss,
4896 currency(loss));
4897 make_angry_shk(shkp, ox, oy);
4898 }
4899 }
4900 return TRUE;
4901 } /* case 21 */
4902 case 20:
4903 case 19:
4904 case 18:
4905 case 17:
4906 pline("A cloud of noxious gas billows from %s.", the(xname(obj)));
4907 poisoned("gas cloud", A_STR, "cloud of poison gas", 15, FALSE);
4908 exercise(A_CON, FALSE);
4909 break;
4910 case 16:
4911 case 15:
4912 case 14:
4913 case 13:
4914 You_feel("a needle prick your %s.", body_part(bodypart));
4915 poisoned("needle", A_CON, "poisoned needle", 10, FALSE);
4916 exercise(A_CON, FALSE);
4917 break;
4918 case 12:
4919 case 11:
4920 case 10:
4921 case 9:
4922 dofiretrap(obj);
4923 break;
4924 case 8:
4925 case 7:
4926 case 6: {
4927 int dmg;
4928
4929 You("are jolted by a surge of electricity!");
4930 if (Shock_resistance) {
4931 shieldeff(u.ux, u.uy);
4932 You("don't seem to be affected.");
4933 dmg = 0;
4934 } else
4935 dmg = d(4, 4);
4936 destroy_item(RING_CLASS, AD_ELEC);
4937 destroy_item(WAND_CLASS, AD_ELEC);
4938 if (dmg)
4939 losehp(dmg, "electric shock", KILLED_BY_AN);
4940 break;
4941 } /* case 6 */
4942 case 5:
4943 case 4:
4944 case 3:
4945 if (!Free_action) {
4946 pline("Suddenly you are frozen in place!");
4947 nomul(-d(5, 6));
4948 multi_reason = "frozen by a trap";
4949 exercise(A_DEX, FALSE);
4950 nomovemsg = You_can_move_again;
4951 } else
4952 You("momentarily stiffen.");
4953 break;
4954 case 2:
4955 case 1:
4956 case 0:
4957 pline("A cloud of %s gas billows from %s.",
4958 Blind ? blindgas[rn2(SIZE(blindgas))] : rndcolor(),
4959 the(xname(obj)));
4960 if (!Stunned) {
4961 if (Hallucination)
4962 pline("What a groovy feeling!");
4963 else
4964 You("%s%s...", stagger(youmonst.data, "stagger"),
4965 Halluc_resistance ? ""
4966 : Blind ? " and get dizzy"
4967 : " and your vision blurs");
4968 }
4969 make_stunned((HStun & TIMEOUT) + (long) rn1(7, 16), FALSE);
4970 (void) make_hallucinated(
4971 (HHallucination & TIMEOUT) + (long) rn1(5, 16), FALSE, 0L);
4972 break;
4973 default:
4974 impossible("bad chest trap");
4975 break;
4976 }
4977 bot(); /* to get immediate botl re-display */
4978 }
4979
4980 return FALSE;
4981 }
4982
4983 struct trap *
t_at(x,y)4984 t_at(x, y)
4985 register int x, y;
4986 {
4987 register struct trap *trap = ftrap;
4988
4989 while (trap) {
4990 if (trap->tx == x && trap->ty == y)
4991 return trap;
4992 trap = trap->ntrap;
4993 }
4994 return (struct trap *) 0;
4995 }
4996
4997 void
deltrap(trap)4998 deltrap(trap)
4999 register struct trap *trap;
5000 {
5001 register struct trap *ttmp;
5002
5003 clear_conjoined_pits(trap);
5004 if (trap == ftrap) {
5005 ftrap = ftrap->ntrap;
5006 } else {
5007 for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap)
5008 if (ttmp->ntrap == trap)
5009 break;
5010 if (!ttmp)
5011 panic("deltrap: no preceding trap!");
5012 ttmp->ntrap = trap->ntrap;
5013 }
5014 if (Sokoban && (trap->ttyp == PIT || trap->ttyp == HOLE))
5015 maybe_finish_sokoban();
5016 dealloc_trap(trap);
5017 }
5018
5019 boolean
conjoined_pits(trap2,trap1,u_entering_trap2)5020 conjoined_pits(trap2, trap1, u_entering_trap2)
5021 struct trap *trap2, *trap1;
5022 boolean u_entering_trap2;
5023 {
5024 int dx, dy, diridx, adjidx;
5025
5026 if (!trap1 || !trap2)
5027 return FALSE;
5028 if (!isok(trap2->tx, trap2->ty) || !isok(trap1->tx, trap1->ty)
5029 || !is_pit(trap2->ttyp)
5030 || !is_pit(trap1->ttyp)
5031 || (u_entering_trap2 && !(u.utrap && u.utraptype == TT_PIT)))
5032 return FALSE;
5033 dx = sgn(trap2->tx - trap1->tx);
5034 dy = sgn(trap2->ty - trap1->ty);
5035 for (diridx = 0; diridx < 8; diridx++)
5036 if (xdir[diridx] == dx && ydir[diridx] == dy)
5037 break;
5038 /* diridx is valid if < 8 */
5039 if (diridx < 8) {
5040 adjidx = (diridx + 4) % 8;
5041 if ((trap1->conjoined & (1 << diridx))
5042 && (trap2->conjoined & (1 << adjidx)))
5043 return TRUE;
5044 }
5045 return FALSE;
5046 }
5047
5048 STATIC_OVL void
clear_conjoined_pits(trap)5049 clear_conjoined_pits(trap)
5050 struct trap *trap;
5051 {
5052 int diridx, adjidx, x, y;
5053 struct trap *t;
5054
5055 if (trap && is_pit(trap->ttyp)) {
5056 for (diridx = 0; diridx < 8; ++diridx) {
5057 if (trap->conjoined & (1 << diridx)) {
5058 x = trap->tx + xdir[diridx];
5059 y = trap->ty + ydir[diridx];
5060 if (isok(x, y)
5061 && (t = t_at(x, y)) != 0
5062 && is_pit(t->ttyp)) {
5063 adjidx = (diridx + 4) % 8;
5064 t->conjoined &= ~(1 << adjidx);
5065 }
5066 trap->conjoined &= ~(1 << diridx);
5067 }
5068 }
5069 }
5070 }
5071
5072 STATIC_OVL boolean
adj_nonconjoined_pit(adjtrap)5073 adj_nonconjoined_pit(adjtrap)
5074 struct trap *adjtrap;
5075 {
5076 struct trap *trap_with_u = t_at(u.ux0, u.uy0);
5077
5078 if (trap_with_u && adjtrap && u.utrap && u.utraptype == TT_PIT
5079 && is_pit(trap_with_u->ttyp) && is_pit(adjtrap->ttyp)) {
5080 int idx;
5081
5082 for (idx = 0; idx < 8; idx++) {
5083 if (xdir[idx] == u.dx && ydir[idx] == u.dy)
5084 return TRUE;
5085 }
5086 }
5087 return FALSE;
5088 }
5089
5090 #if 0
5091 /*
5092 * Mark all neighboring pits as conjoined pits.
5093 * (currently not called from anywhere)
5094 */
5095 STATIC_OVL void
5096 join_adjacent_pits(trap)
5097 struct trap *trap;
5098 {
5099 struct trap *t;
5100 int diridx, x, y;
5101
5102 if (!trap)
5103 return;
5104 for (diridx = 0; diridx < 8; ++diridx) {
5105 x = trap->tx + xdir[diridx];
5106 y = trap->ty + ydir[diridx];
5107 if (isok(x, y)) {
5108 if ((t = t_at(x, y)) != 0 && is_pit(t->ttyp)) {
5109 trap->conjoined |= (1 << diridx);
5110 join_adjacent_pits(t);
5111 } else
5112 trap->conjoined &= ~(1 << diridx);
5113 }
5114 }
5115 }
5116 #endif /*0*/
5117
5118 /*
5119 * Returns TRUE if you escaped a pit and are standing on the precipice.
5120 */
5121 boolean
uteetering_at_seen_pit(trap)5122 uteetering_at_seen_pit(trap)
5123 struct trap *trap;
5124 {
5125 return (trap && is_pit(trap->ttyp) && trap->tseen
5126 && trap->tx == u.ux && trap->ty == u.uy
5127 && !(u.utrap && u.utraptype == TT_PIT));
5128 }
5129
5130 /*
5131 * Returns TRUE if you didn't fall through a hole or didn't
5132 * release a trap door
5133 */
5134 boolean
uescaped_shaft(trap)5135 uescaped_shaft(trap)
5136 struct trap *trap;
5137 {
5138 return (trap && is_hole(trap->ttyp) && trap->tseen
5139 && trap->tx == u.ux && trap->ty == u.uy);
5140 }
5141
5142 /* Destroy a trap that emanates from the floor. */
5143 boolean
delfloortrap(ttmp)5144 delfloortrap(ttmp)
5145 register struct trap *ttmp;
5146 {
5147 /* some of these are arbitrary -dlc */
5148 if (ttmp && ((ttmp->ttyp == SQKY_BOARD) || (ttmp->ttyp == BEAR_TRAP)
5149 || (ttmp->ttyp == LANDMINE) || (ttmp->ttyp == FIRE_TRAP)
5150 || is_pit(ttmp->ttyp)
5151 || is_hole(ttmp->ttyp)
5152 || (ttmp->ttyp == TELEP_TRAP) || (ttmp->ttyp == LEVEL_TELEP)
5153 || (ttmp->ttyp == WEB) || (ttmp->ttyp == MAGIC_TRAP)
5154 || (ttmp->ttyp == ANTI_MAGIC))) {
5155 register struct monst *mtmp;
5156
5157 if (ttmp->tx == u.ux && ttmp->ty == u.uy) {
5158 if (u.utraptype != TT_BURIEDBALL)
5159 reset_utrap(TRUE);
5160 } else if ((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) {
5161 mtmp->mtrapped = 0;
5162 }
5163 deltrap(ttmp);
5164 return TRUE;
5165 }
5166 return FALSE;
5167 }
5168
5169 /* used for doors (also tins). can be used for anything else that opens. */
5170 void
b_trapped(item,bodypart)5171 b_trapped(item, bodypart)
5172 const char *item;
5173 int bodypart;
5174 {
5175 int lvl = level_difficulty(),
5176 dmg = rnd(5 + (lvl < 5 ? lvl : 2 + lvl / 2));
5177
5178 pline("KABOOM!! %s was booby-trapped!", The(item));
5179 wake_nearby();
5180 losehp(Maybe_Half_Phys(dmg), "explosion", KILLED_BY_AN);
5181 exercise(A_STR, FALSE);
5182 if (bodypart)
5183 exercise(A_CON, FALSE);
5184 make_stunned((HStun & TIMEOUT) + (long) dmg, TRUE);
5185 }
5186
5187 /* Monster is hit by trap. */
5188 /* Note: doesn't work if both obj and d_override are null */
5189 STATIC_OVL boolean
thitm(tlev,mon,obj,d_override,nocorpse)5190 thitm(tlev, mon, obj, d_override, nocorpse)
5191 int tlev;
5192 struct monst *mon;
5193 struct obj *obj;
5194 int d_override;
5195 boolean nocorpse;
5196 {
5197 int strike;
5198 boolean trapkilled = FALSE;
5199
5200 if (d_override)
5201 strike = 1;
5202 else if (obj)
5203 strike = (find_mac(mon) + tlev + obj->spe <= rnd(20));
5204 else
5205 strike = (find_mac(mon) + tlev <= rnd(20));
5206
5207 /* Actually more accurate than thitu, which doesn't take
5208 * obj->spe into account.
5209 */
5210 if (!strike) {
5211 if (obj && cansee(mon->mx, mon->my))
5212 pline("%s is almost hit by %s!", Monnam(mon), doname(obj));
5213 } else {
5214 int dam = 1;
5215
5216 if (obj && cansee(mon->mx, mon->my))
5217 pline("%s is hit by %s!", Monnam(mon), doname(obj));
5218 if (d_override)
5219 dam = d_override;
5220 else if (obj) {
5221 dam = dmgval(obj, mon);
5222 if (dam < 1)
5223 dam = 1;
5224 }
5225 mon->mhp -= dam;
5226 if (DEADMONSTER(mon)) {
5227 int xx = mon->mx, yy = mon->my;
5228
5229 monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS);
5230 if (DEADMONSTER(mon)) {
5231 newsym(xx, yy);
5232 trapkilled = TRUE;
5233 }
5234 }
5235 }
5236 if (obj && (!strike || d_override)) {
5237 place_object(obj, mon->mx, mon->my);
5238 stackobj(obj);
5239 } else if (obj)
5240 dealloc_obj(obj);
5241
5242 return trapkilled;
5243 }
5244
5245 boolean
unconscious()5246 unconscious()
5247 {
5248 if (multi >= 0)
5249 return FALSE;
5250
5251 return (boolean) (u.usleep
5252 || (nomovemsg
5253 && (!strncmp(nomovemsg, "You awake", 9)
5254 || !strncmp(nomovemsg, "You regain con", 14)
5255 || !strncmp(nomovemsg, "You are consci", 14))));
5256 }
5257
5258 static const char lava_killer[] = "molten lava";
5259
5260 boolean
lava_effects()5261 lava_effects()
5262 {
5263 register struct obj *obj, *obj2;
5264 int dmg = d(6, 6); /* only applicable for water walking */
5265 boolean usurvive, boil_away;
5266
5267 feel_newsym(u.ux, u.uy); /* in case Blind, map the lava here */
5268 burn_away_slime();
5269 if (likes_lava(youmonst.data))
5270 return FALSE;
5271
5272 usurvive = Fire_resistance || (Wwalking && dmg < u.uhp);
5273 /*
5274 * A timely interrupt might manage to salvage your life
5275 * but not your gear. For scrolls and potions this
5276 * will destroy whole stacks, where fire resistant hero
5277 * survivor only loses partial stacks via destroy_item().
5278 *
5279 * Flag items to be destroyed before any messages so
5280 * that player causing hangup at --More-- won't get an
5281 * emergency save file created before item destruction.
5282 */
5283 if (!usurvive)
5284 for (obj = invent; obj; obj = obj->nobj)
5285 if ((is_organic(obj) || obj->oclass == POTION_CLASS)
5286 && !obj->oerodeproof
5287 && objects[obj->otyp].oc_oprop != FIRE_RES
5288 && obj->otyp != SCR_FIRE && obj->otyp != SPE_FIREBALL
5289 && !obj_resists(obj, 0, 0)) /* for invocation items */
5290 obj->in_use = 1;
5291
5292 /* Check whether we should burn away boots *first* so we know whether to
5293 * make the player sink into the lava. Assumption: water walking only
5294 * comes from boots.
5295 */
5296 if (uarmf && is_organic(uarmf) && !uarmf->oerodeproof) {
5297 obj = uarmf;
5298 pline("%s into flame!", Yobjnam2(obj, "burst"));
5299 iflags.in_lava_effects++; /* (see above) */
5300 (void) Boots_off();
5301 useup(obj);
5302 iflags.in_lava_effects--;
5303 }
5304
5305 if (!Fire_resistance) {
5306 if (Wwalking) {
5307 pline_The("%s here burns you!", hliquid("lava"));
5308 if (usurvive) {
5309 losehp(dmg, lava_killer, KILLED_BY); /* lava damage */
5310 goto burn_stuff;
5311 }
5312 } else
5313 You("fall into the %s!", hliquid("lava"));
5314
5315 usurvive = Lifesaved || discover;
5316 if (wizard)
5317 usurvive = TRUE;
5318
5319 /* prevent remove_worn_item() -> Boots_off(WATER_WALKING_BOOTS) ->
5320 spoteffects() -> lava_effects() recursion which would
5321 successfully delete (via useupall) the no-longer-worn boots;
5322 once recursive call returned, we would try to delete them again
5323 here in the outer call (and access stale memory, probably panic) */
5324 iflags.in_lava_effects++;
5325
5326 for (obj = invent; obj; obj = obj2) {
5327 obj2 = obj->nobj;
5328 /* above, we set in_use for objects which are to be destroyed */
5329 if (obj->otyp == SPE_BOOK_OF_THE_DEAD && !Blind) {
5330 if (usurvive)
5331 pline("%s glows a strange %s, but remains intact.",
5332 The(xname(obj)), hcolor("dark red"));
5333 } else if (obj->in_use) {
5334 if (obj->owornmask) {
5335 if (usurvive)
5336 pline("%s into flame!", Yobjnam2(obj, "burst"));
5337 remove_worn_item(obj, TRUE);
5338 }
5339 useupall(obj);
5340 }
5341 }
5342
5343 iflags.in_lava_effects--;
5344
5345 /* s/he died... */
5346 boil_away = (u.umonnum == PM_WATER_ELEMENTAL
5347 || u.umonnum == PM_STEAM_VORTEX
5348 || u.umonnum == PM_FOG_CLOUD);
5349 for (;;) {
5350 u.uhp = -1;
5351 /* killer format and name are reconstructed every iteration
5352 because lifesaving resets them */
5353 killer.format = KILLED_BY;
5354 Strcpy(killer.name, lava_killer);
5355 You("%s...", boil_away ? "boil away" : "burn to a crisp");
5356 done(BURNING);
5357 if (safe_teleds(TRUE))
5358 break; /* successful life-save */
5359 /* nowhere safe to land; repeat burning loop */
5360 pline("You're still burning.");
5361 }
5362 You("find yourself back on solid %s.", surface(u.ux, u.uy));
5363 return TRUE;
5364 } else if (!Wwalking && (!u.utrap || u.utraptype != TT_LAVA)) {
5365 boil_away = !Fire_resistance;
5366 /* if not fire resistant, sink_into_lava() will quickly be fatal;
5367 hero needs to escape immediately */
5368 set_utrap((unsigned) (rn1(4, 4) + ((boil_away ? 2 : rn1(4, 12)) << 8)),
5369 TT_LAVA);
5370 You("sink into the %s%s!", hliquid("lava"),
5371 !boil_away ? ", but it only burns slightly"
5372 : " and are about to be immolated");
5373 if (u.uhp > 1)
5374 losehp(!boil_away ? 1 : (u.uhp / 2), lava_killer,
5375 KILLED_BY); /* lava damage */
5376 }
5377
5378 burn_stuff:
5379 destroy_item(SCROLL_CLASS, AD_FIRE);
5380 destroy_item(SPBOOK_CLASS, AD_FIRE);
5381 destroy_item(POTION_CLASS, AD_FIRE);
5382 return FALSE;
5383 }
5384
5385 /* called each turn when trapped in lava */
5386 void
sink_into_lava()5387 sink_into_lava()
5388 {
5389 static const char sink_deeper[] = "You sink deeper into the lava.";
5390
5391 if (!u.utrap || u.utraptype != TT_LAVA) {
5392 ; /* do nothing; this usually won't happen but could after
5393 * polymorphing from a flier into a ceiling hider and then hiding;
5394 * allmain() only checks whether the hero is at a lava location,
5395 * not whether he or she is currently sinking */
5396 } else if (!is_lava(u.ux, u.uy)) {
5397 reset_utrap(FALSE); /* this shouldn't happen either */
5398 } else if (!u.uinvulnerable) {
5399 /* ordinarily we'd have to be fire resistant to survive long
5400 enough to become stuck in lava, but it can happen without
5401 resistance if water walking boots allow survival and then
5402 get burned up; u.utrap time will be quite short in that case */
5403 if (!Fire_resistance)
5404 u.uhp = (u.uhp + 2) / 3;
5405
5406 u.utrap -= (1 << 8);
5407 if (u.utrap < (1 << 8)) {
5408 killer.format = KILLED_BY;
5409 Strcpy(killer.name, "molten lava");
5410 You("sink below the surface and die.");
5411 burn_away_slime(); /* add insult to injury? */
5412 done(DISSOLVED);
5413 /* can only get here via life-saving; try to get away from lava */
5414 reset_utrap(TRUE);
5415 /* levitation or flight have become unblocked, otherwise Tport */
5416 if (!Levitation && !Flying)
5417 (void) safe_teleds(TRUE);
5418 } else if (!u.umoved) {
5419 /* can't fully turn into slime while in lava, but might not
5420 have it be burned away until you've come awfully close */
5421 if (Slimed && rnd(10 - 1) >= (int) (Slimed & TIMEOUT)) {
5422 pline(sink_deeper);
5423 burn_away_slime();
5424 } else {
5425 Norep(sink_deeper);
5426 }
5427 u.utrap += rnd(4);
5428 }
5429 }
5430 }
5431
5432 /* called when something has been done (breaking a boulder, for instance)
5433 which entails a luck penalty if performed on a sokoban level */
5434 void
sokoban_guilt()5435 sokoban_guilt()
5436 {
5437 if (Sokoban) {
5438 change_luck(-1);
5439 /* TODO: issue some feedback so that player can learn that whatever
5440 he/she just did is a naughty thing to do in sokoban and should
5441 probably be avoided in future....
5442 Caveat: doing this might introduce message sequencing issues,
5443 depending upon feedback during the various actions which trigger
5444 Sokoban luck penalties. */
5445 }
5446 }
5447
5448 /* called when a trap has been deleted or had its ttyp replaced */
5449 STATIC_OVL void
maybe_finish_sokoban()5450 maybe_finish_sokoban()
5451 {
5452 struct trap *t;
5453
5454 if (Sokoban && !in_mklev) {
5455 /* scan all remaining traps, ignoring any created by the hero;
5456 if this level has no more pits or holes, the current sokoban
5457 puzzle has been solved */
5458 for (t = ftrap; t; t = t->ntrap) {
5459 if (t->madeby_u)
5460 continue;
5461 if (t->ttyp == PIT || t->ttyp == HOLE)
5462 break;
5463 }
5464 if (!t) {
5465 /* we've passed the last trap without finding a pit or hole;
5466 clear the sokoban_rules flag so that luck penalties for
5467 things like breaking boulders or jumping will no longer
5468 be given, and restrictions on diagonal moves are lifted */
5469 Sokoban = 0; /* clear level.flags.sokoban_rules */
5470 /* TODO: give some feedback about solving the sokoban puzzle
5471 (perhaps say "congratulations" in Japanese?) */
5472 }
5473 }
5474 }
5475
5476 /*trap.c*/
5477