The prior behavior was an ugly kludge that treated rumors as
only-eligible-to-appear-from-fortune-cookies if they contained the
string "fortune" or "pity". This commit gets rid of that system and
introduces a system where each rumor can be specified as such by
prefixing it with "[cookie]".
In order to avoid changing existing behavior, I have applied the
[cookie] prefix to all the rumors which were affected under the old
rules. However, several rumors seem like they were misclassified under
the old rules, and I am in favor of changing them as follows:
Should be made cookie-only (they only really make sense in context of
eating a fortune cookie):
- "Ouch. I hate when that happens."
- "PLEASE ignore previous rumor."
- "Suddenly, the dungeon will collapse..."
- "Unfortunately, this message was left intentionally blank."
Should be made non-cookie-only (they make sense if they appear as
engravings, Oracle messages, or artifact whispers):
- "They say that a gypsy could tell your fortune for a price."
- "They say that fortune cookies are food for thought."
sound_verbal(char *text, int32_t gender, int32_t tone, int32_t vol,
int32_t moreinfo);
-- NetHack will call this function when it wants to pass text of
spoken language by a character or creature within the game.
-- text is a transcript of what has been spoken.
-- gender indicates MALE or FEMALE sounding voice.
-- tone indicates the tone of the voice.
-- vol is the volume (1% - 100%) for the sound.
-- moreinfo is used to provide additional information to the soundlib.
-- there may be some accessibility uses for this function.
It may be useful for accessibility purposes too.
A preliminary implementation has been attempted for macsound to test
the interface on macOS. No tinkering of the voices has been done.
Use of the test implementation requires the following at build time with make.
WANT_SPEECH=1
That needs to be included on the make command line to enable the test code,
otherwise just the interface update is compiled in.
I don't know for certain when AVSpeechSynthesizer went into macOS, but older versions
likely don't support it, and would just leave off the WANT_SPEECH=1.
If built with WANT_SPEECH=1, the 'voices' NetHack option needs to be enabled.
It was a bit strange, when I first started up the test, to hear Asidonhopo,
the shopkeeper, talking to me as I entered his shop and interacted with him.
A number of C compiler suites have a math.h library that includes a yn()
function name that conflicts with NetHack's yn() macro:
"The y0(), y1(), and yn() functions are Bessel functions of the second kind,
for orders 0, 1, and n, respectively. The argument x must be positive. The
argument n should be greater than or equal to zero. If n is less than zero,
there will be a negative exponent in the result."
At one point, isaac64.h included math.h, although that has since been removed.
Some libraries used in NetHack (Qt for one) do include math.h and that required
build work-arounds to avoid the conflict.
Rename the NetHack macro from yn() to y_n() and avoid the math.h conflict
altogether, eliminating the need for that particular work-around.
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() */
The pull request included some changes that were neither accidental nor
unintentional, so only a subset of the changes from pull request #869
submitted by klorpa were manually applied.
behaviour -> behavior
speach -> speech
knowlege -> knowledge
incrments -> increments
stethscope -> stethoscope
staiway -> stairway
arifact -> artifact
extracing -> extracting
The uses of "iff" were left alone.
Close#869
Issue #722 posted by copperwater and commented on by Entrez: both
shk_your() and the() inserted "the" in front of a unique monster's
corpse, yielding "the Lord Surtur's corpse glows iridescently" and
"the Lord Surtur's corpse drops to the floor."
Teach both of those routines to skip "the" when used for monsters
with personal names. It now omits "the" for "Medusa's corpse" but
still gives "the Oracle's corpse".
shk_your() operates on an object and can deal explicitly with corpses
of named monsters. the() operates on text and has to guess whether
it is being used in a similar situation. Right now the guess is just
"is there an apostrophe present?" and might need further refinement.
Fixes#722
Instead of returning 0 or 1, we'll now use ECMD_OK or ECMD_TURN.
These have the same meaning as the hardcoded numbers; ECMD_TURN
means the command uses a turn.
In future, could add eg. a flag denoting "user cancelled command"
or "command failed", and should clear eg. the cmdq.
Mostly this was simply replacing return values with the defines
in the extended commands, so hopefully I didn't break anything.
If #wizrumorscheck isn't able to open or read the rumors file,
continue on to check engravings, epitaphs, and bogusmons instead of
immediately returning.
Simplify the handling of capitalized hallucinatory monster names
which should be described as "the Bogon" rather than just "Bogon".
Instead of inserting an ESC character to indicate "this entry
should be freed when done", keep track of how many of the entries
come from mons[] (which always come first and shouldn't be freed)
and just free the rest. So one instance of inserting an ESC and a
couple of skipping it when present and one of testing for it when
freeing are removed.
Also, the check for bogusmons classification code was testing
whether the name started with a letter (or with '@', an obscure
special case for letter()) rather than for the actual type codes
used in dat/bogusmons.txt. If '@' becomes used as a classification
code (so far it isn't one) any entries using it could have been
misclassified and would be misspelled because it would stay as the
first character. And existing entry "/r/tard" was also subject to
misclassification, but since it doesn't start with a capital letter
either with or without leading '/' that had no noticeable effect.
If the leading slash was followed by a capital letter there would
have been a different sort of misspelling with that slash missing
in the copy kept in CapMons[]. I knew all that when I first used
letter() but have decided that it is better to extract 'bogon_types'
from bogusmon() in order for the handling to be more consistent.
The code to provide even distribution for rumors was being successfully
used for engravings, epitaphs, and hallucinatory monster names but not
actually for rumors themselves. Move it into its own routine and have
both the three miscellaneous things and rumors use that.
My testing has verified that asking for a true rumor can produce the
first and last true rumors and not either of the first or last false
rumors, and vice versa when asking for a false rumor. Asking for
unspecified true/false can produce all four of those. Aside from that
verification of correctness (I hope...), I haven't checked that the
distribution when selecting is actually even.
Solve the uneven distribution situation that has been present for
picking random rumors for a long time and for random engravings,
epitaphs, and hallucinatory monster names since 3.6.0. This relies
on the previous partial solution where short lines have been padded
to a longer length. When that length is N and random seek lands in
a long line of length L, retry if the position is in the first L-N
characters. Put differently, it if takes more than N characters to
reach the next newline, reject that random seek and try again. This
effectively makes long lines behave as if they had the same length
of N as the short lines have been padded to and when all lines are
the same length, all entries have the same chance to be chosen.
Two semi-related patches within the span of less than three days and
I overlooked the overlap. When the() deals with a capitalized string,
it calls CapitalMon() to check whether the string matches a monster
and if so whether usage should be "the Monster" (Oracle or Olog-hai
for example) or just "Monster" (Medusa). The first time that gets
called, it collects all capitalized non-the Monsters from both mons[]
and the bogusmon file. The latter just got changed to pad short
lines, and that works fine for selecting hallucinatory monsters at
random via get_rnd_text(), but non-the Monster collection processes
the bogusmon file directly and wasn't updated to strip the padding.
While testing 'the("Capitalized Hallucinatory Monster")' I noticed that
some hallucinatory monsters showed up more often than others. When
the random engravings, epitaphs, and bogus monsters were converted from
hard-coded arrays to data files accessed by random seek (3.6.0), they
became subject to the same distribution irregularites that rumors suffer
from. The chance that an entry will be chosen depends upon the chance
that a random seek will hit somewhere in the line which precedes it, so
entries that follow long lines are more likely to be chosen and entries
that follow short lines are less likely. We improved that for rumors
by having makedefs pad the shortest lines. Distribution still isn't
uniform but is much better than it was (and could be further improved
with a longer padding length at the cost of making data files bigger so
possibly slower to access; both overall size and access speed mattered
back when floppy discs were supported but are probably irrelevant now).
Start doing the same thing for the newer files: pad the shortest lines
to increase the chance that seek will find them. The tradeoff is that
the data files become bigger. Rumors, engravings, and epitaphs lines
are all at least 60 characters now; bogus monsters are at least 20.
These are the data file sizes I see (in bytes: old, new; padding for
rumors was already in use so its size hasn't changed):
bogusmon 4449 7211
engrave 1326 2894
epitaph 14159 24075
rumors 49173 49173
The only place that padding is noticeable in-game is #wizrumorcheck.
Function the() wasn't supposed to be used for monsters because many
of the ones with capitalized names confuse it, but over time multiple
instances of the(mon_nam()) have crept into the code. Instead of
ripping those out, modify the() to handle that situation better.
Pull request #636 by entrez dealt with this with one extra line of
code, but could end up scanning all the names in mons[] repeatedly
if the("Capitalized string") gets called a lot. This uses a similar
one line fix but calls a whole new routine that scans through mons[]
once collecting all the relevant special case names. As a bonus,
it does the same for hallucinatory monster names which name_to_mon()
couldn't handle.
Fixes#626
Whitelist all the verified existing triggers:
makedefs.c: In function ‘name_file’
attrib.c: one compiler balks at a ? b : c for fmtstring
cmd.c: In function ‘extcmd_via_menu’
cmd.c: In function ‘wiz_levltyp_legend’
do.c: In function ‘goto_level’
do_name.c: In function ‘coord_desc’
dungeon.c: In function ‘overview_stats’
eat.c: one compiler balks at a ? b : c for fmtstring
end.c: one compiler balks at a ? b : c for fmtstring
engrave.c: In function ‘engr_stats’
hack:c one compiler balks at a ? b : c for fmtstring
hacklib.c: one compiler balks at a ? b : c for fmtstring
insight.c: one compiler balks at a ? b : c for fmtstring
invent.c: In function ‘let_to_name’
light.c: In function ‘light_stats’
mhitm.c: In function ‘missmm’
options.c: In function ‘handler_symset’
options.c: In function ‘basic_menu_colors’
options.c: In function ‘optfn_o_autopickup_exceptions’
options.c: In function ‘optfn_o_menu_colors’
options.c: In function ‘optfn_o_message_types’
options.c: In function ‘optfn_o_status_cond’
options.c: In function ‘optfn_o_status_hilites’
options.c: In function ‘doset’
options.c: In function ‘doset_add_menu’
options.c: In function ‘show_menu_controls’
options.c: In function ‘handle_add_list_remove’
pager.c: In function ‘do_supplemental_info’
pager.c: In function ‘dohelp’
region.c: In function ‘region_stats’
rumors.c: sscanf usage
sounds.c: In function ‘domonnoise’
spell.c: In function ‘dospellmenu’
timeout.c: In function ‘timer_stats’
topten.c: In function ‘outentry’, fscanf, sscanf, fprintf usage
windows.c: In function ‘genl_status_update’
zap.c: one compiler balks at a ? b : c for fmtstring
win/curses/cursstat.c: In function ‘curses_status_update’
win/tty/wintty.c: In function ‘tty_status_update’
win/win32/mswproc.c: In function ‘mswin_status_update’
Starting out replacing ambiguous pronoun "it" since it might seem
to be referring to "--More--" rather than to "...", then ended up
rewriting whole paragraph.
Fixes#369.
Fixes#370.
The default entries inserted by makedefs -s (starting in 3.6.6,
to guard against having an empty data file which led to divide by
zero crash when nethack picked a random entry) lacked a terminating
newline so the first entry from the file (for the usual case when
that data file wasn't empty) got implicitly concatenated to it.
If the first entry got chosen during play, the initial portion
corresponding to the default entry was decrypted properly but the
concatenated portion corresponding to file's first line didn't.
So gibberish was appended to default engraving or epitaph or bogus
monster; also, the input file's first line would never appear.
The newline fix in makedefs is different from pull request #370
but accomplishes the same thing.
The bulk of the patch is an enhancement to #wizrumorcheck to show
first (default inserted by makedefs), second (first in input file)
and last engravings, epitaphs, and bogusmons in addition to rumors.
The command name has become a little misleading but the limited
functionality doesn't call for separate commands.
If either rumors.tru or rumors.fal was empty when makedefs made
'rumors', init_rumors() will set true_rumor_size to -1 to indicate
that rumors aren't available. It also closes the input file, and
then #wizrumorcheck closed that again, triggering a crash in the
dlb code.
Fortune cookies and oracles work ok (just not very interesting)
when rumors aren't available. Only the check command had trouble
with that.
The traceback points directly to the problem: divide by 0 happens
if the 'bogusmon' file only contains the "do not edit" line, which
would happen if 'bogusmon.txt' is empty. makedefs probably ought to
complain about that.
There is now one hardcoded bogus monster to fall back to: 'bogon'.
Random tombstone epitaphs report divide by 0 if their text source is
empty, but it is done by rn2() rather than rn2_for_display_rng() so
is just a warning for pre-release code. It would crash for release
version though.
I tried placing an empty engravings file and expected similar results
but didn't see any response. Not sure what that means.
After the fix, empty epitaph file yields blank result so graves that
want a random epitaph won't have any epitaph.
Fixes#302
The traceback points directly to the problem: divide by 0 happens
if the 'bogusmon' file only contains the "do not edit" line, which
would happen if 'bogusmon.txt' is empty. makedefs probably ought to
complain about that.
There is now one hardcoded bogus monster to fall back to: 'bogon'.
Random tombstone epitaphs report divide by 0 if their text source is
empty, but it is done by rn2() rather than rn2_for_display_rng() so
is just a warning for pre-release code. It would crash for release
version though.
I tried placing an empty engravings file and expected similar results
but didn't see any response. Not sure what that means.
After the fix, empty epitaph file yields blank result so graves that
want a random epitaph won't have any epitaph.
Fixes#302
Introduce eight achievements that can be attained by more players.
Entered Gnomish Mines - self explanatory
Entered Mine Town - the town portion, not just the level
Entered a shop - any tended shop on any level
Entered a temple - likewise for temple
Consulted the Oracle - bought at least one major or minor oracle
Read a Discworld Novel - read at least one passage
Entered Sokoban - like mines
Entered the Big Room - not always possible since not always present
The novel and bigroom ones aren't always achieveable since novels are
only guaranteed if a book or scroll shop gets created and bigroom is
only guaranteed in wizard mode. No one ever claimed that every
possible achievement can be attained in a single game. (If one for
entering the Fort Ludios level--or perhaps entering the Fort itself--
eventually gets add, that won't be possible in every game either.)
The mine town one probably needs some tweaking. Two of the town's
seven variants have no town boundary (despite a rectangular area of
pre-defined map) and at present simply arriving on either of those
levels is enough to be credited with the entered-town achievement.
Bump EDITLEVEL because u.uachieved[] has increased in size. This
time it has been expanded to the maximum that xlogfile's bitmask of
achievements can handle, enough for up to 9 more achievements without
another EDITLEVEL increment.
When GOLDOBJ was activated unconditionally, several texts started referencing
"money" instead of "gold".
As we don't have the intention to introduce a complex coin system with
different denominations, change it back and also some other places that
reference "money".
Make some progress on a couple of next minor release checklist
items, hopefully without introducing too many new bugs. This
is just the initial commit, and work continues.
Checklist items:
Savefiles compatible between Windows versions, whether 64-bit
or 32-bit in little-endian field format.
Selection of file formats:
historical (structlevel saves),
lendian (little-endian, fieldlevel saves),
and just for proof-of-concept, ascii fieldlevel saves
(the ascii is huge! 10x bigger than little-endian).
For the fieldlevel save, all complex data structures recursively
get broken down until until it is one of the simple types that
can't be broken down any further, and that gets when it gets
written to the output file.
New files needed for this build:
hand-coded:
include/sfprocs.h
src/sfbase.c - really a dispatcher to one of the
output/input format routines.
src/sflendian.c - little-endian output writer/reader.
src/sfascii.c - ascii text output writer/reader.
auto-coded (generated):
include/sfproto.h
src/sfdata.c
This is just one approach. I'm sure there are countless others
and they have different pros and cons.
For producing the auto-coded files a utility called
universal-ctags, that is actively maintained and evolving,
was used to do all the heavy-lifting of parsing the
NetHack C sources to tabulate the data fields, and store
them in an intermediate file called util/nethack.tags
(not required for building NetHack if you already have a
generated include/sfproto.h and src/sfdata.c)
util/readtags (also not required for building NetHack
itself) will decipher the nethack.tags file and produce
the functions that can deal with the NetHack struct data
fields.
You can obtain the source for universal-ctags by cloning it
from here:
https://github.com/universal-ctags/ctags.git
The combination universal-ctags + util/readtags has been
tried and tested under both Windows and Linux, so it is
not tied to a particular platform.
Note: util/readtags will work only with universal-ctags
output, so other ctags are unlikely to work as-is.
Universal-ctags can be build from source very easily
under Linux, or under Windows using visual studio.
This is based on the multiple-RNGs code fron NetHack4, but using
only the parts relevant to the display RNG (and with substantial
changes, both because of post-3.4.3 changes, and because Nethack4's
display code is based on Slash'EM's rather than NetHack's).
If bogusmon, engrave, epitaph, oralces, or rumors won't open, assume
it's because the file is missing so don't have impossible() tack on
"saving and restoring might fix this" when telling the player.
(Missing rumors or oracles previously only used pline() rather than
impossible() so this hadn't been an issue for them. Now it would be.)
Last few && or || followed by end-of-line comments, plus tab replacement
and 'return' parentheses. Not as many of those; some of these files had
already had that done.
Also, tweaked non-cursed scroll of charging read while confused to be a
tiny bit more effective.
To do: find and fix block comments that immediately follow a line with
an end-of-line comment and got misindented to line up with that comment.
I'll push a formatting guide at some point. There may still be
outstanding changes, but please feel free to resolve those as you arrive
a them.
To the best of my knowledge, there is no changes to the actual code
content, but the formatter does have the occasional bug. If you run into
an issue, please fix it!