Skip to content

Commit

Permalink
Stealth infrastructure: numeric stealth property, disturb() overhaul
Browse files Browse the repository at this point in the history
* Introduces a get_stealth() function that takes a struct monst *
  and returns that monster's current stealth level.
   + For the player, ask for get_stealth(&youmonst).
   + The lowest possible and most typical return value is 0.
   + Some monsters are inherently stealthy (e.g., elves).
     This is accomplished by adding a new field to the permonst.
     This also works for the player based on current polyform
     if polyselfed or by race otherwise.
   + Equipped items that grant stealth are taken into account,
     including artifact weapons in the primary slot only.
     Quivered and off-hand items do not count, however.
     Rings of stealth are now chargeable: their spe matters.

* The disturb() function, which decides whether a sleeping monster
  should wake up, has been greatly overhauled.
   + The function takes a number of things into account that
     make it harder or easier to sneak up on a monster.  These
     factors include conflict, scent at close range, and whether
     the monster is an ettin (because it's hard to sneak up on
     both of their heads).
   + The result is somewhat random: if the monster's awareness
     is higher than your stealth, it _can_ wake up, but it may
     not do so every time.  Higher awareness increases the odds
     it will wake up, and higher stealth decreases them.

* The old binary STEALTH property still exists, because objects
  and artifacts use the constant to signal that they grant stealth.
  However, the Stealth and HStealth macros have been commented out
  and should no longer be used:  use get_stealth() instead.  There
  are comments to this effect.

