The g? structs had a mix of variables that were written to
the savefile, and those that were not.
For better clarity and to distinguish those that end up in
the savefile, relocate some g? variables that get written
directly to the savefile into different structs.
This updates EDITLEVEL, although technically it probably
didn't need to, since savefile contents are not changing.
Details:
gb.bases -> svb.bases
gb.bbubbles -> svb.bbubbles
gb.branches -> svb.branches
gc.context -> svc.context
gd.disco -> svd.disco
gd.dndest -> svd.dndest
gd.doors -> svd.doors
gd.doors_alloc -> svd.doors_alloc
gd.dungeon_topology -> svd.dungeon_topology
gd.dungeons -> svd.dungeons
ge.exclusion_zones -> sve.exclusion_zones
gh.hackpid -> svh.hackpid
gi.inv_pos -> svi.inv_pos
gk.killer -> svk.killer
gl.lastseentyp -> svl.lastseentyp
gl.level -> svl.level
gl.level_info -> svl.level_info
gm.mapseenchn -> svm.mapseenchn
gm.moves -> svm.moves
gm.mvitals -> svm.mvitals
gn.n_dgns -> svn.n_dgns
gn.n_regions -> svn.n_regions
gn.nroom -> svn.nroom
go.oracle_cnt -> svo.oracle_cnt
gp.pl_character -> svp.pl_character
gp.pl_fruit -> svp.pl_fruit
gp.plname -> svp.plname
gp.program_state -> svp.program_state
gq.quest_status -> svq.quest_status
gr.rooms -> svr.rooms
gs.sp_levchn -> svs.sp_levchn
gs.spl_book -> svs.spl_book
gt.timer_id -> svt.timer_id
gt.tune -> svt.tune
gu.updest -> svu.updest
gx.xmax -> svx.xmax
gx.xmin -> svx.xmin
gy.ymax -> svy.ymax
gy.ymin -> svy.ymin
Related note:
There are some pointer variables that are heads of chains that were not
moved from 'g?' to 'sv?', because they are not actually written to the
savefile directly, but the objects/monst/trap/lightsource/timer in the
chains they point to are. That can be changed, if desired.
Examples: gi.invent, gm.migrating_objs, gb.billobjs, gm.migrating_mons,
gf.ftrap, gl.light_base, gt.timer_base
1310 lines
44 KiB
C
1310 lines
44 KiB
C
/* NetHack 3.7 lock.c $NHDT-Date: 1718745135 2024/06/18 21:12:15 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.137 $ */
|
|
/* 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 */
|
|
/* unfortunately we don't have a 'tknown' flag to record
|
|
"known to be trapped" so declining to disarm and then
|
|
retrying lock manipulation will find it all over again */
|
|
if (y_n("You find a trap! 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;
|
|
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 <u.ux,u.uy>) */
|
|
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 = 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 <a box> here; <verb> <it|its lock>?" */
|
|
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 <mimic>!" */
|
|
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);
|
|
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 <x,y> is used when hero in amorphous form tries to
|
|
flow under a closed door at <x,y>; 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 (!u.usteed
|
|
&& (flags.autounlock & AUTOUNLOCK_KICK) != 0
|
|
&& 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);
|
|
res = ECMD_TIME;
|
|
}
|
|
}
|
|
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 */
|
|
unblock_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;
|
|
unblock_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*/
|