The recent "trap.c reformatting" commit included a non-formatting
change switching from gb.bhitpos.x,.y to local x,y in the rolling
boudler trap routine. The part of that routine used when a rolling
boulder hits another boulder and transfers its remaining momentum
to that other one got switched to wrong x,y and the first boulder
basically kept going, possibly hitting itself at each new position.
Note: Original change is from xNetHack by copperwater <aosdict@gmail.com>,
but this commit comes from HACKEM-MUCHE by Erik Lunna, with
some minor code formatting.
From xNetHack commit a0a6103bea:
'The original goal: nerf item destruction using a method I initially
proposed for SpliceHack, in which the number of items subject to
damage from any single source is limited by the amount of damage the
effect caused. The intent was to be more fair all around and prevent
aggravating situations where, for instance, a chest shock trap zaps
you for 4 damage and immediately ten of your rings and wands blow up.
Problem 1: no easy way to limit the items destroyed without biasing
heavily towards the start of the invent chain. The old code was able
to get away without bias by just indiscriminately destroying
everything eligible with a 1/3 chance. Here, I had to introduce
reservoir sampling in a somewhat more complex form than I've applied
it elsewhere, since there are a pool of potential items.
Problem 2: destroy_item no longer worked remotely like destroy_mitem,
which still destroyed 1/3 of items indiscriminately. Commence the
process of squishing them into one function that handles both the
player and monsters. (Which required making a lot of adjustments to
destroy_one_item, now named maybe_destroy_item, on nits such as
messaging and when to negate damage. An annoying consequence of the
merge is that in the player case, their HP is deducted and they can
be killed directly, but for monsters they need to add up the
destruction damage and return it.)
Unifying destroy_item and destroy_mitem has some advantages: in
addition to the obvious code duplication removal, it ensures monsters
now take the same damage as players for destruction (previously they
took a piddly 1 damage per destroyed item). Now when you hit
something with Mjollnir and their coveted wand of death breaks apart
and explodes, you at least get the satisfaction of knowing they took
the standard amount of damage from it. Monsters also now get
symmetry with players in having extrinsic elemental resistance
protect them from item destruction, and damage negation from item
destruction if they were appropriately resistant.
Problem 3: a lot of callers didn't preserve the "amount of incoming
damage" that this refactor relies on. E.g. if the defender resisted
that element, the local dmg variable would be set to 0. So I had to
do some wrangling with callers to save that original damage
value. The rule of thumb is: all *incoming* damage counts. So that
includes the player's spellcasting bonus if applicable, but not
things like half damage, negation due to resistance, or extra damage
due to being vulnerable to cold/fire.
Then I figured, while I'm here let's get rid of all those silly cases
where destroy_items is called multiple times for various different
object classes, and cut the object class parameter out of it. This
has a few minor effects:
- Places where different object classes previously rolled
independently for destruction to happen at all now roll
once. (Which, by my calculation, generally means less incidences of
destruction - a fire attack now won't have three separate chances
to hit your scrolls, potions, and spellbooks. On the flip side, a
lucky roll will no longer save an entire object class in your
inventory.)
- Callers can no longer specify different probabilities for
destroying different object classes. The only place this was really
used was to call destroy_item with a slightly lower probability on
SPBOOK_CLASS. With the nerf in this commit, less of them ought to
be destroyed anyway.
- A very edge case of where explosion-vs-monster damage was totted up
differently for golems, which could result in differences of a hit
point here or there.
- All object classes being processed in one go means that less items
are destroyed than would be if they were still processed
independently. This is not really visible compared to the old
baseline of just destroying 33% of everything, but would be a
marked difference versus a copy of the game that still called
destroy_items separately for different object classes. To
compensate, I adjusted my planned damage-to-destruction-limit
scaling factor down from 8 to 5.
Not done: merging in ignite_items(), though that would probably be
really easy now.'
Notes from porting from xNetHack:
- It might be necessary to reexamine at all the conditional checks for
calling destroy_items. Because item destruction is much more
restrained and uses the actual damage from an effect, we might now
need to check 'if (!rn2(3))' and similar in all the places item
destruction occurs.
Monstly changing '<type>* <var>' to '<type> *<var>'. Wrap or shorten
a few wide lines.
One of the rolling boulder routines accessed gb.bhitpos.x,y many times;
change that to use local x,y instead.
If a trap is blocking levitation or flight and you use wand of opening
or spell of knock to get out, you'd get things like
|You start to float in the air!
|You are released from the bear trap.
Give the release message before the actual untrapping.
lava_effects() item destrunction had the logic for handling Book of
the Dead wrong. (However, that didn't matter since the obj_resists()
check earlier would prevent it from being burned up. Fix it anyway.)
Three months ago to prevent an "object lost" panic situation when
stealing an item that let hero survive water (several candidates)
would result in drowning, remove_worn_item() was changed to flag the
item being removed as in_use and emergency_disrobe() was changed to
avoid dropping in_use items while drowning. That seemed to work ok.
But for lava instead of water, in_use is a flag to destroy the item
(set in advance, before issuing messages that can give the player a
chance to trigger a hangup save). So instead of keeping the item
around for theft to finish, it was deallocating it. steal() would
format the freed object and then access some of its fields, leading
to havoc.
This adds a hack to allow one item already flagged as in_use to be
treated differently by lava_effects() from the ones it flags for
destruction. This also seems to work ok, but we may need to start
putting freed items on a deferred deallocation list similar to how
dead monsters are kept around for the rest of the current move.
The fix/hack has revealed two more bugs that this doesn't address.
An item being stolen is removed without any message, then if that
removal doesn't kill the hero a theft message is given. The message
sequencing is wrong. Flying hero who loses amulet of flying just
gets affected by lava; player is only told why after life saving.
The other issue is that life-saving from lava can teleport the hero
to where the thief can no longer be seen, yielding "It steals <item>"
even though "It" was visible when the theft started.
Some minor cleanup of artifacts from the splitting up of trap effects
into the various trapeffect_foo functions: consolidate redundant
variables in trapeffect_pit (tt vs ttype), and simplify the definition
of 'inescapable' traps a couple functions (since the functions are now
specialized to a particular trap, it's unnecessary to check ttype
there in determining whether it's an inescapable Sokoban pit or hole).
Issue reported by Umbire: suggestion to always destroy adjacent webs
via 'F'<dir> if wielding Sting or Fire Brand.
Sting already did that; this adds Fire Brand.
This also augments the #untrap command when wielding either of those,
or any other blade. And rephrases successful untrap message
"You remove {the or your} {bear trap or webbing} from Fido." to
"You extract Fido from {the or your} {bear trap or web}." since the
trap remains intact.
Forcefight and #untrap against webs ought to be reconciled to remove
[some of] their differences and/or share code. But not by me...
Closes#1201
Do isok check apriori to accessing the levl array to filter
out dx/dy that have grown too large.
This fixes:
trap.c:3455:19: runtime error: index 80 out of bounds for type 'rm [80][21]'
Adds a new boolean option, accessiblemsg. If on, some game messages
are prefixed with direction or location information, for example:
(west): The newt bites!
(northwest): You find a hidden door.
I added the info to the most common messages, but several are
still missing it.
Two variations:
IndexOk(idx, array) validate that idx is a valid index into the array
IndexOkT(idx, array) validate that idx is a valid index into the
array, excluding the final Terminator element
Update several places where lazy lastseentyp[] might be an issue.
I think it isn't updated in a timely fashion when newsym() shows
a spot covered by an object or trap, but didn't manage to find any
cases where that caused a problem. This is more in the nature of
a precaution.
Checking the callers:
toss_up() would have segfaulted prior to use of stone_missile() if obj were NULL.
thitu() now has a guard prior to use of stone_missile()
ohitmon() would have crashed from earlier dereference otmp->dknown if it were NULL,
otmp arg is declared nonnull
thitm() now has a guard prior to use of stone_missile().
hmon_hitmon_do_hit() null obj takes a different code path than the code path
using stone_missile(); comment asserting that added
Moving over at item that's resting on ice gives a message about there
being ice present and then about the item, whether mention_decor is On
or Off. With it On, you'll get a message about being back on solid
ground as soon as you leave the ice. With it Off you wouldn't get
that at all if not levitating; that's the basic no-mention_decor
behavior for ice. However, if you were levitating, you would get a
delayed "back on solid ground" message when moving over some other
object, which might occur quite a bit later. Autopickup handling is
calling describe_decor() when the hero is levitating and some of that
wasn't appropriate for no-mention_decor.
This issue has been present since I first implemented mention_decor,
not introduced by recent back_on_ground() changes.
Donning elven boots while riding and not already stealthy, you'd get
the message "you walk quietly" when not walking at all. Instead of
just changing the message, make riding a non-flying steed block
stealth. Riding a flying steed (or one you take aloft with an amulet
of flying) does not. It would have been quite a bit simpler to have
made riding anything block stealth, but the hard part is done.
The same checks were being repeated for every damage type; this
sends them through two centralised functions (one for checking
whether an extrinsic blocks a specific instance of item destruction
and one for the enlightenment message), so that new mechanisms of
item destruction prevention will need to change only one point in
the code.
Report was that converting a novel into a blank spellbook via water
damage resulted in a spellbook of blank paper that increased weight
when put into a bag of holding.
Spellbooks weigh 50 units but novels were defined with a weight of 0;
when one was created, a non-zero weight of 1 got assigned. Blanking
it didn't update the weight; that stayed at 1. Putting it into a
container reset the weight to match the new type: spellbook of blank
paper, so its weight increased.
Do that when blanking rather than wait until a container might fix it
up. If it is already in a [possibly nested] container, update that
container's weight too along with any outer ones.
This also changes the base weight of novel from 0 to 10, so it still
gets magically heavier when turned into a spellbook of blank paper.
(The alternative seems to be to destroy it instead.)
The Book of the Dead weighed only 20 units which seemed odd to be so
much less than a spellbook. This changes that to 50 to match those.
Put everything through a single function that can handle all the
complicated parts of using the correct proposition for different terrain
types, and will not just call things "solid ground" indiscriminately.
This got complicated but I'm not sure if it's possible to do it much
simpler while still using the distinct names for each type of terrain
(unless you are OK with the sentences sounding sort of wonky).
If throwing an item while levitating sent the hero hurtling into a wall
of water, the item would land in the water due to water_damage_chain's
use of bhitpos. Restore the previous value when it is finished to avoid
interfering with the use of bhitpos further up the call stack.
"object lost" panic occurred when hero's worn amulet of magical
breathing was stolen. This prevents drown() -> emergency_disrobe()
from dropping an item while in the midst of it being stolen, avoiding
the possibility of it no longer being in inventory when the theft
completes. There may be variations other than drowning that lead to
unwear -> drop-or-destroy that are still vulnerable, and this fix can
potentially cause items to vanish from hangup save files.
It also has a side-effect of not being able to drop levitation boots
to lighten encumbrance enough to crawl out of water if the drowning
occurs while they are being taken off, not just when being stolen,
even though they should be easily droppable in such circumstance. The
hero will just need to drop other things instead.
While testing something else, I noticed rolling boulders
just ignored walls and trees; in normal play this isn't
a problem - but should probably make boulders handle other
terrain too. Lava and water is already handled correctly.
b_trapped was treating 0 as a null value for its bodypart parameter, but
0 is actually the value of ARM, so b_trapped(..., ARM) would be treated
as intending no A_CON abuse. Add NO_PART = -1 to the bodypart_types
enum, and use that instead of 0 as the "no body part" value in
b_trapped, so that ARM can be passed to it without any ambiguity.
aosdict identified this issue in xNetHack and handled it differently; he
added NO_PART with a value of 0, incremented the existing bodypart_types
values, and padded the body part arrays so the actual body parts would
start at index 1. I think using NO_PART = -1 is simpler, but that's an
alternative approach that can be used instead -- it is advantageous in
that it automatically fixes any other places where 0 is assumed to be a
non-body-part value that I may have overlooked.
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 #259Closes#259
Sitting on a squeaky board wasn't triggering it even after the
handler for that type of trap allowed VIASITTING to override Flying.
The check_in_air() test for floor traps didn't have the same override,
so the squeaky board handler didn't get called.
This fixes that, which led to inconsistency with some other trap
types, and additional fixes for pits and bear traps. There might be
others that still behave oddly. For example, if flying over a hole,
using #sit yields
|You land. There's a gaping hole under you! You don't fall in.
I think that's a message phrasing issue rather than a falling trap
issue; if you want to go down, use '>' instead of #sit. On the other
hand, you do now fall into pit traps for #sit while flying over them.
If the hero deliberately sits on the floor while flying over a squeaky
board, then either they're trying to squeak it on purpose or they haven't
noticed it. Either way, sitting should trigger it.
Especially powerful magic is meant to be able to destroy altars
(breaking a wand of digging or using a drum of earthquake), but it was
being blocked by a check added to maketrap() in a7f6460 designed to
prevent wizard-mode trap wishing from overwriting stairs. The check was
refined in 6a3d82c to add an exception for digging up graves, but
continued to prevent the destruction of other types of
previously-destructible terrain.
Since this block was a side effect of an attempt to add some guard rails
to wizmode terrain wishes, and the code to explicitly permit the
destruction of other furniture with especially powerful magic is still
present, it doesn't seem like it was actually intended. Open up terrain
destruction by digging magic a bit more by excluding only
non-destructible terrain, not all furniture other than graves, from
being overwritten by pits and holes.
Also, use AM_SANCTUM to more precisely identify non-destructible high
altars in dig_check() rather than checking whether the hero is on the
Astral or Sanctum levels.
Pull request from NulCGT: make statues created for statue traps be
5 to 10 points higher in difficulty than the default would be.
5 to 10 points of difficulty higher is already used for figurines.
The pull request chose the same amount but I've reduced it to 3 to 6.
Partly so that they won't be the same, partly so that they won't be
too hard when activated, and partly so that the creature won't be
quite as obvious a give away that the statue is a trap.
Closes#1009
If the priest quest artifact Mitre of Holiness (a helm of brilliance,
so crystal helmet) is acquired via the quest (rather than by wishing)
make it start out tempered (aka crackproof|erodeproof).
Issue reported by loggersviii: dipping a container into an uncursed
potion of water mentions water getting into the container. That
happens even when that type of potion hasn't been discovered yet.
Make POT_WATER become discovered if this occurs. Doesn't apply when
hallucinating where a random liquid is mentioned instead of water.
Fixes#1061
Instead of a 5% chance for crystal plate mail or crystal helmet to
break each time it's subjected to breakage, switch to a 10% chance
but the damage is treated as erosion rather than break/don't-break.
'crystal foo' will need to go through four stages of damage before
breaking: cracked crystal foo, very cracked crystal foo, thoroughly
cracked crystal foo, then gone. Crackproof handling is included,
described as tempered crystal foo.
It mostly still applies to throwing and kicking the item. Having
some hits trigger damage might be worthwhile but isn't implemented.
Object creation within lua code probably needs to be updated, and
when the Mitre of Holiness is created in the priest/priestess quest
it should start out as tempered (erodeproof). Perhaps it ought to
be erodeproof regardless of where/how it's created.
Issue reported by vultur-cadens: changing helm of brilliance to
crystal made it stop being classified as "hard helmet" so it gave
less protection against things falling onto the hero's head.
Change the is_metallic() tests used on helmets to new hard_helmet().
Unlike when thrown, crystal helmets don't break when objects fall
on them.
Fixes#1060