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.
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.
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.
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.
Get rid of some unnecessary code when ignoring unimplemented buried
monsters. A smart compiler probably optimizes away the useless bits
even when not explicitly optimizing but a dumb one isn't likely to.
m_at(x,y) was
| (levl.monsters[x][y] != 0
| && (levl.monsters[x][y] ? levl.monsters[x][y] : 0))
when
| levl.monsters[x][y]
accomplishes the same thing.
The second paragraph about Medusa is an excerpt from Bulfinch's
Mythology, but first paragraph is not. Insert an empty attribution
line between them. Also, remove the reference to Graeae from the
introduction since it contradicts Bulfinch, which describes them as
"three sisters who were gray-haired from their birth" and Gorgons as
"monstrous females with huge teeth, brazen claws, and snaky hair".
In file included from /opt/X11/include/X11/Xos.h:146:
/opt/X11/include/X11/Xarch.h:44:13: fatal error: 'sys/byteorder.h' file not found
^~~~~~~~~~~~~~~~~
1 error generated.
make[1]: *** [winX.o] Error 1
Xarch.h has an #ifdef SVR4 block.
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.
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.
Make sure CLR_MAX is defined when winprocs.h uses it.
sys/vms/vmsmail.c uses wintypes.h and winprocs.h without hack.h;
a change in 3.6.3 broke that but wasn't noticed until now.
I haven't added a fixes entry since we don't know whether 3.7.x
will eventually be buildable on VMS.
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.
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.
'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.
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.
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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.
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.