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.
otmp can be 0 in mk_artifact. In fact, it is explicitly
being set to 0 three lines above the recently added call
to permapoisoned().
The static analyzer was griping also.
This is the third of a series of savefile-related changes.
This adds early-days experimental support for a completely optional
'sfctool' utility (savefile conversion tool), to be able to export
a savefile's contents into a more portable format. There are likely
to be bugs at this stage. In this initial first-attempt, the export
format is a very simple ascii output.
NetHack can be built entirely, without also building this tool.
NetHack has no dependencies on the tool.
Attempts were made to minimize duplication of existing NetHack code.
To achieve that, unfortunately, #ifdef SFCTOOL and #ifndef SFCTOOL
had to be sprinkled around through some of the existing NetHack
source code, so that it could be re-used for building the utility.
The process for building the sfctool typically recompiles the source
files with #define SFCTOOL and a distinct object file with SF- is
produced.
sfctool notes:
Universal ctags is used and required to produce the sfctool utility.
Some targets were added to the Unix and Windows Makefiles to
facilitate the build process.
make sfctool
That should build a copy in util.
Note: At present, the Unix Makefiles do not copy sfctool over to the
NetHack playground during 'make install' or 'make update'.
Until that gets resolved by someone, The tool will
have to be manually copied there by the builder/admin if
desired.
cp util/sfctool ~/nh/install/games/lib/nethackdir/sfctool
Also, a separate Visual Studio sfctool.sln solution was written and
placed in sys/windows/vs. That has has only very limited testing.
Usage:
i) To convert an existing savefile to an exportascii format
that co-resides with the savefile:
sfctool -c savefile
That *must* be executed on the same platform / architecture /
data model that produced the save file in the first place.
ii) To unconvert an existing exportascii format export file to a
historical format savefile that can then be used by NetHack:
sfctool -u savefile
That must be executed on the same target platform / architecture /
data model that was used to build the NetHack that will
utilize the save file that results.
A Windows example:
sfctool -c Fred.NetHack-saved-game
That should result in creation of Fred.NetHack-saved-game.exportascii
from existing savefile:
%USERPROFILE%\AppData\Local\NetHack\3.7\Fred.NetHack-saved-game
A Unix example:
sfctool -c 1000wizard
That should result in creation of 1000wizard.exportascii.gz
from existing savefile in the playground save directory:
1000wizard.gz
Current Mechanics:
1. Makefile recipe, or script uses universal ctags to produce
util/sf.tags.
2. util/sftags is built and executed to read util/sf.tags and
generate: include/sfproto.h and src/sfdata.c.
3. util/sfctool is built from the following:
generated file compiled with -DSFCTOOL:
src/sfdata.c -> sfdata.o
existing files compiled with -DSFCTOOL:
util/sfctool.c -> sfctool.o
util/sfexpasc.c -> sfexpasc.o
src/alloc.c -> sf-alloc.o
src/monst.c -> sf-monst.o
src/objects.c -> sf-objects.o
src/sfbase.c -> sfbase.o
src/sfstruct.c -> sfstruct.o
src/nhlua.c -> sf-nhlua.o
util/panic.c -> panic.o
src/date.c -> sf-date.o
src/decl.c -> sf-decl.o
src/artifact.c -> sf-artifact.o
src/dungeon.c -> sf-dungeon.o
src/end.c -> sf-end.o
src/engrave.c -> sf-engrave.o
src/cfgfiles.c -> sf-cfgfiles.o
src/files.c -> sf-files.o
src/light.c -> sf-light.o
src/mdlib.c -> sf-mdlib.o
src/mkmaze.c -> sf-mkmaze.o
src/mkroom.c -> sf-mkroom.o
src/o_init.c -> sf-o_init.o
src/region.c -> sf-region.o
src/restore.c -> sf-restore.o
src/rumors.c -> sf-rumors.o
src/sys.c -> sf-sys.o
src/timeout.c -> sf-timeout.o
src/track.c -> sf-track.o
src/version.c -> sf-version.o
src/worm.c -> sf-worm.o
src/strutil.c -> strutil.o
This is the second of a series of changes related to save/restore.
No EDITLEVEL bump has been included, because although the code
is changed extensively by this, the content of the savefiles have
not been changed.
Push the use of the structlevel bwrite() and mread() function use
out of the core and into sfstruct.c. This is groundwork for upcoming
changes.
In the core, replace the bwrite() and mread() calls with the
use of type-specific savefile output (Sfo) and savefile
input (Sfi) macros. The macros are defined in a new header file
savefile.h, which also contains the prototypes for the sfo_* and
sfi_* functions that the macros ultimately expand to. The functions
themselves are in src/sfbase.c.
On C99, each Sfo or Sfi macro expansion refers directly to the
corresponding type-specific sfo_* or sfi_* function.
If C23 or later is is use, the majority (all but 3 types) of the
macros refer to a single _Generic output routine sfo(nhfp, dt, tag),
and a single _Generic input routine sfi(nhfp, dt, tag), which handles
the dispatch of the type-specific underlying functions. This was
somewhat experimental, but turned out to be practical because the
compiler would gripe if the type for a variable was not included in
the _Generic when passed as an argument, so it could be fixed.
This alters the savefile verication process by having a common set
return values for the related functions such as uptodate(),
check_version(), etc. The new return values return more information
about savefile incompatibilities, beyond failure/sucess. The
additional information will be useful for an upcoming addition.
The expanded return values are:
SF_UPTODATE (0) everything matched and looks good
SF_OUTDATED (1) savefile is outdated
SF_CRITICAL_BYTE_COUNT_MISMATCH (2) critical size count mismatch
SF_DM_IL32LLP64_ON_ILP32LL64 (3) Windows x64 savefile on x86
SF_DM_I32LP64_ON_ILP32LL64 (4) Unix 64 savefile on x86
SF_DM_ILP32LL64_ON_I32LP64 (5) x86 savefile on Unix 64
SF_DM_ILP32LL64_ON_IL32LLP64 (6) x86 savefile on Windows x64
SF_DM_I32LP64_ON_IL32LLP64 (7) Unix 64 savefile on Windows x64
SF_DM_IL32LLP64_ON_I32LP64 (8) Windows x64 savefile on Unix 64
SF_DM_MISMATCH (9) some other mismatch
The callers in the core have been adjusted to deal with the expanded
return values.
Other miscellaneous inclusions:
- go.oracle_loc -> svo.oracle_loc.
- add a bit (1UL << 30) to called SFCTOOL_BIT as groundwork
for changes to follow.
This is the first of several savefile-related changes to
follow later. This one is groundwork for those later changes.
Remove internal compression schemes (RLECOMP and ZEROCOMP)
and discard the savefile_info struct that was primarily used to
convey which internal compression schemes had been in use.
Relocate some struct definitions into appropriate header files
for use by code to come in later changes.
Remove the two struct size-related fields from version_info and
from the nmakedefs_s. Instead, include a series of bytes near the
beginning of the savefile, representing the size of each
struct or base data type that impacts the historical savefile
content. Those are referred to as the "critical bytes".
(Related note: the "you" struct required two bytes, low and high,
due to its size).
Compare those critical bytes in a savefile against the NetHack
build that is reading the savefile. This allows mismatch detection
early in the savefile-reading process, and a clean exit, rather than
proceeding to read nonsensical values from the file. Include some
feedback on what the first mismatch was when encountering
one.
For arrays stored in the savefile, use loop-logic in the core
to write/read the array elements one at a time, rather than in
a single blob. This will be required for changes to follow later.
(impacts artiexist[], artidisco[], svd.dungeons[], svl.level_info[],
svl.level.locations[][], msrooms[] field of mapseen, svb.bases[],
svb.disco[] objects[], svm.mvitals[], svs.spl_book[], svd.doors[],
go.oracle_loc[], utrack[], wgrowtime[])
This also adds data model to the long version information.
This invalidates existing save and bones files due to the changes in
the information at the start of the file.
Grimtooth is now permanently poisoned, protects the wielder from
poison, and can be invoked to throw poison.
Permapoison code comes from xNetHack by copperwater <aosdict@gmail.com>.
Previously, the code for monster healing was repeated every time it
was needed; this commit sends it all through a common function, which
will make it easier to make changes to how monster healing works in
the future.
This is just a code reorganisation and won't have any gameplay
effect unless I made a mistake.
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).
Add enlightenment feedback for Sunsword's blocking of becoming blind
from light flashes. It uses an extra property so that wizard mode
can report the reason.
EDITLEVEL is being incremented, so existing save and bones files are
invalidated.
I recently realized that I've been editing sources in a terminal
window that was widened in order to fit curses borders for testing
something or other. That has resulted in some new wide lines in the
source. There were lots of old ones too.
This updates some source files to try to achieve the goal of 78
characters or less. As in the past, I've been inconsistent about
lines with 79 characters. Lines with 80 or more have been wrapped
or shortened (usually by trimming an end of line comment or removing
redundant parantheses, sometimes just by reducing the indentation
of the continuation portion of an already wrapped line).
I eliminated one instance of warning manipulation for non-constant
format string, and simplified stone_luck() where Ken had a silly
comment about the function argument's name.
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
Using #invoke on wielded or carried Sunsword and picking direction
'>' or '<' lights the hero's spot. But setting levl[u.ux][u.uy].lit
was too simplistic. Lighting on the Rogue Level operates on full
rooms when done inside a room and doesn't to anything with done in a
corridor.
litroom() gave inappropriate messages in a couple of special cases.
Give a resistance animation if you #invoke Sunsword while it's
wielded and direct its blinding ray at yourself. Flashing a camera
at a monster who is wielding it will also produce the animation.
Support aiming at self (to become blinded) and aiming up/down (to
light the hero's current map spot only, persistently rather than
temporarily). Also, recognize cancel at the "direction?" prompt to
not leave the #invoke cooldown count set when aborted.
Aiming at self was a little trickier than expected to test because
you're blindness-resistant when wielding Sunsword. But it doesn't
have to be wielded to be invoked.
The code doesn't handle zapping blinding ray at yourself;
maybe it should be changed to handle it, but for now just
prevent Sunsword blinding the hero.
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.
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
src/artifact.c(1589): warning C6011: Dereferencing NULL pointer 'magr'.
The 'struct monst *magr' parameter to artifact_hit() can be Null
if 'mdef' is youmonst. mdef is nonnull.
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
Define some macros in include/tradstdc.h, for compilers that support
__attribute__((nonnull)), to assist in identifying which parameters
on functions are not supposed to be null pointers.
Next, for the majority of functions declared in include/extern.h, this
adds the appropriate macro that matches the actual use of each function's
parameters. The additions were done after performing some analysis.
These were the rules that were followed when determining which function
parameters should be nonnul, and which are nullable:
1. If the first use of, or reference to, the pointer parameter in the
function is a dereference, then the parameter will be considered
nonnull.
2. If there is code in the function that tests for the pointer parameter
being null, and adjusts the code-path accordingly so that no segfault
will occur, then the parameter will not be considered nonnull (it can
be null).
The use of the nonnull attributes allows the compiler to detect code in
callers of the function where a null parameter could get passed to the function.
If a warning is received the developer will have to do one of the following:
- If the null being passed to the function is now appropriate,
and the function should be able to expect a null parameter, then the
NONNULLxxx macro will have to be removed from the function's prototype.
or
- If the null being passed to the function is not appropriate,
correct the caller so it is not passing null.
or
- If the warning is about comparing to null, it may indicate an
unnecessary null check in the code involved. If it is deemed to be
unnecessary, it can then be removed.
Some static analysis tools apparently can work with the attribute, as well.
Following this, it was discovered that some functions were using one of the
(now) nonnull parameters in the first argument to the 'is_art(obj, ART)'
macro, which is defined like so:
=> #define is_art(o,art) ((o) && (o)->oartifact == (art))
That macro expansion inline resulted in a diagnostic warning because of the
'(o)' portion of the expanded macro, anywhere the macro was used with one of
the nonnull parameters. A test against null for a 'nonnull parameter' causes
a diagnostic warning.
To work around that, I replaced the is_art() macro with a function in
artifact.c, that accomplishes the same thing as the macro.
=> boolean
is_art(struct obj *obj, int art)
{
if (obj && obj->oartifact == art)
return TRUE;
return FALSE;
}
Some documentation...
These are the macros that have been defined for use when specifying the nonnull
parameters in a function prototype:
----------------------------------------------------------------------------
| Macro | Purpose |
+----------------+---------------------------------------------------------+
| NONULL | The function return value is never NULL. |
+----------------+---------------------------------------------------------+
| NONNULLPTRS | Every pointer argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG1 | The 1st argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG2 | The 2nd argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG3 | The 3rd argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG4 | The 4th argument is declared nonnull (not used). |
+----------------+---------------------------------------------------------+
| NONNULLARG5 | The 5th argument is declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG7 | The 7th argument is declared nonnull (bhit). |
+----------------+---------------------------------------------------------+
| NONNULLARG12 | The 1st and 2nd arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG13 | The 1st and 3rd arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG123 | The 1st, 2nd and 3rd arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG14 | The 1st and 4th arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG134 | The 1st, 3rd and 4th arguments are declared nonnull. |
+----------------+---------------------------------------------------------+
| NONNULLARG17 | The 1st and 7th arguments are declared nonnull (this |
| | was a special-case added for askchain(), where the |
| | arguments are spread out that way. This macro |
| | could be removed if the askchain arguments in the |
| | prototype and callers were changed to make the |
| | nonnull arguments side-by-side). |
+----------------+---------------------------------------------------------+
| NONNULLARG145 | The 1st, 4th and 5th arguments are declared nonnull |
| | (this was a special-case added for find_roll_to_hit(), |
| | in uhitm.c, where the arguments are spread out that way.|
| | We can't just use NONNULLPTRS there because the 3rd |
| | argument 'weapon' can be NULL). |
+----------------+---------------------------------------------------------+
| NONNULLARG24 | The 2nd and 4th arguments are declared nonnull (this |
| | was a special-case added for query_objlist() |
| | in invent.c). |
+----------------+---------------------------------------------------------+
| NONNULLARG45 | The 4th and 5th arguments are declared nonnull (this |
| | was a special-case added for do_screen_description(), |
| | in pager.c, where the arguments are spread out that |
| | way. We can't just use NONNULLPTRS there because the |
| | 6th argument can be NULL). |
+----------------+---------------------------------------------------------+
| NO_NONNULLS | This macro expands to nothing. It is just used to |
| | mark that analysis has been done on the function, |
| | and concluded that none of the arguments could be |
| | marked nonnull.That distinguishes a function that has |
| | not been analyzed (yet), from one that has. |
+----------------+---------------------------------------------------------+
The NO_NONNULLS macro is meant to place a flag on the prototype to
make people aware that an assessed function was determined to not
be eligible for nonnull parameters. It expands to nothing.
Unfortunately, that macro was added partway through this exercise, so there
aren't many instances of it in the upper parts of include/extern.h, even though
the functions there were likely assessed and categorized as not having any
eligible nonnull parameters. It just never got any macro at all, in that case.
Following the parameter usage analysis that was done, the following was
noted:
Some NetHack functions have added a test to catch a passed null
parameter, and exit the function early as a result, or call
impossible(), and then exit. While that approach prevents segfaults
from dereferencing a null parameter, the early return is silent
(when impossible is not called anyway), and the function's true
purpose is not fulfilled. Also, the calling function may have no
awareness that the function did not complete its intended purpose,
in many instances.
Functions with such a test and early return, cannot have the parameter
declared 'nonnull', because the code to test for 'null' will cause a
diagnostic to be issued if the parameter is nonnull.
It might be good to revisit some of those functions and consider,
on a case by case basis, declaring the parameter nonnull in the
prototype, and the test/code-path commented out.
When known discoveries was displayed on a window of type NHW_MENU,
header lines sent to it via menu_add_heading() disappeared into the
bit bucket. Switching back to putstr() made them show up. But I also
changed the type to NHW_TEXT. A menu_add_heading() in artifact.c was
overlooked, and after the window type change that started triggering
a panic when '\' or '`a' was used while any artifacts were discovered.
Not sure how other interfaces were dealing with this.