329 lines
11 KiB
Lua
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
|