Files
2026-10-27 14:08:44 +00:00

258 lines
9.2 KiB
Lua

-- Freewood-OctoWoW-Codex: Kartenschnittstelle fuer die Weltkarte des 1.12-Clients (OctoWoW).
-- Die alte Karte kennt weder SetMapID noch GetMapID noch eine Kartenflaeche mit Zoom. Stattdessen:
-- Kartenflaeche = WorldMapDetailFrame (Anteile 0..1 von links oben), Zone umschalten per SetMapZoom(Kontinent, Zone).
-- Die Zuordnung Karten-ID (AreaTable) -> Kontinent/Zone laeuft ueber den Zonennamen (C_Map.GetMapInfo gegen GetMapZones)
-- und wird zusaetzlich beim Spielen gelernt (stehst du in einer Zone, merkt sich Codex ihre Nummer).
-- Auf Clients mit Retail-Karte (SetMapID/GetMapID) werden diese Funktionen direkt benutzt.
local ADDON, NS = ...
local FW = _G.Freewood
local Codex = NS and NS.Codex
if not FW or not Codex then
return
end
local API = {}
Codex.MapAPI = API
local resolved = {} -- [mapID] = "c:z" oder false (nicht gefunden)
local zoneToID = {} -- ["c:z"] = mapID
local function Learned()
local st = Codex.db and Codex.db.settings
if not st then return {} end
st.mapZones = st.mapZones or {}
return st.mapZones
end
local function Norm(s)
if type(s) ~= "string" then return nil end
s = string.gsub(string.lower(s), "[^%w]", "")
if s == "" then return nil end
return s
end
function API.Classic()
local wm = _G.WorldMapFrame
return not (wm and wm.GetMapID and wm.SetMapID)
end
function API.Canvas()
local wm = _G.WorldMapFrame
if not wm then return nil end
if not API.Classic() then
if wm.GetCanvas then
local ok, c = pcall(wm.GetCanvas, wm)
if ok and c then return c end
end
return wm.ScrollContainer and wm.ScrollContainer.Child or nil
end
return _G.WorldMapDetailFrame or _G.WorldMapButton
end
-- Kontinent/Zone der angezeigten Karte
function API.Current()
if not (_G.GetCurrentMapContinent and _G.GetCurrentMapZone) then return nil end
local c, z = _G.GetCurrentMapContinent(), _G.GetCurrentMapZone()
if type(c) ~= "number" or type(z) ~= "number" then return nil end
return c, z
end
function API.Signature()
local c, z = API.Current()
return c and (c .. ":" .. z) or "?"
end
local function MapName(id)
local CM = _G.C_Map
if CM and CM.GetMapInfo then
local ok, info = pcall(CM.GetMapInfo, id)
if ok and type(info) == "table" and type(info.name) == "string" then return info.name end
end
if _G.C_Map and _G.C_Map.GetAreaInfo then
local ok, name = pcall(_G.C_Map.GetAreaInfo, id)
if ok and type(name) == "string" then return name end
end
return nil
end
-- Karten-ID -> Kontinent, Zone (Zone 0 = Kontinentkarte)
function API.Resolve(id)
if type(id) ~= "number" then return nil end
local key = Learned()[id] or resolved[id]
if key == nil then
key = false
local want = Norm(MapName(id))
if want and _G.GetMapContinents and _G.GetMapZones then
local conts = { _G.GetMapContinents() }
for c = 1, #conts do
if Norm(conts[c]) == want then key = c .. ":0" break end
local zones = { _G.GetMapZones(c) }
for z = 1, #zones do
if Norm(zones[z]) == want then key = c .. ":" .. z break end
end
if key then break end
end
end
resolved[id] = key
end
if not key then return nil end
local _, _, c, z = string.find(key, "^(%d+):(%d+)$")
return tonumber(c), tonumber(z)
end
-- Karten-ID der angezeigten Karte. cands: Liste oder Funktion, die Karten-IDs liefert, unter denen gesucht wird.
function API.CurrentMapID(cands)
local wm = _G.WorldMapFrame
if not API.Classic() then
if wm and wm.GetMapID then
local ok, id = pcall(wm.GetMapID, wm)
if ok then return id end
end
return nil
end
local c, z = API.Current()
if not c then return nil end
local key = c .. ":" .. z
if zoneToID[key] then return zoneToID[key] end
for id, k in pairs(Learned()) do
if k == key then zoneToID[key] = id return id end
end
if type(cands) == "function" then cands = cands() end
local list = cands or {}
local bm = _G.C_Map and _G.C_Map.GetBestMapForUnit and select(2, pcall(_G.C_Map.GetBestMapForUnit, "player"))
if type(bm) == "number" then list[#list + 1] = bm end
for i = 1, #list do
local id = list[i]
local rc, rz = API.Resolve(id)
if rc == c and rz == z then
zoneToID[key] = id
return id
end
end
-- Zone ohne bekannte Daten (und noch nie betreten): Karten-ID ueber den Zonennamen suchen.
-- Quelle: zuerst der Karten-Scan (db.cat.m), dann alle Nummern 1..8000 durchprobieren. Ergebnis wird gemerkt (auch dauerhaft),
-- ein Fehlschlag fuer 60 Sekunden.
local now = _G.GetTime and _G.GetTime() or 0
if API.noMatch and API.noMatch[key] and now - API.noMatch[key] < 60 then return nil end
local zname
if _G.GetMapContinents and _G.GetMapZones then
if z == 0 then zname = ({ _G.GetMapContinents() })[c] else zname = ({ _G.GetMapZones(c) })[z] end
end
local target = Norm(zname)
if target then
local ids = {}
local m = Codex.db and Codex.db.cat and Codex.db.cat.m
-- erst die Karten aus dem Scan (schnell), dann der ganze Bereich: der Scan kennt nicht jede Karte (z. B. Blackstone Island)
local seen = {}
if type(m) == "table" then
for id in pairs(m) do if type(id) == "number" then ids[#ids + 1] = id; seen[id] = true end end
table.sort(ids)
end
for id = 1, 8000 do if not seen[id] then ids[#ids + 1] = id end end
for i = 1, #ids do
local id = ids[i]
if Norm(MapName(id)) == target then
zoneToID[key] = id
resolved[id] = key
Learned()[id] = key
return id
end
end
end
API.noMatch = API.noMatch or {}
API.noMatch[key] = now
return nil
end
-- Blizzards Kartenfenster (Titel, Auswahlfelder) nachziehen, falls es die Umschaltung nicht selbst tut
function API.RefreshWindow(c, z)
local names = { "WorldMapFrame_Update", "WorldMapContinentDropDown_Update", "WorldMapZoneDropDown_Update", "WorldMapZoomOutButton_Update" }
for i = 1, #names do
local f = _G[names[i]]
if type(f) == "function" then pcall(f) end
end
local title = _G.WorldMapFrameTitle or _G.WorldMapFrameTitleText
if title and title.SetText and _G.GetMapZones and _G.GetMapContinents then
local name
if z == 0 then
local conts = { _G.GetMapContinents() }
name = conts[c]
else
local zones = { _G.GetMapZones(c) }
name = zones[z]
end
if name then pcall(title.SetText, title, name) end
end
end
function API.SetMap(id)
local wm = _G.WorldMapFrame
if not API.Classic() then
if wm and wm.SetMapID then pcall(wm.SetMapID, wm, id) return true end
return false
end
local c, z = API.Resolve(id)
if not c or not _G.SetMapZoom then return false end
if z == 0 then pcall(_G.SetMapZoom, c) else pcall(_G.SetMapZoom, c, z) end
API.RefreshWindow(c, z)
if _G.C_Timer and _G.C_Timer.After then
_G.C_Timer.After(0.2, function() API.RefreshWindow(c, z) end)
end
return true
end
-- Mauszeiger auf der Kartenflaeche als Anteil 0..1 (links oben), sonst nil
function API.CursorNorm()
local canvas = API.Canvas()
if not (canvas and canvas.IsVisible and canvas:IsVisible()) then return nil end
local l, t, w, h = canvas:GetLeft(), canvas:GetTop(), canvas:GetWidth(), canvas:GetHeight()
if not (l and t and w and h and w > 1 and h > 1) then return nil end
local cx, cy = _G.GetCursorPosition()
local s = canvas:GetEffectiveScale()
if not (cx and s and s > 0) then return nil end
local x, y = (cx / s - l) / w, (t - cy / s) / h
if x < 0 or x > 1 or y < 0 or y > 1 then return nil end
return x, y
end
-- Zonen-Nummern lernen: Karte kurz auf die aktuelle Zone stellen, solange das Kartenfenster zu ist
local function LearnHere()
local wm = _G.WorldMapFrame
if not API.Classic() or (wm and wm:IsShown()) then return end
if not (_G.SetMapToCurrentZone and _G.C_Map and _G.C_Map.GetBestMapForUnit) then return end
local ok, id = pcall(_G.C_Map.GetBestMapForUnit, "player")
if not ok or type(id) ~= "number" then return end
pcall(_G.SetMapToCurrentZone)
local c, z = API.Current()
if c and z and z > 0 then
local L = Learned()
L[id] = c .. ":" .. z
zoneToID[c .. ":" .. z] = id
end
end
local function LearnLater()
if _G.C_Timer and _G.C_Timer.After then
_G.C_Timer.After(1.5, LearnHere)
else
LearnHere()
end
end
local f = CreateFrame("Frame")
f:RegisterEvent("PLAYER_ENTERING_WORLD")
f:RegisterEvent("ZONE_CHANGED_NEW_AREA")
f:SetScript("OnEvent", LearnLater)
-- /fwcodex mapzones: zeigt die Zuordnung fuer die angezeigte Karte
function API.Debug()
local c, z = API.Current()
FW:Print("Karte: Kontinent " .. tostring(c) .. ", Zone " .. tostring(z) .. ", Flaeche "
.. tostring(API.Canvas() and API.Canvas():GetName()) .. ", klassisch: " .. tostring(API.Classic()))
local n = 0
for id, k in pairs(Learned()) do n = n + 1 end
FW:Print("Gelernte Zonen: " .. n)
end