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(&current_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, &current_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