Commit Graph

8766 Commits

Author SHA1 Message Date
PatR
6586cc84b2 trap setting bit
Treat a trap object that has become the focus of trap setting
occupation as if it had already been used up.  (No discernable change
in behavior unless someone adds an artifact bear trap or landmine
that talks.)

Shouldn't 'trapinfo' be part of 'context' and be saved and restored?
(If so, it will need to include o_id and undergo pointer fixup during
restore.)  When trap arming is in progress hero will be too busy for
player to issue S(ave) command but a hangup save could take place.
2023-05-23 15:34:58 -07:00
copperwater
d813f534ad Fix: use-after-free when applying an object that got destroyed
Found this running the fuzzer with address sanitizer. After applying an
object, the game checks whether it's a talking artifact so that it can
speak to you afterwards.

If, however, the object got destroyed in the process of applying it,
this will read and dereference obj after they have been freed. I found
this with a cream pie, which is always destroyed when someone applies
it.

To fix this, I tracked the obj pointer for what I think are the only two
cases in the big switch statement that don't track this - royal jelly
and cream pie. Royal jelly is only conditionally destroyed depending on
further input, so its function had to be refactored to take a struct
obj**, but cream pies are unconditionally destroyed so it can just be
set to null.
2023-05-23 15:03:02 -07:00
Pasi Kallinen
39530f9228 Fix the mind flayer attacking without knowing your location
This was caused by the mind flayer mind blasting polymorphed hero,
causing them to revert back to human form, and dropping into a pool,
doing an automatic teleport to save them from drowning.
After that the mind flayer tried to hit them in melee, but the hero
was far away.

Recalculate the nearby and inrage variables after mind blast.
2023-05-23 19:24:19 +03:00
nhmall
4761bf190e OpenVMS update follow-up 2023-05-22 20:50:35 -04:00
PatR
ae0b306f9d sanity_check suppression
Handle ^P vs 'sanity_check' differently, and treat ^R similarly.
2023-05-22 14:43:01 -07:00
nhmall
ff727e916f work around a build issue with a compiler
One compiler was issuing an error diagnostic for cmap_to_glyph() macro (see below).
This just works around that by using cmap_a_to_glyph() which does not suffer the same fate.

mkmaze.c

    static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "In_mines(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c

    static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "In_hell(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c

    static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "gd" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c

    static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "on_level(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c

    static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "(&u.uz)->dnum" is not constant, but occurs in a context that requires a constant expression.
