use of #terrain while underwater
[aNetHack.git] / src / trap.c
blob92d2dfd6dcb61226af5317d63e396af517ee3c8b
1 /* NetHack 3.6 trap.c $NHDT-Date: 1461568321 2016/04/25 07:12:01 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.268 $ */
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 ttmp->ntrap = 0;
341 ttmp->tx = x;
342 ttmp->ty = y;
344 /* [re-]initialize all fields except ntrap (handled below) and <tx,ty> */
345 ttmp->vl = zero_vl;
346 ttmp->launch.x = ttmp->launch.y = -1; /* force error if used before set */
347 ttmp->dst.dnum = ttmp->dst.dlevel = -1;
348 ttmp->madeby_u = 0;
349 ttmp->once = 0;
350 ttmp->tseen = (typ == HOLE); /* hide non-holes */
351 ttmp->ttyp = typ;
353 switch (typ) {
354 case SQKY_BOARD: {
355 int tavail[12], tpick[12], tcnt = 0, k;
356 struct trap *t;
358 for (k = 0; k < 12; ++k)
359 tavail[k] = tpick[k] = 0;
360 for (t = ftrap; t; t = t->ntrap)
361 if (t->ttyp == SQKY_BOARD && t != ttmp)
362 tavail[t->tnote] = 1;
363 /* now populate tpick[] with the available indices */
364 for (k = 0; k < 12; ++k)
365 if (tavail[k] == 0)
366 tpick[tcnt++] = k;
367 /* choose an unused note; if all are in use, pick a random one */
368 ttmp->tnote = (short) ((tcnt > 0) ? tpick[rn2(tcnt)] : rn2(12));
369 break;
371 case STATUE_TRAP: { /* create a "living" statue */
372 struct monst *mtmp;
373 struct obj *otmp, *statue;
374 struct permonst *mptr;
375 int trycount = 10;
377 do { /* avoid ultimately hostile co-aligned unicorn */
378 mptr = &mons[rndmonnum()];
379 } while (--trycount > 0 && is_unicorn(mptr)
380 && sgn(u.ualign.type) == sgn(mptr->maligntyp));
381 statue = mkcorpstat(STATUE, (struct monst *) 0, mptr, x, y,
382 CORPSTAT_NONE);
383 mtmp = makemon(&mons[statue->corpsenm], 0, 0, MM_NOCOUNTBIRTH);
384 if (!mtmp)
385 break; /* should never happen */
386 while (mtmp->minvent) {
387 otmp = mtmp->minvent;
388 otmp->owornmask = 0;
389 obj_extract_self(otmp);
390 (void) add_to_container(statue, otmp);
392 statue->owt = weight(statue);
393 mongone(mtmp);
394 break;
396 case ROLLING_BOULDER_TRAP: /* boulder will roll towards trigger */
397 (void) mkroll_launch(ttmp, x, y, BOULDER, 1L);
398 break;
399 case PIT:
400 case SPIKED_PIT:
401 ttmp->conjoined = 0;
402 /*FALLTHRU*/
403 case HOLE:
404 case TRAPDOOR:
405 if (*in_rooms(x, y, SHOPBASE)
406 && (typ == HOLE || typ == TRAPDOOR
407 || IS_DOOR(lev->typ) || IS_WALL(lev->typ)))
408 add_damage(x, y, /* schedule repair */
409 ((IS_DOOR(lev->typ) || IS_WALL(lev->typ))
410 && !context.mon_moving)
411 ? 200L
412 : 0L);
413 lev->doormask = 0; /* subsumes altarmask, icedpool... */
414 if (IS_ROOM(lev->typ)) /* && !IS_AIR(lev->typ) */
415 lev->typ = ROOM;
417 * some cases which can happen when digging
418 * down while phazing thru solid areas
420 else if (lev->typ == STONE || lev->typ == SCORR)
421 lev->typ = CORR;
422 else if (IS_WALL(lev->typ) || lev->typ == SDOOR)
423 lev->typ = level.flags.is_maze_lev
424 ? ROOM
425 : level.flags.is_cavernous_lev ? CORR : DOOR;
427 unearth_objs(x, y);
428 break;
431 if (!oldplace) {
432 ttmp->ntrap = ftrap;
433 ftrap = ttmp;
434 } else {
435 /* oldplace;
436 it shouldn't be possible to override a sokoban pit or hole
437 with some other trap, but we'll check just to be safe */
438 if (Sokoban)
439 maybe_finish_sokoban();
441 return ttmp;
444 void
445 fall_through(td)
446 boolean td; /* td == TRUE : trap door or hole */
448 d_level dtmp;
449 char msgbuf[BUFSZ];
450 const char *dont_fall = 0;
451 int newlevel, bottom;
453 /* we'll fall even while levitating in Sokoban; otherwise, if we
454 won't fall and won't be told that we aren't falling, give up now */
455 if (Blind && Levitation && !Sokoban)
456 return;
458 bottom = dunlevs_in_dungeon(&u.uz);
459 /* when in the upper half of the quest, don't fall past the
460 middle "quest locate" level if hero hasn't been there yet */
461 if (In_quest(&u.uz)) {
462 int qlocate_depth = qlocate_level.dlevel;
464 /* deepest reached < qlocate implies current < qlocate */
465 if (dunlev_reached(&u.uz) < qlocate_depth)
466 bottom = qlocate_depth; /* early cut-off */
468 newlevel = dunlev(&u.uz); /* current level */
469 do {
470 newlevel++;
471 } while (!rn2(4) && newlevel < bottom);
473 if (td) {
474 struct trap *t = t_at(u.ux, u.uy);
476 feeltrap(t);
477 if (!Sokoban) {
478 if (t->ttyp == TRAPDOOR)
479 pline("A trap door opens up under you!");
480 else
481 pline("There's a gaping hole under you!");
483 } else
484 pline_The("%s opens up under you!", surface(u.ux, u.uy));
486 if (Sokoban && Can_fall_thru(&u.uz))
487 ; /* KMH -- You can't escape the Sokoban level traps */
488 else if (Levitation || u.ustuck
489 || (!Can_fall_thru(&u.uz) && !levl[u.ux][u.uy].candig) || Flying
490 || is_clinger(youmonst.data)
491 || (Inhell && !u.uevent.invoked && newlevel == bottom)) {
492 dont_fall = "don't fall in.";
493 } else if (youmonst.data->msize >= MZ_HUGE) {
494 dont_fall = "don't fit through.";
495 } else if (!next_to_u()) {
496 dont_fall = "are jerked back by your pet!";
498 if (dont_fall) {
499 You1(dont_fall);
500 /* hero didn't fall through, but any objects here might */
501 impact_drop((struct obj *) 0, u.ux, u.uy, 0);
502 if (!td) {
503 display_nhwindow(WIN_MESSAGE, FALSE);
504 pline_The("opening under you closes up.");
506 return;
509 if (*u.ushops)
510 shopdig(1);
511 if (Is_stronghold(&u.uz)) {
512 find_hell(&dtmp);
513 } else {
514 int dist = newlevel - dunlev(&u.uz);
515 dtmp.dnum = u.uz.dnum;
516 dtmp.dlevel = newlevel;
517 if (dist > 1)
518 You("fall down a %s%sshaft!", dist > 3 ? "very " : "",
519 dist > 2 ? "deep " : "");
521 if (!td)
522 Sprintf(msgbuf, "The hole in the %s above you closes up.",
523 ceiling(u.ux, u.uy));
525 schedule_goto(&dtmp, FALSE, TRUE, 0, (char *) 0,
526 !td ? msgbuf : (char *) 0);
530 * Animate the given statue. May have been via shatter attempt, trap,
531 * or stone to flesh spell. Return a monster if successfully animated.
532 * If the monster is animated, the object is deleted. If fail_reason
533 * is non-null, then fill in the reason for failure (or success).
535 * The cause of animation is:
537 * ANIMATE_NORMAL - hero "finds" the monster
538 * ANIMATE_SHATTER - hero tries to destroy the statue
539 * ANIMATE_SPELL - stone to flesh spell hits the statue
541 * Perhaps x, y is not needed if we can use get_obj_location() to find
542 * the statue's location... ???
544 * Sequencing matters:
545 * create monster; if it fails, give up with statue intact;
546 * give "statue comes to life" message;
547 * if statue belongs to shop, have shk give "you owe" message;
548 * transfer statue contents to monster (after stolen_value());
549 * delete statue.
550 * [This ordering means that if the statue ends up wearing a cloak of
551 * invisibility or a mummy wrapping, the visibility checks might be
552 * wrong, but to avoid that we'd have to clone the statue contents
553 * first in order to give them to the monster before checking their
554 * shop status--it's not worth the hassle.]
556 struct monst *
557 animate_statue(statue, x, y, cause, fail_reason)
558 struct obj *statue;
559 xchar x, y;
560 int cause;
561 int *fail_reason;
563 int mnum = statue->corpsenm;
564 struct permonst *mptr = &mons[mnum];
565 struct monst *mon = 0, *shkp;
566 struct obj *item;
567 coord cc;
568 boolean historic = (Role_if(PM_ARCHEOLOGIST)
569 && (statue->spe & STATUE_HISTORIC) != 0),
570 golem_xform = FALSE, use_saved_traits;
571 const char *comes_to_life;
572 char statuename[BUFSZ], tmpbuf[BUFSZ];
573 static const char historic_statue_is_gone[] =
574 "that the historic statue is now gone";
576 if (cant_revive(&mnum, TRUE, statue)) {
577 /* mnum has changed; we won't be animating this statue as itself */
578 if (mnum != PM_DOPPELGANGER)
579 mptr = &mons[mnum];
580 use_saved_traits = FALSE;
581 } else if (is_golem(mptr) && cause == ANIMATE_SPELL) {
582 /* statue of any golem hit by stone-to-flesh becomes flesh golem */
583 golem_xform = (mptr != &mons[PM_FLESH_GOLEM]);
584 mnum = PM_FLESH_GOLEM;
585 mptr = &mons[PM_FLESH_GOLEM];
586 use_saved_traits = (has_omonst(statue) && !golem_xform);
587 } else {
588 use_saved_traits = has_omonst(statue);
591 if (use_saved_traits) {
592 /* restore a petrified monster */
593 cc.x = x, cc.y = y;
594 mon = montraits(statue, &cc);
595 if (mon && mon->mtame && !mon->isminion)
596 wary_dog(mon, TRUE);
597 } else {
598 /* statues of unique monsters from bones or wishing end
599 up here (cant_revive() sets mnum to be doppelganger;
600 mptr reflects the original form for use by newcham()) */
601 if ((mnum == PM_DOPPELGANGER && mptr != &mons[PM_DOPPELGANGER])
602 /* block quest guards from other roles */
603 || (mptr->msound == MS_GUARDIAN
604 && quest_info(MS_GUARDIAN) != mnum)) {
605 mon = makemon(&mons[PM_DOPPELGANGER], x, y,
606 NO_MINVENT | MM_NOCOUNTBIRTH | MM_ADJACENTOK);
607 /* if hero has protection from shape changers, cham field will
608 be NON_PM; otherwise, set form to match the statue */
609 if (mon && mon->cham >= LOW_PM)
610 (void) newcham(mon, mptr, FALSE, FALSE);
611 } else
612 mon = makemon(mptr, x, y, (cause == ANIMATE_SPELL)
613 ? (NO_MINVENT | MM_ADJACENTOK)
614 : NO_MINVENT);
617 if (!mon) {
618 if (fail_reason)
619 *fail_reason = unique_corpstat(&mons[statue->corpsenm])
620 ? AS_MON_IS_UNIQUE
621 : AS_NO_MON;
622 return (struct monst *) 0;
625 /* a non-montraits() statue might specify gender */
626 if (statue->spe & STATUE_MALE)
627 mon->female = FALSE;
628 else if (statue->spe & STATUE_FEMALE)
629 mon->female = TRUE;
630 /* if statue has been named, give same name to the monster */
631 if (has_oname(statue) && !unique_corpstat(mon->data))
632 mon = christen_monst(mon, ONAME(statue));
633 /* mimic statue becomes seen mimic; other hiders won't be hidden */
634 if (mon->m_ap_type)
635 seemimic(mon);
636 else
637 mon->mundetected = FALSE;
638 mon->msleeping = 0;
639 if (cause == ANIMATE_NORMAL || cause == ANIMATE_SHATTER) {
640 /* trap always releases hostile monster */
641 mon->mtame = 0; /* (might be petrified pet tossed onto trap) */
642 mon->mpeaceful = 0;
643 set_malign(mon);
646 comes_to_life = !canspotmon(mon)
647 ? "disappears"
648 : golem_xform
649 ? "turns into flesh"
650 : (nonliving(mon->data) || is_vampshifter(mon))
651 ? "moves"
652 : "comes to life";
653 if ((x == u.ux && y == u.uy) || cause == ANIMATE_SPELL) {
654 /* "the|your|Manlobbi's statue [of a wombat]" */
655 shkp = shop_keeper(*in_rooms(mon->mx, mon->my, SHOPBASE));
656 Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue),
657 (cause == ANIMATE_SPELL
658 /* avoid "of a shopkeeper" if it's Manlobbi himself
659 (if carried, it can't be unpaid--hence won't be
660 described as "Manlobbi's statue"--because there
661 wasn't any living shk when statue was picked up) */
662 && (mon != shkp || carried(statue)))
663 ? xname(statue)
664 : "statue");
665 pline("%s %s!", upstart(statuename), comes_to_life);
666 } else if (Hallucination) { /* They don't know it's a statue */
667 pline_The("%s suddenly seems more animated.", rndmonnam((char *) 0));
668 } else if (cause == ANIMATE_SHATTER) {
669 if (cansee(x, y))
670 Sprintf(statuename, "%s%s", shk_your(tmpbuf, statue),
671 xname(statue));
672 else
673 Strcpy(statuename, "a statue");
674 pline("Instead of shattering, %s suddenly %s!", statuename,
675 comes_to_life);
676 } else { /* cause == ANIMATE_NORMAL */
677 You("find %s posing as a statue.",
678 canspotmon(mon) ? a_monnam(mon) : something);
679 if (!canspotmon(mon) && Blind)
680 map_invisible(x, y);
681 stop_occupation();
684 /* if this isn't caused by a monster using a wand of striking,
685 there might be consequences for the hero */
686 if (!context.mon_moving) {
687 /* if statue is owned by a shop, hero will have to pay for it;
688 stolen_value gives a message (about debt or use of credit)
689 which refers to "it" so needs to follow a message describing
690 the object ("the statue comes to life" one above) */
691 if (cause != ANIMATE_NORMAL && costly_spot(x, y)
692 && (shkp = shop_keeper(*in_rooms(x, y, SHOPBASE))) != 0
693 /* avoid charging for Manlobbi's statue of Manlobbi
694 if stone-to-flesh is used on petrified shopkeep */
695 && mon != shkp)
696 (void) stolen_value(statue, x, y, (boolean) shkp->mpeaceful,
697 FALSE);
699 if (historic) {
700 You_feel("guilty %s.", historic_statue_is_gone);
701 adjalign(-1);
703 } else {
704 if (historic && cansee(x, y))
705 You_feel("regret %s.", historic_statue_is_gone);
706 /* no alignment penalty */
709 /* transfer any statue contents to monster's inventory */
710 while ((item = statue->cobj) != 0) {
711 obj_extract_self(item);
712 (void) mpickobj(mon, item);
714 m_dowear(mon, TRUE);
715 /* in case statue is wielded and hero zaps stone-to-flesh at self */
716 if (statue->owornmask)
717 remove_worn_item(statue, TRUE);
718 /* statue no longer exists */
719 delobj(statue);
721 /* avoid hiding under nothing */
722 if (x == u.ux && y == u.uy && Upolyd && hides_under(youmonst.data)
723 && !OBJ_AT(x, y))
724 u.uundetected = 0;
726 if (fail_reason)
727 *fail_reason = AS_OK;
728 return mon;
732 * You've either stepped onto a statue trap's location or you've triggered a
733 * statue trap by searching next to it or by trying to break it with a wand
734 * or pick-axe.
736 struct monst *
737 activate_statue_trap(trap, x, y, shatter)
738 struct trap *trap;
739 xchar x, y;
740 boolean shatter;
742 struct monst *mtmp = (struct monst *) 0;
743 struct obj *otmp = sobj_at(STATUE, x, y);
744 int fail_reason;
747 * Try to animate the first valid statue. Stop the loop when we
748 * actually create something or the failure cause is not because
749 * the mon was unique.
751 deltrap(trap);
752 while (otmp) {
753 mtmp = animate_statue(otmp, x, y,
754 shatter ? ANIMATE_SHATTER : ANIMATE_NORMAL,
755 &fail_reason);
756 if (mtmp || fail_reason != AS_MON_IS_UNIQUE)
757 break;
759 otmp = nxtobj(otmp, STATUE, TRUE);
762 feel_newsym(x, y);
763 return mtmp;
766 STATIC_OVL boolean
767 keep_saddle_with_steedcorpse(steed_mid, objchn, saddle)
768 unsigned steed_mid;
769 struct obj *objchn, *saddle;
771 if (!saddle)
772 return FALSE;
773 while (objchn) {
774 if (objchn->otyp == CORPSE && has_omonst(objchn)) {
775 struct monst *mtmp = OMONST(objchn);
777 if (mtmp->m_id == steed_mid) {
778 /* move saddle */
779 xchar x, y;
780 if (get_obj_location(objchn, &x, &y, 0)) {
781 obj_extract_self(saddle);
782 place_object(saddle, x, y);
783 stackobj(saddle);
785 return TRUE;
788 if (Has_contents(objchn)
789 && keep_saddle_with_steedcorpse(steed_mid, objchn->cobj, saddle))
790 return TRUE;
791 objchn = objchn->nobj;
793 return FALSE;
796 /* monster or you go through and possibly destroy a web.
797 return TRUE if could go through. */
798 boolean
799 mu_maybe_destroy_web(mtmp, domsg, trap)
800 struct monst *mtmp;
801 boolean domsg;
802 struct trap *trap;
804 boolean isyou = (mtmp == &youmonst);
805 struct permonst *mptr = mtmp->data;
806 if (amorphous(mptr) || is_whirly(mptr) || flaming(mptr)
807 || unsolid(mptr) || mptr == &mons[PM_GELATINOUS_CUBE]) {
808 xchar x = trap->tx;
809 xchar y = trap->ty;
810 if (flaming(mptr) || acidic(mptr)) {
811 if (domsg) {
812 if (isyou)
813 You("%s %s spider web!",
814 (flaming(mptr)) ? "burn" : "dissolve",
815 a_your[trap->madeby_u]);
816 else
817 pline("%s %s %s spider web!", Monnam(mtmp),
818 (flaming(mptr)) ? "burns" : "dissolves",
819 a_your[trap->madeby_u]);
821 deltrap(trap);
822 newsym(x, y);
823 return TRUE;
825 if (domsg) {
826 if (isyou)
827 You("flow through %s spider web.", a_your[trap->madeby_u]);
828 else {
829 pline("%s flows through %s spider web.", Monnam(mtmp),
830 a_your[trap->madeby_u]);
831 seetrap(trap);
834 return TRUE;
836 return FALSE;
839 void
840 dotrap(trap, trflags)
841 register struct trap *trap;
842 unsigned trflags;
844 register int ttype = trap->ttyp;
845 register struct obj *otmp;
846 boolean already_seen = trap->tseen,
847 forcetrap = (trflags & FORCETRAP) != 0,
848 webmsgok = (trflags & NOWEBMSG) == 0,
849 forcebungle = (trflags & FORCEBUNGLE) != 0,
850 plunged = (trflags & TOOKPLUNGE) != 0,
851 adj_pit = conjoined_pits(trap, t_at(u.ux0, u.uy0), TRUE);
852 int oldumort;
853 int steed_article = ARTICLE_THE;
855 nomul(0);
857 /* KMH -- You can't escape the Sokoban level traps */
858 if (Sokoban && (ttype == PIT || ttype == SPIKED_PIT
859 || ttype == HOLE || ttype == TRAPDOOR)) {
860 /* The "air currents" message is still appropriate -- even when
861 * the hero isn't flying or levitating -- because it conveys the
862 * reason why the player cannot escape the trap with a dexterity
863 * check, clinging to the ceiling, etc.
865 pline("Air currents pull you down into %s %s!",
866 a_your[trap->madeby_u],
867 defsyms[trap_to_defsym(ttype)].explanation);
868 /* then proceed to normal trap effect */
869 } else if (already_seen && !forcetrap) {
870 if ((Levitation || (Flying && !plunged))
871 && (ttype == PIT || ttype == SPIKED_PIT || ttype == HOLE
872 || ttype == BEAR_TRAP)) {
873 You("%s over %s %s.", Levitation ? "float" : "fly",
874 a_your[trap->madeby_u],
875 defsyms[trap_to_defsym(ttype)].explanation);
876 return;
878 if (!Fumbling && ttype != MAGIC_PORTAL && ttype != VIBRATING_SQUARE
879 && ttype != ANTI_MAGIC && !forcebungle && !plunged && !adj_pit
880 && (!rn2(5) || ((ttype == PIT || ttype == SPIKED_PIT)
881 && is_clinger(youmonst.data)))) {
882 You("escape %s %s.", (ttype == ARROW_TRAP && !trap->madeby_u)
883 ? "an"
884 : a_your[trap->madeby_u],
885 defsyms[trap_to_defsym(ttype)].explanation);
886 return;
890 if (u.usteed) {
891 u.usteed->mtrapseen |= (1 << (ttype - 1));
892 /* suppress article in various steed messages when using its
893 name (which won't occur when hallucinating) */
894 if (has_mname(u.usteed) && !Hallucination)
895 steed_article = ARTICLE_NONE;
898 switch (ttype) {
899 case ARROW_TRAP:
900 if (trap->once && trap->tseen && !rn2(15)) {
901 You_hear("a loud click!");
902 deltrap(trap);
903 newsym(u.ux, u.uy);
904 break;
906 trap->once = 1;
907 seetrap(trap);
908 pline("An arrow shoots out at you!");
909 otmp = mksobj(ARROW, TRUE, FALSE);
910 otmp->quan = 1L;
911 otmp->owt = weight(otmp);
912 otmp->opoisoned = 0;
913 if (u.usteed && !rn2(2) && steedintrap(trap, otmp)) { /* nothing */
915 } else if (thitu(8, dmgval(otmp, &youmonst), otmp, "arrow")) {
916 obfree(otmp, (struct obj *) 0);
917 } else {
918 place_object(otmp, u.ux, u.uy);
919 if (!Blind)
920 otmp->dknown = 1;
921 stackobj(otmp);
922 newsym(u.ux, u.uy);
924 break;
926 case DART_TRAP:
927 if (trap->once && trap->tseen && !rn2(15)) {
928 You_hear("a soft click.");
929 deltrap(trap);
930 newsym(u.ux, u.uy);
931 break;
933 trap->once = 1;
934 seetrap(trap);
935 pline("A little dart shoots out at you!");
936 otmp = mksobj(DART, TRUE, FALSE);
937 otmp->quan = 1L;
938 otmp->owt = weight(otmp);
939 if (!rn2(6))
940 otmp->opoisoned = 1;
941 oldumort = u.umortality;
942 if (u.usteed && !rn2(2) && steedintrap(trap, otmp)) { /* nothing */
944 } else if (thitu(7, dmgval(otmp, &youmonst), otmp, "little dart")) {
945 if (otmp->opoisoned)
946 poisoned("dart", A_CON, "little dart",
947 /* if damage triggered life-saving,
948 poison is limited to attrib loss */
949 (u.umortality > oldumort) ? 0 : 10, TRUE);
950 obfree(otmp, (struct obj *) 0);
951 } else {
952 place_object(otmp, u.ux, u.uy);
953 if (!Blind)
954 otmp->dknown = 1;
955 stackobj(otmp);
956 newsym(u.ux, u.uy);
958 break;
960 case ROCKTRAP:
961 if (trap->once && trap->tseen && !rn2(15)) {
962 pline("A trap door in %s opens, but nothing falls out!",
963 the(ceiling(u.ux, u.uy)));
964 deltrap(trap);
965 newsym(u.ux, u.uy);
966 } else {
967 int dmg = d(2, 6); /* should be std ROCK dmg? */
969 trap->once = 1;
970 feeltrap(trap);
971 otmp = mksobj_at(ROCK, u.ux, u.uy, TRUE, FALSE);
972 otmp->quan = 1L;
973 otmp->owt = weight(otmp);
975 pline("A trap door in %s opens and %s falls on your %s!",
976 the(ceiling(u.ux, u.uy)), an(xname(otmp)), body_part(HEAD));
978 if (uarmh) {
979 if (is_metallic(uarmh)) {
980 pline("Fortunately, you are wearing a hard helmet.");
981 dmg = 2;
982 } else if (flags.verbose) {
983 pline("%s does not protect you.", Yname2(uarmh));
987 if (!Blind)
988 otmp->dknown = 1;
989 stackobj(otmp);
990 newsym(u.ux, u.uy); /* map the rock */
992 losehp(Maybe_Half_Phys(dmg), "falling rock", KILLED_BY_AN);
993 exercise(A_STR, FALSE);
995 break;
997 case SQKY_BOARD: /* stepped on a squeaky board */
998 if ((Levitation || Flying) && !forcetrap) {
999 if (!Blind) {
1000 seetrap(trap);
1001 if (Hallucination)
1002 You("notice a crease in the linoleum.");
1003 else
1004 You("notice a loose board below you.");
1006 } else {
1007 seetrap(trap);
1008 pline("A board beneath you %s%s%s.",
1009 Deaf ? "vibrates" : "squeaks ",
1010 Deaf ? "" : trapnote(trap, 0), Deaf ? "" : " loudly");
1011 wake_nearby();
1013 break;
1015 case BEAR_TRAP: {
1016 int dmg = d(2, 4);
1018 if ((Levitation || Flying) && !forcetrap)
1019 break;
1020 feeltrap(trap);
1021 if (amorphous(youmonst.data) || is_whirly(youmonst.data)
1022 || unsolid(youmonst.data)) {
1023 pline("%s bear trap closes harmlessly through you.",
1024 A_Your[trap->madeby_u]);
1025 break;
1027 if (!u.usteed && youmonst.data->msize <= MZ_SMALL) {
1028 pline("%s bear trap closes harmlessly over you.",
1029 A_Your[trap->madeby_u]);
1030 break;
1032 u.utrap = rn1(4, 4);
1033 u.utraptype = TT_BEARTRAP;
1034 if (u.usteed) {
1035 pline("%s bear trap closes on %s %s!", A_Your[trap->madeby_u],
1036 s_suffix(mon_nam(u.usteed)), mbodypart(u.usteed, FOOT));
1037 if (thitm(0, u.usteed, (struct obj *) 0, dmg, FALSE))
1038 u.utrap = 0; /* steed died, hero not trapped */
1039 } else {
1040 pline("%s bear trap closes on your %s!", A_Your[trap->madeby_u],
1041 body_part(FOOT));
1042 set_wounded_legs(rn2(2) ? RIGHT_SIDE : LEFT_SIDE, rn1(10, 10));
1043 if (u.umonnum == PM_OWLBEAR || u.umonnum == PM_BUGBEAR)
1044 You("howl in anger!");
1045 losehp(Maybe_Half_Phys(dmg), "bear trap", KILLED_BY_AN);
1047 exercise(A_DEX, FALSE);
1048 break;
1051 case SLP_GAS_TRAP:
1052 seetrap(trap);
1053 if (Sleep_resistance || breathless(youmonst.data)) {
1054 You("are enveloped in a cloud of gas!");
1055 } else {
1056 pline("A cloud of gas puts you to sleep!");
1057 fall_asleep(-rnd(25), TRUE);
1059 (void) steedintrap(trap, (struct obj *) 0);
1060 break;
1062 case RUST_TRAP:
1063 seetrap(trap);
1065 /* Unlike monsters, traps cannot aim their rust attacks at
1066 * you, so instead of looping through and taking either the
1067 * first rustable one or the body, we take whatever we get,
1068 * even if it is not rustable.
1070 switch (rn2(5)) {
1071 case 0:
1072 pline("%s you on the %s!", A_gush_of_water_hits, body_part(HEAD));
1073 (void) water_damage(uarmh, helm_simple_name(uarmh), TRUE);
1074 break;
1075 case 1:
1076 pline("%s your left %s!", A_gush_of_water_hits, body_part(ARM));
1077 if (water_damage(uarms, "shield", TRUE) != ER_NOTHING)
1078 break;
1079 if (u.twoweap || (uwep && bimanual(uwep)))
1080 (void) water_damage(u.twoweap ? uswapwep : uwep, 0, TRUE);
1081 glovecheck:
1082 (void) water_damage(uarmg, "gauntlets", TRUE);
1083 /* Not "metal gauntlets" since it gets called
1084 * even if it's leather for the message
1086 break;
1087 case 2:
1088 pline("%s your right %s!", A_gush_of_water_hits, body_part(ARM));
1089 (void) water_damage(uwep, 0, TRUE);
1090 goto glovecheck;
1091 default:
1092 pline("%s you!", A_gush_of_water_hits);
1093 for (otmp = invent; otmp; otmp = otmp->nobj)
1094 if (otmp->lamplit && otmp != uwep
1095 && (otmp != uswapwep || !u.twoweap))
1096 (void) snuff_lit(otmp);
1097 if (uarmc)
1098 (void) water_damage(uarmc, cloak_simple_name(uarmc), TRUE);
1099 else if (uarm)
1100 (void) water_damage(uarm, "armor", TRUE);
1101 else if (uarmu)
1102 (void) water_damage(uarmu, "shirt", TRUE);
1104 update_inventory();
1106 if (u.umonnum == PM_IRON_GOLEM) {
1107 int dam = u.mhmax;
1109 pline("%s you!", A_gush_of_water_hits);
1110 You("are covered with rust!");
1111 losehp(Maybe_Half_Phys(dam), "rusting away", KILLED_BY);
1112 } else if (u.umonnum == PM_GREMLIN && rn2(3)) {
1113 pline("%s you!", A_gush_of_water_hits);
1114 (void) split_mon(&youmonst, (struct monst *) 0);
1117 break;
1119 case FIRE_TRAP:
1120 seetrap(trap);
1121 dofiretrap((struct obj *) 0);
1122 break;
1124 case PIT:
1125 case SPIKED_PIT:
1126 /* KMH -- You can't escape the Sokoban level traps */
1127 if (!Sokoban && (Levitation || (Flying && !plunged)))
1128 break;
1129 feeltrap(trap);
1130 if (!Sokoban && is_clinger(youmonst.data) && !plunged) {
1131 if (trap->tseen) {
1132 You_see("%s %spit below you.", a_your[trap->madeby_u],
1133 ttype == SPIKED_PIT ? "spiked " : "");
1134 } else {
1135 pline("%s pit %sopens up under you!", A_Your[trap->madeby_u],
1136 ttype == SPIKED_PIT ? "full of spikes " : "");
1137 You("don't fall in!");
1139 break;
1141 if (!Sokoban) {
1142 char verbbuf[BUFSZ];
1144 if (u.usteed) {
1145 if ((trflags & RECURSIVETRAP) != 0)
1146 Sprintf(verbbuf, "and %s fall",
1147 x_monnam(u.usteed, steed_article, (char *) 0,
1148 SUPPRESS_SADDLE, FALSE));
1149 else
1150 Sprintf(verbbuf, "lead %s",
1151 x_monnam(u.usteed, steed_article, "poor",
1152 SUPPRESS_SADDLE, FALSE));
1153 } else if (adj_pit) {
1154 You("move into an adjacent pit.");
1155 } else {
1156 Strcpy(verbbuf,
1157 !plunged ? "fall" : (Flying ? "dive" : "plunge"));
1158 You("%s into %s pit!", verbbuf, a_your[trap->madeby_u]);
1161 /* wumpus reference */
1162 if (Role_if(PM_RANGER) && !trap->madeby_u && !trap->once
1163 && In_quest(&u.uz) && Is_qlocate(&u.uz)) {
1164 pline("Fortunately it has a bottom after all...");
1165 trap->once = 1;
1166 } else if (u.umonnum == PM_PIT_VIPER || u.umonnum == PM_PIT_FIEND) {
1167 pline("How pitiful. Isn't that the pits?");
1169 if (ttype == SPIKED_PIT) {
1170 const char *predicament = "on a set of sharp iron spikes";
1172 if (u.usteed) {
1173 pline("%s %s %s!",
1174 upstart(x_monnam(u.usteed, steed_article, "poor",
1175 SUPPRESS_SADDLE, FALSE)),
1176 adj_pit ? "steps" : "lands", predicament);
1177 } else
1178 You("%s %s!", adj_pit ? "step" : "land", predicament);
1180 u.utrap = rn1(6, 2);
1181 u.utraptype = TT_PIT;
1182 if (!steedintrap(trap, (struct obj *) 0)) {
1183 if (ttype == SPIKED_PIT) {
1184 oldumort = u.umortality;
1185 losehp(Maybe_Half_Phys(rnd(adj_pit ? 6 : 10)),
1186 plunged
1187 ? "deliberately plunged into a pit of iron spikes"
1188 : adj_pit ? "stepped into a pit of iron spikes"
1189 : "fell into a pit of iron spikes",
1190 NO_KILLER_PREFIX);
1191 if (!rn2(6))
1192 poisoned("spikes", A_STR,
1193 adj_pit ? "stepping on poison spikes"
1194 : "fall onto poison spikes",
1195 /* if damage triggered life-saving,
1196 poison is limited to attrib loss */
1197 (u.umortality > oldumort) ? 0 : 8, FALSE);
1198 } else {
1199 /* plunging flyers take spike damage but not pit damage */
1200 if (!adj_pit
1201 && !(plunged && (Flying || is_clinger(youmonst.data))))
1202 losehp(Maybe_Half_Phys(rnd(6)),
1203 plunged ? "deliberately plunged into a pit"
1204 : "fell into a pit",
1205 NO_KILLER_PREFIX);
1207 if (Punished && !carried(uball)) {
1208 unplacebc();
1209 ballfall();
1210 placebc();
1212 if (!adj_pit)
1213 selftouch("Falling, you");
1214 vision_full_recalc = 1; /* vision limits change */
1215 exercise(A_STR, FALSE);
1216 exercise(A_DEX, FALSE);
1218 break;
1220 case HOLE:
1221 case TRAPDOOR:
1222 if (!Can_fall_thru(&u.uz)) {
1223 seetrap(trap); /* normally done in fall_through */
1224 impossible("dotrap: %ss cannot exist on this level.",
1225 defsyms[trap_to_defsym(ttype)].explanation);
1226 break; /* don't activate it after all */
1228 fall_through(TRUE);
1229 break;
1231 case TELEP_TRAP:
1232 seetrap(trap);
1233 tele_trap(trap);
1234 break;
1236 case LEVEL_TELEP:
1237 seetrap(trap);
1238 level_tele_trap(trap);
1239 break;
1241 case WEB: /* Our luckless player has stumbled into a web. */
1242 feeltrap(trap);
1243 if (mu_maybe_destroy_web(&youmonst, webmsgok, trap))
1244 break;
1245 if (webmaker(youmonst.data)) {
1246 if (webmsgok)
1247 pline(trap->madeby_u ? "You take a walk on your web."
1248 : "There is a spider web here.");
1249 break;
1251 if (webmsgok) {
1252 char verbbuf[BUFSZ];
1254 if (forcetrap) {
1255 Strcpy(verbbuf, "are caught by");
1256 } else if (u.usteed) {
1257 Sprintf(verbbuf, "lead %s into",
1258 x_monnam(u.usteed, steed_article, "poor",
1259 SUPPRESS_SADDLE, FALSE));
1260 } else {
1261 Sprintf(verbbuf, "%s into",
1262 Levitation ? (const char *) "float"
1263 : locomotion(youmonst.data, "stumble"));
1265 You("%s %s spider web!", verbbuf, a_your[trap->madeby_u]);
1267 u.utraptype = TT_WEB;
1269 /* Time stuck in the web depends on your/steed strength. */
1271 register int str = ACURR(A_STR);
1273 /* If mounted, the steed gets trapped. Use mintrap
1274 * to do all the work. If mtrapped is set as a result,
1275 * unset it and set utrap instead. In the case of a
1276 * strongmonst and mintrap said it's trapped, use a
1277 * short but non-zero trap time. Otherwise, monsters
1278 * have no specific strength, so use player strength.
1279 * This gets skipped for webmsgok, which implies that
1280 * the steed isn't a factor.
1282 if (u.usteed && webmsgok) {
1283 /* mtmp location might not be up to date */
1284 u.usteed->mx = u.ux;
1285 u.usteed->my = u.uy;
1287 /* mintrap currently does not return 2(died) for webs */
1288 if (mintrap(u.usteed)) {
1289 u.usteed->mtrapped = 0;
1290 if (strongmonst(u.usteed->data))
1291 str = 17;
1292 } else {
1293 break;
1296 webmsgok = FALSE; /* mintrap printed the messages */
1298 if (str <= 3)
1299 u.utrap = rn1(6, 6);
1300 else if (str < 6)
1301 u.utrap = rn1(6, 4);
1302 else if (str < 9)
1303 u.utrap = rn1(4, 4);
1304 else if (str < 12)
1305 u.utrap = rn1(4, 2);
1306 else if (str < 15)
1307 u.utrap = rn1(2, 2);
1308 else if (str < 18)
1309 u.utrap = rnd(2);
1310 else if (str < 69)
1311 u.utrap = 1;
1312 else {
1313 u.utrap = 0;
1314 if (webmsgok)
1315 You("tear through %s web!", a_your[trap->madeby_u]);
1316 deltrap(trap);
1317 newsym(u.ux, u.uy); /* get rid of trap symbol */
1320 break;
1322 case STATUE_TRAP:
1323 (void) activate_statue_trap(trap, u.ux, u.uy, FALSE);
1324 break;
1326 case MAGIC_TRAP: /* A magic trap. */
1327 seetrap(trap);
1328 if (!rn2(30)) {
1329 deltrap(trap);
1330 newsym(u.ux, u.uy); /* update position */
1331 You("are caught in a magical explosion!");
1332 losehp(rnd(10), "magical explosion", KILLED_BY_AN);
1333 Your("body absorbs some of the magical energy!");
1334 u.uen = (u.uenmax += 2);
1335 break;
1336 } else {
1337 domagictrap();
1339 (void) steedintrap(trap, (struct obj *) 0);
1340 break;
1342 case ANTI_MAGIC:
1343 seetrap(trap);
1344 /* hero without magic resistance loses spell energy,
1345 hero with magic resistance takes damage instead;
1346 possibly non-intuitive but useful for play balance */
1347 if (!Antimagic) {
1348 drain_en(rnd(u.ulevel) + 1);
1349 } else {
1350 int dmgval2 = rnd(4), hp = Upolyd ? u.mh : u.uhp;
1352 /* Half_XXX_damage has opposite its usual effect (approx)
1353 but isn't cumulative if hero has more than one */
1354 if (Half_physical_damage || Half_spell_damage)
1355 dmgval2 += rnd(4);
1356 /* give Magicbane wielder dose of own medicine */
1357 if (uwep && uwep->oartifact == ART_MAGICBANE)
1358 dmgval2 += rnd(4);
1359 /* having an artifact--other than own quest one--which
1360 confers magic resistance simply by being carried
1361 also increases the effect */
1362 for (otmp = invent; otmp; otmp = otmp->nobj)
1363 if (otmp->oartifact && !is_quest_artifact(otmp)
1364 && defends_when_carried(AD_MAGM, otmp))
1365 break;
1366 if (otmp)
1367 dmgval2 += rnd(4);
1368 if (Passes_walls)
1369 dmgval2 = (dmgval2 + 3) / 4;
1371 You_feel((dmgval2 >= hp) ? "unbearably torpid!"
1372 : (dmgval2 >= hp / 4) ? "very lethargic."
1373 : "sluggish.");
1374 /* opposite of magical explosion */
1375 losehp(dmgval2, "anti-magic implosion", KILLED_BY_AN);
1377 break;
1379 case POLY_TRAP: {
1380 char verbbuf[BUFSZ];
1382 seetrap(trap);
1383 if (u.usteed)
1384 Sprintf(verbbuf, "lead %s",
1385 x_monnam(u.usteed, steed_article, (char *) 0,
1386 SUPPRESS_SADDLE, FALSE));
1387 else
1388 Sprintf(verbbuf, "%s", Levitation
1389 ? (const char *) "float"
1390 : locomotion(youmonst.data, "step"));
1391 You("%s onto a polymorph trap!", verbbuf);
1392 if (Antimagic || Unchanging) {
1393 shieldeff(u.ux, u.uy);
1394 You_feel("momentarily different.");
1395 /* Trap did nothing; don't remove it --KAA */
1396 } else {
1397 (void) steedintrap(trap, (struct obj *) 0);
1398 deltrap(trap); /* delete trap before polymorph */
1399 newsym(u.ux, u.uy); /* get rid of trap symbol */
1400 You_feel("a change coming over you.");
1401 polyself(0);
1403 break;
1405 case LANDMINE: {
1406 unsigned steed_mid = 0;
1407 struct obj *saddle = 0;
1409 if ((Levitation || Flying) && !forcetrap) {
1410 if (!already_seen && rn2(3))
1411 break;
1412 feeltrap(trap);
1413 pline("%s %s in a pile of soil below you.",
1414 already_seen ? "There is" : "You discover",
1415 trap->madeby_u ? "the trigger of your mine" : "a trigger");
1416 if (already_seen && rn2(3))
1417 break;
1418 pline("KAABLAMM!!! %s %s%s off!",
1419 forcebungle ? "Your inept attempt sets"
1420 : "The air currents set",
1421 already_seen ? a_your[trap->madeby_u] : "",
1422 already_seen ? " land mine" : "it");
1423 } else {
1424 /* prevent landmine from killing steed, throwing you to
1425 * the ground, and you being affected again by the same
1426 * mine because it hasn't been deleted yet
1428 static boolean recursive_mine = FALSE;
1430 if (recursive_mine)
1431 break;
1432 feeltrap(trap);
1433 pline("KAABLAMM!!! You triggered %s land mine!",
1434 a_your[trap->madeby_u]);
1435 if (u.usteed)
1436 steed_mid = u.usteed->m_id;
1437 recursive_mine = TRUE;
1438 (void) steedintrap(trap, (struct obj *) 0);
1439 recursive_mine = FALSE;
1440 saddle = sobj_at(SADDLE, u.ux, u.uy);
1441 set_wounded_legs(LEFT_SIDE, rn1(35, 41));
1442 set_wounded_legs(RIGHT_SIDE, rn1(35, 41));
1443 exercise(A_DEX, FALSE);
1445 blow_up_landmine(trap);
1446 if (steed_mid && saddle && !u.usteed)
1447 (void) keep_saddle_with_steedcorpse(steed_mid, fobj, saddle);
1448 newsym(u.ux, u.uy); /* update trap symbol */
1449 losehp(Maybe_Half_Phys(rnd(16)), "land mine", KILLED_BY_AN);
1450 /* fall recursively into the pit... */
1451 if ((trap = t_at(u.ux, u.uy)) != 0)
1452 dotrap(trap, RECURSIVETRAP);
1453 fill_pit(u.ux, u.uy);
1454 break;
1457 case ROLLING_BOULDER_TRAP: {
1458 int style = ROLL | (trap->tseen ? LAUNCH_KNOWN : 0);
1460 feeltrap(trap);
1461 pline("Click! You trigger a rolling boulder trap!");
1462 if (!launch_obj(BOULDER, trap->launch.x, trap->launch.y,
1463 trap->launch2.x, trap->launch2.y, style)) {
1464 deltrap(trap);
1465 newsym(u.ux, u.uy); /* get rid of trap symbol */
1466 pline("Fortunately for you, no boulder was released.");
1468 break;
1471 case MAGIC_PORTAL:
1472 feeltrap(trap);
1473 domagicportal(trap);
1474 break;
1476 case VIBRATING_SQUARE:
1477 feeltrap(trap);
1478 /* messages handled elsewhere; the trap symbol is merely to mark the
1479 * square for future reference */
1480 break;
1482 default:
1483 feeltrap(trap);
1484 impossible("You hit a trap of type %u", trap->ttyp);
1488 STATIC_OVL char *
1489 trapnote(trap, noprefix)
1490 struct trap *trap;
1491 boolean noprefix;
1493 static char tnbuf[12];
1494 const char *tn,
1495 *tnnames[12] = { "C note", "D flat", "D note", "E flat",
1496 "E note", "F note", "F sharp", "G note",
1497 "G sharp", "A note", "B flat", "B note" };
1499 tnbuf[0] = '\0';
1500 tn = tnnames[trap->tnote];
1501 if (!noprefix)
1502 Sprintf(tnbuf, "%s ",
1503 (*tn == 'A' || *tn == 'E' || *tn == 'F') ? "an" : "a");
1504 Sprintf(eos(tnbuf), "%s", tn);
1505 return tnbuf;
1508 STATIC_OVL int
1509 steedintrap(trap, otmp)
1510 struct trap *trap;
1511 struct obj *otmp;
1513 struct monst *steed = u.usteed;
1514 int tt;
1515 boolean trapkilled, steedhit;
1517 if (!steed || !trap)
1518 return 0;
1519 tt = trap->ttyp;
1520 steed->mx = u.ux;
1521 steed->my = u.uy;
1522 trapkilled = steedhit = FALSE;
1524 switch (tt) {
1525 case ARROW_TRAP:
1526 if (!otmp) {
1527 impossible("steed hit by non-existant arrow?");
1528 return 0;
1530 trapkilled = thitm(8, steed, otmp, 0, FALSE);
1531 steedhit = TRUE;
1532 break;
1533 case DART_TRAP:
1534 if (!otmp) {
1535 impossible("steed hit by non-existant dart?");
1536 return 0;
1538 trapkilled = thitm(7, steed, otmp, 0, FALSE);
1539 steedhit = TRUE;
1540 break;
1541 case SLP_GAS_TRAP:
1542 if (!resists_sleep(steed) && !breathless(steed->data)
1543 && !steed->msleeping && steed->mcanmove) {
1544 if (sleep_monst(steed, rnd(25), -1))
1545 /* no in_sight check here; you can feel it even if blind */
1546 pline("%s suddenly falls asleep!", Monnam(steed));
1548 steedhit = TRUE;
1549 break;
1550 case LANDMINE:
1551 trapkilled = thitm(0, steed, (struct obj *) 0, rnd(16), FALSE);
1552 steedhit = TRUE;
1553 break;
1554 case PIT:
1555 case SPIKED_PIT:
1556 trapkilled = (steed->mhp <= 0
1557 || thitm(0, steed, (struct obj *) 0,
1558 rnd((tt == PIT) ? 6 : 10), FALSE));
1559 steedhit = TRUE;
1560 break;
1561 case POLY_TRAP:
1562 if (!resists_magm(steed) && !resist(steed, WAND_CLASS, 0, NOTELL)) {
1563 (void) newcham(steed, (struct permonst *) 0, FALSE, FALSE);
1564 if (!can_saddle(steed) || !can_ride(steed))
1565 dismount_steed(DISMOUNT_POLY);
1566 else
1567 You("have to adjust yourself in the saddle on %s.",
1568 x_monnam(steed, ARTICLE_A, (char *) 0, SUPPRESS_SADDLE,
1569 FALSE));
1571 steedhit = TRUE;
1572 break;
1573 default:
1574 break;
1577 if (trapkilled) {
1578 dismount_steed(DISMOUNT_POLY);
1579 return 2;
1581 return steedhit ? 1 : 0;
1584 /* some actions common to both player and monsters for triggered landmine */
1585 void
1586 blow_up_landmine(trap)
1587 struct trap *trap;
1589 int x = trap->tx, y = trap->ty, dbx, dby;
1590 struct rm *lev = &levl[x][y];
1592 (void) scatter(x, y, 4,
1593 MAY_DESTROY | MAY_HIT | MAY_FRACTURE | VIS_EFFECTS,
1594 (struct obj *) 0);
1595 del_engr_at(x, y);
1596 wake_nearto(x, y, 400);
1597 if (IS_DOOR(lev->typ))
1598 lev->doormask = D_BROKEN;
1599 /* destroy drawbridge if present */
1600 if (lev->typ == DRAWBRIDGE_DOWN || is_drawbridge_wall(x, y) >= 0) {
1601 dbx = x, dby = y;
1602 /* if under the portcullis, the bridge is adjacent */
1603 if (find_drawbridge(&dbx, &dby))
1604 destroy_drawbridge(dbx, dby);
1605 trap = t_at(x, y); /* expected to be null after destruction */
1607 /* convert landmine into pit */
1608 if (trap) {
1609 if (Is_waterlevel(&u.uz) || Is_airlevel(&u.uz)) {
1610 /* no pits here */
1611 deltrap(trap);
1612 } else {
1613 trap->ttyp = PIT; /* explosion creates a pit */
1614 trap->madeby_u = FALSE; /* resulting pit isn't yours */
1615 seetrap(trap); /* and it isn't concealed */
1621 * The following are used to track launched objects to
1622 * prevent them from vanishing if you are killed. They
1623 * will reappear at the launchplace in bones files.
1625 static struct {
1626 struct obj *obj;
1627 xchar x, y;
1628 } launchplace;
1630 static void
1631 launch_drop_spot(obj, x, y)
1632 struct obj *obj;
1633 xchar x, y;
1635 if (!obj) {
1636 launchplace.obj = (struct obj *) 0;
1637 launchplace.x = 0;
1638 launchplace.y = 0;
1639 } else {
1640 launchplace.obj = obj;
1641 launchplace.x = x;
1642 launchplace.y = y;
1646 boolean
1647 launch_in_progress()
1649 if (launchplace.obj)
1650 return TRUE;
1651 return FALSE;
1654 void
1655 force_launch_placement()
1657 if (launchplace.obj) {
1658 launchplace.obj->otrapped = 0;
1659 place_object(launchplace.obj, launchplace.x, launchplace.y);
1664 * Move obj from (x1,y1) to (x2,y2)
1666 * Return 0 if no object was launched.
1667 * 1 if an object was launched and placed somewhere.
1668 * 2 if an object was launched, but used up.
1671 launch_obj(otyp, x1, y1, x2, y2, style)
1672 short otyp;
1673 register int x1, y1, x2, y2;
1674 int style;
1676 register struct monst *mtmp;
1677 register struct obj *otmp, *otmp2;
1678 register int dx, dy;
1679 struct obj *singleobj;
1680 boolean used_up = FALSE;
1681 boolean otherside = FALSE;
1682 int dist;
1683 int tmp;
1684 int delaycnt = 0;
1686 otmp = sobj_at(otyp, x1, y1);
1687 /* Try the other side too, for rolling boulder traps */
1688 if (!otmp && otyp == BOULDER) {
1689 otherside = TRUE;
1690 otmp = sobj_at(otyp, x2, y2);
1692 if (!otmp)
1693 return 0;
1694 if (otherside) { /* swap 'em */
1695 int tx, ty;
1697 tx = x1;
1698 ty = y1;
1699 x1 = x2;
1700 y1 = y2;
1701 x2 = tx;
1702 y2 = ty;
1705 if (otmp->quan == 1L) {
1706 obj_extract_self(otmp);
1707 singleobj = otmp;
1708 otmp = (struct obj *) 0;
1709 } else {
1710 singleobj = splitobj(otmp, 1L);
1711 obj_extract_self(singleobj);
1713 newsym(x1, y1);
1714 /* in case you're using a pick-axe to chop the boulder that's being
1715 launched (perhaps a monster triggered it), destroy context so that
1716 next dig attempt never thinks you're resuming previous effort */
1717 if ((otyp == BOULDER || otyp == STATUE)
1718 && singleobj->ox == context.digging.pos.x
1719 && singleobj->oy == context.digging.pos.y)
1720 (void) memset((genericptr_t) &context.digging, 0,
1721 sizeof(struct dig_info));
1723 dist = distmin(x1, y1, x2, y2);
1724 bhitpos.x = x1;
1725 bhitpos.y = y1;
1726 dx = sgn(x2 - x1);
1727 dy = sgn(y2 - y1);
1728 switch (style) {
1729 case ROLL | LAUNCH_UNSEEN:
1730 if (otyp == BOULDER) {
1731 You_hear(Hallucination ? "someone bowling."
1732 : "rumbling in the distance.");
1734 style &= ~LAUNCH_UNSEEN;
1735 goto roll;
1736 case ROLL | LAUNCH_KNOWN:
1737 /* use otrapped as a flag to ohitmon */
1738 singleobj->otrapped = 1;
1739 style &= ~LAUNCH_KNOWN;
1740 /* fall through */
1741 roll:
1742 case ROLL:
1743 delaycnt = 2;
1744 /* fall through */
1745 default:
1746 if (!delaycnt)
1747 delaycnt = 1;
1748 if (!cansee(bhitpos.x, bhitpos.y))
1749 curs_on_u();
1750 tmp_at(DISP_FLASH, obj_to_glyph(singleobj));
1751 tmp_at(bhitpos.x, bhitpos.y);
1753 /* Mark a spot to place object in bones files to prevent
1754 * loss of object. Use the starting spot to ensure that
1755 * a rolling boulder will still launch, which it wouldn't
1756 * do if left midstream. Unfortunately we can't use the
1757 * target resting spot, because there are some things/situations
1758 * that would prevent it from ever getting there (bars), and we
1759 * can't tell that yet.
1761 launch_drop_spot(singleobj, bhitpos.x, bhitpos.y);
1763 /* Set the object in motion */
1764 while (dist-- > 0 && !used_up) {
1765 struct trap *t;
1766 tmp_at(bhitpos.x, bhitpos.y);
1767 tmp = delaycnt;
1769 /* dstage@u.washington.edu -- Delay only if hero sees it */
1770 if (cansee(bhitpos.x, bhitpos.y))
1771 while (tmp-- > 0)
1772 delay_output();
1774 bhitpos.x += dx;
1775 bhitpos.y += dy;
1776 t = t_at(bhitpos.x, bhitpos.y);
1778 if ((mtmp = m_at(bhitpos.x, bhitpos.y)) != 0) {
1779 if (otyp == BOULDER && throws_rocks(mtmp->data)) {
1780 if (rn2(3)) {
1781 if (cansee(bhitpos.x, bhitpos.y))
1782 pline("%s snatches the boulder.", Monnam(mtmp));
1783 singleobj->otrapped = 0;
1784 (void) mpickobj(mtmp, singleobj);
1785 used_up = TRUE;
1786 launch_drop_spot((struct obj *) 0, 0, 0);
1787 break;
1790 if (ohitmon(mtmp, singleobj, (style == ROLL) ? -1 : dist,
1791 FALSE)) {
1792 used_up = TRUE;
1793 launch_drop_spot((struct obj *) 0, 0, 0);
1794 break;
1796 } else if (bhitpos.x == u.ux && bhitpos.y == u.uy) {
1797 if (multi)
1798 nomul(0);
1799 if (thitu(9 + singleobj->spe, dmgval(singleobj, &youmonst),
1800 singleobj, (char *) 0))
1801 stop_occupation();
1803 if (style == ROLL) {
1804 if (down_gate(bhitpos.x, bhitpos.y) != -1) {
1805 if (ship_object(singleobj, bhitpos.x, bhitpos.y, FALSE)) {
1806 used_up = TRUE;
1807 launch_drop_spot((struct obj *) 0, 0, 0);
1808 break;
1811 if (t && otyp == BOULDER) {
1812 switch (t->ttyp) {
1813 case LANDMINE:
1814 if (rn2(10) > 2) {
1815 pline(
1816 "KAABLAMM!!!%s",
1817 cansee(bhitpos.x, bhitpos.y)
1818 ? " The rolling boulder triggers a land mine."
1819 : "");
1820 deltrap(t);
1821 del_engr_at(bhitpos.x, bhitpos.y);
1822 place_object(singleobj, bhitpos.x, bhitpos.y);
1823 singleobj->otrapped = 0;
1824 fracture_rock(singleobj);
1825 (void) scatter(bhitpos.x, bhitpos.y, 4,
1826 MAY_DESTROY | MAY_HIT | MAY_FRACTURE
1827 | VIS_EFFECTS,
1828 (struct obj *) 0);
1829 if (cansee(bhitpos.x, bhitpos.y))
1830 newsym(bhitpos.x, bhitpos.y);
1831 used_up = TRUE;
1832 launch_drop_spot((struct obj *) 0, 0, 0);
1834 break;
1835 case LEVEL_TELEP:
1836 case TELEP_TRAP:
1837 if (cansee(bhitpos.x, bhitpos.y))
1838 pline("Suddenly the rolling boulder disappears!");
1839 else
1840 You_hear("a rumbling stop abruptly.");
1841 singleobj->otrapped = 0;
1842 if (t->ttyp == TELEP_TRAP)
1843 (void) rloco(singleobj);
1844 else {
1845 int newlev = random_teleport_level();
1846 d_level dest;
1848 if (newlev == depth(&u.uz) || In_endgame(&u.uz))
1849 continue;
1850 add_to_migration(singleobj);
1851 get_level(&dest, newlev);
1852 singleobj->ox = dest.dnum;
1853 singleobj->oy = dest.dlevel;
1854 singleobj->owornmask = (long) MIGR_RANDOM;
1856 seetrap(t);
1857 used_up = TRUE;
1858 launch_drop_spot((struct obj *) 0, 0, 0);
1859 break;
1860 case PIT:
1861 case SPIKED_PIT:
1862 case HOLE:
1863 case TRAPDOOR:
1864 /* the boulder won't be used up if there is a
1865 monster in the trap; stop rolling anyway */
1866 x2 = bhitpos.x, y2 = bhitpos.y; /* stops here */
1867 if (flooreffects(singleobj, x2, y2, "fall")) {
1868 used_up = TRUE;
1869 launch_drop_spot((struct obj *) 0, 0, 0);
1871 dist = -1; /* stop rolling immediately */
1872 break;
1874 if (used_up || dist == -1)
1875 break;
1877 if (flooreffects(singleobj, bhitpos.x, bhitpos.y, "fall")) {
1878 used_up = TRUE;
1879 launch_drop_spot((struct obj *) 0, 0, 0);
1880 break;
1882 if (otyp == BOULDER
1883 && (otmp2 = sobj_at(BOULDER, bhitpos.x, bhitpos.y)) != 0) {
1884 const char *bmsg = " as one boulder sets another in motion";
1886 if (!isok(bhitpos.x + dx, bhitpos.y + dy) || !dist
1887 || IS_ROCK(levl[bhitpos.x + dx][bhitpos.y + dy].typ))
1888 bmsg = " as one boulder hits another";
1890 You_hear("a loud crash%s!",
1891 cansee(bhitpos.x, bhitpos.y) ? bmsg : "");
1892 obj_extract_self(otmp2);
1893 /* pass off the otrapped flag to the next boulder */
1894 otmp2->otrapped = singleobj->otrapped;
1895 singleobj->otrapped = 0;
1896 place_object(singleobj, bhitpos.x, bhitpos.y);
1897 singleobj = otmp2;
1898 otmp2 = (struct obj *) 0;
1899 wake_nearto(bhitpos.x, bhitpos.y, 10 * 10);
1902 if (otyp == BOULDER && closed_door(bhitpos.x, bhitpos.y)) {
1903 if (cansee(bhitpos.x, bhitpos.y))
1904 pline_The("boulder crashes through a door.");
1905 levl[bhitpos.x][bhitpos.y].doormask = D_BROKEN;
1906 if (dist)
1907 unblock_point(bhitpos.x, bhitpos.y);
1910 /* if about to hit iron bars, do so now */
1911 if (dist > 0 && isok(bhitpos.x + dx, bhitpos.y + dy)
1912 && levl[bhitpos.x + dx][bhitpos.y + dy].typ == IRONBARS) {
1913 x2 = bhitpos.x, y2 = bhitpos.y; /* object stops here */
1914 if (hits_bars(&singleobj,
1915 x2, y2, x2+dx, y2+dy,
1916 !rn2(20), 0)) {
1917 if (!singleobj) {
1918 used_up = TRUE;
1919 launch_drop_spot((struct obj *) 0, 0, 0);
1921 break;
1925 tmp_at(DISP_END, 0);
1926 launch_drop_spot((struct obj *) 0, 0, 0);
1927 if (!used_up) {
1928 singleobj->otrapped = 0;
1929 place_object(singleobj, x2, y2);
1930 newsym(x2, y2);
1931 return 1;
1932 } else
1933 return 2;
1936 void
1937 seetrap(trap)
1938 struct trap *trap;
1940 if (!trap->tseen) {
1941 trap->tseen = 1;
1942 newsym(trap->tx, trap->ty);
1946 /* like seetrap() but overrides vision */
1947 void
1948 feeltrap(trap)
1949 struct trap *trap;
1951 trap->tseen = 1;
1952 map_trap(trap, 1);
1953 /* in case it's beneath something, redisplay the something */
1954 newsym(trap->tx, trap->ty);
1957 STATIC_OVL int
1958 mkroll_launch(ttmp, x, y, otyp, ocount)
1959 struct trap *ttmp;
1960 xchar x, y;
1961 short otyp;
1962 long ocount;
1964 struct obj *otmp;
1965 register int tmp;
1966 schar dx, dy;
1967 int distance;
1968 coord cc;
1969 coord bcc;
1970 int trycount = 0;
1971 boolean success = FALSE;
1972 int mindist = 4;
1974 if (ttmp->ttyp == ROLLING_BOULDER_TRAP)
1975 mindist = 2;
1976 distance = rn1(5, 4); /* 4..8 away */
1977 tmp = rn2(8); /* randomly pick a direction to try first */
1978 while (distance >= mindist) {
1979 dx = xdir[tmp];
1980 dy = ydir[tmp];
1981 cc.x = x;
1982 cc.y = y;
1983 /* Prevent boulder from being placed on water */
1984 if (ttmp->ttyp == ROLLING_BOULDER_TRAP
1985 && is_pool_or_lava(x + distance * dx, y + distance * dy))
1986 success = FALSE;
1987 else
1988 success = isclearpath(&cc, distance, dx, dy);
1989 if (ttmp->ttyp == ROLLING_BOULDER_TRAP) {
1990 boolean success_otherway;
1992 bcc.x = x;
1993 bcc.y = y;
1994 success_otherway = isclearpath(&bcc, distance, -(dx), -(dy));
1995 if (!success_otherway)
1996 success = FALSE;
1998 if (success)
1999 break;
2000 if (++tmp > 7)
2001 tmp = 0;
2002 if ((++trycount % 8) == 0)
2003 --distance;
2005 if (!success) {
2006 /* create the trap without any ammo, launch pt at trap location */
2007 cc.x = bcc.x = x;
2008 cc.y = bcc.y = y;
2009 } else {
2010 otmp = mksobj(otyp, TRUE, FALSE);
2011 otmp->quan = ocount;
2012 otmp->owt = weight(otmp);
2013 place_object(otmp, cc.x, cc.y);
2014 stackobj(otmp);
2016 ttmp->launch.x = cc.x;
2017 ttmp->launch.y = cc.y;
2018 if (ttmp->ttyp == ROLLING_BOULDER_TRAP) {
2019 ttmp->launch2.x = bcc.x;
2020 ttmp->launch2.y = bcc.y;
2021 } else
2022 ttmp->launch_otyp = otyp;
2023 newsym(ttmp->launch.x, ttmp->launch.y);
2024 return 1;
2027 STATIC_OVL boolean
2028 isclearpath(cc, distance, dx, dy)
2029 coord *cc;
2030 int distance;
2031 schar dx, dy;
2033 uchar typ;
2034 xchar x, y;
2036 x = cc->x;
2037 y = cc->y;
2038 while (distance-- > 0) {
2039 x += dx;
2040 y += dy;
2041 typ = levl[x][y].typ;
2042 if (!isok(x, y) || !ZAP_POS(typ) || closed_door(x, y))
2043 return FALSE;
2045 cc->x = x;
2046 cc->y = y;
2047 return TRUE;
2051 mintrap(mtmp)
2052 register struct monst *mtmp;
2054 register struct trap *trap = t_at(mtmp->mx, mtmp->my);
2055 boolean trapkilled = FALSE;
2056 struct permonst *mptr = mtmp->data;
2057 struct obj *otmp;
2059 if (!trap) {
2060 mtmp->mtrapped = 0; /* perhaps teleported? */
2061 } else if (mtmp->mtrapped) { /* is currently in the trap */
2062 if (!trap->tseen && cansee(mtmp->mx, mtmp->my) && canseemon(mtmp)
2063 && (trap->ttyp == SPIKED_PIT || trap->ttyp == BEAR_TRAP
2064 || trap->ttyp == HOLE || trap->ttyp == PIT
2065 || trap->ttyp == WEB)) {
2066 /* If you come upon an obviously trapped monster, then
2067 * you must be able to see the trap it's in too.
2069 seetrap(trap);
2072 if (!rn2(40)) {
2073 if (sobj_at(BOULDER, mtmp->mx, mtmp->my)
2074 && (trap->ttyp == PIT || trap->ttyp == SPIKED_PIT)) {
2075 if (!rn2(2)) {
2076 mtmp->mtrapped = 0;
2077 if (canseemon(mtmp))
2078 pline("%s pulls free...", Monnam(mtmp));
2079 fill_pit(mtmp->mx, mtmp->my);
2081 } else {
2082 mtmp->mtrapped = 0;
2084 } else if (metallivorous(mptr)) {
2085 if (trap->ttyp == BEAR_TRAP) {
2086 if (canseemon(mtmp))
2087 pline("%s eats a bear trap!", Monnam(mtmp));
2088 deltrap(trap);
2089 mtmp->meating = 5;
2090 mtmp->mtrapped = 0;
2091 } else if (trap->ttyp == SPIKED_PIT) {
2092 if (canseemon(mtmp))
2093 pline("%s munches on some spikes!", Monnam(mtmp));
2094 trap->ttyp = PIT;
2095 mtmp->meating = 5;
2098 } else {
2099 register int tt = trap->ttyp;
2100 boolean in_sight, tear_web, see_it,
2101 inescapable = force_mintrap || ((tt == HOLE || tt == PIT)
2102 && Sokoban && !trap->madeby_u);
2103 const char *fallverb;
2105 /* true when called from dotrap, inescapable is not an option */
2106 if (mtmp == u.usteed)
2107 inescapable = TRUE;
2108 if (!inescapable && ((mtmp->mtrapseen & (1 << (tt - 1))) != 0
2109 || (tt == HOLE && !mindless(mptr)))) {
2110 /* it has been in such a trap - perhaps it escapes */
2111 if (rn2(4))
2112 return 0;
2113 } else {
2114 mtmp->mtrapseen |= (1 << (tt - 1));
2116 /* Monster is aggravated by being trapped by you.
2117 Recognizing who made the trap isn't completely
2118 unreasonable; everybody has their own style. */
2119 if (trap->madeby_u && rnl(5))
2120 setmangry(mtmp);
2122 in_sight = canseemon(mtmp);
2123 see_it = cansee(mtmp->mx, mtmp->my);
2124 /* assume hero can tell what's going on for the steed */
2125 if (mtmp == u.usteed)
2126 in_sight = TRUE;
2127 switch (tt) {
2128 case ARROW_TRAP:
2129 if (trap->once && trap->tseen && !rn2(15)) {
2130 if (in_sight && see_it)
2131 pline("%s triggers a trap but nothing happens.",
2132 Monnam(mtmp));
2133 deltrap(trap);
2134 newsym(mtmp->mx, mtmp->my);
2135 break;
2137 trap->once = 1;
2138 otmp = mksobj(ARROW, TRUE, FALSE);
2139 otmp->quan = 1L;
2140 otmp->owt = weight(otmp);
2141 otmp->opoisoned = 0;
2142 if (in_sight)
2143 seetrap(trap);
2144 if (thitm(8, mtmp, otmp, 0, FALSE))
2145 trapkilled = TRUE;
2146 break;
2147 case DART_TRAP:
2148 if (trap->once && trap->tseen && !rn2(15)) {
2149 if (in_sight && see_it)
2150 pline("%s triggers a trap but nothing happens.",
2151 Monnam(mtmp));
2152 deltrap(trap);
2153 newsym(mtmp->mx, mtmp->my);
2154 break;
2156 trap->once = 1;
2157 otmp = mksobj(DART, TRUE, FALSE);
2158 otmp->quan = 1L;
2159 otmp->owt = weight(otmp);
2160 if (!rn2(6))
2161 otmp->opoisoned = 1;
2162 if (in_sight)
2163 seetrap(trap);
2164 if (thitm(7, mtmp, otmp, 0, FALSE))
2165 trapkilled = TRUE;
2166 break;
2167 case ROCKTRAP:
2168 if (trap->once && trap->tseen && !rn2(15)) {
2169 if (in_sight && see_it)
2170 pline(
2171 "A trap door above %s opens, but nothing falls out!",
2172 mon_nam(mtmp));
2173 deltrap(trap);
2174 newsym(mtmp->mx, mtmp->my);
2175 break;
2177 trap->once = 1;
2178 otmp = mksobj(ROCK, TRUE, FALSE);
2179 otmp->quan = 1L;
2180 otmp->owt = weight(otmp);
2181 if (in_sight)
2182 seetrap(trap);
2183 if (thitm(0, mtmp, otmp, d(2, 6), FALSE))
2184 trapkilled = TRUE;
2185 break;
2186 case SQKY_BOARD:
2187 if (is_flyer(mptr))
2188 break;
2189 /* stepped on a squeaky board */
2190 if (in_sight) {
2191 if (!Deaf) {
2192 pline("A board beneath %s squeaks %s loudly.",
2193 mon_nam(mtmp), trapnote(trap, 0));
2194 seetrap(trap);
2195 } else {
2196 pline("%s stops momentarily and appears to cringe.",
2197 Monnam(mtmp));
2199 } else
2200 You_hear("a distant %s squeak.", trapnote(trap, 1));
2201 /* wake up nearby monsters */
2202 wake_nearto(mtmp->mx, mtmp->my, 40);
2203 break;
2204 case BEAR_TRAP:
2205 if (mptr->msize > MZ_SMALL && !amorphous(mptr) && !is_flyer(mptr)
2206 && !is_whirly(mptr) && !unsolid(mptr)) {
2207 mtmp->mtrapped = 1;
2208 if (in_sight) {
2209 pline("%s is caught in %s bear trap!", Monnam(mtmp),
2210 a_your[trap->madeby_u]);
2211 seetrap(trap);
2212 } else {
2213 if (mptr == &mons[PM_OWLBEAR]
2214 || mptr == &mons[PM_BUGBEAR])
2215 You_hear("the roaring of an angry bear!");
2217 } else if (force_mintrap) {
2218 if (in_sight) {
2219 pline("%s evades %s bear trap!", Monnam(mtmp),
2220 a_your[trap->madeby_u]);
2221 seetrap(trap);
2224 if (mtmp->mtrapped)
2225 trapkilled = thitm(0, mtmp, (struct obj *) 0, d(2, 4), FALSE);
2226 break;
2227 case SLP_GAS_TRAP:
2228 if (!resists_sleep(mtmp) && !breathless(mptr) && !mtmp->msleeping
2229 && mtmp->mcanmove) {
2230 if (sleep_monst(mtmp, rnd(25), -1) && in_sight) {
2231 pline("%s suddenly falls asleep!", Monnam(mtmp));
2232 seetrap(trap);
2235 break;
2236 case RUST_TRAP: {
2237 struct obj *target;
2239 if (in_sight)
2240 seetrap(trap);
2241 switch (rn2(5)) {
2242 case 0:
2243 if (in_sight)
2244 pline("%s %s on the %s!", A_gush_of_water_hits,
2245 mon_nam(mtmp), mbodypart(mtmp, HEAD));
2246 target = which_armor(mtmp, W_ARMH);
2247 (void) water_damage(target, helm_simple_name(target), TRUE);
2248 break;
2249 case 1:
2250 if (in_sight)
2251 pline("%s %s's left %s!", A_gush_of_water_hits,
2252 mon_nam(mtmp), mbodypart(mtmp, ARM));
2253 target = which_armor(mtmp, W_ARMS);
2254 if (water_damage(target, "shield", TRUE) != ER_NOTHING)
2255 break;
2256 target = MON_WEP(mtmp);
2257 if (target && bimanual(target))
2258 (void) water_damage(target, 0, TRUE);
2259 glovecheck:
2260 target = which_armor(mtmp, W_ARMG);
2261 (void) water_damage(target, "gauntlets", TRUE);
2262 break;
2263 case 2:
2264 if (in_sight)
2265 pline("%s %s's right %s!", A_gush_of_water_hits,
2266 mon_nam(mtmp), mbodypart(mtmp, ARM));
2267 (void) water_damage(MON_WEP(mtmp), 0, TRUE);
2268 goto glovecheck;
2269 default:
2270 if (in_sight)
2271 pline("%s %s!", A_gush_of_water_hits, mon_nam(mtmp));
2272 for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
2273 if (otmp->lamplit
2274 && (otmp->owornmask & (W_WEP | W_SWAPWEP)) == 0)
2275 (void) snuff_lit(otmp);
2276 if ((target = which_armor(mtmp, W_ARMC)) != 0)
2277 (void) water_damage(target, cloak_simple_name(target),
2278 TRUE);
2279 else if ((target = which_armor(mtmp, W_ARM)) != 0)
2280 (void) water_damage(target, "armor", TRUE);
2281 else if ((target = which_armor(mtmp, W_ARMU)) != 0)
2282 (void) water_damage(target, "shirt", TRUE);
2285 if (mptr == &mons[PM_IRON_GOLEM]) {
2286 if (in_sight)
2287 pline("%s falls to pieces!", Monnam(mtmp));
2288 else if (mtmp->mtame)
2289 pline("May %s rust in peace.", mon_nam(mtmp));
2290 mondied(mtmp);
2291 if (mtmp->mhp <= 0)
2292 trapkilled = TRUE;
2293 } else if (mptr == &mons[PM_GREMLIN] && rn2(3)) {
2294 (void) split_mon(mtmp, (struct monst *) 0);
2296 break;
2297 } /* RUST_TRAP */
2298 case FIRE_TRAP:
2299 mfiretrap:
2300 if (in_sight)
2301 pline("A %s erupts from the %s under %s!", tower_of_flame,
2302 surface(mtmp->mx, mtmp->my), mon_nam(mtmp));
2303 else if (see_it) /* evidently `mtmp' is invisible */
2304 You_see("a %s erupt from the %s!", tower_of_flame,
2305 surface(mtmp->mx, mtmp->my));
2307 if (resists_fire(mtmp)) {
2308 if (in_sight) {
2309 shieldeff(mtmp->mx, mtmp->my);
2310 pline("%s is uninjured.", Monnam(mtmp));
2312 } else {
2313 int num = d(2, 4), alt;
2314 boolean immolate = FALSE;
2316 /* paper burns very fast, assume straw is tightly
2317 * packed and burns a bit slower */
2318 switch (monsndx(mptr)) {
2319 case PM_PAPER_GOLEM:
2320 immolate = TRUE;
2321 alt = mtmp->mhpmax;
2322 break;
2323 case PM_STRAW_GOLEM:
2324 alt = mtmp->mhpmax / 2;
2325 break;
2326 case PM_WOOD_GOLEM:
2327 alt = mtmp->mhpmax / 4;
2328 break;
2329 case PM_LEATHER_GOLEM:
2330 alt = mtmp->mhpmax / 8;
2331 break;
2332 default:
2333 alt = 0;
2334 break;
2336 if (alt > num)
2337 num = alt;
2339 if (thitm(0, mtmp, (struct obj *) 0, num, immolate))
2340 trapkilled = TRUE;
2341 else
2342 /* we know mhp is at least `num' below mhpmax,
2343 so no (mhp > mhpmax) check is needed here */
2344 mtmp->mhpmax -= rn2(num + 1);
2346 if (burnarmor(mtmp) || rn2(3)) {
2347 (void) destroy_mitem(mtmp, SCROLL_CLASS, AD_FIRE);
2348 (void) destroy_mitem(mtmp, SPBOOK_CLASS, AD_FIRE);
2349 (void) destroy_mitem(mtmp, POTION_CLASS, AD_FIRE);
2351 if (burn_floor_objects(mtmp->mx, mtmp->my, see_it, FALSE)
2352 && !see_it && distu(mtmp->mx, mtmp->my) <= 3 * 3)
2353 You("smell smoke.");
2354 if (is_ice(mtmp->mx, mtmp->my))
2355 melt_ice(mtmp->mx, mtmp->my, (char *) 0);
2356 if (see_it)
2357 seetrap(trap);
2358 break;
2359 case PIT:
2360 case SPIKED_PIT:
2361 fallverb = "falls";
2362 if (is_flyer(mptr) || is_floater(mptr)
2363 || (mtmp->wormno && count_wsegs(mtmp) > 5)
2364 || is_clinger(mptr)) {
2365 if (force_mintrap && !Sokoban) {
2366 /* openfallingtrap; not inescapable here */
2367 if (in_sight) {
2368 seetrap(trap);
2369 pline("%s doesn't fall into the pit.", Monnam(mtmp));
2371 break; /* inescapable = FALSE; */
2373 if (!inescapable)
2374 break; /* avoids trap */
2375 fallverb = "is dragged"; /* sokoban pit */
2377 if (!passes_walls(mptr))
2378 mtmp->mtrapped = 1;
2379 if (in_sight) {
2380 pline("%s %s into %s pit!", Monnam(mtmp), fallverb,
2381 a_your[trap->madeby_u]);
2382 if (mptr == &mons[PM_PIT_VIPER]
2383 || mptr == &mons[PM_PIT_FIEND])
2384 pline("How pitiful. Isn't that the pits?");
2385 seetrap(trap);
2387 mselftouch(mtmp, "Falling, ", FALSE);
2388 if (mtmp->mhp <= 0 || thitm(0, mtmp, (struct obj *) 0,
2389 rnd((tt == PIT) ? 6 : 10), FALSE))
2390 trapkilled = TRUE;
2391 break;
2392 case HOLE:
2393 case TRAPDOOR:
2394 if (!Can_fall_thru(&u.uz)) {
2395 impossible("mintrap: %ss cannot exist on this level.",
2396 defsyms[trap_to_defsym(tt)].explanation);
2397 break; /* don't activate it after all */
2399 if (is_flyer(mptr) || is_floater(mptr) || mptr == &mons[PM_WUMPUS]
2400 || (mtmp->wormno && count_wsegs(mtmp) > 5)
2401 || mptr->msize >= MZ_HUGE) {
2402 if (force_mintrap && !Sokoban) {
2403 /* openfallingtrap; not inescapable here */
2404 if (in_sight) {
2405 seetrap(trap);
2406 if (tt == TRAPDOOR)
2407 pline(
2408 "A trap door opens, but %s doesn't fall through.",
2409 mon_nam(mtmp));
2410 else /* (tt == HOLE) */
2411 pline("%s doesn't fall through the hole.",
2412 Monnam(mtmp));
2414 break; /* inescapable = FALSE; */
2416 if (inescapable) { /* sokoban hole */
2417 if (in_sight) {
2418 pline("%s seems to be yanked down!", Monnam(mtmp));
2419 /* suppress message in mlevel_tele_trap() */
2420 in_sight = FALSE;
2421 seetrap(trap);
2423 } else
2424 break;
2426 /*FALLTHRU*/
2427 case LEVEL_TELEP:
2428 case MAGIC_PORTAL: {
2429 int mlev_res;
2431 mlev_res = mlevel_tele_trap(mtmp, trap, inescapable, in_sight);
2432 if (mlev_res)
2433 return mlev_res;
2434 break;
2436 case TELEP_TRAP:
2437 mtele_trap(mtmp, trap, in_sight);
2438 break;
2439 case WEB:
2440 /* Monster in a web. */
2441 if (webmaker(mptr))
2442 break;
2443 if (mu_maybe_destroy_web(mtmp, in_sight, trap))
2444 break;
2445 tear_web = FALSE;
2446 switch (monsndx(mptr)) {
2447 case PM_OWLBEAR: /* Eric Backus */
2448 case PM_BUGBEAR:
2449 if (!in_sight) {
2450 You_hear("the roaring of a confused bear!");
2451 mtmp->mtrapped = 1;
2452 break;
2454 /* fall though */
2455 default:
2456 if (mptr->mlet == S_GIANT
2457 /* exclude baby dragons and relatively short worms */
2458 || (mptr->mlet == S_DRAGON && extra_nasty(mptr))
2459 || (mtmp->wormno && count_wsegs(mtmp) > 5)) {
2460 tear_web = TRUE;
2461 } else if (in_sight) {
2462 pline("%s is caught in %s spider web.", Monnam(mtmp),
2463 a_your[trap->madeby_u]);
2464 seetrap(trap);
2466 mtmp->mtrapped = tear_web ? 0 : 1;
2467 break;
2468 /* this list is fairly arbitrary; it deliberately
2469 excludes wumpus & giant/ettin zombies/mummies */
2470 case PM_TITANOTHERE:
2471 case PM_BALUCHITHERIUM:
2472 case PM_PURPLE_WORM:
2473 case PM_JABBERWOCK:
2474 case PM_IRON_GOLEM:
2475 case PM_BALROG:
2476 case PM_KRAKEN:
2477 case PM_MASTODON:
2478 case PM_ORION:
2479 case PM_NORN:
2480 case PM_CYCLOPS:
2481 case PM_LORD_SURTUR:
2482 tear_web = TRUE;
2483 break;
2485 if (tear_web) {
2486 if (in_sight)
2487 pline("%s tears through %s spider web!", Monnam(mtmp),
2488 a_your[trap->madeby_u]);
2489 deltrap(trap);
2490 newsym(mtmp->mx, mtmp->my);
2491 } else if (force_mintrap && !mtmp->mtrapped) {
2492 if (in_sight) {
2493 pline("%s avoids %s spider web!", Monnam(mtmp),
2494 a_your[trap->madeby_u]);
2495 seetrap(trap);
2498 break;
2499 case STATUE_TRAP:
2500 break;
2501 case MAGIC_TRAP:
2502 /* A magic trap. Monsters usually immune. */
2503 if (!rn2(21))
2504 goto mfiretrap;
2505 break;
2506 case ANTI_MAGIC:
2507 /* similar to hero's case, more or less */
2508 if (!resists_magm(mtmp)) { /* lose spell energy */
2509 if (!mtmp->mcan && (attacktype(mptr, AT_MAGC)
2510 || attacktype(mptr, AT_BREA))) {
2511 mtmp->mspec_used += d(2, 2);
2512 if (in_sight) {
2513 seetrap(trap);
2514 pline("%s seems lethargic.", Monnam(mtmp));
2517 } else { /* take some damage */
2518 int dmgval2 = rnd(4);
2520 if ((otmp = MON_WEP(mtmp)) != 0
2521 && otmp->oartifact == ART_MAGICBANE)
2522 dmgval2 += rnd(4);
2523 for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
2524 if (otmp->oartifact
2525 && defends_when_carried(AD_MAGM, otmp))
2526 break;
2527 if (otmp)
2528 dmgval2 += rnd(4);
2529 if (passes_walls(mptr))
2530 dmgval2 = (dmgval2 + 3) / 4;
2532 if (in_sight)
2533 seetrap(trap);
2534 if ((mtmp->mhp -= dmgval2) <= 0)
2535 monkilled(mtmp,
2536 in_sight
2537 ? "compression from an anti-magic field"
2538 : (const char *) 0,
2539 -AD_MAGM);
2540 if (mtmp->mhp <= 0)
2541 trapkilled = TRUE;
2542 if (see_it)
2543 newsym(trap->tx, trap->ty);
2545 break;
2546 case LANDMINE:
2547 if (rn2(3))
2548 break; /* monsters usually don't set it off */
2549 if (is_flyer(mptr)) {
2550 boolean already_seen = trap->tseen;
2552 if (in_sight && !already_seen) {
2553 pline("A trigger appears in a pile of soil below %s.",
2554 mon_nam(mtmp));
2555 seetrap(trap);
2557 if (rn2(3))
2558 break;
2559 if (in_sight) {
2560 newsym(mtmp->mx, mtmp->my);
2561 pline_The("air currents set %s off!",
2562 already_seen ? "a land mine" : "it");
2564 } else if (in_sight) {
2565 newsym(mtmp->mx, mtmp->my);
2566 pline("%s%s triggers %s land mine!",
2567 !Deaf ? "KAABLAMM!!! " : "", Monnam(mtmp),
2568 a_your[trap->madeby_u]);
2570 if (!in_sight && !Deaf)
2571 pline("Kaablamm! You hear an explosion in the distance!");
2572 blow_up_landmine(trap);
2573 /* explosion might have destroyed a drawbridge; don't
2574 dish out more damage if monster is already dead */
2575 if (mtmp->mhp <= 0
2576 || thitm(0, mtmp, (struct obj *) 0, rnd(16), FALSE)) {
2577 trapkilled = TRUE;
2578 } else {
2579 /* monsters recursively fall into new pit */
2580 if (mintrap(mtmp) == 2)
2581 trapkilled = TRUE;
2583 /* a boulder may fill the new pit, crushing monster */
2584 fill_pit(trap->tx, trap->ty);
2585 if (mtmp->mhp <= 0)
2586 trapkilled = TRUE;
2587 if (unconscious()) {
2588 multi = -1;
2589 nomovemsg = "The explosion awakens you!";
2591 break;
2592 case POLY_TRAP:
2593 if (resists_magm(mtmp)) {
2594 shieldeff(mtmp->mx, mtmp->my);
2595 } else if (!resist(mtmp, WAND_CLASS, 0, NOTELL)) {
2596 if (newcham(mtmp, (struct permonst *) 0, FALSE, FALSE))
2597 /* we're done with mptr but keep it up to date */
2598 mptr = mtmp->data;
2599 if (in_sight)
2600 seetrap(trap);
2602 break;
2603 case ROLLING_BOULDER_TRAP:
2604 if (!is_flyer(mptr)) {
2605 int style = ROLL | (in_sight ? 0 : LAUNCH_UNSEEN);
2607 newsym(mtmp->mx, mtmp->my);
2608 if (in_sight)
2609 pline("Click! %s triggers %s.", Monnam(mtmp),
2610 trap->tseen ? "a rolling boulder trap" : something);
2611 if (launch_obj(BOULDER, trap->launch.x, trap->launch.y,
2612 trap->launch2.x, trap->launch2.y, style)) {
2613 if (in_sight)
2614 trap->tseen = TRUE;
2615 if (mtmp->mhp <= 0)
2616 trapkilled = TRUE;
2617 } else {
2618 deltrap(trap);
2619 newsym(mtmp->mx, mtmp->my);
2622 break;
2623 case VIBRATING_SQUARE:
2624 if (see_it && !Blind) {
2625 if (in_sight)
2626 pline("You see a strange vibration beneath %s %s.",
2627 s_suffix(mon_nam(mtmp)),
2628 makeplural(mbodypart(mtmp, FOOT)));
2629 else
2630 pline("You see the ground vibrate in the distance.");
2631 seetrap(trap);
2633 break;
2634 default:
2635 impossible("Some monster encountered a strange trap of type %d.",
2636 tt);
2639 if (trapkilled)
2640 return 2;
2641 return mtmp->mtrapped;
2644 /* Combine cockatrice checks into single functions to avoid repeating code. */
2645 void
2646 instapetrify(str)
2647 const char *str;
2649 if (Stone_resistance)
2650 return;
2651 if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM))
2652 return;
2653 You("turn to stone...");
2654 killer.format = KILLED_BY;
2655 if (str != killer.name)
2656 Strcpy(killer.name, str ? str : "");
2657 done(STONING);
2660 void
2661 minstapetrify(mon, byplayer)
2662 struct monst *mon;
2663 boolean byplayer;
2665 if (resists_ston(mon))
2666 return;
2667 if (poly_when_stoned(mon->data)) {
2668 mon_to_stone(mon);
2669 return;
2671 if (!vamp_stone(mon))
2672 return;
2674 /* give a "<mon> is slowing down" message and also remove
2675 intrinsic speed (comparable to similar effect on the hero) */
2676 mon_adjust_speed(mon, -3, (struct obj *) 0);
2678 if (cansee(mon->mx, mon->my))
2679 pline("%s turns to stone.", Monnam(mon));
2680 if (byplayer) {
2681 stoned = TRUE;
2682 xkilled(mon, XKILL_NOMSG);
2683 } else
2684 monstone(mon);
2687 void
2688 selftouch(arg)
2689 const char *arg;
2691 char kbuf[BUFSZ];
2693 if (uwep && uwep->otyp == CORPSE && touch_petrifies(&mons[uwep->corpsenm])
2694 && !Stone_resistance) {
2695 pline("%s touch the %s corpse.", arg, mons[uwep->corpsenm].mname);
2696 Sprintf(kbuf, "%s corpse", an(mons[uwep->corpsenm].mname));
2697 instapetrify(kbuf);
2698 /* life-saved; unwield the corpse if we can't handle it */
2699 if (!uarmg && !Stone_resistance)
2700 uwepgone();
2702 /* Or your secondary weapon, if wielded [hypothetical; we don't
2703 allow two-weapon combat when either weapon is a corpse] */
2704 if (u.twoweap && uswapwep && uswapwep->otyp == CORPSE
2705 && touch_petrifies(&mons[uswapwep->corpsenm]) && !Stone_resistance) {
2706 pline("%s touch the %s corpse.", arg, mons[uswapwep->corpsenm].mname);
2707 Sprintf(kbuf, "%s corpse", an(mons[uswapwep->corpsenm].mname));
2708 instapetrify(kbuf);
2709 /* life-saved; unwield the corpse */
2710 if (!uarmg && !Stone_resistance)
2711 uswapwepgone();
2715 void
2716 mselftouch(mon, arg, byplayer)
2717 struct monst *mon;
2718 const char *arg;
2719 boolean byplayer;
2721 struct obj *mwep = MON_WEP(mon);
2723 if (mwep && mwep->otyp == CORPSE && touch_petrifies(&mons[mwep->corpsenm])
2724 && !resists_ston(mon)) {
2725 if (cansee(mon->mx, mon->my)) {
2726 pline("%s%s touches %s.", arg ? arg : "",
2727 arg ? mon_nam(mon) : Monnam(mon),
2728 corpse_xname(mwep, (const char *) 0, CXN_PFX_THE));
2730 minstapetrify(mon, byplayer);
2731 /* if life-saved, might not be able to continue wielding */
2732 if (mon->mhp > 0 && !which_armor(mon, W_ARMG) && !resists_ston(mon))
2733 mwepgone(mon);
2737 /* start levitating */
2738 void
2739 float_up()
2741 context.botl = TRUE;
2742 if (u.utrap) {
2743 if (u.utraptype == TT_PIT) {
2744 u.utrap = 0;
2745 You("float up, out of the pit!");
2746 vision_full_recalc = 1; /* vision limits change */
2747 fill_pit(u.ux, u.uy);
2748 } else if (u.utraptype == TT_INFLOOR) {
2749 Your("body pulls upward, but your %s are still stuck.",
2750 makeplural(body_part(LEG)));
2751 } else {
2752 You("float up, only your %s is still stuck.", body_part(LEG));
2754 #if 0
2755 } else if (Is_waterlevel(&u.uz)) {
2756 pline("It feels as though you've lost some weight.");
2757 #endif
2758 } else if (u.uinwater) {
2759 spoteffects(TRUE);
2760 } else if (u.uswallow) {
2761 You(is_animal(u.ustuck->data) ? "float away from the %s."
2762 : "spiral up into %s.",
2763 is_animal(u.ustuck->data) ? surface(u.ux, u.uy)
2764 : mon_nam(u.ustuck));
2765 } else if (Hallucination) {
2766 pline("Up, up, and awaaaay! You're walking on air!");
2767 } else if (Is_airlevel(&u.uz)) {
2768 You("gain control over your movements.");
2769 } else {
2770 You("start to float in the air!");
2772 if (u.usteed && !is_floater(u.usteed->data)
2773 && !is_flyer(u.usteed->data)) {
2774 if (Lev_at_will) {
2775 pline("%s magically floats up!", Monnam(u.usteed));
2776 } else {
2777 You("cannot stay on %s.", mon_nam(u.usteed));
2778 dismount_steed(DISMOUNT_GENERIC);
2781 if (Flying)
2782 You("are no longer able to control your flight.");
2783 BFlying |= I_SPECIAL;
2784 return;
2787 void
2788 fill_pit(x, y)
2789 int x, y;
2791 struct obj *otmp;
2792 struct trap *t;
2794 if ((t = t_at(x, y)) && ((t->ttyp == PIT) || (t->ttyp == SPIKED_PIT))
2795 && (otmp = sobj_at(BOULDER, x, y))) {
2796 obj_extract_self(otmp);
2797 (void) flooreffects(otmp, x, y, "settle");
2801 /* stop levitating */
2803 float_down(hmask, emask)
2804 long hmask, emask; /* might cancel timeout */
2806 register struct trap *trap = (struct trap *) 0;
2807 d_level current_dungeon_level;
2808 boolean no_msg = FALSE;
2810 HLevitation &= ~hmask;
2811 ELevitation &= ~emask;
2812 if (Levitation)
2813 return 0; /* maybe another ring/potion/boots */
2814 if (BLevitation) {
2815 /* Levitation is blocked, so hero is not actually floating
2816 hence shouldn't have float_down effects and feedback */
2817 float_vs_flight(); /* before nomul() rather than after */
2818 return 0;
2820 context.botl = TRUE;
2821 nomul(0); /* stop running or resting */
2822 if (BFlying) {
2823 /* controlled flight no longer overridden by levitation */
2824 BFlying &= ~I_SPECIAL;
2825 if (Flying) {
2826 You("have stopped levitating and are now flying.");
2827 return 1;
2830 if (u.uswallow) {
2831 You("float down, but you are still %s.",
2832 is_animal(u.ustuck->data) ? "swallowed" : "engulfed");
2833 return 1;
2836 if (Punished && !carried(uball)
2837 && (is_pool(uball->ox, uball->oy)
2838 || ((trap = t_at(uball->ox, uball->oy))
2839 && ((trap->ttyp == PIT) || (trap->ttyp == SPIKED_PIT)
2840 || (trap->ttyp == TRAPDOOR) || (trap->ttyp == HOLE))))) {
2841 u.ux0 = u.ux;
2842 u.uy0 = u.uy;
2843 u.ux = uball->ox;
2844 u.uy = uball->oy;
2845 movobj(uchain, uball->ox, uball->oy);
2846 newsym(u.ux0, u.uy0);
2847 vision_full_recalc = 1; /* in case the hero moved. */
2849 /* check for falling into pool - added by GAN 10/20/86 */
2850 if (!Flying) {
2851 if (!u.uswallow && u.ustuck) {
2852 if (sticks(youmonst.data))
2853 You("aren't able to maintain your hold on %s.",
2854 mon_nam(u.ustuck));
2855 else
2856 pline("Startled, %s can no longer hold you!",
2857 mon_nam(u.ustuck));
2858 u.ustuck = 0;
2860 /* kludge alert:
2861 * drown() and lava_effects() print various messages almost
2862 * every time they're called which conflict with the "fall
2863 * into" message below. Thus, we want to avoid printing
2864 * confusing, duplicate or out-of-order messages.
2865 * Use knowledge of the two routines as a hack -- this
2866 * should really be handled differently -dlc
2868 if (is_pool(u.ux, u.uy) && !Wwalking && !Swimming && !u.uinwater)
2869 no_msg = drown();
2871 if (is_lava(u.ux, u.uy)) {
2872 (void) lava_effects();
2873 no_msg = TRUE;
2876 if (!trap) {
2877 trap = t_at(u.ux, u.uy);
2878 if (Is_airlevel(&u.uz)) {
2879 You("begin to tumble in place.");
2880 } else if (Is_waterlevel(&u.uz) && !no_msg) {
2881 You_feel("heavier.");
2882 /* u.uinwater msgs already in spoteffects()/drown() */
2883 } else if (!u.uinwater && !no_msg) {
2884 if (!(emask & W_SADDLE)) {
2885 if (Sokoban && trap) {
2886 /* Justification elsewhere for Sokoban traps is based
2887 * on air currents. This is consistent with that.
2888 * The unexpected additional force of the air currents
2889 * once levitation ceases knocks you off your feet.
2891 if (Hallucination)
2892 pline("Bummer! You've crashed.");
2893 else
2894 You("fall over.");
2895 losehp(rnd(2), "dangerous winds", KILLED_BY);
2896 if (u.usteed)
2897 dismount_steed(DISMOUNT_FELL);
2898 selftouch("As you fall, you");
2899 } else if (u.usteed && (is_floater(u.usteed->data)
2900 || is_flyer(u.usteed->data))) {
2901 You("settle more firmly in the saddle.");
2902 } else if (Hallucination) {
2903 pline("Bummer! You've %s.",
2904 is_pool(u.ux, u.uy)
2905 ? "splashed down"
2906 : "hit the ground");
2907 } else {
2908 You("float gently to the %s.", surface(u.ux, u.uy));
2914 /* can't rely on u.uz0 for detecting trap door-induced level change;
2915 it gets changed to reflect the new level before we can check it */
2916 assign_level(&current_dungeon_level, &u.uz);
2917 if (trap) {
2918 switch (trap->ttyp) {
2919 case STATUE_TRAP:
2920 break;
2921 case HOLE:
2922 case TRAPDOOR:
2923 if (!Can_fall_thru(&u.uz) || u.ustuck)
2924 break;
2925 /*FALLTHRU*/
2926 default:
2927 if (!u.utrap) /* not already in the trap */
2928 dotrap(trap, 0);
2931 if (!Is_airlevel(&u.uz) && !Is_waterlevel(&u.uz) && !u.uswallow
2932 /* falling through trap door calls goto_level,
2933 and goto_level does its own pickup() call */
2934 && on_level(&u.uz, &current_dungeon_level))
2935 (void) pickup(1);
2936 return 1;
2939 /* shared code for climbing out of a pit */
2940 void
2941 climb_pit()
2943 if (!u.utrap || u.utraptype != TT_PIT)
2944 return;
2946 if (Passes_walls) {
2947 /* marked as trapped so they can pick things up */
2948 You("ascend from the pit.");
2949 u.utrap = 0;
2950 fill_pit(u.ux, u.uy);
2951 vision_full_recalc = 1; /* vision limits change */
2952 } else if (!rn2(2) && sobj_at(BOULDER, u.ux, u.uy)) {
2953 Your("%s gets stuck in a crevice.", body_part(LEG));
2954 display_nhwindow(WIN_MESSAGE, FALSE);
2955 clear_nhwindow(WIN_MESSAGE);
2956 You("free your %s.", body_part(LEG));
2957 } else if ((Flying || is_clinger(youmonst.data)) && !Sokoban) {
2958 /* eg fell in pit, then poly'd to a flying monster;
2959 or used '>' to deliberately enter it */
2960 You("%s from the pit.", Flying ? "fly" : "climb");
2961 u.utrap = 0;
2962 fill_pit(u.ux, u.uy);
2963 vision_full_recalc = 1; /* vision limits change */
2964 } else if (!(--u.utrap)) {
2965 You("%s to the edge of the pit.",
2966 (Sokoban && Levitation)
2967 ? "struggle against the air currents and float"
2968 : u.usteed ? "ride" : "crawl");
2969 fill_pit(u.ux, u.uy);
2970 vision_full_recalc = 1; /* vision limits change */
2971 } else if (u.dz || flags.verbose) {
2972 if (u.usteed)
2973 Norep("%s is still in a pit.", upstart(y_monnam(u.usteed)));
2974 else
2975 Norep((Hallucination && !rn2(5))
2976 ? "You've fallen, and you can't get up."
2977 : "You are still in a pit.");
2981 STATIC_OVL void
2982 dofiretrap(box)
2983 struct obj *box; /* null for floor trap */
2985 boolean see_it = !Blind;
2986 int num, alt;
2988 /* Bug: for box case, the equivalent of burn_floor_objects() ought
2989 * to be done upon its contents.
2992 if ((box && !carried(box)) ? is_pool(box->ox, box->oy) : Underwater) {
2993 pline("A cascade of steamy bubbles erupts from %s!",
2994 the(box ? xname(box) : surface(u.ux, u.uy)));
2995 if (Fire_resistance)
2996 You("are uninjured.");
2997 else
2998 losehp(rnd(3), "boiling water", KILLED_BY);
2999 return;
3001 pline("A %s %s from %s!", tower_of_flame, box ? "bursts" : "erupts",
3002 the(box ? xname(box) : surface(u.ux, u.uy)));
3003 if (Fire_resistance) {
3004 shieldeff(u.ux, u.uy);
3005 num = rn2(2);
3006 } else if (Upolyd) {
3007 num = d(2, 4);
3008 switch (u.umonnum) {
3009 case PM_PAPER_GOLEM:
3010 alt = u.mhmax;
3011 break;
3012 case PM_STRAW_GOLEM:
3013 alt = u.mhmax / 2;
3014 break;
3015 case PM_WOOD_GOLEM:
3016 alt = u.mhmax / 4;
3017 break;
3018 case PM_LEATHER_GOLEM:
3019 alt = u.mhmax / 8;
3020 break;
3021 default:
3022 alt = 0;
3023 break;
3025 if (alt > num)
3026 num = alt;
3027 if (u.mhmax > mons[u.umonnum].mlevel)
3028 u.mhmax -= rn2(min(u.mhmax, num + 1)), context.botl = 1;
3029 } else {
3030 num = d(2, 4);
3031 if (u.uhpmax > u.ulevel)
3032 u.uhpmax -= rn2(min(u.uhpmax, num + 1)), context.botl = 1;
3034 if (!num)
3035 You("are uninjured.");
3036 else
3037 losehp(num, tower_of_flame, KILLED_BY_AN); /* fire damage */
3038 burn_away_slime();
3040 if (burnarmor(&youmonst) || rn2(3)) {
3041 destroy_item(SCROLL_CLASS, AD_FIRE);
3042 destroy_item(SPBOOK_CLASS, AD_FIRE);
3043 destroy_item(POTION_CLASS, AD_FIRE);
3045 if (!box && burn_floor_objects(u.ux, u.uy, see_it, TRUE) && !see_it)
3046 You("smell paper burning.");
3047 if (is_ice(u.ux, u.uy))
3048 melt_ice(u.ux, u.uy, (char *) 0);
3051 STATIC_OVL void
3052 domagictrap()
3054 register int fate = rnd(20);
3056 /* What happened to the poor sucker? */
3058 if (fate < 10) {
3059 /* Most of the time, it creates some monsters. */
3060 register int cnt = rnd(4);
3062 /* blindness effects */
3063 if (!resists_blnd(&youmonst)) {
3064 You("are momentarily blinded by a flash of light!");
3065 make_blinded((long) rn1(5, 10), FALSE);
3066 if (!Blind)
3067 Your1(vision_clears);
3068 } else if (!Blind) {
3069 You_see("a flash of light!");
3072 /* deafness effects */
3073 if (!Deaf) {
3074 You_hear("a deafening roar!");
3075 incr_itimeout(&HDeaf, rn1(20, 30));
3076 context.botl = TRUE;
3077 } else {
3078 /* magic vibrations still hit you */
3079 You_feel("rankled.");
3080 incr_itimeout(&HDeaf, rn1(5, 15));
3081 context.botl = TRUE;
3083 while (cnt--)
3084 (void) makemon((struct permonst *) 0, u.ux, u.uy, NO_MM_FLAGS);
3085 } else
3086 switch (fate) {
3087 case 10:
3088 case 11:
3089 /* sometimes nothing happens */
3090 break;
3091 case 12: /* a flash of fire */
3092 dofiretrap((struct obj *) 0);
3093 break;
3095 /* odd feelings */
3096 case 13:
3097 pline("A shiver runs up and down your %s!", body_part(SPINE));
3098 break;
3099 case 14:
3100 You_hear(Hallucination ? "the moon howling at you."
3101 : "distant howling.");
3102 break;
3103 case 15:
3104 if (on_level(&u.uz, &qstart_level))
3105 You_feel(
3106 "%slike the prodigal son.",
3107 (flags.female || (Upolyd && is_neuter(youmonst.data)))
3108 ? "oddly "
3109 : "");
3110 else
3111 You("suddenly yearn for %s.",
3112 Hallucination
3113 ? "Cleveland"
3114 : (In_quest(&u.uz) || at_dgn_entrance("The Quest"))
3115 ? "your nearby homeland"
3116 : "your distant homeland");
3117 break;
3118 case 16:
3119 Your("pack shakes violently!");
3120 break;
3121 case 17:
3122 You(Hallucination ? "smell hamburgers." : "smell charred flesh.");
3123 break;
3124 case 18:
3125 You_feel("tired.");
3126 break;
3128 /* very occasionally something nice happens. */
3129 case 19: { /* tame nearby monsters */
3130 int i, j;
3131 struct monst *mtmp;
3133 (void) adjattrib(A_CHA, 1, FALSE);
3134 for (i = -1; i <= 1; i++)
3135 for (j = -1; j <= 1; j++) {
3136 if (!isok(u.ux + i, u.uy + j))
3137 continue;
3138 mtmp = m_at(u.ux + i, u.uy + j);
3139 if (mtmp)
3140 (void) tamedog(mtmp, (struct obj *) 0);
3142 break;
3144 case 20: { /* uncurse stuff */
3145 struct obj pseudo;
3146 long save_conf = HConfusion;
3148 pseudo = zeroobj; /* neither cursed nor blessed,
3149 and zero out oextra */
3150 pseudo.otyp = SCR_REMOVE_CURSE;
3151 HConfusion = 0L;
3152 (void) seffects(&pseudo);
3153 HConfusion = save_conf;
3154 break;
3156 default:
3157 break;
3161 /* Set an item on fire.
3162 * "force" means not to roll a luck-based protection check for the
3163 * item.
3164 * "x" and "y" are the coordinates to dump the contents of a
3165 * container, if it burns up.
3167 * Return whether the object was destroyed.
3169 boolean
3170 fire_damage(obj, force, x, y)
3171 struct obj *obj;
3172 boolean force;
3173 xchar x, y;
3175 int chance;
3176 struct obj *otmp, *ncobj;
3177 int in_sight = !Blind && couldsee(x, y); /* Don't care if it's lit */
3178 int dindx;
3180 /* object might light in a controlled manner */
3181 if (catch_lit(obj))
3182 return FALSE;
3184 if (Is_container(obj)) {
3185 switch (obj->otyp) {
3186 case ICE_BOX:
3187 return FALSE; /* Immune */
3188 case CHEST:
3189 chance = 40;
3190 break;
3191 case LARGE_BOX:
3192 chance = 30;
3193 break;
3194 default:
3195 chance = 20;
3196 break;
3198 if ((!force && (Luck + 5) > rn2(chance))
3199 || (is_flammable(obj) && obj->oerodeproof))
3200 return FALSE;
3201 /* Container is burnt up - dump contents out */
3202 if (in_sight)
3203 pline("%s catches fire and burns.", Yname2(obj));
3204 if (Has_contents(obj)) {
3205 if (in_sight)
3206 pline("Its contents fall out.");
3207 for (otmp = obj->cobj; otmp; otmp = ncobj) {
3208 ncobj = otmp->nobj;
3209 obj_extract_self(otmp);
3210 if (!flooreffects(otmp, x, y, ""))
3211 place_object(otmp, x, y);
3214 setnotworn(obj);
3215 delobj(obj);
3216 return TRUE;
3217 } else if (!force && (Luck + 5) > rn2(20)) {
3218 /* chance per item of sustaining damage:
3219 * max luck (Luck==13): 10%
3220 * avg luck (Luck==0): 75%
3221 * awful luck (Luck<-4): 100%
3223 return FALSE;
3224 } else if (obj->oclass == SCROLL_CLASS || obj->oclass == SPBOOK_CLASS) {
3225 if (obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
3226 return FALSE;
3227 if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
3228 if (in_sight)
3229 pline("Smoke rises from %s.", the(xname(obj)));
3230 return FALSE;
3232 dindx = (obj->oclass == SCROLL_CLASS) ? 3 : 4;
3233 if (in_sight)
3234 pline("%s %s.", Yname2(obj),
3235 destroy_strings[dindx][(obj->quan > 1L)]);
3236 setnotworn(obj);
3237 delobj(obj);
3238 return TRUE;
3239 } else if (obj->oclass == POTION_CLASS) {
3240 dindx = (obj->otyp != POT_OIL) ? 1 : 2;
3241 if (in_sight)
3242 pline("%s %s.", Yname2(obj),
3243 destroy_strings[dindx][(obj->quan > 1L)]);
3244 setnotworn(obj);
3245 delobj(obj);
3246 return TRUE;
3247 } else if (erode_obj(obj, (char *) 0, ERODE_BURN, EF_DESTROY)
3248 == ER_DESTROYED) {
3249 return TRUE;
3251 return FALSE;
3255 * Apply fire_damage() to an entire chain.
3257 * Return number of objects destroyed. --ALI
3260 fire_damage_chain(chain, force, here, x, y)
3261 struct obj *chain;
3262 boolean force, here;
3263 xchar x, y;
3265 struct obj *obj, *nobj;
3266 int num = 0;
3267 for (obj = chain; obj; obj = nobj) {
3268 nobj = here ? obj->nexthere : obj->nobj;
3269 if (fire_damage(obj, force, x, y))
3270 ++num;
3273 if (num && (Blind && !couldsee(x, y)))
3274 You("smell smoke.");
3275 return num;
3278 void
3279 acid_damage(obj)
3280 struct obj *obj;
3282 /* Scrolls but not spellbooks can be erased by acid. */
3283 struct monst *victim;
3284 boolean vismon;
3286 if (!obj)
3287 return;
3289 victim = carried(obj) ? &youmonst : mcarried(obj) ? obj->ocarry : NULL;
3290 vismon = victim && (victim != &youmonst) && canseemon(victim);
3292 if (obj->greased) {
3293 grease_protect(obj, (char *) 0, victim);
3294 } else if (obj->oclass == SCROLL_CLASS && obj->otyp != SCR_BLANK_PAPER) {
3295 if (obj->otyp != SCR_BLANK_PAPER
3296 #ifdef MAIL
3297 && obj->otyp != SCR_MAIL
3298 #endif
3300 if (!Blind) {
3301 if (victim == &youmonst)
3302 pline("Your %s.", aobjnam(obj, "fade"));
3303 else if (vismon)
3304 pline("%s %s.", s_suffix(Monnam(victim)),
3305 aobjnam(obj, "fade"));
3308 obj->otyp = SCR_BLANK_PAPER;
3309 obj->spe = 0;
3310 obj->dknown = 0;
3311 } else
3312 erode_obj(obj, (char *) 0, ERODE_CORRODE, EF_GREASE | EF_VERBOSE);
3315 /* context for water_damage(), managed by water_damage_chain();
3316 when more than one stack of potions of acid explode while processing
3317 a chain of objects, use alternate phrasing after the first message */
3318 static struct h2o_ctx {
3319 int dkn_boom, unk_boom; /* track dknown, !dknown separately */
3320 boolean ctx_valid;
3321 } acid_ctx = { 0, 0, FALSE };
3323 /* Get an object wet and damage it appropriately.
3324 * "ostr", if present, is used instead of the object name in some
3325 * messages.
3326 * "force" means not to roll luck to protect some objects.
3327 * Returns an erosion return value (ER_*)
3330 water_damage(obj, ostr, force)
3331 struct obj *obj;
3332 const char *ostr;
3333 boolean force;
3335 if (!obj)
3336 return ER_NOTHING;
3338 if (snuff_lit(obj))
3339 return ER_DAMAGED;
3341 if (!ostr)
3342 ostr = cxname(obj);
3344 if (obj->otyp == CAN_OF_GREASE && obj->spe > 0) {
3345 return ER_NOTHING;
3346 } else if (obj->otyp == TOWEL && obj->spe < 7) {
3347 wet_a_towel(obj, rnd(7), TRUE);
3348 return ER_NOTHING;
3349 } else if (obj->greased) {
3350 if (!rn2(2))
3351 obj->greased = 0;
3352 if (carried(obj))
3353 update_inventory();
3354 return ER_GREASED;
3355 } else if (Is_container(obj) && !Is_box(obj)
3356 && (obj->otyp != OILSKIN_SACK || (obj->cursed && !rn2(3)))) {
3357 if (carried(obj))
3358 pline("Water gets into your %s!", ostr);
3360 water_damage_chain(obj->cobj, FALSE);
3361 return ER_NOTHING;
3362 } else if (!force && (Luck + 5) > rn2(20)) {
3363 /* chance per item of sustaining damage:
3364 * max luck: 10%
3365 * avg luck (Luck==0): 75%
3366 * awful luck (Luck<-4): 100%
3368 return ER_NOTHING;
3369 } else if (obj->oclass == SCROLL_CLASS) {
3370 if (obj->otyp == SCR_BLANK_PAPER
3371 #ifdef MAIL
3372 || obj->otyp == SCR_MAIL
3373 #endif
3374 ) return 0;
3375 if (carried(obj))
3376 pline("Your %s %s.", ostr, vtense(ostr, "fade"));
3378 obj->otyp = SCR_BLANK_PAPER;
3379 obj->dknown = 0;
3380 obj->spe = 0;
3381 if (carried(obj))
3382 update_inventory();
3383 return ER_DAMAGED;
3384 } else if (obj->oclass == SPBOOK_CLASS) {
3385 if (obj->otyp == SPE_BOOK_OF_THE_DEAD) {
3386 pline("Steam rises from %s.", the(xname(obj)));
3387 return 0;
3388 } else if (obj->otyp == SPE_BLANK_PAPER) {
3389 return 0;
3391 if (carried(obj))
3392 pline("Your %s %s.", ostr, vtense(ostr, "fade"));
3394 if (obj->otyp == SPE_NOVEL) {
3395 obj->novelidx = 0;
3396 free_oname(obj);
3399 obj->otyp = SPE_BLANK_PAPER;
3400 obj->dknown = 0;
3401 if (carried(obj))
3402 update_inventory();
3403 return ER_DAMAGED;
3404 } else if (obj->oclass == POTION_CLASS) {
3405 if (obj->otyp == POT_ACID) {
3406 char *bufp;
3407 boolean one = (obj->quan == 1L), update = carried(obj),
3408 exploded = FALSE;
3410 if (Blind && !carried(obj))
3411 obj->dknown = 0;
3412 if (acid_ctx.ctx_valid)
3413 exploded = ((obj->dknown ? acid_ctx.dkn_boom
3414 : acid_ctx.unk_boom) > 0);
3415 /* First message is
3416 * "a [potion|<color> potion|potion of acid] explodes"
3417 * depending on obj->dknown (potion has been seen) and
3418 * objects[POT_ACID].oc_name_known (fully discovered),
3419 * or "some {plural version} explode" when relevant.
3420 * Second and subsequent messages for same chain and
3421 * matching dknown status are
3422 * "another [potion|<color> &c] explodes" or plural
3423 * variant.
3425 bufp = simpleonames(obj);
3426 pline("%s %s %s!", /* "A potion explodes!" */
3427 !exploded ? (one ? "A" : "Some")
3428 : (one ? "Another" : "More"),
3429 bufp, vtense(bufp, "explode"));
3430 if (acid_ctx.ctx_valid) {
3431 if (obj->dknown)
3432 acid_ctx.dkn_boom++;
3433 else
3434 acid_ctx.unk_boom++;
3436 setnotworn(obj);
3437 delobj(obj);
3438 if (update)
3439 update_inventory();
3440 return ER_DESTROYED;
3441 } else if (obj->odiluted) {
3442 if (carried(obj))
3443 pline("Your %s %s further.", ostr, vtense(ostr, "dilute"));
3445 obj->otyp = POT_WATER;
3446 obj->dknown = 0;
3447 obj->blessed = obj->cursed = 0;
3448 obj->odiluted = 0;
3449 if (carried(obj))
3450 update_inventory();
3451 return ER_DAMAGED;
3452 } else if (obj->otyp != POT_WATER) {
3453 if (carried(obj))
3454 pline("Your %s %s.", ostr, vtense(ostr, "dilute"));
3456 obj->odiluted++;
3457 if (carried(obj))
3458 update_inventory();
3459 return ER_DAMAGED;
3461 } else {
3462 return erode_obj(obj, ostr, ERODE_RUST, EF_NONE);
3464 return ER_NOTHING;
3467 void
3468 water_damage_chain(obj, here)
3469 struct obj *obj;
3470 boolean here;
3472 struct obj *otmp;
3474 /* initialize acid context: so far, neither seen (dknown) potions of
3475 acid nor unseen have exploded during this water damage sequence */
3476 acid_ctx.dkn_boom = acid_ctx.unk_boom = 0;
3477 acid_ctx.ctx_valid = TRUE;
3479 for (; obj; obj = otmp) {
3480 otmp = here ? obj->nexthere : obj->nobj;
3481 water_damage(obj, (char *) 0, FALSE);
3484 /* reset acid context */
3485 acid_ctx.dkn_boom = acid_ctx.unk_boom = 0;
3486 acid_ctx.ctx_valid = FALSE;
3490 * This function is potentially expensive - rolling
3491 * inventory list multiple times. Luckily it's seldom needed.
3492 * Returns TRUE if disrobing made player unencumbered enough to
3493 * crawl out of the current predicament.
3495 STATIC_OVL boolean
3496 emergency_disrobe(lostsome)
3497 boolean *lostsome;
3499 int invc = inv_cnt(TRUE);
3501 while (near_capacity() > (Punished ? UNENCUMBERED : SLT_ENCUMBER)) {
3502 register struct obj *obj, *otmp = (struct obj *) 0;
3503 register int i;
3505 /* Pick a random object */
3506 if (invc > 0) {
3507 i = rn2(invc);
3508 for (obj = invent; obj; obj = obj->nobj) {
3510 * Undroppables are: body armor, boots, gloves,
3511 * amulets, and rings because of the time and effort
3512 * in removing them + loadstone and other cursed stuff
3513 * for obvious reasons.
3515 if (!((obj->otyp == LOADSTONE && obj->cursed) || obj == uamul
3516 || obj == uleft || obj == uright || obj == ublindf
3517 || obj == uarm || obj == uarmc || obj == uarmg
3518 || obj == uarmf || obj == uarmu
3519 || (obj->cursed && (obj == uarmh || obj == uarms))
3520 || welded(obj)))
3521 otmp = obj;
3522 /* reached the mark and found some stuff to drop? */
3523 if (--i < 0 && otmp)
3524 break;
3526 /* else continue */
3529 if (!otmp)
3530 return FALSE; /* nothing to drop! */
3531 if (otmp->owornmask)
3532 remove_worn_item(otmp, FALSE);
3533 *lostsome = TRUE;
3534 dropx(otmp);
3535 invc--;
3537 return TRUE;
3541 /* return TRUE iff player relocated */
3542 boolean
3543 drown()
3545 const char *pool_of_water;
3546 boolean inpool_ok = FALSE, crawl_ok;
3547 int i, x, y;
3549 /* happily wading in the same contiguous pool */
3550 if (u.uinwater && is_pool(u.ux - u.dx, u.uy - u.dy)
3551 && (Swimming || Amphibious)) {
3552 /* water effects on objects every now and then */
3553 if (!rn2(5))
3554 inpool_ok = TRUE;
3555 else
3556 return FALSE;
3559 if (!u.uinwater) {
3560 You("%s into the water%c", Is_waterlevel(&u.uz) ? "plunge" : "fall",
3561 Amphibious || Swimming ? '.' : '!');
3562 if (!Swimming && !Is_waterlevel(&u.uz))
3563 You("sink like %s.", Hallucination ? "the Titanic" : "a rock");
3566 water_damage_chain(invent, FALSE);
3568 if (u.umonnum == PM_GREMLIN && rn2(3))
3569 (void) split_mon(&youmonst, (struct monst *) 0);
3570 else if (u.umonnum == PM_IRON_GOLEM) {
3571 You("rust!");
3572 i = Maybe_Half_Phys(d(2, 6));
3573 if (u.mhmax > i)
3574 u.mhmax -= i;
3575 losehp(i, "rusting away", KILLED_BY);
3577 if (inpool_ok)
3578 return FALSE;
3580 if ((i = number_leashed()) > 0) {
3581 pline_The("leash%s slip%s loose.", (i > 1) ? "es" : "",
3582 (i > 1) ? "" : "s");
3583 unleash_all();
3586 if (Amphibious || Swimming) {
3587 if (Amphibious) {
3588 if (flags.verbose)
3589 pline("But you aren't drowning.");
3590 if (!Is_waterlevel(&u.uz)) {
3591 if (Hallucination)
3592 Your("keel hits the bottom.");
3593 else
3594 You("touch bottom.");
3597 if (Punished) {
3598 unplacebc();
3599 placebc();
3601 vision_recalc(2); /* unsee old position */
3602 u.uinwater = 1;
3603 under_water(1);
3604 vision_full_recalc = 1;
3605 return FALSE;
3607 if ((Teleportation || can_teleport(youmonst.data)) && !Unaware
3608 && (Teleport_control || rn2(3) < Luck + 2)) {
3609 You("attempt a teleport spell."); /* utcsri!carroll */
3610 if (!level.flags.noteleport) {
3611 (void) dotele();
3612 if (!is_pool(u.ux, u.uy))
3613 return TRUE;
3614 } else
3615 pline_The("attempted teleport spell fails.");
3617 if (u.usteed) {
3618 dismount_steed(DISMOUNT_GENERIC);
3619 if (!is_pool(u.ux, u.uy))
3620 return TRUE;
3622 crawl_ok = FALSE;
3623 x = y = 0; /* lint suppression */
3624 /* if sleeping, wake up now so that we don't crawl out of water
3625 while still asleep; we can't do that the same way that waking
3626 due to combat is handled; note unmul() clears u.usleep */
3627 if (u.usleep)
3628 unmul("Suddenly you wake up!");
3629 /* being doused will revive from fainting */
3630 if (is_fainted())
3631 reset_faint();
3632 /* can't crawl if unable to move (crawl_ok flag stays false) */
3633 if (multi < 0 || (Upolyd && !youmonst.data->mmove))
3634 goto crawl;
3635 /* look around for a place to crawl to */
3636 for (i = 0; i < 100; i++) {
3637 x = rn1(3, u.ux - 1);
3638 y = rn1(3, u.uy - 1);
3639 if (crawl_destination(x, y)) {
3640 crawl_ok = TRUE;
3641 goto crawl;
3644 /* one more scan */
3645 for (x = u.ux - 1; x <= u.ux + 1; x++)
3646 for (y = u.uy - 1; y <= u.uy + 1; y++)
3647 if (crawl_destination(x, y)) {
3648 crawl_ok = TRUE;
3649 goto crawl;
3651 crawl:
3652 if (crawl_ok) {
3653 boolean lost = FALSE;
3654 /* time to do some strip-tease... */
3655 boolean succ = Is_waterlevel(&u.uz) ? TRUE : emergency_disrobe(&lost);
3657 You("try to crawl out of the water.");
3658 if (lost)
3659 You("dump some of your gear to lose weight...");
3660 if (succ) {
3661 pline("Pheew! That was close.");
3662 teleds(x, y, TRUE);
3663 return TRUE;
3665 /* still too much weight */
3666 pline("But in vain.");
3668 u.uinwater = 1;
3669 You("drown.");
3670 for (i = 0; i < 5; i++) { /* arbitrary number of loops */
3671 /* killer format and name are reconstructed every iteration
3672 because lifesaving resets them */
3673 pool_of_water = waterbody_name(u.ux, u.uy);
3674 killer.format = KILLED_BY_AN;
3675 /* avoid "drowned in [a] water" */
3676 if (!strcmp(pool_of_water, "water"))
3677 pool_of_water = "deep water", killer.format = KILLED_BY;
3678 Strcpy(killer.name, pool_of_water);
3679 done(DROWNING);
3680 /* oops, we're still alive. better get out of the water. */
3681 if (safe_teleds(TRUE))
3682 break; /* successful life-save */
3683 /* nowhere safe to land; repeat drowning loop... */
3684 pline("You're still drowning.");
3686 if (u.uinwater) {
3687 u.uinwater = 0;
3688 You("find yourself back %s.",
3689 Is_waterlevel(&u.uz) ? "in an air bubble" : "on land");
3691 return TRUE;
3694 void
3695 drain_en(n)
3696 int n;
3698 if (!u.uenmax) {
3699 /* energy is completely gone */
3700 You_feel("momentarily lethargic.");
3701 } else {
3702 /* throttle further loss a bit when there's not much left to lose */
3703 if (n > u.uenmax || n > u.ulevel)
3704 n = rnd(n);
3706 You_feel("your magical energy drain away%c", (n > u.uen) ? '!' : '.');
3707 u.uen -= n;
3708 if (u.uen < 0) {
3709 u.uenmax -= rnd(-u.uen);
3710 if (u.uenmax < 0)
3711 u.uenmax = 0;
3712 u.uen = 0;
3714 context.botl = 1;
3718 /* disarm a trap */
3720 dountrap()
3722 if (near_capacity() >= HVY_ENCUMBER) {
3723 pline("You're too strained to do that.");
3724 return 0;
3726 if ((nohands(youmonst.data) && !webmaker(youmonst.data))
3727 || !youmonst.data->mmove) {
3728 pline("And just how do you expect to do that?");
3729 return 0;
3730 } else if (u.ustuck && sticks(youmonst.data)) {
3731 pline("You'll have to let go of %s first.", mon_nam(u.ustuck));
3732 return 0;
3734 if (u.ustuck || (welded(uwep) && bimanual(uwep))) {
3735 Your("%s seem to be too busy for that.", makeplural(body_part(HAND)));
3736 return 0;
3738 return untrap(FALSE);
3741 /* Probability of disabling a trap. Helge Hafting */
3742 STATIC_OVL int
3743 untrap_prob(ttmp)
3744 struct trap *ttmp;
3746 int chance = 3;
3748 /* Only spiders know how to deal with webs reliably */
3749 if (ttmp->ttyp == WEB && !webmaker(youmonst.data))
3750 chance = 30;
3751 if (Confusion || Hallucination)
3752 chance++;
3753 if (Blind)
3754 chance++;
3755 if (Stunned)
3756 chance += 2;
3757 if (Fumbling)
3758 chance *= 2;
3759 /* Your own traps are better known than others. */
3760 if (ttmp && ttmp->madeby_u)
3761 chance--;
3762 if (Role_if(PM_ROGUE)) {
3763 if (rn2(2 * MAXULEV) < u.ulevel)
3764 chance--;
3765 if (u.uhave.questart && chance > 1)
3766 chance--;
3767 } else if (Role_if(PM_RANGER) && chance > 1)
3768 chance--;
3769 return rn2(chance);
3772 /* Replace trap with object(s). Helge Hafting */
3773 void
3774 cnv_trap_obj(otyp, cnt, ttmp, bury_it)
3775 int otyp;
3776 int cnt;
3777 struct trap *ttmp;
3778 boolean bury_it;
3780 struct obj *otmp = mksobj(otyp, TRUE, FALSE);
3782 otmp->quan = cnt;
3783 otmp->owt = weight(otmp);
3784 /* Only dart traps are capable of being poisonous */
3785 if (otyp != DART)
3786 otmp->opoisoned = 0;
3787 place_object(otmp, ttmp->tx, ttmp->ty);
3788 if (bury_it) {
3789 /* magical digging first disarms this trap, then will unearth it */
3790 (void) bury_an_obj(otmp, (boolean *) 0);
3791 } else {
3792 /* Sell your own traps only... */
3793 if (ttmp->madeby_u)
3794 sellobj(otmp, ttmp->tx, ttmp->ty);
3795 stackobj(otmp);
3797 newsym(ttmp->tx, ttmp->ty);
3798 if (u.utrap && ttmp->tx == u.ux && ttmp->ty == u.uy)
3799 u.utrap = 0;
3800 deltrap(ttmp);
3803 /* while attempting to disarm an adjacent trap, we've fallen into it */
3804 STATIC_OVL void
3805 move_into_trap(ttmp)
3806 struct trap *ttmp;
3808 int bc = 0;
3809 xchar x = ttmp->tx, y = ttmp->ty, bx, by, cx, cy;
3810 boolean unused;
3812 bx = by = cx = cy = 0; /* lint suppression */
3813 /* we know there's no monster in the way, and we're not trapped */
3814 if (!Punished
3815 || drag_ball(x, y, &bc, &bx, &by, &cx, &cy, &unused, TRUE)) {
3816 u.ux0 = u.ux, u.uy0 = u.uy;
3817 u.ux = x, u.uy = y;
3818 u.umoved = TRUE;
3819 newsym(u.ux0, u.uy0);
3820 vision_recalc(1);
3821 check_leash(u.ux0, u.uy0);
3822 if (Punished)
3823 move_bc(0, bc, bx, by, cx, cy);
3824 /* marking the trap unseen forces dotrap() to treat it like a new
3825 discovery and prevents pickup() -> look_here() -> check_here()
3826 from giving a redundant "there is a <trap> here" message when
3827 there are objects covering this trap */
3828 ttmp->tseen = 0; /* hack for check_here() */
3829 /* trigger the trap */
3830 spoteffects(TRUE); /* pickup() + dotrap() */
3831 exercise(A_WIS, FALSE);
3835 /* 0: doesn't even try
3836 * 1: tries and fails
3837 * 2: succeeds
3839 STATIC_OVL int
3840 try_disarm(ttmp, force_failure)
3841 struct trap *ttmp;
3842 boolean force_failure;
3844 struct monst *mtmp = m_at(ttmp->tx, ttmp->ty);
3845 int ttype = ttmp->ttyp;
3846 boolean under_u = (!u.dx && !u.dy);
3847 boolean holdingtrap = (ttype == BEAR_TRAP || ttype == WEB);
3849 /* Test for monster first, monsters are displayed instead of trap. */
3850 if (mtmp && (!mtmp->mtrapped || !holdingtrap)) {
3851 pline("%s is in the way.", Monnam(mtmp));
3852 return 0;
3854 /* We might be forced to move onto the trap's location. */
3855 if (sobj_at(BOULDER, ttmp->tx, ttmp->ty) && !Passes_walls && !under_u) {
3856 There("is a boulder in your way.");
3857 return 0;
3859 /* duplicate tight-space checks from test_move */
3860 if (u.dx && u.dy && bad_rock(youmonst.data, u.ux, ttmp->ty)
3861 && bad_rock(youmonst.data, ttmp->tx, u.uy)) {
3862 if ((invent && (inv_weight() + weight_cap() > 600))
3863 || bigmonst(youmonst.data)) {
3864 /* don't allow untrap if they can't get thru to it */
3865 You("are unable to reach the %s!",
3866 defsyms[trap_to_defsym(ttype)].explanation);
3867 return 0;
3870 /* untrappable traps are located on the ground. */
3871 if (!can_reach_floor(TRUE)) {
3872 if (u.usteed && P_SKILL(P_RIDING) < P_BASIC)
3873 rider_cant_reach();
3874 else
3875 You("are unable to reach the %s!",
3876 defsyms[trap_to_defsym(ttype)].explanation);
3877 return 0;
3880 /* Will our hero succeed? */
3881 if (force_failure || untrap_prob(ttmp)) {
3882 if (rnl(5)) {
3883 pline("Whoops...");
3884 if (mtmp) { /* must be a trap that holds monsters */
3885 if (ttype == BEAR_TRAP) {
3886 if (mtmp->mtame)
3887 abuse_dog(mtmp);
3888 if ((mtmp->mhp -= rnd(4)) <= 0)
3889 killed(mtmp);
3890 } else if (ttype == WEB) {
3891 if (!webmaker(youmonst.data)) {
3892 struct trap *ttmp2 = maketrap(u.ux, u.uy, WEB);
3894 if (ttmp2) {
3895 pline_The(
3896 "webbing sticks to you. You're caught too!");
3897 dotrap(ttmp2, NOWEBMSG);
3898 if (u.usteed && u.utrap) {
3899 /* you, not steed, are trapped */
3900 dismount_steed(DISMOUNT_FELL);
3903 } else
3904 pline("%s remains entangled.", Monnam(mtmp));
3906 } else if (under_u) {
3907 dotrap(ttmp, 0);
3908 } else {
3909 move_into_trap(ttmp);
3911 } else {
3912 pline("%s %s is difficult to %s.",
3913 ttmp->madeby_u ? "Your" : under_u ? "This" : "That",
3914 defsyms[trap_to_defsym(ttype)].explanation,
3915 (ttype == WEB) ? "remove" : "disarm");
3917 return 1;
3919 return 2;
3922 STATIC_OVL void
3923 reward_untrap(ttmp, mtmp)
3924 struct trap *ttmp;
3925 struct monst *mtmp;
3927 if (!ttmp->madeby_u) {
3928 if (rnl(10) < 8 && !mtmp->mpeaceful && !mtmp->msleeping
3929 && !mtmp->mfrozen && !mindless(mtmp->data)
3930 && mtmp->data->mlet != S_HUMAN) {
3931 mtmp->mpeaceful = 1;
3932 set_malign(mtmp); /* reset alignment */
3933 pline("%s is grateful.", Monnam(mtmp));
3935 /* Helping someone out of a trap is a nice thing to do,
3936 * A lawful may be rewarded, but not too often. */
3937 if (!rn2(3) && !rnl(8) && u.ualign.type == A_LAWFUL) {
3938 adjalign(1);
3939 You_feel("that you did the right thing.");
3944 STATIC_OVL int
3945 disarm_holdingtrap(ttmp) /* Helge Hafting */
3946 struct trap *ttmp;
3948 struct monst *mtmp;
3949 int fails = try_disarm(ttmp, FALSE);
3951 if (fails < 2)
3952 return fails;
3954 /* ok, disarm it. */
3956 /* untrap the monster, if any.
3957 There's no need for a cockatrice test, only the trap is touched */
3958 if ((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) {
3959 mtmp->mtrapped = 0;
3960 You("remove %s %s from %s.", the_your[ttmp->madeby_u],
3961 (ttmp->ttyp == BEAR_TRAP) ? "bear trap" : "webbing",
3962 mon_nam(mtmp));
3963 reward_untrap(ttmp, mtmp);
3964 } else {
3965 if (ttmp->ttyp == BEAR_TRAP) {
3966 You("disarm %s bear trap.", the_your[ttmp->madeby_u]);
3967 cnv_trap_obj(BEARTRAP, 1, ttmp, FALSE);
3968 } else /* if (ttmp->ttyp == WEB) */ {
3969 You("succeed in removing %s web.", the_your[ttmp->madeby_u]);
3970 deltrap(ttmp);
3973 newsym(u.ux + u.dx, u.uy + u.dy);
3974 return 1;
3977 STATIC_OVL int
3978 disarm_landmine(ttmp) /* Helge Hafting */
3979 struct trap *ttmp;
3981 int fails = try_disarm(ttmp, FALSE);
3983 if (fails < 2)
3984 return fails;
3985 You("disarm %s land mine.", the_your[ttmp->madeby_u]);
3986 cnv_trap_obj(LAND_MINE, 1, ttmp, FALSE);
3987 return 1;
3990 /* getobj will filter down to cans of grease and known potions of oil */
3991 static NEARDATA const char oil[] = { ALL_CLASSES, TOOL_CLASS, POTION_CLASS,
3992 0 };
3994 /* it may not make much sense to use grease on floor boards, but so what? */
3995 STATIC_OVL int
3996 disarm_squeaky_board(ttmp)
3997 struct trap *ttmp;
3999 struct obj *obj;
4000 boolean bad_tool;
4001 int fails;
4003 obj = getobj(oil, "untrap with");
4004 if (!obj)
4005 return 0;
4007 bad_tool = (obj->cursed
4008 || ((obj->otyp != POT_OIL || obj->lamplit)
4009 && (obj->otyp != CAN_OF_GREASE || !obj->spe)));
4010 fails = try_disarm(ttmp, bad_tool);
4011 if (fails < 2)
4012 return fails;
4014 /* successfully used oil or grease to fix squeaky board */
4015 if (obj->otyp == CAN_OF_GREASE) {
4016 consume_obj_charge(obj, TRUE);
4017 } else {
4018 useup(obj); /* oil */
4019 makeknown(POT_OIL);
4021 You("repair the squeaky board."); /* no madeby_u */
4022 deltrap(ttmp);
4023 newsym(u.ux + u.dx, u.uy + u.dy);
4024 more_experienced(1, 5);
4025 newexplevel();
4026 return 1;
4029 /* removes traps that shoot arrows, darts, etc. */
4030 STATIC_OVL int
4031 disarm_shooting_trap(ttmp, otyp)
4032 struct trap *ttmp;
4033 int otyp;
4035 int fails = try_disarm(ttmp, FALSE);
4037 if (fails < 2)
4038 return fails;
4039 You("disarm %s trap.", the_your[ttmp->madeby_u]);
4040 cnv_trap_obj(otyp, 50 - rnl(50), ttmp, FALSE);
4041 return 1;
4044 /* Is the weight too heavy?
4045 * Formula as in near_capacity() & check_capacity() */
4046 STATIC_OVL int
4047 try_lift(mtmp, ttmp, wt, stuff)
4048 struct monst *mtmp;
4049 struct trap *ttmp;
4050 int wt;
4051 boolean stuff;
4053 int wc = weight_cap();
4055 if (((wt * 2) / wc) >= HVY_ENCUMBER) {
4056 pline("%s is %s for you to lift.", Monnam(mtmp),
4057 stuff ? "carrying too much" : "too heavy");
4058 if (!ttmp->madeby_u && !mtmp->mpeaceful && mtmp->mcanmove
4059 && !mindless(mtmp->data) && mtmp->data->mlet != S_HUMAN
4060 && rnl(10) < 3) {
4061 mtmp->mpeaceful = 1;
4062 set_malign(mtmp); /* reset alignment */
4063 pline("%s thinks it was nice of you to try.", Monnam(mtmp));
4065 return 0;
4067 return 1;
4070 /* Help trapped monster (out of a (spiked) pit) */
4071 STATIC_OVL int
4072 help_monster_out(mtmp, ttmp)
4073 struct monst *mtmp;
4074 struct trap *ttmp;
4076 int wt;
4077 struct obj *otmp;
4078 boolean uprob;
4081 * This works when levitating too -- consistent with the ability
4082 * to hit monsters while levitating.
4084 * Should perhaps check that our hero has arms/hands at the
4085 * moment. Helping can also be done by engulfing...
4087 * Test the monster first - monsters are displayed before traps.
4089 if (!mtmp->mtrapped) {
4090 pline("%s isn't trapped.", Monnam(mtmp));
4091 return 0;
4093 /* Do you have the necessary capacity to lift anything? */
4094 if (check_capacity((char *) 0))
4095 return 1;
4097 /* Will our hero succeed? */
4098 if ((uprob = untrap_prob(ttmp)) && !mtmp->msleeping && mtmp->mcanmove) {
4099 You("try to reach out your %s, but %s backs away skeptically.",
4100 makeplural(body_part(ARM)), mon_nam(mtmp));
4101 return 1;
4104 /* is it a cockatrice?... */
4105 if (touch_petrifies(mtmp->data) && !uarmg && !Stone_resistance) {
4106 You("grab the trapped %s using your bare %s.", mtmp->data->mname,
4107 makeplural(body_part(HAND)));
4109 if (poly_when_stoned(youmonst.data) && polymon(PM_STONE_GOLEM)) {
4110 display_nhwindow(WIN_MESSAGE, FALSE);
4111 } else {
4112 char kbuf[BUFSZ];
4114 Sprintf(kbuf, "trying to help %s out of a pit",
4115 an(mtmp->data->mname));
4116 instapetrify(kbuf);
4117 return 1;
4120 /* need to do cockatrice check first if sleeping or paralyzed */
4121 if (uprob) {
4122 You("try to grab %s, but cannot get a firm grasp.", mon_nam(mtmp));
4123 if (mtmp->msleeping) {
4124 mtmp->msleeping = 0;
4125 pline("%s awakens.", Monnam(mtmp));
4127 return 1;
4130 You("reach out your %s and grab %s.", makeplural(body_part(ARM)),
4131 mon_nam(mtmp));
4133 if (mtmp->msleeping) {
4134 mtmp->msleeping = 0;
4135 pline("%s awakens.", Monnam(mtmp));
4136 } else if (mtmp->mfrozen && !rn2(mtmp->mfrozen)) {
4137 /* After such manhandling, perhaps the effect wears off */
4138 mtmp->mcanmove = 1;
4139 mtmp->mfrozen = 0;
4140 pline("%s stirs.", Monnam(mtmp));
4143 /* is the monster too heavy? */
4144 wt = inv_weight() + mtmp->data->cwt;
4145 if (!try_lift(mtmp, ttmp, wt, FALSE))
4146 return 1;
4148 /* is the monster with inventory too heavy? */
4149 for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj)
4150 wt += otmp->owt;
4151 if (!try_lift(mtmp, ttmp, wt, TRUE))
4152 return 1;
4154 You("pull %s out of the pit.", mon_nam(mtmp));
4155 mtmp->mtrapped = 0;
4156 fill_pit(mtmp->mx, mtmp->my);
4157 reward_untrap(ttmp, mtmp);
4158 return 1;
4162 untrap(force)
4163 boolean force;
4165 register struct obj *otmp;
4166 register int x, y;
4167 int ch;
4168 struct trap *ttmp;
4169 struct monst *mtmp;
4170 const char *trapdescr;
4171 boolean here, useplural, confused = (Confusion || Hallucination),
4172 trap_skipped = FALSE, deal_with_floor_trap;
4173 int boxcnt = 0;
4174 char the_trap[BUFSZ], qbuf[QBUFSZ];
4176 if (!getdir((char *) 0))
4177 return 0;
4178 x = u.ux + u.dx;
4179 y = u.uy + u.dy;
4180 if (!isok(x, y)) {
4181 pline_The("perils lurking there are beyond your grasp.");
4182 return 0;
4184 ttmp = t_at(x, y);
4185 if (ttmp && !ttmp->tseen)
4186 ttmp = 0;
4187 trapdescr = ttmp ? defsyms[trap_to_defsym(ttmp->ttyp)].explanation : 0;
4188 here = (x == u.ux && y == u.uy); /* !u.dx && !u.dy */
4190 if (here) /* are there are one or more containers here? */
4191 for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere)
4192 if (Is_box(otmp)) {
4193 if (++boxcnt > 1)
4194 break;
4197 deal_with_floor_trap = can_reach_floor(FALSE);
4198 if (!deal_with_floor_trap) {
4199 *the_trap = '\0';
4200 if (ttmp)
4201 Strcat(the_trap, an(trapdescr));
4202 if (ttmp && boxcnt)
4203 Strcat(the_trap, " and ");
4204 if (boxcnt)
4205 Strcat(the_trap, (boxcnt == 1) ? "a container" : "containers");
4206 useplural = ((ttmp && boxcnt > 0) || boxcnt > 1);
4207 /* note: boxcnt and useplural will always be 0 for !here case */
4208 if (ttmp || boxcnt)
4209 There("%s %s %s but you can't reach %s%s.",
4210 useplural ? "are" : "is", the_trap, here ? "here" : "there",
4211 useplural ? "them" : "it",
4212 u.usteed ? " while mounted" : "");
4213 trap_skipped = (ttmp != 0);
4214 } else { /* deal_with_floor_trap */
4216 if (ttmp) {
4217 Strcpy(the_trap, the(trapdescr));
4218 if (boxcnt) {
4219 if (ttmp->ttyp == PIT || ttmp->ttyp == SPIKED_PIT) {
4220 You_cant("do much about %s%s.", the_trap,
4221 u.utrap ? " that you're stuck in"
4222 : " while standing on the edge of it");
4223 trap_skipped = TRUE;
4224 deal_with_floor_trap = FALSE;
4225 } else {
4226 Sprintf(
4227 qbuf, "There %s and %s here. %s %s?",
4228 (boxcnt == 1) ? "is a container" : "are containers",
4229 an(trapdescr),
4230 (ttmp->ttyp == WEB) ? "Remove" : "Disarm", the_trap);
4231 switch (ynq(qbuf)) {
4232 case 'q':
4233 return 0;
4234 case 'n':
4235 trap_skipped = TRUE;
4236 deal_with_floor_trap = FALSE;
4237 break;
4241 if (deal_with_floor_trap) {
4242 if (u.utrap) {
4243 You("cannot deal with %s while trapped%s!", the_trap,
4244 (x == u.ux && y == u.uy) ? " in it" : "");
4245 return 1;
4247 if ((mtmp = m_at(x, y)) != 0
4248 && (mtmp->m_ap_type == M_AP_FURNITURE
4249 || mtmp->m_ap_type == M_AP_OBJECT)) {
4250 stumble_onto_mimic(mtmp);
4251 return 1;
4253 switch (ttmp->ttyp) {
4254 case BEAR_TRAP:
4255 case WEB:
4256 return disarm_holdingtrap(ttmp);
4257 case LANDMINE:
4258 return disarm_landmine(ttmp);
4259 case SQKY_BOARD:
4260 return disarm_squeaky_board(ttmp);
4261 case DART_TRAP:
4262 return disarm_shooting_trap(ttmp, DART);
4263 case ARROW_TRAP:
4264 return disarm_shooting_trap(ttmp, ARROW);
4265 case PIT:
4266 case SPIKED_PIT:
4267 if (here) {
4268 You("are already on the edge of the pit.");
4269 return 0;
4271 if (!mtmp) {
4272 pline("Try filling the pit instead.");
4273 return 0;
4275 return help_monster_out(mtmp, ttmp);
4276 default:
4277 You("cannot disable %s trap.", !here ? "that" : "this");
4278 return 0;
4281 } /* end if */
4283 if (boxcnt) {
4284 for (otmp = level.objects[x][y]; otmp; otmp = otmp->nexthere)
4285 if (Is_box(otmp)) {
4286 (void) safe_qbuf(qbuf, "There is ",
4287 " here. Check it for traps?", otmp,
4288 doname, ansimpleoname, "a box");
4289 switch (ynq(qbuf)) {
4290 case 'q':
4291 return 0;
4292 case 'n':
4293 continue;
4296 if ((otmp->otrapped
4297 && (force || (!confused
4298 && rn2(MAXULEV + 1 - u.ulevel) < 10)))
4299 || (!force && confused && !rn2(3))) {
4300 You("find a trap on %s!", the(xname(otmp)));
4301 if (!confused)
4302 exercise(A_WIS, TRUE);
4304 switch (ynq("Disarm it?")) {
4305 case 'q':
4306 return 1;
4307 case 'n':
4308 trap_skipped = TRUE;
4309 continue;
4312 if (otmp->otrapped) {
4313 exercise(A_DEX, TRUE);
4314 ch = ACURR(A_DEX) + u.ulevel;
4315 if (Role_if(PM_ROGUE))
4316 ch *= 2;
4317 if (!force && (confused || Fumbling
4318 || rnd(75 + level_difficulty() / 2)
4319 > ch)) {
4320 (void) chest_trap(otmp, FINGER, TRUE);
4321 } else {
4322 You("disarm it!");
4323 otmp->otrapped = 0;
4325 } else
4326 pline("That %s was not trapped.", xname(otmp));
4327 return 1;
4328 } else {
4329 You("find no traps on %s.", the(xname(otmp)));
4330 return 1;
4334 You(trap_skipped ? "find no other traps here."
4335 : "know of no traps here.");
4336 return 0;
4339 if (stumble_on_door_mimic(x, y))
4340 return 1;
4342 } /* deal_with_floor_trap */
4343 /* doors can be manipulated even while levitating/unskilled riding */
4345 if (!IS_DOOR(levl[x][y].typ)) {
4346 if (!trap_skipped)
4347 You("know of no traps there.");
4348 return 0;
4351 switch (levl[x][y].doormask) {
4352 case D_NODOOR:
4353 You("%s no door there.", Blind ? "feel" : "see");
4354 return 0;
4355 case D_ISOPEN:
4356 pline("This door is safely open.");
4357 return 0;
4358 case D_BROKEN:
4359 pline("This door is broken.");
4360 return 0;
4363 if ((levl[x][y].doormask & D_TRAPPED
4364 && (force || (!confused && rn2(MAXULEV - u.ulevel + 11) < 10)))
4365 || (!force && confused && !rn2(3))) {
4366 You("find a trap on the door!");
4367 exercise(A_WIS, TRUE);
4368 if (ynq("Disarm it?") != 'y')
4369 return 1;
4370 if (levl[x][y].doormask & D_TRAPPED) {
4371 ch = 15 + (Role_if(PM_ROGUE) ? u.ulevel * 3 : u.ulevel);
4372 exercise(A_DEX, TRUE);
4373 if (!force && (confused || Fumbling
4374 || rnd(75 + level_difficulty() / 2) > ch)) {
4375 You("set it off!");
4376 b_trapped("door", FINGER);
4377 levl[x][y].doormask = D_NODOOR;
4378 unblock_point(x, y);
4379 newsym(x, y);
4380 /* (probably ought to charge for this damage...) */
4381 if (*in_rooms(x, y, SHOPBASE))
4382 add_damage(x, y, 0L);
4383 } else {
4384 You("disarm it!");
4385 levl[x][y].doormask &= ~D_TRAPPED;
4387 } else
4388 pline("This door was not trapped.");
4389 return 1;
4390 } else {
4391 You("find no traps on the door.");
4392 return 1;
4396 /* for magic unlocking; returns true if targetted monster (which might
4397 be hero) gets untrapped; the trap remains intact */
4398 boolean
4399 openholdingtrap(mon, noticed)
4400 struct monst *mon;
4401 boolean *noticed; /* set to true iff hero notices the effect; */
4402 { /* otherwise left with its previous value intact */
4403 struct trap *t;
4404 char buf[BUFSZ];
4405 const char *trapdescr, *which;
4406 boolean ishero = (mon == &youmonst);
4408 if (mon == u.usteed)
4409 ishero = TRUE;
4410 t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my);
4411 /* if no trap here or it's not a holding trap, we're done */
4412 if (!t || (t->ttyp != BEAR_TRAP && t->ttyp != WEB))
4413 return FALSE;
4415 trapdescr = defsyms[trap_to_defsym(t->ttyp)].explanation;
4416 which = t->tseen ? the_your[t->madeby_u]
4417 : index(vowels, *trapdescr) ? "an" : "a";
4419 if (ishero) {
4420 if (!u.utrap)
4421 return FALSE;
4422 u.utrap = 0; /* released regardless of type */
4423 *noticed = TRUE;
4424 /* give message only if trap was the expected type */
4425 if (u.utraptype == TT_BEARTRAP || u.utraptype == TT_WEB) {
4426 if (u.usteed)
4427 Sprintf(buf, "%s is", noit_Monnam(u.usteed));
4428 else
4429 Strcpy(buf, "You are");
4430 pline("%s released from %s %s.", buf, which, trapdescr);
4432 } else {
4433 if (!mon->mtrapped)
4434 return FALSE;
4435 mon->mtrapped = 0;
4436 if (canspotmon(mon)) {
4437 *noticed = TRUE;
4438 pline("%s is released from %s %s.", Monnam(mon), which,
4439 trapdescr);
4440 } else if (cansee(t->tx, t->ty) && t->tseen) {
4441 *noticed = TRUE;
4442 if (t->ttyp == WEB)
4443 pline("%s is released from %s %s.", Something, which,
4444 trapdescr);
4445 else /* BEAR_TRAP */
4446 pline("%s %s opens.", upstart(strcpy(buf, which)), trapdescr);
4448 /* might pacify monster if adjacent */
4449 if (rn2(2) && distu(mon->mx, mon->my) <= 2)
4450 reward_untrap(t, mon);
4452 return TRUE;
4455 /* for magic locking; returns true if targetted monster (which might
4456 be hero) gets hit by a trap (might avoid actually becoming trapped) */
4457 boolean
4458 closeholdingtrap(mon, noticed)
4459 struct monst *mon;
4460 boolean *noticed; /* set to true iff hero notices the effect; */
4461 { /* otherwise left with its previous value intact */
4462 struct trap *t;
4463 unsigned dotrapflags;
4464 boolean ishero = (mon == &youmonst), result;
4466 if (mon == u.usteed)
4467 ishero = TRUE;
4468 t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my);
4469 /* if no trap here or it's not a holding trap, we're done */
4470 if (!t || (t->ttyp != BEAR_TRAP && t->ttyp != WEB))
4471 return FALSE;
4473 if (ishero) {
4474 if (u.utrap)
4475 return FALSE; /* already trapped */
4476 *noticed = TRUE;
4477 dotrapflags = FORCETRAP;
4478 /* dotrap calls mintrap when mounted hero encounters a web */
4479 if (u.usteed)
4480 dotrapflags |= NOWEBMSG;
4481 ++force_mintrap;
4482 dotrap(t, dotrapflags);
4483 --force_mintrap;
4484 result = (u.utrap != 0);
4485 } else {
4486 if (mon->mtrapped)
4487 return FALSE; /* already trapped */
4488 /* you notice it if you see the trap close/tremble/whatever
4489 or if you sense the monster who becomes trapped */
4490 *noticed = cansee(t->tx, t->ty) || canspotmon(mon);
4491 ++force_mintrap;
4492 result = (mintrap(mon) != 0);
4493 --force_mintrap;
4495 return result;
4498 /* for magic unlocking; returns true if targetted monster (which might
4499 be hero) gets hit by a trap (target might avoid its effect) */
4500 boolean
4501 openfallingtrap(mon, trapdoor_only, noticed)
4502 struct monst *mon;
4503 boolean trapdoor_only;
4504 boolean *noticed; /* set to true iff hero notices the effect; */
4505 { /* otherwise left with its previous value intact */
4506 struct trap *t;
4507 boolean ishero = (mon == &youmonst), result;
4509 if (mon == u.usteed)
4510 ishero = TRUE;
4511 t = t_at(ishero ? u.ux : mon->mx, ishero ? u.uy : mon->my);
4512 /* if no trap here or it's not a falling trap, we're done
4513 (note: falling rock traps have a trapdoor in the ceiling) */
4514 if (!t || ((t->ttyp != TRAPDOOR && t->ttyp != ROCKTRAP)
4515 && (trapdoor_only || (t->ttyp != HOLE && t->ttyp != PIT
4516 && t->ttyp != SPIKED_PIT))))
4517 return FALSE;
4519 if (ishero) {
4520 if (u.utrap)
4521 return FALSE; /* already trapped */
4522 *noticed = TRUE;
4523 dotrap(t, FORCETRAP);
4524 result = (u.utrap != 0);
4525 } else {
4526 if (mon->mtrapped)
4527 return FALSE; /* already trapped */
4528 /* you notice it if you see the trap close/tremble/whatever
4529 or if you sense the monster who becomes trapped */
4530 *noticed = cansee(t->tx, t->ty) || canspotmon(mon);
4531 /* monster will be angered; mintrap doesn't handle that */
4532 wakeup(mon);
4533 ++force_mintrap;
4534 result = (mintrap(mon) != 0);
4535 --force_mintrap;
4536 /* mon might now be on the migrating monsters list */
4538 return result;
4541 /* only called when the player is doing something to the chest directly */
4542 boolean
4543 chest_trap(obj, bodypart, disarm)
4544 register struct obj *obj;
4545 register int bodypart;
4546 boolean disarm;
4548 register struct obj *otmp = obj, *otmp2;
4549 char buf[80];
4550 const char *msg;
4551 coord cc;
4553 if (get_obj_location(obj, &cc.x, &cc.y, 0)) /* might be carried */
4554 obj->ox = cc.x, obj->oy = cc.y;
4556 otmp->otrapped = 0; /* trap is one-shot; clear flag first in case
4557 chest kills you and ends up in bones file */
4558 You(disarm ? "set it off!" : "trigger a trap!");
4559 display_nhwindow(WIN_MESSAGE, FALSE);
4560 if (Luck > -13 && rn2(13 + Luck) > 7) { /* saved by luck */
4561 /* trap went off, but good luck prevents damage */
4562 switch (rn2(13)) {
4563 case 12:
4564 case 11:
4565 msg = "explosive charge is a dud";
4566 break;
4567 case 10:
4568 case 9:
4569 msg = "electric charge is grounded";
4570 break;
4571 case 8:
4572 case 7:
4573 msg = "flame fizzles out";
4574 break;
4575 case 6:
4576 case 5:
4577 case 4:
4578 msg = "poisoned needle misses";
4579 break;
4580 case 3:
4581 case 2:
4582 case 1:
4583 case 0:
4584 msg = "gas cloud blows away";
4585 break;
4586 default:
4587 impossible("chest disarm bug");
4588 msg = (char *) 0;
4589 break;
4591 if (msg)
4592 pline("But luckily the %s!", msg);
4593 } else {
4594 switch (rn2(20) ? ((Luck >= 13) ? 0 : rn2(13 - Luck)) : rn2(26)) {
4595 case 25:
4596 case 24:
4597 case 23:
4598 case 22:
4599 case 21: {
4600 struct monst *shkp = 0;
4601 long loss = 0L;
4602 boolean costly, insider;
4603 register xchar ox = obj->ox, oy = obj->oy;
4605 /* the obj location need not be that of player */
4606 costly = (costly_spot(ox, oy)
4607 && (shkp = shop_keeper(*in_rooms(ox, oy, SHOPBASE)))
4608 != (struct monst *) 0);
4609 insider = (*u.ushops && inside_shop(u.ux, u.uy)
4610 && *in_rooms(ox, oy, SHOPBASE) == *u.ushops);
4612 pline("%s!", Tobjnam(obj, "explode"));
4613 Sprintf(buf, "exploding %s", xname(obj));
4615 if (costly)
4616 loss += stolen_value(obj, ox, oy, (boolean) shkp->mpeaceful,
4617 TRUE);
4618 delete_contents(obj);
4619 /* unpunish() in advance if either ball or chain (or both)
4620 is going to be destroyed */
4621 if (Punished && ((uchain->ox == u.ux && uchain->oy == u.uy)
4622 || (uball->where == OBJ_FLOOR
4623 && uball->ox == u.ux && uball->oy == u.uy)))
4624 unpunish();
4626 for (otmp = level.objects[u.ux][u.uy]; otmp; otmp = otmp2) {
4627 otmp2 = otmp->nexthere;
4628 if (costly)
4629 loss += stolen_value(otmp, otmp->ox, otmp->oy,
4630 (boolean) shkp->mpeaceful, TRUE);
4631 delobj(otmp);
4633 wake_nearby();
4634 losehp(Maybe_Half_Phys(d(6, 6)), buf, KILLED_BY_AN);
4635 exercise(A_STR, FALSE);
4636 if (costly && loss) {
4637 if (insider)
4638 You("owe %ld %s for objects destroyed.", loss,
4639 currency(loss));
4640 else {
4641 You("caused %ld %s worth of damage!", loss,
4642 currency(loss));
4643 make_angry_shk(shkp, ox, oy);
4646 return TRUE;
4647 } /* case 21 */
4648 case 20:
4649 case 19:
4650 case 18:
4651 case 17:
4652 pline("A cloud of noxious gas billows from %s.", the(xname(obj)));
4653 poisoned("gas cloud", A_STR, "cloud of poison gas", 15, FALSE);
4654 exercise(A_CON, FALSE);
4655 break;
4656 case 16:
4657 case 15:
4658 case 14:
4659 case 13:
4660 You_feel("a needle prick your %s.", body_part(bodypart));
4661 poisoned("needle", A_CON, "poisoned needle", 10, FALSE);
4662 exercise(A_CON, FALSE);
4663 break;
4664 case 12:
4665 case 11:
4666 case 10:
4667 case 9:
4668 dofiretrap(obj);
4669 break;
4670 case 8:
4671 case 7:
4672 case 6: {
4673 int dmg;
4675 You("are jolted by a surge of electricity!");
4676 if (Shock_resistance) {
4677 shieldeff(u.ux, u.uy);
4678 You("don't seem to be affected.");
4679 dmg = 0;
4680 } else
4681 dmg = d(4, 4);
4682 destroy_item(RING_CLASS, AD_ELEC);
4683 destroy_item(WAND_CLASS, AD_ELEC);
4684 if (dmg)
4685 losehp(dmg, "electric shock", KILLED_BY_AN);
4686 break;
4687 } /* case 6 */
4688 case 5:
4689 case 4:
4690 case 3:
4691 if (!Free_action) {
4692 pline("Suddenly you are frozen in place!");
4693 nomul(-d(5, 6));
4694 multi_reason = "frozen by a trap";
4695 exercise(A_DEX, FALSE);
4696 nomovemsg = You_can_move_again;
4697 } else
4698 You("momentarily stiffen.");
4699 break;
4700 case 2:
4701 case 1:
4702 case 0:
4703 pline("A cloud of %s gas billows from %s.",
4704 Blind ? blindgas[rn2(SIZE(blindgas))] : rndcolor(),
4705 the(xname(obj)));
4706 if (!Stunned) {
4707 if (Hallucination)
4708 pline("What a groovy feeling!");
4709 else
4710 You("%s%s...", stagger(youmonst.data, "stagger"),
4711 Halluc_resistance ? ""
4712 : Blind ? " and get dizzy"
4713 : " and your vision blurs");
4715 make_stunned((HStun & TIMEOUT) + (long) rn1(7, 16), FALSE);
4716 (void) make_hallucinated(
4717 (HHallucination & TIMEOUT) + (long) rn1(5, 16), FALSE, 0L);
4718 break;
4719 default:
4720 impossible("bad chest trap");
4721 break;
4723 bot(); /* to get immediate botl re-display */
4726 return FALSE;
4729 struct trap *
4730 t_at(x, y)
4731 register int x, y;
4733 register struct trap *trap = ftrap;
4735 while (trap) {
4736 if (trap->tx == x && trap->ty == y)
4737 return trap;
4738 trap = trap->ntrap;
4740 return (struct trap *) 0;
4743 void
4744 deltrap(trap)
4745 register struct trap *trap;
4747 register struct trap *ttmp;
4749 clear_conjoined_pits(trap);
4750 if (trap == ftrap) {
4751 ftrap = ftrap->ntrap;
4752 } else {
4753 for (ttmp = ftrap; ttmp; ttmp = ttmp->ntrap)
4754 if (ttmp->ntrap == trap)
4755 break;
4756 if (!ttmp)
4757 panic("deltrap: no preceding trap!");
4758 ttmp->ntrap = trap->ntrap;
4760 if (Sokoban && (trap->ttyp == PIT || trap->ttyp == HOLE))
4761 maybe_finish_sokoban();
4762 dealloc_trap(trap);
4765 boolean
4766 conjoined_pits(trap2, trap1, u_entering_trap2)
4767 struct trap *trap2, *trap1;
4768 boolean u_entering_trap2;
4770 int dx, dy, diridx, adjidx;
4772 if (!trap1 || !trap2)
4773 return FALSE;
4774 if (!isok(trap2->tx, trap2->ty) || !isok(trap1->tx, trap1->ty)
4775 || !(trap2->ttyp == PIT || trap2->ttyp == SPIKED_PIT)
4776 || !(trap1->ttyp == PIT || trap1->ttyp == SPIKED_PIT)
4777 || (u_entering_trap2 && !(u.utrap && u.utraptype == TT_PIT)))
4778 return FALSE;
4779 dx = sgn(trap2->tx - trap1->tx);
4780 dy = sgn(trap2->ty - trap1->ty);
4781 for (diridx = 0; diridx < 8; diridx++)
4782 if (xdir[diridx] == dx && ydir[diridx] == dy)
4783 break;
4784 /* diridx is valid if < 8 */
4785 if (diridx < 8) {
4786 adjidx = (diridx + 4) % 8;
4787 if ((trap1->conjoined & (1 << diridx))
4788 && (trap2->conjoined & (1 << adjidx)))
4789 return TRUE;
4791 return FALSE;
4794 void
4795 clear_conjoined_pits(trap)
4796 struct trap *trap;
4798 int diridx, adjidx, x, y;
4799 struct trap *t;
4801 if (trap && (trap->ttyp == PIT || trap->ttyp == SPIKED_PIT)) {
4802 for (diridx = 0; diridx < 8; ++diridx) {
4803 if (trap->conjoined & (1 << diridx)) {
4804 x = trap->tx + xdir[diridx];
4805 y = trap->ty + ydir[diridx];
4806 if (isok(x, y)
4807 && (t = t_at(x, y)) != 0
4808 && (t->ttyp == PIT || t->ttyp == SPIKED_PIT)) {
4809 adjidx = (diridx + 4) % 8;
4810 t->conjoined &= ~(1 << adjidx);
4812 trap->conjoined &= ~(1 << diridx);
4818 #if 0
4820 * Mark all neighboring pits as conjoined pits.
4821 * (currently not called from anywhere)
4823 STATIC_OVL void
4824 join_adjacent_pits(trap)
4825 struct trap *trap;
4827 struct trap *t;
4828 int diridx, x, y;
4830 if (!trap)
4831 return;
4832 for (diridx = 0; diridx < 8; ++diridx) {
4833 x = trap->tx + xdir[diridx];
4834 y = trap->ty + ydir[diridx];
4835 if (isok(x, y)) {
4836 if ((t = t_at(x, y)) != 0
4837 && (t->ttyp == PIT || t->ttyp == SPIKED_PIT)) {
4838 trap->conjoined |= (1 << diridx);
4839 join_adjacent_pits(t);
4840 } else
4841 trap->conjoined &= ~(1 << diridx);
4845 #endif /*0*/
4848 * Returns TRUE if you escaped a pit and are standing on the precipice.
4850 boolean
4851 uteetering_at_seen_pit(trap)
4852 struct trap *trap;
4854 if (trap && trap->tseen && (!u.utrap || u.utraptype != TT_PIT)
4855 && (trap->ttyp == PIT || trap->ttyp == SPIKED_PIT))
4856 return TRUE;
4857 else
4858 return FALSE;
4861 /* Destroy a trap that emanates from the floor. */
4862 boolean
4863 delfloortrap(ttmp)
4864 register struct trap *ttmp;
4866 /* some of these are arbitrary -dlc */
4867 if (ttmp && ((ttmp->ttyp == SQKY_BOARD) || (ttmp->ttyp == BEAR_TRAP)
4868 || (ttmp->ttyp == LANDMINE) || (ttmp->ttyp == FIRE_TRAP)
4869 || (ttmp->ttyp == PIT) || (ttmp->ttyp == SPIKED_PIT)
4870 || (ttmp->ttyp == HOLE) || (ttmp->ttyp == TRAPDOOR)
4871 || (ttmp->ttyp == TELEP_TRAP) || (ttmp->ttyp == LEVEL_TELEP)
4872 || (ttmp->ttyp == WEB) || (ttmp->ttyp == MAGIC_TRAP)
4873 || (ttmp->ttyp == ANTI_MAGIC))) {
4874 register struct monst *mtmp;
4876 if (ttmp->tx == u.ux && ttmp->ty == u.uy) {
4877 u.utrap = 0;
4878 u.utraptype = 0;
4879 } else if ((mtmp = m_at(ttmp->tx, ttmp->ty)) != 0) {
4880 mtmp->mtrapped = 0;
4882 deltrap(ttmp);
4883 return TRUE;
4885 return FALSE;
4888 /* used for doors (also tins). can be used for anything else that opens. */
4889 void
4890 b_trapped(item, bodypart)
4891 const char *item;
4892 int bodypart;
4894 int lvl = level_difficulty(),
4895 dmg = rnd(5 + (lvl < 5 ? lvl : 2 + lvl / 2));
4897 pline("KABOOM!! %s was booby-trapped!", The(item));
4898 wake_nearby();
4899 losehp(Maybe_Half_Phys(dmg), "explosion", KILLED_BY_AN);
4900 exercise(A_STR, FALSE);
4901 if (bodypart)
4902 exercise(A_CON, FALSE);
4903 make_stunned((HStun & TIMEOUT) + (long) dmg, TRUE);
4906 /* Monster is hit by trap. */
4907 /* Note: doesn't work if both obj and d_override are null */
4908 STATIC_OVL boolean
4909 thitm(tlev, mon, obj, d_override, nocorpse)
4910 int tlev;
4911 struct monst *mon;
4912 struct obj *obj;
4913 int d_override;
4914 boolean nocorpse;
4916 int strike;
4917 boolean trapkilled = FALSE;
4919 if (d_override)
4920 strike = 1;
4921 else if (obj)
4922 strike = (find_mac(mon) + tlev + obj->spe <= rnd(20));
4923 else
4924 strike = (find_mac(mon) + tlev <= rnd(20));
4926 /* Actually more accurate than thitu, which doesn't take
4927 * obj->spe into account.
4929 if (!strike) {
4930 if (obj && cansee(mon->mx, mon->my))
4931 pline("%s is almost hit by %s!", Monnam(mon), doname(obj));
4932 } else {
4933 int dam = 1;
4935 if (obj && cansee(mon->mx, mon->my))
4936 pline("%s is hit by %s!", Monnam(mon), doname(obj));
4937 if (d_override)
4938 dam = d_override;
4939 else if (obj) {
4940 dam = dmgval(obj, mon);
4941 if (dam < 1)
4942 dam = 1;
4944 if ((mon->mhp -= dam) <= 0) {
4945 int xx = mon->mx;
4946 int yy = mon->my;
4948 monkilled(mon, "", nocorpse ? -AD_RBRE : AD_PHYS);
4949 if (mon->mhp <= 0) {
4950 newsym(xx, yy);
4951 trapkilled = TRUE;
4955 if (obj && (!strike || d_override)) {
4956 place_object(obj, mon->mx, mon->my);
4957 stackobj(obj);
4958 } else if (obj)
4959 dealloc_obj(obj);
4961 return trapkilled;
4964 boolean
4965 unconscious()
4967 if (multi >= 0)
4968 return FALSE;
4970 return (boolean) (u.usleep
4971 || (nomovemsg
4972 && (!strncmp(nomovemsg, "You awake", 9)
4973 || !strncmp(nomovemsg, "You regain con", 14)
4974 || !strncmp(nomovemsg, "You are consci", 14))));
4977 static const char lava_killer[] = "molten lava";
4979 boolean
4980 lava_effects()
4982 register struct obj *obj, *obj2;
4983 int dmg = d(6, 6); /* only applicable for water walking */
4984 boolean usurvive, boil_away;
4986 burn_away_slime();
4987 if (likes_lava(youmonst.data))
4988 return FALSE;
4990 usurvive = Fire_resistance || (Wwalking && dmg < u.uhp);
4992 * A timely interrupt might manage to salvage your life
4993 * but not your gear. For scrolls and potions this
4994 * will destroy whole stacks, where fire resistant hero
4995 * survivor only loses partial stacks via destroy_item().
4997 * Flag items to be destroyed before any messages so
4998 * that player causing hangup at --More-- won't get an
4999 * emergency save file created before item destruction.
5001 if (!usurvive)
5002 for (obj = invent; obj; obj = obj->nobj)
5003 if ((is_organic(obj) || obj->oclass == POTION_CLASS)
5004 && !obj->oerodeproof
5005 && objects[obj->otyp].oc_oprop != FIRE_RES
5006 && obj->otyp != SCR_FIRE && obj->otyp != SPE_FIREBALL
5007 && !obj_resists(obj, 0, 0)) /* for invocation items */
5008 obj->in_use = 1;
5010 /* Check whether we should burn away boots *first* so we know whether to
5011 * make the player sink into the lava. Assumption: water walking only
5012 * comes from boots.
5014 if (uarmf && is_organic(uarmf) && !uarmf->oerodeproof) {
5015 obj = uarmf;
5016 pline("%s into flame!", Yobjnam2(obj, "burst"));
5017 iflags.in_lava_effects++; /* (see above) */
5018 (void) Boots_off();
5019 useup(obj);
5020 iflags.in_lava_effects--;
5023 if (!Fire_resistance) {
5024 if (Wwalking) {
5025 pline_The("lava here burns you!");
5026 if (usurvive) {
5027 losehp(dmg, lava_killer, KILLED_BY); /* lava damage */
5028 goto burn_stuff;
5030 } else
5031 You("fall into the lava!");
5033 usurvive = Lifesaved || discover;
5034 if (wizard)
5035 usurvive = TRUE;
5037 /* prevent remove_worn_item() -> Boots_off(WATER_WALKING_BOOTS) ->
5038 spoteffects() -> lava_effects() recursion which would
5039 successfully delete (via useupall) the no-longer-worn boots;
5040 once recursive call returned, we would try to delete them again
5041 here in the outer call (and access stale memory, probably panic) */
5042 iflags.in_lava_effects++;
5044 for (obj = invent; obj; obj = obj2) {
5045 obj2 = obj->nobj;
5046 /* above, we set in_use for objects which are to be destroyed */
5047 if (obj->otyp == SPE_BOOK_OF_THE_DEAD && !Blind) {
5048 if (usurvive)
5049 pline("%s glows a strange %s, but remains intact.",
5050 The(xname(obj)), hcolor("dark red"));
5051 } else if (obj->in_use) {
5052 if (obj->owornmask) {
5053 if (usurvive)
5054 pline("%s into flame!", Yobjnam2(obj, "burst"));
5055 remove_worn_item(obj, TRUE);
5057 useupall(obj);
5061 iflags.in_lava_effects--;
5063 /* s/he died... */
5064 boil_away = (u.umonnum == PM_WATER_ELEMENTAL
5065 || u.umonnum == PM_STEAM_VORTEX
5066 || u.umonnum == PM_FOG_CLOUD);
5067 for (;;) {
5068 u.uhp = -1;
5069 /* killer format and name are reconstructed every iteration
5070 because lifesaving resets them */
5071 killer.format = KILLED_BY;
5072 Strcpy(killer.name, lava_killer);
5073 You("%s...", boil_away ? "boil away" : "burn to a crisp");
5074 done(BURNING);
5075 if (safe_teleds(TRUE))
5076 break; /* successful life-save */
5077 /* nowhere safe to land; repeat burning loop */
5078 pline("You're still burning.");
5080 You("find yourself back on solid %s.", surface(u.ux, u.uy));
5081 return TRUE;
5082 } else if (!Wwalking && (!u.utrap || u.utraptype != TT_LAVA)) {
5083 boil_away = !Fire_resistance;
5084 /* if not fire resistant, sink_into_lava() will quickly be fatal;
5085 hero needs to escape immediately */
5086 u.utrap = rn1(4, 4) + ((boil_away ? 2 : rn1(4, 12)) << 8);
5087 u.utraptype = TT_LAVA;
5088 You("sink into the lava%s!", !boil_away
5089 ? ", but it only burns slightly"
5090 : " and are about to be immolated");
5091 if (u.uhp > 1)
5092 losehp(!boil_away ? 1 : (u.uhp / 2), lava_killer,
5093 KILLED_BY); /* lava damage */
5096 burn_stuff:
5097 destroy_item(SCROLL_CLASS, AD_FIRE);
5098 destroy_item(SPBOOK_CLASS, AD_FIRE);
5099 destroy_item(POTION_CLASS, AD_FIRE);
5100 return FALSE;
5103 /* called each turn when trapped in lava */
5104 void
5105 sink_into_lava()
5107 static const char sink_deeper[] = "You sink deeper into the lava.";
5109 if (!u.utrap || u.utraptype != TT_LAVA) {
5110 ; /* do nothing; this shouldn't happen */
5111 } else if (!is_lava(u.ux, u.uy)) {
5112 u.utrap = 0; /* this shouldn't happen either */
5113 } else if (!u.uinvulnerable) {
5114 /* ordinarily we'd have to be fire resistant to survive long
5115 enough to become stuck in lava, but it can happen without
5116 resistance if water walking boots allow survival and then
5117 get burned up; u.utrap time will be quite short in that case */
5118 if (!Fire_resistance)
5119 u.uhp = (u.uhp + 2) / 3;
5121 u.utrap -= (1 << 8);
5122 if (u.utrap < (1 << 8)) {
5123 killer.format = KILLED_BY;
5124 Strcpy(killer.name, "molten lava");
5125 You("sink below the surface and die.");
5126 burn_away_slime(); /* add insult to injury? */
5127 done(DISSOLVED);
5128 /* can only get here via life-saving; try to get away from lava */
5129 u.utrap = 0;
5130 (void) safe_teleds(TRUE);
5131 } else if (!u.umoved) {
5132 /* can't fully turn into slime while in lava, but might not
5133 have it be burned away until you've come awfully close */
5134 if (Slimed && rnd(10 - 1) >= (int) (Slimed & TIMEOUT)) {
5135 pline(sink_deeper);
5136 burn_away_slime();
5137 } else {
5138 Norep(sink_deeper);
5140 u.utrap += rnd(4);
5145 /* called when something has been done (breaking a boulder, for instance)
5146 which entails a luck penalty if performed on a sokoban level */
5147 void
5148 sokoban_guilt()
5150 if (Sokoban) {
5151 change_luck(-1);
5152 /* TODO: issue some feedback so that player can learn that whatever
5153 he/she just did is a naughty thing to do in sokoban and should
5154 probably be avoided in future....
5155 Caveat: doing this might introduce message sequencing issues,
5156 depending upon feedback during the various actions which trigger
5157 Sokoban luck penalties. */
5161 /* called when a trap has been deleted or had its ttyp replaced */
5162 STATIC_OVL void
5163 maybe_finish_sokoban()
5165 struct trap *t;
5167 if (Sokoban && !in_mklev) {
5168 /* scan all remaining traps, ignoring any created by the hero;
5169 if this level has no more pits or holes, the current sokoban
5170 puzzle has been solved */
5171 for (t = ftrap; t; t = t->ntrap) {
5172 if (t->madeby_u)
5173 continue;
5174 if (t->ttyp == PIT || t->ttyp == HOLE)
5175 break;
5177 if (!t) {
5178 /* we've passed the last trap without finding a pit or hole;
5179 clear the sokoban_rules flag so that luck penalties for
5180 things like breaking boulders or jumping will no longer
5181 be given, and restrictions on diagonal moves are lifted */
5182 Sokoban = 0; /* clear level.flags.sokoban_rules */
5183 /* TODO: give some feedback about solving the sokoban puzzle
5184 (perhaps say "congratulations" in Japanese?) */
5189 /*trap.c*/