github PR #259 - paranoid_confirmation:trap

Fairly old pull request from copperwater:  add new paranoid_confirm
setting 'trap'.

The old commit suffered from bit rot and merging needed too much
fixing up despite there not being many bands of change in the commit's
diffs.  I ultimately redid it from scratch, although the two biggest
chunks of code started with copy+paste of the pull request's commit.

It operates like paranoid:pray.  Setting paranoid:trap adds a new
"Really step into <trap>?" y/n prompt when attempting to move
into/onto a known trap, even if an object covers it on the map.
Setting both 'paranoid:Confirm trap' turns that into a yes/no prompt.
(Adding 'Confirm' affects other paranoid confirmations; in addition
to requiring yes<return> rather than just y to accept, it also forces
no<return> to reject.)

However, moving into a known trap that is considered to be harmless
behaves as if no trap was present.  Some of the trap classification
might be out of date; several types of traps have undergone changes
since implementation of the original pull request, notably anti-magic
field.  When the hero is hallucinating, all known traps are considered
harmful since the map no longer reliably describes them.

Preceding a movement command with the 'm' prefix also behaves as if
no trap was present, bypassing confirmation for that move, similar to
how paranoid:swim currently behaves.  Being stunned or confused also
behaves as if no trap was present, taking priority over hallucination.

This updates the documentation.

Supersedes #259
Closes #259
This commit is contained in:
PatR
2023-09-08 15:55:31 -07:00
parent 33d85bfe08
commit 1a64ee1c28
10 changed files with 248 additions and 11 deletions

View File

