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
+12 -1
View File
@@ -4039,8 +4039,19 @@ function Codex.CollectPoints(spec)
end
elseif kind == "quests" then
local q = Codex.V().quests[id]
local sg, sturn
if Codex.QuestSpot then sg, sturn = Codex.QuestSpot(id) end
if not q and (sg or sturn) then
for _, def in ipairs({ { sg, L.CODEX_TAG_GIVE, "give" }, { sturn, L.CODEX_TAG_TURN, "turn" } }) do
local ref = def[1]
if ref then
points[#points + 1] = { m = ref.m, x = ref.x, y = ref.y, n = 4, pin = true, tag = def[3],
name = string.format(def[2], ref.n or "?") }
end
end
end
if q then
for _, def in ipairs({ { q.give, L.CODEX_TAG_GIVE, "give" }, { q.turn, L.CODEX_TAG_TURN, "turn" } }) do
for _, def in ipairs({ { q.give or sg, L.CODEX_TAG_GIVE, "give" }, { q.turn or sturn, L.CODEX_TAG_TURN, "turn" } }) do
local ref = def[1]
if ref and ref.m and ref.x and ref.y then
points[#points + 1] = { m = ref.m, x = ref.x, y = ref.y, n = 4, pin = true, tag = def[3],
+1263
View File
File diff suppressed because it is too large Load Diff
+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)
+6398
View File
File diff suppressed because it is too large Load Diff
+30
View File
@@ -706,3 +706,33 @@ function Seed.Count()
end
return n(S.q), n(S.u), n(S.o), n(S.i)
end
------------------------------------------------------------------------
-- Orte von Geber und Abgabe aus der mitgelieferten Liste (Codex.QuestSeed, nur Rueckfall: eigene Daten gehen vor)
------------------------------------------------------------------------
local spotCache = {}
local function SpotRef(a, b)
local m, x, y = tonumber(a[2]), tonumber(a[3]), tonumber(a[4])
if not m or not x or not y then return nil end
return { n = (a[1] ~= "" and a[1]) or nil, m = m, x = x / 1000, y = y / 1000, seed = true }
end
-- Rueckgabe: Geber, Abgabe (je { n, m, x, y } oder nil)
function Codex.QuestSpot(id)
local c = spotCache[id]
if c == nil then
local raw = Codex.QuestSeed and Codex.QuestSeed[id]
if raw then
local f = {}
for part in string.gfind and string.gfind(raw .. "|", "([^|]*)|") or string.gmatch(raw .. "|", "([^|]*)|") do f[#f + 1] = part end
c = { SpotRef({ f[1], f[2], f[3], f[4] }, nil), SpotRef({ f[5], f[6], f[7], f[8] }, nil) }
c.n = true
else
c = false
end
spotCache[id] = c
end
if not c then return nil, nil end
return c[1], c[2]
end
+3 -1
View File
@@ -3,7 +3,7 @@
## Notes: Learns quests, creatures and drops while you play: who gives and takes a quest, rewards, objectives, drop chances.
## Notes-deDE: Lernt beim Spielen Quests, Kreaturen und Beute: wer eine Quest vergibt und annimmt, Belohnungen, Ziele, Drop-Chancen.
## Author: Lacustat
## Version: 0.1.222
## Version: 0.1.223
## Dependencies: Freewood-OctoWoW-Core
## SavedVariablesPerCharacter: FreewoodSnapshot
## OptionalDeps: Freewood-OctoWoW-CodexData, Freewood-OctoWoW-ForeverData
@@ -25,6 +25,8 @@ CodexMapAPI.lua
CodexPathMap.lua
CodexTaxi.lua
CodexTaxiSeed.lua
CodexNavSeed.lua
CodexQuestSeed.lua
CodexTaxiUI.lua
CodexTransport.lua
CodexSpots.lua