From 1a64ee1c285f8d9a27e58efa3989a281413f9b8e Mon Sep 17 00:00:00 2001 From: PatR Date: Fri, 8 Sep 2023 15:55:31 -0700 Subject: [PATCH] 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 ?" 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 rather than just y to accept, it also forces no 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 --- dat/opthelp | 3 +- doc/Guidebook.mn | 7 +- doc/Guidebook.tex | 9 ++- doc/fixes3-7-0.txt | 5 ++ include/extern.h | 1 + include/flag.h | 5 +- include/trap.h | 19 ++++-- src/hack.c | 39 +++++++++++ src/options.c | 5 +- src/trap.c | 166 +++++++++++++++++++++++++++++++++++++++++++++ 10 files changed, 248 insertions(+), 11 deletions(-) diff --git a/dat/opthelp b/dat/opthelp index 80140b410..07aed9fa2 100644 --- a/dat/opthelp +++ b/dat/opthelp @@ -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' diff --git a/doc/Guidebook.mn b/doc/Guidebook.mn index 0bc716354..d0bf11eb6 100644 --- a/doc/Guidebook.mn +++ b/doc/Guidebook.mn @@ -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 diff --git a/doc/Guidebook.tex b/doc/Guidebook.tex index 1b7de3ccd..7529b5918 100644 --- a/doc/Guidebook.tex +++ b/doc/Guidebook.tex @@ -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}' diff --git a/doc/fixes3-7-0.txt b/doc/fixes3-7-0.txt index c353a8bbd..07c3f4d20 100644 --- a/doc/fixes3-7-0.txt +++ b/doc/fixes3-7-0.txt @@ -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 diff --git a/include/extern.h b/include/extern.h index 42eb939b5..f0566e34f 100644 --- a/include/extern.h +++ b/include/extern.h @@ -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); diff --git a/include/flag.h b/include/flag.h index d4eda3a3d..3a0a5ddb4 100644 --- a/include/flag.h +++ b/include/flag.h @@ -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) diff --git a/include/trap.h b/include/trap.h index 35cfdd507..463fe9413 100644 --- a/include/trap.h +++ b/include/trap.h @@ -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 */ diff --git a/src/hack.c b/src/hack.c index 4112076a4..ac3798472 100644 --- a/src/hack.c +++ b/src/hack.c @@ -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); diff --git a/src/options.c b/src/options.c index 2941f3e89..099e3e296 100644 --- a/src/options.c +++ b/src/options.c @@ -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 */ diff --git a/src/trap.c b/src/trap.c index 975447f2c..2db4de9bd 100644 --- a/src/trap.c +++ b/src/trap.c @@ -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,