make rank() static again
[NetHack.git] / src / lock.c
blob47e160b7a0805996e8eb87eadec8a171f4048d97
1 /* NetHack 3.7 lock.c $NHDT-Date: 1718745135 2024/06/18 21:12:15 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.137 $ */
2 /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
3 /*-Copyright (c) Robert Patrick Rankin, 2011. */
4 /* NetHack may be freely redistributed. See license for details. */
6 #include "hack.h"
8 /* occupation callbacks */
9 staticfn int picklock(void);
10 staticfn int forcelock(void);
12 staticfn const char *lock_action(void);
13 staticfn boolean obstructed(coordxy, coordxy, boolean);
14 staticfn void chest_shatter_msg(struct obj *);
16 boolean
17 picking_lock(coordxy *x, coordxy *y)
19 if (go.occupation == picklock) {
20 *x = u.ux + u.dx;
21 *y = u.uy + u.dy;
22 return TRUE;
23 } else {
24 *x = *y = 0;
25 return FALSE;
29 boolean
30 picking_at(coordxy x, coordxy y)
32 return (boolean) (go.occupation == picklock
33 && gx.xlock.door == &levl[x][y]);
36 /* produce an occupation string appropriate for the current activity */
37 staticfn const char *
38 lock_action(void)
40 /* "unlocking"+2 == "locking" */
41 static const char *const actions[] = {
42 "unlocking the door", /* [0] */
43 "unlocking the chest", /* [1] */
44 "unlocking the box", /* [2] */
45 "picking the lock" /* [3] */
48 /* if the target is currently unlocked, we're trying to lock it now */
49 if (gx.xlock.door && !(gx.xlock.door->doormask & D_LOCKED))
50 return actions[0] + 2; /* "locking the door" */
51 else if (gx.xlock.box && !gx.xlock.box->olocked)
52 return gx.xlock.box->otyp == CHEST ? actions[1] + 2 : actions[2] + 2;
53 /* otherwise we're trying to unlock it */
54 else if (gx.xlock.picktyp == LOCK_PICK)
55 return actions[3]; /* "picking the lock" */
56 else if (gx.xlock.picktyp == CREDIT_CARD)
57 return actions[3]; /* same as lock_pick */
58 else if (gx.xlock.door)
59 return actions[0]; /* "unlocking the door" */
60 else if (gx.xlock.box)
61 return gx.xlock.box->otyp == CHEST ? actions[1] : actions[2];
62 else
63 return actions[3];
66 /* try to open/close a lock */
67 staticfn int
68 picklock(void)
70 if (gx.xlock.box) {
71 if (gx.xlock.box->where != OBJ_FLOOR
72 || gx.xlock.box->ox != u.ux || gx.xlock.box->oy != u.uy) {
73 return ((gx.xlock.usedtime = 0)); /* you or it moved */
75 } else { /* door */
76 if (gx.xlock.door != &(levl[u.ux + u.dx][u.uy + u.dy])) {
77 return ((gx.xlock.usedtime = 0)); /* you moved */
79 switch (gx.xlock.door->doormask) {
80 case D_NODOOR:
81 pline("This doorway has no door.");
82 return ((gx.xlock.usedtime = 0));
83 case D_ISOPEN:
84 You("cannot lock an open door.");
85 return ((gx.xlock.usedtime = 0));
86 case D_BROKEN:
87 pline("This door is broken.");
88 return ((gx.xlock.usedtime = 0));
92 if (gx.xlock.usedtime++ >= 50 || nohands(gy.youmonst.data)) {
93 You("give up your attempt at %s.", lock_action());
94 exercise(A_DEX, TRUE); /* even if you don't succeed */
95 return ((gx.xlock.usedtime = 0));
98 if (rn2(100) >= gx.xlock.chance)
99 return 1; /* still busy */
101 /* using the Master Key of Thievery finds traps if its bless/curse
102 state is adequate (non-cursed for rogues, blessed for others;
103 checked when setting up 'xlock') */
104 if ((!gx.xlock.door ? (int) gx.xlock.box->otrapped
105 : (gx.xlock.door->doormask & D_TRAPPED) != 0)
106 && gx.xlock.magic_key) {
107 gx.xlock.chance += 20; /* less effort needed next time */
108 if (!gx.xlock.door) {
109 if (!gx.xlock.box->tknown)
110 You("find a trap!");
111 gx.xlock.box->tknown = 1;
113 if (y_n("Do you want to try to disarm it?") == 'y') {
114 const char *what;
115 boolean alreadyunlocked;
117 /* disarming while using magic key always succeeds */
118 if (gx.xlock.door) {
119 gx.xlock.door->doormask &= ~D_TRAPPED;
120 what = "door";
121 alreadyunlocked = !(gx.xlock.door->doormask & D_LOCKED);
122 } else {
123 gx.xlock.box->otrapped = 0;
124 gx.xlock.box->tknown = 0;
125 what = (gx.xlock.box->otyp == CHEST) ? "chest" : "box";
126 alreadyunlocked = !gx.xlock.box->olocked;
128 You("succeed in disarming the trap. The %s is still %slocked.",
129 what, alreadyunlocked ? "un" : "");
130 exercise(A_WIS, TRUE);
131 } else {
132 You("stop %s.", lock_action());
133 exercise(A_WIS, FALSE);
135 return ((gx.xlock.usedtime = 0));
138 You("succeed in %s.", lock_action());
139 if (gx.xlock.door) {
140 if (gx.xlock.door->doormask & D_TRAPPED) {
141 b_trapped("door", FINGER);
142 gx.xlock.door->doormask = D_NODOOR;
143 unblock_point(u.ux + u.dx, u.uy + u.dy);
144 if (*in_rooms(u.ux + u.dx, u.uy + u.dy, SHOPBASE))
145 add_damage(u.ux + u.dx, u.uy + u.dy, SHOP_DOOR_COST);
146 newsym(u.ux + u.dx, u.uy + u.dy);
147 } else if (gx.xlock.door->doormask & D_LOCKED)
148 gx.xlock.door->doormask = D_CLOSED;
149 else
150 gx.xlock.door->doormask = D_LOCKED;
151 } else {
152 gx.xlock.box->olocked = !gx.xlock.box->olocked;
153 gx.xlock.box->lknown = 1;
154 if (gx.xlock.box->otrapped)
155 (void) chest_trap(gx.xlock.box, FINGER, FALSE);
157 exercise(A_DEX, TRUE);
158 return ((gx.xlock.usedtime = 0));
161 void
162 breakchestlock(struct obj *box, boolean destroyit)
164 if (!destroyit) { /* bill for the box but not for its contents */
165 struct obj *hide_contents = box->cobj;
167 box->cobj = 0;
168 costly_alteration(box, COST_BRKLCK);
169 box->cobj = hide_contents;
170 box->olocked = 0;
171 box->obroken = 1;
172 box->lknown = 1;
173 } else { /* #force has destroyed this box (at <u.ux,u.uy>) */
174 struct obj *otmp;
175 struct monst *shkp = (*u.ushops && costly_spot(u.ux, u.uy))
176 ? shop_keeper(*u.ushops)
177 : 0;
178 boolean costly = (boolean) (shkp != 0),
179 peaceful_shk = costly && (boolean) shkp->mpeaceful;
180 long loss = 0L;
182 pline("In fact, you've totally destroyed %s.", the(xname(box)));
183 /* Put the contents on ground at the hero's feet. */
184 while ((otmp = box->cobj) != 0) {
185 obj_extract_self(otmp);
186 if (!rn2(3) || otmp->oclass == POTION_CLASS) {
187 chest_shatter_msg(otmp);
188 if (costly)
189 loss += stolen_value(otmp, u.ux, u.uy, peaceful_shk,
190 TRUE);
191 if (otmp->quan == 1L) {
192 obfree(otmp, (struct obj *) 0);
193 continue;
195 /* this works because we're sure to have at least 1 left;
196 otherwise it would fail since otmp is not in inventory */
197 useup(otmp);
199 if (box->otyp == ICE_BOX && otmp->otyp == CORPSE) {
200 otmp->age = svm.moves - otmp->age; /* actual age */
201 start_corpse_timeout(otmp);
203 place_object(otmp, u.ux, u.uy);
204 stackobj(otmp);
206 if (costly)
207 loss += stolen_value(box, u.ux, u.uy, peaceful_shk, TRUE);
208 if (loss)
209 You("owe %ld %s for objects destroyed.", loss, currency(loss));
210 delobj(box);
214 /* try to force a locked chest */
215 staticfn int
216 forcelock(void)
218 if ((gx.xlock.box->ox != u.ux) || (gx.xlock.box->oy != u.uy))
219 return ((gx.xlock.usedtime = 0)); /* you or it moved */
221 if (gx.xlock.usedtime++ >= 50 || !uwep || nohands(gy.youmonst.data)) {
222 You("give up your attempt to force the lock.");
223 if (gx.xlock.usedtime >= 50) /* you made the effort */
224 exercise((gx.xlock.picktyp) ? A_DEX : A_STR, TRUE);
225 return ((gx.xlock.usedtime = 0));
228 if (gx.xlock.picktyp) { /* blade */
229 if (rn2(1000 - (int) uwep->spe) > (992 - greatest_erosion(uwep) * 10)
230 && !uwep->cursed && !obj_resists(uwep, 0, 99)) {
231 /* for a +0 weapon, probability that it survives an unsuccessful
232 * attempt to force the lock is (.992)^50 = .67
234 pline("%sour %s broke!", (uwep->quan > 1L) ? "One of y" : "Y",
235 xname(uwep));
236 useup(uwep);
237 You("give up your attempt to force the lock.");
238 exercise(A_DEX, TRUE);
239 return ((gx.xlock.usedtime = 0));
241 } else /* blunt */
242 wake_nearby(FALSE); /* due to hammering on the container */
244 if (rn2(100) >= gx.xlock.chance)
245 return 1; /* still busy */
247 You("succeed in forcing the lock.");
248 exercise(gx.xlock.picktyp ? A_DEX : A_STR, TRUE);
249 /* breakchestlock() might destroy xlock.box; if so, xlock context will
250 be cleared (delobj -> obfree -> maybe_reset_pick); but it might not,
251 so explicitly clear that manually */
252 breakchestlock(gx.xlock.box, (boolean) (!gx.xlock.picktyp && !rn2(3)));
253 reset_pick(); /* lock-picking context is no longer valid */
255 return 0;
258 void
259 reset_pick(void)
261 gx.xlock.usedtime = gx.xlock.chance = gx.xlock.picktyp = 0;
262 gx.xlock.magic_key = FALSE;
263 gx.xlock.door = (struct rm *) 0;
264 gx.xlock.box = (struct obj *) 0;
267 /* level change or object deletion; context may no longer be valid */
268 void
269 maybe_reset_pick(struct obj *container) /* passed from obfree() */
272 * If a specific container, only clear context if it is for that
273 * particular container (which is being deleted). Other stuff on
274 * the current dungeon level remains valid.
275 * However if 'container' is Null, clear context if not carrying
276 * gx.xlock.box (which might be Null if context is for a door).
277 * Used for changing levels, where a floor container or a door is
278 * being left behind and won't be valid on the new level but a
279 * carried container will still be. There might not be any context,
280 * in which case redundantly clearing it is harmless.
282 if (container ? (container == gx.xlock.box)
283 : (!gx.xlock.box || !carried(gx.xlock.box)))
284 reset_pick();
287 /* pick a tool for autounlock */
288 struct obj *
289 autokey(boolean opening) /* True: key, pick, or card; False: key or pick */
291 struct obj *o, *key, *pick, *card, *akey, *apick, *acard;
293 /* mundane item or regular artifact or own role's quest artifact */
294 key = pick = card = (struct obj *) 0;
295 /* other role's quest artifact (Rogue's Key or Tourist's Credit Card) */
296 akey = apick = acard = (struct obj *) 0;
297 for (o = gi.invent; o; o = o->nobj) {
298 if (any_quest_artifact(o) && !is_quest_artifact(o)) {
299 switch (o->otyp) {
300 case SKELETON_KEY:
301 if (!akey)
302 akey = o;
303 break;
304 case LOCK_PICK:
305 if (!apick)
306 apick = o;
307 break;
308 case CREDIT_CARD:
309 if (!acard)
310 acard = o;
311 break;
312 default:
313 break;
315 } else {
316 switch (o->otyp) {
317 case SKELETON_KEY:
318 if (!key || is_magic_key(&gy.youmonst, o))
319 key = o;
320 break;
321 case LOCK_PICK:
322 if (!pick)
323 pick = o;
324 break;
325 case CREDIT_CARD:
326 if (!card)
327 card = o;
328 break;
329 default:
330 break;
334 if (!opening)
335 card = acard = 0;
336 /* only resort to other role's quest artifact if no other choice */
337 if (!key && !pick && !card)
338 key = akey;
339 if (!pick && !card)
340 pick = apick;
341 if (!card)
342 card = acard;
343 return key ? key : pick ? pick : card ? card : 0;
346 DISABLE_WARNING_FORMAT_NONLITERAL
348 /* for doapply(); if player gives a direction or resumes an interrupted
349 previous attempt then it usually costs hero a move even if nothing
350 ultimately happens; when told "can't do that" before being asked for
351 direction or player cancels with ESC while giving direction, it doesn't */
352 #define PICKLOCK_LEARNED_SOMETHING (-1) /* time passes */
353 #define PICKLOCK_DID_NOTHING 0 /* no time passes */
354 #define PICKLOCK_DID_SOMETHING 1
356 /* player is applying a key, lock pick, or credit card */
358 pick_lock(
359 struct obj *pick,
360 coordxy rx, coordxy ry, /* coordinates of door/container, for autounlock:
361 * doesn't prompt for direction if these are set */
362 struct obj *container) /* container, for autounlock */
364 struct obj dummypick;
365 int picktyp, c, ch;
366 coord cc;
367 struct rm *door;
368 struct obj *otmp;
369 char qbuf[QBUFSZ];
370 boolean autounlock = (rx != 0 || container != NULL);
372 /* 'pick' might be Null [called by do_loot_cont() for AUTOUNLOCK_UNTRAP] */
373 if (!pick) {
374 dummypick = cg.zeroobj;
375 pick = &dummypick; /* pick->otyp will be STRANGE_OBJECT */
377 picktyp = pick->otyp;
379 /* check whether we're resuming an interrupted previous attempt */
380 if (gx.xlock.usedtime && picktyp == gx.xlock.picktyp) {
381 static char no_longer[] = "Unfortunately, you can no longer %s %s.";
383 if (nohands(gy.youmonst.data)) {
384 const char *what = (picktyp == LOCK_PICK) ? "pick" : "key";
386 if (picktyp == CREDIT_CARD)
387 what = "card";
388 pline(no_longer, "hold the", what);
389 reset_pick();
390 return PICKLOCK_LEARNED_SOMETHING;
391 } else if (u.uswallow || (gx.xlock.box && !can_reach_floor(TRUE))) {
392 pline(no_longer, "reach the", "lock");
393 reset_pick();
394 return PICKLOCK_LEARNED_SOMETHING;
395 } else {
396 const char *action = lock_action();
398 You("resume your attempt at %s.", action);
399 gx.xlock.magic_key = is_magic_key(&gy.youmonst, pick);
400 set_occupation(picklock, action, 0);
401 return PICKLOCK_DID_SOMETHING;
405 if (nohands(gy.youmonst.data)) {
406 You_cant("hold %s -- you have no hands!", doname(pick));
407 return PICKLOCK_DID_NOTHING;
408 } else if (u.uswallow) {
409 You_cant("%sunlock %s.", (picktyp == CREDIT_CARD) ? "" : "lock or ",
410 mon_nam(u.ustuck));
411 return PICKLOCK_DID_NOTHING;
414 if (pick != &dummypick && picktyp != SKELETON_KEY
415 && picktyp != LOCK_PICK && picktyp != CREDIT_CARD) {
416 impossible("picking lock with object %d?", picktyp);
417 return PICKLOCK_DID_NOTHING;
419 ch = 0; /* lint suppression */
421 if (rx != 0) { /* autounlock; caller has provided coordinates */
422 cc.x = rx;
423 cc.y = ry;
424 } else if (!get_adjacent_loc((char *) 0, "Invalid location!",
425 u.ux, u.uy, &cc)) {
426 return PICKLOCK_DID_NOTHING;
429 if (u_at(cc.x, cc.y)) { /* pick lock on a container */
430 const char *verb;
431 char qsfx[QBUFSZ];
432 boolean it;
433 int count;
436 * FIXME:
437 * (chest->otrapped && chest->tknown) is handled, to skip
438 * checking for a trap and continue with asking about disarm;
439 * (chest->tknown && !chest->otrapped) ignores tknown and will
440 * ask about checking for non-existant trap.
443 if (u.dz < 0 && !autounlock) { /* beware stale u.dz value */
444 There("isn't any sort of lock up %s.",
445 Levitation ? "here" : "there");
446 return PICKLOCK_LEARNED_SOMETHING;
447 } else if (is_lava(u.ux, u.uy)) {
448 pline("Doing that would probably melt %s.", yname(pick));
449 return PICKLOCK_LEARNED_SOMETHING;
450 } else if (is_pool(u.ux, u.uy) && !Underwater) {
451 pline_The("%s has no lock.", hliquid("water"));
452 return PICKLOCK_LEARNED_SOMETHING;
455 count = 0;
456 c = 'n'; /* in case there are no boxes here */
457 for (otmp = svl.level.objects[cc.x][cc.y]; otmp;
458 otmp = otmp->nexthere) {
459 /* autounlock on boxes: only the one that was just discovered to
460 be locked; don't include any other boxes which might be here */
461 if (autounlock && otmp != container)
462 continue;
463 if (Is_box(otmp)) {
464 ++count;
465 if (!can_reach_floor(TRUE)) {
466 You_cant("reach %s from up here.", the(xname(otmp)));
467 return PICKLOCK_LEARNED_SOMETHING;
469 it = 0;
470 if (otmp->obroken)
471 verb = "fix";
472 else if (!otmp->olocked)
473 verb = "lock", it = 1;
474 else if (picktyp != LOCK_PICK)
475 verb = "unlock", it = 1;
476 else
477 verb = "pick";
479 if (autounlock && (flags.autounlock & AUTOUNLOCK_UNTRAP) != 0
480 && could_untrap(FALSE, TRUE)
481 && (c = ynq(safe_qbuf(qbuf, "Check ", " for a trap?",
482 otmp, yname, ysimple_name, "this")))
483 != 'n') {
484 if (c == 'q')
485 return PICKLOCK_DID_NOTHING; /* c == 'q' */
486 /* c == 'y' */
487 untrap(FALSE, 0, 0, otmp);
488 return PICKLOCK_DID_SOMETHING; /* even if no trap found */
489 } else if (autounlock
490 && (flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0) {
491 c = 'q';
492 if (pick != &dummypick) {
493 Sprintf(qbuf, "Unlock it with %s?", yname(pick));
494 c = ynq(qbuf);
496 if (c != 'y')
497 return PICKLOCK_DID_NOTHING;
498 } else {
499 /* "There is <a box> here; <verb> <it|its lock>?" */
500 Sprintf(qsfx, " here; %s %s?",
501 verb, it ? "it" : "its lock");
502 (void) safe_qbuf(qbuf, "There is ", qsfx, otmp, doname,
503 ansimpleoname, "a box");
504 otmp->lknown = 1;
506 c = ynq(qbuf);
507 if (c == 'q')
508 return PICKLOCK_DID_NOTHING;
509 if (c == 'n')
510 continue; /* try next box */
513 if (otmp->obroken) {
514 You_cant("fix its broken lock with %s.",
515 ansimpleoname(pick));
516 return PICKLOCK_LEARNED_SOMETHING;
517 } else if (picktyp == CREDIT_CARD && !otmp->olocked) {
518 /* credit cards are only good for unlocking */
519 You_cant("do that with %s.",
520 an(simple_typename(picktyp)));
521 return PICKLOCK_LEARNED_SOMETHING;
522 } else if (autounlock
523 && !touch_artifact(pick, &gy.youmonst)) {
524 /* note: for !autounlock, apply already did touch check */
525 return PICKLOCK_DID_SOMETHING;
527 switch (picktyp) {
528 case CREDIT_CARD:
529 ch = ACURR(A_DEX) + 20 * Role_if(PM_ROGUE);
530 break;
531 case LOCK_PICK:
532 ch = 4 * ACURR(A_DEX) + 25 * Role_if(PM_ROGUE);
533 break;
534 case SKELETON_KEY:
535 ch = 75 + ACURR(A_DEX);
536 break;
537 default:
538 ch = 0;
540 if (otmp->cursed)
541 ch /= 2;
543 gx.xlock.box = otmp;
544 gx.xlock.door = 0;
545 break;
548 if (c != 'y') {
549 if (!count)
550 There("doesn't seem to be any sort of lock here.");
551 return PICKLOCK_LEARNED_SOMETHING; /* decided against all boxes */
554 /* not the hero's location; pick the lock in an adjacent door */
555 } else {
556 struct monst *mtmp;
558 if (u.utrap && u.utraptype == TT_PIT) {
559 You_cant("reach over the edge of the pit.");
560 /* this used to return PICKLOCK_LEARNED_SOMETHING but the
561 #open command doesn't use a turn for similar situation */
562 return PICKLOCK_DID_NOTHING;
565 door = &levl[cc.x][cc.y];
566 mtmp = m_at(cc.x, cc.y);
567 if (mtmp && canseemon(mtmp) && M_AP_TYPE(mtmp) != M_AP_FURNITURE
568 && M_AP_TYPE(mtmp) != M_AP_OBJECT) {
569 if (picktyp == CREDIT_CARD
570 && (mtmp->isshk || mtmp->data == &mons[PM_ORACLE])) {
571 SetVoice(mtmp, 0, 80, 0);
572 verbalize("No checks, no credit, no problem.");
573 } else {
574 pline("I don't think %s would appreciate that.",
575 mon_nam(mtmp));
577 return PICKLOCK_LEARNED_SOMETHING;
578 } else if (mtmp && is_door_mappear(mtmp)) {
579 /* "The door actually was a <mimic>!" */
580 stumble_onto_mimic(mtmp);
581 /* mimic might keep the key (50% chance, 10% for PYEC or MKoT) */
582 maybe_absorb_item(mtmp, pick, 50, 10);
583 return PICKLOCK_LEARNED_SOMETHING;
585 if (!IS_DOOR(door->typ)) {
586 int res = PICKLOCK_DID_NOTHING, oldglyph = door->glyph;
587 schar oldlastseentyp = update_mapseen_for(cc.x, cc.y);
589 /* this is probably only relevant when blind */
590 feel_location(cc.x, cc.y);
591 if (door->glyph != oldglyph
592 || svl.lastseentyp[cc.x][cc.y] != oldlastseentyp)
593 res = PICKLOCK_LEARNED_SOMETHING;
595 if (is_drawbridge_wall(cc.x, cc.y) >= 0)
596 You("%s no lock on the drawbridge.", Blind ? "feel" : "see");
597 else
598 You("%s no door there.", Blind ? "feel" : "see");
599 return res;
601 switch (door->doormask) {
602 case D_NODOOR:
603 pline("This doorway has no door.");
604 return PICKLOCK_LEARNED_SOMETHING;
605 case D_ISOPEN:
606 You("cannot lock an open door.");
607 return PICKLOCK_LEARNED_SOMETHING;
608 case D_BROKEN:
609 pline("This door is broken.");
610 return PICKLOCK_LEARNED_SOMETHING;
611 default:
612 if ((flags.autounlock & AUTOUNLOCK_UNTRAP) != 0
613 && could_untrap(FALSE, FALSE)
614 && (c = ynq("Check this door for a trap?")) != 'n') {
615 if (c == 'q')
616 return PICKLOCK_DID_NOTHING;
617 /* c == 'y' */
618 untrap(FALSE, cc.x, cc.y, (struct obj *) 0);
619 return PICKLOCK_DID_SOMETHING; /* even if no trap found */
621 /* credit cards are only good for unlocking */
622 if (picktyp == CREDIT_CARD && !(door->doormask & D_LOCKED)) {
623 You_cant("lock a door with a credit card.");
624 return PICKLOCK_LEARNED_SOMETHING;
627 Sprintf(qbuf, "%s it%s%s?",
628 (door->doormask & D_LOCKED) ? "Unlock" : "Lock",
629 autounlock ? " with " : "",
630 autounlock ? yname(pick) : "");
631 c = ynq(qbuf);
632 if (c != 'y')
633 return PICKLOCK_DID_NOTHING;
635 /* note: for !autounlock, 'apply' already did touch check */
636 if (autounlock && !touch_artifact(pick, &gy.youmonst))
637 return PICKLOCK_DID_SOMETHING;
639 switch (picktyp) {
640 case CREDIT_CARD:
641 ch = 2 * ACURR(A_DEX) + 20 * Role_if(PM_ROGUE);
642 break;
643 case LOCK_PICK:
644 ch = 3 * ACURR(A_DEX) + 30 * Role_if(PM_ROGUE);
645 break;
646 case SKELETON_KEY:
647 ch = 70 + ACURR(A_DEX);
648 break;
649 default:
650 ch = 0;
652 gx.xlock.door = door;
653 gx.xlock.box = 0;
656 svc.context.move = 0;
657 gx.xlock.chance = ch;
658 gx.xlock.picktyp = picktyp;
659 gx.xlock.magic_key = is_magic_key(&gy.youmonst, pick);
660 gx.xlock.usedtime = 0;
661 set_occupation(picklock, lock_action(), 0);
662 return PICKLOCK_DID_SOMETHING;
665 /* is hero wielding a weapon that can #force? */
666 boolean
667 u_have_forceable_weapon(void)
669 if (!uwep /* proper type test */
670 || ((uwep->oclass == WEAPON_CLASS || is_weptool(uwep))
671 ? (objects[uwep->otyp].oc_skill < P_DAGGER
672 || objects[uwep->otyp].oc_skill == P_FLAIL
673 || objects[uwep->otyp].oc_skill > P_LANCE)
674 : uwep->oclass != ROCK_CLASS))
675 return FALSE;
676 return TRUE;
679 RESTORE_WARNING_FORMAT_NONLITERAL
681 /* the #force command - try to force a chest with your weapon */
683 doforce(void)
685 struct obj *otmp;
686 int c, picktyp;
687 char qbuf[QBUFSZ];
690 * TODO?
691 * allow force with edged weapon to be performed on doors.
694 if (u.uswallow) {
695 You_cant("force anything from inside here.");
696 return ECMD_OK;
698 if (!u_have_forceable_weapon()) {
699 boolean use_plural = uwep && uwep->quan > 1;
701 You_cant("force anything %s weapon%s.",
702 !uwep ? "when not wielding a"
703 : (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep))
704 ? (use_plural ? "without proper" : "without a proper")
705 : (use_plural ? "with those" : "with that"),
706 use_plural ? "s" : "");
707 return ECMD_OK;
709 if (!can_reach_floor(TRUE)) {
710 cant_reach_floor(u.ux, u.uy, FALSE, TRUE);
711 return ECMD_OK;
714 picktyp = is_blade(uwep) && !is_pick(uwep);
715 if (gx.xlock.usedtime && gx.xlock.box && picktyp == gx.xlock.picktyp) {
716 You("resume your attempt to force the lock.");
717 set_occupation(forcelock, "forcing the lock", 0);
718 return ECMD_TIME;
721 /* A lock is made only for the honest man, the thief will break it. */
722 gx.xlock.box = (struct obj *) 0;
723 for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere)
724 if (Is_box(otmp)) {
725 if (otmp->obroken || !otmp->olocked) {
726 /* force doname() to omit known "broken" or "unlocked"
727 prefix so that the message isn't worded redundantly;
728 since we're about to set lknown, there's no need to
729 remember and then reset its current value */
730 otmp->lknown = 0;
731 There("is %s here, but its lock is already %s.",
732 doname(otmp), otmp->obroken ? "broken" : "unlocked");
733 otmp->lknown = 1;
734 continue;
736 (void) safe_qbuf(qbuf, "There is ", " here; force its lock?",
737 otmp, doname, ansimpleoname, "a box");
738 otmp->lknown = 1;
740 c = ynq(qbuf);
741 if (c == 'q')
742 return ECMD_OK;
743 if (c == 'n')
744 continue;
746 if (picktyp)
747 You("force %s into a crack and pry.", yname(uwep));
748 else
749 You("start bashing it with %s.", yname(uwep));
750 gx.xlock.box = otmp;
751 gx.xlock.chance = objects[uwep->otyp].oc_wldam * 2;
752 gx.xlock.picktyp = picktyp;
753 gx.xlock.magic_key = FALSE;
754 gx.xlock.usedtime = 0;
755 break;
758 if (gx.xlock.box)
759 set_occupation(forcelock, "forcing the lock", 0);
760 else
761 You("decide not to force the issue.");
762 return ECMD_TIME;
765 boolean
766 stumble_on_door_mimic(coordxy x, coordxy y)
768 struct monst *mtmp;
770 if ((mtmp = m_at(x, y)) && is_door_mappear(mtmp)
771 && !Protection_from_shape_changers) {
772 stumble_onto_mimic(mtmp);
773 return TRUE;
775 return FALSE;
778 /* the #open command - try to open a door */
780 doopen(void)
782 return doopen_indir(0, 0);
785 /* try to open a door in direction u.dx/u.dy */
787 doopen_indir(coordxy x, coordxy y)
789 coord cc;
790 struct rm *door;
791 boolean portcullis;
792 const char *dirprompt;
793 int res = ECMD_OK;
795 if (nohands(gy.youmonst.data)) {
796 You_cant("open anything -- you have no hands!");
797 return ECMD_OK;
800 dirprompt = NULL; /* have get_adjacent_loc() -> getdir() use default */
801 if (u.utrap && u.utraptype == TT_PIT && container_at(u.ux, u.uy, FALSE))
802 dirprompt = "Open where? [.>]";
804 if (x > 0 && y >= 0) {
805 /* nonzero <x,y> is used when hero in amorphous form tries to
806 flow under a closed door at <x,y>; the test here was using
807 'y > 0' but that would give incorrect results if doors are
808 ever allowed to be placed on the top row of the map */
809 cc.x = x;
810 cc.y = y;
811 } else if (!get_adjacent_loc(dirprompt, (char *) 0, u.ux, u.uy, &cc)) {
812 return ECMD_OK;
815 /* open at yourself/up/down: switch to loot unless there is a closed
816 door here (possible with Passes_walls) and direction isn't 'down' */
817 if (u_at(cc.x, cc.y) && (u.dz > 0 || !closed_door(u.ux, u.uy)))
818 return doloot();
820 /* this used to be done prior to get_adjacent_loc() but doing so was
821 incorrect once open at hero's spot became an alternate way to loot */
822 if (u.utrap && u.utraptype == TT_PIT) {
823 You_cant("reach over the edge of the pit.");
824 return ECMD_OK;
827 if (stumble_on_door_mimic(cc.x, cc.y))
828 return ECMD_TIME;
830 /* when choosing a direction is impaired, use a turn
831 regardless of whether a door is successfully targeted */
832 if (Confusion || Stunned)
833 res = ECMD_TIME;
835 door = &levl[cc.x][cc.y];
836 portcullis = (is_drawbridge_wall(cc.x, cc.y) >= 0);
837 /* this used to be 'if (Blind)' but using a key skips that so we do too */
839 int oldglyph = door->glyph;
840 schar oldlastseentyp = update_mapseen_for(cc.x, cc.y);
842 newsym(cc.x, cc.y);
843 if (door->glyph != oldglyph
844 || svl.lastseentyp[cc.x][cc.y] != oldlastseentyp)
845 res = ECMD_TIME; /* learned something */
848 if (portcullis || !IS_DOOR(door->typ)) {
849 /* closed portcullis or spot that opened bridge would span */
850 if (is_db_wall(cc.x, cc.y) || door->typ == DRAWBRIDGE_UP)
851 There("is no obvious way to open the drawbridge.");
852 else if (portcullis || door->typ == DRAWBRIDGE_DOWN)
853 pline_The("drawbridge is already open.");
854 else if (container_at(cc.x, cc.y, TRUE))
855 pline("%s like something lootable over there.",
856 Blind ? "Feels" : "Seems");
857 else
858 You("%s no door there.", Blind ? "feel" : "see");
859 return res;
862 if (!(door->doormask & D_CLOSED)) {
863 const char *mesg;
864 boolean locked = FALSE;
866 switch (door->doormask) {
867 case D_BROKEN:
868 mesg = " is broken";
869 break;
870 case D_NODOOR:
871 mesg = "way has no door";
872 break;
873 case D_ISOPEN:
874 mesg = " is already open";
875 break;
876 default:
877 mesg = " is locked";
878 locked = TRUE;
879 break;
881 set_msg_xy(cc.x, cc.y);
882 pline("This door%s.", mesg);
883 if (locked && flags.autounlock) {
884 struct obj *unlocktool;
886 u.dz = 0; /* should already be 0 since hero moved toward door */
887 if ((flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0
888 && (unlocktool = autokey(TRUE)) != 0) {
889 res = pick_lock(unlocktool, cc.x, cc.y,
890 (struct obj *) 0) ? ECMD_TIME : ECMD_OK;
891 } else if ((flags.autounlock & AUTOUNLOCK_KICK) != 0
892 && !u.usteed /* kicking is different when mounted */
893 && ynq("Kick it?") == 'y') {
894 cmdq_add_ec(CQ_CANNED, dokick);
895 cmdq_add_dir(CQ_CANNED,
896 sgn(cc.x - u.ux), sgn(cc.y - u.uy), 0);
897 /* this was 'ECMD_TIME', but time shouldn't elapse until
898 the canned kick takes place */
899 res = ECMD_OK;
902 return res;
905 if (verysmall(gy.youmonst.data)) {
906 pline("You're too small to pull the door open.");
907 return res;
910 /* door is known to be CLOSED */
911 if (rnl(20) < (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 3) {
912 set_msg_xy(cc.x, cc.y);
913 pline_The("door opens.");
914 if (door->doormask & D_TRAPPED) {
915 b_trapped("door", FINGER);
916 door->doormask = D_NODOOR;
917 if (*in_rooms(cc.x, cc.y, SHOPBASE))
918 add_damage(cc.x, cc.y, SHOP_DOOR_COST);
919 } else
920 door->doormask = D_ISOPEN;
921 feel_newsym(cc.x, cc.y); /* the hero knows she opened it */
922 recalc_block_point(cc.x, cc.y); /* vision: new see through there */
923 } else {
924 exercise(A_STR, TRUE);
925 set_msg_xy(cc.x, cc.y);
926 pline_The("door resists!");
929 return ECMD_TIME;
932 staticfn boolean
933 obstructed(coordxy x, coordxy y, boolean quietly)
935 struct monst *mtmp = m_at(x, y);
937 if (mtmp && M_AP_TYPE(mtmp) != M_AP_FURNITURE) {
938 if (M_AP_TYPE(mtmp) == M_AP_OBJECT)
939 goto objhere;
940 if (!quietly) {
941 char *Mn = Some_Monnam(mtmp); /* Monnam, Someone or Something */
943 if ((mtmp->mx != x || mtmp->my != y) && canspotmon(mtmp))
944 /* s_suffix() returns a modifiable buffer */
945 Mn = strcat(s_suffix(Mn), " tail");
947 pline("%s blocks the way!", Mn);
949 if (!canspotmon(mtmp))
950 map_invisible(x, y);
951 return TRUE;
953 if (OBJ_AT(x, y)) {
954 objhere:
955 if (!quietly)
956 pline("%s's in the way.", Something);
957 return TRUE;
959 return FALSE;
962 /* the #close command - try to close a door */
964 doclose(void)
966 coordxy x, y;
967 struct rm *door;
968 boolean portcullis;
969 int res = ECMD_OK;
971 if (nohands(gy.youmonst.data)) {
972 You_cant("close anything -- you have no hands!");
973 return ECMD_OK;
976 if (u.utrap && u.utraptype == TT_PIT) {
977 You_cant("reach over the edge of the pit.");
978 return ECMD_OK;
981 if (!getdir((char *) 0))
982 return ECMD_CANCEL;
984 x = u.ux + u.dx;
985 y = u.uy + u.dy;
986 if (u_at(x, y) && !Passes_walls) {
987 You("are in the way!");
988 return ECMD_TIME;
991 if (!isok(x, y))
992 goto nodoor;
994 if (stumble_on_door_mimic(x, y))
995 return ECMD_TIME;
997 /* when choosing a direction is impaired, use a turn
998 regardless of whether a door is successfully targeted */
999 if (Confusion || Stunned)
1000 res = ECMD_TIME;
1002 door = &levl[x][y];
1003 portcullis = (is_drawbridge_wall(x, y) >= 0);
1004 if (Blind) {
1005 int oldglyph = door->glyph;
1006 schar oldlastseentyp = update_mapseen_for(x, y);
1008 feel_location(x, y);
1009 if (door->glyph != oldglyph
1010 || svl.lastseentyp[x][y] != oldlastseentyp)
1011 res = ECMD_TIME; /* learned something */
1014 if (portcullis || !IS_DOOR(door->typ)) {
1015 /* is_db_wall: closed portcullis */
1016 if (is_db_wall(x, y) || door->typ == DRAWBRIDGE_UP)
1017 pline_The("drawbridge is already closed.");
1018 else if (portcullis || door->typ == DRAWBRIDGE_DOWN)
1019 There("is no obvious way to close the drawbridge.");
1020 else {
1021 nodoor:
1022 You("%s no door there.", Blind ? "feel" : "see");
1024 return res;
1027 if (door->doormask == D_NODOOR) {
1028 pline("This doorway has no door.");
1029 return res;
1030 } else if (obstructed(x, y, FALSE)) {
1031 return res;
1032 } else if (door->doormask == D_BROKEN) {
1033 pline("This door is broken.");
1034 return res;
1035 } else if (door->doormask & (D_CLOSED | D_LOCKED)) {
1036 pline("This door is already closed.");
1037 return res;
1040 if (door->doormask == D_ISOPEN) {
1041 if (verysmall(gy.youmonst.data) && !u.usteed) {
1042 pline("You're too small to push the door closed.");
1043 return res;
1045 if (u.usteed
1046 || rn2(25) < (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 3) {
1047 pline_The("door closes.");
1048 door->doormask = D_CLOSED;
1049 feel_newsym(x, y); /* the hero knows she closed it */
1050 block_point(x, y); /* vision: no longer see there */
1051 } else {
1052 exercise(A_STR, TRUE);
1053 pline_The("door resists!");
1057 return ECMD_TIME;
1060 /* box obj was hit with spell or wand effect otmp;
1061 returns true if something happened */
1062 boolean
1063 boxlock(struct obj *obj, struct obj *otmp) /* obj *is* a box */
1065 boolean res = 0;
1067 switch (otmp->otyp) {
1068 case WAN_LOCKING:
1069 case SPE_WIZARD_LOCK:
1070 if (!obj->olocked) { /* lock it; fix if broken */
1071 Soundeffect(se_klunk, 50);
1072 pline("Klunk!");
1073 obj->olocked = 1;
1074 obj->obroken = 0;
1075 if (Role_if(PM_WIZARD))
1076 obj->lknown = 1;
1077 else
1078 obj->lknown = 0;
1079 res = 1;
1080 } /* else already closed and locked */
1081 break;
1082 case WAN_OPENING:
1083 case SPE_KNOCK:
1084 if (obj->olocked) { /* unlock; isn't broken so doesn't need fixing */
1085 Soundeffect(se_klick, 50);
1086 pline("Klick!");
1087 obj->olocked = 0;
1088 res = 1;
1089 if (Role_if(PM_WIZARD))
1090 obj->lknown = 1;
1091 else
1092 obj->lknown = 0;
1093 } else /* silently fix if broken */
1094 obj->obroken = 0;
1095 break;
1096 case WAN_POLYMORPH:
1097 case SPE_POLYMORPH:
1098 /* maybe start unlocking chest, get interrupted, then zap it;
1099 we must avoid any attempt to resume unlocking it */
1100 if (gx.xlock.box == obj)
1101 reset_pick();
1102 break;
1104 return res;
1107 /* Door/secret door was hit with spell or wand effect otmp;
1108 returns true if something happened */
1109 boolean
1110 doorlock(struct obj *otmp, coordxy x, coordxy y)
1112 struct rm *door = &levl[x][y];
1113 boolean res = TRUE;
1114 int loudness = 0;
1115 const char *msg = (const char *) 0;
1116 const char *dustcloud = "A cloud of dust";
1117 const char *quickly_dissipates = "quickly dissipates";
1118 boolean mysterywand = (otmp->oclass == WAND_CLASS && !otmp->dknown);
1120 if (door->typ == SDOOR) {
1121 switch (otmp->otyp) {
1122 case WAN_OPENING:
1123 case SPE_KNOCK:
1124 case WAN_STRIKING:
1125 case SPE_FORCE_BOLT:
1126 door->typ = DOOR;
1127 door->doormask = D_CLOSED | (door->doormask & D_TRAPPED);
1128 newsym(x, y);
1129 if (cansee(x, y))
1130 pline("A door appears in the wall!");
1131 if (otmp->otyp == WAN_OPENING || otmp->otyp == SPE_KNOCK)
1132 return TRUE;
1133 break; /* striking: continue door handling below */
1134 case WAN_LOCKING:
1135 case SPE_WIZARD_LOCK:
1136 default:
1137 return FALSE;
1141 switch (otmp->otyp) {
1142 case WAN_LOCKING:
1143 case SPE_WIZARD_LOCK:
1144 if (Is_rogue_level(&u.uz)) {
1145 boolean vis = cansee(x, y);
1147 /* Can't have real locking in Rogue, so just hide doorway */
1148 if (vis) {
1149 pline("%s springs up in the older, more primitive doorway.",
1150 dustcloud);
1151 } else {
1152 Soundeffect(se_swoosh, 25);
1153 You_hear("a swoosh.");
1155 if (obstructed(x, y, mysterywand)) {
1156 if (vis)
1157 pline_The("cloud %s.", quickly_dissipates);
1158 return FALSE;
1160 block_point(x, y);
1161 door->typ = SDOOR, door->doormask = D_NODOOR;
1162 if (vis)
1163 pline_The("doorway vanishes!");
1164 newsym(x, y);
1165 return TRUE;
1167 if (obstructed(x, y, mysterywand))
1168 return FALSE;
1169 /* Don't allow doors to close over traps. This is for pits */
1170 /* & trap doors, but is it ever OK for anything else? */
1171 if (t_at(x, y)) {
1172 /* maketrap() clears doormask, so it should be NODOOR */
1173 pline("%s springs up in the doorway, but %s.", dustcloud,
1174 quickly_dissipates);
1175 return FALSE;
1178 switch (door->doormask & ~D_TRAPPED) {
1179 case D_CLOSED:
1180 msg = "The door locks!";
1181 break;
1182 case D_ISOPEN:
1183 msg = "The door swings shut, and locks!";
1184 break;
1185 case D_BROKEN:
1186 msg = "The broken door reassembles and locks!";
1187 break;
1188 case D_NODOOR:
1189 msg =
1190 "A cloud of dust springs up and assembles itself into a door!";
1191 break;
1192 default:
1193 res = FALSE;
1194 break;
1196 block_point(x, y);
1197 door->doormask = D_LOCKED | (door->doormask & D_TRAPPED);
1198 newsym(x, y);
1199 break;
1200 case WAN_OPENING:
1201 case SPE_KNOCK:
1202 if (door->doormask & D_LOCKED) {
1203 msg = "The door unlocks!";
1204 door->doormask = D_CLOSED | (door->doormask & D_TRAPPED);
1205 } else
1206 res = FALSE;
1207 break;
1208 case WAN_STRIKING:
1209 case SPE_FORCE_BOLT:
1210 if (door->doormask & (D_LOCKED | D_CLOSED)) {
1211 /* sawit: closed door location is more visible than open */
1212 boolean sawit, seeit;
1214 if (door->doormask & D_TRAPPED) {
1215 struct monst *mtmp = m_at(x, y);
1217 sawit = mtmp ? canseemon(mtmp) : cansee(x, y);
1218 door->doormask = D_NODOOR;
1219 unblock_point(x, y);
1220 newsym(x, y);
1221 seeit = mtmp ? canseemon(mtmp) : cansee(x, y);
1222 if (mtmp) {
1223 (void) mb_trapped(mtmp, sawit || seeit);
1224 } else {
1225 /* for mtmp, mb_trapped() does is own wake_nearto() */
1226 loudness = 40;
1227 if (flags.verbose) {
1228 Soundeffect(se_kaboom_door_explodes, 75);
1229 if ((sawit || seeit) && !Unaware) {
1230 pline("KABOOM!! You see a door explode.");
1231 } else if (!Deaf) {
1232 Soundeffect(se_explosion, 75);
1233 You_hear("a %s explosion.",
1234 (distu(x, y) > 7 * 7) ? "distant"
1235 : "nearby");
1239 break;
1241 sawit = cansee(x, y);
1242 door->doormask = D_BROKEN;
1243 recalc_block_point(x, y);
1244 seeit = cansee(x, y);
1245 newsym(x, y);
1246 if (flags.verbose) {
1247 if ((sawit || seeit) && !Unaware) {
1248 pline_The("door crashes open!");
1249 } else if (!Deaf) {
1250 Soundeffect(se_crashing_sound, 100);
1251 You_hear("a crashing sound.");
1254 /* force vision recalc before printing more messages */
1255 if (gv.vision_full_recalc)
1256 vision_recalc(0);
1257 loudness = 20;
1258 } else
1259 res = FALSE;
1260 break;
1261 default:
1262 impossible("magic (%d) attempted on door.", otmp->otyp);
1263 break;
1265 if (msg && cansee(x, y))
1266 pline1(msg);
1267 if (loudness > 0) {
1268 /* door was destroyed */
1269 wake_nearto(x, y, loudness);
1270 if (*in_rooms(x, y, SHOPBASE))
1271 add_damage(x, y, 0L);
1274 if (res && picking_at(x, y)) {
1275 /* maybe unseen monster zaps door you're unlocking */
1276 stop_occupation();
1277 reset_pick();
1279 return res;
1282 staticfn void
1283 chest_shatter_msg(struct obj *otmp)
1285 const char *disposition;
1286 const char *thing;
1287 long save_HBlinded, save_BBlinded;
1289 if (otmp->oclass == POTION_CLASS) {
1290 You("%s %s shatter!", Blind ? "hear" : "see", an(bottlename()));
1291 if (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data))
1292 potionbreathe(otmp);
1293 return;
1295 /* We have functions for distant and singular names, but not one */
1296 /* which does _both_... */
1297 save_HBlinded = HBlinded, save_BBlinded = BBlinded;
1298 HBlinded = 1L, BBlinded = 0L;
1299 thing = singular(otmp, xname);
1300 HBlinded = save_HBlinded, BBlinded = save_BBlinded;
1301 switch (objects[otmp->otyp].oc_material) {
1302 case PAPER:
1303 disposition = "is torn to shreds";
1304 break;
1305 case WAX:
1306 disposition = "is crushed";
1307 break;
1308 case VEGGY:
1309 disposition = "is pulped";
1310 break;
1311 case FLESH:
1312 disposition = "is mashed";
1313 break;
1314 case GLASS:
1315 disposition = "shatters";
1316 break;
1317 case WOOD:
1318 disposition = "splinters to fragments";
1319 break;
1320 default:
1321 disposition = "is destroyed";
1322 break;
1324 pline("%s %s!", An(thing), disposition);
1327 /*lock.c*/