My fixes to the travel stuck oscillation did not fix all of them,
and I've even seen a 3-step loop - which my fixes cannot detect.
I guess there could be arbitrary-sized loops too.
To definitely fix this, keep track of all the map locations travel
has moved the hero through, and if it tries to go on a location already
used, stop travel and give the unsure -message.
There were at least two cases of travel oscillation that occurred,
even after my previous fix. To fix those, the guessing routine
would also have to consider distance to original target location.
I opted not to make that part more complex - as there was
no guarantee those changes would catch all of the oscillation cases.
Instead, when we're guessing where to move, and we would actually move
back to where we came from, stop travel, and give a message.
This should fix (and fuzzing seems to confirm) all of the travel
oscillation bugs for good, and it shouldn't affect actual good travel.
(Other than the player getting a YAFM in the occasional case of trying
to travel to a location with no travel path)
There's been occasional reports (perhaps once or twice a year)
of travel getting stuck moving repeatedly between two locations
next to each other, but it has never been reproduced before.
This special level lua code fragment is a minimal test case
which triggers it:
--- special level lua fragment, indented
des.map([[
--##---
###----
#-+----
####--L
]]);
des.door("open", 2,2)
--- end of special level lua fragment
The open door is required.
Magic map the level. Start from somewhere NW of the door, and try
to travel to the lava pool. Hero will get stuck oscillating between NW
of the door and two steps west of the door.
Here are the maps of the travel[][] array values from findtravelpath()
in those two steps in the above map:
------------- -------------
| . . 2 3 | | . . 1 2 |
| 1 1 2 . | | 1 @ 1 . |
| @ . . . | | 1 . 2 . |
| 1 1 2 . | | 2 . 3 4 |
------------- -------------
There are two possible closest locations to the lava pool,
the one marked with "3" on the left map, and "4" on the right map.
Based on that alone, both would be valid places to path to.
But, in the left case, hero could not see the bottom location, so
the code won't even consider pathing to it, so it will start moving
towards the "3".
When hero moves to the second position, in the right map, now the "4"
could be seen. Now there are two possible closest locations we could
choose from. The code that scans the possible locations goes from top-left
to bottom-right, first going down (y-axis). So, the code sees the
"2" on the right. distmin() to there is 2. Good, we pick that location
to path to. Next, going down, the code considers the "4" ... which is
also equally close to the lava pool. and distmin does not consider terrain,
so ignores the door, so it has the same distmin value of "2", so the
code picks this location.
But: this was just a guess, because there's no known valid path to the
lava pool. The code loops back up to rebuild the travel[][] array
with a new starting location as the "4" from the right map, pathing
back to hero.
This is that travel array map:
-------------
| . . . . |
| 4 @ 3 . |
| 3 . 2 . |
| 3 2 1 x |
-------------
The way travelstepx and travelstepy arrays are built means that the first
location that considers the hero's location is the "3" SW of hero, so
hero will move there next. Repeat from beginning. If there was no door,
the travelsteps would reach hero's location first from SE.
(I left the travel[][] array rebuild and travelstepx/travelstrepy build
off from the other movement position, as it's not relevant)
The fix: When considering which of the two possible closest places to the
lava pool to path to, use the one with the lowest value in the travel array.
That value is the real number of moves it takes for the hero to walk there,
so the code will consistently path to the upper location, as it is "2",
instead of considering the "4" below it.
Also some minor code reorg, so it considers couldsee first instead of
later in two separate places.
Nearly same code was in two places - the only difference was
how g.context.run == 1 was handled. Previously, if you were
blind, and knew about the water or lava, you still ran into it.
Now, we always avoid the dunking if running/rushing.
Instead of returning monster's mtrapped-state, return specific
trap return values.
Add one extra trap return value, for when a monster was
moved by the trap.
In the name of accessibility: Prevent moving into dangerous liquids.
Now with themed rooms, water and lava are more common, and it's
unreasonable to expect blind players to check every step for those.
With paranoid:swim, just prevent normal walking into those liquids,
unless you prefix the movement with 'm', or if the liquid would not
harm you.
Doesn't completely prevent an accidental dunking - for example
if the hero is impaired or couldn't see the liquid.
This comes from xNetHack by copperwater <aosdict@gmail.com>
with some changes to the code.
Log game events, such as entering a new dungeon level, breaking
a conduct, or killing a unique monster, in a new "Major events"
chronicle. The entries record the turn when the event happened.
The log can be viewed with #chronicle -command, and the entries
also show up in the end-of-game dump, if that is available.
This feature is on by default, but can be disabled by
defining NO_CHRONICLE compile-time option.
This also contains "live logging", writing the events as they
happen into a single livelog-file. This is mostly useful for
public servers. The livelog is off by default, and must be
compiled in with LIVELOG, and then turned on in sysconf.
Mostly this a version of livelogging from the Hardfought server,
with some changes.
The "water" terrain (as used on the Plane of Water) behaved
strangely outside the plane. Make it behave a bit more consistently,
although it's still not really usable elsewhere.
The rationale here being it's a solid wall of water.
Firstly, disable levitation and flying (which was already done
when moving into the water on the Plane of Water), and moving into
it refers to it as a "wall of water" to make it clear it's a solid
block of water.
Bumping into an unseen wall reported "a wall" instead of "solid stone",
even though you could not know it was a wall when looking at it.
Use the same method when looking at glyphs on the map instead of
the map location type.
Fixes#318
Instead of returning 0 or 1, we'll now use ECMD_OK or ECMD_TURN.
These have the same meaning as the hardcoded numbers; ECMD_TURN
means the command uses a turn.
In future, could add eg. a flag denoting "user cancelled command"
or "command failed", and should clear eg. the cmdq.
Mostly this was simply replacing return values with the defines
in the extended commands, so hopefully I didn't break anything.
Follow up on some old groundwork. For tty, if the core has designated
a message as 'urgent', override any message suppression taking place
because of ESC typed at the --More-- prompt. Right now, "You die"
messages, feedback about having something stolen, feedback for
"amorous demon" interaction (mainly in case of armor removal), and
exploding a bag of holding are treated as urgent.
The "You die" case is already handled by a hack in top-line handling;
I left that in place so the conversion of 3 or 4 pline("You die.*")
to custompline(URGENT_MESSAGE, "You die.*") was redundant. There
are probably various non-You_die messages which precede done() which
should be marked urgent too.
Other interfaces might want to do something similar. And we ought to
implement MSGTYPE=force or MSGTYPE=urgent to allow players to indicate
other messages that they want have to override suppression. But I'm
not intending to work on either of those. I mainly wanted to force
the magic bag explosion message to be shown since a sequence of "You
put <foo> into <bag>." messages is a likely candidate for --More--ESC.
Teleporting a monster only updated the map. Give a message
so blind players can get the same information.
Making a monster invisible gives the same message, if you
cannot detect invisible.
Several other places where monsters teleported themselves
now also give the same message.
The pull request by argrath changes weight_cap() to never return a
value less than 1 because try_lift() divides by that return value
and a 0 would trigger a crash. The code involved is used when
attempting to pull a monster out of a pit via #untrap.
I'm fairly sure that weight_cap() can never produce a value that's
less than 1 already, but have put in a variation of the PR's fix.
I've also implemented a different fix that removes the division
from try_lift(). The original code seems to have gone out of its
way to avoid calculating inv_weight() twice, but doing the latter
(for the once in a hundred games where it might happen) greatly
simplifies things by removing details of carrying capacity.
Fixes#621
Reported by G7Nation; attacking walls and such with a polearm just
gave lackluster "you miss; nobody's there" feedback.
Make applying a polearm at some non-monster locations give feedback
similar to using 'F'orcefight with melee weapons. Was
|You miss; there is no one there to hit.
now
|You uselessly attack the closed door.
Also, extend the supported locations to include dungeon furniture.
Was
|You attack thin air. ('F')
now
|You harmlessly attack the throne. ('F')
|You uselessly attack the throne. ('a')
This doesn't address #613: attempting to hit non-visible locations
with an applied polearm.
Closes#614