Commit Graph

568 Commits

Author SHA1 Message Date
Pasi Kallinen
d0b9846367 Move item actions into separate src file
Haven't tested compilation on Windows and VMS ...
2026-01-11 14:46:25 +02:00
PatR
ac7f0d3615 context sensitive item action for candles
When picking candles from an inventory display, expand the 'a' choice
if carrying the Candelabrum of Invocation.
2026-01-11 01:49:06 -08:00
PatR
5030de7343 fix #1466 fix - stacking in inventory
The earlier fix from a couple of days ago was mislabeled as #1455
but was actually #1466.  It fixed picking up a thrown stack into
hero's empty quiver but broke keeping thrown items, dropped items,
and stolen items separate on the floor.  This repairs that.
2025-12-26 14:53:59 -08:00
PatR
5bda026acb fix issue #1455 - fully thrown quiver stack won't
autoquiver when picked back up

Issue reported by ars3niy:  empty quiver used to be refilled when
picking a thrown item or stack up.  Bug introduced by a previous fix
(commit 593a93d254) dealing with the
post-3.6 obj->how_lost field.

As with the last time I dealt with this, there was a lot of trial
and error involved.  This fixes the quiver issue without bringing
the earlier problem back.  This time the problem was that how_lost
got cleared before it was used to check whether an item being picked
up had been thrown.

Dropping part of a stack and throwing another part of the same stack
may behave oddly if a monster picks both up.  I am not going to try
to figure that out.

Fixes #1466
2025-12-24 22:11:16 -08:00
nhmall
7dc4512bb3 follow-up: just use existing carrying() 2025-12-09 15:25:10 -05:00
nhmall
6ba053669e follow-up for #name permanent inventory update
Using extended #name for an object on the floor (for example)
wasn't updating the permanent inventory to reflect the updated
object type name if there was also one in inventory.
2025-12-09 14:17:42 -05:00
Alex Smith
621d21ebb9 Archeologists can decipher scroll labels
Prior to this commit, Archeologists had an incredibly difficult
start, worse than was intended. This is intended to make it a bit
easier (whilst making the role more different from other roles) via
allowing them to identify scroll types earlier in the game than the
other roles are able to (they do it using a knowledge of ancient
languages that lets them read scroll labels that other roles
couldn't).
2025-12-03 21:35:17 +00:00
Alex Smith
656f58b099 A basic version of starting character rerolling
This adds a "reroll" option that lets players reroll their
character's attributes and starting inventory. Although I generally
think doing this makes the game worse, a) some players are going to
do it regardless and b) if a player is going for a challenge game,
rather than to win, it may be required. So in the absence of an
option like this, players repeatedly start and quit games instead,
creating a large number of junk logfile entries and generally
causing problems for other players on the same shared machine
(because repeatedly reloading the game is very CPU-intensive).

This should in theory be windowport-agnostic (although in practice
it may not be). Tested on tty, X11 and curses; on tty and X11 it
works fine (although X11 treats the change in attributes as
something that needs a status highlight), on curses it is slightly
jankier in terms of what other windows are drawn in the background
(but still plays correctly and I suspect this is a pre-existing
bug).

To form a complete implementation, we will need to consider the
following:

- Should there be a delay on a) starting the game and/or b)
  rerolling? If so, what should it be (maybe configurable via
  sysconf?)

- Should we take more steps to discourage players from rerolling?
  It would be bad if players see the option exists and turn it on
  just because it exists, or (worse) treat it as condoning the
  particular style of play.

- Should we take steps to detect that players are rerolling
  manually and a) tell them to use the option instead, b) tell them
  that this is not an intended way to play (and may make the game
  less enjoyable and/or prevent them getting the practice they need
  to eventually win)?

