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