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

@@ -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,