Luck from sacrificing is now limited by the value of the sacrifice.
This fixes two exploits, both of which rely on getting luck up to
maximum as soon as you have an altar, a luckstone, and a few
rations, via altar-camping until you accumulate enough luck. One of
them is to use the resulting luck to throw off the balance of combat
via using it to make hit chance calculations irrelevant. The other
is to use it to get crowned early in the game; in particular,
getting crowned pre-Sokoban is often viable and, especially for
chaotic characters, solves most of the game's difficulty at that
point (because the intrinisics and weapon are enough to carry a
character to the Castle given even mediocre luck with finding armor).
After this commit, becoming crowned very early in the game is more
difficult (likely requiring unicorns and identified gems), and the
hit chance gain from luck becomes a more gradual gain over the
course of the game rather than all happening immediately upon
finding the altar and luckstone.
In addition to making the game more balanced, this also discourages
grinding by reducing the incentive for altar-camping, so it will
hopefully make it more fun as well.
In 3.6, artifact gifts are often either a) entirely useless or
b) gamebreaking, neither of which is really ideal from a balance
perspective.
This commit aims to make artifact gifts more useful in the early
game by greatly increasing the chance for situational artifacts to
generate positively enchanted. However, the most powerful
artifacts will now only be gifted if you offer a high-value corpse,
meaning that they are only likely to be accessible later in the
game. The selection of which artifact to gift has become more
complicated in order to a) increase the chance that it fits the
character and b) reduce cheese strategies (e.g. it is no longer
possible for elves to force the gifting of Stormbringer as the
first sacrifice gift).
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.
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/1315https://www.reddit.com/r/nethack/comments/1gkc9ub/even_if_you_drop_an_item_before_drinking_from_the/
Changes to setuhpmax() a couple of days ago to deal with sanity_check
for "current hero health as monster better than maximum" ended up
triggering sanity_check about "current hero health better than maximum"
when gaining experience level(s) while polymorphed.
Prayer result of fix very low HP. Tricky because when poly'd,
prayer operates on both u.mhmax and regular u.uhpmax but setuhpmax()
only operates on one of the two depending on Upolyd.
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
When you attempt #offer at a non-altar location, the feedback
"you are not standing on an altar" made it sound as if you wouldn't
be able to #offer if you were levitating. Since that is possible,
change the feedback.
In 3.6.2 parts of the wakeup code were merged together, and this
caused pets consider any noise made by the hero - such as hitting
iron bars or digging - as whistling for them to come to the hero.
Change it to only consider actual whistling and ringing a bell.
This will increase the potential challenge involved in donning a helm of
opposite alignment on Astral, now that it confuses the hero -- it means
that you must spend at least a turn after putting on the helm clearing
the confusion. Since putting on the helm of opposite alignment may also
summon erinyes, this hopefully turns it from a situation where it's a
no-brainer to immediately ditch them by #offering the Amulet of Yendor
into one where the player must actually stop and think a little bit.
After the drawbridge was destroyed, playing an instrument on the castle
level while knowing the tune continued to offer a chance to play it.
Then nothing interesting happened even if you were close enough to the
former bridge for it to have been useful prior to the destruction.
I think the hero could also be given the tune as a divine prayer boon
after bridge destruction but I didn't verify that. The player might
not know that the tune is no good anymore, but the hero's patron deity
should.
This is for completely destroying an altar with extra-powerful magical
digging -- the normal altar_wrath() punishment didn't seem sufficient
for such an outrage to me, so skip straight to slinging the lightning
bolts. Destroying an altar is unlikely to happen by accident (though
it's possible with poorly timed usage of a drum of earthquake).
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 status field 'hitpoints' has rules for both 'criticalhp' and 'up'
or 'down' or 'changed', make critical-hp take precedence. Otherwise
critical-hp might never be seen because of the value changing every
move (if hero has regeneration attribute). Normally up/down/changed
take precedence over other types of highlighting.
Something is messed up with up/down/changed HP though. I'm seeing
the 'up' highlight when it goes either up or down and not seeing the
'down' highlight at all. 'up' and 'down' for gold work as expected.
An issue from nearly three years ago, reported by Anerag: asking
player whether to really pray isn't paranoid enough because it
accepts 'y' rather than requring "yes".
This changes it to require "yes" followed by <return> or <enter> if
paranoid_confirm:Confirm is also set. (A side-effect of that is
explicit "no<return|enter>" is required instead of just 'n' to
decline--for all the paranoid confirmations, not just for prayer.)
This extension of paranoid:Confirm applies to paranoid:AutoAll too.
A comment asks why paranoid_confirm:pray is different from the other
paranoid questions in the first place. The answer is that when it
isn't set, no confirmation prompt is issued at all. The others all
have y/n confirmation prompts when the corresponding paranoid option
isn't set.
Once upon a time there was a boolean option called 'prayconfirm' that
issued "really pray?" prompt when True. It was added after players
whinged about typing Alt+p when they meant to type Alt+o. When the
more advanced 'paranoid_confirmation' was introduced, paranoid:pray
superseded prayconfirm, but it still only issues a confirmation
prompt where there normally wouldn't be one rather than change an
existing one to require "yes<return|enter>" instead of 'y'.
Closes#303
Replace a couple of hardcoded "back on solid ground" messages with
something more versatile.
Also, make life-saving handling for failed rescue from drowning
similar to that of failed rescue immolation by lava. If there are
any cases where more than two tries is needed, they elude me. The
new code doesn't confer temporary water walking if emergency teleport
fails; perhaps it should.