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. */
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
*);
17 picking_lock(coordxy
*x
, coordxy
*y
)
19 if (go
.occupation
== picklock
) {
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 */
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];
66 /* try to open/close a lock */
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 */
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
) {
81 pline("This doorway has no door.");
82 return ((gx
.xlock
.usedtime
= 0));
84 You("cannot lock an open door.");
85 return ((gx
.xlock
.usedtime
= 0));
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
)
111 gx
.xlock
.box
->tknown
= 1;
113 if (y_n("Do you want to try to disarm it?") == 'y') {
115 boolean alreadyunlocked
;
117 /* disarming while using magic key always succeeds */
119 gx
.xlock
.door
->doormask
&= ~D_TRAPPED
;
121 alreadyunlocked
= !(gx
.xlock
.door
->doormask
& D_LOCKED
);
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
);
132 You("stop %s.", lock_action());
133 exercise(A_WIS
, FALSE
);
135 return ((gx
.xlock
.usedtime
= 0));
138 You("succeed in %s.", lock_action());
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
;
150 gx
.xlock
.door
->doormask
= D_LOCKED
;
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));
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
;
168 costly_alteration(box
, COST_BRKLCK
);
169 box
->cobj
= hide_contents
;
173 } else { /* #force has destroyed this box (at <u.ux,u.uy>) */
175 struct monst
*shkp
= (*u
.ushops
&& costly_spot(u
.ux
, u
.uy
))
176 ? shop_keeper(*u
.ushops
)
178 boolean costly
= (boolean
) (shkp
!= 0),
179 peaceful_shk
= costly
&& (boolean
) shkp
->mpeaceful
;
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
);
189 loss
+= stolen_value(otmp
, u
.ux
, u
.uy
, peaceful_shk
,
191 if (otmp
->quan
== 1L) {
192 obfree(otmp
, (struct obj
*) 0);
195 /* this works because we're sure to have at least 1 left;
196 otherwise it would fail since otmp is not in inventory */
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
);
207 loss
+= stolen_value(box
, u
.ux
, u
.uy
, peaceful_shk
, TRUE
);
209 You("owe %ld %s for objects destroyed.", loss
, currency(loss
));
214 /* try to force a locked chest */
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",
237 You("give up your attempt to force the lock.");
238 exercise(A_DEX
, TRUE
);
239 return ((gx
.xlock
.usedtime
= 0));
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 */
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 */
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
)))
287 /* pick a tool for autounlock */
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
)) {
318 if (!key
|| is_magic_key(&gy
.youmonst
, o
))
336 /* only resort to other role's quest artifact if no other choice */
337 if (!key
&& !pick
&& !card
)
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 */
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
;
370 boolean autounlock
= (rx
!= 0 || container
!= NULL
);
372 /* 'pick' might be Null [called by do_loot_cont() for AUTOUNLOCK_UNTRAP] */
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
)
388 pline(no_longer
, "hold the", what
);
390 return PICKLOCK_LEARNED_SOMETHING
;
391 } else if (u
.uswallow
|| (gx
.xlock
.box
&& !can_reach_floor(TRUE
))) {
392 pline(no_longer
, "reach the", "lock");
394 return PICKLOCK_LEARNED_SOMETHING
;
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 ",
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 */
424 } else if (!get_adjacent_loc((char *) 0, "Invalid location!",
426 return PICKLOCK_DID_NOTHING
;
429 if (u_at(cc
.x
, cc
.y
)) { /* pick lock on a container */
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
;
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
)
465 if (!can_reach_floor(TRUE
)) {
466 You_cant("reach %s from up here.", the(xname(otmp
)));
467 return PICKLOCK_LEARNED_SOMETHING
;
472 else if (!otmp
->olocked
)
473 verb
= "lock", it
= 1;
474 else if (picktyp
!= LOCK_PICK
)
475 verb
= "unlock", it
= 1;
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")))
485 return PICKLOCK_DID_NOTHING
; /* c == 'q' */
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) {
492 if (pick
!= &dummypick
) {
493 Sprintf(qbuf
, "Unlock it with %s?", yname(pick
));
497 return PICKLOCK_DID_NOTHING
;
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");
508 return PICKLOCK_DID_NOTHING
;
510 continue; /* try next box */
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
;
529 ch
= ACURR(A_DEX
) + 20 * Role_if(PM_ROGUE
);
532 ch
= 4 * ACURR(A_DEX
) + 25 * Role_if(PM_ROGUE
);
535 ch
= 75 + ACURR(A_DEX
);
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 */
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.");
574 pline("I don't think %s would appreciate that.",
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");
598 You("%s no door there.", Blind
? "feel" : "see");
601 switch (door
->doormask
) {
603 pline("This doorway has no door.");
604 return PICKLOCK_LEARNED_SOMETHING
;
606 You("cannot lock an open door.");
607 return PICKLOCK_LEARNED_SOMETHING
;
609 pline("This door is broken.");
610 return PICKLOCK_LEARNED_SOMETHING
;
612 if ((flags
.autounlock
& AUTOUNLOCK_UNTRAP
) != 0
613 && could_untrap(FALSE
, FALSE
)
614 && (c
= ynq("Check this door for a trap?")) != 'n') {
616 return PICKLOCK_DID_NOTHING
;
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
) : "");
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
;
641 ch
= 2 * ACURR(A_DEX
) + 20 * Role_if(PM_ROGUE
);
644 ch
= 3 * ACURR(A_DEX
) + 30 * Role_if(PM_ROGUE
);
647 ch
= 70 + ACURR(A_DEX
);
652 gx
.xlock
.door
= door
;
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? */
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
))
679 RESTORE_WARNING_FORMAT_NONLITERAL
681 /* the #force command - try to force a chest with your weapon */
691 * allow force with edged weapon to be performed on doors.
695 You_cant("force anything from inside here.");
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" : "");
709 if (!can_reach_floor(TRUE
)) {
710 cant_reach_floor(u
.ux
, u
.uy
, FALSE
, TRUE
);
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);
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
)
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 */
731 There("is %s here, but its lock is already %s.",
732 doname(otmp
), otmp
->obroken
? "broken" : "unlocked");
736 (void) safe_qbuf(qbuf
, "There is ", " here; force its lock?",
737 otmp
, doname
, ansimpleoname
, "a box");
747 You("force %s into a crack and pry.", yname(uwep
));
749 You("start bashing it with %s.", yname(uwep
));
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;
759 set_occupation(forcelock
, "forcing the lock", 0);
761 You("decide not to force the issue.");
766 stumble_on_door_mimic(coordxy x
, coordxy y
)
770 if ((mtmp
= m_at(x
, y
)) && is_door_mappear(mtmp
)
771 && !Protection_from_shape_changers
) {
772 stumble_onto_mimic(mtmp
);
778 /* the #open command - try to open a door */
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
)
792 const char *dirprompt
;
795 if (nohands(gy
.youmonst
.data
)) {
796 You_cant("open anything -- you have no hands!");
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 */
811 } else if (!get_adjacent_loc(dirprompt
, (char *) 0, u
.ux
, u
.uy
, &cc
)) {
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
)))
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.");
827 if (stumble_on_door_mimic(cc
.x
, cc
.y
))
830 /* when choosing a direction is impaired, use a turn
831 regardless of whether a door is successfully targeted */
832 if (Confusion
|| Stunned
)
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
);
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");
858 You("%s no door there.", Blind
? "feel" : "see");
862 if (!(door
->doormask
& D_CLOSED
)) {
864 boolean locked
= FALSE
;
866 switch (door
->doormask
) {
871 mesg
= "way has no door";
874 mesg
= " is already open";
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 */
905 if (verysmall(gy
.youmonst
.data
)) {
906 pline("You're too small to pull the door open.");
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
);
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 */
924 exercise(A_STR
, TRUE
);
925 set_msg_xy(cc
.x
, cc
.y
);
926 pline_The("door resists!");
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
)
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
))
956 pline("%s's in the way.", Something
);
962 /* the #close command - try to close a door */
971 if (nohands(gy
.youmonst
.data
)) {
972 You_cant("close anything -- you have no hands!");
976 if (u
.utrap
&& u
.utraptype
== TT_PIT
) {
977 You_cant("reach over the edge of the pit.");
981 if (!getdir((char *) 0))
986 if (u_at(x
, y
) && !Passes_walls
) {
987 You("are in the way!");
994 if (stumble_on_door_mimic(x
, y
))
997 /* when choosing a direction is impaired, use a turn
998 regardless of whether a door is successfully targeted */
999 if (Confusion
|| Stunned
)
1003 portcullis
= (is_drawbridge_wall(x
, y
) >= 0);
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.");
1022 You("%s no door there.", Blind
? "feel" : "see");
1027 if (door
->doormask
== D_NODOOR
) {
1028 pline("This doorway has no door.");
1030 } else if (obstructed(x
, y
, FALSE
)) {
1032 } else if (door
->doormask
== D_BROKEN
) {
1033 pline("This door is broken.");
1035 } else if (door
->doormask
& (D_CLOSED
| D_LOCKED
)) {
1036 pline("This door is already closed.");
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.");
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 */
1052 exercise(A_STR
, TRUE
);
1053 pline_The("door resists!");
1060 /* box obj was hit with spell or wand effect otmp;
1061 returns true if something happened */
1063 boxlock(struct obj
*obj
, struct obj
*otmp
) /* obj *is* a box */
1067 switch (otmp
->otyp
) {
1069 case SPE_WIZARD_LOCK
:
1070 if (!obj
->olocked
) { /* lock it; fix if broken */
1071 Soundeffect(se_klunk
, 50);
1075 if (Role_if(PM_WIZARD
))
1080 } /* else already closed and locked */
1084 if (obj
->olocked
) { /* unlock; isn't broken so doesn't need fixing */
1085 Soundeffect(se_klick
, 50);
1089 if (Role_if(PM_WIZARD
))
1093 } else /* silently fix if broken */
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
)
1107 /* Door/secret door was hit with spell or wand effect otmp;
1108 returns true if something happened */
1110 doorlock(struct obj
*otmp
, coordxy x
, coordxy y
)
1112 struct rm
*door
= &levl
[x
][y
];
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
) {
1125 case SPE_FORCE_BOLT
:
1127 door
->doormask
= D_CLOSED
| (door
->doormask
& D_TRAPPED
);
1130 pline("A door appears in the wall!");
1131 if (otmp
->otyp
== WAN_OPENING
|| otmp
->otyp
== SPE_KNOCK
)
1133 break; /* striking: continue door handling below */
1135 case SPE_WIZARD_LOCK
:
1141 switch (otmp
->otyp
) {
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 */
1149 pline("%s springs up in the older, more primitive doorway.",
1152 Soundeffect(se_swoosh
, 25);
1153 You_hear("a swoosh.");
1155 if (obstructed(x
, y
, mysterywand
)) {
1157 pline_The("cloud %s.", quickly_dissipates
);
1161 door
->typ
= SDOOR
, door
->doormask
= D_NODOOR
;
1163 pline_The("doorway vanishes!");
1167 if (obstructed(x
, y
, mysterywand
))
1169 /* Don't allow doors to close over traps. This is for pits */
1170 /* & trap doors, but is it ever OK for anything else? */
1172 /* maketrap() clears doormask, so it should be NODOOR */
1173 pline("%s springs up in the doorway, but %s.", dustcloud
,
1174 quickly_dissipates
);
1178 switch (door
->doormask
& ~D_TRAPPED
) {
1180 msg
= "The door locks!";
1183 msg
= "The door swings shut, and locks!";
1186 msg
= "The broken door reassembles and locks!";
1190 "A cloud of dust springs up and assembles itself into a door!";
1197 door
->doormask
= D_LOCKED
| (door
->doormask
& D_TRAPPED
);
1202 if (door
->doormask
& D_LOCKED
) {
1203 msg
= "The door unlocks!";
1204 door
->doormask
= D_CLOSED
| (door
->doormask
& D_TRAPPED
);
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
);
1221 seeit
= mtmp
? canseemon(mtmp
) : cansee(x
, y
);
1223 (void) mb_trapped(mtmp
, sawit
|| seeit
);
1225 /* for mtmp, mb_trapped() does is own wake_nearto() */
1227 if (flags
.verbose
) {
1228 Soundeffect(se_kaboom_door_explodes
, 75);
1229 if ((sawit
|| seeit
) && !Unaware
) {
1230 pline("KABOOM!! You see a door explode.");
1232 Soundeffect(se_explosion
, 75);
1233 You_hear("a %s explosion.",
1234 (distu(x
, y
) > 7 * 7) ? "distant"
1241 sawit
= cansee(x
, y
);
1242 door
->doormask
= D_BROKEN
;
1243 recalc_block_point(x
, y
);
1244 seeit
= cansee(x
, y
);
1246 if (flags
.verbose
) {
1247 if ((sawit
|| seeit
) && !Unaware
) {
1248 pline_The("door crashes open!");
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
)
1262 impossible("magic (%d) attempted on door.", otmp
->otyp
);
1265 if (msg
&& cansee(x
, y
))
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 */
1283 chest_shatter_msg(struct obj
*otmp
)
1285 const char *disposition
;
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
);
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
) {
1303 disposition
= "is torn to shreds";
1306 disposition
= "is crushed";
1309 disposition
= "is pulped";
1312 disposition
= "is mashed";
1315 disposition
= "shatters";
1318 disposition
= "splinters to fragments";
1321 disposition
= "is destroyed";
1324 pline("%s %s!", An(thing
), disposition
);