/* NetHack 3.7 lock.c $NHDT-Date: 1741793439 2025/03/12 07:30:39 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.145 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ #include "hack.h" /* occupation callbacks */ staticfn int picklock(void); staticfn int forcelock(void); staticfn const char *lock_action(void); staticfn boolean obstructed(coordxy, coordxy, boolean); staticfn void chest_shatter_msg(struct obj *); boolean picking_lock(coordxy *x, coordxy *y) { if (go.occupation == picklock) { *x = u.ux + u.dx; *y = u.uy + u.dy; return TRUE; } else { *x = *y = 0; return FALSE; } } boolean picking_at(coordxy x, coordxy y) { return (boolean) (go.occupation == picklock && gx.xlock.door == &levl[x][y]); } /* produce an occupation string appropriate for the current activity */ staticfn const char * lock_action(void) { /* "unlocking"+2 == "locking" */ static const char *const actions[] = { "unlocking the door", /* [0] */ "unlocking the chest", /* [1] */ "unlocking the box", /* [2] */ "picking the lock" /* [3] */ }; /* if the target is currently unlocked, we're trying to lock it now */ if (gx.xlock.door && !(gx.xlock.door->doormask & D_LOCKED)) return actions[0] + 2; /* "locking the door" */ else if (gx.xlock.box && !gx.xlock.box->olocked) return gx.xlock.box->otyp == CHEST ? actions[1] + 2 : actions[2] + 2; /* otherwise we're trying to unlock it */ else if (gx.xlock.picktyp == LOCK_PICK) return actions[3]; /* "picking the lock" */ else if (gx.xlock.picktyp == CREDIT_CARD) return actions[3]; /* same as lock_pick */ else if (gx.xlock.door) return actions[0]; /* "unlocking the door" */ else if (gx.xlock.box) return gx.xlock.box->otyp == CHEST ? actions[1] : actions[2]; else return actions[3]; } /* try to open/close a lock */ staticfn int picklock(void) { if (gx.xlock.box) { if (gx.xlock.box->where != OBJ_FLOOR || gx.xlock.box->ox != u.ux || gx.xlock.box->oy != u.uy) { return ((gx.xlock.usedtime = 0)); /* you or it moved */ } } else { /* door */ if (gx.xlock.door != &(levl[u.ux + u.dx][u.uy + u.dy])) { return ((gx.xlock.usedtime = 0)); /* you moved */ } switch (gx.xlock.door->doormask) { case D_NODOOR: pline("This doorway has no door."); return ((gx.xlock.usedtime = 0)); case D_ISOPEN: You("cannot lock an open door."); return ((gx.xlock.usedtime = 0)); case D_BROKEN: pline("This door is broken."); return ((gx.xlock.usedtime = 0)); } } if (gx.xlock.usedtime++ >= 50 || nohands(gy.youmonst.data)) { You("give up your attempt at %s.", lock_action()); exercise(A_DEX, TRUE); /* even if you don't succeed */ return ((gx.xlock.usedtime = 0)); } if (rn2(100) >= gx.xlock.chance) return 1; /* still busy */ /* using the Master Key of Thievery finds traps if its bless/curse state is adequate (non-cursed for rogues, blessed for others; checked when setting up 'xlock') */ if ((!gx.xlock.door ? (int) gx.xlock.box->otrapped : (gx.xlock.door->doormask & D_TRAPPED) != 0) && gx.xlock.magic_key) { gx.xlock.chance += 20; /* less effort needed next time */ if (!gx.xlock.door) { if (!gx.xlock.box->tknown) You("find a trap!"); gx.xlock.box->tknown = 1; } if (y_n("Do you want to try to disarm it?") == 'y') { const char *what; boolean alreadyunlocked; /* disarming while using magic key always succeeds */ if (gx.xlock.door) { gx.xlock.door->doormask &= ~D_TRAPPED; what = "door"; alreadyunlocked = !(gx.xlock.door->doormask & D_LOCKED); } else { gx.xlock.box->otrapped = 0; gx.xlock.box->tknown = 0; what = (gx.xlock.box->otyp == CHEST) ? "chest" : "box"; alreadyunlocked = !gx.xlock.box->olocked; } You("succeed in disarming the trap. The %s is still %slocked.", what, alreadyunlocked ? "un" : ""); exercise(A_WIS, TRUE); } else { You("stop %s.", lock_action()); exercise(A_WIS, FALSE); } return ((gx.xlock.usedtime = 0)); } You("succeed in %s.", lock_action()); if (gx.xlock.door) { if (gx.xlock.door->doormask & D_TRAPPED) { b_trapped("door", FINGER); gx.xlock.door->doormask = D_NODOOR; unblock_point(u.ux + u.dx, u.uy + u.dy); if (*in_rooms(u.ux + u.dx, u.uy + u.dy, SHOPBASE)) add_damage(u.ux + u.dx, u.uy + u.dy, SHOP_DOOR_COST); newsym(u.ux + u.dx, u.uy + u.dy); } else if (gx.xlock.door->doormask & D_LOCKED) gx.xlock.door->doormask = D_CLOSED; else gx.xlock.door->doormask = D_LOCKED; } else { gx.xlock.box->olocked = !gx.xlock.box->olocked; gx.xlock.box->lknown = 1; if (gx.xlock.box->otrapped) (void) chest_trap(gx.xlock.box, FINGER, FALSE); } exercise(A_DEX, TRUE); return ((gx.xlock.usedtime = 0)); } void breakchestlock(struct obj *box, boolean destroyit) { if (!destroyit) { /* bill for the box but not for its contents */ struct obj *hide_contents = box->cobj; box->cobj = 0; costly_alteration(box, COST_BRKLCK); box->cobj = hide_contents; box->olocked = 0; box->obroken = 1; box->lknown = 1; } else { /* #force has destroyed this box (at ) */ struct obj *otmp; struct monst *shkp = (*u.ushops && costly_spot(u.ux, u.uy)) ? shop_keeper(*u.ushops) : 0; boolean costly = (boolean) (shkp != 0), peaceful_shk = costly && (boolean) shkp->mpeaceful; long loss = 0L; pline("In fact, you've totally destroyed %s.", the(xname(box))); /* Put the contents on ground at the hero's feet. */ while ((otmp = box->cobj) != 0) { obj_extract_self(otmp); if (!rn2(3) || otmp->oclass == POTION_CLASS) { chest_shatter_msg(otmp); if (costly) loss += stolen_value(otmp, u.ux, u.uy, peaceful_shk, TRUE); if (otmp->quan == 1L) { obfree(otmp, (struct obj *) 0); continue; } /* this works because we're sure to have at least 1 left; otherwise it would fail since otmp is not in inventory */ useup(otmp); } if (box->otyp == ICE_BOX && otmp->otyp == CORPSE) { otmp->age = svm.moves - otmp->age; /* actual age */ start_corpse_timeout(otmp); } place_object(otmp, u.ux, u.uy); stackobj(otmp); } if (costly) loss += stolen_value(box, u.ux, u.uy, peaceful_shk, TRUE); if (loss) You("owe %ld %s for objects destroyed.", loss, currency(loss)); delobj(box); } } /* try to force a locked chest */ staticfn int forcelock(void) { if ((gx.xlock.box->ox != u.ux) || (gx.xlock.box->oy != u.uy)) return ((gx.xlock.usedtime = 0)); /* you or it moved */ if (gx.xlock.usedtime++ >= 50 || !uwep || nohands(gy.youmonst.data)) { You("give up your attempt to force the lock."); if (gx.xlock.usedtime >= 50) /* you made the effort */ exercise((gx.xlock.picktyp) ? A_DEX : A_STR, TRUE); return ((gx.xlock.usedtime = 0)); } if (gx.xlock.picktyp) { /* blade */ if (rn2(1000 - (int) uwep->spe) > (992 - greatest_erosion(uwep) * 10) && !uwep->cursed && !obj_resists(uwep, 0, 99)) { /* for a +0 weapon, probability that it survives an unsuccessful * attempt to force the lock is (.992)^50 = .67 */ pline("%sour %s broke!", (uwep->quan > 1L) ? "One of y" : "Y", xname(uwep)); useup(uwep); You("give up your attempt to force the lock."); exercise(A_DEX, TRUE); return ((gx.xlock.usedtime = 0)); } } else /* blunt */ wake_nearby(FALSE); /* due to hammering on the container */ if (rn2(100) >= gx.xlock.chance) return 1; /* still busy */ You("succeed in forcing the lock."); exercise(gx.xlock.picktyp ? A_DEX : A_STR, TRUE); /* breakchestlock() might destroy xlock.box; if so, xlock context will be cleared (delobj -> obfree -> maybe_reset_pick); but it might not, so explicitly clear that manually */ breakchestlock(gx.xlock.box, (boolean) (!gx.xlock.picktyp && !rn2(3))); reset_pick(); /* lock-picking context is no longer valid */ return 0; } void reset_pick(void) { gx.xlock.usedtime = gx.xlock.chance = gx.xlock.picktyp = 0; gx.xlock.magic_key = FALSE; gx.xlock.door = (struct rm *) 0; gx.xlock.box = (struct obj *) 0; } /* level change or object deletion; context may no longer be valid */ void maybe_reset_pick(struct obj *container) /* passed from obfree() */ { /* * If a specific container, only clear context if it is for that * particular container (which is being deleted). Other stuff on * the current dungeon level remains valid. * However if 'container' is Null, clear context if not carrying * gx.xlock.box (which might be Null if context is for a door). * Used for changing levels, where a floor container or a door is * being left behind and won't be valid on the new level but a * carried container will still be. There might not be any context, * in which case redundantly clearing it is harmless. */ if (container ? (container == gx.xlock.box) : (!gx.xlock.box || !carried(gx.xlock.box))) reset_pick(); } /* pick a tool for autounlock */ struct obj * autokey(boolean opening) /* True: key, pick, or card; False: key or pick */ { struct obj *o, *key, *pick, *card, *akey, *apick, *acard; /* mundane item or regular artifact or own role's quest artifact */ key = pick = card = (struct obj *) 0; /* other role's quest artifact (Rogue's Key or Tourist's Credit Card) */ akey = apick = acard = (struct obj *) 0; for (o = gi.invent; o; o = o->nobj) { if (any_quest_artifact(o) && !is_quest_artifact(o)) { switch (o->otyp) { case SKELETON_KEY: if (!akey) akey = o; break; case LOCK_PICK: if (!apick) apick = o; break; case CREDIT_CARD: if (!acard) acard = o; break; default: break; } } else { switch (o->otyp) { case SKELETON_KEY: if (!key || is_magic_key(&gy.youmonst, o)) key = o; break; case LOCK_PICK: if (!pick) pick = o; break; case CREDIT_CARD: if (!card) card = o; break; default: break; } } } if (!opening) card = acard = 0; /* only resort to other role's quest artifact if no other choice */ if (!key && !pick && !card) key = akey; if (!pick && !card) pick = apick; if (!card) card = acard; return key ? key : pick ? pick : card ? card : 0; } DISABLE_WARNING_FORMAT_NONLITERAL /* for doapply(); if player gives a direction or resumes an interrupted previous attempt then it usually costs hero a move even if nothing ultimately happens; when told "can't do that" before being asked for direction or player cancels with ESC while giving direction, it doesn't */ #define PICKLOCK_LEARNED_SOMETHING (-1) /* time passes */ #define PICKLOCK_DID_NOTHING 0 /* no time passes */ #define PICKLOCK_DID_SOMETHING 1 /* player is applying a key, lock pick, or credit card */ int pick_lock( struct obj *pick, coordxy rx, coordxy ry, /* coordinates of door/container, for autounlock: * doesn't prompt for direction if these are set */ struct obj *container) /* container, for autounlock */ { struct obj dummypick; int picktyp, c, ch; coord cc; struct rm *door; struct obj *otmp; char qbuf[QBUFSZ]; boolean autounlock = (rx != 0 || container != NULL); /* 'pick' might be Null [called by do_loot_cont() for AUTOUNLOCK_UNTRAP] */ if (!pick) { dummypick = cg.zeroobj; pick = &dummypick; /* pick->otyp will be STRANGE_OBJECT */ } picktyp = pick->otyp; /* check whether we're resuming an interrupted previous attempt */ if (gx.xlock.usedtime && picktyp == gx.xlock.picktyp) { static char no_longer[] = "Unfortunately, you can no longer %s %s."; if (nohands(gy.youmonst.data)) { const char *what = (picktyp == LOCK_PICK) ? "pick" : "key"; if (picktyp == CREDIT_CARD) what = "card"; pline(no_longer, "hold the", what); reset_pick(); return PICKLOCK_LEARNED_SOMETHING; } else if (u.uswallow || (gx.xlock.box && !can_reach_floor(TRUE))) { pline(no_longer, "reach the", "lock"); reset_pick(); return PICKLOCK_LEARNED_SOMETHING; } else { const char *action = lock_action(); You("resume your attempt at %s.", action); gx.xlock.magic_key = is_magic_key(&gy.youmonst, pick); set_occupation(picklock, action, 0); return PICKLOCK_DID_SOMETHING; } } if (nohands(gy.youmonst.data)) { You_cant("hold %s -- you have no hands!", doname(pick)); return PICKLOCK_DID_NOTHING; } else if (u.uswallow) { You_cant("%sunlock %s.", (picktyp == CREDIT_CARD) ? "" : "lock or ", mon_nam(u.ustuck)); return PICKLOCK_DID_NOTHING; } if (pick != &dummypick && picktyp != SKELETON_KEY && picktyp != LOCK_PICK && picktyp != CREDIT_CARD) { impossible("picking lock with object %d?", picktyp); return PICKLOCK_DID_NOTHING; } ch = 0; /* lint suppression */ if (rx != 0) { /* autounlock; caller has provided coordinates */ cc.x = rx; cc.y = ry; } else if (!get_adjacent_loc((char *) 0, "Invalid location!", u.ux, u.uy, &cc)) { return PICKLOCK_DID_NOTHING; } if (u_at(cc.x, cc.y)) { /* pick lock on a container */ const char *verb; char qsfx[QBUFSZ]; boolean it; int count; if (u.dz < 0 && !autounlock) { /* beware stale u.dz value */ There("isn't any sort of lock up %s.", Levitation ? "here" : "there"); return PICKLOCK_LEARNED_SOMETHING; } else if (is_lava(u.ux, u.uy)) { pline("Doing that would probably melt %s.", yname(pick)); return PICKLOCK_LEARNED_SOMETHING; } else if (is_pool(u.ux, u.uy) && !Underwater) { pline_The("%s has no lock.", hliquid("water")); return PICKLOCK_LEARNED_SOMETHING; } count = 0; c = 'n'; /* in case there are no boxes here */ for (otmp = svl.level.objects[cc.x][cc.y]; otmp; otmp = otmp->nexthere) { /* autounlock on boxes: only the one that was just discovered to be locked; don't include any other boxes which might be here */ if (autounlock && otmp != container) continue; if (Is_box(otmp)) { ++count; if (!can_reach_floor(TRUE)) { You_cant("reach %s from up here.", the(xname(otmp))); return PICKLOCK_LEARNED_SOMETHING; } it = 0; if (otmp->obroken) verb = "fix"; else if (!otmp->olocked) verb = "lock", it = 1; else if (picktyp != LOCK_PICK) verb = "unlock", it = 1; else verb = "pick"; if (autounlock && (flags.autounlock & AUTOUNLOCK_UNTRAP) != 0 && could_untrap(FALSE, TRUE) && (c = otmp->tknown ? (otmp->otrapped ? 'y' : 'n') : ynq(safe_qbuf(qbuf, "Check ", " for a trap?", otmp, yname, ysimple_name, "this"))) != 'n') { if (c == 'q') return PICKLOCK_DID_NOTHING; /* c == 'q' */ /* c == 'y' */ untrap(FALSE, 0, 0, otmp); return PICKLOCK_DID_SOMETHING; /* even if no trap found */ } else if (autounlock && (flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0) { c = 'q'; if (pick != &dummypick) { Sprintf(qbuf, "Unlock it with %s?", yname(pick)); c = ynq(qbuf); } if (c != 'y') return PICKLOCK_DID_NOTHING; } else { /* "There is here; ?" */ Sprintf(qsfx, " here; %s %s?", verb, it ? "it" : "its lock"); (void) safe_qbuf(qbuf, "There is ", qsfx, otmp, doname, ansimpleoname, "a box"); otmp->lknown = 1; c = ynq(qbuf); if (c == 'q') return PICKLOCK_DID_NOTHING; if (c == 'n') continue; /* try next box */ } if (otmp->obroken) { You_cant("fix its broken lock with %s.", ansimpleoname(pick)); return PICKLOCK_LEARNED_SOMETHING; } else if (picktyp == CREDIT_CARD && !otmp->olocked) { /* credit cards are only good for unlocking */ You_cant("do that with %s.", an(simple_typename(picktyp))); return PICKLOCK_LEARNED_SOMETHING; } else if (autounlock && !touch_artifact(pick, &gy.youmonst)) { /* note: for !autounlock, apply already did touch check */ return PICKLOCK_DID_SOMETHING; } switch (picktyp) { case CREDIT_CARD: ch = ACURR(A_DEX) + 20 * Role_if(PM_ROGUE); break; case LOCK_PICK: ch = 4 * ACURR(A_DEX) + 25 * Role_if(PM_ROGUE); break; case SKELETON_KEY: ch = 75 + ACURR(A_DEX); break; default: ch = 0; } if (otmp->cursed) ch /= 2; gx.xlock.box = otmp; gx.xlock.door = 0; break; } } if (c != 'y') { if (!count) There("doesn't seem to be any sort of lock here."); return PICKLOCK_LEARNED_SOMETHING; /* decided against all boxes */ } /* not the hero's location; pick the lock in an adjacent door */ } else { struct monst *mtmp; if (u.utrap && u.utraptype == TT_PIT) { You_cant("reach over the edge of the pit."); /* this used to return PICKLOCK_LEARNED_SOMETHING but the #open command doesn't use a turn for similar situation */ return PICKLOCK_DID_NOTHING; } door = &levl[cc.x][cc.y]; mtmp = m_at(cc.x, cc.y); if (mtmp && canseemon(mtmp) && M_AP_TYPE(mtmp) != M_AP_FURNITURE && M_AP_TYPE(mtmp) != M_AP_OBJECT) { if (picktyp == CREDIT_CARD && (mtmp->isshk || mtmp->data == &mons[PM_ORACLE])) { SetVoice(mtmp, 0, 80, 0); verbalize("No checks, no credit, no problem."); } else { pline("I don't think %s would appreciate that.", mon_nam(mtmp)); } return PICKLOCK_LEARNED_SOMETHING; } else if (mtmp && is_door_mappear(mtmp)) { /* "The door actually was a !" */ stumble_onto_mimic(mtmp); /* mimic might keep the key (50% chance, 10% for PYEC or MKoT) */ maybe_absorb_item(mtmp, pick, 50, 10); return PICKLOCK_LEARNED_SOMETHING; } if (!IS_DOOR(door->typ)) { int res = PICKLOCK_DID_NOTHING, oldglyph = door->glyph; schar oldlastseentyp = update_mapseen_for(cc.x, cc.y); /* this is probably only relevant when blind */ feel_location(cc.x, cc.y); if (door->glyph != oldglyph || svl.lastseentyp[cc.x][cc.y] != oldlastseentyp) res = PICKLOCK_LEARNED_SOMETHING; if (is_drawbridge_wall(cc.x, cc.y) >= 0) You("%s no lock on the drawbridge.", Blind ? "feel" : "see"); else You("%s no door there.", Blind ? "feel" : "see"); return res; } switch (door->doormask) { case D_NODOOR: pline("This doorway has no door."); return PICKLOCK_LEARNED_SOMETHING; case D_ISOPEN: You("cannot lock an open door."); return PICKLOCK_LEARNED_SOMETHING; case D_BROKEN: pline("This door is broken."); return PICKLOCK_LEARNED_SOMETHING; default: if ((flags.autounlock & AUTOUNLOCK_UNTRAP) != 0 && could_untrap(FALSE, FALSE) && (c = ynq("Check this door for a trap?")) != 'n') { if (c == 'q') return PICKLOCK_DID_NOTHING; /* c == 'y' */ untrap(FALSE, cc.x, cc.y, (struct obj *) 0); return PICKLOCK_DID_SOMETHING; /* even if no trap found */ } /* credit cards are only good for unlocking */ if (picktyp == CREDIT_CARD && !(door->doormask & D_LOCKED)) { You_cant("lock a door with a credit card."); return PICKLOCK_LEARNED_SOMETHING; } Sprintf(qbuf, "%s it%s%s?", (door->doormask & D_LOCKED) ? "Unlock" : "Lock", autounlock ? " with " : "", autounlock ? yname(pick) : ""); c = ynq(qbuf); if (c != 'y') return PICKLOCK_DID_NOTHING; /* note: for !autounlock, 'apply' already did touch check */ if (autounlock && !touch_artifact(pick, &gy.youmonst)) return PICKLOCK_DID_SOMETHING; switch (picktyp) { case CREDIT_CARD: ch = 2 * ACURR(A_DEX) + 20 * Role_if(PM_ROGUE); break; case LOCK_PICK: ch = 3 * ACURR(A_DEX) + 30 * Role_if(PM_ROGUE); break; case SKELETON_KEY: ch = 70 + ACURR(A_DEX); break; default: ch = 0; } gx.xlock.door = door; gx.xlock.box = 0; } } svc.context.move = 0; gx.xlock.chance = ch; gx.xlock.picktyp = picktyp; gx.xlock.magic_key = is_magic_key(&gy.youmonst, pick); gx.xlock.usedtime = 0; set_occupation(picklock, lock_action(), 0); return PICKLOCK_DID_SOMETHING; } /* is hero wielding a weapon that can #force? */ boolean u_have_forceable_weapon(void) { if (!uwep /* proper type test */ || ((uwep->oclass == WEAPON_CLASS || is_weptool(uwep)) ? (objects[uwep->otyp].oc_skill < P_DAGGER || objects[uwep->otyp].oc_skill == P_FLAIL || objects[uwep->otyp].oc_skill > P_LANCE) : uwep->oclass != ROCK_CLASS)) return FALSE; return TRUE; } RESTORE_WARNING_FORMAT_NONLITERAL /* the #force command - try to force a chest with your weapon */ int doforce(void) { struct obj *otmp; int c, picktyp; char qbuf[QBUFSZ]; /* * TODO? * allow force with edged weapon to be performed on doors. */ if (u.uswallow) { You_cant("force anything from inside here."); return ECMD_OK; } if (!u_have_forceable_weapon()) { boolean use_plural = uwep && uwep->quan > 1; You_cant("force anything %s weapon%s.", !uwep ? "when not wielding a" : (uwep->oclass != WEAPON_CLASS && !is_weptool(uwep)) ? (use_plural ? "without proper" : "without a proper") : (use_plural ? "with those" : "with that"), use_plural ? "s" : ""); return ECMD_OK; } if (!can_reach_floor(TRUE)) { cant_reach_floor(u.ux, u.uy, FALSE, TRUE, FALSE); return ECMD_OK; } picktyp = is_blade(uwep) && !is_pick(uwep); if (gx.xlock.usedtime && gx.xlock.box && picktyp == gx.xlock.picktyp) { You("resume your attempt to force the lock."); set_occupation(forcelock, "forcing the lock", 0); return ECMD_TIME; } /* A lock is made only for the honest man, the thief will break it. */ gx.xlock.box = (struct obj *) 0; for (otmp = svl.level.objects[u.ux][u.uy]; otmp; otmp = otmp->nexthere) if (Is_box(otmp)) { if (otmp->obroken || !otmp->olocked) { /* force doname() to omit known "broken" or "unlocked" prefix so that the message isn't worded redundantly; since we're about to set lknown, there's no need to remember and then reset its current value */ otmp->lknown = 0; There("is %s here, but its lock is already %s.", doname(otmp), otmp->obroken ? "broken" : "unlocked"); otmp->lknown = 1; continue; } (void) safe_qbuf(qbuf, "There is ", " here; force its lock?", otmp, doname, ansimpleoname, "a box"); otmp->lknown = 1; c = ynq(qbuf); if (c == 'q') return ECMD_OK; if (c == 'n') continue; if (picktyp) You("force %s into a crack and pry.", yname(uwep)); else You("start bashing it with %s.", yname(uwep)); gx.xlock.box = otmp; gx.xlock.chance = objects[uwep->otyp].oc_wldam * 2; gx.xlock.picktyp = picktyp; gx.xlock.magic_key = FALSE; gx.xlock.usedtime = 0; break; } if (gx.xlock.box) set_occupation(forcelock, "forcing the lock", 0); else You("decide not to force the issue."); return ECMD_TIME; } boolean stumble_on_door_mimic(coordxy x, coordxy y) { struct monst *mtmp; if ((mtmp = m_at(x, y)) && is_door_mappear(mtmp) && !Protection_from_shape_changers) { stumble_onto_mimic(mtmp); return TRUE; } return FALSE; } /* the #open command - try to open a door */ int doopen(void) { return doopen_indir(0, 0); } /* try to open a door in direction u.dx/u.dy */ int doopen_indir(coordxy x, coordxy y) { coord cc; struct rm *door; boolean portcullis; const char *dirprompt; int res = ECMD_OK; if (nohands(gy.youmonst.data)) { You_cant("open anything -- you have no hands!"); return ECMD_OK; } dirprompt = NULL; /* have get_adjacent_loc() -> getdir() use default */ if (u.utrap && u.utraptype == TT_PIT && container_at(u.ux, u.uy, FALSE)) dirprompt = "Open where? [.>]"; if (x > 0 && y >= 0) { /* nonzero is used when hero in amorphous form tries to flow under a closed door at ; the test here was using 'y > 0' but that would give incorrect results if doors are ever allowed to be placed on the top row of the map */ cc.x = x; cc.y = y; } else if (!get_adjacent_loc(dirprompt, (char *) 0, u.ux, u.uy, &cc)) { return ECMD_OK; } /* open at yourself/up/down: switch to loot unless there is a closed door here (possible with Passes_walls) and direction isn't 'down' */ if (u_at(cc.x, cc.y) && (u.dz > 0 || !closed_door(u.ux, u.uy))) return doloot(); /* this used to be done prior to get_adjacent_loc() but doing so was incorrect once open at hero's spot became an alternate way to loot */ if (u.utrap && u.utraptype == TT_PIT) { You_cant("reach over the edge of the pit."); return ECMD_OK; } if (stumble_on_door_mimic(cc.x, cc.y)) return ECMD_TIME; /* when choosing a direction is impaired, use a turn regardless of whether a door is successfully targeted */ if (Confusion || Stunned) res = ECMD_TIME; door = &levl[cc.x][cc.y]; portcullis = (is_drawbridge_wall(cc.x, cc.y) >= 0); /* this used to be 'if (Blind)' but using a key skips that so we do too */ { int oldglyph = door->glyph; schar oldlastseentyp = update_mapseen_for(cc.x, cc.y); newsym(cc.x, cc.y); if (door->glyph != oldglyph || svl.lastseentyp[cc.x][cc.y] != oldlastseentyp) res = ECMD_TIME; /* learned something */ } if (portcullis || !IS_DOOR(door->typ)) { /* closed portcullis or spot that opened bridge would span */ if (is_db_wall(cc.x, cc.y) || door->typ == DRAWBRIDGE_UP) There("is no obvious way to open the drawbridge."); else if (portcullis || door->typ == DRAWBRIDGE_DOWN) pline_The("drawbridge is already open."); else if (container_at(cc.x, cc.y, TRUE)) pline("%s like something lootable over there.", Blind ? "Feels" : "Seems"); else You("%s no door there.", Blind ? "feel" : "see"); return res; } if (!(door->doormask & D_CLOSED)) { const char *mesg; boolean locked = FALSE; switch (door->doormask) { case D_BROKEN: mesg = " is broken"; break; case D_NODOOR: mesg = "way has no door"; break; case D_ISOPEN: mesg = " is already open"; break; default: mesg = " is locked"; locked = TRUE; break; } set_msg_xy(cc.x, cc.y); pline("This door%s.", mesg); if (locked && flags.autounlock) { struct obj *unlocktool; u.dz = 0; /* should already be 0 since hero moved toward door */ if ((flags.autounlock & AUTOUNLOCK_APPLY_KEY) != 0 && (unlocktool = autokey(TRUE)) != 0) { res = pick_lock(unlocktool, cc.x, cc.y, (struct obj *) 0) ? ECMD_TIME : ECMD_OK; } else if ((flags.autounlock & AUTOUNLOCK_KICK) != 0 && !u.usteed /* kicking is different when mounted */ && ynq("Kick it?") == 'y') { cmdq_add_ec(CQ_CANNED, dokick); cmdq_add_dir(CQ_CANNED, sgn(cc.x - u.ux), sgn(cc.y - u.uy), 0); /* this was 'ECMD_TIME', but time shouldn't elapse until the canned kick takes place */ res = ECMD_OK; } } return res; } if (verysmall(gy.youmonst.data)) { pline("You're too small to pull the door open."); return res; } /* door is known to be CLOSED */ if (rnl(20) < (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 3) { set_msg_xy(cc.x, cc.y); pline_The("door opens."); if (door->doormask & D_TRAPPED) { b_trapped("door", FINGER); door->doormask = D_NODOOR; if (*in_rooms(cc.x, cc.y, SHOPBASE)) add_damage(cc.x, cc.y, SHOP_DOOR_COST); } else door->doormask = D_ISOPEN; feel_newsym(cc.x, cc.y); /* the hero knows she opened it */ recalc_block_point(cc.x, cc.y); /* vision: new see through there */ } else { exercise(A_STR, TRUE); set_msg_xy(cc.x, cc.y); pline_The("door resists!"); } return ECMD_TIME; } staticfn boolean obstructed(coordxy x, coordxy y, boolean quietly) { struct monst *mtmp = m_at(x, y); if (mtmp && M_AP_TYPE(mtmp) != M_AP_FURNITURE) { if (M_AP_TYPE(mtmp) == M_AP_OBJECT) goto objhere; if (!quietly) { char *Mn = Some_Monnam(mtmp); /* Monnam, Someone or Something */ if ((mtmp->mx != x || mtmp->my != y) && canspotmon(mtmp)) /* s_suffix() returns a modifiable buffer */ Mn = strcat(s_suffix(Mn), " tail"); pline("%s blocks the way!", Mn); } if (!canspotmon(mtmp)) map_invisible(x, y); return TRUE; } if (OBJ_AT(x, y)) { objhere: if (!quietly) pline("%s's in the way.", Something); return TRUE; } return FALSE; } /* the #close command - try to close a door */ int doclose(void) { coordxy x, y; struct rm *door; boolean portcullis; int res = ECMD_OK; if (nohands(gy.youmonst.data)) { You_cant("close anything -- you have no hands!"); return ECMD_OK; } if (u.utrap && u.utraptype == TT_PIT) { You_cant("reach over the edge of the pit."); return ECMD_OK; } if (!getdir((char *) 0)) return ECMD_CANCEL; x = u.ux + u.dx; y = u.uy + u.dy; if (u_at(x, y) && !Passes_walls) { You("are in the way!"); return ECMD_TIME; } if (!isok(x, y)) goto nodoor; if (stumble_on_door_mimic(x, y)) return ECMD_TIME; /* when choosing a direction is impaired, use a turn regardless of whether a door is successfully targeted */ if (Confusion || Stunned) res = ECMD_TIME; door = &levl[x][y]; portcullis = (is_drawbridge_wall(x, y) >= 0); if (Blind) { int oldglyph = door->glyph; schar oldlastseentyp = update_mapseen_for(x, y); feel_location(x, y); if (door->glyph != oldglyph || svl.lastseentyp[x][y] != oldlastseentyp) res = ECMD_TIME; /* learned something */ } if (portcullis || !IS_DOOR(door->typ)) { /* is_db_wall: closed portcullis */ if (is_db_wall(x, y) || door->typ == DRAWBRIDGE_UP) pline_The("drawbridge is already closed."); else if (portcullis || door->typ == DRAWBRIDGE_DOWN) There("is no obvious way to close the drawbridge."); else { nodoor: You("%s no door there.", Blind ? "feel" : "see"); } return res; } if (door->doormask == D_NODOOR) { pline("This doorway has no door."); return res; } else if (obstructed(x, y, FALSE)) { return res; } else if (door->doormask == D_BROKEN) { pline("This door is broken."); return res; } else if (door->doormask & (D_CLOSED | D_LOCKED)) { pline("This door is already closed."); return res; } if (door->doormask == D_ISOPEN) { if (verysmall(gy.youmonst.data) && !u.usteed) { pline("You're too small to push the door closed."); return res; } if (u.usteed || rn2(25) < (ACURRSTR + ACURR(A_DEX) + ACURR(A_CON)) / 3) { pline_The("door closes."); door->doormask = D_CLOSED; feel_newsym(x, y); /* the hero knows she closed it */ block_point(x, y); /* vision: no longer see there */ } else { exercise(A_STR, TRUE); pline_The("door resists!"); } } return ECMD_TIME; } /* box obj was hit with spell or wand effect otmp; returns true if something happened */ boolean boxlock(struct obj *obj, struct obj *otmp) /* obj *is* a box */ { boolean res = 0; switch (otmp->otyp) { case WAN_LOCKING: case SPE_WIZARD_LOCK: if (!obj->olocked) { /* lock it; fix if broken */ Soundeffect(se_klunk, 50); pline("Klunk!"); obj->olocked = 1; obj->obroken = 0; if (Role_if(PM_WIZARD)) obj->lknown = 1; else obj->lknown = 0; res = 1; } /* else already closed and locked */ break; case WAN_OPENING: case SPE_KNOCK: if (obj->olocked) { /* unlock; isn't broken so doesn't need fixing */ Soundeffect(se_klick, 50); pline("Klick!"); obj->olocked = 0; res = 1; if (Role_if(PM_WIZARD)) obj->lknown = 1; else obj->lknown = 0; } else /* silently fix if broken */ obj->obroken = 0; break; case WAN_POLYMORPH: case SPE_POLYMORPH: /* maybe start unlocking chest, get interrupted, then zap it; we must avoid any attempt to resume unlocking it */ if (gx.xlock.box == obj) reset_pick(); break; } return res; } /* Door/secret door was hit with spell or wand effect otmp; returns true if something happened */ boolean doorlock(struct obj *otmp, coordxy x, coordxy y) { struct rm *door = &levl[x][y]; boolean res = TRUE; int loudness = 0; const char *msg = (const char *) 0; const char *dustcloud = "A cloud of dust"; const char *quickly_dissipates = "quickly dissipates"; boolean mysterywand = (otmp->oclass == WAND_CLASS && !otmp->dknown); if (door->typ == SDOOR) { switch (otmp->otyp) { case WAN_OPENING: case SPE_KNOCK: case WAN_STRIKING: case SPE_FORCE_BOLT: door->typ = DOOR; door->doormask = D_CLOSED | (door->doormask & D_TRAPPED); newsym(x, y); if (cansee(x, y)) pline("A door appears in the wall!"); if (otmp->otyp == WAN_OPENING || otmp->otyp == SPE_KNOCK) return TRUE; break; /* striking: continue door handling below */ case WAN_LOCKING: case SPE_WIZARD_LOCK: default: return FALSE; } } switch (otmp->otyp) { case WAN_LOCKING: case SPE_WIZARD_LOCK: if (Is_rogue_level(&u.uz)) { boolean vis = cansee(x, y); /* Can't have real locking in Rogue, so just hide doorway */ if (vis) { pline("%s springs up in the older, more primitive doorway.", dustcloud); } else { Soundeffect(se_swoosh, 25); You_hear("a swoosh."); } if (obstructed(x, y, mysterywand)) { if (vis) pline_The("cloud %s.", quickly_dissipates); return FALSE; } block_point(x, y); door->typ = SDOOR, door->doormask = D_NODOOR; if (vis) pline_The("doorway vanishes!"); newsym(x, y); return TRUE; } if (obstructed(x, y, mysterywand)) return FALSE; /* Don't allow doors to close over traps. This is for pits */ /* & trap doors, but is it ever OK for anything else? */ if (t_at(x, y)) { /* maketrap() clears doormask, so it should be NODOOR */ pline("%s springs up in the doorway, but %s.", dustcloud, quickly_dissipates); return FALSE; } switch (door->doormask & ~D_TRAPPED) { case D_CLOSED: msg = "The door locks!"; break; case D_ISOPEN: msg = "The door swings shut, and locks!"; break; case D_BROKEN: msg = "The broken door reassembles and locks!"; break; case D_NODOOR: msg = "A cloud of dust springs up and assembles itself into a door!"; break; default: res = FALSE; break; } block_point(x, y); door->doormask = D_LOCKED | (door->doormask & D_TRAPPED); newsym(x, y); break; case WAN_OPENING: case SPE_KNOCK: if (door->doormask & D_LOCKED) { msg = "The door unlocks!"; door->doormask = D_CLOSED | (door->doormask & D_TRAPPED); } else res = FALSE; break; case WAN_STRIKING: case SPE_FORCE_BOLT: if (door->doormask & (D_LOCKED | D_CLOSED)) { /* sawit: closed door location is more visible than open */ boolean sawit, seeit; if (door->doormask & D_TRAPPED) { struct monst *mtmp = m_at(x, y); sawit = mtmp ? canseemon(mtmp) : cansee(x, y); door->doormask = D_NODOOR; unblock_point(x, y); newsym(x, y); seeit = mtmp ? canseemon(mtmp) : cansee(x, y); if (mtmp) { (void) mb_trapped(mtmp, sawit || seeit); } else { /* for mtmp, mb_trapped() does is own wake_nearto() */ loudness = 40; if (flags.verbose) { Soundeffect(se_kaboom_door_explodes, 75); if ((sawit || seeit) && !Unaware) { pline("KABOOM!! You see a door explode."); } else if (!Deaf) { Soundeffect(se_explosion, 75); You_hear("a %s explosion.", (distu(x, y) > 7 * 7) ? "distant" : "nearby"); } } } break; } sawit = cansee(x, y); door->doormask = D_BROKEN; recalc_block_point(x, y); seeit = cansee(x, y); newsym(x, y); if (flags.verbose) { if ((sawit || seeit) && !Unaware) { pline_The("door crashes open!"); } else if (!Deaf) { Soundeffect(se_crashing_sound, 100); You_hear("a crashing sound."); } } /* force vision recalc before printing more messages */ if (gv.vision_full_recalc) vision_recalc(0); loudness = 20; } else res = FALSE; break; default: impossible("magic (%d) attempted on door.", otmp->otyp); break; } if (msg && cansee(x, y)) pline1(msg); if (loudness > 0) { /* door was destroyed */ wake_nearto(x, y, loudness); if (*in_rooms(x, y, SHOPBASE)) add_damage(x, y, 0L); } if (res && picking_at(x, y)) { /* maybe unseen monster zaps door you're unlocking */ stop_occupation(); reset_pick(); } return res; } staticfn void chest_shatter_msg(struct obj *otmp) { const char *disposition; const char *thing; long save_HBlinded, save_BBlinded; if (otmp->oclass == POTION_CLASS) { You("%s %s shatter!", Blind ? "hear" : "see", an(bottlename())); if (!breathless(gy.youmonst.data) || haseyes(gy.youmonst.data)) potionbreathe(otmp); return; } /* We have functions for distant and singular names, but not one */ /* which does _both_... */ save_HBlinded = HBlinded, save_BBlinded = BBlinded; HBlinded = 1L, BBlinded = 0L; thing = singular(otmp, xname); HBlinded = save_HBlinded, BBlinded = save_BBlinded; switch (objects[otmp->otyp].oc_material) { case PAPER: disposition = "is torn to shreds"; break; case WAX: disposition = "is crushed"; break; case VEGGY: disposition = "is pulped"; break; case FLESH: disposition = "is mashed"; break; case GLASS: disposition = "shatters"; break; case WOOD: disposition = "splinters to fragments"; break; default: disposition = "is destroyed"; break; } pline("%s %s!", An(thing), disposition); } /*lock.c*/