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>.
A common pain point I encounter when working on themed rooms is making
specific rooms generate. The only ways to do this were mass commenting
out the rooms not being tested, or hacking in different room frequency
values (even more annoying when testing a fill, not a room, or testing
pure-function rooms/fills that have no frequency).
This change solves that problem by allowing a wizard-mode user to define
THEMERM or THEMERMFILL environment variables to make specific rooms or
fills generate.
The first part of this change is converting all themed rooms and fills
that were plain functions into tables, and converting their comment
names into actual names in those tables. The names are not intended to
be shown during gameplay, but instead serve as values that THEMERM or
THEMERMFILL can be matched to to generate those rooms. It's no longer
possible to have a function themeroom; this will raise an impossible.
As far as I'm concerned, this is a good change because it allows some
code simplification of themerooms_generate and makes it easier to add
difficulty or eligibility parameters to rooms.
The second part of this change is adding a new nh.debug_themerm function
to make the environment variable values accessible to themerms.lua. I
looked for an existing way to do this but didn't see one (nh.variable
is the closest but appears to be for variables that get saved).
The final part is inserting behavior into the actual themeroom
generation code that changes how they generate when either a room or a
fill is set. I don't think it's safe to generate every single room with
the requested type or fill - that might lead to cases where the stairs
or a magic portal cannot generate. So it creates ordinary rooms half of
the time, which still results in plenty of themed rooms on levels.
Another thing to note is that any themed room using filler_region will
still only pick a fill 30% of the time. If one specifies both a fill and
a room that uses filler_region, many of those rooms will appear without
a themed fill.
Added the silver mace to be the base weapon type of Demonbane. It is appropriate that an artifact weapon designed to slay Demons would be made of (or plated with) silver. This helps to offset the damage reduction when Demonbane was changed from a longsword to a mace and makes it more specialized against silver-haters.
Set the probability to 2, equal to that of a silver spear.
Increased the weight of the silver mace by 120% -- equal to the weight increase from a normal spear to a silver spear. (Assuming the weapon is silver plated rather than made entirely out of silver.)
Increased the base cost to 60, a similar increase as spear to silver spear, to be an even number between silver spear and silver saber.
Monsters will prefer silver maces over regular maces.
Otherwise, identical in function to a normal mace.
If user has changed the stone glyph to something other than a space
(or uses a tileset), Sokoban levels showed the unreachable stone outside
the map area. Prevent marking those areas as seen, so the stone
glyphs aren't shown.
Author: PatR <rankin@nethack.org>
Date: Mon Apr 7 13:58:28 2025 -0700
fix issue #1404 - re-tamed feral pet starves
Issue reported by k21971: changes in 'struct edog' initialization
resulted in re-taming of a feral former pet producing a tame monst
that immediately dies of starvation.
I didn't look at the earlier behavior, just forced hunger to be
initialized separately from other edog fields.
Fixes#1404
Fix up a few comments in the monster throwing code. And change a
couple 'if (!Blind)' checks to use 'if (canseemon(magr))' instead
of that the player won't be told about a returning aklys hitting an
invisible monster ("it") on the arm.
Issue reported by k21971: a gnome throwing a wielded aklys at the
hero was killed when failing to catch its return. Bookkeeping for
dead monsters got messed up, then a crash occurred.
This fixes things. Instead of a comment stating that the thrower
might be dead, kill it off.
This has been sitting around for a long time. It prevents at least
one fuzzer exit.
Before adding this, I did trigger one yn_function() 'impossible'
while doing ordinary testing but wasn't able to reproduce that, so
am still not sure what is going on.
This was discovered when a game of xNetHack crashed with stack smashing
detected during dumplog creation after an ascension. I traced the
problem to a wish with a very long string the player had made much
earlier in the game ("greased very blessed holy rustproof unlit historic
thoroughly +5 very cloak of protection named it would be a shame if
something happened to me wearing this cloak"), which is further recorded
in an even longer form in the chronicle as 'wished for "X", got "Y"'.
That string does get truncated, but since the gamelog strings are
dynamically allocated, they can be longer than BUFSZ.
When show_gamelog was subsequently called, it didn't use any bounds
checking, which allowed its stack-allocated buffer to overflow. Changing
the offending sprintf to snprintf and limiting it to the buffer size
appears to fix this issue. It will truncate the string at BUFSZ-1
characters and therefore will be expressed in the dumplog as an
incomplete string, but 1) that was happening anyway because the gamelog
string already doesn't capture the entire "wished for X, got Y" message
on such a wish, and 2) this should only ever happen for very long
wishes.
Most things that can be dug or chopped can only have that done by one
of the two types of digging/chopping tools: pick-axe or axe. Since
closed door can be broken open via either type, mention the type of
implement in the final "you break through the door" message by adding
"with your <uwep>."
After updating the --dumpweights code in hack.c to insert "pair of"
for gloves and boots and "set of" for dragon scales, I've switched
it to use simple_typename() instead.
Turns out that that routine also lacked handling for 'pair|set of'.
And it was generating "coin of gold piece". Fix those.
Roughly half of the gems are "<gem>" and the others "<gem> stone",
so the --dumpweights output is different by more than just pair/set.
Experience-level and experience-points, if enabled, could be
highlighted via 'up' or 'changed' rules in initial display after
restore. I tried 'down' rule too but didn't produce with that.
I don't understand what was going on but was able to reproduce it
and then fix it via the trial and error method....
Those places that use get_table_str_opt() to get an optional string
value can instead use a function that returns a string.
This can be used for example in quest data lua table, or some table
fields in the lua api bindings, or the dungeon definition.
For example, in quest.lua
text = "You again sense %l pleading for help.",
could be replaced with
text = function() return "You again sense %l pleading for help."; end,
which of course allows using lua to build the string.
There were couple reports of doors being generated in a corner
of a room. This happened for randomly generated irregular rooms,
because the code that was deciding if a corridor starting or
ending location was good did not handle irregular rooms at all.
This changes the corridor code so it can now generate start and
end points properly for any valid position in irregular rooms,
instead of only on the edges. This means the corridor sometimes
meanders a bit more than before, because it tries to find
the end point away from the edge of the room rectangle.
Also added is sanity checking for the randomly generated rooms
and corridors level, testing for door placement and room connectivity.
And another fix for a rare special case where dig_corridor
created a zero-tile long corridor; the entrance door was placed,
but there was nothing behind it.
Fixes github #1269 and #1385
Fix warnings
objnam.c: In function ‘wizterrainwish’:
objnam.c:3536:54: warning: variable ‘didblock’ set but not used [-Wunused-but-set-variable]
3536 | boolean madeterrain = FALSE, badterrain = FALSE, didblock, is_dbridge;
| ^~~~~~~~
Disarming a chest trap was setting obj->tknown = 0 even though the
hero just discovered that it isn't trapped.
Triggering a chest trap behaved similarly. Since there are no
repeating chest traps, hero should know that the chest whose trap
just went off is no longer trapped.
chest_trap() didn't document its return value but was clearly meant
to return True if the chest was destroyed. It didn't handle that
correctly when the chest was being carried. However, none of the
callers actually use the return value. [This fix tracks whether the
chest gets deleted; a better fix would be to destroy an exploding
chest even when it is being carried.]
If 'autounlock' is set to test a chest for traps, skip "check for
traps?" when tknown is set; go directly to "disarm trap?" if the
chest is trapped, skip that too if it isn't.
If wand of probing hits a chest, set the tknown bit.
Issue reported by elunna: Using the 'F' prefix against a displacer
beast prevented swapping places.
This doesn't use the suggested fix. It is quite short but there is
a large diff due to change in indentation and reformatting several
comments because of that.
Attacking a displacer beast either with or without 'F' might miss,
hit, or swap places. It won't "harmlessly attack thin air."
Fixes#1377
Protect carried items as well as hero when carrying the Mitre of
Holiness. Already handled when wearing that artifact.
This might make it be too strong. At the time that it was given
the carrued attribute, there was no such thing as carried items
providing any protection to other carried items.
"You have disabled loading of bones levels" (during play) and
"You disabled loading of bones levels" (end of game disclosure)
both clearly refer to the player rather than the hero.
"You have never encountered a bones level" is accurate for current
hero but not necessarily accurate for the player. Rephrase it.
Also, if OPTIONS=!bones is set and the hero just died, extend
"You disabled loading of bones levels" during disclosure to
"You disabled loading and storing of bones levels" (even in the
case where bones wouldn't be saved anyway).
Initially diagnosed in an xnethack fuzzer crash - unblock_point
shouldn't be called when a closed door becomes non-closed, because it's
possible that there's a gas cloud on the space which means it still
blocks vision. These always need to be recalc_block_point. A number of
them were fixed, but when I went through all the xnethack ones, I found
some that were unchanged from upstream NetHack. I reproduced the sanity
check impossibles usually by breathing gas at a door as an iron golem
and then opening or destroying the door to trigger the unblock_point
call.
The use of recalc_block_point in wizterrainwish was not triggering this
bug, but the previous code there basically duplicated
recalc_block_point.