Commit Graph

212 Commits

Author SHA1 Message Date
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
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
d8d4f18a00 analyzer lint for do_wear.c, dothrow.c 2025-01-19 11:23:05 -08:00
PatR
ce947600e5 discoverying water walking boots
If water walking boots haven't been discovered yet and underwater
hero rises to the surface when putting a pair on, discover them.

(Sinking while removing such on water already discovers them.)
2025-01-02 23:12:15 -08:00
Pasi Kallinen
78289a7f83 Archeologists' fedora is lucky 2024-12-08 16:17:35 +02:00
Pasi Kallinen
e23e6ef235 Blessed scroll of destroy armor asks which armor to destroy
via xNetHack with some slight modifications.
2024-12-06 11:09:27 +02: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
PatR
cbc93a0555 github issue #1312 - prot from shape changers
Issue reported by youkan700:  shape change anomalies.  Shapechangers
could change shape despite active protection-from-shape-changers if
hero wore two rings of protection from shape changers and took one
off.  Shapechangers who migrated to a not-yet-visited level that
eventually got visited with protection from shape changers in effect
would be stuck in their current shape, even if the PfSC attribute
got toggled off and back on.

The issue included suggested fixes and those are what I've used.  I
noticed a third case that only applies to wizard mode:  if player
used #wizintrinsic to set a timed value for PfSC, monsters wouldn't
resume changing shape after it timed out, unless/until it got toggled
on and back off via a PfSC ring or hero left the level and returned.

Fixes #1312
2024-11-28 11:33:26 -08:00
PatR
ea6cdd3f39 Amulet_on() & Amulet_off()
Fix a FIXME in Amulet_off() for removing an amulet of magical
breathing when within a poison gas cloud.

Redo message sequencing for both Amulet_on() and Amulet_off().