@@ -214,7 +214,7 @@ packorder a list of default symbols for kinds of [")[%?+!=/(*`0_]
paranoid_confirmation space separated list [paranoid_confirm:pray swim]
of situations where alternate prompting is desired
Confirm -- when requiring "yes", also require "no" to reject;
also requires yes rather than y for pray, Autoall
also requires yes rather than y for pray, trap, Autoall
quit -- yes vs y to confirm quitting or to enter explore mode
die -- yes vs y to confirm dying (for explore or debug mode)
bones -- yes vs y to confirm saving bones data in debug mode
@@ -224,6 +224,7 @@ paranoid_confirmation space separated list [paranoid_confirm:pray swim]
Were-change -- yes vs y to confirm changing form due to
lycanthropy when hero has polymorph control;
pray -- y to confirm an attempt to pray; on by default
trap -- y to enter a known trap unless it is harmless;
swim -- require m prefix to move into water or lava when
hero has seen it and isn't impaired; on by default;
AutoAll -- y to confirm if using menustyle:Full and choice 'A'

View File

@@ -37,7 +37,7 @@
.ds f0 "\*(vr
.ds f1
.\"DO NOT REMOVE NH_DATESUB .ds f2 "DATE(%B %-d, %Y)
.ds f2 "August 5, 2023
.ds f2 "September 8, 2023
.
.\" A note on some special characters:
.\" \(lq = left double quote
@@ -4246,6 +4246,11 @@ to lycanthropy when hero has polymorph control;
require \(oqy\(cq to confirm an attempt to pray rather
than immediately praying; on by default;
(to require \(lqyes\(rq rather than just \(oqy\(cq, set Confirm too);
.PL trap
require \(oqy\(cq to confirm an attempt to move into or onto a known trap,
unless doing so is considered to be harmless;
(to require \(lqyes\(rq rather than just \(oqy\(cq, set Confirm too);
confirmation can be skipped by using the \(oq\f(CRm\fP\(cq movement prefix;
.PL swim
prevent walking into water or lava; on by default; (to deliberately step
onto/into such terrain when this is set, use the \(oq\f(CRm\fP\(cq

View File

@@ -46,7 +46,7 @@
\author{Original version - Eric S. Raymond\\
(Edited and expanded for 3.7.0 by Mike Stephenson and others)}
%DO NOT REMOVE NH_DATESUB \date{DATE(%B %-d, %Y)}
\date{August 5, 2023}
\date{September 8, 2023}
\maketitle
@@ -4646,7 +4646,7 @@ a peaceful monster;
\item[{\tt wand-break}]
require ``{\tt yes}'' rather than `{\tt y}' to confirm breaking
a wand with the {\it apply} command;
\item[{\tt eating}]
\item[{\tt eating~}]
require ``{\tt yes}'' rather than `{\tt y}' to confirm whether to
continue eating;
\item[{\tt Were-change}]
@@ -4656,6 +4656,11 @@ to lycanthropy when hero has polymorph control;
require `{\tt y}' to confirm an attempt to pray rather
than immediately praying; on by default;
(to require ``yes'' rather than just `y', set Confirm too);
\item[{\tt trap~~~}]
require `{\tt y}' to confirm an attempt to move into or onto a known trap,
unless doing so is considered to be harmless;
(to require ``yes'' rather than just `y', set Confirm too);
confirmation can be skipped by using the `{\tt m}' movement prefix;
\item[{\tt swim~~~}]
prevent walking into water or lava; on by default; (to deliberately step
onto/into such terrain when this is set, use the `{\tt m}'

View File

@@ -1234,6 +1234,8 @@ if Magicbane cancelled a shapeshifter, forcing it to 'unshift', subsequent
a pet that was poison resistant but not stoning resistant would eat Medusa's
corpse and be turned to stone
ring of hunger prevents choking on your food
paranoid_confirm:pray can be changed to require yes/no response instead of y/n
by also setting paranoid_confirm:Confirm
Fixes to 3.7.0-x General Problems Exposed Via git Repository
@@ -2171,6 +2173,9 @@ reading a blessed scroll of light has a chance to improve bless/curse state
added a chronicle of major events, and optional live logging of those
paranoid_confirm:swim to prevent accidental dunking into dangerous liquids;
joins paranoid_confirm:pray as the default setting
paranoid_confirm:trap to confirm entering a known trap unless it is harmless;
like revised paranoid_confirm:pray, requires y/n response; add
paranoid_confirm:Confirm to require yes/no instead
paranoid_confirm:Autoall to confirm picking 'A' in menustyle:Full filter menu
looking at a monster will indicate whether it is asleep, and waking up a
monster yields a message

View File

@@ -2913,6 +2913,7 @@ extern struct monst *animate_statue(struct obj *, coordxy, coordxy,
int, int *);
extern struct monst *activate_statue_trap(struct trap *, coordxy, coordxy,
boolean);
extern int immune_to_trap(struct monst *, unsigned);
extern void set_utrap(unsigned, unsigned);
extern void reset_utrap(boolean);
extern void dotrap(struct trap *, unsigned);

View File

@@ -85,7 +85,8 @@ struct flag {
#define PARANOID_WERECHANGE 0x0100
#define PARANOID_EATING 0x0200
#define PARANOID_SWIM 0x0400
#define PARANOID_AUTOALL 0x0800
#define PARANOID_TRAP 0x0800
#define PARANOID_AUTOALL 0x1000
int pickup_burden; /* maximum burden before prompt */
int pile_limit; /* controls feedback when walking over objects */
char discosort; /* order of dodiscovery/doclassdisco output: o,s,c,a */
@@ -482,6 +483,8 @@ enum runmode_types {
#define ParanoidEating ((flags.paranoia_bits & PARANOID_EATING) != 0)
/* Prevent going into lava or water without explicitly forcing it */
#define ParanoidSwim ((flags.paranoia_bits & PARANOID_SWIM) != 0)
/* Prevent going onto/into known trap unless it is harmless */
#define ParanoidTrap ((flags.paranoia_bits & PARANOID_TRAP) != 0)
/* Require confirmation for choosing 'A' in class menu for menustyle:Full */
#define ParanoidAutoAll ((flags.paranoia_bits & PARANOID_AUTOALL) != 0U)

View File

@@ -93,13 +93,22 @@ enum trap_types {
};
/* some trap-related function return results */
enum { Trap_Effect_Finished = 0,
Trap_Is_Gone = 0,
Trap_Caught_Mon = 1,
Trap_Killed_Mon = 2,
Trap_Moved_Mon = 3, /* new location, or new level */
enum trap_result {
Trap_Effect_Finished = 0,
Trap_Is_Gone = 0,
Trap_Caught_Mon = 1,
Trap_Killed_Mon = 2,
Trap_Moved_Mon = 3, /* new location, or new level */
};
/* return codes from immune_to_trap() */
enum trap_immunities {
TRAP_NOT_IMMUNE = 0,
TRAP_CLEARLY_IMMUNE = 1,
TRAP_HIDDEN_IMMUNE = 2,
};
#define is_pit(ttyp) ((ttyp) == PIT || (ttyp) == SPIKED_PIT)
#define is_hole(ttyp) ((ttyp) == HOLE || (ttyp) == TRAPDOOR)
#define unhideable_trap(ttyp) ((ttyp) == HOLE) /* visible traps */

View File

@@ -2459,6 +2459,45 @@ domove_core(void)
if (u_rooted())
return;
/* warn maybe player before walking into known traps */
if (ParanoidTrap && (trap = t_at(x, y)) != 0 && trap->tseen
&& (!gc.context.nopick || gc.context.run)
&& !Stunned && !Confusion
&& (immune_to_trap(&gy.youmonst, trap->ttyp) != TRAP_CLEARLY_IMMUNE
/* hallucination: all traps still show as ^, but the
hero can't tell what they are, so treat as dangerous */
|| Hallucination)) {
char qbuf[QBUFSZ];
int traptype = (Hallucination ? rnd(TRAPNUM - 1) : (int) trap->ttyp);
boolean into = FALSE; /* "onto" the trap vs "into" */
switch (traptype) {
case BEAR_TRAP:
case PIT:
case SPIKED_PIT:
case HOLE:
case TELEP_TRAP:
case LEVEL_TELEP:
case MAGIC_PORTAL:
case WEB:
into = TRUE;
break;
}
Snprintf(qbuf, sizeof qbuf, "Really %s %s that %s?",
locomotion(gy.youmonst.data, "step"),
into ? "into" : "onto",
defsyms[trap_to_defsym(traptype)].explanation);
/* handled like paranoid_confirm:pray; when paranoid_confirm:trap
isn't set, don't ask at all but if it is set (checked above),
ask via y/n if parnoid_confirm:confirm isn't also set or via
yes/no if it is */
if (!paranoid_query(ParanoidConfirm, qbuf)) {
nomul(0);
gc.context.move = 0;
return;
}
}
if (u.utrap) {
boolean moved = trapmove(x, y, trap);

View File

@@ -176,9 +176,12 @@ static const struct paranoia_opts {
"yes vs y to continue eating after first bite when satiated" },
{ PARANOID_WERECHANGE, "Were-change", 2, (const char *) 0, 0,
"yes vs y to change form when lycanthropy is controllable" },
/* extra y/n questions rather than changing y/n to yes/n[o] */
/* extra y/n questions rather than changing y/n to yes/n[o];
they switch to yes/no if paranoid:confirm is also set */
{ PARANOID_PRAY, "pray", 1, 0, 0,
"y required to pray (supersedes old \"prayconfirm\" option)" },
{ PARANOID_TRAP, "trap", 1, "move-trap", 1,
"y required to enter known trap unless considered harmless" },
{ PARANOID_AUTOALL, "Autoall", 2, "autoselect-all", 2,
"y required to pick filter choice 'A' for menustyle:Full" },
/* not a yes/n[o] vs y/n change nor a y/n addition */

View File

@@ -2544,6 +2544,172 @@ trapeffect_vibrating_square(
return Trap_Effect_Finished;
}
/*
* for PR#259 - paranoid_confirm:trap
*
* Will a monster suffer any adverse effects from a certain trap?
* Note: does NOT mean "will a monster trigger a trap in the first place",
* though if it won't that does imply that they'll not suffer adverse effects.
* For example, an elf is considered immune to sleeping gas traps even though
* they'll set the trap off.
* Return value:
* TRAP_NOT_IMMUNE = not immune at the moment;
* TRAP_CLEARLY_IMMUNE = obviously immune (if player is polymorphed, assume
* they know which traps they are immune to in their current form);
* TRAP_HIDDEN_IMMUNE = immune but in non-obvious way such as an unidentified
* item or hidden intrinsic providing a resistance; the player should still
* be warned of this trap, while monsters implicitly know they're immune.
*/
int
immune_to_trap(struct monst *mon, unsigned ttype)
{
struct permonst *pm;
struct obj *obj;
boolean is_you;
if (!mon) {
impossible("immune_to_trap: null monster");
return TRAP_NOT_IMMUNE;
}
pm = mon->data;
is_you = (mon == &gy.youmonst);
switch (ttype) {
case ARROW_TRAP:
case DART_TRAP:
case ROCKTRAP:
/* can hit anything; even noncorporeal monsters might get a blessed
projectile */
return TRAP_NOT_IMMUNE;
case BEAR_TRAP:
if (pm->msize <= MZ_SMALL
|| amorphous(pm) || is_whirly(pm) || unsolid(pm))
return TRAP_CLEARLY_IMMUNE;
/*FALLTHRU*/
case SQKY_BOARD:
case LANDMINE:
case ROLLING_BOULDER_TRAP:
case HOLE:
case TRAPDOOR:
case PIT:
case SPIKED_PIT:
/* ground-based traps, which can be evaded by levitation, flying, or
hanging to the ceiling */
if (Sokoban && (is_pit(ttype) || is_hole(ttype)))
return TRAP_NOT_IMMUNE;
if (is_floater(pm) || is_flyer(pm)
|| (is_clinger(pm) && has_ceiling(&u.uz)))
return TRAP_CLEARLY_IMMUNE;
else if (is_you && (Levitation || Flying))
return TRAP_CLEARLY_IMMUNE;
return TRAP_NOT_IMMUNE;
case SLP_GAS_TRAP:
if (breathless(pm))
return TRAP_CLEARLY_IMMUNE;
else if (!is_you && resists_sleep(mon))
return TRAP_CLEARLY_IMMUNE;
else if (is_you && Sleep_resistance)
return TRAP_HIDDEN_IMMUNE;
return TRAP_NOT_IMMUNE;
case LEVEL_TELEP:
case TELEP_TRAP:
/* consider unintended teleporting to be an adverse effect; if in
the endgame or carrying the Amulet, the teleport trap won't work
anyway, so anything hitting it is immune. */
if (In_endgame(&u.uz) || mon_has_amulet(mon))
return TRAP_CLEARLY_IMMUNE;
return TRAP_NOT_IMMUNE;
case POLY_TRAP:
if (resists_magm(mon))
/* covers Antimagic for player */
return (is_you ? TRAP_HIDDEN_IMMUNE : TRAP_CLEARLY_IMMUNE);
return TRAP_NOT_IMMUNE;
case STATUE_TRAP:
/* no effect on monsters, only affects players; only trap detection
can let player know that this is a statue trap there ahead of time;
in the rare case this happens, do consider it an adverse effect */
if (!is_you)
return TRAP_CLEARLY_IMMUNE;
return TRAP_NOT_IMMUNE;
case WEB:
/* most of this code is lifted from mu_maybe_destroy_web */
if (webmaker(pm) || amorphous(pm) || is_whirly(pm) || flaming(pm)
|| unsolid(pm) || pm == &mons[PM_GELATINOUS_CUBE])
return TRAP_CLEARLY_IMMUNE;
return TRAP_NOT_IMMUNE;
case ANTI_MAGIC:
/* doesn't hurt any non-magic-resistant monster with no magic */
if (is_you) {
if (Antimagic)
return TRAP_NOT_IMMUNE;
else if (u.uenmax == 0)
/* player won't lose HP and can't lose more Pw */
return TRAP_HIDDEN_IMMUNE;
/* following conditional lifted from mintrap ANTI_MAGIC logic */
} else if (!resists_magm(mon)
&& (mon->mcan || (!attacktype(pm, AT_MAGC)
&& !attacktype(pm, AT_BREA)))) {
return TRAP_CLEARLY_IMMUNE;
}
return TRAP_NOT_IMMUNE;
case RUST_TRAP:
/* harmful if wearing anything rustable or if mon is an iron golem */
if (pm == &mons[PM_IRON_GOLEM])
return TRAP_NOT_IMMUNE;
for (obj = is_you ? gi.invent : mon->minvent; obj; obj = obj->nobj) {
/* rust traps can currently hit only worn armor and weapons */
if (is_rustprone(obj) && obj->owornmask) {
if (is_you && (obj == uquiver
|| (obj == uswapwep && !u.twoweap)))
continue;
return TRAP_NOT_IMMUNE;
}
}
return TRAP_CLEARLY_IMMUNE;
case MAGIC_TRAP:
/* for player, any number of bad effects;
for monsters, only replicates fire trap, so fall through */
if (is_you)
return TRAP_NOT_IMMUNE;
/*FALLTHRU*/
case FIRE_TRAP: /* can always destroy items being carried */
/* harmful if not resistant or if carrying anything that could burn */
if (is_you ? !Fire_resistance : !resists_fire(mon))
return TRAP_NOT_IMMUNE;
for (obj = is_you ? gi.invent : mon->minvent; obj; obj = obj->nobj) {
if (obj->oclass == SCROLL_CLASS || obj->oclass == POTION_CLASS
|| obj->oclass == SPBOOK_CLASS
|| (obj->owornmask && is_flammable(obj))) {
if ((obj->otyp == SCR_FIRE || obj->otyp == SPE_FIREBALL)
/* mon knows scroll of fire or spellbook of fireball
won't be affected; hero knows iff this one has been
seen and its type has been discovered */
&& (!is_you
|| (obj->dknown && objects[obj->otyp].oc_name_known)))
continue;
return TRAP_NOT_IMMUNE;
}
}
return (is_you ? TRAP_HIDDEN_IMMUNE : TRAP_CLEARLY_IMMUNE);
case MAGIC_PORTAL:
/* never hurts anything, but player is considered non-immune so they
can be asked about entering it */
if (!is_you)
return TRAP_CLEARLY_IMMUNE;
return TRAP_NOT_IMMUNE;
case VIBRATING_SQUARE:
/* no adverse effects */
return TRAP_CLEARLY_IMMUNE;
default:
impossible("immune_to_trap: bad ttype %u", ttype);
break;
}
return TRAP_NOT_IMMUNE;
}
static int
trapeffect_selector(
struct monst* mtmp,