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