Use up an amulet of change if put on while the Unchanging attribute
is active (via #wizintrinsic) instead of wearing it with no effect.

Don't discover amulet of strangulation if put on while already
Strangled (via #wizintrinsic).
2024-11-26 21:20:30 -08: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
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
2471306112 prayer fix for being in poison gas cloud region
Like stuck-in-wall, there might not be any place to move the hero
in order to escape a poison gas cloud.  That could result in "fix
all troubles" becoming stuck in a loop.  3.6.something fixed the
stuck-in-wall case by temporarily conferring Passes_walls.  Fix the
in-poison-region case by temporarily conferring Magical_breathing.

Not adequately tested...

I don't think that this fixes "#K4252: NH 3.7 Prayer Bug" where the
game hung during a couple out of scores of prayers.  I didn't see
any other fix-trouble cases that seemed likely to remain unfixed
and end up with repair attempts retrying.
2024-09-25 01:01:17 -07:00
nhmall
0eb7f109e0 follow-up, program_state 2024-07-13 16:31:35 -04:00
PatR
72d2b0414c fix github issue #1257 - nymph theft while blind
Issue reported by Meklon2007:  some theft feedback during nymph
attacks refers to the attacker as "she" and others as "it" if hero
is blind.

The "she" references are intentional.  However, mixing them with
"it" references when a series of messages occurs is jarring.  This
changes "it" to "someone", which is still different from "she" but
hopefully enough less so to be tolerable.

That resulted in monkeys also being referred to as "someone" because
they're classified as humanoid.  Change x_monnam()'s AUGMENT_IT
handling, which chooses between "someone" and "something" when the
monster is not seen, to override humanoid for animals (affects 'Y'
class) and for mindless (affects zombies, mummies, and golems).
So an unseen monkey will be "it" again.

The final message for current item was relying on a cached monster
name value.  If an unseen nymph or monkey stole a worn blindfold
so that hero's vision was restored:  "It <stole> item" before the
changes and "Someone|Something <stole> item" after.  So update the
cached name if sight gets regained, to give "<Mon> stole <item>."
(If the item is worn armor and the thief is a nymph, it was and
still is "She stole <item>".)

The message for having any worn item be stolen, which got split
into two parts within the past year, was giving "<Mon> takes off
<alt weapon>".  When not dual-wielded, the alternate weapon isn't
really worn.  Rather than suppress it outright, change the message
for uwep/uswapwep/uquiver to say "disarms" instead of "takes off".
For accessories, change "takes off" to "removes".  Those are more
or less interchangeable these days but "removes" matches R instead
of T.

While testing, my pet evidently killed a nymph (I was blinded and
couldn't see it happen) while she stole my gloves and the next
message I got was "You finish taking off your suit."  The gloves
weren't worn anymore so equipname() defaulted to suit.  Get rid of
equipname() altogether and switch to armor_simple_name() which
doesn't rely on the worn-armor pointers.

Fixes #1257
2024-07-13 12:30:59 -07: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
nhmall
588b3ae92f replace some leading tabs that had crept in 2024-06-10 10:57:59 -04:00
Pasi Kallinen
e0cb6e2206 Query menu for putting ring on left or right hand 2024-03-23 13:22:29 +02: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
nhmall
688ac6ffbe remove register from variable declarations 2024-02-19 16:30:07 -05:00
Michael Meyer
f42250eb9d Eliminate magic numbers from uchangealign() 2024-01-10 22:59:18 -08:00
nhmall
4e19221e55 variable 'display' causes shadow variable warnings in X11 build
display.botl      -> disp.botl
display.botlx     -> disp.botlx
display.time_botl -> disp.time_botl
2024-01-05 05:58:51 -05:00
nhmall
49a5d043c0 consistent use of TRUE vs 1 with botl and botlx 2024-01-04 23:48:38 -05:00
nhmall
22e52ee905 bundle the display-related hints, that tell bot() and others
that an update is required, into a struct. Remove it from
context since there is no reason to save those.
2024-01-04 23:16:27 -05:00
nhmall
9d2d437d0e follow-up comment bit 2023-12-28 19:25:49 -05:00
nhmall
719919b16a revisit destroy_armor()
add a static function maybe_destroy_armor() to replace
the DESTROY_ARM() macro.

The DESTROY_ARM() macro expanded to legal code, but did include
side-effects of making assignments to local variable otmp (not an
argument to the macro), referenced local variable atmp (not an
argument to the macro), and sometimes setting the in_use field
on the impacted armor obj.

The assignment statements within the if (...) caused some compilers
and code analyzers to complain and suggest that perhaps
'=' should have been '==', which was incorrect.

The maybe_destroy_armor() function provides the
caller with information about whether the armor resisted,
so that appropriate action can be taken within the caller.
2023-12-28 19:17:43 -05:00
PatR
a7db78f7d6 fix #K4060 - "you walk quietly" while riding
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.
2023-12-10 22:09:26 -08:00
PatR
2bd967fe8a a few fewer update_inventory() calls
A couple of things I noticed while running umpteen tests for tty
perm_invent.  Remove the update_inventory() from unmul(), and limit
the one that deals with seeing inventory when recovering from blindness.

Just a drop in the bucket overall, and the screen updates nearly
instantly for update_inventory() except when debugging perm_invent so
players aren't likely to notice this.
2023-12-07 22:47:57 -08:00
nhmall
e4e8eea4e8 track the handedness of the hero
Don't make either LEFT_HANDED or RIGHT_HANDED be an advantage
or a disadvantage.

use suggested macros
2023-12-02 11:23:43 -05:00
nhmall
a7242760f7 consistent cast syntax 2023-11-13 19:28:19 -05:00
nhmall
6cbefc7c2d Revert "granular verbose message suppression mechanics"
This reverts commit be76727265.
2023-10-29 20:39:07 -04:00
PatR
7d052bafb0 crystal armor bit
Accidentally left out of the "breaking crystal armor" commit
e9c58c2fe4.
2023-06-14 16:14:18 -07:00
PatR
450f060132 github issue #1060 - crystal helmet
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
2023-06-14 06:13:11 -07:00
PatR
f9a35132d6 crystal helmet
Discussed a long time ago, change helm of brilliance from iron to
crystal so that it doesn't need to be a special case for metallic
armor's affect on spell casting.  It now has a fixed description of
"crystal helmet" but is not pre-discovered.

Add new helm of caution to retain the "etched helmet" description
among the shuffled helms.  Wearing it confers the Warning attribute.
That's fairly lame but not necessarily useless.  It's iron and gets
half the former probability for a random piece of armor being helm
of brilliance so is not likely to be popular.

Helm of caution keeps the old helm of brilliance tile and new helm
of brilliance is basically the same image with different color and
pointed on the top.

Not changed:  the etched helmet was marked as green and is drawn
that way for text but the tile doesn't actually use green for it.
Crystal helmet started as hi_glass (rendered as 'bright cyan') but
has been changed to clr_white to match crystal plate mail.

Old save and bones files are invalidated.
2023-04-29 02:18:29 -07:00
copperwater
dad1c3f8b7 Fix: breathless monsters always generate in water in special levels
... unless explicitly specified to generate at a specific point or
within a specific area. But if they are permitted to generate anywhere
on the level, and it contains water, they always end up in the water. I
noticed this when trying to explicitly specify ghouls to generate
anywhere on a level with a minimal amount of water.

This was due to the definition of "amphibious" being conflated with
"breathless", such that all breathless monsters counted as amphibious.
There are plenty of breathless monsters in the game that decidedly don't
normally inhabit water, such as undead, but they would pass the
amphibious() check in pm_to_humidity and thus the game decides that they
must generate in wet terrain if there is any available.

This fix takes the approach of changing amphibious() so that it no
longer checks the M1_BREATHLESS flag and only considers M1_AMPHIBIOUS,
then updating the places where amphibious() and Amphibious are used
accordingly. I also added a new macro cant_drown() which wraps up
swimming, amphibiousness, and breathlessness because these three things
are frequently checked together in the context of whether something
should drown.

Places where amphibious() or Amphibious did NOT have an extra
breathless() or Breathless check added on, and thus where behavior has
been changed:
- The pm_to_humidity function (to fix the bug).
- Player vs water in goodpos; it didn't seem like being polymorphed into
  a breathless non-amphibious monster should make it fair game to
  randomly teleport into water even though it's technically safe.
- Awarding extra experience when killing an eel. (So the hero will get
  the extra experience if they are polymorphed into a breathless
  non-amphibious monster and don't have magical breathing. Very much an
  edge case.)
2023-04-16 09:57:41 +03:00
copperwater
fe39a2b08d Don't drop aklys on the floor due to slippery fingers
Aklyses uniquely have a tether attached to one's wrist, which would stay
secure even when one's fingers are slippery. Therefore, it doesn't make
for the weapon to be dropped completely, tether and all, when Glib.

However, the game doesn't have a way to model a weapon that's on the
floor at your feet but still attached to your arm. (Technically the
uball and uchain code does do this sort of thing already, but it can't
be used here - what if you have an aklys slipping out of your grasp and
are punished at the same time?) So the next best thing is to give
aklyses immunity to slipping from one's hand.
2023-04-16 09:48:42 +03:00
PatR
a77aef09d7 wornarm_destroyed() vs destroy_arm()
Refine the fix #K3888 by moving multiple instances of the check for
cancel_don() from destroy_arm() to one instance in wornarm_destroyed().
Should be no change in behavior.
2023-03-20 22:59:16 -07:00
PatR
3ce02acbc5 fix #K3888 - object lost panic cased by lava
Report was for spell-casting monster using the destroy armor spell on
hero's levitation boots while hero was floating over lava.  The boots
became unworn but still in inventory, hero dropped into lava, the
boots happened to be an inventory item which got burned up, then the
call stack unwound back to the destroy armor routine which tried to
finish by deleting them but they were already gone by then.  Could
also happen for black dragon breath, hero reading scroll of destroy
armor, or overenchanting the boots with scroll of enchant armor, so
not so unlikely that nobody would be expected to notice.

Initially I couldn't reproduce the object lost panic.  It only happens
if the memory used by the boots is cleared or clobbered during first
time it's freed, otherwise second free doesn't notice any problem.

The 'wornarm_destroyed()' portion of this commit is sufficient to fix
this.  The other bits are things I tried before figuring out how to
reproduce it, plus zeroing out any object passed to dealloc_obj().
2023-03-20 17:05:02 -07:00
nhmall
ecf74d5308 some pline()-like function usage 2023-03-08 19:12:52 -05:00
nhmall
5b27e25d6d avoid a warning on x ? :
Surround the code to the left of the '?' in the DESTROY_ARM(o) macro
with () to quiet a warning on each use of the macro.
2023-01-12 16:31:35 -05:00
nhmall
02a48aa8cf split g into multiple structures
The consolidation of global variables from scattered source
files into decl.c and declared in decl.h was begun in 3.7.0.
Their placement in common files was done for centralized
initialization and potential re-initialization during a
"play again" scenario.

It wasn't really necessary for all of them to be housed in a
single huge structure to meet the "play again" requirement,
and the single huge structure has been a little unwieldy when
it comes to maintenance.

Following this commit, instead of one single extremely large structure
named 'g' to house all of the relocated global variables, they
are distributed into several ga through gz.

To make things easy for the developer, each variable is placed
into the struct corresponding to the starting letter of the variable.
That way, no lookup is required in order to know which struct houses
a particular variable, it is a simple match to the starting letter
for all the centralized global variables.

A global variable named 'amulets', would be found in ga.
    ga.amulets
     ^ ^
A global varable named 'move', would be found in gm.
    gm.moves
     ^ ^
A global variable named 'val_for_n_or_more' would be found in gv.
    gv.val_for_n_or_more
     ^ ^
A global variable named 'youmonst' would be found in gy.
    gy.youmonst
     ^ ^
2022-11-29 21:53:21 -05:00
PatR
84fabf764a allow big humanoids to wear mummy wrappings
A giant mummy starts out with a mummy wrapping but couldn't wear it.
Allow humanoids who are bigger than human size (including poly'd hero
when applicable) to wear such cloaks.  They won't do so if they are
invisible and the cloak would let hero start seeing them.
2022-10-22 17:14:22 -07:00
Pasi Kallinen
fd9745f9c6 Command repeating by using cmd queues
This replaces the old pushq/saveq arrays (which were used to save
the keys pressed by the user for repeating a previous command)
with a new command queue.  This means there's no hard-coded limit
to the saved keys, and it can repeat extended commands which are
not bound to any key.
2022-08-09 11:54:45 +03:00
nhmall
be76727265 granular verbose message suppression mechanics
Switch to using a macro invocation Verbos(n, s) in place of the
flags.verbose checks.

Provide the mechanics for individual suppression of any of the
existing messages that were considered verbose.

Mechanics only - this code update does not provide any means of
setting the suppression bits.

iflags.verbose = 0
is still a master suppression of all the verbose messages.

iflags.verbose = 1
turns on the verbose messages only for those whose suppression
bit is 0 (not set).
2022-06-09 13:53:20 -04:00
SHIRAKATA Kentaro
037683581f split adjusting attributes into separate function 2022-05-09 20:38:15 +09:00
nhmall
9334086506 compiler warning
src/do_wear.c(2884): warning C4389: '!=': signed/unsigned mismatch
2022-04-25 09:45:10 -04:00
PatR
9abec79df8 "empty handed" vs "bare handed"
When not wielding anything, ^X reports "you are empty handed" if
wearing gloves or "you are bare handed" if no gloves.  The ')',
'w-', and 'A)' commands were using "empty handed" unconditionally.
Make them be consisitent with ^X.

After this, body part HANDED is no longer used anywhere except in
body_part().
2022-04-25 01:31:34 -07:00
PatR
c2ac36f0c9 inventory item-action: add '-' for uswapwep
There's no 'w-' or 'Q-' for alternate weapon, but context-sensitive
inventory is starting from the object rather than the command so can
finesse that.  'A' does allow alternate weapon to be directly unset
(aka reset to bare/gloved hands) but is not friendly to being passed
queued input.

This adds an extra internal command which only handles unset uswapwep,
even though that is something which is awfully specialized to get it's
own command.  Users don't see this command so that shouldn't matter.
2022-04-23 11:58:52 -07:00