I forced a test compile to -std=c++20 mostly to see what we would
be up against. There was only a small number of things and they
are corrected in this commit.
c++20 has some issues with comparisons and bit twiddling between
different enums.
The vendor-supplied Qt5 header files triggered some of those issues as
well, so the qt_pre.h and qt_post.h NetHack header files were adjusted
to make those new warnings go away. I have not tested Qt6 under the
new compiler and c++ version yet.
Because there are multiple pragmas in qt_pre.h now, the conditional
ifdef structure in there was modified a little to make maintenance
simpler and have a single pragma push at the top. The pragma pop
comes after the Qt vendor-supplied header files, and is done
in qt_post.h.
The display.h macro cmap_to_glyph() was used in
a Qt c++ file and triggered a series of warnings because of that.
Rather than write c++20-friendly versions of those macros, the
simple fix is to provide a function on the C side of things
to front the cmap_to_glyph() macro, so fn_cmap_to_glyph()
was added.
Also thrown into this commit, PatR picked up on the fact that for
yesterday's new warning in qt_menu.cpp, the compiler had correctly
picked up on the fact that the format range of the variable 'cash'
had been correctly upper-capped at 999999999L in the warning message
because of an assignment prior. He suggested that perhaps by also adding
if (cash < 0)
cash = 0;
the warning might be eliminated altogether.
After a test, that was proven to be correct, so yesterday's
more-kludgy change is reverted and replaced with that variable
variable restriction ahead of the snprintf().
When a game is restored while hero is Gehennom, give the "It is hot
here. You smell smoke..." message after the welcome back message.
For both entering Gehennom and restoring there, switch from "smell" to
"sense" in the second part of that message if poly'd into a form that
doesn't have sense of smell.
Some unrelated stuff that got caught up in this diff:
1) move welcome()'s call to l_nhcore_call() to the start of the routine
instead of placing that after a potential early return;
2) remove a redundant glyphmap flags reset line; the routine being
called starts by setting its flags field to 0 for level change so
caller doesn't need to do that;
3) look_here() is just a formatting bit.
I recently captured preprocessor output for a file and the amount of
code being expanded--and subsequently compiled--for canspotmon() was
quite an eye opener. This converts most of the macros it uses into
function calls. The resulting executable generated for OSX (built
for x86_64 and containing four interfaces) is about 5.5% smaller! and
there wasn't any difference in speed that I could notice.
The knowninvisible() macro has been in error for as far back as the
git logs go (which include those for the second cvs repository, so
over 20 years now).
Reported directly to devteam by entrez, the rloc() monst vanishes/
appears nearby/&c message was being given before "satisified, <shk>
suddenly disappears" making the latter redundant. As discussed, the
fix isn't as simple as suppressing one message or the other because
both are given conditionally.
This seems to solve it but has only been lightly tested.
A new feature, enabled by default to maximize testing, but one which can
be disabled by commenting it out in config.h
With this, some additional information is added to the glyphmap entries
in a new optional substructure called u with these fields:
ucolor RGB color for use with truecolor terminals/platforms.
A ucolor value of zero means "not set." The actual
rgb value of 0 has the 0x1000000 bit set.
u256coloridx 256 color index value for use with 256 color
terminals, the closest color match to ucolor.
utf8str Custom representation via utf-8 string (can be null).
There is a new symset included in the symbols file, called enhanced1.
Some initial code has been added to parse individual
OPTIONS=glyph:glyphid/R-G-B entries in the config file.
The glyphid can, in theory, either be an individual glyph (G_* glyphid)
for a single glyph, or it can be an existing symbol S_ value
(monster, object, or cmap symbol) to store the custom representation for
all the glyphs that match that symbol.
Examples:
OPTIONS=glyph:G_fountain/U+03A8/0-150-255
(Your platform/terminal font needs to be able to include/display the
character, of course.)
The NetHack core code does parsing and storing the customized
entries, and adding them to the glyphmap data structure.
Any window port can utilize the additional information in the glyphinfo
that is passed to them, once code is added to do so.
Also, consolidate some symbol-related code into symbols.c, and remove it from
files.c and options.c
Fix the problem reported by entrez of a zombie corpse reviving and
crawling out of the ground while the hero was busy doing something
(searching, digging, &c) and having the hero fail to react and just
keep doing whatever the thing was because the zombie was already
inside the range where a monster changes from no-threat to threat.
Done in the monster creation routine so any new monster (including
one revived from a corpse) that is visible,&c will cause the hero's
action to be interrupted. Teleport arrival probably needs this too.
Only interrupts an occupation, not other voluntary multi-turn
actitivy such as running or traveling. That would be trivial to
change ['if (g.occupation...' to 'if ((g.occupation || multi > 0)...']
but I'm not sure whether it ought to be extended to that.
Implement 'untrap' as an 'autounlock' action. Quite a bit more work
than anticipated. The new documentation is rather clumsy; too many
if-this and if-not-that clauses have intruded.
I'll be astonished if all the return values are correct....
[A couple of places were checking for (rx != 0 && ry != 0) to decide
whether they were performating an autounlock action at <rx,ry> but
that erroneously excludes the top line of the map if the current
level extends that far. Just check rx for zero/non-zero.]
This is enough to prevent abuse by denying access to functions and
denial of service (RAM and instruction step limits), but not enough
to allow restricted use of things that require finer control (e.g.
filesystem access).
If something goes wrong, the whole thing can be turned off, for
now, in config.h (see NHL_SANDBOX).
None of the current functionality requires changes to build systems;
some of the possible future functionality may require some #defines
- TBD. There is lots of dead code (#ifdef notyet) for bits of that
additional functionality; we can rip it out if we don't want those
additions or we can complete (parts of) it depending on our needs.
All current uses of Lua are connected to sandboxes and guarded with
nhl_pcall (sandbox and lua_pcall wrapper); options and limits can
be set at the callsites in the passed nhl_sandbox_info. Some of
the error handling may be wrong - panic() vs. impossible() vs
silence.
Memory and instruction step limits should be tuned prior to release;
there's no point tuning them now.
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().
Reported directly to devteam: when a Rider revived, its corpse
didn't get used up.
The change to have delobj() never destroy Rider corpses, like it
won't destroy the Amulet or invocation items, didn't take into
account that they should be destroyed when Riders revive from them.
Add delobj_core() to be able to do that without changing existing
delobj() usage.
I'm surprised hardfought players haven't been all over this one.
Add some basic functions to iterate through the monster list,
ignoring dead monsters. Mainly just to allow splitting up code
into discrete functions.
Not quite happy with the get_iter_mons_xy - should probably have
a pointer to iterator data struct, which gets passed through instead,
but this works for now.
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.
The fire command could claim that time passed when it hadn't (fill
quiver with ammo, which takes no time, then queue commands to switch
to matching launcher, which should also take no time while queueing,
only during subsequent execution).
If quiver is empty or has ammo in it, give wielded thrown-and-return
weapon (aklys) priority over filling quiver or switching to ammo's
launcher. Don't do that if quiver has non-ammo in it, otherwise
players running Valks who wield Mjollnir with super strength but
want to throw quivered daggers would complain.
When player is being asked what to fill the quiver with, use the
\#quiver command to do that. Using it honors a count to split a
stack, handles switching uwep or uswapwep to uquiver, and gives
feedback. This is actually a fairly substantial change.
For 'fireassist', when switching to a launcher that isn't already
uswapwep pick one known to be blessed or uncursed over one having
unknown BUC status. But use the latter as last resort.
More context-sensitive inventory support. While examining inventory,
if you pick an item other than gold and it has a quantity of more
than 1, "I - Adjust inventory by splitting this stack" will be one
of the menu choices.
Breaking doorganize() into two parts was much easier than expected,
but the new internal command added to be an alternate for the first
part had more niggling details than anticipated.
Message history only shows the first digit with "Split off how many?"
if the player enters more than that.
Fix '#repeat' for tty; both it and ^A can repeat an extended command.
Fix both for curses; they can repeat an extended command instead of
just repeating the initial '#' to start getting an extended command.
X11 (tested), Qt (tested), and probably Windows GUI (not tested)
behave the same as before: ^A (or #repeat) after an extended command
just repeats the # to run the dialog to get an extended command.
I hope this introduces fewer bugs than it fixes but I don't think I'd
bet on that....
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.
Normally dipping gets the thing to dip first and what to dip it
into second and the item-action handling knows that. I'm not sure
why that wasn't working as intended and I couldn't figure out how
to make it do that, so went another way: this adds an internal
extended command that executes an alternate dip routine which gets
the potion to dip into first and the thing to dip into it second.
The #dip command should allow an 'm' prefix to skip fountains and
pools, similar to how eating accepts it to skip food on the floor.
But this doesn't implement that.
Picking a corpse while looking at inventory issued a menu that had
entry for eating that and if on an altar another one for offering
that. Picking the eat or offer choice worked as long as there
weren't any other corpses on the ground or altar. If there were
others, they'd be skipped but you'd get prompted for which item in
inventory to eat or offer instead of operating on the one that was
used to initiate the action.
Stop attempting to catch up for lost time for shop damage repair
when getlev() loads a previousl visited level. Normal shopkeeper
behavior will take care of that.
Also, fixes the display related aspects of shop damage repair
interacting with ball and chain. They don't happen when its done
while the map is being shown.
Use 'fuzzymatch(,," -",)' when checking whether the name specified
in a player's wish text matches an artifact name so that extra or
omitted spaces and dashes are ignored. Wishing for "firebrand" will
yield "Fire Brand" and "demon bane" will yield "Demonbane".
sengr_at() is used as a boolean, declared as int, and returns FALSE
if there is no engraving present. Change the declaration to boolean.
Also, using fuzzymatch() without any list of ignorable characters
just to get case-insensitve matching didn't make sense so switch to
strcmpi().
monster and Elbereth unless there's no other choice.
Suggested by NetSysFire, don't create new monsters on top of scrolls
of scare monster. Not mentioned in the suggestion: unless they are
a type of monster that isn't affected by such scrolls. This extends
it to teleport destination too.
Avoid placing a monster on a scroll of scare monster or on engraved
Elbereth if there are other locations available. Only performed for
callers of goodpos() who explicitly request it, which at the moment
are makemon(), rloc(), and enexto().
Also, propagate 'mmflags_nht' to a bunch of places that were left
using long or unsigned for makemon() and goodpos() flags. I didn't
attempt to be systematic about that though.
Implements #717
while wielding a cockatrice corpse without gloves
Reported by vultur-cadens: if safely wielding a cockatrice corpse
without gloves due to temporary stoning resistance or wearing yellow
dragon scales/mail, having the resistance be lost to timeout or to
taking off the dragon armor should have turned the hero to stone but
didn't.
Extend the handling for taking off gloves to cover these other two
cases too. The feedback for these deaths is usually too verbose to
fit on the tombstone but does show up in logfile.
Fixes#715
For timed acid resistance and timed stoning resistance, report
"You {are,were} temporarily {acid,petrification} resistant."
For items being protected by worn equipment, add "by your {armor,&c}"
similar to the existing feeback about you being protected "because
<some-reason>". Wizard mode only.
When eating a meal that is affected by acid resistance or stoning
resistance and protected by temporary resistance, increase the timeout
so that the resistance doesn't expire until after the meal finishes.
That avoids getting the "you no longer feel safe from {acid,stoning}"
during the meal and not being affected by the dangerous food despite
that message. Useful because the protection is checked at the start
of the meal and not rechecked during; extending the duration hides
the latter.
Make selection rndcoord return a table with x and y keys.
Allow (most) coordinate parameters accept such a table.
Fix selection and des lua tests broken by the above changes and
an earlier change, because selections tried to set terrain
at column 0, and it now causes a complaint.
Life-saving has been setting u.uhpmax to max(2 * u.ulevel, 10)
and if it took place during level drain that could make u.uhpmax
increase instead of decrease, confusing healing which gets applied
to a monster who has drained the hero with Stormbringer or the
Staff of Aesculapius. Change the setting to be max(u.ulevel, 10)
(removing the times two part) and also have level drain force it
to be set back to previous value if/when it gets increased.
Max HP loss due to strength trying to drop below 3 or to fire trap
or to being hit by Death now uses a mininum max HP of u.ulevel
rather than 1. They don't have the alternate minimum of 10; I'm
uneasy that there are still two different minimum values.
I changed adjattrib() to set the flag to request a status update
before it gave its optional message rather than after so that the
new characteristic value would be visible during the message. That
resulted in not updating status when eating royal jelly changed HP
or max HP after boosting strength. But the same missing update
would have occurred--or rather, failed to occur--without the change
in sequencing if the strength boost causes a change in encumbrance.
Add a type to force g.{command_count,last_command_count,multi} to have the
same type (because cmd.c: g.multi = g.command_count;) and some resulting
cleanup.
Noticed that when I set a selection to grow in a random direction, it
instead grew in all directions, which is not what I wanted. Turns out
the -1 random dir ended up being passed straight to the code which
checks bitmasks, without any form of randomizing among directions.
So this adds code to do that, and defines W_RANDOM as -1 rather than
using a magic number. In the process I also noticed that specifying
"random" as the wall for a door in a room made it rerandomize the
direction every iteration of its loop, essentially rolling two rn2(4)s
and only proceeding if they matched. That was pointless so I cleaned it
up a bit.
Also added safety checks in the form of an impossible for des.corridor()
being called with "random" as either walldir, because this is not
implemented currently.
And lastly, I noticed that create_secret_door was entirely unused
(secret door creation is handled in create_door), so I deleted it.
The only behavior change caused by this is that the Valkyrie quest lava
pools will be a little smaller, which is the only place grow is
currently used. If it's desired to keep them the same, that should be
changed to "all".
trycall() is a short docall() wrapper that is a no-op if the item is
already identified or the player has called the object type already. For
some reason, many calls to docall() did those same exact checks
beforehand.
This commit eliminates that redundancy by converting those calls into
trycall(), which is now made extern rather than local to do.c. No
behavior should be changed by this commit; I've checked that none of the
affected places could take a different code path now that the
oc_name_known and oc_uname checks are removed.
Expose object timers to lua scripts. For example:
local o = obj.new("cockatrice egg");
o:placeobj(5, 5);
o:start_timer("hatch-egg", 3);
Available methods are:
- obj.has_timer("rot-corpse")
returns true if object has attached timer, false otherwise.
- obj.peek_timer("hatch-egg")
returns an integer value, which is the turn when the timer
attached to the object would trigger. returns 0 if no such timer.
- obj.stop_timer("shrink-glob")
stops attached timer, or if no timer type is given, stops all
timers attached to the object.
- obj.start_timer("zombify-mon", 15)
starts a timer with a trigger time in that many turns in the future.
replaces any previous timer of the same type.
Valid timers are "rot-organic", "rot-corpse", "revive-mon",
"zombify-mon", "burn-obj", "hatch-egg", "fig-transform",
and "shrink-glob". Also "melt-ice" is recognized, but does nothing
to objects.
Redo the recent artifact creation stuff by replacing several nearly
identical routines with one more general one. Also adds a tracking
bit for one or two more creation methods. That changed artiexist[]
from an array of structs holding 8 or less bits to one holding 9, so
bump EDITLEVEL in case the total size changed.