Apply a similar fix as a791b4b and f441696 ended up settling on for
normal vampire transformation to the vampire transformation that happens
when their shapeshifted form is engulfed.
dobuzz() was modified in 677b32c2a7 to anger the target, but beams
handled by dobuzz() don't necessarily originate from the hero. Monsters
can zap wands that hit other monsters, dragons can use their breath
attacks, etc. Those things were causing the targets to become angry at
the hero.
The <x,y> params of dog_eat are the pet's starting position that turn,
not necessarily the position of the object being eaten. If the pet is
doing a combined move-and-eat action, <x,y> will be its original spot,
but it will have already moved to <mtmp->mx,mtmp->my>, where the food
object also is.
The attempt to check whether the eating was happening out-of-sight
underwater (to suppress the message in that case) was checking the pet's
starting location, not its new location/the location of the food object.
So if a pet moved from water to land to eat something, the chowing-down
message would be improperly suppressed (and presumably the message for a
pet moving from land to water to eat would be improperly left
_un_suppressed, though I didn't actually try to reproduce that).
Selecting both 'P' (the just-picked-up items category) and 'A' (the
'auto-select relevant items' flag) when dropping items by type with 'D'
was automatically dropping every item in inventory instead of only items
that were marked as just having been picked up. Basically, it was
causing 'A' to act like it did before b65c93c amended its functionality.
This commit is more-or-less identical to 991e739, both in terms of the
problem and the fix, except that it applies to dropping items instead of
looting.
A monster may be unhidden if it's caught in a trap, but maybe_unhide_at
was checking mtmp->mtrapped across the board, which wouldn't work for
the hero. Use u.utrap instead under those circumstances. Also refactor
a bit so it shouldn't need the repeated guards against mtmp being null.
inherits() only examined the first item in u.ushops, so some shopkeepers
that should have had first dibs were ignoring the hero, one of the
causes of #969. Examine the entire u.ushops array instead of just the
first item so that the hero's position within the shop will be correctly
identified (and do the same in set_repo_loc, though it's probably not
really necessary there).
I think moving the m_consume_obj call (which will free the eaten item)
further down should fix this without causing any really wacky message
sequencing issues, but if maintaining the exact order is important
obj->unpaid and its price could be cached before the free instead.
Underwater vision was updated only once per turn, so if the hero had
more than one move per turn it could cause some spots to be left behind
on the map. For example, after moving around underwater while very fast
for a while:
}
}}}
}}}}
} }}@}
} }}}
Not only does the radius of vision appear to "smear" temporarily, but if
the hero moves fast enough, isolated spots can be left entirely behind
(since the normal underwater vision update only clears nearby spots, not
the entire map). Both these effects are visible in the example above.
The fix in this commit is to update the frequency of underwater vision
updates to "once-per-time-taken" rather than "once-per-turn", so that it
updates with every move. I'm not sure if it needs to happen more
frequently than that (i.e. in the "once-per-input" section) but I might
be overlooking something.
Also add missing punctuation to the message for applying a lamp
underwater.
The shopkeeper is speaking out loud, so use verbalize for consistency
with other types of speech.
I couldn't figure out a way to wrap the multiline version in quotes in a
way that actually worked and looked good, so I restricted this to the
pline responses.
A mute shopkeeper shouldn't be able to verbally tell you the prices of
objects. For normal chatting, on the other hand, shk_chat can handle a
mute shopkeeper (by changing from speech to "indications" -- hand signs,
body language, etc), so allow execution to reach that even if the
shopkeeper is mute (in a silent polyform).
Also more generally allow a shopkeeper to continue chatting with normal
shopkeeper responses if polymorphed into another creature, since they
apparently retain their minds (are able to tell you prices, can
transact, etc).
This is mostly inspired by the fact shk_chat has extensive handling for
mute shopkeepers, but it was unreachable as far as I can tell. It is
also funny to think of a newt or something wriggling around to indicate
it's been making a lot of money lately.
You can attempt to smell a monster at particular coordinates even if you
can't see it. Revert the message for the 'can't find a monster there to
smell' case to "That is not a monster", as it was before, since the
change makes it so that you only reach that line if there genuinely is
no monster there (previously it would be reached if there was a hidden
or undetected monster on the specified spot).
I know this is inane, especially for a little-used debug-mode-only
command, but I can't get it out of my head...
The use of the actual monster data instead of the glyph as a proxy ought
to make it somewhat more consistent as well (e.g. hallucination and
'showrace' won't affect the results), though I think that'd hardly be
worth it were it not for the incredibly funny message.
tinklebear on IRC noticed that a hero paralyzed by a floating eye was
still "charmed" and capable of "removing her armor" as part of a nymph's
theft attack. The same thing was true of foocubus seduction: a
paralyzed hero was still able to respond to the questions about whether
particular pieces of armor should be removed (and also do whatever else
may be involved in a successful attack...).
I think paralysis should prevent both those things. Nymph theft will
still work, unless she needs the hero's active cooperation in removing a
bulky piece of armor. Foocubus attacks will be prevented entirely by
paralysis, making it interfere like unconsciousness already does.
Apply a similar constraint to hero vs monster seduction, as well.
I realized that the "move the boulder billing to 'used-up items'" part
of 20392a6 didn't properly handle boulder movement from one shop to
another via a gap in a shared wall, becuase it wasn't looking at the
complete in_rooms() array (plus, it only triggered if the new spot
wasn't in any shops at all). When I tried to fix that, I realized that
stolen_value() was similarly not working reliably when passed a location
shared between two shops, and for the same reason: it was only using the
first character of the in_rooms() array.
I think this patch fixes both those things, but it would be worth
examining the change to stolen_value() carefully, to ensure getting the
roomno via the shkp won't change anything about its normal functioning.
I'm not totally sure about that -- I didn't notice any problems in some
brief tests of typical stolen_value() uses, but it seems like it
probably has some tricky edge cases.
At the very least, passing a boulder fully through a shared wall between
two shops one way, then back the other way, no longer triggers an unpaid
obj sanity check in my testing.
This will distinguish between a hat and a helmet, shoes and boots, etc,
so that a "conical hat" doesn't turn into a "helmet called dunce" after
type-naming it, and so on. Instead, more specific names will be used
for the base type, consistent with the terms used in various messages
(so "a hat called dunce").
Some of these don't seem like necessary distinctions to make, and some
of them probably don't make a difference at all (e.g. gloves vs
gauntlets, since "gauntlets" only shows up once you've IDed the item so
the user-assigned typename isn't visible), but it probably makes sense
to be consistent in using these functions if it's done for any armor
type.
An engulfing monster can expel you onto a level teleporter or other
level-changing trap, in which case it may (under highly specific
circumstances[1]) no longer have been in memory by the time mtmp->mx/my
were accessed to see whether the "Brrooaa" message should be printed.
It also doesn't make much sense to print that message by the time you've
already fallen through a portal, trapdoor, etc, onto another level, so I
think moving it before the spoteffects() call kills two birds with one
stone.
[1] The highly specific circumstances: you must die due to illness or
some other timeout (or generally die on your own turn rather than the
monsters' turn, since this ensures the level change isn't deferred until
the end of the turn), while engulfed above a level teleporter [or maybe
another similar trap -- I tested with a level teleporter], and be
lifesaved, while positioned such that the engulfer can't follow you
through the levelport after expulsion (e.g. surrounded by other
monsters). It may happen under some other conditions too, but even if
so it's pretty rare and was tough to reproduce.
A potion of acid could be destroyed and freed by dipping into a
fountain, then dereferenced after the fact -- both when checking its
type immediately after the water_damage() call (as was noticed by
hackemslashem and amateurhour on IRC), and also in the later switch/case
a few lines further down in dipfountain().
I basically reversed the original 'er != ER_DESTROYED' test here: as it
was before this, I think the only thing that could hit it was a greased
potion of acid, which would survive the initial dip due to the grease.
Such a potion would be silently deleted. Potions of acid which were
actually destroyed by water_damage, on the other hand, could be allowed
to continue down to the switch/case of further effects (and associated
dereferences). I think this makes more sense in reverse, with potions
that were protected by grease actually being protected and producing
normal dip effects, and potions of acid which exploded causing an early
return with no further effects. This effectively prevents the various
use-after-free scenarios that were possible, too.
This was totally silent, which -- at least for me -- has led to quite a
few cases of believing my bag or cloak is still greased when it actually
wore off the last time I took a dip. I think telling the player that
the grease has worn off would be helpful, and is consistent with other
types of water damage.
The message is printed even if you are blind, since that seems to be
true of all the other messages in water_damage(). I am not sure if that
makes complete sense (especially for ones like a scroll fading -- some
like water getting into a bag could be sensed by touch) but I didn't
change anything there.
The getobj prompt for charging was presenting any chargeable tool in the
hero's inventory as a suggested charging target, even tools which were
unidentified and undistinguishable from their mundane counterparts
(e.g. bag of tricks, magic harp, horn of plenty...). This leaked
information about the identity of these items and made it possible to
determine whether a generic 'harp' was magic or not.
When suggesting chargeable tools, include only those which are actually
known to be chargeable (unidentified or unseen chargeable tools can
still be selected, they just won't be suggested targets). Basically the
same as what's done for a potion of oil in the apply prompt.
Pushing a shop-owned boulder out of the shop wouldn't charge the hero
anything. Remedy this (and remove the boulder from the bill if the hero
then pushes it back in). Also tried to handle a couple other uncharged
boulder "theft" scenarios: pushing a boulder into lava or water, into a
trapdoor or hole, or into a level teleporter (various other traps
already charged for the boulder -- it was pretty inconsistent).
I externified onbill() for this, since relying on otmp->unpaid by itself
impossibles if you push a boulder through a gap in a wall between two
adjoining shops.
Short for distu(mtmp->mx, mtmp->my) (i.e. the distance between the hero
and the specified monster), which is a very common use of distu(). The
idea is that this would be a convenient shorthand for it; I actually
thought it (or something very similar) existed already, but couldn't
find it when I tried to use it earlier. Based on the number of uses of
fully-spelled-out 'distu(mtmp->mx, mtmp->my)' replaced in this commit
I'm guessing I just imagined it.
"A dry rattle comes from its throat" would be printed whenever a
canceled monster tried to spit at you or another monster while not in
the hero's line of sight. That seemed weird to me: you can't see the
monster and don't know what it is, but you can tell the sound is
definitely coming from "its throat".
Change the message if the monster isn't visible, and make sure it's
printed it only if the monster is nearby (within reasonable hearing
range for a "dry rattle").
Permit levelport by name to "delphi". That is what the Oracle calls the
level (or at least her room) in-game, so it seems like a natural guess
for the name of the level.
The message for trying to (re)name an unnameable monster was weird when
the player entered a blank name (a name consisting of only spaces), in
an attempt to delete the monster's existing name rather than give it a
new one. Several of the normal messages looked incomplete due to using
the blank string as the "new name" (e.g., "I'm Feyfer, not ."), and the
one which didn't include the name still seemed a little off ("calling
names" is almost the opposite of what the player is doing). Add a new
message for attempting to erase the name of one of the special
unnameable monsters, e.g. "Juiblex would rather keep his existing name"
or "The Oracle would rather keep her existing title".
I also noticed that the message for trying to give an unrenameable
monster the name it already has (e.g. trying to name Death "Death") was
revealing the genders of the Riders with personal pronouns. This is
avoided elsewhere (e.g. using "the way" in mdisplacem, "its" baked
into the message in mhitm_ad_deth), so I also added a slightly rephrased
alternate message for Riders which avoids any pronouns. For the
unnaming message, on the other hand, I just used "its" for the Riders,
since I couldn't think of a way to phrase it that avoided pronouns
entirely.
When fighting an unseen displacer beast mapped as an 'I' glyph on the
map, its typical ability to swap places with you was disabled and
replaced by it being treated like "thin air". This was because
execution reaches domove_fight_empty when the target swaps places with
you. Other than the displacement passive, this function is typically
only reached if there's no monster on the target square, so it prints
the "thin air" message and wastes a turn if you'd "expect" to attack
something (either because the player used an 'F' prefix, or because
there is an 'I' mapped on the destination square being moved into).
Hitting "thin air" seems like OK behavior for force-fighting a displacer
beast, since you are explicitly not trying to move into its spot (though
you could probably make an argument that the displacement should happen
even then, since it's the displacer beast initiating it), but the mon
being mapped as an 'I' doesn't seem like a good reason to disallow the
actual displacement from happening.
Don't treat an 'I' as "thin air" in domove_fight_empty if there's
actually a monster there, since it means there's a displacer beast
trying to swap places with you.
A couple other related changes: put an 'I' down on the map when an
unseen displacer beast swaps places with you, and use 'something' in the
'it swaps places with you' message when you didn't even realize there
was a monster there to begin with, so there's no context for the 'it' (I
used Some_Monnam at first, but the 'something' felt a little weird when
you were intentionally attacking an 'I' or warning glyph; this limits
the usage of 'something' further than Some_Monnam does).
The #showgold command now does mention known contained gold in your
inventory, so the various lines in the Guidebook which explicitly state
that it doesn't needed to be updated. Wish I had noticed this in time
to put it into the previous Guidebook patch I submitted, but what can
you do.
Reverts 690e072, which changed the various is_foo macros from this:
| #define is_elf(ptr) ((((ptr)->mflags2 & M2_ELF) != 0L)
to this:
| #define is_elf(ptr) ((((ptr)->mflags2 & M2_ELF) != 0L) \
| || ((ptr) == g.youmonst.data && \
| !Upolyd && Race_if(PM_ELF)))
This is a problem because g.youmonst.data is not unique to the hero:
the '(ptr) == g.youmonst.data' test will also be true of all player
monsters of the same role. For this reason, any of those player
monsters will be treated as sharing the hero's race, producing strange
results. For example, if the player is an elven ranger, any ranger
player monster generated will be considered 'elven' too (so will get a
to-hit bonus when attacking orcs, etc) -- but only while the hero is
unpolymorphed.
There are already other ways of checking the hero's race in addition to
her current polyform, most notably the maybe_polyd() macro. maybe_polyd
or something similar is already used in nearly all the cases where the
hero's race is being evaluated, meaning Race_if gets used instead when
the hero is in her natural form. So I think the check of the hero's
race in is_foo had very little effect except for the unintended
side-effects on player monsters.
In reviewing all the uses of is_{elf,dwarf,gnome,orc,human}, I noticed
only one case that relied on the hero-race-checking behavior. That has
been changed in this commit to use maybe_polyd (there's another 'raw'
is_human(g.youmonst.data) a few lines down, but it doesn't need
maybe_polyd since it already distinguishes between 'hero in nonhuman
polyform' vs 'nonpolyd or human polyform'). same_race(mondata.c) is
another case where &g.youmonst.data can be passed to is_foo, but
everywhere that calls it for the hero also calls your_race() or
same_race(&mons[Race_switch]) to handle the racial case.
There was a TODO about this; not exactly a great challenge but it feels
like a worthwhile change since the name was misleading. I also updated
the name of the do_intrinsics parameter of extract_from_minvent(worn.c),
since it was in a similar situation (and directly related, since it
controls whether to call update_mon_{in/ex}trinsics).
Specifying a count of 1 when throwing an object could leave you with two
stacks sharing one inventory letter. The second stack gets split off
when the player specifies a count (e.g. 't1o'), but keeps its original
invlet. Some early returns, like trying to throw at yourself with '.',
could fail to unsplit the stack. Theoretically, specifying multiple
items to multishot and then failing to throw them all could also leave a
partial stack; I don't think this is actually possible right now with
't' but I tried to make sure it won't become a problem if greater counts
than 1 are ever allowed.
The fix doesn't affect 'f', which can be a combined "create a quiver
stack and throw" action and doesn't have the issue with duping invlets.
Specifying a count to split off a new quiver stack with 'f' shouldn't be
reverted if the throwing fails or only part of the stack is thrown,
because the newly created stack may be intended for continued use as the
quiver in future turns. This slightly changes the behavior of the
existing unsplit when cancelling the throw (which previously unsplit the
newly created quiver and quivered the entire parent stack), but I think
this actually makes more sense -- the player only declined to throw the
new stack, not to create it (as if they canceled earlier in the action).
I routed a couple early returns through the stack unsplitting that
shouldn't actually need it (like Mjollnir and welded items) for
consistency's sake; I don't think it hurts anything.
Because the existing error was the default case in a switch/case
statement only reachable if the option matched one of the expected ones
in the list, it wasn't actually reachable: something totally out of
left-field wouldn't match one of the expected options so never hit the
switch, and something that did match one of the expected options would
by definition have a first character handled by one of the cases in the
switch/case.
Do it a slightly different way that should successfully raise an
unexpected value error for 'OPTIONS=autounlock:foobar'. I didn't remove
the default case entirely, because it could still catch an error if
some new value is added to unlocktypes[] without a corresponding case
being added to the switch statement.
The autounlock handler included an explicit 'none' option, a choice that
gave it a different UX from similar existing compound option handlers
(e.g. paranoid_confirm or pickup_types), which set 'none' simply by
deselecting all options. It didn't make the menu any easier to use (at
least in my experience), since in order to go from some combination of
options to 'none', you'd have to deselect everything anyway (which on
its own was enough to set 'none', so there was no reason to explicitly
select it after doing so).
Make the autounlock handler work like other compound option handlers,
such that deselecting all options is the way to set 'none', and there is
no explicit 'none' option included in the list.
The line got a lot longer than most other #attributes lines when the
hero had gold both in open inventory and in stashed containers, so break
it up into two lines (using the same approach as the pantheon info in
the first section). Maybe this isn't necessary but it does make it
stand out less.
The #showgold command now mentions (known) gold socked away in
containers in your inventory as of 706b1a9. Since the gold info in the
attributes display and dumplog matches the output of #showgold
otherwise, update it to do the same thing. Also refactored doprgold a
bit to be a little more compact, as opposed to enumerating all the
different combinations of gold/no gold in open inventory/containers.
This eliminated some string constants that were broken up into multiple
constants/lines (like "line 1 " "line 2"), which NetHack code style
seems to prefer to avoid.
Because the existing error was the default case in a switch/case
statement only reachable if the option matched one of the expected ones
in the list, it wasn't actually reachable: something totally out of
left-field wouldn't match one of the expected options so never hit the
switch, and something that did match one of the expected options would
by definition have a first character handled by one of the cases in the
switch/case.
Do it a slightly different way that should successfully raise an
unexpected value error for 'OPTIONS=autounlock:foobar'. I didn't remove
the default case entirely, because it could still catch an error if
some new value is added to unlocktypes[] without a corresponding case
being added to the switch statement.
The autounlock handler included an explicit 'none' option, a choice that
gave it a different UX from similar existing compound option handlers
(e.g. paranoid_confirm or pickup_types), which set 'none' simply by
deselecting all options. It didn't make the menu any easier to use (at
least in my experience), since in order to go from some combination of
options to 'none', you'd have to deselect everything anyway (which on
its own was enough to set 'none', so there was no reason to explicitly
select it after doing so).
Make the autounlock handler work like other compound option handlers,
such that deselecting all options is the way to set 'none', and there is
no explicit 'none' option included in the list.
The line got a lot longer than most other #attributes lines when the
hero had gold both in open inventory and in stashed containers, so break
it up into two lines (using the same approach as the pantheon info in
the first section). Maybe this isn't necessary but it does make it
stand out less.
The #showgold command now mentions (known) gold socked away in
containers in your inventory as of 706b1a9. Since the gold info in the
attributes display and dumplog matches the output of #showgold
otherwise, update it to do the same thing. Also refactored doprgold a
bit to be a little more compact, as opposed to enumerating all the
different combinations of gold/no gold in open inventory/containers.
This eliminated some string constants that were broken up into multiple
constants/lines (like "line 1 " "line 2"), which NetHack code style
seems to prefer to avoid.
A monster which has grabbed you could move away without becoming unstuck
if it hit the "move and shoot" or "helpless" conditions in the dochug
MMOVE_MOVED case (since those lead to early return or break), leaving
the hero stuck to a monster which is no longer adjacent. Put the
'grabber moved away -> become unstuck' stuff at the top of the block so
that it will always be evaluated if a grabber has moved.
I would have liked to move the whole "grabber checks" block up, but I
think it'd change behavior to have the u.uswallow attack come before the
early return for a helpless monster, so I split it up instead.
Min Str is typically 3 no matter the hero's race, but could be higher
(at least in theory?). Using ATTRMIN makes losestr respect the same
minimum as other kinds of attribute loss (I'm operating under the
assumption that this wasn't an intentional move to fix the minimum at 3
regardless of other factors).
Since losestr and losehp calls go together most of the time, this feels
like it probably makes more sense than repeating the killer name/format
twice in a row all over the place.
Remove callers' responsibility to deal with possible hero death when
calling losestr. This is less fragile and error-prone than leaving it
in the caller's hands, but it means that death from the monster spell
'weaken target' no longer goes through done_in_by, and the death reason
is no longer "killed by <monster name>".
Killer format isn't a boolean, since it has 3 possible values
(KILLED_BY_AN, KILLED_BY, NO_KILLER_PREFIX). It shouldn't make any
difference behind the scenes, but it's confusing to use 'boolean' for
it.
...could leave hero in creature form with negative u.mh
losestr can subtract HP, but doesn't directly kill its target. The
caller is responsible for possibly killing the hero if losestr reduces
her HP to 0 or lower; most callers do this by combining losestr with a
losehp call, which can kill off the hero if necessary.
MGC_WEAKEN_YOU calls done_in_by if u.uhp < 1 after losestr, but didn't
handle the Upolyd u.mh case, so could leave a polymorphed hero with
negative health. Add a rehumanize call in that case.
This could also be done by changing losestr to call losehp itself for
the HP loss it deals out, but this would interfere with
cast_wizard_spell's use of done_in_by to generate the death reason:
either all strength loss is described one way ("terminal frailty" or
something -- not great) or else losestr must be passed a death reason
and is described a different way than other attack spells (because it
wouldn't go through done_in_by).
Monster purple worms can now gain intrinsics from swallowing foes whole,
so maybe the hero should be able to do so too. Intrinsics aren't
granted immediately upon swallowing (that would probably have been
easier), but only once a corpse is created and then entirely digested.
I'm not sure if this is too powerful and was being avoided deliberately
for that reason, since it includes potential level gain from wraith
corpses in addition to other intrinsics. That's consistent with monster
purple worms but may be a bit too much in the hands of the hero, though
it is limited by needing the corpse creation roll to succeed.
The SIGHUP handling for the TTY windowport added in 594cb5f was wrapped
in '#ifdef HANGUP_HANDLING', but the actual define is 'HANGUPHANDLING'
without the underscore.
Instead of allocating space for ((n + 1) * size) (to make room for all
entries plus terminator), it allocated space for (n * size + 1).
init_extended_commands_popup would therefore write past the end of the
memory allocated for command_list and command_indx when trying to store
their respective terminator entries. This meant the X11 windowport
would crash when accessing the extended command menu, if NetHack had
been compiled with -fsanitize=address.
I also did some minor cleanup/refactoring to eliminate variables and
lines that were made redundant or useless by 9d64d13, which changed the
way the function worked and removed the need for things like tracking
indices in the source and destination arrays with separate variables.
The "[type the name]" prompt seems appropriate for handling via
cmdassist, so experienced players who don't need the help can hide it.
I also added a corresponding help note for the class genocide prompt.
So that a blank line wouldn't use up one of the player's "tries" for
class genocide, the game would continue to prompt until the input was
non-blank (or the user hit <esc>), with a tight loop around the getlin
call that only exited when it got some non-empty input. This apparently
risked leaving the game endlessly looping under some worst-case-scenario
hangup conditions. It was also inconsistent with normal genocide, which
doesn't have special handling of blank lines. Make the class genocide
prompt behave like the normal genocide prompt by removing the "blank
input" loop (and consequently treating a blank line the same way as any
other attempt to write a name).
The macro (currently unused, I think) for checking whether a particular
index designates a subroom was off by one on the maximum allowable
value.
Because of the dedicated extra space for the g.rooms array terminator
flag (hx == -1), subroom indices in g.rooms are set out in the range
[MAXNROFROOMS+1, MAXNROFROOMS*2], inclusive.
Also some minor formatting tweaks.
The way hole destinations work now theoretically allows for a
cross-branch hole or trap door to move you across branches in a way that
decreases your overall depth. If this happened, it would cause an
impossible when the negative result of (depth(new) - depth(old)) was
used to calculate fall damage. Limit fall damage to 1d6 if dist <= 0.
Missed this way to use the trap door (in a block added in 05761ba) in
previous commits, though I'm a little confused about whether that block
in dodown is even reachable given how various trap scenarios are handled
with dotrap earlier in the function.
This should prevent anyone from exploiting falling into the sanctum (by
taking advantage of a trap door in a bones file from a differently
laid-out dungeon, as described in the previous commit) to bypass the
invocation, in addition to falling past the actual end of the dungeon.
Similar story with saved trap door destinations: if a bones file near
the end of the dungeon came from a longer dungeon (i.e. with a
lower-depth castle) than the one the bones file is loaded into, the
trap door destination could be past the dungeon end. Clamp the
destination so it won't be lower than the bottom level of the dungeon.
The fixed destination of a hole or trap door was being used for the hero
but not for monsters. Make everyone land in the same place, so you can
chase a monster into a hole and actually find it.
Trap doors saved their destinations as an absolute level, rather than a
relative one, so if you loaded bones from a special level their
destinations would reflect the dungeon layout from the bones player's
game. For example, die on the Oracle level, on dlvl5, with a trap door
that goes to dlvl6. Another player gets those bones on their Oracle
level, which is dlvl8... the trap door would still go to dlvl6. Pretty
amazing trap door -- something you might see in a funhouse!
Include relative rather than absolute destinations in save and bones
files, much like stairs do, to avoid this problem.
I bumped EDITLEVEL because although this won't break save files in an
obvious way, it will interpret the (absolute) destinations in existing
save and bones files as relative, leading to some crazy long falls. :)
The special handling of polymorphing corpses included an "is acceptable
food if starving" rule which ignored the pet's other food preferences,
so a starving herbivorous pet would become willing to eat a
non-vegetarian corpse iff the corpse would polymorph it when eaten.
Along the same lines but latent: if there were a vegan polymorphing
corpse, a carnivorous pet would eat it not only if starving, but also if
maltreated and on the verge of becoming feral.
Instead of trying to fix this by reimplementing all the herbi vs carni
rules specifically for polymorphing corpses, just have a "don't eat
polymorphing corpses if neither starving nor almost untame" rule, and
fall back to the normal palatable corpses tests once it's been
determined that the pet is willing to eat even a polymorphing corpse.
I rephrased the comment just to make it negative (about avoiding
polymorph, rather than polymorph being OK under certain circumstances)
to match the new form of the rule.
Use verbiage for mon vs mon and hero (mostly hero) engulf attacks that
matches recent changes to monster vs hero engulf attacks more closely
(e.g. "swallows whole" instead of "engulfs" for purple worm, other
changes in b07fe59...). Also ensure non-AD_DGST engulf attacks
(e.g. from revamped trapper or lurker above polyforms) aren't treated as
"eating" (or as involving "debris").
Also change the enfolds and digests macros so they produce booleans
rather than attack pointers (I got a compiler warning about casting
struct attack * to boolean when I did 'boolean b = digests(ptr);').
An invisible monster reviving would be called "it" (as in, "It rises
from the dead!"). Improve on this a bit by instead saying that "the
troll corpse disappears!" (similar to the messaging used when undead
turning is used on an invisible monster's corpse), or calling the
revived monster "something" (as in "something escapes from a sack!")
instead of "it". Distinguish between the original location of the
corpse and the location of the revived monster when describing seeing
things that have happened to the corpse, since revival may not place it
on the corpse's location.
Trying to split an unpaid stack of named items in a shop, with
perm_invent enabled, would cause an impossible 'unpaid_cost: object
wasn't on any bill' because copy_oextra -> oname triggered an inventory
update while the newly created split stack was marked unpaid but before
the billing information had been split to match. Defer the copy_oextra
call until the billing info has already been split.
Nothing about read_simplemail is incompatible with using const, and the
lack of const required some contortions (copying ADMIN_SERVER_MSG to
another buffer with nonconst() to prevent a compiler warning).
This was the last place nonconst() was used, so I removed it.
Make admin message use urgent_pline so it's less likely to be skipped
and inadvertently missed, make ending punctuation conditional on message
itself not containing any (similar to what's done for T-shirt messages
in read.c), guard against printing an empty message (from a line like
"name:\n"; it does mean that subsequent messages in a single batch will
be discarded, but that's true of the existing guard against malformed
lines as well, and it should make the overwriting of characters past the
'msg' ptr safer).
Like some of the other coordinates in testmove.lua (addressed in
70008fc), the attempt to overwrite the stairs was targeting a spot
one space to the left of the actual position of the stairs, causing it
to have no effect (discussion suggested this may have been a result of
99715e0). Update it so the stairs are properly erased and won't
interfere with movement tests by stopping a running/rushing hero early.
(unconnected to the current dungeon branch, that is)
Level teleporting allows you to type in the name of a level instead of
its number. This normally only works for levels within your current
dungeon branch (main dungeon <-> Gehennom levelports are the exception):
entering "medusa" as a destination won't work while the hero is in the
Gnomish Mines, but it will work fine to get to the Medusa level from
elsewhere in the main dungeon.
Teleporting to a particular branch entrance didn't apply the same
restriction. The teleport would still happen even if the destination
branch was unreachable from the current branch, and in such a case the
game would just try to get the hero to the depth of the branch entrance,
within the current branch. For example, entering "quest" as a
destination within the Gnomish Mines would bring the hero to Mines' End,
since that's the closest depth-wise it's possible to get to the quest
portal level. Apply the same rules to branch entrances as exist for
named levels, excluding destinations that are unreachable from the
current branch.
The sections of the dumplog can be broadly organized into two
categories: 'current state' and 'game overview'. Current state includes
information about what exactly was happening when the game ended: the
map, recent messages, current inventory, and current attributes. Game
overview is more like a history of the game up to that point: vanquished
monsters, extinct species, conducts, and dungeon overview.
All the current state sections are listed first, followed by the game
overview sections -- I'm not sure if this was a deliberate move to break
the dumplog into two distinct 'chapters', but it's convenient for
readers who may only want to know the circumstances of a death without
seeing the nitty-gritty details of the entire game up to that point.
The one section that wasn't ordered with its category was major events,
which was positioned near the top of the 'current state' group, above
the inventory listing. This commit moves it into the 'game overview'
group. I put it at the top, since it can serve as a sort of summary of
the game for those who are interested but don't care about some of the
details of monsters killed, etc.
There are already pretty solid tests of makeplural, so add some in the
other direction, since it's similarly complicated and has to implement
many of the same special cases and exceptions in the other direction. I
originally tried just running the makeplural tests backwards to this
end, but several of them are tests to guarantee already-plural words
aren't further pluralized, so are not reversible.
These plurals (e.g. "gateau" -> "gateaux") were added to makeplural and
makesingular in 0c0262e, so the test was outdated and failed. I also
added another test for "Federal Bureau of Investigation", since 'bureau'
is an exception to the -eau rule (pluralizes as 'bureaus') and the
exception was originally implemented in a way that would have caused the
"Federal Bureau of Investigation" test to fail -- so it seems like a
good pitfall to test for.
Movement tests weren't working because terrain was being positioned one
square to the left of where it was expected, and the error message
printed something other than the expected coordinates.
I wasn't able to figure out why they were like this in the first place
(referring to one spot to the left of the hero with <u.ux-2, u.uy>), so
it's quite possible that I am missing some context here, but the test
failed consistently for me before making these changes, and now works.
Enable 'debugging' function calls, including error(), from the lua files
called by the wizard-mode #wizloadlua command. Without enabling them,
calling error() still produces an error but with a message like
"attempt to call a nil value (global 'error')" instead of the one
specified in the lua file.
I think this is the right way to do this without just enabling
everything across the board (which I assume would contradict the goals
of NHL_SANDBOX), but I will admit to being a little confused by trying
to understand exactly how the sandbox permissions work, so it's possible
this isn't the right solution.
Incidentally, I am probably misunderstanding this, but considering a lua
error still occurs when error() is called, just with a different error
message, is the function really "disabled" successfully by NHL_SANDBOX?
Engulfing pets were getting a double chance to get an intrinsic from a
digestion attack, because they got the mon_givit call in mhitm_ad_dgst
and then also the one in mdamagem.
There is a bit of inconsistency here, in that mhitm_ad_dgst requires
corpse creation to grant nutrition, but the non-nutrition effects of
eating a corpse like polymorph, extra health, etc, in mdamagem don't
have any such requirement. As a result, one of the mon_givit calls
required a corpse to be "created" before granting an intrinsic, and the
other didn't. In choosing which one to remove, I figured intrinsic
granting is probably closer to those special effects than providing
nutrition (and also putting it in mhitm_ad_dgst is not ideal w/r/t
message ordering: it causes the 'intrinsic granted' message to appear
before the monster kill message).
When breaking a wand of sleep, don't print the message "the sleep ray
hits you!" since it produces an area effect/explosion rather than a ray.
For a couple other wands, !ordinary (wand breakage) effects don't
produce a message (I assume because the do_break_wand feedback is
considered sufficient), but I put in an alternative message for the
explosion since I think it's important to inform the player the hero has
fallen asleep.
When you steal from a shop, its shopkeeper will remember you as a thief
and charge you higher prices in the future (as well as be more curt and
less polite in interactions with you, though not outright hostile) even
if you pacify them, or die on the level and revisit it later as a bones
file. This was an idea aosdict had, and I think it makes sense that a
shopkeeper doesn't forgive and forget, immediately returning to treating
you exactly like anyone else, just because you were terrorized into
paying her back. Paying a shopkeeper off may cause her to stop actively
attacking you, but it feels like she'd have her eye on you as a known
thief going forward (and maybe would hang up a sign with your picture,
saying something like "DO NOT ACCEPT CHECKS FROM THIS HERO").
This surchage already existed, but since it was tied to active anger
(which typically causes a shopkeeper to quickly abandon their shop to
follow you) it was somewhat rare to see it in action.
I did not implement it here, but one possible further tweak might be to
clear the surcharge if the shopkeeper is pacified via taming magic
(which more-or-less magically brainwashes the target to feel positively
towards the hero) but not if you simply pay your debts.
Whether a trap exists is independent on the underlying terrain type, so
putting a check for traps in a block structured like
| if (IS_ROCK(levl[x][y].typ) || levl[x][y].typ == ROOM)
| ; /* do nothing */
| else if (levl[x][y].typ == CORR)
| do_corridor();
| else if ((trap = t_at(x, y)))
| avoid_trap(trap);
would mean that the check for traps only happens on terrain other than
normal room and corridor spots. As a result, it wasn't being evaluated
in most places where traps might actually occur.
Move the test for traps outside of the terrain type evaluation if/else
series, so that it happens independent of terrain (and remove the
'continue' so it doesn't preclue evaluation of the terrain).
Once the rule actually started coming into play, it became clear the
avoid_moving_on_trap message was being printed in cases where the hero
didn't actually stop (i.e. shift-dir runmode, the "if you must" case),
so I also modified the value for that parameter so it will match the
situations where the hero stops running.
The refactor of domove made it impossible to run onto a trap (or water,
if blind), even in the shift-dir mode where the hero is meant to stop
only upon "hitting a wall or running into something". I use this
runmode to sprint large distances across the map, and the change to this
behavior meant that it was no longer possible to press shift-dir again
to continue past the trap. This restores the old behavior of allowing
shift-dir running to carry you onto a trap (though the hero will still
stop before hitting a trap in less breakneck runmodes).
The dark_room option determines whether remembered but unseen waslit
corridor spaces are displayed with S_litcorr or not. When it is
disabled, a remembered permanently-lit corridor spot will be shown
as "lit corridor" when out of sight. When it is enabled,
corridors are only shown as lit if they are currently in view.
Toggling this option was not immediately refreshing how corridors on the
map were displayed: an unseen spot shown as a lit corridor because
dark_room was disabled would continue to be shown as a lit corridor
when it was toggled on, until the hero's memory was refreshed by
visiting the spot and then leaving.
Slightly extend the existing function for updating how room spaces are
displayed when dark_room is toggled so that it will update the glyph
used for unseen lit corridors in a similar way.
Because back_to_glyph assumes the hero can directly see the spot, when
used for #terrain (e.g. when a spot was covered by a remembered object)
it would display corridors as lit if lit_corridor was enabled. Instead
of trying to suppress back_to_glyph's S_litcorr result only under
circumstances where it would be unusual, just show all corridor spots
with S_corr, so that none of them appear as "lit corridor". This
is consistent with what is already done for room spots for #terrain
(S_darkroom is forced to the basic S_room across the board).
Members of the watch can never be bribed, no matter how much gold is
thrown at them, but when a bribe was attempted they would say "that's
not enough" (implying incorrectly some higher amount would be enough to
pacify them). Also, an already-peaceful mercenary would take on an
unusually aggressive tone when given any amount of gold, giving the
player the same message to "beat it" as when a bribe succeeds in
pacifying them.
Adjust the mercenary bribery code a bit so that a more friendly message
is given if the monster is already peaceful, and so that unbribeable
monsters don't imply they are just not being given enough gold. I think
the actual effects of bribery shouldn't be affected by this commit, just
the messages/feedback.
Suggested by aosdict: instead of describing all gulp attacks as
"engulfing you", say "The <monster> swallows you whole" for purple worms
(or any other animal engulfer) to visibly differentiate between
engulfing and swallowing.
Using #loot with the 'm' prefix would claim there was nothing to loot
even if the hero was standing on a box, as long as there was no adjacent
monster. 'm' prefix is an explicit request to select a direction so
make it work regardless of whether a monster is there. Once I did that
I noticed that it was providing no feedback for using '.' or '>' as the
direction if no container was available, so adjust that to give the
"there is nothing here to loot" feedback.
Entering a count to repeat an action stopped working for counts greater
than 2 after 93db2a8: the unconditional call to reset_cmd_vars at the
top of rhack was resetting many more variables than had previously been
cleared at that point, including g.multi (which in this context tracks
how many more times the requested action should be repeated). As a
result any count would perform the requested action twice at most.
Reduce the list of variables zeroed out at the start of rhack back to
what it was before 93db2a8.
M-<key> shortcuts with altmeta enabled weren't working when preceded by
a count (e.g. 2 M-j for "jump twice"): g.program_state.getting_a_command
determined whether <esc> should be read as staring a potential meta key
combination, and was being reset by readchar on the first digit entered
without being reactivated for subsequent input. As a result, by the
time the player entered the actual command to be modified by the count,
readchar wasn't bothering to look for M-<key> sequences.
When the hero zaps a wand of digging down in an undiggable level, it
creates a pit. When a fleeing monster did the same thing, it had no
effect. Bring this closer to the behavior experienced by the hero: if a
monster tries to use a wand of digging to create a hole in an undiggable
floor, a pit will be made (and the monster will fall into it, if not a
flyer).
This is a bit more efficient, since everything up to S_hcdoor is
skipped anyway, but it's a little harder to understand so needs more
comments. Not sure if it's worth it or not...
When some parts of getpos were rearranged in 7404597, tests of terrain
types and glyphs were moved to a loop which iterated through the defsyms
array without being updated to handle cmap values instead. Consequently
they weren't excluding certain terrain types from being 'jumped to' as
intended. (Though it seems as though they actually worked by chance to
some extent, just because there's some overlap between terrain types and
defsyms in terms of where the 'walls/doors' blocks start and end.)
I also noticed that cmap_to_type was missing S_darkroom (it fell through
to the default case so that the function returned STONE), so I added it.
An amorphous engulfer like a fog cloud could engulf the hero, then carry
him into a closed door. If it was killed or decided to spit out the
hero, he would be left occupying the same spot as a closed/locked door.
Make an amorphous monster unable to move into a door if currently
engulfing the hero.
Something more complicated could be done along the lines of allowing the
move if the hero is himself in an amorphous polyform, but that verges on
being a little too silly, maybe.
I also included fixes to a couple miscellaneous, unrelated formatting
issues that I noticed recently.
Stair dlevels weren't being restored with the correct values when
recovered after the game crashed, apparently because they weren't being
reset back to their 'absolute' level from a 'relative' level. I'm not
totally sure of why this affected only recovered games (maybe that's the
only time when the 'relative' stair values are used?) but this fix seems
to work.
Fixes#812
If NH_DEVEL_STATUS was set to NH_STATUS_RELEASED or NetHack was compiled
without DEBUG defined, the 'vlen' variable in a couple pline.c functions
wasn't used. This could trigger compiler warnings.
When using #glance, the player is never prompted about whether she wants
'more info' about something on the map, even if flags.help is true and
she has pressed '.' -- the 'more info' prompt is exclusive to #whatis.
getpos_help already changed its description of '.' based on whether
'help' was on or off; adjust those criteria so that the description of
the 'more info' prompt is only included for #whatis, where it is
actually relevant.
I recently looked at the help while using #glance and got confused
about why the 'more info' prompt was never appearing, so hopefully this
should help forestall that sort of thing. #glance also prompts only
once, so there's some other information about "moving to another spot"
in the help text which is not relevant -- that could definitely be
addressed as well but I think it's less likely to be confusing, so I
didn't bother with it in this commit.
Commit 32234b1d in 2003 mentioned in its commit message that pet dragons
shouldn't be able to eat underwater food items. This was mostly true:
non-swimming pets wouldn't select underwater food as a goal, and
wouldn't spend an action eating something unreachable on their current
position. But the "combined eat and move" case in dog_move didn't check
could_reach_item, so if a non-swimming pet happened to move to a spot
with underwater food for some reason, they could eat it on that turn
anyway. This commit should close this loophole and prevent non-swimming
monsters from eating underwater food (or other unreachable food) even if
they happen to fly over its position.
A comment in allow_category states that if gold is explicitly selected
as a category, it should be included in the results regardless of what
other BUCX filters may have also been applied. That makes sense to me:
there's only one thing in the gold category, and it always has the same
BUCX status, so it's pointless to try to "filter" the gold category with
a BUCX filter. Considering it a "also add gold, on top of the filtered
results" category adds utility.
However, other categories which may include gold (specifically
justpicked; unpaid is in the code too, but that can't actually happen
in-game) were treated the same way: if the category included gold, no
filter could exclude it. As a result, if the hero had just picked up
gold, 'P'+'C', 'P'+'U', 'P'+'X', and 'P'+'B' all showed the just-picked
gold pieces -- there was no way to filter justpicked to exclude gold
the BUCX categories. This approach made less sense to me: justpicked as
a category may include gold, but filtering by BUCX actually has utility
there, and selecting it doesn't carry a "show me gold in addition to the
other filtered items" implication.
Maintain the same special treatment of selecting the coins category, but
drop it for justpicked and unpaid. In those cases whether gold is
listed in the justpicked result will depend on it not being filtered out
by the selected BUCX categories (and which one it belongs to, in turn,
depends on the 'goldX' option).
Just-picked-up gold was included in the list of items in the just-picked
category for most category-based menus like 'D' or #loot, but special
handling of gold for 'I'/#inventtype (to accomodate the 'goldX' option)
caused it to be excluded from consideration as a just-picked item.
Include recently picked up gold in 'P'/justpicked when doing type
inventory, consist with other category-menu-based actions.
Add another use to wizmgender: when it is enabled, include the gender
specified in corpstat flags in the name of a statue, corpse, or
figurine, since it can influence various things but otherwise remains
invisible (for monsters without gendered names). A little while ago
lichen corpses weren't stacking because, despite being a neuter monster,
the corpses they produced were being flagged as female or male; this
could be useful for debugging issues along those lines.
The wizard-mode option to highlight female monsters stopped having any
in-game effect after cb0c21e. Formerly it caused female monsters to be
highlighted with a red background (red color + inverse); this commit
uses inverse video only without overriding their color. Ensuring the
color override works consistently with the ENHANCED_SYMBOLS 24-bit color
doesn't seem worth it for what is a very niche debugging option, and I
think inverse video should probably suffice.
It also used to be a TTY-only option, but this enables it in curses as
well.
There was already handling in place to prevent showing the "continue
eating?" prompt for one-gulp food (like a wraith corpse), since the hero
would finish eating the food on that turn regardless of what the player
answered to the prompt. Resuming an interrupted multi-bite meal with
only a single bite remaining had the same problem, but wasn't accounted
for in the special "one gulp" handling. Modify the condition so it
checks for the number of bites remaining in the food, not the number of
bites total, and show the prompt only when there's more than one bite
left.
If you have a known source of water walking (i.e. are wearing formally
IDed water walking boots), allow travel to path you over water and allow
running over water. A transition from land to water will still cause
the hero to stop in modes other than the shift+dir "move until you hit
something" running mode, so that you don't careen across the entire
level unless you really want to.
Also, fix running over water when levitation or flying is equipped.
This stopped working after f88dce6970, only allowing the hero to move
one square at a time. And prevent travel from pathing the hero into a
wall of water even if flying or levitation is equipped, since they don't
allow you to bypass that terrain.