at line number 1448 in file mkmaze.c

    static const struct rm water_pos = { cmap_to_glyph(S_water), WATER, 0, 0,
.........................................^
%CC-E-NEEDCONSTEXPR, In the initializer for water_pos.glyph, "gd.dungeon_topology.d_sokoban_dnum" is not constant, but occurs in a context that requires a const
ant expression.
at line number 1448 in file mkmaze.c

    static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "In_mines(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c

    static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "In_hell(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c

    static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "gd" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c

    static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "on_level(...)" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c

    static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "(&u.uz)->dnum" is no
t constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c

    static const struct rm air_pos = { cmap_to_glyph(S_cloud), AIR, 0, 0, 0,
.......................................^
%CC-E-NEEDCONSTEXPR, In the initializer for air_pos.glyph, "gd.dungeon_topology.
d_sokoban_dnum" is not constant, but occurs in a context that requires a constant expression.
at line number 1450 in file mkmaze.c
2023-05-22 14:58:29 -04:00
nhmall
68b8e84aa3 changes to build with VSI C compiler
The changes from past OpenVMS compilers are #ifdef'd VMS9
2023-05-22 14:43:10 -04:00
PatR
1f8b3dfef5 ^P vs sanity_check
Normally when sanity_check is enabled it will take place for every
command executed.  Avoid that when the command is ^P because if a
sanity warning is a recurring one and the msg_window setting is
single or combination (tty-only) which shows one message, ^P will
just repeat each new warning without having any chance to cycle back
to earlier messages.
2023-05-22 00:32:25 -07:00
PatR
4e46a7de69 change enexto() to use collect_coords()
Greatly simplify enexto().  The new code might does more work up front
but less overall when/if a nearby spot is hard to find.
2023-05-21 17:30:06 -07:00
PatR
92a993a0b6 fix github issue #1029 - erroneous death feedback
for monsters that haven't died, when being removed from play to keep
them out of bones.

Reported by copperwater:  if the quest nemesis was present on a level
being saved as bones, it was removed from the map to keep it out of
the bones level but the quest feedback for nemesis death was given
in the process.  Would happen for blessed genocide of "*" too.  (It
didn't happen for #wizmakemap and I'm not sure why.)

This was an unintended side-effect of moving some common stuff from
mondead() and mongone() into m_detach().  The quest feedback isn't
actually common to both.  Instead of moving that back, pass a flag
that m_detach() can use to determine which routine called it.

Fixes #1029
2023-05-20 16:44:18 -07:00
PatR
9052bd5099 fix #K3925 - u.ustuck of long worm tail
Don't allow stick/wrap/engulf attacks directed at long worm tails
to succeed.  Achieved by making sure that 'notonhead' is up do date
in a bunch of places and utilizing the fairly recent can't-{stick,wrap,
engulf}-unsolid-monsters code.

Should prevent a 'sanity_check' warning about being too far from
u.ustuck that would happen when holding the tail while the head was
not adjacent to the hero.

Also don't let pet ranged attacks from choosing a long worm's tail
as target.  They'll still be able to target long worms provided that
the head is lined up and not shielded by tail segment(s).
2023-05-20 15:34:32 -07:00
PatR
f176318c45 covetous fixe(es)
Extracted from a larger pending commit.  While trying to make sure
that bhitpos and notonhead are up to date when attacks are processed,
I noticed that covetous monster handling was buggy.  For dist2(),
a value of 1 means adjacent in an orthogonal direction, so testing
for less than 2 unintentionally excluded diagonal adjacency.
2023-05-20 15:26:36 -07:00
PatR
f8e4c3e078 give fuzzer some protection against brainlessness
With the LifeSaved property (via amulet), hero being killed by
brainlessness gets killed twice.  But for explore|wizard mode where
the answer to "Die?" might be "no" and for the fuzzer where it's
always "no", a mind flayer's 3 drain-Int attacks or master flayer's
5 drain-Int attacks will usually kill the hero all over again (and
again, ...).  Skip remaining ones, like happens when one of them
hits and discovers that the target has no head or is mindless, for
the rest of the flayer's current move.

Monsters wearing life-saving don't get killed twice.  That doesn't
seem very fair, but this hasn't touched that.
2023-05-20 01:58:30 -07:00
PatR
925ebf6ea1 fix #K3924 - teleport as giant eel
The safe_teleds() change that restored picking random destination
attempts prior to making an exhaustive search contained a typo tjat
accidentally only accepted invalid positions instead of valid ones.
So unless it randomly picked 40 good spots, erroneously rejecting
all of them and then falling back to the try-everywhere situation
(which has its own testing without any typo), it would yield strange
results by placing the hero in walls or solid rock via choosing the
first inappropriate spot it tried.

Not part of that bug but related, sort of:  for rloc(), use
rloc_pos_ok() instead of goodpos() during the exhaustive search as
well as during the random tries, but hang on to the first (after
randomization) position that passes goodpos() for a last resort.

The collect_coords() flag for 'skip-inaccessible' intended to be a
quick way to filter out walls and solid rock was using !ACCESSIBLE()
which also rejects water and lava locations.  So such spots wouldn't
be picked by either safe_teleds() or rloc() when they were finding
a spot for aquatic or lava-tolerant forms.  Instead of duplicating
a bunch of code to decide whether the hero's current form or the
teleporting monster should avoid !ACCESSIBLE() for a reason other
than having Passes_walls, make collect_coords(CC_SKIP_INACCS) use
!ZAP_POS() which rejects walls and rock but allows pools.
2023-05-17 18:51:28 -07:00
PatR
49e93760a8 collect_coords() revisited
Make recently added collect_coords() global even though it is still
only being used in teleport.c.

Add CC_SKIP_INACCS flag to only collect accessible locations so that
there's no need for a custom filter callback or of collecting spots
that will always be rejected when put to use.  Caller needs to check
Passes_walls/passes_walls() to decide whether that is suitable.

Merge some of the old safe_teleds() with the new, making it try
randomly 40 times before collecting coordinates for an exhaustive
selection.  Prior to the recent change which added collect_coords()
it was trying 400 times and giving up if that didn't find a good spot.

Start using collect_coords() for rloc() as well as for safe_teleds().
Only try to pick a spot randomly 50 times now instead of 1000.  If
those all fail, it does an exhaustive search of a randomized list of
candidates instead of old left-to-right, top-to-bottom map traversal.
Has not had nearly as much testing as safe_teleds() underwent.

rloc() was explicilty ignoring map column 1 for some reason.  Unlike
reserved column 0, column 1 is part of every level and should be
considered for monster teleport destination even though most levels
don't utilize it.
2023-05-17 00:33:54 -07:00
nhmall
d7f581d8ad fix compiler complaint
a check for baselen being less than or equal to zero drew a compiler
complaint because the variable is unsigned
2023-05-15 23:14:26 -04:00
nhmall
420048b8d3 keep external identifiers under length of 31 char 2023-05-15 23:11:34 -04:00
PatR
59677e7440 rewrite safe_teleds()
The teleport to safety routine would try to find a viable spot 400
times, the first 200 rejecting trap locations and the last 200
accepting such.  While testing various escape from lava variations
recently, I noticed that I could fail to reach safety even when there
was an open spot immediately adjacent to me.  I added a BandAid(tm)
to make another 400 tries if the first attempt failed, but that was
clumsy and still didn't guarantee picking a viable spot.

This adds a new routine, collect_coords(), which will gather a list
of coordinates for the entire map.  safe_teleds() goes through them
one by one until either finding a spot or exhausting the possibilies,
without randomly trying and retrying the same spot multiple times and
without missing other potential spots, also without just scanning the
map from left to right and top to bottom or similar.

Various other things which retry over and over, and especially the
ones which make a bunch of random attempts and then fallback to trying
every spot on the map, could be switched over to this, at least for
the falling back phase.  Right now collect_coords() is local to
teleport.c but that could be easily changed.

The try-at-random method is much quicker when there are lots of
available spots but the gather-shuffled-candidates method is
guaranteed to succeed if success is possible.  The way safe_teleds()
is presently using it collects the list with all locations within
2 steps first, then those within 3 to 4, then 5 to 6, and so on out,
randomized within each block of ranges.  So the destination will be
within one step of being as close to the starting spot as possible
but not always immediately adjacent when that happens to be available.
2023-05-15 01:42:13 -07:00
PatR
2646688e2d back_on_ground()
Replace a couple of hardcoded "back on solid ground" messages with
something more versatile.

Also, make life-saving handling for failed rescue from drowning
similar to that of failed rescue immolation by lava.  If there are
any cases where more than two tries is needed, they elude me.  The
new code doesn't confer temporary water walking if emergency teleport
fails; perhaps it should.
2023-05-15 01:08:04 -07:00
nhmall
ec9d3cb88e keep external identifiers under 31 characters length 2023-05-13 13:49:57 -04:00
Pasi Kallinen
a54b6ba75c Split object init out of mksobj
Move the random erosions in there, as those should only be done if
the object is getting a random initialization.
2023-05-13 12:12:36 +03:00
Pasi Kallinen
f4f74b2e07 Purple worm burp wakes up monsters 2023-05-12 09:17:09 +03:00
PatR
7737fcc97c fix out of date safe_teleds() calls
When safe_teleds() was changed to take an int flags argument instead
of a boolean, some calls weren't updated.
2023-05-11 12:12:12 -07:00
PatR
56f0340f0c fix #K3920 - migrating mimicker sanity
Report says that Wizard of Yendor posing as some other monster
triggers a sanity_check warning if on the migrating_mons list and
hero has Protection_from_shape_changers attribute.  My attmpts to
reproduce that failed, but this updates the mon_sanity checking to
explicitly allow a monster posing as another monster to be on the
migrating monsters list.

This also adds checks for whether a monster on the fmon list has
MON_MIGRATING or MON_LIMBO or MON_DETACH bits set in monst->mstate
and whether a monster on the migrating_mons list has MON_DETACH set
or both MON_MIGRATING and MON_LIMBO clear.  I won't be surprised if
these new checks trigger sanity complaints.
2023-05-11 11:51:57 -07:00
Pasi Kallinen
91257e00c7 Werewolf howling wakes up monsters 2023-05-11 18:48:02 +03:00
Pasi Kallinen
8c8acee423 Fix hero trapped in nonexistent pit
If hero was trapped in a pit, and a monster threw a boulder
at that location, the boulder filled the pit but hero was still
trapped in it.
2023-05-11 16:00:57 +03:00
Alex Smith
fcb86ad709 Fix "while helpless" showing up for some characters who weren't helpless
If the game ended while the player was in the middle of a repeated
command (that was not cancelled), this would spuriously cause a
"while helpless" to appear in the death message and logfile entry.
This bug is hard to observe because most things that can kill the
character also cause repeated commands to stop repeating, but is
easy to reproduce by command-repeating the #offer command that's
used to ascend (as "n20#offer" or "20#offer" depending on the
control scheme): doing so produced "ascended, while helpless"
prior to this patch.
2023-05-11 01:43:06 +01:00
Pasi Kallinen
81884a92e0 Don't show error when monster in lava cannot tele away
... due to the level being full of monsters.
2023-05-10 18:43:35 +03:00
PatR
92ca5dbf71 describe monster's size for stethoscope/probing
Monster size affects knockback but it wasn't provided anywhere within
the game.  Have wand of probing and stethscope feedback for monsters
include tiny|small|medium|large|huge|gigantic as applicable.
2023-05-10 02:23:57 -07:00
Pasi Kallinen
ead2f94d6e Fix genetic engineer poly hit killing the defender
The polymorph hit can kill the monster, but this info wasn't
propagated back.  If the defending dead monster retaliated,
the game issued an impossible.
2023-05-10 09:34:28 +03:00
PatR
845e813e86 fuzzer vs repeat deaths, take IV
A limit of 20 deaths on the same move before having the fuzzer give up
was not enough.  Bump that to 100.  No effect at all on normal play.
2023-05-09 22:38:53 -07:00
PatR
dd7d7f2eae fuzzer vs lava, take III
Change dying in lava to attempt life-saving at most twice.  The first
time might be via amulet or via declining to die.  The second time
can only be via declining to die after failing to teleport to safety
the first time, but could happen in explore mode as well as in wizard
mode, either interactive or fuzzer.

If the hero dies twice without ending the game, confer temporary fire
resistance and water walking so that other actions can be attempted.
After 5 turns without getting away from the lava or doing something
to acquire those capabilities, the hero will be subject to falling in
again.

Then the whole cycle might repeat, even many times, but the fuzzer
will eventually choose ^V or #wizmakemap and escape.  Players in
explore mode will either figure out a way to get out of it or
eventually have to give up but can try as many times as they like,
not that much different than being cornered by a deadly monster.
2023-05-08 15:21:03 -07:00
PatR
3f4634211f fix #K3918 - statue-to-boulder polymorph in water
'sanity_check' complains if it finds a boulder in water or lava.
Polymorphig a statue usually produces another statue but might
produce a boulder.  If done it water, keeping the boulder intact
would trigger the sanity warning.  Break it into rocks if object
polymorph produces a boulder at water or lava location.
2023-05-08 02:07:31 -07:00
PatR
210387c176 break fuzzer out of life-save loop, take II
When being burned up by lava, die 20 times before giving up the
attempt at life-saving (was unlimited).  Giving up leads to the hero
standing on lava rather than dying.  Normally moveloop() dunks the
hero again on next turn but fuzzer life-saving now has a chance to
confer temporary fire resistance.  So hero might have an opportunity
to level teleport or use ranged attacks that free up spots so have
somewhere available to teleport to safety if/when dunked again.

The recent code to give up on trying to resurrect the dying hero
after 15 deaths on the same move is extended to 20.  They apply to
each of the 20 lava resurrect attempts but still doesn't guarantee
that the hero will eventually get free before done() gives up.
2023-05-08 01:35:21 -07:00
Pasi Kallinen
9ca1c5fb56 Fix dmonsfree warning caused by knockback
When a monster being attacked was knocked back into a level
teleport trap, the attacker could still hit the defender.
If the second hit then killed the defender, this could result
in dmonsfree warning.
2023-05-07 22:12:56 +03:00
Pasi Kallinen
3ffb69bf2f Fix another "no monster to remove" impossible
Pet ranged attack code was using the same variable for
both pet attacking a monster and then the monster's possible
retaliation attack. The retaliation obviously overwrote the
pet attack return code, allowing pet to move afterwards.
In certain case this could result in "no monster to remove"
warning.
2023-05-07 22:12:56 +03:00
nhmall
afca423f06 build fix 2023-05-07 07:20:11 -04:00
Pasi Kallinen
e18548c821 Fix knockback impossible
When monster attacked another monster, and the retaliation attack
knocked back the attacking monster, the variables holding the
attacking monster coordinates were out of sync and caused
"no monster to remove" warning.

Propagate back the knockback hit, so the current monster cannot
do anything further.
2023-05-07 12:57:09 +03:00
PatR
b4b251b618 temporary blindness vs permanent blindness
While testing the fix for unicorn horn vs blindness, I noticed that
when 'blind from birth' (OPTIONS=blind) you would get "your vision
seems to dim for a moment but is normal now" when timed blindness
was added to persistent blindness.  That happened for both 3.6.x and
to-be-3.7.  Change it "you have a strange feeling for a moment".

In 3.7, having temporary blindness timeout while permanently blind
produced "your vision seems to brighten for a moment but is normal
now".  Change that to strange feeling too.  For some reason I haven't
tried to figure out, 3.6.x stayed silent when this took place.
2023-05-07 01:03:29 -07:00
PatR
76fbca7d25 fix github issue #1020 - unicorn horn vs blindness
Reported by argrath:  the test for whether blindness could be fixed
by applying a unicorn horn got broken by the recent change to the
Blinded macro.

While in there, undefine macros once they stop being useful.  I put
these #undef lines closer to their #define instead of at end of file.

Also, remove an out of date comment about encoding property troubles.
It became obsolete when unicorn horn stop fixing lost Str, Dex, &c.

Fixes #1020
2023-05-07 00:37:51 -07:00
PatR
ca02bc4898 fix #K3917 - fuzzer stuck in endless lifesave loop
Life-saving from being burned up in lava attempts to teleport the
hero to safely.  If that fails, hero immediately burns up again.
For fuzz testing, that results in an infinite loop.

While implementing a fix (in done(), not just lava-specific), I
noticed that hangup while running interactively in explore or
wizard mode could be subject to similar effect.

For the fuzzer, if hero dies 15 times without advancing the move
count (not 'moves', the turn count), don't life-save again.  With
hangup, don't prompt for "Die?" more than once.

Normal interactive declining to die still works.  The more exotic
situations aren't tested.
2023-05-06 16:51:59 -07:00
Pasi Kallinen
5023e4bfad Fix tame soldier being angry
Wizard-mode created tame soldier got angry when another
soldier played a bugle.
2023-05-06 23:12:59 +03:00
Pasi Kallinen
fe3dcb4416 Genetic engineer uses up turn when polying a monster
The fuzzer encountered a "no monster to remove" impossible,
this should fix it, even though I did not manage to reproduce
it manually.
2023-05-06 20:16:28 +03:00
Pasi Kallinen
d036116dee Fix hiding under nonexistent obj
Zapping a wand of teleportation at a location with object and
a monster hiding under it, but with the level full of monsters,
the monster would stay hidden even when the object was moved.
2023-05-06 20:13:10 +03:00
PatR
6de996971b fix #K3915 - "deleting worn obj" warning
Knocking a monster into lava triggering impossible warnig "deleting
worn object" for a wooden shield.  The monster had to be wearning
flammable armor and be fire resistant to survive instead dying and
dropping inventory, and it couldn't be a creature that can survive at
lava locations like a salamander and the armor needed to already be
thoroughly burnt.

It wasn't hard to figure out what needed to be fixed, but it was very
hard to reproduce the situation in order to verify that fix.  The
report was for monster vs monster knockback but I jousted with a lance
instead.  I still don't understand why burning up a worn wooden shield
triggered the warning but burning up a worn orcish cloak did not.
2023-05-06 00:08:35 -07:00
PatR
41c8a18d3d hurtling monster bumping into hero
Typo/thinko resulted in passing wrong monster variable to x_monnam().
2023-05-05 17:50:57 -07:00
PatR
e28d15f491 endgame portal fix
A recent change to prevent creating webs at water locations also
deliberately prevented them at air locations but had the unintended
side-effect of preventing creation of magic portals on the Planes
or Air and Water.  Those two levels always place the portal in air.
Explicitly allow air if the trap being created is a magic portal.
2023-05-05 17:42:57 -07:00
PatR
180042434e more engraving sanity feedback
Delete engravings made in a breach of a shop's wall or of a vault's
wall or in the guard's temporary corridor when the wall is repaired
or the corridor removed.  If 'sanity_check' was On, those would
trigger impossible warning "engraving sanity: illegal surface (x)"
where x was the terrain type code for solid rock or relevant walls.

Adding del_engr_at() calls to the shop code was straightforward.
The vault code is very complicated and I'm not sure that all the
calls I added were actually necessary.
2023-05-04 06:02:23 -07:00
PatR
c0441126b8 fix #K3912 - engraving sanity: closed doors
Engraving in an empty doorway and then using locking magic to create
a door there resulted in an impossible warning: "engraving sanity:
illegal surface (23)" if the 'sanity_check' option was On (wizard
mode only).  Engraving in an open doorway and then simply closing
the existing door produced the same effect.

Accept engravings at closed doors.  Presumably hero will be using
Passes_walls to attempt that so treat closed doors same as open ones.
Update the engraving sanity check to deal with that.

Bonus fix: engraving sanity checking stopped after the first problem
instead of checking every engraving.  Have it continue instead.

Not fixed:  vault wall repair and temporary corridor removal does
not delete engravings and can trigger the illegal surface warning if
player engraves before the repairs.  I didn't test shop wall repair
but it doesn't have any engr references so probably has the same bug.
2023-05-03 14:29:03 -07:00
PatR
53adba6602 maybe fix #K3909 - hurtled monster detached twice
I haven't forced a test case to verify this, but the logic for
calling mliquid() at the end of mhurtle() was clearly wrong.

Also, implement missing case of one monster causing another to
hurtle and hit the hero.  Compiles but is otherwise untested.
2023-05-03 12:18:15 -07:00