| name | uo-navigation |
| description | Walk the headless UO character to a target (x, y), diagnose/handle blocked paths (walls, dead ends), and travel long distances via moongates. Doors are opened automatically. Use whenever the user asks to walk/go/move/navigate/travel to a place, when a `goto` reports "no path" or repeated "Walk denied" warnings, or when the user mentions a moongate. |
Navigating to a coordinate
Requires a logged-in character with the map loaded (see the uo-login skill).
1. Figure out the target (x, y)
- User gave coordinates directly — use them as-is.
- User named a static landmark or category of place ("go to a healer", "go to
the nearest bank", "go to blacksmith in moonglow") — try the optional POI data
first (loaded during
uo-login step 4; see also its own reference below):
echo "closest healer" >> /tmp/cuocmd
echo "findpoi blacksmith in moonglow" >> /tmp/cuocmd
sleep 1
tail -3 /tmp/cuolog
closest <category> [x] [y] finds the nearest match to your current position (or
an explicit coordinate); run help to see every category the currently-loaded
data actually has. findpoi <text> does a case-insensitive substring search
across POI names instead — useful for a specific named place rather than a
category. Either one prints an (x,y,z) you can hand straight to goto (see
below) or canwalk. If you get No POI data loaded. Run: loadpoi or no match,
fall back to the live scan below — POI data only covers static landmarks
(shops, banks, moongates, dungeon entrances, etc.), not mobiles or anything that
moves.
- User named a mobile or moving thing ("walk to the bull", "go to that guard")
— find its coordinates via a live scan instead:
printf 'mobiles\nitems\n' >> /tmp/cuocmd
sleep 2
tail -30 /tmp/cuolog
mobiles [range] / items [range] list nearby entities with (x,y,z). Pick the
coordinate of (or adjacent to, for a mobile you don't want to stand on top of) the
target.
- Sanity-check the destination is walkable before committing to a long walk:
echo "canwalk <x> <y>" >> /tmp/cuocmd
sleep 1
tail -8 /tmp/cuolog
walkable=False means the tile itself is a dead end (inside a wall, etc.) — pick an
adjacent tile instead.
- Optional:
path <x> <y> prints the A* route as a list of directions without moving
(budget matches goto's, 8000 nodes). In practice this dry-run buys little —
goto/travel/gonear (below) run the identical search internally and simply
don't move if it fails, so prefer calling them directly over pre-flighting with
path first.
- A POI or building-derived target may not be a standable tile at all — e.g. a
bank's coordinate often marks the building's interior/centroid, not a tile a player
can actually walk onto. Don't assume
goto failing on such a target means the
route is blocked; see gonear below, which is built for exactly this.
2. Walk there
echo "goto <x> <y>" >> /tmp/cuocmd
sleep 3
tail -10 /tmp/cuolog
Don't sleep for the whole estimated trip in one shot. A single long blind sleep
(e.g. sleep 30 for a cross-map walk) means you can't see problems until the entire
wait is over, and gives no earlier chance to notice a stuck/denied walk or a target
that needs re-pathing. Poll in short intervals instead — sleep 3–5 — and re-check
tail/pos each time, repeating until you see GoTo: arrived or a failure. This is
just as fast for a successful walk (you're not adding extra delay, just checking more
often) and much faster to notice/react to a failed one.
Success looks like:
GoTo (x,y) starting...
GoTo: arrived at (x,y)
Arrived at (x,y)
stop / stopgo cancels an in-flight goto.
Closed doors along the way are opened automatically — goto and walk both
detect a door in the next step and send the open-door macro before/after walking
into it, no separate action needed. You'll see System: Opening door... lines
appear in the log on their own during a walk; that's normal, not something to react
to. This also means goto routes straight through building interiors instead of
stopping outside them — no more manual entrance-hunting for a destination behind a
door.
Very long walks: travel chunks it for you automatically
For a trip of roughly 100+ tiles, a single goto to the final destination can fail
outright with no path ... 0 extra blocked even when the destination itself is
walkable — the A* search has a node-exploration cap (8000 nodes) and can exhaust it
searching a route that far before ever finding one, especially across varied terrain.
travel <x> <y> [hopTiles] handles this for you: it breaks the trip into a chain of
waypoints spaced hopTiles tiles apart (60 by default — pass a different value as
the third argument if you want shorter/longer hops) along the straight line to the
target, and walks each one via GoToNear (see below) in turn, so no single search
ever has to solve the whole distance at once.
echo "travel <x> <y>" >> /tmp/cuocmd
sleep 5
tail -10 /tmp/cuolog
Success looks like a series of Travel: heading toward (...) lines followed by
Travel finished at (...). This is the same technique as manually picking
intermediate goto waypoints — just automatic. stop/stopgo cancels an
in-flight travel the same way it cancels a goto (shared cancellation token).
Reach for manual waypoint-picking only if travel itself stalls on a specific hop
and you need to diagnose why (step 4 below).
When the exact tile isn't reachable: gonear
Sometimes there's no walkable tile at all at the exact (x, y) you're aiming for —
most commonly because the coordinate came from a POI or was otherwise derived from a
building (its interior, centroid, or a sign mounted on a wall you can't stand at).
goto/travel will just report no path in this case, which looks identical to a
genuinely blocked route.
gonear <x> <y> is built for this: it runs the same A* search, and if the exact
target isn't reachable, walks to whichever tile the search actually got closest to
instead of failing outright:
echo "gonear <x> <y>" >> /tmp/cuocmd
sleep 5
tail -10 /tmp/cuolog
GoToNear: (4471,1156) isn't reachable — closest reachable tile is (4473,1154), 3 tiles short
GoTo: arrived at (4473,1154)
GoToNear finished at (4473,1154) (requested 4471,1156)
travel's final hop already calls GoToNear internally, so a long trip to an
imprecise target is covered automatically — reach for gonear directly when the
target is close enough that travel's waypoint-chunking isn't needed, or you
specifically want the closest-reachable-tile fallback without breaking the trip into
hops.
3. If it fails: read the failure, don't just retry blindly
Two symptoms, same underlying cause (something between here and there is blocking):
[WARN ] Walk denied - resync to (x,y,z)
[WARN ] GoTo: no path to (tx,ty) from (x,y) — N extra blocked
GoTo (tx,ty) stopped
Important gotcha: goto's internal "blocked tiles" blacklist is local to that one
call — it is not remembered on the next goto. So if whatever was blocking you
clears (a mobile moves off the tile, etc.), you must reissue goto <tx> <ty> fresh;
it will not resume or auto-retry on its own. Conversely, blindly re-running the same
goto without anything having changed will usually just fail the same way, so check
the tile before assuming a retry will help.
Closed doors no longer cause this failure — they're opened automatically (see step 2).
What's left once that's ruled out is: a genuine wall/dead end, a locked door (the
open-door macro can't unlock one), or a transient block like a mobile standing on the
tile. Just retrying is worth one shot for the transient case, but if it fails twice,
inspect.
4. Inspect the blocking tile
Figure out roughly which tile is the chokepoint (usually the first tile in the
direction of travel from your current pos, or a gap in a wall line), then:
printf 'tiles <bx> <by>\ncanwalk <bx> <by>\n' >> /tmp/cuocmd
sleep 1
tail -12 /tmp/cuolog
This prints every static at that tile with its decoded flags, e.g.:
Static 0x0018 z=20 h=3 flags=Wall, Impassable, NoShoot, Armor impass=True wall=True
canwalk treats door-flagged statics as passable (matching goto's own routing —
see step 2), so a walkable=False result here is a genuine wall/dead end: there's
nothing to open, route around it with a different waypoint (try an adjacent tile a
step or two over, or path a nearby alternate coordinate).
If instead canwalk says walkable=True but you're still getting denied at that
tile after goto already tried its automatic door-open-and-retry (step 2), the
most likely explanation is a locked door — the open-door macro can't unlock one.
There's no direct way to confirm this from the CLI beyond noticing the pattern
(walkable tile, right at what looks like a building entrance, denies every attempt);
if so, treat it like a dead end and look for another way in, or ask the user how
they'd like to proceed (e.g. it may need a key).
5. Long-distance travel via moongates
For a trip that would otherwise mean crossing most of the map, check whether a
moongate gets you there in one hop before planning a long goto/waypoint walk.
This procedure works for any moongate — public city gates, dungeon gates, event/
faction gates, whatever — regardless of gump id or which destinations it offers.
Don't assume a fixed set of cities or a fixed gump id; always read the actual gump
the game sends and decode it fresh each time.
Finding a moongate
If POI data is loaded (see uo-login step 4 / section 1 above), the fastest way to
locate one is closest moongate — moongate is one of the standard POI categories
in poi/common.json/poi/dungeons.json. That gives you an (x,y) to goto, but
not the item serial use needs — you still have to items scan once you arrive
(step 2 below) to get that.
Otherwise, or to confirm what's actually on the ground: moongates are ground items
with graphic 0x0F6C ("blue moongate"), though colored variants (red gates, etc.)
exist with different graphics but the same interaction model. Search near a
suspected location:
echo "items 20" >> /tmp/cuocmd
sleep 2
tail -30 /tmp/cuolog
Look for a line like:
[401F819D] Graphic:0F6C Amt:1 at (3725,2498,9) Hue:0000 Movable:? "blue moongate"
Note its serial (401F819D here) and (x,y,z).
1. Walk to within one tile of it
You must be standing on or adjacent to the moongate — double-clicking it from
range does nothing. Use the normal goto flow (sections 1-2 above) to reach the
moongate's exact (x,y) or a neighboring tile:
echo "canwalk <gate_x> <gate_y>" >> /tmp/cuocmd
sleep 1
echo "goto <gate_x> <gate_y>" >> /tmp/cuocmd
2. Open the travel menu
Double-click (use) the moongate's serial:
echo "use <gate_serial>" >> /tmp/cuocmd
sleep 2
tail -20 /tmp/cuolog
This logs a [GUMP] Compressed gump serial=... gumpId=<gump_id> layout=... line
followed by a [GUMP] Compressed gump text lines: [...] line listing the option
names, then:
[INFO ] [GUMP] Unrecognized compressed gump <gump_id> — holding for manual response. Use: gumpresponse <serial> <gumpid> <button>
Any gump id the client doesn't specifically recognize (resurrection prompts, MOTD,
etc. are the only ones it auto-answers) is held open instead of auto-dismissed, so
you have time to read it and respond — this isn't moongate-specific. The <serial>
in that log line is a fresh id every time you open the gump — always copy the one
from the most recent use, never reuse an older one.
3. Decode which button picks which destination
Don't rely on a memorized table of destination→button mappings — different gumps
(and possibly different servers/shards) can order or number things differently.
Decode it from the log output you just got, every time:
-
Get the plain option names, in order, from the [GUMP] ... text lines: [...]
line. Each entry looks like <BASEFONT COLOR=#xxxxxx><BIG>Name</BIG></BASEFONT>
— strip the markup to get Name. The first one or two lines are usually the
gump's own title (e.g. "Moongate Travel Menu") — skip those. The remaining lines
normally come in consecutive duplicate pairs (same name, two different colors
— one for the unselected state, one for hover/selected) or a single mono-color
line per option on simpler gumps. Collapse consecutive duplicates into one entry
per option, keeping order — this gives you an ordered list of option names.
-
Get the button ids, in order, from the [GUMP] ... layout=... line. Every
clickable option shows up as a { button <x> <y> <upGumpId> <downGumpId> 1 0 <button_id> } fragment. Scan the layout left-to-right and pull out the
<button_id> (the last number) from each button fragment, in the order they
appear. Some layouts include a second redundant set of buttons later in the
string (e.g. a geographic map overlay mirroring the same options spatially) — if
the button count is a multiple of the option count, just take the first N
buttons, where N = the number of options from step 1; in every case observed so
far, the first block matches the text order 1:1.
-
Zip the two ordered lists together — the Nth option name pairs with the Nth
button id. Find the option whose name matches (case-insensitive) what the user
asked for, and take its button id.
If a gump doesn't fit this "colored text pairs + list of buttons" shape at all (rare
— most menu-style gumps like this do), fall back to matching by screen position: each
option's htmlgump text coordinates and its button coordinates in the layout are
usually close together (within a few pixels), so you can pair them up by proximity
instead of pure ordering.
4. Send the response
echo "gumpresponse <gump_serial> <gump_id> <button_id>" >> /tmp/cuocmd
sleep 3
echo "pos" >> /tmp/cuocmd
sleep 1
tail -8 /tmp/cuolog
A successful travel shows a large jump in pos — the destination's moongate
coordinates — rather than the small deltas a normal walk produces. Note that
gumpresponse takes the gump's serial (from the [GUMP] log lines, a
player/session-scoped id like 00092D19), not the moongate item's serial
(401F819D-style) used for use — these are different serials.
Coming back / hopping again
There's often another moongate within a step or two of where you land (city
moongates commonly cluster near each other, but don't assume — re-check with
items 10). Repeat steps 1-4 for the next hop; you'll get a fresh gump serial (and
potentially a different gump id or option set) every time, so re-decode rather than
reusing anything from the previous hop.