Files
nethack/src/teleport.c
Pasi Kallinen 229930e505 Fixes and sanity checks for monster undetected and trapped states
Adds sanity checks for mtrapped and mundetected states.

Fixes cases where those were left in wrong state.

1. Trapped monster (eg. a nymph) teleported out of a trap
2. Monster was hiding under ball or chain, which then got removed
3. While restoring a level, a zombie corpse revived while monster
   was hiding under it
4. A general case where the only object was deleted off floor and
   a monster was hiding under it

Monsters hiding under ball or chain will now get revealed when
the b or c are moved.
2020-11-24 19:37:43 +02:00

1648 lines
57 KiB
C

/* NetHack 3.7 teleport.c $NHDT-Date: 1605305493 2020/11/13 22:11:33 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.134 $ */
/* 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"
static boolean FDECL(tele_jump_ok, (int, int, int, int));
static boolean FDECL(teleok, (int, int, BOOLEAN_P));
static void NDECL(vault_tele);
static boolean FDECL(rloc_pos_ok, (int, int, struct monst *));
static void FDECL(mvault_tele, (struct monst *));
/* teleporting is prevented on this level for this monster? */
boolean
noteleport_level(mon)
struct monst *mon;
{
struct monst *mtmp;
/* demon court in Gehennom prevent others from teleporting */
if (In_hell(&u.uz) && !(is_dlord(mon->data) || is_dprince(mon->data)))
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
if (is_dlord(mtmp->data) || is_dprince(mtmp->data))
return TRUE;
/* natural no-teleport level */
if (g.level.flags.noteleport)
return TRUE;
return FALSE;
}
/*
* Is (x,y) a good position of mtmp? If mtmp is NULL, then is (x,y) good
* for an object?
*
* This function will only look at mtmp->mdat, so makemon, mplayer, etc can
* call it to generate new monster positions with fake monster structures.
*/
boolean
goodpos(x, y, mtmp, gpflags)
int x, y;
struct monst *mtmp;
long gpflags;
{
struct permonst *mdat = (struct permonst *) 0;
boolean ignorewater = ((gpflags & MM_IGNOREWATER) != 0),
allow_u = ((gpflags & GP_ALLOW_U) != 0);
if (!isok(x, y))
return FALSE;
/* in many cases, we're trying to create a new monster, which
* can't go on top of the player or any existing monster.
* however, occasionally we are relocating engravings or objects,
* which could be co-located and thus get restricted a bit too much.
* oh well.
*/
if (!allow_u) {
if (x == u.ux && y == u.uy && mtmp != &g.youmonst
&& (mtmp != u.ustuck || !u.uswallow)
&& (!u.usteed || mtmp != u.usteed))
return FALSE;
}
if (mtmp) {
struct monst *mtmp2 = m_at(x, y);
/* Be careful with long worms. A monster may be placed back in
* its own location. Normally, if m_at() returns the same monster
* that we're trying to place, the monster is being placed in its
* own location. However, that is not correct for worm segments,
* because all the segments of the worm return the same m_at().
* Actually we overdo the check a little bit--a worm can't be placed
* in its own location, period. If we just checked for mtmp->mx
* != x || mtmp->my != y, we'd miss the case where we're called
* to place the worm segment and the worm's head is at x,y.
*/
if (mtmp2 && (mtmp2 != mtmp || mtmp->wormno))
return FALSE;
mdat = mtmp->data;
if (is_pool(x, y) && !ignorewater) {
/* [what about Breathless?] */
if (mtmp == &g.youmonst)
return (Swimming || Amphibious
|| (!Is_waterlevel(&u.uz)
/* water on the Plane of Water has no surface
so there's no way to be on or above that */
&& (Levitation || Flying || Wwalking)));
else
return (is_swimmer(mdat)
|| (!Is_waterlevel(&u.uz)
&& (is_floater(mdat) || is_flyer(mdat)
|| is_clinger(mdat))));
} else if (mdat->mlet == S_EEL && rn2(13) && !ignorewater) {
return FALSE;
} else if (is_lava(x, y)) {
/* 3.6.3: floating eye can levitate over lava but it avoids
that due the effect of the heat causing it to dry out */
if (mdat == &mons[PM_FLOATING_EYE])
return FALSE;
else if (mtmp == &g.youmonst)
return (Levitation || Flying
|| (Fire_resistance && Wwalking && uarmf
&& uarmf->oerodeproof)
|| (Upolyd && likes_lava(g.youmonst.data)));
else
return (is_floater(mdat) || is_flyer(mdat)
|| likes_lava(mdat));
}
if (passes_walls(mdat) && may_passwall(x, y))
return TRUE;
if (amorphous(mdat) && closed_door(x, y))
return TRUE;
}
if (!accessible(x, y)) {
if (!(is_pool(x, y) && ignorewater))
return FALSE;
}
if (sobj_at(BOULDER, x, y) && (!mdat || !throws_rocks(mdat)))
return FALSE;
return TRUE;
}
/*
* "entity next to"
*
* Attempt to find a good place for the given monster type in the closest
* position to (xx,yy). Do so in successive square rings around (xx,yy).
* If there is more than one valid position in the ring, choose one randomly.
* Return TRUE and the position chosen when successful, FALSE otherwise.
*/
boolean
enexto(cc, xx, yy, mdat)
coord *cc;
register xchar xx, yy;
struct permonst *mdat;
{
return enexto_core(cc, xx, yy, mdat, NO_MM_FLAGS);
}
boolean
enexto_core(cc, xx, yy, mdat, entflags)
coord *cc;
xchar xx, yy;
struct permonst *mdat;
long entflags;
{
#define MAX_GOOD 15
coord good[MAX_GOOD], *good_ptr;
int x, y, range, i;
int xmin, xmax, ymin, ymax, rangemax;
struct monst fakemon; /* dummy monster */
boolean allow_xx_yy = (boolean) ((entflags & GP_ALLOW_XY) != 0);
entflags &= ~GP_ALLOW_XY;
if (!mdat) {
debugpline0("enexto() called with null mdat");
/* default to player's original monster type */
mdat = &mons[u.umonster];
}
fakemon = cg.zeromonst;
set_mon_data(&fakemon, mdat); /* set up for goodpos */
/* used to use 'if (range > ROWNO && range > COLNO) return FALSE' below,
so effectively 'max(ROWNO, COLNO)' which performs useless iterations
(possibly many iterations if <xx,yy> is in the center of the map) */
xmax = max(xx - 1, (COLNO - 1) - xx);
ymax = max(yy - 0, (ROWNO - 1) - yy);
rangemax = max(xmax, ymax);
/* setup: no suitable spots yet, first iteration checks adjacent spots */
good_ptr = good;
range = 1;
/*
* Walk around the border of the square with center (xx,yy) and
* radius range. Stop when we find at least one valid position.
*/
do {
xmin = max(1, xx - range);
xmax = min(COLNO - 1, xx + range);
ymin = max(0, yy - range);
ymax = min(ROWNO - 1, yy + range);
for (x = xmin; x <= xmax; x++) {
if (goodpos(x, ymin, &fakemon, entflags)) {
good_ptr->x = x;
good_ptr->y = ymin;
/* beware of accessing beyond segment boundaries.. */
if (good_ptr++ == &good[MAX_GOOD - 1])
goto full;
}
if (goodpos(x, ymax, &fakemon, entflags)) {
good_ptr->x = x;
good_ptr->y = ymax;
if (good_ptr++ == &good[MAX_GOOD - 1])
goto full;
}
}
/* 3.6.3: this used to use 'ymin+1' which left top row unchecked */
for (y = ymin; y < ymax; y++) {
if (goodpos(xmin, y, &fakemon, entflags)) {
good_ptr->x = xmin;
good_ptr->y = y;
if (good_ptr++ == &good[MAX_GOOD - 1])
goto full;
}
if (goodpos(xmax, y, &fakemon, entflags)) {
good_ptr->x = xmax;
good_ptr->y = y;
if (good_ptr++ == &good[MAX_GOOD - 1])
goto full;
}
}
} while (++range <= rangemax && good_ptr == good);
/* return False if we exhausted 'range' without finding anything */
if (good_ptr == good) {
/* 3.6.3: earlier versions didn't have the option to try <xx,yy>,
and left 'cc' uninitialized when returning False */
cc->x = xx, cc->y = yy;
/* if every spot other than <xx,yy> has failed, try <xx,yy> itself */
if (allow_xx_yy && goodpos(xx, yy, &fakemon, entflags)) {
return TRUE; /* 'cc' is set */
} else {
debugpline3("enexto(\"%s\",%d,%d) failed", mdat->mname, xx, yy);
return FALSE;
}
}
full:
/* we've got between 1 and SIZE(good) candidates; choose one */
i = rn2((int) (good_ptr - good));
cc->x = good[i].x;
cc->y = good[i].y;
return TRUE;
}
/*
* Check for restricted areas present in some special levels. (This might
* need to be augmented to allow deliberate passage in wizard mode, but
* only for explicitly chosen destinations.)
*/
static boolean
tele_jump_ok(x1, y1, x2, y2)
int x1, y1, x2, y2;
{
if (!isok(x2, y2))
return FALSE;
if (g.dndest.nlx > 0) {
/* if inside a restricted region, can't teleport outside */
if (within_bounded_area(x1, y1, g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy)
&& !within_bounded_area(x2, y2, g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy))
return FALSE;
/* and if outside, can't teleport inside */
if (!within_bounded_area(x1, y1, g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy)
&& within_bounded_area(x2, y2, g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy))
return FALSE;
}
if (g.updest.nlx > 0) { /* ditto */
if (within_bounded_area(x1, y1, g.updest.nlx, g.updest.nly,
g.updest.nhx, g.updest.nhy)
&& !within_bounded_area(x2, y2, g.updest.nlx, g.updest.nly,
g.updest.nhx, g.updest.nhy))
return FALSE;
if (!within_bounded_area(x1, y1, g.updest.nlx, g.updest.nly,
g.updest.nhx, g.updest.nhy)
&& within_bounded_area(x2, y2, g.updest.nlx, g.updest.nly,
g.updest.nhx, g.updest.nhy))
return FALSE;
}
return TRUE;
}
static boolean
teleok(x, y, trapok)
register int x, y;
boolean trapok;
{
if (!trapok) {
/* allow teleportation onto vibrating square, it's not a real trap */
struct trap *trap = t_at(x, y);
if (trap && trap->ttyp != VIBRATING_SQUARE)
return FALSE;
}
if (!goodpos(x, y, &g.youmonst, 0))
return FALSE;
if (!tele_jump_ok(u.ux, u.uy, x, y))
return FALSE;
if (!in_out_region(x, y))
return FALSE;
return TRUE;
}
void
teleds(nux, nuy, teleds_flags)
int nux, nuy;
int teleds_flags;
{
boolean ball_active, ball_still_in_range = FALSE,
allow_drag = (teleds_flags & TELEDS_ALLOW_DRAG) != 0,
is_teleport = (teleds_flags & TELEDS_TELEPORT) != 0;
struct monst *vault_guard = vault_occupied(u.urooms) ? findgd() : 0;
if (u.utraptype == TT_BURIEDBALL) {
/* unearth it */
buried_ball_to_punishment();
}
ball_active = (Punished && uball->where != OBJ_FREE);
if (!ball_active
|| near_capacity() > SLT_ENCUMBER
|| distmin(u.ux, u.uy, nux, nuy) > 1)
allow_drag = FALSE;
/* If they have to move the ball, then drag if allow_drag is true;
* otherwise they are teleporting, so unplacebc().
* If they don't have to move the ball, then always "drag" whether or
* not allow_drag is true, because we are calling that function, not
* to drag, but to move the chain. *However* there are some dumb
* special cases:
* 0 0
* _X move east -----> X_
* @ @
* These are permissible if teleporting, but not if dragging. As a
* result, drag_ball() needs to know about allow_drag and might end
* up dragging the ball anyway. Also, drag_ball() might find that
* dragging the ball is completely impossible (ball in range but there's
* rock in the way), in which case it teleports the ball on its own.
*/
if (ball_active) {
if (!carried(uball) && distmin(nux, nuy, uball->ox, uball->oy) <= 2)
ball_still_in_range = TRUE; /* don't have to move the ball */
else if (!allow_drag)
unplacebc(); /* have to move the ball */
}
reset_utrap(FALSE);
set_ustuck((struct monst *) 0);
u.ux0 = u.ux;
u.uy0 = u.uy;
if (!hideunder(&g.youmonst) && g.youmonst.data->mlet == S_MIMIC) {
/* mimics stop being unnoticed */
g.youmonst.m_ap_type = M_AP_NOTHING;
}
if (u.uswallow) {
/* subset of unstuck() */
u.uswldtim = u.uswallow = 0;
if (Punished) { /* ball&chain are off map while swallowed */
ball_active = TRUE; /* to put chain and non-carried ball on map */
ball_still_in_range = allow_drag = FALSE; /* (redundant) */
}
docrt();
}
if (ball_active && (ball_still_in_range || allow_drag)) {
int bc_control;
xchar ballx, bally, chainx, chainy;
boolean cause_delay;
if (drag_ball(nux, nuy, &bc_control, &ballx, &bally, &chainx,
&chainy, &cause_delay, allow_drag))
move_bc(0, bc_control, ballx, bally, chainx, chainy);
else /* dragging fails if hero is encumbered beyond 'burdened' */
unplacebc(); /* to match placebc() below */
}
/* must set u.ux, u.uy after drag_ball(), which may need to know
the old position if allow_drag is true... */
u_on_newpos(nux, nuy); /* set u.<x,y>, usteed-><mx,my>; cliparound() */
fill_pit(u.ux0, u.uy0);
if (ball_active && uchain && uchain->where == OBJ_FREE)
placebc(); /* put back the ball&chain if they were taken off map */
initrack(); /* teleports mess up tracking monsters without this */
update_player_regions();
/*
* Make sure the hero disappears from the old location. This will
* not happen if she is teleported within sight of her previous
* location. Force a full vision recalculation because the hero
* is now in a new location.
*/
newsym(u.ux0, u.uy0);
see_monsters();
g.vision_full_recalc = 1;
nomul(0);
vision_recalc(0); /* vision before effects */
/* this used to take place sooner, but if a --More-- prompt was issued
then the old map display was shown instead of the new one */
if (is_teleport && flags.verbose)
You("materialize in %s location!",
(nux == u.ux0 && nuy == u.uy0) ? "the same" : "a different");
/* if terrain type changes, levitation or flying might become blocked
or unblocked; might issue message, so do this after map+vision has
been updated for new location instead of right after u_on_newpos() */
if (levl[u.ux][u.uy].typ != levl[u.ux0][u.uy0].typ)
switch_terrain();
/* sequencing issue: we want guard's alarm, if any, to occur before
room entry message, if any, so need to check for vault exit prior
to spoteffects; but spoteffects() sets up new value for u.urooms
and vault code depends upon that value, so we need to fake it */
if (vault_guard) {
char save_urooms[5]; /* [sizeof u.urooms] */
Strcpy(save_urooms, u.urooms);
Strcpy(u.urooms, in_rooms(u.ux, u.uy, VAULT));
/* if hero has left vault, make guard notice */
if (!vault_occupied(u.urooms))
uleftvault(vault_guard);
Strcpy(u.urooms, save_urooms); /* reset prior to spoteffects() */
}
/* possible shop entry message comes after guard's shrill whistle */
spoteffects(TRUE);
invocation_message();
}
boolean
safe_teleds(teleds_flags)
int teleds_flags;
{
register int nux, nuy, tcnt = 0;
do {
nux = rnd(COLNO - 1);
nuy = rn2(ROWNO);
} while (!teleok(nux, nuy, (boolean) (tcnt > 200)) && ++tcnt <= 400);
if (tcnt <= 400) {
teleds(nux, nuy, teleds_flags);
return TRUE;
} else
return FALSE;
}
static void
vault_tele()
{
register struct mkroom *croom = search_special(VAULT);
coord c;
if (croom && somexy(croom, &c) && teleok(c.x, c.y, FALSE)) {
teleds(c.x, c.y, TELEDS_TELEPORT);
return;
}
tele();
}
boolean
teleport_pet(mtmp, force_it)
register struct monst *mtmp;
boolean force_it;
{
register struct obj *otmp;
if (mtmp == u.usteed)
return FALSE;
if (mtmp->mleashed) {
otmp = get_mleash(mtmp);
if (!otmp) {
impossible("%s is leashed, without a leash.", Monnam(mtmp));
goto release_it;
}
if (otmp->cursed && !force_it) {
yelp(mtmp);
return FALSE;
} else {
Your("leash goes slack.");
release_it:
m_unleash(mtmp, FALSE);
return TRUE;
}
}
return TRUE;
}
/* teleport the hero via some method other than scroll of teleport */
void
tele()
{
scrolltele((struct obj *) 0);
}
/* teleport the hero; usually discover scroll of teleporation if via scroll */
void
scrolltele(scroll)
struct obj *scroll;
{
coord cc;
/* Disable teleportation in stronghold && Vlad's Tower */
if (noteleport_level(&g.youmonst) && !wizard) {
pline("A mysterious force prevents you from teleporting!");
if (scroll)
learnscroll(scroll); /* this is obviously a teleport scroll */
return;
}
/* don't show trap if "Sorry..." */
if (!Blinded)
make_blinded(0L, FALSE);
if ((u.uhave.amulet || On_W_tower_level(&u.uz)) && !rn2(3)) {
You_feel("disoriented for a moment.");
/* don't discover the scroll [at least not yet for wizard override];
disorientation doesn't reveal that this is a teleport attempt */
if (!wizard || yn("Override?") != 'y')
return;
}
if (((Teleport_control || (scroll && scroll->blessed)) && !Stunned)
|| wizard) {
if (unconscious()) {
pline("Being unconscious, you cannot control your teleport.");
} else {
char whobuf[BUFSZ];
Strcpy(whobuf, "you");
if (u.usteed)
Sprintf(eos(whobuf), " and %s", mon_nam(u.usteed));
pline("Where do %s want to be teleported?", whobuf);
if (scroll)
learnscroll(scroll);
cc.x = u.ux;
cc.y = u.uy;
if (isok(iflags.travelcc.x, iflags.travelcc.y)) {
/* The player showed some interest in traveling here;
* pre-suggest this coordinate. */
cc = iflags.travelcc;
}
if (getpos(&cc, TRUE, "the desired position") < 0)
return; /* abort */
/* possible extensions: introduce a small error if
magic power is low; allow transfer to solid rock */
if (teleok(cc.x, cc.y, FALSE)) {
/* for scroll, discover it regardless of destination */
teleds(cc.x, cc.y, TELEDS_TELEPORT);
if (iflags.travelcc.x == u.ux && iflags.travelcc.y == u.uy)
iflags.travelcc.x = iflags.travelcc.y = 0;
return;
}
pline("Sorry...");
}
}
/* we used to suppress discovery if hero teleported to a nearby
spot which was already within view, but now there is always a
"materialize" message regardless of how far you teleported so
discovery of scroll type is unconditional */
if (scroll)
learnscroll(scroll);
(void) safe_teleds(TELEDS_TELEPORT);
}
/* ^T command; 'm ^T' == choose among several teleport modes */
int
dotelecmd()
{
long save_HTele, save_ETele;
int res, added, hidden;
boolean ignore_restrictions = FALSE;
/* also defined in spell.c */
#define NOOP_SPELL 0
#define HIDE_SPELL 1
#define ADD_SPELL 2
#define UNHIDESPELL 3
#define REMOVESPELL 4
/* normal mode; ignore 'm' prefix if it was given */
if (!wizard)
return dotele(FALSE);
added = hidden = NOOP_SPELL;
save_HTele = HTeleportation, save_ETele = ETeleportation;
if (!iflags.menu_requested) {
ignore_restrictions = TRUE;
} else {
static const struct tporttypes {
char menulet;
const char *menudesc;
} tports[] = {
/*
* Potential combinations:
* 1) attempt ^T without intrinsic, not know spell;
* 2) via intrinsic, not know spell, obey restrictions;
* 3) via intrinsic, not know spell, ignore restrictions;
* 4) via intrinsic, know spell, obey restrictions;
* 5) via intrinsic, know spell, ignore restrictions;
* 6) via spell, not have intrinsic, obey restrictions;
* 7) via spell, not have intrinsic, ignore restrictions;
* 8) force, obey other restrictions;
* 9) force, ignore restrictions.
* We only support the 1st (t), 2nd (n), 6th (s), and 9th (w).
*
* This ignores the fact that there is an experience level
* (or poly-form) requirement which might make normal ^T fail.
*/
{ 'n', "normal ^T on demand; no spell, obey restrictions" },
{ 's', "via spellcast; no intrinsic teleport" },
{ 't', "try ^T without having it; no spell" },
{ 'w', "debug mode; ignore restrictions" }, /* trad wizard mode */
};
menu_item *picks = (menu_item *) 0;
anything any;
winid win;
int i, tmode;
win = create_nhwindow(NHW_MENU);
start_menu(win, MENU_BEHAVE_STANDARD);
any = cg.zeroany;
for (i = 0; i < SIZE(tports); ++i) {
any.a_int = (int) tports[i].menulet;
add_menu(win, NO_GLYPH, &any, (char) any.a_int, 0, ATR_NONE,
tports[i].menudesc,
(tports[i].menulet == 'w') ? MENU_ITEMFLAGS_SELECTED
: MENU_ITEMFLAGS_NONE);
}
end_menu(win, "Which way do you want to teleport?");
i = select_menu(win, PICK_ONE, &picks);
destroy_nhwindow(win);
if (i > 0) {
tmode = picks[0].item.a_int;
/* if we got 2, use the one which wasn't preselected */
if (i > 1 && tmode == 'w')
tmode = picks[1].item.a_int;
free((genericptr_t) picks);
} else if (i == 0) {
/* preselected one was explicitly chosen and got toggled off */
tmode = 'w';
} else { /* ESC */
return 0;
}
switch (tmode) {
case 'n':
HTeleportation |= I_SPECIAL; /* confer intrinsic teleportation */
hidden = tport_spell(HIDE_SPELL); /* hide teleport-away */
break;
case 's':
HTeleportation = ETeleportation = 0L; /* suppress intrinsic */
added = tport_spell(ADD_SPELL); /* add teleport-away */
break;
case 't':
HTeleportation = ETeleportation = 0L; /* suppress intrinsic */
hidden = tport_spell(HIDE_SPELL); /* hide teleport-away */
break;
case 'w':
ignore_restrictions = TRUE;
break;
}
}
/* if dotele() can be fatal, final disclosure might lie about
intrinsic teleportation; we should be able to live with that
since the menu finagling is only applicable in wizard mode */
res = dotele(ignore_restrictions);
HTeleportation = save_HTele;
ETeleportation = save_ETele;
if (added != NOOP_SPELL || hidden != NOOP_SPELL)
/* can't both be non-NOOP so addition will yield the non-NOOP one */
(void) tport_spell(added + hidden - NOOP_SPELL);
return res;
}
int
dotele(break_the_rules)
boolean break_the_rules; /* True: wizard mode ^T */
{
struct trap *trap;
const char *cantdoit;
boolean trap_once = FALSE;
trap = t_at(u.ux, u.uy);
if (trap && !trap->tseen)
trap = 0;
if (trap) {
if (trap->ttyp == LEVEL_TELEP && trap->tseen) {
if (yn("There is a level teleporter here. Trigger it?") == 'y') {
level_tele_trap(trap, FORCETRAP);
/* deliberate jumping will always take time even if it doesn't
* work */
return 1;
} else
trap = 0; /* continue with normal horizontal teleport */
} else if (trap->ttyp == TELEP_TRAP) {
trap_once = trap->once; /* trap may get deleted, save this */
if (trap->once) {
pline("This is a vault teleport, usable once only.");
if (yn("Jump in?") == 'n') {
trap = 0;
} else {
deltrap(trap);
newsym(u.ux, u.uy);
}
}
if (trap)
You("%s onto the teleportation trap.",
locomotion(g.youmonst.data, "jump"));
} else
trap = 0;
}
if (!trap) {
boolean castit = FALSE;
register int sp_no = 0, energy = 0;
if (!Teleportation || (u.ulevel < (Role_if(PM_WIZARD) ? 8 : 12)
&& !can_teleport(g.youmonst.data))) {
/* Try to use teleport away spell. */
for (sp_no = 0; sp_no < MAXSPELL; sp_no++)
if (g.spl_book[sp_no].sp_id == SPE_TELEPORT_AWAY)
break;
/* casting isn't inhibited by being Stunned (...it ought to be) */
castit = (sp_no < MAXSPELL && !Confusion);
if (!castit && !break_the_rules) {
You("%s.",
!Teleportation ? ((sp_no < MAXSPELL)
? "can't cast that spell"
: "don't know that spell")
: "are not able to teleport at will");
return 0;
}
}
cantdoit = 0;
/* 3.6.2: the magic numbers for hunger, strength, and energy
have been changed to match the ones used in spelleffects().
Also, failing these tests used to return 1 and use a move
even though casting failure due to these reasons doesn't.
[Note: this spellev() is different from the one in spell.c
but they both yield the same result.] */
#define spellev(spell_otyp) ((int) objects[spell_otyp].oc_level)
energy = 5 * spellev(SPE_TELEPORT_AWAY);
if (break_the_rules) {
if (!castit)
energy = 0;
/* spell will cost more if carrying the Amulet, but the
amount is rnd(2 * energy) so we can't know by how much;
average is twice the normal cost, but could be triple;
the extra energy is spent even if that results in not
having enough to cast (which also uses the move) */
else if (u.uen < energy)
u.uen = energy;
} else if (u.uhunger <= 10) {
cantdoit = "are too weak from hunger";
} else if (ACURR(A_STR) < 4) {
cantdoit = "lack the strength";
} else if (energy > u.uen) {
cantdoit = "lack the energy";
}
if (cantdoit) {
You("%s %s.", cantdoit,
castit ? "for a teleport spell" : "to teleport");
return 0;
} else if (check_capacity(
"Your concentration falters from carrying so much.")) {
return 1; /* this failure in spelleffects() also uses the move */
}
if (castit) {
/* energy cost is deducted in spelleffects() */
exercise(A_WIS, TRUE);
if (spelleffects(sp_no, TRUE))
return 1;
else if (!break_the_rules)
return 0;
} else {
/* bypassing spelleffects(); apply energy cost directly */
u.uen -= energy;
g.context.botl = 1;
}
}
if (next_to_u()) {
if (trap && trap_once)
vault_tele();
else
tele();
(void) next_to_u();
} else {
You("%s", shudder_for_moment);
return 0;
}
if (!trap)
morehungry(100);
return 1;
}
void
level_tele()
{
static const char get_there_from[] = "get there from %s.";
register int newlev;
d_level newlevel;
const char *escape_by_flying = 0; /* when surviving dest of -N */
char buf[BUFSZ];
boolean force_dest = FALSE;
if (iflags.debug_fuzzer)
goto random_levtport;
if ((u.uhave.amulet || In_endgame(&u.uz) || In_sokoban(&u.uz))
&& !wizard) {
You_feel("very disoriented for a moment.");
return;
}
if ((Teleport_control && !Stunned) || wizard) {
char qbuf[BUFSZ];
int trycnt = 0;
Strcpy(qbuf, "To what level do you want to teleport?");
do {
if (iflags.menu_requested) {
/* wizard mode 'm ^V' skips prompting on first pass
(note: level Tport via menu won't have any second pass) */
iflags.menu_requested = FALSE;
if (wizard)
goto levTport_menu;
}
if (++trycnt == 2) {
if (wizard)
Strcat(qbuf, " [type a number, name, or ? for a menu]");
else
Strcat(qbuf, " [type a number or name]");
}
*buf = '\0'; /* EDIT_GETLIN: if we're on second or later pass,
the previous input was invalid so don't use it
as getlin()'s preloaded default answer */
getlin(qbuf, buf);
if (!strcmp(buf, "\033")) { /* cancelled */
if (Confusion && rnl(5)) {
pline("Oops...");
goto random_levtport;
}
return;
} else if (!strcmp(buf, "*")) {
goto random_levtport;
} else if (Confusion && rnl(5)) {
pline("Oops...");
goto random_levtport;
}
if (wizard && !strcmp(buf, "?")) {
schar destlev;
xchar destdnum;
levTport_menu:
destlev = 0;
destdnum = 0;
newlev = (int) print_dungeon(TRUE, &destlev, &destdnum);
if (!newlev)
return;
newlevel.dnum = destdnum;
newlevel.dlevel = destlev;
if (In_endgame(&newlevel) && !In_endgame(&u.uz)) {
struct obj *amu;
if (!u.uhave.amulet
&& (amu = mksobj(AMULET_OF_YENDOR, TRUE, FALSE))
!= 0) {
/* ordinarily we'd use hold_another_object()
for something like this, but we don't want
fumbling or already full pack to interfere */
amu = addinv(amu);
prinv("Endgame prerequisite:", amu, 0L);
}
}
force_dest = TRUE;
} else if ((newlev = lev_by_name(buf)) == 0)
newlev = atoi(buf);
} while (!newlev && !digit(buf[0])
&& (buf[0] != '-' || !digit(buf[1])) && trycnt < 10);
/* no dungeon escape via this route */
if (newlev == 0) {
if (trycnt >= 10)
goto random_levtport;
if (ynq("Go to Nowhere. Are you sure?") != 'y')
return;
You("%s in agony as your body begins to warp...",
is_silent(g.youmonst.data) ? "writhe" : "scream");
display_nhwindow(WIN_MESSAGE, FALSE);
You("cease to exist.");
if (g.invent)
Your("possessions land on the %s with a thud.",
surface(u.ux, u.uy));
g.killer.format = NO_KILLER_PREFIX;
Strcpy(g.killer.name, "committed suicide");
done(DIED);
pline("An energized cloud of dust begins to coalesce.");
Your("body rematerializes%s.",
g.invent ? ", and you gather up all your possessions" : "");
return;
}
/* if in Knox and the requested level > 0, stay put.
* we let negative values requests fall into the "heaven" loop.
*/
if (Is_knox(&u.uz) && newlev > 0 && !force_dest) {
You1(shudder_for_moment);
return;
}
/* if in Quest, the player sees "Home 1", etc., on the status
* line, instead of the logical depth of the level. controlled
* level teleport request is likely to be relativized to the
* status line, and consequently it should be incremented to
* the value of the logical depth of the target level.
*
* we let negative values requests fall into the "heaven" handling.
*/
if (In_quest(&u.uz) && newlev > 0)
newlev = newlev + g.dungeons[u.uz.dnum].depth_start - 1;
} else { /* involuntary level tele */
random_levtport:
newlev = random_teleport_level();
if (newlev == depth(&u.uz)) {
You1(shudder_for_moment);
return;
}
}
if (u.utrap && u.utraptype == TT_BURIEDBALL)
buried_ball_to_punishment();
if (!next_to_u() && !force_dest) {
You1(shudder_for_moment);
return;
}
if (In_endgame(&u.uz)) { /* must already be wizard */
int llimit = dunlevs_in_dungeon(&u.uz);
if (newlev >= 0 || newlev <= -llimit) {
You_cant(get_there_from, "here");
return;
}
newlevel.dnum = u.uz.dnum;
newlevel.dlevel = llimit + newlev;
schedule_goto(&newlevel, UTOTYPE_NONE, (char *) 0, (char *) 0);
return;
}
g.killer.name[0] = 0; /* still alive, so far... */
if (iflags.debug_fuzzer && newlev < 0)
goto random_levtport;
if (newlev < 0 && !force_dest) {
if (*u.ushops0) {
/* take unpaid inventory items off of shop bills */
g.in_mklev = TRUE; /* suppress map update */
u_left_shop(u.ushops0, TRUE);
/* you're now effectively out of the shop */
*u.ushops0 = *u.ushops = '\0';
g.in_mklev = FALSE;
}
if (newlev <= -10) {
You("arrive in heaven.");
verbalize("Thou art early, but we'll admit thee.");
g.killer.format = NO_KILLER_PREFIX;
Strcpy(g.killer.name, "went to heaven prematurely");
} else if (newlev == -9) {
You_feel("deliriously happy.");
pline("(In fact, you're on Cloud 9!)");
display_nhwindow(WIN_MESSAGE, FALSE);
} else
You("are now high above the clouds...");
if (g.killer.name[0]) {
; /* arrival in heaven is pending */
} else if (Levitation) {
escape_by_flying = "float gently down to earth";
} else if (Flying) {
escape_by_flying = "fly down to the ground";
} else {
pline("Unfortunately, you don't know how to fly.");
You("plummet a few thousand feet to your death.");
Sprintf(g.killer.name,
"teleported out of the dungeon and fell to %s death",
uhis());
g.killer.format = NO_KILLER_PREFIX;
}
}
if (g.killer.name[0]) { /* the chosen destination was not survivable */
d_level lsav;
/* set specific death location; this also suppresses bones */
lsav = u.uz; /* save current level, see below */
u.uz.dnum = 0; /* main dungeon */
u.uz.dlevel = (newlev <= -10) ? -10 : 0; /* heaven or surface */
done(DIED);
/* can only get here via life-saving (or declining to die in
explore|debug mode); the hero has now left the dungeon... */
escape_by_flying = "find yourself back on the surface";
u.uz = lsav; /* restore u.uz so escape code works */
}
/* calls done(ESCAPED) if newlevel==0 */
if (escape_by_flying) {
You("%s.", escape_by_flying);
/* [dlevel used to be set to 1, but it doesn't make sense to
teleport out of the dungeon and float or fly down to the
surface but then actually arrive back inside the dungeon] */
newlevel.dnum = 0; /* specify main dungeon */
newlevel.dlevel = 0; /* escape the dungeon */
} else if (force_dest) {
/* wizard mode menu; no further validation needed */
;
} else if (u.uz.dnum == medusa_level.dnum
&& newlev >= g.dungeons[u.uz.dnum].depth_start
+ dunlevs_in_dungeon(&u.uz)) {
find_hell(&newlevel);
} else {
/* FIXME: we should avoid using hard-coded knowledge of
which branches don't connect to anything deeper;
mainly used to distinguish "can't get there from here"
vs "from anywhere" rather than to control destination */
d_level *qbranch = In_quest(&u.uz) ? &qstart_level
: In_mines(&u.uz) ? &mineend_level
: &sanctum_level;
int deepest = g.dungeons[qbranch->dnum].depth_start
+ dunlevs_in_dungeon(qbranch) - 1;
/* if invocation did not yet occur, teleporting into
* the last level of Gehennom is forbidden.
*/
if (!wizard && Inhell && !u.uevent.invoked && newlev >= deepest) {
newlev = deepest - 1;
pline("Sorry...");
}
/* no teleporting out of quest dungeon */
if (In_quest(&u.uz) && newlev < depth(&qstart_level))
newlev = depth(&qstart_level);
/* the player thinks of levels purely in logical terms, so
* we must translate newlev to a number relative to the
* current dungeon.
*/
get_level(&newlevel, newlev);
if (on_level(&newlevel, &u.uz) && newlev != depth(&u.uz)) {
You_cant(get_there_from,
(newlev > deepest) ? "anywhere" : "here");
return;
}
}
schedule_goto(&newlevel, UTOTYPE_NONE, (char *) 0,
flags.verbose ? "You materialize on a different level!"
: (char *) 0);
/* in case player just read a scroll and is about to be asked to
call it something, we can't defer until the end of the turn */
if (u.utotype && !g.context.mon_moving)
deferred_goto();
}
void
domagicportal(ttmp)
register struct trap *ttmp;
{
struct d_level target_level;
if (u.utrap && u.utraptype == TT_BURIEDBALL)
buried_ball_to_punishment();
if (!next_to_u()) {
You1(shudder_for_moment);
return;
}
/* if landed from another portal, do nothing */
/* problem: level teleport landing escapes the check */
if (!on_level(&u.uz, &u.uz0))
return;
You("activated a magic portal!");
/* prevent the poor shnook, whose amulet was stolen while in
* the endgame, from accidently triggering the portal to the
* next level, and thus losing the game
*/
if (In_endgame(&u.uz) && !u.uhave.amulet) {
You_feel("dizzy for a moment, but nothing happens...");
return;
}
target_level = ttmp->dst;
schedule_goto(&target_level, UTOTYPE_PORTAL,
"You feel dizzy for a moment, but the sensation passes.",
(char *) 0);
}
void
tele_trap(trap)
struct trap *trap;
{
if (In_endgame(&u.uz) || Antimagic) {
if (Antimagic)
shieldeff(u.ux, u.uy);
You_feel("a wrenching sensation.");
} else if (!next_to_u()) {
You1(shudder_for_moment);
} else if (trap->once) {
deltrap(trap);
newsym(u.ux, u.uy); /* get rid of trap symbol */
vault_tele();
} else
tele();
}
void
level_tele_trap(trap, trflags)
struct trap *trap;
unsigned trflags;
{
char verbbuf[BUFSZ];
boolean intentional = FALSE;
if ((trflags & (VIASITTING | FORCETRAP)) != 0) {
Strcpy(verbbuf, "trigger"); /* follows "You sit down." */
intentional = TRUE;
} else
Sprintf(verbbuf, "%s onto",
Levitation ? (const char *) "float"
: locomotion(g.youmonst.data, "step"));
You("%s a level teleport trap!", verbbuf);
if (Antimagic && !intentional) {
shieldeff(u.ux, u.uy);
}
if ((Antimagic && !intentional) || In_endgame(&u.uz)) {
You_feel("a wrenching sensation.");
return;
}
if (!Blind)
You("are momentarily blinded by a flash of light.");
else
You("are momentarily disoriented.");
deltrap(trap);
newsym(u.ux, u.uy); /* get rid of trap symbol */
level_tele();
}
/* check whether monster can arrive at location <x,y> via Tport (or fall) */
static boolean
rloc_pos_ok(x, y, mtmp)
register int x, y; /* coordinates of candidate location */
struct monst *mtmp;
{
register int xx, yy;
if (!goodpos(x, y, mtmp, 0))
return FALSE;
/*
* Check for restricted areas present in some special levels.
*
* `xx' is current column; if 0, then `yy' will contain flag bits
* rather than row: bit #0 set => moving upwards; bit #1 set =>
* inside the Wizard's tower.
*/
xx = mtmp->mx;
yy = mtmp->my;
if (!xx) {
/* no current location (migrating monster arrival) */
if (g.dndest.nlx && On_W_tower_level(&u.uz))
return (((yy & 2) != 0)
/* inside xor not within */
^ !within_bounded_area(x, y, g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy));
if (g.updest.lx && (yy & 1) != 0) /* moving up */
return (within_bounded_area(x, y, g.updest.lx, g.updest.ly,
g.updest.hx, g.updest.hy)
&& (!g.updest.nlx
|| !within_bounded_area(x, y,
g.updest.nlx, g.updest.nly,
g.updest.nhx, g.updest.nhy)));
if (g.dndest.lx && (yy & 1) == 0) /* moving down */
return (within_bounded_area(x, y, g.dndest.lx, g.dndest.ly,
g.dndest.hx, g.dndest.hy)
&& (!g.dndest.nlx
|| !within_bounded_area(x, y,
g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy)));
} else {
/* [try to] prevent a shopkeeper or temple priest from being
sent out of his room (caller might resort to goodpos() if
we report failure here, so this isn't full prevention) */
if (mtmp->isshk && inhishop(mtmp)) {
if (levl[x][y].roomno != (unsigned char) ESHK(mtmp)->shoproom)
return FALSE;
} else if (mtmp->ispriest && inhistemple(mtmp)) {
if (levl[x][y].roomno != (unsigned char) EPRI(mtmp)->shroom)
return FALSE;
}
/* current location is <xx,yy> */
if (!tele_jump_ok(xx, yy, x, y))
return FALSE;
}
/* <x,y> is ok */
return TRUE;
}
/*
* rloc_to()
*
* Pulls a monster from its current position and places a monster at
* a new x and y. If oldx is 0, then the monster was not in the
* levels.monsters array. However, if oldx is 0, oldy may still have
* a value because mtmp is a migrating_mon. Worm tails are always
* placed randomly around the head of the worm.
*/
void
rloc_to(mtmp, x, y)
struct monst *mtmp;
register int x, y;
{
register int oldx = mtmp->mx, oldy = mtmp->my;
boolean resident_shk = mtmp->isshk && inhishop(mtmp);
if (x == mtmp->mx && y == mtmp->my && m_at(x, y) == mtmp)
return; /* that was easy */
if (oldx) { /* "pick up" monster */
if (mtmp->wormno) {
remove_worm(mtmp);
} else {
remove_monster(oldx, oldy);
newsym(oldx, oldy); /* update old location */
}
}
memset(mtmp->mtrack, 0, sizeof mtmp->mtrack);
place_monster(mtmp, x, y); /* put monster down */
update_monster_region(mtmp);
if (mtmp->wormno) /* now put down tail */
place_worm_tail_randomly(mtmp, x, y);
if (u.ustuck == mtmp) {
if (u.uswallow) {
u_on_newpos(mtmp->mx, mtmp->my);
docrt();
} else if (distu(mtmp->mx, mtmp->my) > 2) {
unstuck(mtmp);
}
}
newsym(x, y); /* update new location */
set_apparxy(mtmp); /* orient monster */
/* shopkeepers will only teleport if you zap them with a wand of
teleportation or if they've been transformed into a jumpy monster;
the latter only happens if you've attacked them with polymorph */
if (resident_shk && !inhishop(mtmp))
make_angry_shk(mtmp, oldx, oldy);
/* trapped monster teleported away */
if (mtmp->mtrapped && !mtmp->wormno)
(void) mintrap(mtmp);
}
static stairway *
stairway_find_forwiz(isladder, up)
boolean isladder, up;
{
stairway *stway = g.stairs;
while (stway && !(stway->isladder == isladder
&& stway->up == up && stway->tolev.dnum == u.uz.dnum))
stway = stway->next;
return stway;
}
/* place a monster at a random location, typically due to teleport */
/* return TRUE if successful, FALSE if not */
boolean
rloc(mtmp, suppress_impossible)
struct monst *mtmp; /* mx==0 implies migrating monster arrival */
boolean suppress_impossible;
{
register int x, y, trycount;
stairway *stway;
if (mtmp == u.usteed) {
tele();
return TRUE;
}
if (mtmp->iswiz && mtmp->mx) { /* Wizard, not just arriving */
if (!In_W_tower(u.ux, u.uy, &u.uz)) {
stway = stairway_find_forwiz(FALSE, TRUE);
x = stway->sx;
y = stway->sy;
} else if (!stairway_find_forwiz(TRUE, FALSE)) { /* bottom level of tower */
stway = stairway_find_forwiz(TRUE, TRUE);
x = stway->sx;
y = stway->sy;
} else {
stway = stairway_find_forwiz(TRUE, FALSE);
x = stway->sx;
y = stway->sy;
}
/* if the wiz teleports away to heal, try the up staircase,
to block the player's escaping before he's healed
(deliberately use `goodpos' rather than `rloc_pos_ok' here) */
if (goodpos(x, y, mtmp, 0))
goto found_xy;
}
trycount = 0;
do {
x = rn1(COLNO - 3, 2);
y = rn2(ROWNO);
if ((trycount < 500) ? rloc_pos_ok(x, y, mtmp)
: goodpos(x, y, mtmp, 0))
goto found_xy;
} while (++trycount < 1000);
/* last ditch attempt to find a good place */
for (x = 2; x < COLNO - 1; x++)
for (y = 0; y < ROWNO; y++)
if (goodpos(x, y, mtmp, 0))
goto found_xy;
/* level either full of monsters or somehow faulty */
if (!suppress_impossible)
impossible("rloc(): couldn't relocate monster");
return FALSE;
found_xy:
rloc_to(mtmp, x, y);
return TRUE;
}
static void
mvault_tele(mtmp)
struct monst *mtmp;
{
struct mkroom *croom = search_special(VAULT);
coord c;
if (croom && somexy(croom, &c) && goodpos(c.x, c.y, mtmp, 0)) {
rloc_to(mtmp, c.x, c.y);
return;
}
(void) rloc(mtmp, TRUE);
}
boolean
tele_restrict(mon)
struct monst *mon;
{
if (noteleport_level(mon)) {
if (canseemon(mon))
pline("A mysterious force prevents %s from teleporting!",
mon_nam(mon));
return TRUE;
}
return FALSE;
}
void
mtele_trap(mtmp, trap, in_sight)
struct monst *mtmp;
struct trap *trap;
int in_sight;
{
char *monname;
if (tele_restrict(mtmp))
return;
if (teleport_pet(mtmp, FALSE)) {
/* save name with pre-movement visibility */
monname = Monnam(mtmp);
/* Note: don't remove the trap if a vault. Other-
* wise the monster will be stuck there, since
* the guard isn't going to come for it...
*/
if (trap->once)
mvault_tele(mtmp);
else
(void) rloc(mtmp, TRUE);
if (in_sight) {
if (canseemon(mtmp))
pline("%s seems disoriented.", monname);
else
pline("%s suddenly disappears!", monname);
seetrap(trap);
}
}
}
/* return 0 if still on level, 3 if not */
int
mlevel_tele_trap(mtmp, trap, force_it, in_sight)
struct monst *mtmp;
struct trap *trap;
boolean force_it;
int in_sight;
{
int tt = (trap ? trap->ttyp : NO_TRAP);
if (mtmp == u.ustuck) /* probably a vortex */
return 0; /* temporary? kludge */
if (teleport_pet(mtmp, force_it)) {
d_level tolevel;
int migrate_typ = MIGR_RANDOM;
if (is_hole(tt)) {
if (Is_stronghold(&u.uz)) {
assign_level(&tolevel, &valley_level);
} else if (Is_botlevel(&u.uz)) {
if (in_sight && trap->tseen)
pline("%s avoids the %s.", Monnam(mtmp),
(tt == HOLE) ? "hole" : "trap");
return 0;
} else {
get_level(&tolevel, depth(&u.uz) + 1);
}
} else if (tt == MAGIC_PORTAL) {
if (In_endgame(&u.uz) && (mon_has_amulet(mtmp)
|| is_home_elemental(mtmp->data)
|| rn2(7))) {
if (in_sight && mtmp->data->mlet != S_ELEMENTAL) {
pline("%s seems to shimmer for a moment.", Monnam(mtmp));
seetrap(trap);
}
return 0;
} else {
assign_level(&tolevel, &trap->dst);
migrate_typ = MIGR_PORTAL;
}
} else if (tt == LEVEL_TELEP || tt == NO_TRAP) {
int nlev;
if (mon_has_amulet(mtmp) || In_endgame(&u.uz)
/* NO_TRAP is used when forcing a monster off the level;
onscary(0,0,) is true for the Wizard, Riders, lawful
minions, Angels of any alignment, shopkeeper or priest
currently inside his or her own special room */
|| (tt == NO_TRAP && onscary(0, 0, mtmp))) {
if (in_sight)
pline("%s seems very disoriented for a moment.",
Monnam(mtmp));
return 0;
}
if (tt == NO_TRAP) {
/* creature is being forced off the level to make room;
it will try to return to this level (at a random spot
rather than its current one) if the level is left by
the hero and then revisited */
assign_level(&tolevel, &u.uz);
} else {
nlev = random_teleport_level();
if (nlev == depth(&u.uz)) {
if (in_sight)
pline("%s shudders for a moment.", Monnam(mtmp));
return 0;
}
get_level(&tolevel, nlev);
}
} else {
impossible("mlevel_tele_trap: unexpected trap type (%d)", tt);
return 0;
}
if (in_sight) {
pline("Suddenly, %s disappears out of sight.", mon_nam(mtmp));
if (trap)
seetrap(trap);
}
migrate_to_level(mtmp, ledger_no(&tolevel), migrate_typ, (coord *) 0);
return 3; /* no longer on this level */
}
return 0;
}
/* place object randomly, returns False if it's gone (eg broken) */
boolean
rloco(obj)
register struct obj *obj;
{
register xchar tx, ty, otx, oty;
boolean restricted_fall;
int try_limit = 4000;
if (obj->otyp == CORPSE && is_rider(&mons[obj->corpsenm])) {
if (revive_corpse(obj))
return FALSE;
}
obj_extract_self(obj);
otx = obj->ox;
oty = obj->oy;
restricted_fall = (otx == 0 && g.dndest.lx);
do {
tx = rn1(COLNO - 3, 2);
ty = rn2(ROWNO);
if (!--try_limit)
break;
} while (!goodpos(tx, ty, (struct monst *) 0, 0)
|| (restricted_fall
&& (!within_bounded_area(tx, ty, g.dndest.lx, g.dndest.ly,
g.dndest.hx, g.dndest.hy)
|| (g.dndest.nlx
&& within_bounded_area(tx, ty,
g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy))))
/* on the Wizard Tower levels, objects inside should
stay inside and objects outside should stay outside */
|| (g.dndest.nlx && On_W_tower_level(&u.uz)
&& within_bounded_area(tx, ty, g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy)
!= within_bounded_area(otx, oty,
g.dndest.nlx, g.dndest.nly,
g.dndest.nhx, g.dndest.nhy)));
if (flooreffects(obj, tx, ty, "fall")) {
/* update old location since flooreffects() couldn't;
unblock_point() for boulder handled by obj_extract_self() */
newsym(otx, oty);
return FALSE;
} else if (otx == 0 && oty == 0) {
; /* fell through a trap door; no update of old loc needed */
} else {
if (costly_spot(otx, oty)
&& (!costly_spot(tx, ty)
|| !index(in_rooms(tx, ty, 0), *in_rooms(otx, oty, 0)))) {
if (costly_spot(u.ux, u.uy)
&& index(u.urooms, *in_rooms(otx, oty, 0)))
addtobill(obj, FALSE, FALSE, FALSE);
else
(void) stolen_value(obj, otx, oty, FALSE, FALSE);
}
newsym(otx, oty); /* update old location */
}
place_object(obj, tx, ty);
/* note: block_point() for boulder handled by place_object() */
newsym(tx, ty);
return TRUE;
}
/* Returns an absolute depth */
int
random_teleport_level()
{
int nlev, max_depth, min_depth, cur_depth = (int) depth(&u.uz);
/* [the endgame case can only occur in wizard mode] */
if (!rn2(5) || Is_knox(&u.uz) || In_endgame(&u.uz))
return cur_depth;
/* What I really want to do is as follows:
* -- If in a dungeon that goes down, the new level is to be restricted
* to [top of parent, bottom of current dungeon]
* -- If in a dungeon that goes up, the new level is to be restricted
* to [top of current dungeon, bottom of parent]
* -- If in a quest dungeon or similar dungeon entered by portals,
* the new level is to be restricted to [top of current dungeon,
* bottom of current dungeon]
* The current behavior is not as sophisticated as that ideal, but is
* still better what we used to do, which was like this for players
* but different for monsters for no obvious reason. Currently, we
* must explicitly check for special dungeons. We check for Knox
* above; endgame is handled in the caller due to its different
* message ("disoriented").
* --KAA
* 3.4.2: explicitly handle quest here too, to fix the problem of
* monsters sometimes level teleporting out of it into main dungeon.
* Also prevent monsters reaching the Sanctum prior to invocation.
*/
if (In_quest(&u.uz)) {
int bottom = dunlevs_in_dungeon(&u.uz),
qlocate_depth = qlocate_level.dlevel;
/* if hero hasn't reached the middle locate level yet,
no one can randomly teleport past it */
if (dunlev_reached(&u.uz) < qlocate_depth)
bottom = qlocate_depth;
min_depth = g.dungeons[u.uz.dnum].depth_start;
max_depth = bottom + (g.dungeons[u.uz.dnum].depth_start - 1);
} else {
min_depth = 1;
max_depth = dunlevs_in_dungeon(&u.uz)
+ (g.dungeons[u.uz.dnum].depth_start - 1);
/* can't reach Sanctum if the invocation hasn't been performed */
if (Inhell && !u.uevent.invoked)
max_depth -= 1;
}
/* Get a random value relative to the current dungeon */
/* Range is 1 to current+3, current not counting */
nlev = rn2(cur_depth + 3 - min_depth) + min_depth;
if (nlev >= cur_depth)
nlev++;
if (nlev > max_depth) {
nlev = max_depth;
/* teleport up if already on bottom */
if (Is_botlevel(&u.uz))
nlev -= rnd(3);
}
if (nlev < min_depth) {
nlev = min_depth;
if (nlev == cur_depth) {
nlev += rnd(3);
if (nlev > max_depth)
nlev = max_depth;
}
}
return nlev;
}
/* you teleport a monster (via wand, spell, or poly'd q.mechanic attack);
return false iff the attempt fails */
boolean
u_teleport_mon(mtmp, give_feedback)
struct monst *mtmp;
boolean give_feedback;
{
coord cc;
if (mtmp->ispriest && *in_rooms(mtmp->mx, mtmp->my, TEMPLE)) {
if (give_feedback)
pline("%s resists your magic!", Monnam(mtmp));
return FALSE;
} else if (u.uswallow && mtmp == u.ustuck && noteleport_level(mtmp)) {
if (give_feedback)
You("are no longer inside %s!", mon_nam(mtmp));
unstuck(mtmp);
(void) rloc(mtmp, TRUE);
} else if (is_rider(mtmp->data) && rn2(13)
&& enexto(&cc, u.ux, u.uy, mtmp->data))
rloc_to(mtmp, cc.x, cc.y);
else
(void) rloc(mtmp, TRUE);
return TRUE;
}
/*teleport.c*/