-- Freewood-OctoWoW-Codex: Vorlage (Startdaten aus Freewood-OctoWoW-CodexData, Vanilla) -- Das Datenaddon ist "LoadOnDemand" und wird erst beim ersten Zugriff geladen. Alles hier ist nur eine Vorlage: -- eigene und geteilte Live-Daten gehen immer vor. Die Daten liegen als kurze Zeichenketten vor und werden erst -- beim Zugriff entpackt (und gemerkt). -- -- Formate (siehe tools/pf2seed.lua): -- Quest "Stufe|Mindeststufe|Rassen|Klassen|Beruf|Vorquests|Start|Abgabe|Ziele" (Refs: "U123.O55.I6497") -- Kreatur "Stufe|Fraktion|Rang|Gebiet:Zelle,Zelle;Gebiet:Zelle" (Zelle = x*100+y in 1 %) -- Item "u=,...;o=,...;v,..." -- Rollen meta[rolle] = "id:wert,id:wert" (negative IDs sind Objekte) local ADDON, NS = ... local FW = _G.Freewood local Codex = NS.Codex if not FW or not Codex then return end local Util = FW.Util local GRID = Codex.GRID local Seed = {} Codex.Seed = Seed local DATA_ADDON = "Freewood-OctoWoW-CodexData" local S -- FreewoodCodexSeed, sobald geladen local failed = false local floor = math.floor -- Rollen: Reihenfolge der Anzeige Seed.ROLES = { units = { "vendor", "repair", "flight", "innkeeper", "stablemaster", "spirithealer", "auctioneer", "battlemaster", "banker", "rares" }, objects = { "mailbox", "meetingstone", "chests", "herbs", "mines", "fish" }, } ------------------------------------------------------------------------ -- Laden ------------------------------------------------------------------------ function Seed.Load() if S then return true end if failed then return false end local g = _G.FreewoodCodexSeed if not (g and g.ready) then local load = (C_AddOns and C_AddOns.LoadAddOn) or LoadAddOn if type(load) == "function" then pcall(load, DATA_ADDON) end g = _G.FreewoodCodexSeed end if g and g.ready then S = g return true end failed = true return false end -- Laedt bei Bedarf (nur die Oberflaeche ruft das auf, damit der Ladevorgang beim Oeffnen des Tabs geschieht) function Seed.Disabled() local db = Codex.db return db and db.settings and db.settings.noSeed == true or false end function Seed.Available() if Seed.Disabled() then return false end return Seed.Load() end -- Nur pruefen, ob schon geladen ist, ohne zu laden (fuer Code, der waehrend des Spielens laeuft) function Seed.Ready() return S ~= nil and not Seed.Disabled() end -- fuer Tests: Zustand zuruecksetzen local caches function Seed.Reset() S, failed = nil, false caches = { quest = {}, unit = {}, object = {}, item = {}, lists = {}, hay = {} } Seed.nextIdx, Seed.rev, Seed.roleIdx, Seed.areaMap = nil, nil, nil, nil end Seed.Reset() local function Lang() return Codex.Lang() end ------------------------------------------------------------------------ -- Kleine Helfer ------------------------------------------------------------------------ local function Fields(s, n) local out, pos = {}, 1 for i = 1, n do local e = s:find("|", pos, true) if e then out[i] = s:sub(pos, e - 1) pos = e + 1 else out[i] = s:sub(pos) pos = #s + 2 end end return out end local function Nums(s) local list = {} for n in (s or ""):gmatch("%d+") do list[#list + 1] = tonumber(n) end return list end local KIND_OF = { U = "units", O = "objects", I = "items" } local function Refs(s) local list = {} for k, id in (s or ""):gmatch("(%a)(%d+)") do list[#list + 1] = { kind = KIND_OF[k], id = tonumber(id) } end return list end local function AreaMap() if Seed.areaMap then return Seed.areaMap end local map = {} for a, m in (S.areas or ""):gmatch("(%d+)=(%d+)") do map[tonumber(a)] = tonumber(m) end Seed.areaMap = map return map end local function Pick(de, en) if Lang() == "en" then return en or de end return de or en end ------------------------------------------------------------------------ -- Quests ------------------------------------------------------------------------ function Seed.Quest(id) local c = caches.quest[id] if c ~= nil then return c or nil end if not Seed.Load() then return nil end local s = S.q[id] if not s then caches.quest[id] = false return nil end local f = Fields(s, 9) local q = { id = id, lvl = tonumber(f[1]), min = tonumber(f[2]), race = tonumber(f[3]), class = tonumber(f[4]), skill = tonumber(f[5]), pre = Nums(f[6]), start = Refs(f[7]), stop = Refs(f[8]), obj = Refs(f[9]), } caches.quest[id] = q return q end function Seed.QuestTitle(id) if not Seed.Load() then return nil end return Pick(S.qd[id], S.qe[id]) end -- Folgequests: alle Quests, die diese als Vorquest haben function Seed.NextQuests(id) if not Seed.Load() then return {} end if not Seed.nextIdx then local idx = {} for qid, s in pairs(S.q) do local pre = Fields(s, 6)[6] for p in pre:gmatch("%d+") do local n = tonumber(p) local l = idx[n] if not l then l = {} idx[n] = l end l[#l + 1] = qid end end for _, l in pairs(idx) do table.sort(l) end Seed.nextIdx = idx end return Seed.nextIdx[id] or {} end -- Position in der Questkette: pos, total (zum Beispiel 2, 4), nil ausserhalb einer Kette. -- Bei Verzweigungen zaehlt der laengste Weg vor und nach der Quest. local chainMemo = {} local function ChainLen(id, dir, depth) local key = dir .. id local c = chainMemo[key] if c then return c end if depth > 40 then return 1 end local best = 0 local list if dir == "u" then local q = Seed.Quest(id) list = q and q.pre or {} else list = Seed.NextQuests(id) end for _, other in ipairs(list) do if other ~= id then best = math.max(best, ChainLen(other, dir, depth + 1)) end end chainMemo[key] = best + 1 return best + 1 end function Seed.ChainPos(id) if not Seed.Quest(id) then return nil end local up, down = ChainLen(id, "u", 0), ChainLen(id, "d", 0) if up + down <= 2 then return nil end return up, up + down - 1 end -- Stellung in einer Questkette: "start" (hat Folgequests, keine Vorquest), "mid", "end" (nur Vorquests) -- oder nil (gehoert zu keiner Kette) function Seed.ChainRole(id) local q = Seed.Quest(id) if not q then return nil end -- manche Quests nennen sich selbst als Vorquest: das zaehlt nicht als Kette local hasPre, hasNext = false, false for _, p in ipairs(q.pre or {}) do if p ~= id then hasPre = true break end end for _, n in ipairs(Seed.NextQuests(id)) do if n ~= id then hasNext = true break end end if hasPre and hasNext then return "mid" end if hasNext then return "start" end if hasPre then return "end" end return nil end ------------------------------------------------------------------------ -- Kreaturen und Objekte ------------------------------------------------------------------------ local function EntityData(kind) return kind == "units" and S.u or S.o end -- Rueckgabe: { id, l1, l2, fac, rank, spots, cells } function Seed.Entity(kind, id) local cache = kind == "units" and caches.unit or caches.object local c = cache[id] if c ~= nil then return c or nil end if not Seed.Load() then return nil end local s = EntityData(kind)[id] if not s then cache[id] = false return nil end local f = Fields(s, 4) local l1, l2 = f[1]:match("^(%d+)%-?(%d*)$") if l1 and l2 == "" then l2 = l1 end local e = { id = id, l1 = tonumber(l1), l2 = tonumber(l2), fac = f[2] ~= "" and f[2] or nil, rank = tonumber(f[3]), spots = f[4], } cache[id] = e return e end function Seed.Name(kind, id) if not Seed.Load() then return nil end if kind == "units" then return Pick(S.ud[id], S.ue[id]) elseif kind == "objects" then return Pick(S.od[id], S.oe[id]) elseif kind == "items" then return Pick(S.id[id], S.ie[id]) end return nil end -- Zellen wie im Codex: { [Karte*GRID^2 + y*GRID + x] = 1 } function Seed.Cells(kind, id) local e = Seed.Entity(kind, id) if not e then return {} end if e.cells then return e.cells end local cells = {} local areas = AreaMap() for area, list in e.spots:gmatch("(%d+):([%d,]+)") do local m = areas[tonumber(area)] if m then for c in list:gmatch("%d+") do c = tonumber(c) local cx = math.min(GRID - 1, floor(c / 100) * 2 + 1) local cy = math.min(GRID - 1, (c % 100) * 2 + 1) cells[m * GRID * GRID + cy * GRID + cx] = 1 end end end e.cells = cells return cells end -- Erste Fundstelle: Karte, x, y (Anteile 0..1) function Seed.FirstSpot(kind, id) local e = Seed.Entity(kind, id) if not e then return nil end local area, list = e.spots:match("(%d+):([%d,]+)") local m = area and AreaMap()[tonumber(area)] local c = list and tonumber(list:match("%d+")) if not (m and c) then return nil end return m, (floor(c / 100) + 0.5) / 100, ((c % 100) + 0.5) / 100 end -- Gebiet einer Kreatur / eines Objekts (Karten-ID der ersten Fundstelle) function Seed.MapOf(kind, id) local m = Seed.FirstSpot(kind, id) return m end -- Kleinster Abstand (Kartenanteil) einer Zellenmenge zum Spieler auf der aktuellen Karte, sonst nil function Seed.Distance(cells, loc) loc = loc or (Util.GetPlayerLocation and Util.GetPlayerLocation()) if not (loc and loc.mapID and loc.x and loc.y) then return nil end local best local g2 = GRID * GRID for key in pairs(cells or {}) do if floor(key / g2) == loc.mapID then local idx = key % g2 local dx = ((idx % GRID) + 0.5) / GRID - loc.x local dy = (floor(idx / GRID) + 0.5) / GRID - loc.y local d = dx * dx + dy * dy if not best or d < best then best = d end end end return best and math.sqrt(best) or nil end ------------------------------------------------------------------------ -- Items: Quellen ------------------------------------------------------------------------ -- Rueckgabe: { u = { { id, chance } }, o = { { id, chance } }, v = { haendlerID } } function Seed.Item(id) local c = caches.item[id] if c ~= nil then return c or nil end if not Seed.Load() then return nil end local s = S.i[id] if not s then caches.item[id] = false return nil end local it = { u = {}, o = {}, v = {} } for part in s:gmatch("[^;]+") do local tag, body = part:sub(1, 1), part:sub(2) if tag == "u" or tag == "o" then for k, v in body:gmatch("(%d+)=([%d%.]+)") do local t = it[tag] t[#t + 1] = { id = tonumber(k), chance = tonumber(v) } end elseif tag == "v" then it.v = Nums(body) end end caches.item[id] = it return it end function Seed.ItemHasVendor(id) if not Seed.Load() then return false end local s = S.i[id] return s ~= nil and (s:find(";v", 1, true) ~= nil or s:sub(1, 1) == "v") end local function Reverse() if Seed.rev then return Seed.rev end -- flache Listen { Item, Chance, Item, Chance ... } sparen Speicher local rev = { u = {}, o = {}, v = {} } for itemID, s in pairs(S.i) do for part in s:gmatch("[^;]+") do local tag, body = part:sub(1, 1), part:sub(2) if tag == "u" or tag == "o" then local t = rev[tag] for k, v in body:gmatch("(%d+)=([%d%.]+)") do local key = tonumber(k) local l = t[key] if not l then l = {} t[key] = l end l[#l + 1] = itemID l[#l + 1] = tonumber(v) end elseif tag == "v" then for n in body:gmatch("%d+") do local key = tonumber(n) local l = rev.v[key] if not l then l = {} rev.v[key] = l end l[#l + 1] = itemID end end end end Seed.rev = rev return rev end -- Beute einer Kreatur / eines Objekts laut Vorlage: { { item, chance (Prozent) } }, nach Chance sortiert function Seed.Drops(kind, id) if not Seed.Load() then return {} end local flat = Reverse()[kind == "units" and "u" or "o"][id] local list = {} for i = 1, #(flat or {}), 2 do list[#list + 1] = { item = flat[i], chance = flat[i + 1] } end table.sort(list, function(a, b) if a.chance ~= b.chance then return a.chance > b.chance end return a.item < b.item end) return list end -- Waren eines Haendlers: { itemID } function Seed.VendorItems(id) if not Seed.Load() then return {} end local list = {} for i, itemID in ipairs(Reverse().v[id] or {}) do list[i] = itemID end table.sort(list) return list end ------------------------------------------------------------------------ -- Quests je Kreatur / Objekt (Geber, Abgabe, Ziel) ------------------------------------------------------------------------ function Seed.QuestsOf(kind, id) if not Seed.Load() then return {}, {}, {} end if not Seed.questRev then local rev = { give = {}, turn = {}, target = {} } local function Add(t, key, qid) local l = t[key] if not l then l = {} t[key] = l end if l[#l] ~= qid then l[#l + 1] = qid end end for qid in pairs(S.q) do local q = Seed.Quest(qid) for _, r in ipairs(q.start) do if r.kind ~= "items" then Add(rev.give, r.kind .. r.id, qid) end end for _, r in ipairs(q.stop) do if r.kind ~= "items" then Add(rev.turn, r.kind .. r.id, qid) end end for _, r in ipairs(q.obj) do if r.kind ~= "items" then Add(rev.target, r.kind .. r.id, qid) end end end Seed.questRev = rev end local key = kind .. id local rev = Seed.questRev return rev.give[key] or {}, rev.turn[key] or {}, rev.target[key] or {} end ------------------------------------------------------------------------ -- Rollen (Haendler, Flugmeister ...) ------------------------------------------------------------------------ local function RoleIndex() if Seed.roleIdx then return Seed.roleIdx end local idx = { units = {}, objects = {}, ids = {} } for _, group in ipairs({ Seed.ROLES.units, Seed.ROLES.objects }) do for _, role in ipairs(group) do local list = {} for k, v in (S.meta[role] or ""):gmatch("(%-?%d+):([^,]*)") do local n = tonumber(k) local kind = n > 0 and "units" or "objects" n = math.abs(n) local t = idx[kind][n] if not t then t = {} idx[kind][n] = t end t[role] = v ~= "" and v or true list[#list + 1] = { kind = kind, id = n } end idx.ids[role] = list end end Seed.roleIdx = idx return idx end -- Rollen einer Kreatur / eines Objekts: { vendor = "H", herbs = 280, ... } oder nil function Seed.Roles(kind, id) if not Seed.Load() then return nil end return RoleIndex()[kind][id] end -- Rollen als Liste in fester Reihenfolge function Seed.RoleList(kind, id) local roles = Seed.Roles(kind, id) if not roles then return {} end local list = {} for _, role in ipairs(Seed.ROLES[kind]) do if roles[role] then list[#list + 1] = role end end return list end function Seed.RoleIDs(role) if not Seed.Load() then return {} end return RoleIndex().ids[role] or {} end -- Naechste Kreaturen / Objekte mit einer Rolle auf der aktuellen Karte: { { kind, id, dist, m, x, y } } function Seed.Nearest(role, limit, faction) local loc = Util.GetPlayerLocation and Util.GetPlayerLocation() local list = {} if not (loc and loc.mapID and loc.x and loc.y) or not Seed.Load() then return list end local side = faction or (UnitFactionGroup and select(1, UnitFactionGroup("player"))) or nil local letter = side == "Alliance" and "A" or side == "Horde" and "H" or nil for _, ref in ipairs(Seed.RoleIDs(role)) do local e = Seed.Entity(ref.kind, ref.id) local roles = Seed.Roles(ref.kind, ref.id) local value = roles and roles[role] local fac = (type(value) == "string" and value:match("^[AH]+$")) or (e and e.fac) if e and (not letter or not fac or fac:find(letter, 1, true)) then local d = Seed.Distance(Seed.Cells(ref.kind, ref.id)) if d then local best, bm, bx, by local g2 = GRID * GRID for key in pairs(Seed.Cells(ref.kind, ref.id)) do if floor(key / g2) == loc.mapID then local idx = key % g2 local x, y = ((idx % GRID) + 0.5) / GRID, (floor(idx / GRID) + 0.5) / GRID local dd = (x - loc.x) ^ 2 + (y - loc.y) ^ 2 if not best or dd < best then best, bm, bx, by = dd, loc.mapID, x, y end end end list[#list + 1] = { kind = ref.kind, id = ref.id, dist = d, m = bm, x = bx, y = by } end end end table.sort(list, function(a, b) if a.dist ~= b.dist then return a.dist < b.dist end return a.id < b.id end) if limit then for i = #list, limit + 1, -1 do list[i] = nil end end return list end ------------------------------------------------------------------------ -- Listen fuer die Oberflaeche ------------------------------------------------------------------------ -- Alle IDs einer Art (sortiert) function Seed.IDs(kind) if not Seed.Load() then return {} end local c = caches.lists[kind] if c then return c end local src if kind == "quests" then src = S.q elseif kind == "units" then src = S.u elseif kind == "objects" then src = S.o elseif kind == "items" then src = S.ie end local list = {} for id in pairs(src or {}) do list[#list + 1] = id end if kind == "items" then for id in pairs(S.i) do if not S.ie[id] then list[#list + 1] = id end end end table.sort(list) caches.lists[kind] = list return list end -- Kleingeschriebene Namen (deutsch und englisch) zur Suche function Seed.Haystack(kind, id) local c = caches.hay[kind] if not c then c = {} caches.hay[kind] = c end local h = c[id] if h then return h end local de, en if kind == "quests" then de, en = S.qd[id], S.qe[id] elseif kind == "units" then de, en = S.ud[id], S.ue[id] elseif kind == "objects" then de, en = S.od[id], S.oe[id] else de, en = S.id[id], S.ie[id] end h = ((de or "") .. "\n" .. (en or "")):lower() c[id] = h return h end function Seed.Count() if not Seed.Load() then return 0, 0, 0, 0 end local function n(t) local c = 0 for _ in pairs(t) do c = c + 1 end return c end return n(S.q), n(S.u), n(S.o), n(S.i) end