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.
Makes Sokoban far less tedious when you don't have to worry about
monsters randomly popping up in the trap hallway while you're pushing
the boulder.
Adds a new exclusion zone for monster generation, and the goodpos
routine avoids the zones when GP_AVOID_MONPOS is used.
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
While testing the wall crumbling message TODO yesterday, I saw a
case where the top line of the moat around the invocation area was
truncated even through there were 3 lines of map (on a cavernous
level rather than maze one) above where it cut off. This improves
things although it may not be 100% correct.
Picking the invocation position is unchanged, so there should be no
risk of it now being positioned too close to the map edge. It's
possible that it has been farther away from the edge than necessary
though; if so, it will still be.
Adds a new lua command
des.exclusion({ type = "teleport", region = { x1,y1, x2,y2 } });
which allows defining "exclusion zones" in the level, areas where
random teleports (or falling into the level) will never place the hero.
Does not prevent targeted teleportation into the area.
Breaks saves and bones.
ff727e9 introduced an issue where unseen water and cloud on the
respective planes were shown with open and closed drawbridge glyphs
instead of the appropriate glyphs. This is because they fall into cmap
section B, but the translation from symbols/cmap index to glyphs was
being done as though they were in cmap section A (ff727e9 manually used
that particular cmap section macro instead of the general cmap_to_glyph
to work around some compiler warning).
Issue reported by vultur-cadens: arriving on the Mine Town level
via falling or level teleport won't register the "entered Minetown"
achievement if hero doesn't arrive inside a room.
Reorder some code in check_special_room() so that town entry will be
tested before the early return if no room entry has occurred. This
adds 'level.flags.has_town' to make the town test be cheaper when
the hero hasn't attained the achievement yet and is wandering around
the mines.
Fixes#1070
This replaces most of commit 0ca2af4d8b
from a couple of days ago with something more robust. That change
actually introduced redundant code that caused fountain and/or sink
count to be off instead of preventing it.
Revise set_levltyp() to update level.flags.nfountains and
level.flags.nsinks if setting the type to or from fountain or sink.
A bunch of places that were setting levl[x][y].typ directly needed
to be revised to use set_levltyp() instead. set_levltyp() itself
hadn't been updated to handle LAVAWALL (to force such to be lit).
One compiler was issuing an error diagnostic for cmap_to_glyph() macro (see below).
This just works around that by using cmap_a_to_glyph() which does not suffer the same fate.
mkmaze.c
static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "In_mines(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c
static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "In_hell(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c
static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "gd" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c
static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "on_level(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c
static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "(&u.uz)->dnum" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c
static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "gd.dungeon_topology.d_sokoban_dnum" is not constant, but occurs in a context that requires a const
ant expression.
at line number 1448 in file mkmaze.c
static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "In_mines(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "In_hell(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "gd" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "on_level(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "(&u.uz)->dnum" is no
t constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "gd.dungeon_topology.
d_sokoban_dnum" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
While fuzzing, the game got stuck here trying to generate
a random stoneable monster for a Medusa level statue.
I didn't investigate deeper why it failed to find a matching
monster type.
The Gehennom changes broke the vibrating square, allowing hero to go
down into the Sanctum via stairs without performing the invocation.
Fix this by making the hellfill lua check for invocation level, and
placing down the vibrating square trap, instead of stairs.
Redo the restore_waterlevel() code a bit to eliminate a static
analyzer complaint. The previous code would not have done the right
thing if 'gb.bbubbles' was already non-Null, but that should not be
possible. I didn't backtrack to make sure that it was always Null
at the time restore_waterlevel() gets called.
Also, some of the code was misformatted.
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
^ ^
Instead of using index() macro defined to strchr, use C99 strchr.
Instead of using rindex() macro defined to strrchr, use C99 strrchr.
If you want to try building on a platform that doesn't offer those
two functions, these are available:
define NOT_C99 /* to make some non-C99 code available */
define NEED_INDEX /* to define a macro for index() */
define NEED_RINDX /* to define a macro for rindex() */
Avoid potential impossible "obfree: deleting worn object" warnings
when entering the endgame.
The code to get rid of items migrating to non-endgame levels passes
those items to obfree(). It needs to clear obj->owornmask first
because that's used for migration flags (undelivered orctown loot
has a non-zero value).
By temporarily changing the type definition for each of xint16 and
coordxy to int32_t, the compiler was able to find several places where
the type definitions were wrong.
One of the drivers of this change was that screen coordinates require a
type that can hold values greater than 127. Parameters to the window
port routines require a large type in order to be able to have values
a fair bit larger than COLNO and ROWNO passed to them, particularly for
their use to the right of the map window.
This splits the uses of xchar into 3 different situations, and adjusts
their type and size:
xchar
|
-----------------------
| | |
coordxy xint16 xint8
coordxy: Actual x or y coordinates for various things (moved to 16-bits).
xint16: Same data size as coordxy, but for non-coordinate use (16-bits).
xint8: There are only a few use cases initially, where it was very
plain to see that the variable could remain as 8-bits, rather
than be bumped to 16-bits. There are probably more such cases
that could be changed after additional review.
Note: This first changed all xchar variables to coordxy. Some were
reviewed and got changed to xint16 or xint8 when it became apparent that
their usage was not for coordinates.
This increments EDITLEVEL in patchlevel.h
A test build with gcc-12 cause two new warnings to appear.
mkmaze.c: In function ‘makemaz’:
mkmaze.c:983:44: warning: ‘sprintf’ may write a terminating nul past the end of the destination [-Wformat-overflow=]
983 | Sprintf(protofile, "%s%d-%d", g.dungeons[u.uz.dnum].proto,
| ^
In file included from ../include/config.h:665,
from ../include/hack.h:10,
from mkmaze.c:6:
../include/global.h:262:24: note: ‘sprintf’ output between 4 and 31 bytes into a destination of size 20
262 | #define Sprintf (void) sprintf
mkmaze.c:983:17: note: in expansion of macro ‘Sprintf’
983 | Sprintf(protofile, "%s%d-%d", g.dungeons[u.uz.dnum].proto,
| ^~~~~~~-+
As usual, that one can easily be rectified by replacing it with an Snprintf() call.
There were several Sprintf calls in the vicinity, targeting the same destination
buffer, so I figured that I might as well replace the several.
../win/Qt/qt_menu.cpp: In member function
‘virtual void nethack_qt_::NetHackQtTextWindow::UseRIP(int, time_t)’:
../win/Qt/qt_menu.cpp:1082:63: warning:
‘%ld’ directive output may be truncated writing between 1 and 20 bytes into a region
of size 17 [-Wformat-truncation=]
1082 | (void) snprintf(rip_line[GOLD_LINE], STONE_LINE_LEN + 1, "%ld Au", cash);
| ^~~
../win/Qt/qt_menu.cpp:1082:62: note: directive argument in the range [-9223372036854775808, 999999999]
1082 | (void) snprintf(rip_line[GOLD_LINE], STONE_LINE_LEN + 1, "%ld Au", cash);
| ^~~~~~~~
../win/Qt/qt_menu.cpp:1082:20: note: ‘snprintf’ output between 5 and 24 bytes into a destination of size 17
1082 | (void) snprintf(rip_line[GOLD_LINE], STONE_LINE_LEN + 1, "%ld Au", cash);
| ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
That one was a little different. It wasn't complaining about the destination buffer
size in the way -Wformat-overflow was on the previous warning from gcc, and it
was already using snprintf().
It looks like what the C++ compiler was warning about there, was that snprintf()
informs you after-the-call that the destination buffer was too small for the
result string to be fully written. It informs the developer of that by returning
the number of characters that would have been written if the buffer had been big
enough. Presumably, the C++ compiler picked up on the fact that the return value
was being cast to void, thus throwing away that truncation information from the
return value.
Worked around it by putting the return value into a variable, and flagging the
variable with nhUse(variable) so as not to exchange the -Wformat-truncation
warning with a variable-set-but-not-used warning.
The air bubbles on the Plane of Water and the clouds on the Plane of
Air were being saved and restored as part of the current level's state
(which is the 'u' struct and invent and such) rather than with the
current level itself. That was ok for normal play, but for wizard
mode's ^V allowing you to return to a previously visited endgame level
after moving to a different one it meant a new set of bubbles for
Water and new set of clouds for Air. Even that was ok since it only
applied to wizard mode, but using #wizmakemap to recreate Water or Air
while you were on it added a new set of bubbles or clouds to the
existing ones. If repeated, eventually there wouldn't be much water
or air left.
Instead of just adding a hack to #wizmakemap, change save/restore to
keep the bubbles/clouds with the level rather than with the state.
That wasn't trivial and now I know why the old odd arrangement was
chosen. Saving hides u.uz by zeroing it out for levels that the hero
isn't on and it is zero during restore so simple checks for whether a
given level is water or air won't work.
This also adds another non-file/non-debugpline() use of DEBUGFILES:
DEBUGFILES=seethru nethack -D
will make water and clouds be transparent instead of opaque. It also
makes fumaroles and other light-blocking gas clouds be transparent
which wasn't really intended, but avoiding it would be extra work that
doesn't accomplish much.
Increments EDITLEVEL for the third time this week....
Instead of hardcoding the lava terrain change in core, if the stairs
are created in a fixed location, force the terrain to room floor first.
Move the surrounding lava changing to room floor to the Val-goal lua
file.
Some maze code treats row y_maze_max and column x_maze_max as being
in play, other parts treat them as out. mazexy() was doing both; the
first loop to choose a random spot allowed them, the second loop to
try every possible spot disallowed them. Make those be consistent.
I think the extreme row and column are both expected to be solid wall
so failing to consider them might not be causing any problems.
While in there, change mazexy() to not set cc->{x,y} until it has
found a viable spot instead of potentionally making that assignment
dozens or hundreds of times. The only difference there is that 'cc'
won't have been assigned any value if panic() gets called.
The wall of water goaded me into updating waterbody_name(). It's
mostly the same, aside from being moved from mkmaze.c to pager.c and
adding "{wall of|limitless} water" instead of plain "water" for WATER
terrain. I'm not very happy with "limitless" for the Plane of Water
because limits imposed by air bubbles are all over the place. "Wall
of water" might work ok for that level.
Water on Medusa's level is now described as "shallow sea" rather than
lame "water". The two unusual pools on the Samurai home level are
described as "pond" rather than previous "water" which replaced 3.6's
ridiculous "moat". When lava is hallucinated, it is described as
"molten <substance>" (yielding silly things like "molten yoghurt"),
rather than just "<substance>" to distinguish it from hallucinated
water. 'autodescribe' doesn't use waterbody_name() though.
If you want to declare a pointer which the address pointed to is constant,
you should declare it as like `static const char *const var = "...";`.
This commit supplies missing `const` and prevents some programming
error in the future.
Teleporting a monster only updated the map. Give a message
so blind players can get the same information.
Making a monster invisible gives the same message, if you
cannot detect invisible.
Several other places where monsters teleported themselves
now also give the same message.
like the leg hackery, if the baalz level gets flipped during creation
the fixup that modifies the locations in front of the eyes to allow
digging needs to deal with that too.