Breaks save and bones files.
2025-11-30 06:49:14 +00:00
Alex Smith
8c29b20010 Accurately track which items have been discovered, even if not #named
This fixes a couple of bugs: a long-standing bug in which writing a
scroll by label could fail even if you've already seen a scroll with
that label (due to the game not tracking whether or not you've seen a
scroll if it doesn't have a name); and a somewhat newer bug in which
spellbooks auto-identified by Wizard knowledge were marked as having
been encountered (rather than as known but not encountered).

Breaks save file compatibility, but not bones files.
2025-11-25 22:42:38 +00:00
Alex Smith
fce66245ca Don't attempt to cache encumber_msg result
There was only one point in the code at which this caching was
being done, and it was incorrect: it's possible for the result of
near_capacity to change during a monster turn because monster
actions can change either inventory weight or carry capacity.

The bug was particularly relevant in cases where a character
polymorphed into a slow weak monster gets attacked by a monster
that moves at normal speed: due to the polyform being slow, the
normal-speed monster gets in a lot of attacks and causes a
rehumanization, but due to the polyform being weak, it was
burdened at the start of the monster turn, and so when that
penalty is (due to the bug) applied to the next turn it can
mean that the character misses the next turn too, and may end up
dying as a result.
2025-11-24 02:07:23 +00:00
PatR
f7e12d2801 do-again vs yn_function()
This fixes the impossible from yn_function() for ^A after Z.  One
call to yn_function stored the spell letter for do-again and then
another call was unintentionally using that when getting a y/n
response for askchain() while using menustyle:Traditional [when
spell was identify and eligible objects needed confirmation about
whether to be ID'd].

Fixing that seemed to break #pray so the paranoid_confirm routine
has been changed to not rely on canned input, even for queries where
the player hasn't specified that confirmation be required.

Behavior of ^A might be different in unexpected ways, but it wasn't
working correctly before.
2025-11-08 18:38:55 -08:00
nhmall
d5658018ac alternative to display_inventory for window-port
Several window ports that support perm_invent were
using a call back to the core display_inventory()
function.

While calling from the window port back to core functions
is arguably not ideal in the first place, it was recently
brought to light that code NetHack-3.7 code changes to
display_inventory() actually caused it to stop repopulating
the perm_invent window as intended under certain circumstances.

For now, provide an alternative function, repopulate_perminvent(),
that hopefullshould still work the way it did previously.

There will likely be some additional changes after this to
further improve things, at some point.

For now though, this
Resolves #1454
2025-11-08 14:26:07 -05:00
nhmall
0f01260605 Revert "blank perm_invent after repeating spell cast"
This reverts commit 7763f4c5b0.
2025-11-06 18:44:12 -05:00
nhmall
4574738c48 Revert "follow-up: program_state field isn't boolean"
This reverts commit 6af5a906bc.
2025-11-06 18:43:48 -05:00
nhmall
6af5a906bc follow-up: program_state field isn't boolean 2025-11-06 01:38:06 -05:00
nhmall
7763f4c5b0 blank perm_invent after repeating spell cast
add mechanics to ensure display_inventory() refreshes perm_invent as
expected when update_inventory() is called from a repeat command.

Resolves #1454
2025-11-06 01:34:52 -05:00
PatR
7e3586acad ring item action bit for 'P'
If the hero is in a form without fingers but is wearing two rings (put
on before shape change), examining inventory and selecting a third
ring shows an item action menu entry of "P - [both ring fingers in use]"
(as of a couple of days ago).  Change that to plug in appropriate body
part for finger.
2025-10-21 14:06:16 -07:00
PatR
966145a61d item action 'T' against covered armor
Using 'i'+menu choice for suit+'T' to try to take off a suit that is
covered by a cloak (or shirt covered by suit and/or cloak) wouldn't
do anything.  It should report that you need to take off the outer
garment first and then not take the chosen item off.

There is probably a simpler fix.  It took me a long time to figure
where things were going wrong and them cobble this together.

A big chunk of the diff for invent.c is just identation, surrounding
a one-line change there.
2025-10-20 13:29:42 -07:00
PatR
a9f84bfe9a item action for towel
Change the menu entry for putting on a towel to "Put this on to blindfold
yourself" since "Use this ..." seems ambiguous.

Also, for the 'P' and 'R' item actions, list amulets before rings like
most other routines that can deal with both.
2025-10-18 10:20:13 -07:00
PatR
da20b839b5 item actions for accessories
Update item actions for rings, amulets, and eyewear.  Make 'P' for an
accessory that isn't worn behave similarly to recently modified 'W',
and make 'R' for an accessory that is worn be more specific.
2025-10-15 23:49:52 -07:00
PatR
6f8c1127ed item action 'W' for armor
In the context-sensitive menu when picking an item of armor from an
inventory listing, distinguish between wear-this-armor from could-
wear-this-armor-if-something-else-wasn't-already-worn-in-its-slot.
2025-10-13 23:13:15 -07:00
nhmall
1a0ef41406 fix a warning
invent.c
.\invent.c(3668): warning C5287: operands are different enum types 'inv_modes' and 'inv_mode_bits'; use an explicit cast to silence this warning
.\invent.c(3669): warning C5287: operands are different enum types 'inv_modes' and 'inv_mode_bits'; use an explicit cast to silence this warning
2025-05-13 20:02:13 -04:00
PatR
6368bf2e73 analyzer lint for i*.c 2025-01-19 22:53:03 -08:00
nhmall
25558e1e01 follow-up
Check the correct bits before returning.
2025-01-12 13:58:13 -05:00
nhmall
bee21e3447 fix K4318
Reported by paxed. A potion of oil, that was already in the midst of exploding,
got picked up through spot_effects(), which led to it merging with
another potion of oil and the freeing of the original obj.

The original obj pointer was still held by breakobj(), and breakobj()
proceeded to delete the obj (again).

Function nesting:

 1    spelleffects()
 2     -> weffects()
 3      -> bhit()
 4       -> bhitpile()
 5        -> bhito(obj ...)
 6         -> hero_breaks(obj ...)
 7          -> breakobj(obj ...)
 8           -> explode_oil(obj ...)
 9            -> splatter_burning_oil()
10             -> explode()
11              -> zap_over_floor()
12               -> melt_ice()
13                -> spot_effects()
14                 -> pickup()
15                  -> pickup_object(obj ...)
16                   -> pick_obj(obj ...)
17                    -> addinv(obj ...)
18                     -> addinv_core0(obj ...)
19                      -> merged(obj ...)
20                       -> obfree(obj ...)
21                        -> dealloc_obj(obj ...)

 8           -> delobj(obj ...)
 9            -> delobj_core(obj ...)
10             -> obfree(obj ...)
11              -> dealloc_obj(obj ...)
12               -> impossible("obj already deleted)

This marks the exploding potion with LOST_EXPLODING, so that it won't
get picked up, or merged with another object during the long
sequence of functions, and that should take care of 15-21 above.
2025-01-12 13:50:25 -05:00
Pasi Kallinen
9313fb7747 Clear tin-eating struct when object goes away
The tin-eating context was pointing to a non-existent object,
causing an error when the fuzzer somehow managed to continue eating
the freed tin object.
Clear the pointer when the tin leaves inventory or the object
is deleted.
2025-01-03 22:21:14 +02:00
nhmall
d3c57e1b42 fix reported crash of TTY_PERM_INVENT segfaulting
Options processing can be early, even before ttyDisplay is allocated.
If we find that TTY_PERM_INVENT initialization is happening too early,
just set a marker (iflags.perm_invent_pending) to try again a bit later.

The changes in win/share are just to be able to sucessfully
reproduce the original issue on Windows. It was easily reproduced
on Unix, just by building with TTY_PERM_INVENT in include/config.h
and setting OPTIONS=perm_invent in config file.
2025-01-02 11:46:15 -05:00
nhmall
8da4f74dcf revisit some coordxy casts inherited from time of xchar
The ones below marked "left,revisit" might be worthy of code changes

explode.c:removed: arg sx,sy passed to breaks() are coordxy and so are the parameters
invent.c: removed: discover_artifact() parameter is xint16, so change cast to match
mail.c: removed: gv.viz_rmin[] contains coordxy's, and enexto() 2nd param is too
mkmaze.c: removed: x, y are coordxy and so are cc->x, cc->y
mkroom.c: removed: tx, ty are coordxy and so are parameters to occupied()
mplayer.c: left,revisit: x,y declared int, but mk_mplayer() params are coordxy
sp_lev.c 2890: left alone: x,y declared int, but is_ok_location() params coordxy
sp_lev.c 4114: left alone: cast from lua_Integer
sp_lev.c 4123: left alone: cast from lua_Integer
sp_lev.c 4650: left alone: cast from lua_Integer returned from lua_tointeger
sp_lev.c 4765: left alone: cast from int_Integer returned from lua_tointeger
sp_lev.c 4772: left alone: cast from int_Integer returned from get_table_int
teleport.c 366: left alone: cast from int returned from rn2()
timeout.c 2171: left alone: cast from long
vault.c: left,revisit: guardx,guardy declared int, but bestcc fields are coordxy
worm.c 791: left,revisit: wseg.wx, wseg.wy are coordxy, so are nx, ny, but not
                          ox, oy; why are ox, oy needed at all?
worm.c 955: left alone: wx, wy are coordxy, wseg_at() params are int x, int y
zap.c 5061: left alone: cast from long
2024-12-17 13:02:05 -05:00
nhmall
0792e5fe9e expand implicit fallthrough detection to non-gcc compilers
gcc has recognized various "magic comments" for white-listing
occurrences of implicit fallthrough in switch statements for
a long time:

    The range and shape of "falls through" comments accepted are
    contingent upon the level of the warning. (The default level is =3.)

    -Wimplicit-fallthrough=0 disables the warning altogether.
    -Wimplicit-fallthrough=1 treats any kind of comment as a "falls through" comment.
    -Wimplicit-fallthrough=2 essentially accepts any comment that contains something
     that matches (case insensitively) "falls?[ \t-]*thr(ough|u)" regular expression.
    -Wimplicit-fallthrough=3 case sensitively matches a wide range of regular
     expressions, listed in the GCC manual. E.g., all of these are accepted:
        /* Falls through. */
        /* fall-thru */
        /* Else falls through. */
        /* FALLTHRU */
        /* ... falls through ... */
       etc.
    -Wimplicit-fallthrough=4 also, case sensitively matches a range of regular
     expressions but is much more strict than level =3.
    -Wimplicit-fallthrough=5 doesn't recognize any comments.

Plenty of other compilers did not recognize the gcc comment convention,
and up until now the compiler warning for detecting unintended
fallthrough had to be suppressed on other compilers. That's because the code
in NetHack has been relying on the gcc approach, and only the gcc approach.

The C23 standard introduces an attribute [[fallthrough]] for the
functionality, when implicit fallthrough warnings have been enabled.

Several popular compilers already support that, or a very similar attribute
style approach, today, even ahead of their C23 support:

       C compiler                       whitelist approach
       ---------------------------   -------------------------------------
       C23 conforming compilers         [[fallthrough]]

       clang versions supporting
       standards prior to
       C23                              __attribute__((__fallthrough__))

       Microsoft Visual Studio
       since VS 2022 17.4.
       The warning C5262 controls
       whether the implict
       fallthrough is detected and
       warned about with
       /std:clatest.                    [[fallthrough]]

This adds support to NetHack for the attribute approach by inserting a
macro FALLTHROUGH to the existing cases that require white-listing, so
other compilers can analyze things too.

The definition of the FALLTHROUGH macro is controlled in include/tradstdc.h.

The gcc comment approach has also been left in place at this time.
2024-11-30 14:16:27 -05:00
nhmall
d428beb8dd remove an unused prototype 2024-11-10 17:58:04 -05:00
nhmall
c87a373b11 more follow-up related to #1320 2024-11-10 10:06:07 -05:00
nhmall
5cc529efc8 follow-up related to #1320
This is additional groundwork related to
https://github.com/NetHack/NetHack/issues/1320

This additional groundwork just puts some safeguards
in place to make it rather tough to end up with an
instant death from handling a cockatrice corpse in
your inventory without appropriate protection.

At this point, still no actual petrification will occur.
2024-11-09 23:49:10 -05:00
nhmall
9c554895b1 adjust wish for cockatrice corpse
Wishing is powerful, so if you cannot safely handle a cockatrice
corpse, then have a wish for one result in the corpse materializing
on the floor rather than in your inventory.

Resolves #1320
2024-11-09 14:06:43 -05:00
nhmall
e863583c56 iterating gi.invent (github issue #1315)
GitHub issue #1315 points out that it is possible for
a downstream function to change an object's nobj field
to point to a completely different chain.

The cited example by @vultur-cadens was:

     for (obj = gi.invent; obj; obj = obj->nobj)
         if (obj->oclass != COIN_CLASS && !obj->cursed && !rn2(5)) {
             curse(obj);
             ++buc_changed;
         }

    curse() drops the weapon with drop_uswapwep(),
        which calls dropx(),
            which calls dropy(),
                which calls dropz(),
                    which calls place_object().

place_object alters the nobj pointer, to point to the floor chain:
    otmp->nobj = fobj;
    fobj = otmp;

The result was that the next loop iteration was then using floor
objects from the floor chain.

This alters several for-loops to use a more consistent approach,
particularly when the obj is being handed off to a function,
where a downstream function might, or might not, alter the nobj
field.

References:

https://github.com/NetHack/NetHack/issues/1315
https://www.reddit.com/r/nethack/comments/1gkc9ub/even_if_you_drop_an_item_before_drinking_from_the/
2024-11-06 16:59:51 -05:00
PatR
02cc7c76e6 add '?' choice after choosing '* for force_invmenu
If the force_invmenu option is On for choosing an inventory item
and the set of likely candidates is only a subset of full inventory,
then "* (list everything)" is an extra menu choice.  If the player
picks that, the menu is reissued showing entire inventory (with the
extra "* (list everyhing)" choice omitted this time).

When that happens, add "? (list likely candidates)" instead, so that
the menu can be toggled back to how it initially was.

If the menu allows choosing anything in inventory (the 'd' command,
for instance), it will already show full inventory and neither '*'
nor '?' gets included as helper choices.
2024-09-26 09:57:31 -07:00
nhmall
60dc14952d overflow if 'word' arg points to full QBUFSZ buf
This prevents a buffer overflow that was encountered during fuzzing,
but the underlying issue in the caller dodip() is still pending.

That appears to be the result of 'obuf' not being filled with
appropriate content prior to being used at line 2343 in potion.c.
2024-09-14 12:54:34 -04:00
PatR
aa043f0ddf some reformatting (2 of 4) 2024-09-05 14:51:21 -07:00
PatR
65b5f2cff1 farlook & probing feedback for monsters in regions
When looking at a monster that's inside a gas cloud, include that
fact in the output for farlook and for probing.  When the monster
being examined is sensed rather than seen, you'll sense the presense
of the cloud as well as the monster even if the cloud can't be seen.

Do likewise for self when using look-here (':').  Bonus fix: zapping
wand of probing at self while engulfed reported that you were just
held by the engulfer.

Also fix an old comment typo/thinko.
2024-08-19 13:10:28 -07:00
nhmall
0eb7f109e0 follow-up, program_state 2024-07-13 16:31:35 -04:00
nhmall
6c0ae092c6 distinguish global variables that get written to savefile
The g? structs had a mix of variables that were written to
the savefile, and those that were not.

For better clarity and to distinguish those that end up in
the savefile, relocate some g? variables that get written
directly to the savefile into different structs.

This updates EDITLEVEL, although technically it probably
didn't need to, since savefile contents are not changing.

Details:

    gb.bases            -> svb.bases
    gb.bbubbles         -> svb.bbubbles
    gb.branches         -> svb.branches
    gc.context          -> svc.context
    gd.disco            -> svd.disco
    gd.dndest           -> svd.dndest
    gd.doors            -> svd.doors
    gd.doors_alloc      -> svd.doors_alloc
    gd.dungeon_topology -> svd.dungeon_topology
    gd.dungeons         -> svd.dungeons
    ge.exclusion_zones  -> sve.exclusion_zones
    gh.hackpid          -> svh.hackpid
    gi.inv_pos          -> svi.inv_pos
    gk.killer           -> svk.killer
    gl.lastseentyp      -> svl.lastseentyp
    gl.level            -> svl.level
    gl.level_info       -> svl.level_info
    gm.mapseenchn       -> svm.mapseenchn
    gm.moves            -> svm.moves
    gm.mvitals          -> svm.mvitals
    gn.n_dgns           -> svn.n_dgns
    gn.n_regions        -> svn.n_regions
    gn.nroom            -> svn.nroom
    go.oracle_cnt       -> svo.oracle_cnt
    gp.pl_character     -> svp.pl_character
    gp.pl_fruit         -> svp.pl_fruit
    gp.plname           -> svp.plname
    gp.program_state    -> svp.program_state
    gq.quest_status     -> svq.quest_status
    gr.rooms            -> svr.rooms
    gs.sp_levchn        -> svs.sp_levchn
    gs.spl_book         -> svs.spl_book
    gt.timer_id         -> svt.timer_id
    gt.tune             -> svt.tune
    gu.updest           -> svu.updest
    gx.xmax             -> svx.xmax
    gx.xmin             -> svx.xmin
    gy.ymax             -> svy.ymax
    gy.ymin             -> svy.ymin

Related note:
There are some pointer variables that are heads of chains that were not
moved from 'g?' to 'sv?', because they are not actually written to the
savefile directly, but the objects/monst/trap/lightsource/timer in the
chains they point to are. That can be changed, if desired.
Examples: gi.invent, gm.migrating_objs, gb.billobjs, gm.migrating_mons,
          gf.ftrap, gl.light_base, gt.timer_base
2024-07-13 14:57:50 -04:00
PatR
f634bb863f refine context-sensitive inventory action 'w'
|w - wield this item in your hands
is "suboptimal" when poly'd into some form which lacks hands.  Don't
offer 'w' as a choice if in a form what can't wield anything, and
substitute "claws" for "hands" when in forms that warrant it.

Include iron ball as a type of item leading to 'w - wield as weapon'
instead of falling through to 'w - wield in hands'.
2024-05-21 14:27:49 -07:00
PatR
a1692a8061 update context-sensitve inventory actions
Since inventory actions can be used as teaching aids for regular
command usage, change picking a potion in inventory from
|q - Quaff this potion
to
|q - Quaff (drink) this potion

When picking the uquiver item in inventory, add
|f - {Shoot|Throw} this item
and for the shoot case, tack on "with wielded <launcher>".

That makes 't - {Shoot|Throw} this item' nearly redundant (but only
for an item in uquiver slot) so tack on "(same as 'f')".

For the 'Q' choice (only offered for weapons or rocks/gems), have it
mention what the quiver is for rather than just that choosing that
menu entry would put the stack in the quiver slot.

For 'E - Write on the floor with this object', change the verb to
"Engrave" when the object is a bladed weapon, hard gem, hard gem
ring, or wand.  It also changes 'floor' to surface() which might
need some refinement.

For non-weapons, change 'w - Hold this tin opener...' and 'w - Hold
this item in your hands' to 'w - Wield this...'.  The concept of
holding an item in hero's hands isn't applicable in general, and in
particular any "held" item replaces wielded weapon.

For a bunch of instances of "this item", when the quantity of the
selected inventory entry is more than 1, change to "one of these
items" or "this stack" depending on how the specified action deals
with stacks.
2024-05-21 11:28:12 -07:00
Pasi Kallinen
312a4bcd26 Fix weight of merged partially eaten food
Due to integer rounding, merging two partially eaten food rations
will have wrong weight if we add the old weights together.
Recalculate the weight instead.

quan = 1, weight = 6  (6.675)
quan = 2, weight = 13 (13.35)
2024-05-06 12:06:33 +03:00
Michael Meyer
b662134eba Fix: bill_dummy_obj billed excessively for stacks
Add a way to request that unpaid_cost() produce the cost for a single
item, which is necessary for the price adjustment made in
bill_dummy_object.  Another option would be to simply divide by quan in
bill_dummy_object, but this might be more future-proof in case
unpaid_cost ever involves more than simple multiplication by quan
(e.g. the use of alternate units vs the base price, as are used for
globs).

Fixes #1236
2024-04-27 18:42:50 -07:00
PatR
f801863e5c misbehavior by #adjust
Reported directly to devteam:  using '#adjust a a' to collect
invent stacks compatible with the one in slot 'a' all into 'a' gave
feedback of "Merging: a - ..." even though "Collecting: a - ..."
was intended.  Also, if there weren't any such compatible stacks,
so that the whole operation didn't accomplish anything, it reported
"Collecting a - ..." without the intended colon between the action
and the inventory letter.

Test case was trivial:  start with a stack of 2 of something in 'a'
and use '#adjust 1a b' to split into two stacks, then '#adjust a a'
to collect them back again.

While fixing this, I noticed that '#adjust a b' and '#adjust b a'
(from same starting situation) just swapped a and b instead of the
intended behavior of merging them back together.
2024-03-21 22:32:31 -07:00
nhmall
295d6e257c used, unused variables
some variables marked as unused, are now actually used
some unused variables are eliminated or commented out
2024-03-16 12:53:58 -04:00
nhkeni
9c0ed8ae63 NOSTATICFN for src/* 2024-03-14 17:41:51 -04:00
RainRat
a3658f85ac fix typos 2024-02-28 20:15:56 -08:00
Erik Lunna
eb22a81088 Refactor, unify, and nerf item destruction
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.
2024-02-20 22:03:54 +02:00
nhmall
688ac6ffbe remove register from variable declarations 2024-02-19 16:30:07 -05:00