Files
nethack/src/teleport.c
Alex Smith 07fc4904c6 Add a new wand, the wand of stasis
A wand of stasis prevents teleportation (even in some cases where
it would normally not be prevented, e.g. the hero teleporting a
monster, or covetous monsters teleporting). This is intended to
provide an alternative tactic against covetous monsters (and their
AI has been adjusted to handle being under a stasis effect), but
might also be useful in other situations. It does not prevent
teleportation of objects, only the hero / monsters, and does not
at present prevent level teleportation (although I'm not sure about
this and it might well change in the future).

This breaks save compatibility, but is being pushed together with
other save-breaking changes to avoid the need for multiple bumps to
EDITLEVEL.
2026-03-19 01:16:30 +00:00

2294 lines
86 KiB
C

/* NetHack 3.7 teleport.c $NHDT-Date: 1769342601 2026/01/25 04:03:21 $ $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.239 $ */
/* 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"
#define NEW_ENEXTO
staticfn boolean goodpos_onscary(coordxy, coordxy, struct permonst *);
staticfn boolean tele_jump_ok(coordxy, coordxy, coordxy, coordxy);
staticfn boolean teleok(coordxy, coordxy, boolean);
staticfn void vault_tele(void);
staticfn boolean rloc_pos_ok(coordxy, coordxy, struct monst *);
staticfn void rloc_to_core(struct monst *, coordxy, coordxy, unsigned);
staticfn void mvault_tele(struct monst *);
staticfn boolean m_blocks_teleporting(struct monst *);
staticfn stairway *stairway_find_forwiz(boolean, boolean);
/* does monster block others from teleporting? */
staticfn boolean
m_blocks_teleporting(struct monst *mtmp)
{
if (is_dlord(mtmp->data) || is_dprince(mtmp->data))
return TRUE;
return FALSE;
}
/* teleporting is prevented on this level for this monster? */
boolean
noteleport_level(struct monst *mon)
{
/* demon court in Gehennom prevent others from teleporting */
if (In_hell(&u.uz) && !(is_dlord(mon->data) || is_dprince(mon->data)))
if (get_iter_mons(m_blocks_teleporting))
return TRUE;
/* natural no-teleport level; covetous monsters can bypass these */
if (svl.level.flags.noteleport && !is_covetous(mon->data))
return TRUE;
/* wand of stasis prevents teleportation while the effect is active
(even for covetous monsters) */
if (svl.level.flags.stasis_until >= svm.moves)
return TRUE;
return FALSE;
}
/* this is an approximation of onscary() that doesn't use any 'struct monst'
fields aside from 'monst->data'; used primarily for new monster creation
and monster teleport destination, not for ordinary monster movement */
staticfn boolean
goodpos_onscary(
coordxy x, coordxy y,
struct permonst *mptr)
{
/* onscary() checks Angels and lawful minions; this oversimplifies */
if (mptr->mlet == S_HUMAN || mptr->mlet == S_ANGEL
|| is_rider(mptr) || unique_corpstat(mptr))
return FALSE;
/* onscary() checks for vampshifted vampire bats/fog clouds/wolves too */
if (IS_ALTAR(levl[x][y].typ) && mptr->mlet == S_VAMPIRE)
return TRUE;
/* scare monster scroll doesn't have any of the below restrictions,
being its own source of power */
if (sobj_at(SCR_SCARE_MONSTER, x, y))
return TRUE;
/* engraved Elbereth doesn't work in Gehennom or the end-game */
if (Inhell || In_endgame(&u.uz))
return FALSE;
/* creatures who don't (or can't) fear a written Elbereth and weren't
caught by the minions check */
if (mptr == &mons[PM_MINOTAUR] || !haseyes(mptr))
return FALSE;
return sengr_at("Elbereth", x, y, TRUE) ? TRUE : 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(
coordxy x, coordxy y,
struct monst *mtmp,
mmflags_nht gpflags)
{
struct permonst *mdat = (struct permonst *) 0;
boolean ignorewater = ((gpflags & MM_IGNOREWATER) != 0),
ignorelava = ((gpflags & MM_IGNORELAVA) != 0),
checkscary = ((gpflags & GP_CHECKSCARY) != 0),
allow_u = ((gpflags & GP_ALLOW_U) != 0),
avoid_monpos = ((gpflags & GP_AVOID_MONPOS) != 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 (u_at(x, y) && mtmp != &gy.youmonst
&& (mtmp != u.ustuck || !u.uswallow)
&& (!u.usteed || mtmp != u.usteed))
return FALSE;
}
if (MON_AT(x, y) && avoid_monpos)
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 == &gy.youmonst)
return (Swimming || Amphibious
|| (!Is_waterlevel(&u.uz)
&& !is_waterwall(x, y)
/* 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_waterwall(x, y)
&& m_in_air(mtmp)));
} else if (mdat->mlet == S_EEL && rn2(13) && !ignorewater) {
return FALSE;
} else if (is_lava(x, y) && !ignorelava) {
/* 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 == &gy.youmonst)
return (Levitation || Flying
|| (Fire_resistance && Wwalking && uarmf
&& uarmf->oerodeproof)
|| (Upolyd && likes_lava(gy.youmonst.data)));
else
return (m_in_air(mtmp) || likes_lava(mdat));
}
if (passes_walls(mdat) && may_passwall(x, y))
return TRUE;
if (amorphous(mdat) && closed_door(x, y))
return TRUE;
/* avoid onscary() if caller has specified that restriction */
if (checkscary && (mtmp->m_id ? onscary(x, y, mtmp)
: goodpos_onscary(x, y, mdat)))
return FALSE;
}
if (!accessible(x, y)) {
if (!(is_pool(x, y) && ignorewater)
&& !(is_lava(x, y) && ignorelava))
return FALSE;
}
/* skip boulder locations for most creatures */
if (sobj_at(BOULDER, x, y) && (!mdat || !throws_rocks(mdat)))
return FALSE;
/* pretend GP_AVOID_MONPOS == monster creation */
if (avoid_monpos && is_exclusion_zone(LR_MONGEN, x, y))
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(
coord *cc,
coordxy xx, coordxy yy,
struct permonst *mdat)
{
return (enexto_core(cc, xx, yy, mdat, GP_CHECKSCARY)
|| enexto_core(cc, xx, yy, mdat, NO_MM_FLAGS));
}
boolean
enexto_gpflags(
coord *cc,
coordxy xx, coordxy yy,
struct permonst *mdat,
mmflags_nht entflags)
{
return (enexto_core(cc, xx, yy, mdat, GP_CHECKSCARY | entflags)
|| enexto_core(cc, xx, yy, mdat, entflags));
}
#ifdef NEW_ENEXTO
boolean
enexto_core(
coord *cc, /* output; <cc.x,cc.y> as close as feasible to <xx,yy> */
coordxy xx, coordxy yy, /* input coordinates */
struct permonst *mdat, /* type of monster; affects whether water or
* lava or boulder spots will be considered */
mmflags_nht entflags) /* flags for goodpos() */
{
coord candy[ROWNO * (COLNO - 1)]; /* enough room for every location */
int i, nearcandyct, allcandyct;
struct monst fakemon; /* dummy monster */
boolean allow_xx_yy = (boolean) ((entflags & GP_ALLOW_XY) != 0);
/* note: GP_ALLOW_XY isn't used by goodpos(); old enext_core() used to
mask it off to hide it from goodpos but that isn't required and we
want to keep it in case the debugpline4() gets called */
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() */
/* gather candidate coordinates within 3 steps, those 1 step away in
random order first, then those 2 steps away in random order, then 3;
this will usually find a good spot without scanning the whole map */
nearcandyct = collect_coords(candy, xx, yy, 3, CC_NO_FLAGS,
(boolean (*)(coordxy, coordxy)) 0);
for (i = 0; i < nearcandyct; ++i) {
*cc = candy[i];
if (goodpos(cc->x, cc->y, &fakemon, entflags))
return TRUE;
}
/* didn't find a spot; gather coordinates for the whole map except
for <xx,yy> itself, ordered in expanding distance from <xx,yy>
(subsets of equal distance grouped together with order randomized) */
allcandyct = collect_coords(candy, xx, yy, 0, CC_NO_FLAGS,
(boolean (*)(coordxy, coordxy)) 0);
/* skip first 'nearcandyct' spots, they have already been rejected;
they will occur in different random order but same overall total */
for (i = nearcandyct; i < allcandyct; ++i) {
*cc = candy[i];
if (goodpos(cc->x, cc->y, &fakemon, entflags))
return TRUE;
}
/* still didn't find a spot; maybe try <xx,yy> itself */
cc->x = xx, cc->y = yy; /* final value for 'cc' in case we return False */
if (allow_xx_yy && goodpos(cc->x, cc->y, &fakemon, entflags))
return TRUE;
/* failed to find any acceptable spot */
debugpline4("enexto(\"%s\",%d,%d,0x%08lx) failed",
mdat->pmnames[NEUTRAL], (int) xx, (int) yy,
(unsigned long) entflags);
return FALSE;
}
#else /* !NEW_ENEXTO */
boolean
enexto_core(
coord *cc,
coordxy xx, coordxy yy,
struct permonst *mdat,
mmflags_nht entflags)
{
#define MAX_GOOD 15
coord good[MAX_GOOD], *good_ptr;
coordxy x, y, range, i;
coordxy 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->pmnames[NEUTRAL], (int) xx, (int) yy);
return FALSE;
}
}
full:
/* we've got between 1 and SIZE(good) candidates; choose one */
i = (coordxy) rn2((int) (good_ptr - good));
cc->x = good[i].x;
cc->y = good[i].y;
return TRUE;
#undef MAX_GOOD
}
#endif /* ?NEW_ENEXTO */
/*
* 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.)
*/
staticfn boolean
tele_jump_ok(coordxy x1, coordxy y1, coordxy x2, coordxy y2)
{
if (!isok(x2, y2))
return FALSE;
if (svd.dndest.nlx > 0) {
/* if inside a restricted region, can't teleport outside */
if (within_bounded_area(x1, y1, svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy)
&& !within_bounded_area(x2, y2, svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy))
return FALSE;
/* and if outside, can't teleport inside */
if (!within_bounded_area(x1, y1, svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy)
&& within_bounded_area(x2, y2, svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy))
return FALSE;
}
if (svu.updest.nlx > 0) { /* ditto */
if (within_bounded_area(x1, y1, svu.updest.nlx, svu.updest.nly,
svu.updest.nhx, svu.updest.nhy)
&& !within_bounded_area(x2, y2, svu.updest.nlx, svu.updest.nly,
svu.updest.nhx, svu.updest.nhy))
return FALSE;
if (!within_bounded_area(x1, y1, svu.updest.nlx, svu.updest.nly,
svu.updest.nhx, svu.updest.nhy)
&& within_bounded_area(x2, y2, svu.updest.nlx, svu.updest.nly,
svu.updest.nhx, svu.updest.nhy))
return FALSE;
}
return TRUE;
}
staticfn boolean
teleok(coordxy x, coordxy y, boolean trapok)
{
if (!trapok) {
/* allow teleportation onto vibrating square, it's not a real trap;
also allow pits and holes if levitating or flying */
struct trap *trap = t_at(x, y);
if (!trap)
trapok = TRUE;
else if (trap->ttyp == VIBRATING_SQUARE)
trapok = TRUE;
else if ((is_pit(trap->ttyp) || is_hole(trap->ttyp))
&& (Levitation || Flying))
trapok = TRUE;
if (!trapok)
return FALSE;
}
if (!goodpos(x, y, &gy.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(coordxy nux, coordxy nuy, int teleds_flags)
{
unsigned was_swallowed;
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);
was_swallowed = u.uswallow; /* set_ustuck(Null) clears uswallow */
set_ustuck((struct monst *) 0);
u.ux0 = u.ux;
u.uy0 = u.uy;
if (!hideunder(&gy.youmonst) && gy.youmonst.data->mlet == S_MIMIC) {
/* mimics stop being unnoticed */
gy.youmonst.m_ap_type = M_AP_NOTHING;
}
if (was_swallowed) {
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;
coordxy 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' */
/* uball might've been cleared via drag_ball -> spoteffects ->
dotrap -> magic trap unpunishment */
ball_active = (Punished && uball->where != OBJ_FREE);
if (ball_active)
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 */
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();
gv.vision_full_recalc = 1;
nomul(0);
notice_mon_off();
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();
notice_mon_on();
notice_all_mons(TRUE);
return;
}
/* make a list of coordinates in expanding distance from <cx,cy>;
return value is number of coordinates inserted into ccc[] */
int
collect_coords(
coord *ccc, /* pointer to array of at least size ROWNO*(COLNO-1) */
coordxy cx, coordxy cy, /* center point, not necessarily <u.ux,u.uy> */
int maxradius, /* how far from center to go collecting spots;
* 0 means collect entire map */
unsigned cc_flags, /* incl_center: put <cx,cy> in output list
* (provided that it passes filter, if any);
* unshuffled: keep output in collection order;
* ring_pairs: shuffle pairs of rings together
* instead of keeping each ring distinct;
* skip_mons: reject occupied spots;
* skip_inaccs: reject !ZAP_POS() spots */
boolean (*filter)(coordxy, coordxy)) /* if Null, no filtering */
{
coordxy x, y, lox, hix, loy, hiy;
int radius, rowrange, colrange, k, n = 0;
coord cc, *passcc = NULL;
boolean newpass, passend,
/* is <cx,cy> a candidate? */
include_cxcy = (cc_flags & CC_INCL_CENTER) != 0,
/* flag is negative; turn local variable into positive */
scramble = (cc_flags & CC_UNSHUFFLED) == 0,
/* if scrambling, shuffle rings 1+2, 3+4, &c together */
ring_pairs = (scramble && (cc_flags & CC_RING_PAIRS) != 0),
/* exclude locations containing monsters from output */
skip_mons = (cc_flags & CC_SKIP_MONS) != 0,
/* exclude !ZAP_POS() locations from output; allows pools+lava */
skip_inaccessible = (cc_flags & CC_SKIP_INACCS) != 0;
int result = 0;
rowrange = (cy < ROWNO / 2) ? (ROWNO - 1 - cy) : cy;
colrange = (cx < COLNO / 2) ? (COLNO - 1 - cx) : cx;
k = max(rowrange, colrange);
/* if no radius limit has been specified, cover the whole map */
if (!maxradius)
maxradius = k;
else
maxradius = min(maxradius, k);
/*
* We operate on an expanding radius around the center, optionally
* starting with the center spot itself, and shuffle the edges of
* each expanding square or "ring". (So all 1's are shuffled at
* end of the pass for radius==1, then all 2's at end of radius==2,
* and so on. Shuffling of each ring doesn't encroach on any of
* the others except when ring_pairs mode is specified.)
*
* Diagram of first three rings (four if 'include_cxcy' is specified)
* rings unshuffled output sample shuffled output (varies)
* 3333333 25 26 . . . . 31 33 29 . . . . 44
* 3222223 32 9 10 11 12 13 33 35 22 16 14 24 13 40
* 3211123 34 14 1 2 3 15 . 38 20 2 8 3 15 .
* 3210123 . 16 4 0 5 17 . . 11 1 0 6 9 .
* 3211123 . 18 6 7 8 19 39 . 19 5 7 4 12 25
* 3222223 40 20 21 22 23 24 41 43 10 21 17 23 18 27
* 3333333 42 . . . . 47 48 37 . . . . 30 36
* . == entry not shown to reduce clutter when viewing inner portion.
*
* The digits under 'rings' are ring number, which is also distmin
* from center, indicating the order in which sets of spots are
* evaluated. Output gets collected in expanding rings. For the
* two output grids, the number shown represents the position in the
* returned list of coordinates. When shuffling while ring_pairs is
* specified, the 1's and 2's will be mixed together, the 3's and
* (unshown) 4's will be mixed together, and so forth.
*
* If caller processes the output list in order, the closest viable
* spot will be chosen. If a completely random spot is preferred,
* the list can be requested to be unscrambled and then the caller
* can shuffle it, overriding the collection rings. A filter function
* could be used to skip everything after the first acceptable spot.
* 'ring_pairs' mode allows for choosing a very close spot that isn't
* immediately adjacent to the center point, useful for emergency
* teleport to not always end up at the closest possible spot.
*
* TODO:
* Redo filter interface to have caller pass in a context cookie
* that can be passed through to filter so that it could have access
* to more info than just <x,y>. Also add a way to stop collecting
* when an optimal value is found, without checking and skipping the
* rest of the map.
*/
for (radius = include_cxcy ? 0 : 1; radius <= maxradius; ++radius) {
if (!ring_pairs) {
newpass = passend = TRUE;
} else {
/* 0 (if include_cxcy) and maxradius override odd/even */
newpass = ((radius % 2) != 0 || radius == 0); /* odd */
passend = ((radius % 2) == 0 || radius == maxradius); /* even */
}
if (newpass || !passcc) { /* !passcc is redundant but used to fend
* off analyzers thinking use of passcc
* below might occur while still Null */
passcc = ccc; /* start of output entries for current radius (or
* first half of radius pair depending on flags) */
n = 0; /* number of entries for passcc; used for shuffling */
}
lox = cx - radius, hix = cx + radius;
loy = cy - radius, hiy = cy + radius;
for (y = max(loy, 0); y <= hiy; ++y) {
if (y > ROWNO - 1)
break; /* done with collection for current radius */
for (x = max(lox, 1); x <= hix; ++x) { /* column 0 is not used */
if (x > COLNO - 1)
break; /* advance to next 'y' */
if (x != lox && x != hix && y != loy && y != hiy)
continue; /* not any edge of ring/square */
if ((skip_mons && m_at(x, y))
/* note: !ACCESSIBLE() would reject water and lava;
!ZAP_POS() accepts them; caller needs to handle such */
|| (skip_inaccessible && !ZAP_POS(levl[x][y].typ)))
continue; /* quick filters */
if (filter && !(*filter)(x, y))
continue;
cc.x = x, cc.y = y;
*ccc++ = cc;
++n;
++result;
}
}
if (scramble && passend) {
/* shuffle entries gathered for current radius (or pair) */
while (n > 1) {
k = rn2(n); /* 0..n-1 */
if (k) { /* swap [k] with [0] when k is 1..n-1 */
cc = passcc[0];
passcc[0] = passcc[k];
passcc[k] = cc;
}
++passcc; /* passcc[0] has reached its final place */
--n; /* and become exempt from further shuffling */
}
}
} /* radius loop */
debugpline4("collect_coords(,%d,%d,%d,,)=%d", cx, cy, maxradius, result);
return result;
}
/* [try to] teleport hero to a safe spot */
boolean
safe_teleds(int teleds_flags)
{
coordxy nux, nuy;
unsigned cc_flags;
coord candy[ROWNO * (COLNO - 1)], backupspot;
int tcnt, candycount;
/*
* This used to try random locations up to 400 times, with first 200
* tries disallowing trap locations and remaining 200 accepting such.
* On levels without many accessible locations (either due to being
* mostly stone or high monster population) it could fail to find a
* spot.
*
* Now it tries completely randomly only 40 times, all disallowing
* traps, then resorts to checking the entire map, near hero's spot
* first then expanding out from there. If no non-trap spot is found,
* first trap spot is used.
*/
for (tcnt = 0; tcnt < 40; ++tcnt) {
nux = rnd(COLNO - 1);
nuy = rn2(ROWNO);
if (teleok(nux, nuy, FALSE)) {
teleds(nux, nuy, teleds_flags);
return TRUE;
}
}
/* get a shuffled list of candidate locations, starting with spots
1 or 2 steps from hero, then 3 or 4 steps, then 5 or 6, on up */
cc_flags = CC_RING_PAIRS | CC_SKIP_MONS;
if (!Passes_walls)
cc_flags |= CC_SKIP_INACCS;
candycount = collect_coords(candy, u.ux, u.uy, 0, cc_flags,
(boolean (*)(coordxy, coordxy)) 0);
backupspot.x = backupspot.y = 0;
/* skip trap locations via teleok(,,FALSE) but remember first
encountered trap spot that is acceptable to teleok(,,TRUE) */
for (tcnt = 0; tcnt < candycount; ++tcnt) {
nux = candy[tcnt].x, nuy = candy[tcnt].y;
if (teleok(nux, nuy, FALSE)) {
teleds(nux, nuy, teleds_flags);
return TRUE;
}
if (!backupspot.x && t_at(nux, nuy) && teleok(nux, nuy, TRUE))
backupspot.x = nux, backupspot.y = nuy;
}
/* no non-trap spot found; if we skipped a viable trap spot, use it */
if (backupspot.x) {
teleds(backupspot.x, backupspot.y, teleds_flags);
return TRUE;
}
return FALSE;
}
staticfn void
vault_tele(void)
{
struct mkroom *croom = search_special(VAULT);
coord c;
if (croom && somexyspace(croom, &c) && teleok(c.x, c.y, FALSE)) {
teleds(c.x, c.y, TELEDS_TELEPORT);
return;
}
tele();
}
boolean
teleport_pet(struct monst *mtmp, boolean force_it)
{
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 to random pet, if valid location next to it */
void
tele_to_rnd_pet(void)
{
struct monst *mtmp, *pet = (struct monst *) 0;
int cnt = 0;
if (noteleport_level(&gy.youmonst)) {
impossible("%s", "attempt to teleport hero to be near a pet"
" on no-teleport level");
return;
}
for (mtmp = fmon; mtmp; mtmp = mtmp->nmon)
if (!DEADMONSTER(mtmp) && mtmp->mtame && !mon_offmap(mtmp)) {
cnt++;
if (!rn2(cnt))
pet = mtmp;
}
if (pet && !m_next2u(pet)) {
coordxy tx = pet->mx + rn2(3) - 1,
ty = pet->my + rn2(3) - 1;
if (isok(tx, ty) && teleok(tx, ty, FALSE))
teleds(tx, ty, TELEDS_TELEPORT);
}
}
/* teleport the hero via some method other than scroll of teleport */
void
tele(void)
{
scrolltele((struct obj *) 0);
}
/* teleport the hero; usually discover scroll of teleportation if via scroll */
void
scrolltele(struct obj *scroll)
{
coord cc;
/* Disable teleportation in stronghold && Vlad's Tower */
if (noteleport_level(&gy.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 || y_n("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 (u_at(iflags.travelcc.x, iflags.travelcc.y))
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);
}
/* the #teleport command; 'm ^T' == choose among several teleport modes */
int
dotelecmd(void)
{
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) ? ECMD_TIME : ECMD_OK;
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, clr = NO_COLOR;
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, &nul_glyphinfo, &any, (char) any.a_int, 0,
ATR_NONE, clr, 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 ECMD_OK;
}
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 ? ECMD_TIME : ECMD_OK;
#undef NOOP_SPELL
#undef HIDE_SPELL
#undef ADD_SPELL
#undef UNHIDESPELL
#undef REMOVESPELL
}
int
dotele(
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 (y_n("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 (y_n("Jump in?") == 'n') {
trap = 0;
} else {
deltrap(trap);
newsym(u.ux, u.uy);
}
}
if (trap)
You("%s onto the teleportation trap.", u_locomotion("jump"));
} else
trap = 0;
}
if (!trap && !break_the_rules) {
boolean castit = FALSE;
int energy = 0;
if (!Teleportation || (u.ulevel < (Role_if(PM_WIZARD) ? 8 : 12)
&& !can_teleport(gy.youmonst.data))) {
/* Try to use teleport away spell. */
int knownsp = known_spell(SPE_TELEPORT_AWAY);
/* casting isn't inhibited by being Stunned (...it ought to be) */
castit = (knownsp >= spe_Fresh && !Confusion);
if (!castit && !break_the_rules) {
You("%s.", (!Teleportation ? ((knownsp != spe_Unknown)
? "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);
#undef spelllev
#if 0
/* the addition of !break_the_rules to the outer if-block in
1ada454f rendered this dead code */
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)
energy = u.uen;
} else
#endif
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(SPE_TELEPORT_AWAY, TRUE, FALSE) & ECMD_TIME))
return 1;
else if (!break_the_rules)
return 0;
} else {
/* bypassing spelleffects(); apply energy cost directly */
u.uen -= energy;
disp.botl = TRUE;
}
}
if (next_to_u()) {
if (trap && trap_once) {
vault_tele();
} else if (trap && isok(trap->teledest.x, trap->teledest.y)) {
teleds(trap->teledest.x, trap->teledest.y, TELEDS_TELEPORT);
} else {
iflags.travelcc.x = iflags.travelcc.y = 0;
tele();
}
(void) next_to_u();
} else {
You("%s", shudder_for_moment);
return 0;
}
if (!trap)
morehungry(100);
return 1;
}
void
level_tele(void)
{
static const char get_there_from[] = "get there from %s.";
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) {
do {
newlevel.dnum = rn2(svn.n_dgns);
} while (newlevel.dnum == astral_level.dnum
|| svd.dungeons[newlevel.dnum].flags.unconnected
|| !svd.dungeons[newlevel.dnum].num_dunlevs);
newlevel.dlevel = 1 + rn2(dunlevs_in_dungeon(&newlevel));
assign_level(&u.ucamefrom, &u.uz);
schedule_goto(&newlevel, UTOTYPE_NONE, (char *) 0, (char *) 0);
return;
}
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, "*")) {
goto random_levtport;
} else if (Confusion && rnl(5)) {
pline("Oops...");
goto random_levtport;
} else if (!strcmp(buf, "\033")) { /* cancelled */
return;
}
if (wizard && !strcmp(buf, "?")) {
schar destlev;
xint16 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(gy.youmonst.data) ? "writhe" : "scream");
display_nhwindow(WIN_MESSAGE, FALSE);
You("cease to exist.");
if (gi.invent)
Your("possessions land on the %s with a thud.",
surface(u.ux, u.uy));
svk.killer.format = NO_KILLER_PREFIX;
Strcpy(svk.killer.name, "committed suicide");
done(DIED);
pline("An energized cloud of dust begins to coalesce.");
Your("body rematerializes%s.",
gi.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 (single_level_branch(&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 + svd.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;
}
svk.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 */
gi.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';
gi.in_mklev = FALSE;
}
if (newlev <= -10) {
You("arrive in heaven.");
SetVoice((struct monst *) 0, 0, 80, voice_deity);
verbalize("Thou art early, but we'll admit thee.");
svk.killer.format = NO_KILLER_PREFIX;
Strcpy(svk.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 (svk.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(svk.killer.name,
"teleported out of the dungeon and fell to %s death",
uhis());
svk.killer.format = NO_KILLER_PREFIX;
}
}
if (svk.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 >= svd.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 = svd.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);
#if 0 /* always wait until end of turn to change level, otherwise code
* that references monsters as this call stack unwinds won't be
* able to access them reliably; the do-the-change-now code here
* dates from when reading a scroll of teleportation wouldn't
* always make the scroll become discovered but that's no longer
* the case so it shouldn't be needed anymore */
/* 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 && !svc.context.mon_moving)
deferred_goto();
#endif
}
void
domagicportal(struct trap *ttmp)
{
struct d_level target_level;
int totype;
const char *stunmsg = (char *) 0;
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;
/* coming back from tutorial doesn't trigger stunning */
if (In_tutorial(&u.uz) && !In_tutorial(&target_level)) {
/* returning to normal play => arrive on level 1 stairs */
totype = UTOTYPE_ATSTAIRS;
stunmsg = "Resuming regular play.";
} else {
totype = UTOTYPE_PORTAL;
stunmsg = !Stunned ? "You feel slightly dizzy."
: "You feel dizzier.";
make_stunned((HStun & TIMEOUT) + 3L, FALSE);
}
schedule_goto(&target_level, totype, stunmsg, (char *) 0);
}
void
tele_trap(struct trap *trap)
{
/* a fixed-destination teleport trap could theoretically place hero onto a
* second teleport trap; prevent the recursive call from spoteffects() from
* triggering the trap at the destination */
static boolean in_tele_trap = FALSE;
if (in_tele_trap)
return;
in_tele_trap = TRUE;
if (In_endgame(&u.uz) || Antimagic || noteleport_level(&gy.youmonst)) {
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 if (isok(trap->teledest.x, trap->teledest.y)) {
coord cc;
struct monst *mtmp = m_at(trap->teledest.x, trap->teledest.y);
settrack();
if (mtmp) {
if (!enexto(&cc, mtmp->mx, mtmp->my, mtmp->data)) {
/* could not find some other place to put mtmp; the level must
* be nearly or completely full */
You1(shudder_for_moment);
}
else {
rloc_to(mtmp, cc.x, cc.y);
mtmp = (struct monst *) 0; /* no longer a monster at dest */
}
}
if (!mtmp) {
teleds(trap->teledest.x, trap->teledest.y, TELEDS_TELEPORT);
}
} else
tele();
in_tele_trap = FALSE;
}
void
level_tele_trap(struct trap *trap, unsigned int 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", u_locomotion("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;
}
deltrap(trap);
newsym(u.ux, u.uy); /* get rid of trap symbol */
level_tele();
if (Hallucination || Teleport_control)
You("briefly feel %s.", Hallucination ? "oriented" : "centered");
else
You_feel("%sdisoriented.", Confusion ? "even more " : "");
/* magic portal traversal causes brief Stun; for level teleport, use
confusion instead, and only when hero lacks control; do this after
processing the level teleportation attempt because being confused
can affect the outcome ("Oops" result) */
if (!Teleport_control)
make_confused((HConfusion & TIMEOUT) + 3L, FALSE);
}
/* check whether monster can arrive at location <x,y> via Tport (or fall) */
staticfn boolean
rloc_pos_ok(
coordxy x, coordxy y, /* coordinates of candidate location */
struct monst *mtmp)
{
coordxy xx, yy;
if (!goodpos(x, y, mtmp, GP_CHECKSCARY))
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 (svd.dndest.nlx && On_W_tower_level(&u.uz))
return (((yy & 2) != 0)
/* inside xor not within */
^ !within_bounded_area(x, y,
svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy));
if (svu.updest.lx && (yy & 1) != 0) /* moving up */
return (within_bounded_area(x, y,
svu.updest.lx, svu.updest.ly,
svu.updest.hx, svu.updest.hy)
&& (!svu.updest.nlx
|| !within_bounded_area(x, y,
svu.updest.nlx, svu.updest.nly,
svu.updest.nhx, svu.updest.nhy)));
if (svd.dndest.lx && (yy & 1) == 0) /* moving down */
return (within_bounded_area(x, y,
svd.dndest.lx, svd.dndest.ly,
svd.dndest.hx, svd.dndest.hy)
&& (!svd.dndest.nlx
|| !within_bounded_area(x, y,
svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.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.
*/
staticfn void
rloc_to_core(
struct monst *mtmp,
coordxy x, coordxy y,
unsigned rlocflags)
{
coordxy oldx = mtmp->mx, oldy = mtmp->my;
boolean resident_shk = mtmp->isshk && inhishop(mtmp);
boolean preventmsg = (rlocflags & RLOC_NOMSG) != 0;
boolean vanishmsg = (rlocflags & RLOC_MSG) != 0;
boolean appearmsg = (mtmp->mstrategy & STRAT_APPEARMSG) != 0;
boolean domsg = !gi.in_mklev && (vanishmsg || appearmsg) && !preventmsg;
boolean telemsg = FALSE;
if (x == mtmp->mx && y == mtmp->my && m_at(x, y) == mtmp)
return; /* that was easy */
if (oldx) { /* "pick up" monster */
if (domsg && canspotmon(mtmp)) {
if (couldsee(x, y) || sensemon(mtmp)) {
telemsg = TRUE;
} else {
pline("%s vanishes!", Monnam(mtmp));
}
/* avoid "It suddenly appears!" for a STRAT_APPEARMSG monster
that has just teleported away if we won't see it after this
vanishing (the regular appears message will be given if we
do see it) */
appearmsg = FALSE;
}
if (mtmp->wormno) {
remove_worm(mtmp);
} else {
remove_monster(oldx, oldy);
newsym(oldx, oldy); /* update old location */
}
}
mon_track_clear(mtmp);
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);
check_special_room(FALSE);
docrt();
} else if (!m_next2u(mtmp)) {
unstuck(mtmp);
}
}
maybe_unhide_at(x, y);
newsym(x, y); /* update new location */
set_apparxy(mtmp); /* orient monster */
if (domsg && (canspotmon(mtmp) || appearmsg || mtmp == u.ustuck)) {
int du = distu(x, y), olddu;
const char *next = (du <= 2) ? " next to you" : 0, /* next2u() */
*nearu = (du <= BOLT_LIM * BOLT_LIM) ? " close by" : 0;
set_msg_xy(x, y);
mtmp->mstrategy &= ~STRAT_APPEARMSG; /* one chance only */
if (mtmp == u.ustuck && !u_at(u.ux0, u.uy0)) {
You("and %s teleport together.", mon_nam(mtmp));
} else if (telemsg && (couldsee(x, y) || sensemon(mtmp))) {
pline("%s vanishes and reappears%s.",
Monnam(mtmp),
next ? next
: nearu ? nearu
: ((olddu = distu(oldx, oldy)) == du) ? ""
: (du < olddu) ? " closer to you"
: " farther away");
} else {
pline("%s %s%s%s!",
appearmsg ? Amonnam(mtmp) : Monnam(mtmp),
appearmsg ? "suddenly " : "",
!Blind ? "appears" : "arrives",
next ? next : nearu ? nearu : "");
}
/* wand discovery only happens if a messaage is delivered (bug?);
if spell or q.mechanic attack or artifact #invoke for banish
then current_wand will be Null */
if (gc.current_wand && gc.current_wand->otyp == WAN_TELEPORTATION)
makeknown(WAN_TELEPORTATION);
}
/* 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
[FIXME? or they've been hit by a genetic engineer, which won't
necessarily be due to Conflict by hero] */
if (resident_shk && !inhishop(mtmp))
make_angry_shk(mtmp, oldx, oldy);
/* if a monster carrying shop goods teleports out of the shop, blame
it on the hero; chance of an unpaid item is vanishingly small, but
no_charge is easily possible and needs to be cleared if not in shop;
a for-sale item is ordinary here--shk won't notice it leaving; if
mtmp teleports from one shop into another, no_charge status sticks
and an item on the first shk's bill stays there */
if (mtmp->minvent && !costly_spot(x, y)) {
struct obj *otmp;
struct monst *shkp = find_objowner(mtmp->minvent, oldx, oldy);
boolean peaceful = !shkp || shkp->mpeaceful;
for (otmp = mtmp->minvent; otmp; otmp = otmp->nobj) {
if (otmp->no_charge)
otmp->no_charge = 0;
else if (shkp && onshopbill(otmp, shkp, TRUE))
stolen_value(otmp, oldx, oldy, peaceful, FALSE);
}
}
/* if hero is busy, maybe stop occupation */
if (go.occupation)
(void) dochugw(mtmp, FALSE);
/* trapped monster teleported away */
if (mtmp->mtrapped && !mtmp->wormno)
(void) mintrap(mtmp, NO_TRAP_FLAGS);
}
void
rloc_to(struct monst *mtmp, coordxy x, coordxy y)
{
rloc_to_core(mtmp, x, y, RLOC_NOMSG);
}
void
rloc_to_flag(
struct monst *mtmp,
coordxy x, coordxy y,
unsigned rlocflags)
{
rloc_to_core(mtmp, x, y, rlocflags);
}
staticfn stairway *
stairway_find_forwiz(boolean isladder, boolean up)
{
stairway *stway = gs.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; rlocflags is RLOC_foo flags */
boolean
rloc(
struct monst *mtmp, /* mtmp->mx==0 implies migrating monster arrival */
unsigned rlocflags)
{
coord cc, backupcc, candy[ROWNO * (COLNO - 1)]; /* room for entire map */
unsigned cc_flags;
coordxy x, y;
int trycount, i, j, candycount;
if (mtmp == u.usteed) {
tele();
return TRUE;
}
if (mtmp->iswiz && mtmp->mx) { /* Wizard, not just arriving */
stairway *stway;
if (!In_W_tower(u.ux, u.uy, &u.uz)) {
stway = stairway_find_forwiz(FALSE, TRUE);
} else if (!stairway_find_forwiz(TRUE, FALSE)) { /* bottom of tower */
stway = stairway_find_forwiz(TRUE, TRUE);
} else {
stway = stairway_find_forwiz(TRUE, FALSE);
}
x = stway ? stway->sx : 0;
y = stway ? stway->sy : 0;
/* 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, NO_MM_FLAGS))
goto found_xy;
}
/* wizard-mode player can choose destination by setting 'montelecontrol'
option; ignored if/when this is arrival of a migrating monster */
if (iflags.mon_telecontrol && mtmp->mx) {
cc.x = mtmp->mx, cc.y = mtmp->my;
if (control_mon_tele(mtmp, &cc, rlocflags, TRUE)) {
x = cc.x, y = cc.y;
goto found_xy;
}
}
/* this used to try randomly 1000 times, then fallback to left-to-right
top-to-bottom exhaustive check; now that the exhaustive check uses
randomized order, reduce the number of random attempts to 50;
on levels with lots of available space, random can find a spot more
quickly but might fail to find one no matter how many tries it makes */
for (trycount = 0; trycount < 50; ++trycount) {
x = rnd(COLNO - 1); /* 1..COLNO-1 */
y = rn2(ROWNO); /* 0..ROWNO-1 */
if (rloc_pos_ok(x, y, mtmp)) /* rejects 'onscary' */
goto found_xy;
}
/* try harder to find a good place; gather a list of all candidate
locations (every accessible unoccupied spot except for hero's;
goodpos() will reject that), then shuffle them ourselves instead
of having collect_coords() do it (which would be in rings centered
around arbitrary <COLNO/2,ROWNO/2>) */
cc_flags = CC_INCL_CENTER | CC_UNSHUFFLED | CC_SKIP_MONS;
if (!passes_walls(mtmp->data))
cc_flags |= CC_SKIP_INACCS;
candycount = collect_coords(candy, COLNO / 2, ROWNO / 2, 0, cc_flags,
(boolean (*)(coordxy, coordxy)) 0);
backupcc.x = backupcc.y = 0;
for (i = 0; i < candycount; ++i) {
if ((j = rn2(candycount - i)) > 0) {
cc = candy[i];
candy[i] = candy[i + j];
candy[i + j] = cc;
}
x = candy[i].x, y = candy[i].y;
if (rloc_pos_ok(x, y, mtmp))
goto found_xy;
if (!backupcc.x && goodpos(x, y, mtmp, NO_MM_FLAGS))
backupcc.x = x, backupcc.y = y;
}
/* we didn't find any spot acceptable to rloc_pos_ok() which avoids
'onscary' and honors teleport regions, but if we did find a spot
that was acceptable to goodpos() (which ignores 'onscary' and
teleport regions) we'll use that; otherwise give up */
if (!backupcc.x) {
/* level either full of monsters or somehow faulty */
if ((rlocflags & RLOC_ERR) != 0)
impossible("rloc(): couldn't relocate monster");
return FALSE;
}
x = backupcc.x, y = backupcc.y;
/*goto found_xy;*/
found_xy:
rloc_to_core(mtmp, x, y, rlocflags);
return TRUE;
}
/* let wizard-mode player choose a teleporting monster's destination */
boolean
control_mon_tele(
struct monst *mon,
coord *cc_p, /* input: default spot; output: player selected spot */
unsigned rlocflags,
boolean via_rloc)
{
char tcbuf[BUFSZ];
if (!isok(cc_p->x, cc_p->y)) {
cc_p->x = mon->mx, cc_p->y = mon->my;
if (!isok(cc_p->x, cc_p->y))
cc_p->x = u.ux, cc_p->y = u.uy;
}
if (!wizard || !iflags.mon_telecontrol)
return FALSE;
pline("Teleport %s @ <%d,%d> where?",
noit_mon_nam(mon), mon->mx, mon->my);
/* getpos '?' will show "Move the cursor to <where to teleport Foo>:" */
Sprintf(tcbuf, "where to teleport %s", noit_mon_nam(mon));
if (getpos(cc_p, FALSE, tcbuf) >= 0 && !u_at(cc_p->x, cc_p->y)) {
if (via_rloc
? rloc_pos_ok(cc_p->x, cc_p->y, mon)
: goodpos(cc_p->x, cc_p->y, mon, rlocflags))
return TRUE;
if (!iflags.debug_fuzzer) {
Sprintf(tcbuf, "<%d,%d> is not considered viable; force anyway?",
mon->mx, mon->my);
if (y_n(tcbuf) == 'y')
return TRUE;
}
}
pline("%s destination.", via_rloc ? "Picking random" : "Using derived");
return FALSE;
}
staticfn void
mvault_tele(struct monst *mtmp)
{
struct mkroom *croom = search_special(VAULT);
coord c;
if (croom && somexyspace(croom, &c) && goodpos(c.x, c.y, mtmp, 0)) {
rloc_to(mtmp, c.x, c.y);
return;
}
(void) rloc(mtmp, RLOC_NONE);
}
boolean
tele_restrict(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(struct monst *mtmp, struct trap *trap, int in_sight)
{
char *monname;
/* don't print feedback here: a monster stepping on a trap and not
teleporting from it isn't visible */
if (noteleport_level(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 if (isok(trap->teledest.x, trap->teledest.y)) {
/* monster teleporting onto hero's or another monster's spot does
* not work the same as hero teleporting onto monster's spot where
* the incoming monster displaces the resident to the nearest
* possible space - instead it just doesn't work. */
if (!(m_at(trap->teledest.x, trap->teledest.y)
|| u_at(trap->teledest.x, trap->teledest.y))) {
rloc_to_core(mtmp, trap->teledest.x, trap->teledest.y,
RLOC_MSG);
}
} else
(void) rloc(mtmp, RLOC_NONE);
if (in_sight) {
if (canseemon(mtmp))
pline("%s seems disoriented.", monname);
else
pline("%s suddenly disappears!", monname);
seetrap(trap);
}
}
}
/* return Trap_Effect_Finished if still on level, Trap_Moved_Mon if not */
int
mlevel_tele_trap(
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 Trap_Effect_Finished; /* 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_mon(mtmp, "%s avoids the %s.",
Monnam(mtmp),
(tt == HOLE) ? "hole" : "trap");
return Trap_Effect_Finished;
} else {
assign_level(&tolevel, &trap->dst);
(void) clamp_hole_destination(&tolevel);
}
} 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_mon(mtmp,
"%s seems to shimmer for a moment.",
Monnam(mtmp));
seetrap(trap);
}
return Trap_Effect_Finished;
} 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_mon(mtmp,
"%s seems very disoriented for a moment.",
Monnam(mtmp));
return Trap_Effect_Finished;
}
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_mon(mtmp, "%s shudders for a moment.",
Monnam(mtmp));
return Trap_Effect_Finished;
}
get_level(&tolevel, nlev);
}
} else {
impossible("mlevel_tele_trap: unexpected trap type (%d)", tt);
return Trap_Effect_Finished;
}
if (in_sight) {
pline_mon(mtmp, "Suddenly, %s %s.", mon_nam(mtmp),
(tt == HOLE) ? "falls into a hole"
: (tt == TRAPDOOR) ? "falls through a trap door"
: "disappears out of sight");
if (trap)
seetrap(trap);
}
if (is_xport(tt) && !control_teleport(mtmp->data))
mtmp->mconf = 1;
migrate_to_level(mtmp, ledger_no(&tolevel), migrate_typ, (coord *) 0);
return Trap_Moved_Mon; /* no longer on this level */
}
return Trap_Effect_Finished;
}
/* place object randomly, returns False if it's gone (eg broken) */
boolean
rloco(struct obj *obj)
{
coordxy 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 && svd.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,
svd.dndest.lx, svd.dndest.ly,
svd.dndest.hx, svd.dndest.hy)
|| (svd.dndest.nlx
&& within_bounded_area(tx, ty,
svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy))))
/* on the Wizard Tower levels, objects inside should
stay inside and objects outside should stay outside */
|| (svd.dndest.nlx && On_W_tower_level(&u.uz)
&& within_bounded_area(tx, ty,
svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.dndest.nhy)
!= within_bounded_area(otx, oty,
svd.dndest.nlx, svd.dndest.nly,
svd.dndest.nhx, svd.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 {
struct monst *shkp = find_objowner(obj, otx, oty);
boolean objinshop = shkp && costly_spot(otx, oty),
onboundary = shkp && costly_adjacent(shkp, otx, oty);
/*
* If object starts inside shop or is unpaid and on shop boundary:
* if hero is outside the shop, treat this as theft;
* otherwise, if it arrives inside same shop, remove it from bill;
* otherwise, if it arrives on the boundary, add it to bill;
* if it arrives outside the shop, treat this as a theft.
* Billing routines deal with obj->no_charge.
*/
if (objinshop || (obj->unpaid && onboundary)) {
char h = *in_rooms(u.ux, u.uy, SHOPBASE),
oo = *in_rooms(otx, oty, 0);
boolean hinshop = h && strchr(in_rooms(shkp->mx, shkp->my, 0), h);
if (hinshop && costly_spot(tx, ty)
/* verify that it's the same shop */
&& oo && strchr(in_rooms(tx, ty, 0), oo)) {
if (obj->unpaid)
subfrombill(obj, shkp);
} else if (hinshop && costly_adjacent(shkp, tx, ty)
&& oo && strchr(in_rooms(tx, ty, 0), oo)) {
if (!obj->unpaid)
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(void)
{
int nlev, max_depth, min_depth, cur_depth = (int) depth(&u.uz);
/* [the endgame case can only occur in wizard mode] */
if (!rn2(5) || single_level_branch(&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 = svd.dungeons[u.uz.dnum].depth_start;
max_depth = bottom + (svd.dungeons[u.uz.dnum].depth_start - 1);
} else {
min_depth = 1;
max_depth = dunlevs_in_dungeon(&u.uz)
+ (svd.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(
struct monst *mtmp,
boolean give_feedback)
{
coord cc;
if (svl.level.flags.stasis_until >= svm.moves) {
if (give_feedback)
pline("A mysterious force prevents you teleporting %s!",
mon_nam(mtmp));
return FALSE;
} else if (mtmp->ispriest && *in_rooms(mtmp->mx, mtmp->my, TEMPLE)) {
if (give_feedback)
pline("%s resists your magic!", Monnam(mtmp));
return FALSE;
} else if (engulfing_u(mtmp) && noteleport_level(mtmp)) {
if (give_feedback)
You("are no longer inside %s!", mon_nam(mtmp));
unstuck(mtmp);
if (!rloc(mtmp, RLOC_MSG))
m_into_limbo(mtmp);
} else if ((is_rider(mtmp->data) || control_teleport(mtmp->data))
&& rn2(13) && enexto(&cc, u.ux, u.uy, mtmp->data)) {
rloc_to(mtmp, cc.x, cc.y);
} else {
if (!rloc(mtmp, RLOC_MSG))
return FALSE;
}
return TRUE;
}
/*teleport.c*/