* The binary STEALTH property has been removed from race and role
  property-at-level lists, including starting properties for two
  roles.  I put in TODO comments to the effect that they should
  be given Basic skill in stealth to start instead, but that is
  not implemented yet.  (Acquiring it later on levelup will no
  longer be a thing as such, but the skill exists now, so it can
  be #enhanced, which more than compensates.)

This isn't well tested yet, because I found out that I can't
trivially test it by using #genesis to create monsters and zapping
sleep at them -- because zapping sleep at monsters doesn't set
their msleeping state, it does something else different.  I plan
to add "sleeping" support to #genesis as a workaround for this,
and then I will do testing and probably tweak the formulae in
disturb() and possibly in get_stealth().
  • Loading branch information
tsadok committed Oct 13, 2015
1 parent b7242a5 commit 965e9c3
Show file tree
Hide file tree
Showing 16 changed files with 222 additions and 163 deletions.
2 changes: 2 additions & 0 deletions libnethack/include/extern.h
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ extern void adjabil(int, int);
extern int newhp(void);
extern schar acurr(int);
extern schar acurrstr(void);
extern schar get_stealth(struct monst *);
extern schar get_player_ac(void);
extern void adjalign(int);
extern void calc_attr_bonus(void);
Expand Down Expand Up @@ -1204,6 +1205,7 @@ extern short mprof(const struct monst *, int);

extern boolean itsstuck(struct monst *);
extern boolean mb_trapped(struct monst *);
extern int disturb(struct monst *);
extern void mon_regen(struct monst *, boolean);
extern int dochugw(struct monst *);
extern boolean onscary(int, int, struct monst *);
Expand Down
1 change: 1 addition & 0 deletions libnethack/include/permonst.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ struct permonst {
schar mlevel, /* base monster level */
mmove, /* move speed */
ac, /* (base) armor class */
stealth, /* (base) stealthiness */
mr; /* (base) magic resistance */
aligntyp maligntyp; /* basic monster alignment */
unsigned short geno; /* creation/geno mask value */
Expand Down
2 changes: 1 addition & 1 deletion libnethack/include/prop.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum youprop {
POLYMORPH = 16,
POLYMORPH_CONTROL = 17,
LEVITATION = 18,
STEALTH = 19,
STEALTH = 19, /* objects, artifacts use this constant */
AGGRAVATE_MONSTER = 20,
CONFLICT = 21,
PROTECTION = 22,
Expand Down
3 changes: 3 additions & 0 deletions libnethack/include/youprop.h
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,11 @@
# define BInvis worn_blocked(INVIS)
# define Invisible (Invis && !See_invisible)
# define Displaced worn_extrinsic(DISPLACED)
/* u_any_property(STEALTH) should no longer be checked directly.
Use get_stealth() instead.
# define HStealth u.uintrinsic[STEALTH]
# define Stealth u_any_property(STEALTH)
*/
# define HAggravate_monster u.uintrinsic[AGGRAVATE_MONSTER]
# define Aggravate_monster u_any_property(AGGRAVATE_MONSTER)
# define HConflict u.uintrinsic[CONFLICT]
Expand Down
43 changes: 35 additions & 8 deletions libnethack/src/attrib.c
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ struct innate {
};

static const struct innate arc_abil[] = {
{1, &(HStealth), "", ""},
{1, &(HFast), "", ""},
{10, &(HSearching), "perceptive", ""},
{0, 0, 0, 0}
Expand All @@ -36,7 +35,6 @@ static const struct innate arc_abil[] = {
static const struct innate bar_abil[] = {
{1, &(HPoison_resistance), "", ""},
{7, &(HFast), "quick", "slow"},
{15, &(HStealth), "stealthy", ""},
{0, 0, 0, 0}
};

Expand All @@ -62,7 +60,6 @@ static const struct innate mon_abil[] = {
{1, &(HSleep_resistance), "", ""},
{1, &(HSee_invisible), "", ""},
{3, &(HPoison_resistance), "healthy", ""},
{5, &(HStealth), "stealthy", ""},
{7, &(HWarning), "sensitive", ""},
{9, &(HSearching), "perceptive", "unaware"},
{11, &(HFire_resistance), "cool", "warmer"},
Expand All @@ -80,20 +77,17 @@ static const struct innate pri_abil[] = {

static const struct innate ran_abil[] = {
{1, &(HSearching), "", ""},
{7, &(HStealth), "stealthy", ""},
{15, &(HSee_invisible), "", ""},
{0, 0, 0, 0}
};

static const struct innate rog_abil[] = {
{1, &(HStealth), "", ""},
{10, &(HSearching), "perceptive", ""},
{0, 0, 0, 0}
};

static const struct innate sam_abil[] = {
{1, &(HFast), "", ""},
{15, &(HStealth), "stealthy", ""},
{0, 0, 0, 0}
};

Expand All @@ -105,7 +99,6 @@ static const struct innate tou_abil[] = {

static const struct innate val_abil[] = {
{1, &(HCold_resistance), "", ""},
{1, &(HStealth), "", ""},
{7, &(HFast), "quick", "slow"},
{0, 0, 0, 0}
};
Expand Down Expand Up @@ -133,7 +126,6 @@ static const struct innate sylph_abil[] = {
/* They also get a form of slotless regeneration, but only under
certain conditions, and with hunger implications so that's
special-cased elsewhere. */
{3, &(HStealth), "stealthy", "obvious"},
{5, &(HInfravision), "perceptive", "half blind"},
{7, &(HDisplacement), "elusive", "exposed"},
{16, &(HDetect_monsters), "perceptive", "dull"},
Expand Down Expand Up @@ -872,6 +864,41 @@ acurrstr(void)
return (schar) (str - 100);
}

/* Returns a monster's (or the player's) current level of stealth. Note that 1
is subtracted, because a base value of 1 is the default "no stealth unless
wearing stealth-granting item" level. If a monster is given a base stealth
value of 0, wearing a single stealth-granting item is not enough to make it
stealthy. 2 or more means stealthy by default, and higher is stealthier. */
/* Invisibility is NOT added in here, because we don't know who the observer is,
and they may be able to see invisible. So that is handled in the caller. */
schar
get_stealth(struct monst *mon)
{
boolean player = (mon == &youmonst) ? TRUE : FALSE;
enum objslot i;
/* start with intrinsic stealth */
int s = player ? (Upolyd ? youmonst.data->stealth :
mons[urace.malenum].stealth) : mon->data->stealth;
/* Now check the armor and ring slots for stealth-granting items: */
for (i = os_arm; i <= os_last_equip; i++) {
if (i != os_quiver && i != os_swapwep) {
struct obj *item = which_armor(mon, i);
if (item && item_provides_extrinsic(item, STEALTH, 0)) {
if (i == os_ringl || i == os_ringr)
s += item->spe;
else
s++;
}
}
}
/* Finally, add in skill modifier: */
if (player)
s += P_SKILL(P_STEALTH);
if (s > 1)
return (schar) s - 1;
return (schar) 0;
}

/* Returns the player's effective AC rating. Use in place of u.uac. */
schar
get_player_ac(void)
Expand Down
6 changes: 3 additions & 3 deletions libnethack/src/hack.c
Original file line number Diff line number Diff line change
Expand Up @@ -2787,8 +2787,8 @@ check_special_room(boolean newlev)
level->rooms[roomno].rtype = OROOM;
if (rt == COURT || rt == SWAMP || rt == MORGUE || rt == ZOO)
for (mtmp = level->monlist; mtmp; mtmp = mtmp->nmon)
if (!DEADMONSTER(mtmp) && !Stealth && !rn2(3))
mtmp->msleeping = 0;
if (!DEADMONSTER(mtmp) && !rn2(3))
disturb(mtmp);
}
}

Expand Down Expand Up @@ -3181,7 +3181,7 @@ maybe_wail(void)
{
static const short powers[] = { TELEPORT, SEE_INVIS, POISON_RES, COLD_RES,
SHOCK_RES, FIRE_RES, SLEEP_RES, DISINT_RES,
TELEPORT_CONTROL, STEALTH, FAST, INVIS
TELEPORT_CONTROL, FAST, INVIS
};

if (moves <= wailmsg + 50)
Expand Down
9 changes: 6 additions & 3 deletions libnethack/src/mkroom.c
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ fill_dragonhall(struct level *lev, struct mkroom *sroom, enum rng rng)
itemone, itemtwo, itemthree;
int harder = !!(12 < rn2_on_rng(depth(&lev->z), rng));
int color = rn2_on_rng(6, rng);
struct monst *mon;
imax = 0;
switch (color) {
case 1: /* blue */
Expand Down Expand Up @@ -480,12 +481,14 @@ fill_dragonhall(struct level *lev, struct mkroom *sroom, enum rng rng)
}
/* here be dragons */
if (i <= cutoffone) {
makemon(&mons[greatpm], lev, pos[i].x, pos[i].y, MM_ANGRY);
mon = makemon(&mons[greatpm], lev, pos[i].x, pos[i].y, MM_ANGRY);
} else if (i <= cutofftwo) {
makemon(&mons[adultpm], lev, pos[i].x, pos[i].y, MM_ANGRY);
mon = makemon(&mons[adultpm], lev, pos[i].x, pos[i].y, MM_ANGRY);
} else if (i <= cutoffthree) {
makemon(&mons[babypm], lev, pos[i].x, pos[i].y, 0);
mon = makemon(&mons[babypm], lev, pos[i].x, pos[i].y, 0);
}
if (mon)
mon->msleeping = 1;
}
}

Expand Down
3 changes: 1 addition & 2 deletions libnethack/src/mon.c
Original file line number Diff line number Diff line change
Expand Up @@ -2562,8 +2562,7 @@ wake_nearby(boolean intentional)
{
wake_nearto(u.ux, u.uy, Aggravate_monster ? COLNO * COLNO + ROWNO * ROWNO :
intentional ? u.ulevel * 20 :
600 / (u.ulevel * (Role_if(PM_ROGUE) ? 2 : 1) *
(Stealth ? 2 : 1)));
300 / (u.ulevel * (1 + get_stealth(&youmonst))));
}

/* Produce noise at a particular location. Monsters in the given dist2 radius
Expand Down
68 changes: 41 additions & 27 deletions libnethack/src/monmove.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include "epri.h"


static int disturb(struct monst *);
static void distfleeck(struct monst *, int *, int *, int *);
static int m_arrival(struct monst *);
static void watch_on_duty(struct monst *);
Expand Down Expand Up @@ -128,36 +127,51 @@ mon_regen(struct monst *mon, boolean digest_meal)
}
}

/*
* Possibly awaken the given monster. Return a 1 if the monster has been
* jolted awake.
*/
static int
/* Possibly awaken the given monster. Return a 1 if the monster has been jolted
awake or has noticed you, 0 if nothing has happened. */
/* TODO: put player stealth on its own rng. */
int
disturb(struct monst *mtmp)
{
int stealth = get_stealth(&youmonst);
int alertness = 1; /* base value */
int distance = distu(mtmp->mx, mtmp->my);
if (!(mtmp->data->mlet == S_NYMPH || mtmp->data == &mons[PM_JABBERWOCK]
|| mtmp->data == &mons[PM_LEPRECHAUN]))
/* Most monsters are more alert than these ones. */
alertness++;
if (mtmp->data == &mons[PM_ETTIN])
alertness += 2; /* Ettins are hard to surprise, having two heads */
if ((mtmp->data->mlet == S_DOG || mtmp->data->mlet == S_HUMAN) ||
(!rn2(7) && mtmp->m_ap_type != M_AP_FURNITURE &&
mtmp->m_ap_type != M_AP_OBJECT))
alertness++;
if (Aggravate_monster) /* makes things more likely to notice you */
alertness++;
if (Conflict) /* all monsters are more alert during conflict */
alertness++;

if (mtmp->data->mflags3 & M3_SCENT) {
alertness += 9 / distance;
} else if (Invisible && !perceives(mtmp->data)) {
stealth = (stealth + 1) * 2;
}
/*
* + Ettins are hard to surprise.
* + Nymphs, jabberwocks, and leprechauns do not easily wake up.
*
* Wake up if:
* in line of effect AND
* within 10 squares AND
* not stealthy or (mon is an ettin and 9/10) AND
* (mon is not a nymph, jabberwock, or leprechaun) or 1/50 AND
* Aggravate or mon is (dog or human) or
* (1/7 and mon is not mimicing furniture or object)
*/
if (couldsee(mtmp->mx, mtmp->my) && distu(mtmp->mx, mtmp->my) <= 100 &&
(!Stealth || (mtmp->data == &mons[PM_ETTIN] && rn2(10))) &&
(!(mtmp->data->mlet == S_NYMPH || mtmp->data == &mons[PM_JABBERWOCK]
|| mtmp->data == &mons[PM_LEPRECHAUN]) || !rn2(50)) &&
(Aggravate_monster ||
(mtmp->data->mlet == S_DOG || mtmp->data->mlet == S_HUMAN)
|| (!rn2(7) && mtmp->m_ap_type != M_AP_FURNITURE &&
mtmp->m_ap_type != M_AP_OBJECT))) {
mtmp->msleeping = 0;
return 1;
if (wizard)
pline("disturb(%d): d=%d; s=%d; a=%d0",
mtmp->m_id, distance, stealth, alertness);
*/
if (couldsee(mtmp->mx, mtmp->my) &&
(((1 + stealth) * distance / 5) <= rn2(alertness))) {
if (mtmp->msleeping) {
mtmp->msleeping = 0;
return 1;
} else {
/* TODO: Monster _notices_ you. */
return 1;
}
}
use_skill(P_STEALTH, 1);
return 0;
}

Expand Down
Loading

0 comments on commit 965e9c3

Please sign in to comment.