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

329 lines
11 KiB
Lua

-- Freewood-OctoWoW-Codex: Kreaturen ueber ihre ID nachschlagen (OctoWoW)
--
-- C_CreatureInfo.GetCreatureInfoByID liefert zu einer Kreatur-ID: creatureID, displayID, name (englisch), type (Nummer),
-- family, rank, subName. Ist die Kreatur dem Client noch unbekannt, laedt RequestLoadCreatureByID sie nach
-- (Antwort: Ereignis CREATURE_DATA_LOAD_RESULT).
-- Im Hintergrund werden bei allen Kreaturen, die wir schon kennen, fehlende Angaben ergaenzt:
-- ct Typ (sprachunabhaengig, aus der Typ-Nummer)
-- c Klasse (elite, selten, Boss ...) falls noch keine bekannt
-- ne englischer Name (Ersatz, wenn kein gesehener Name da ist)
-- Es werden nur bestehende Datensaetze ergaenzt, keine neuen angelegt.
local ADDON, NS = ...
local FW = _G.Freewood
local Codex = NS and NS.Codex
if not FW or not Codex then
return
end
local Lookup = { started = false }
NS.Lookup = Lookup
Codex.Lookup = Lookup
local TYPE_CODE = {
[1] = "beast", [2] = "dragonkin", [3] = "demon", [4] = "elemental", [5] = "giant", [6] = "undead",
[7] = "humanoid", [8] = "critter", [9] = "mechanical", [10] = "other", [11] = "totem", [12] = "pet", [13] = "gascloud",
}
local RANK_CODE = { [1] = "elite", [2] = "rareelite", [3] = "worldboss", [4] = "rare" }
local TICK = 0.4 -- Abstand zwischen zwei Schritten (Sekunden)
local WAIT = 1.6 -- so lange auf die Antwort einer Anfrage warten
local PER_TICK = 25 -- bereits bekannte Kreaturen je Schritt (nur lesen, kostet fast nichts)
local RETRY_AFTER = 7 * 86400 -- erfolglose IDs nach einer Woche wieder versuchen
local FIRE_EVERY = 40 -- Oberflaeche hoechstens alle so viele Aenderungen auffrischen
local state = {
queue = {}, first = 1, last = 0,
pending = nil, pendingAt = 0,
changed = 0, lastFire = 0, generation = 0,
stats = { read = 0, requested = 0, filled = 0, failed = 0 },
}
Lookup.state = state
local function Now()
return GetTime and GetTime() or 0
end
local function Store()
local db = Codex.db
db.lookup = db.lookup or {}
db.lookup.fail = db.lookup.fail or {}
return db.lookup
end
local function Enabled()
return Codex.db ~= nil and Codex.db.settings ~= nil and Codex.db.settings.lookup ~= false
and _G.C_CreatureInfo ~= nil and _G.C_CreatureInfo.GetCreatureInfoByID ~= nil
end
local function ReadInfo(id)
local ok, info = pcall(_G.C_CreatureInfo.GetCreatureInfoByID, id)
if ok and type(info) == "table" and info.creatureID == id then
return info
end
return nil
end
local function NeedsInfo(u)
return type(u) == "table" and (not u.ct or not u.ne)
end
-- Schreibt die Antwort in den bestehenden Datensatz. Rueckgabe: true, wenn sich etwas geaendert hat
function Lookup.Apply(id, info)
local u = Codex.db.units[id]
if type(u) ~= "table" or type(info) ~= "table" then
return false
end
local changed = false
local code = TYPE_CODE[info.type]
if code and u.ct ~= code then
u.ct = code
changed = true
end
local rc = RANK_CODE[info.rank]
if rc and not u.c then
u.c = rc
changed = true
end
local name = info.name
if type(name) == "string" and name ~= "" and not u.ne then
u.ne = name:sub(1, 60)
changed = true
end
return changed
end
local function Changed(id)
state.changed = state.changed + 1
state.stats.filled = state.stats.filled + 1
if state.changed - state.lastFire >= FIRE_EVERY then
state.lastFire = state.changed
FW:Fire("CODEX_CHANGED", "unit", id)
end
end
-- Warteschlange (mit Zaehlern, nicht table.remove)
local function Push(id)
state.last = state.last + 1
state.queue[state.last] = id
end
local function Pop()
if state.first > state.last then
return nil
end
local id = state.queue[state.first]
state.queue[state.first] = nil
state.first = state.first + 1
if state.first > state.last then
state.first, state.last = 1, 0
end
return id
end
function Lookup.QueueSize()
return state.last - state.first + 1
end
-- Alle bekannten Kreaturen, denen etwas fehlt, in die Schlange stellen
function Lookup.Scan()
if not Enabled() then
return 0
end
local fail = Store().fail
local now = time and time() or 0
local queued = {}
for i = state.first, state.last do
queued[state.queue[i]] = true
end
local ids = {}
for id, u in pairs(Codex.db.units) do
if type(id) == "number" and NeedsInfo(u) and not queued[id] then
local f = fail[id]
if not f or now - f > RETRY_AFTER then
ids[#ids + 1] = id
end
end
end
table.sort(ids)
for i = 1, #ids do
Push(ids[i])
end
return #ids
end
local function Finish(id, info)
state.pending = nil
if info then
if Lookup.Apply(id, info) then
Changed(id)
end
else
state.stats.failed = state.stats.failed + 1
Store().fail[id] = time and time() or 1
end
end
function Lookup.OnLoadResult(_, id, ok)
id = tonumber(id)
if state.pending and id == state.pending then
Finish(id, ReadInfo(id))
end
end
function Lookup.Step()
if not Enabled() then
return
end
local now = Now()
if state.pending then
if now - state.pendingAt < WAIT then
return
end
-- keine Antwort: noch einmal lesen, sonst als erfolglos merken
Finish(state.pending, ReadInfo(state.pending))
end
local n = 0
while n < PER_TICK do
local id = Pop()
if not id then
return
end
local u = Codex.db.units[id]
if NeedsInfo(u) then
local info = ReadInfo(id)
if info then
state.stats.read = state.stats.read + 1
if Lookup.Apply(id, info) then
Changed(id)
end
n = n + 1
else
-- dem Client unbekannt: nachladen lassen und auf die Antwort warten
state.pending, state.pendingAt = id, now
state.stats.requested = state.stats.requested + 1
local ok = pcall(_G.C_CreatureInfo.RequestLoadCreatureByID, id)
if not ok then
Finish(id, nil)
end
return
end
end
end
end
function Lookup:Start()
if self.started then
return
end
self.started = true
state.generation = state.generation + 1
local gen = state.generation
FW:RegisterEvent("CREATURE_DATA_LOAD_RESULT", Lookup.OnLoadResult, Lookup)
local function Loop(delay, fn)
C_Timer.After(delay, function()
if not Lookup.started or gen ~= state.generation then
return
end
fn()
Loop(delay, fn)
end)
end
Loop(TICK, Lookup.Step)
-- erste Runde kurz nach dem Einloggen, danach alle 10 Minuten fuer neu gesehene Kreaturen
C_Timer.After(20, function()
if Lookup.started and gen == state.generation then
Lookup.Scan()
end
end)
Loop(600, Lookup.Scan)
end
function Lookup:Stop()
self.started = false
state.generation = state.generation + 1
FW:UnregisterEvent("CREATURE_DATA_LOAD_RESULT", Lookup)
end
-- /fwlookup Stand anzeigen
-- /fwlookup run sofort neu durchsuchen
-- /fwlookup retry auch fruehere Fehlschlaege noch einmal versuchen
-- /fwlookup id <N> eine einzelne Kreatur nachschlagen und anzeigen
-- /fwlookup clean Reste ohne Daten anzeigen; "clean yes" entfernt sie
function Lookup.Command(msg)
msg = (msg or ""):lower()
if msg == "retry" then
Store().fail = {}
msg = "run"
end
if msg == "run" then
local n = Lookup.Scan()
FW:Print(string.format("Nachschlagen: %d Kreaturen vorgemerkt", n))
return
end
if msg == "clean" or msg == "clean yes" then
-- Reste ohne Beobachtungen: Kreaturen, die der Client nicht kennt (Nachschlagen erfolglos), ohne Kills, Positionen,
-- Rollen, Beute oder sonstige Daten. Erst anzeigen, nur mit "clean yes" loeschen.
local Share = Codex.Share
local list = {}
for id in pairs(Store().fail) do
local u = Codex.db.units[id]
if type(u) == "table" and not (Share and Share.IsShareable and Share.IsShareable("u", u)) and not u.ed and not u.cde and not u.cen
and not u.ro and not u.d and not u.cells and (u.k or 0) == 0 then
list[#list + 1] = id
end
end
table.sort(list)
if #list == 0 then
FW:Print("Aufraeumen: keine Reste gefunden")
return
end
for _, id in ipairs(list) do
local u = Codex.db.units[id]
FW:Print(string.format(" %d %s", id, tostring(u.n or u.ne or "?")))
end
if msg == "clean yes" then
for _, id in ipairs(list) do
Codex.db.units[id] = nil
Store().fail[id] = nil
end
FW:Fire("CODEX_CHANGED", "unit", list[1])
FW:Print(string.format("Aufraeumen: %d Kreaturen entfernt", #list))
else
FW:Print(string.format("Aufraeumen: %d Kreaturen ohne Daten. Zum Entfernen: /fwlookup clean yes", #list))
end
return
end
local want = msg:match("^id%s+(%d+)$")
if want then
local id = tonumber(want)
local info = ReadInfo(id)
if info then
FW:Print(string.format("Kreatur %d: %s, Typ %s, Rang %s, Familie %s, Anzeige %s", id, tostring(info.name),
tostring(info.type), tostring(info.rank), tostring(info.family), tostring(info.displayID)))
else
FW:Print("Kreatur " .. id .. " ist dem Client noch unbekannt (/fwnpc " .. id .. " laedt sie nach)")
end
return
end
local total, withType, withName = 0, 0, 0
for id, u in pairs(Codex.db.units) do
if type(id) == "number" then
total = total + 1
if u.ct then withType = withType + 1 end
if u.ne or u.n then withName = withName + 1 end
end
end
local failed = 0
for _ in pairs(Store().fail) do failed = failed + 1 end
local s = state.stats
FW:Print(string.format("Nachschlagen: %s | Kreaturen %d, mit Typ %d, mit Namen %d | gelesen %d, angefragt %d, ergaenzt %d, erfolglos %d (gemerkt %d) | Schlange %d",
Enabled() and "an" or "aus", total, withType, withName, s.read, s.requested, s.filled, s.failed, failed, Lookup.QueueSize()))
end
if _G.SlashCmdList then
_G.SLASH_FWLOOKUP1 = "/fwlookup"
_G.SlashCmdList["FWLOOKUP"] = function(msg) Lookup.Command(msg) end
end