0.1.223: Start-Wegenetz aus Karten-Rohdaten (A* auf Raster), Questorte als Rueckfall

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016onJCSHLwhs9udhC9ZLzk9
This commit is contained in:
Lacustat
2026-11-01 14:07:16 +01:00
co-authored by Claude Sonnet 5.5
parent a69ac8131e
commit c045446264
6 changed files with 7924 additions and 3 deletions
+218 -1
View File
@@ -592,7 +592,7 @@ end
-- Netzpunkten), oder nil, wenn dort kein zusammenhaengender Weg bekannt ist (dann rechnet der Plan in Luftlinie).
local BLOCK = 4
local SNAP_BLOCKS = 3 -- so weit (in Bloecken) darf ein Ort vom Netz entfernt sein
local SNAP_BLOCKS = 8 -- so weit (in Bloecken zu 40 Yards) darf ein Ort vom Netz entfernt sein (die Strecke dorthin zaehlt als Luftlinie)
local MAX_EXPAND = 20000
local GRAPH_STALE = 120 -- Sekunden, die ein vorhandener Graph trotz neuer Daten weiterverwendet wird
local graphs = {}
@@ -750,6 +750,202 @@ if graphFrame then
graphFrame:SetScript("OnUpdate", function() Path.StepJobs() end)
end
------------------------------------------------------------------------
-- Start-Wegenetz: grobes Begehbarkeits-Raster aus den Karten-Rohdaten (Codex.NavSeed, 41,7 Yards je Zelle).
-- Die Wegsuche laeuft darauf (A*, 8 Nachbarn); gelaufene Zellen (db.path) gelten als bewiesen und sind etwas billiger.
------------------------------------------------------------------------
local NAV_COST = { [0] = 0, 1.0 / 1.4, 1.0, 1.4, 1.8, 2.2 } -- Kostenfaktor je Klasse (a gesperrt, b Strasse, c normal, d huegelig, e steil, f Wasser)
local NAV_WALKED = 0.8
local NAV_MAX_EXPAND = 30000
local NAV_SNAP = 6 -- Zellen, so weit darf ein Ort von begehbarem Gelaende entfernt sein
local navs = {} -- [welt] = { w, h, cell, left, top, cls = { [idx] = klasse }, walked = { [idx] = true }, rev, built, cache = {} }
local function NavLoad(world)
local n = navs[world]
if n ~= nil then return n or nil end
local src = Codex.NavSeed and Codex.NavSeed[world]
if not src then navs[world] = false return nil end
local gm = string.gmatch or string.gfind
local cls, i = {}, 0
for _, row in ipairs(src.rows) do
for letter, len in gm(row, "(%a)(%d+)") do
local c = string.byte(letter) - 97
for _ = 1, tonumber(len) do
i = i + 1
cls[i] = c
end
end
end
n = { w = src.w, h = src.h, cell = src.cell, left = src.left, top = src.top, cls = cls, walked = {}, rev = -1, built = 0, cache = {} }
navs[world] = n
return n
end
-- gelaufene Zellen auf das Raster legen (hoechstens alle GRAPH_STALE Sekunden neu, wenn es neue Daten gibt)
local function NavWalked(n, world)
local rev = Path.rev or 0
local now = Now()
if n.rev == rev or (n.built > 0 and now - n.built < GRAPH_STALE) then return end
local cells = Path.Cells(world)
local walked = {}
local floor = math.floor
for key, mask in pairs(cells or {}) do
if mask >= 65536 then
local cx, cy = floor(key / 65536) - OFFSET, key % 65536 - OFFSET
local row, col = floor((n.top - (cx * CELL + CELL / 2)) / n.cell), floor((n.left - (cy * CELL + CELL / 2)) / n.cell)
if row >= 0 and row < n.h and col >= 0 and col < n.w then walked[row * n.w + col + 1] = true end
end
end
n.walked, n.rev, n.built, n.cache = walked, rev, now, {}
end
local function NavCell(n, x, y)
local row, col = math.floor((n.top - x) / n.cell), math.floor((n.left - y) / n.cell)
return row, col
end
-- naechste begehbare Zelle zu (row, col); Rueckgabe: Index, row, col
local function NavSnap(n, row, col)
local w, h, cls = n.w, n.h, n.cls
if row >= 0 and row < h and col >= 0 and col < w and cls[row * w + col + 1] ~= 0 then return row * w + col + 1, row, col end
for r = 1, NAV_SNAP do
local best, bd, br, bc
for dr = -r, r do
for dc = -r, r do
if math.max(math.abs(dr), math.abs(dc)) == r then
local rr, cc = row + dr, col + dc
if rr >= 0 and rr < h and cc >= 0 and cc < w then
local idx = rr * w + cc + 1
if cls[idx] ~= 0 then
local d = dr * dr + dc * dc
if not bd or d < bd then best, bd, br, bc = idx, d, rr, cc end
end
end
end
end
end
if best then return best, br, bc end
end
return nil
end
local NAV_DIRS = { { 1, 0, 1 }, { -1, 0, 1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 1, 1, 1.4142 }, { 1, -1, 1.4142 }, { -1, 1, 1.4142 }, { -1, -1, 1.4142 } }
-- A* von Zelle a nach Zelle b. Rueckgabe: Liste von Zellindizes (a ... b) oder nil
local function NavSearch(n, a, b)
local w, h, cls, walked = n.w, n.h, n.cls, n.walked
local bi = b - 1
local br, bc = math.floor(bi / w), bi % w
local g, prev, closed = { [a] = 0 }, {}, {}
local heap, size = {}, 0
local function Push(k, f)
size = size + 1
local i = size
heap[i] = { k, f }
while i > 1 do
local p = math.floor(i / 2)
if heap[p][2] <= heap[i][2] then break end
heap[p], heap[i] = heap[i], heap[p]
i = p
end
end
local function Pop()
local top = heap[1]
local last = heap[size]
heap[size] = nil
size = size - 1
if size > 0 then
heap[1] = last
local i = 1
while true do
local l, r, m = i * 2, i * 2 + 1, i
if l <= size and heap[l][2] < heap[m][2] then m = l end
if r <= size and heap[r][2] < heap[m][2] then m = r end
if m == i then break end
heap[m], heap[i] = heap[i], heap[m]
i = m
end
end
return top
end
local function H(r, c) return 0.7 * math.sqrt((r - br) ^ 2 + (c - bc) ^ 2) * 1.15 end
local ai = a - 1
Push(a, H(math.floor(ai / w), ai % w))
local expanded = 0
while size > 0 do
local top = Pop()
local k = top[1]
if k == b then
local chain, c = {}, k
while c do table.insert(chain, 1, c) c = prev[c] end
return chain
end
if not closed[k] then
closed[k] = true
expanded = expanded + 1
if expanded > NAV_MAX_EXPAND then return nil end
local ki = k - 1
local r, c = math.floor(ki / w), ki % w
local gk = g[k]
for i = 1, 8 do
local d = NAV_DIRS[i]
local rr, cc = r + d[1], c + d[2]
if rr >= 0 and rr < h and cc >= 0 and cc < w then
local nk = rr * w + cc + 1
local cl = cls[nk]
if cl and cl ~= 0 and not closed[nk] then
local f = walked[nk] and NAV_WALKED or NAV_COST[cl]
if walked[nk] and cl == 5 then f = NAV_COST[5] end
local ng = gk + d[3] * f
if not g[nk] or ng < g[nk] then
g[nk] = ng
prev[nk] = k
Push(nk, ng + H(rr, cc))
end
end
end
end
end
end
return nil
end
-- Weg zwischen zwei Orten (Yards im Zellraum): Zellmitten als { x, y }, Laenge in Yards (ohne Anschluss an die Enden), Entfernung der Enden zum Netz
local function NavPath(world, ax, ay, bx, by)
local n = NavLoad(world)
if not n then return nil end
NavWalked(n, world)
local ar, ac = NavCell(n, ax, ay)
local br, bc = NavCell(n, bx, by)
local a, ar2, ac2 = NavSnap(n, ar, ac)
local b, br2, bc2 = NavSnap(n, br, bc)
if not a or not b then return nil end
local ck = a .. ":" .. b
local hit = n.cache[ck]
local chain
if hit ~= nil then
chain = hit or nil
else
chain = a == b and { a } or NavSearch(n, a, b)
n.cache[ck] = chain or false
end
if not chain then return nil end
local pts = {}
local len = 0
for i, k in ipairs(chain) do
local ki = k - 1
local r, c = math.floor(ki / n.w), ki % n.w
pts[i] = { n.top - (r + 0.5) * n.cell, n.left - (c + 0.5) * n.cell }
if i > 1 then len = len + math.sqrt((pts[i][1] - pts[i - 1][1]) ^ 2 + (pts[i][2] - pts[i - 1][2]) ^ 2) end
end
local da = math.sqrt((pts[1][1] - ax) ^ 2 + (pts[1][2] - ay) ^ 2)
local db_ = math.sqrt((pts[#pts][1] - bx) ^ 2 + (pts[#pts][2] - by) ^ 2)
return pts, len, da, db_
end
function Path.NavReady(world) return NavLoad(world) ~= nil end
-- Naechster Netzpunkt zu (x, y) in Yards. Rueckgabe: Knotenschluessel, Entfernung in Yards
local function Snap(g, x, y)
local sk = math.floor(x / 10) * 100000 + math.floor(y / 10)
@@ -876,6 +1072,8 @@ function Path.Distance(world, ax, ay, bx, by)
if type(world) ~= "number" then return nil end
local straight = math.sqrt((ax - bx) ^ 2 + (ay - by) ^ 2)
if straight < 2 * BLOCK * CELL then return nil end
local pts, len, na, nb = NavPath(world, ax, ay, bx, by)
if pts then return na + len + nb end
local g = Graph(world)
if not g then return nil end
local sa, da = Snap(g, ax, ay)
@@ -898,6 +1096,25 @@ function Path.Route(world, ax, ay, bx, by, step)
if type(world) ~= "number" then return nil end
local straight = math.sqrt((ax - bx) ^ 2 + (ay - by) ^ 2)
if straight < 2 * BLOCK * CELL then return nil end
local pts, len, na, nb = NavPath(world, ax, ay, bx, by)
if pts then
-- Ecken glaetten (gleitender Mittelwert), dann alle "step" Yards einen Punkt; Ziel zuletzt
local sm = {}
for i, p in ipairs(pts) do
if i == 1 or i == #pts then sm[i] = p
else sm[i] = { (pts[i - 1][1] + p[1] + pts[i + 1][1]) / 3, (pts[i - 1][2] + p[2] + pts[i + 1][2]) / 3 } end
end
step = step or 250
local out, last = {}, { ax, ay }
for i, p in ipairs(sm) do
if math.sqrt((p[1] - last[1]) ^ 2 + (p[2] - last[2]) ^ 2) >= step then
out[#out + 1] = p
last = p
end
end
out[#out + 1] = { bx, by }
return out, na + len + nb
end
local g = Graph(world)
if not g then return nil end
local sa = Snap(g, ax, ay)