Dateien nach "/" hochladen
This commit is contained in:
@@ -0,0 +1,333 @@
|
||||
-- Freewood-OctoWoW-Codex: Rezepte der Berufe
|
||||
-- Lernt beim Oeffnen eines Berufsfensters jedes Rezept: Ergebnis-Item, Material mit Mengen und die Schwierigkeit
|
||||
-- (orange/gelb/gruen/grau) bei deinem damaligen Skill. Zusammen mit den Lehrer-Daten (Skill-Anforderung, Preis)
|
||||
-- weiss der Plan so, was ein Rezept braucht und was es bringt, auch bevor du es gelernt hast.
|
||||
-- db.recipes[Beruf][Rezeptname] = { it = Ergebnis-Item, rg = { [Materialitem] = Anzahl },
|
||||
-- ob = { optimal|medium|easy|trivial = { kleinster, groesster Skill } }, t = Zeit }
|
||||
-- Nur lokal; das Teilen mit anderen Spielern kommt spaeter.
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
local Codex = NS and NS.Codex
|
||||
if not FW or not Codex then
|
||||
return
|
||||
end
|
||||
|
||||
local R = {}
|
||||
NS.Recipes = R
|
||||
Codex.Recipes = R
|
||||
|
||||
local DIFFS = { optimal = true, medium = true, easy = true, trivial = true }
|
||||
local MAX_RECIPES = 600
|
||||
|
||||
local function Store()
|
||||
local db = Codex.db
|
||||
if not db then return nil end
|
||||
db.recipes = db.recipes or {}
|
||||
return db.recipes
|
||||
end
|
||||
|
||||
local function ItemIDOf(link)
|
||||
return type(link) == "string" and tonumber(link:match("item:(%d+)")) or nil
|
||||
end
|
||||
|
||||
-- Schwierigkeit aus Zahl (Enum: 0 optimal, 1 mittel, 2 leicht, 3 trivial) oder Text
|
||||
local DIFF_NUM = { [0] = "optimal", [1] = "medium", [2] = "easy", [3] = "trivial" }
|
||||
local function DiffOf(v)
|
||||
if type(v) == "number" then return DIFF_NUM[v] end
|
||||
if type(v) == "string" then
|
||||
v = v:lower()
|
||||
if DIFFS[v] then return v end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function Try(fn, ...)
|
||||
if type(fn) ~= "function" then return nil end
|
||||
local ok, a, b, c, d = pcall(fn, ...)
|
||||
if ok then return a, b, c, d end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Neue Berufsoberflaeche (C_TradeSkillUI): { profession, skill, max, list = { { name, item, diff, reag, learned } } }
|
||||
local function SnapshotNew()
|
||||
local T = _G.C_TradeSkillUI
|
||||
if type(T) ~= "table" then return nil end
|
||||
local base = Try(T.GetBaseProfessionInfo)
|
||||
local name = type(base) == "table" and (base.professionName or base.parentProfessionName) or nil
|
||||
local skill = type(base) == "table" and tonumber(base.skillLevel) or nil
|
||||
local max = type(base) == "table" and tonumber(base.maxSkillLevel) or nil
|
||||
if not name or name == "" then return nil end
|
||||
local ids = Try(T.GetAllRecipeIDs)
|
||||
if type(ids) ~= "table" or #ids == 0 then return nil end
|
||||
local list = {}
|
||||
for _, id in ipairs(ids) do
|
||||
local info = Try(T.GetRecipeInfo, id)
|
||||
if type(info) == "table" and type(info.name) == "string" then
|
||||
local diff = DiffOf(info.relativeDifficulty) or DiffOf(info.difficulty)
|
||||
local item
|
||||
local link = Try(T.GetRecipeItemLink, id)
|
||||
item = ItemIDOf(link)
|
||||
local reag = {}
|
||||
local schem = Try(T.GetRecipeSchematic, id, false)
|
||||
if type(schem) == "table" then
|
||||
item = item or tonumber(schem.outputItemID)
|
||||
for _, slot in ipairs(schem.reagentSlotSchematics or {}) do
|
||||
local first = slot.reagents and slot.reagents[1]
|
||||
local rid = first and tonumber(first.itemID)
|
||||
if rid and tonumber(slot.quantityRequired) then reag[rid] = tonumber(slot.quantityRequired) end
|
||||
end
|
||||
end
|
||||
if not next(reag) then
|
||||
local n = Try(T.GetRecipeNumReagents, id) or 0
|
||||
for j = 1, n do
|
||||
local _, _, need = Try(T.GetRecipeReagentInfo, id, j)
|
||||
local rid = ItemIDOf(Try(T.GetRecipeReagentItemLink, id, j))
|
||||
if rid and tonumber(need) then reag[rid] = tonumber(need) end
|
||||
end
|
||||
end
|
||||
list[#list + 1] = { name = info.name, item = item, diff = diff or "optimal", reag = reag,
|
||||
learned = info.learned ~= false, hasDiff = diff ~= nil, trivial = tonumber(info.maxTrivialLevel) }
|
||||
end
|
||||
end
|
||||
return { profession = name, skill = skill or 0, max = max or 0, list = list }
|
||||
end
|
||||
|
||||
-- Alte Handwerks-Schnittstelle (GetTradeSkill...)
|
||||
local function SnapshotOld()
|
||||
if not (GetTradeSkillLine and GetNumTradeSkills and GetTradeSkillInfo) then return nil end
|
||||
local ok, name, rank, maxRank = pcall(GetTradeSkillLine)
|
||||
if not ok or type(name) ~= "string" or name == "" or name == "UNKNOWN" then return nil end
|
||||
local list = {}
|
||||
for i = 1, math.min(GetNumTradeSkills() or 0, MAX_RECIPES) do
|
||||
local rname, rtype = GetTradeSkillInfo(i)
|
||||
if type(rname) == "string" and DIFFS[rtype] then
|
||||
local item = GetTradeSkillItemLink and ItemIDOf(GetTradeSkillItemLink(i)) or nil
|
||||
local reag = {}
|
||||
local nr = GetTradeSkillNumReagents and GetTradeSkillNumReagents(i) or 0
|
||||
for j = 1, nr do
|
||||
local _, _, need = GetTradeSkillReagentInfo(i, j)
|
||||
local rid = GetTradeSkillReagentItemLink and ItemIDOf(GetTradeSkillReagentItemLink(i, j)) or nil
|
||||
if rid and tonumber(need) then reag[rid] = tonumber(need) end
|
||||
end
|
||||
list[#list + 1] = { name = rname, item = item, diff = rtype, reag = reag, learned = true, hasDiff = true }
|
||||
end
|
||||
end
|
||||
return { profession = name, skill = tonumber(rank) or 0, max = tonumber(maxRank) or 0, list = list }
|
||||
end
|
||||
|
||||
-- Offenes Berufsfenster lesen: neue Oberflaeche zuerst, sonst die alte
|
||||
function R.Snapshot()
|
||||
return SnapshotNew() or SnapshotOld()
|
||||
end
|
||||
|
||||
function R.ReadOpenWindow()
|
||||
local store = Store()
|
||||
if not store then return false end
|
||||
local snap = R.Snapshot()
|
||||
if not snap then return false end
|
||||
R.last = snap
|
||||
local prof = store[snap.profession]
|
||||
if not prof then prof = {}; store[snap.profession] = prof end
|
||||
local n = 0
|
||||
for _ in pairs(prof) do n = n + 1 end
|
||||
for _, r in ipairs(snap.list) do
|
||||
local rec = prof[r.name]
|
||||
if not rec then
|
||||
if n >= MAX_RECIPES then break end
|
||||
rec = { rg = {}, ob = {} }
|
||||
prof[r.name] = rec
|
||||
n = n + 1
|
||||
end
|
||||
if r.item then rec.it = r.item end
|
||||
if r.trivial and r.trivial > 0 then rec.tv = r.trivial end -- ab diesem Skill grau
|
||||
for rid, need in pairs(r.reag) do rec.rg[rid] = need end
|
||||
if r.learned and r.hasDiff then
|
||||
local range = rec.ob[r.diff]
|
||||
if range then
|
||||
range[1], range[2] = math.min(range[1], snap.skill), math.max(range[2], snap.skill)
|
||||
else
|
||||
rec.ob[r.diff] = { snap.skill, snap.skill }
|
||||
end
|
||||
end
|
||||
rec.t = time and time() or 0
|
||||
end
|
||||
FW:Fire("CODEX_RECIPES_CHANGED", snap.profession)
|
||||
return true, #snap.list
|
||||
end
|
||||
|
||||
local reading = false
|
||||
local function OnTradeSkill()
|
||||
if reading then return end
|
||||
reading = true
|
||||
local function Run()
|
||||
reading = false
|
||||
Codex.Safe(R.ReadOpenWindow)
|
||||
end
|
||||
if C_Timer and C_Timer.After then
|
||||
C_Timer.After(0.5, Run)
|
||||
else
|
||||
Run()
|
||||
end
|
||||
end
|
||||
|
||||
-- Diagnose fuer /fwberufdump: zeigt, was das Spiel zurueckgibt
|
||||
function R.Dump()
|
||||
local function Say(msg) print("|cffe0b84dFreewood Beruf:|r " .. tostring(msg)) end
|
||||
local T = _G.C_TradeSkillUI
|
||||
Say("C_TradeSkillUI: " .. tostring(type(T) == "table") .. ", alte Schnittstelle (GetTradeSkillLine): " .. tostring(type(GetTradeSkillLine) == "function"))
|
||||
if type(T) == "table" then
|
||||
local base = Try(T.GetBaseProfessionInfo)
|
||||
if type(base) == "table" then
|
||||
local keys = {}
|
||||
for k, v in pairs(base) do keys[#keys + 1] = k .. "=" .. tostring(v) end
|
||||
table.sort(keys)
|
||||
Say("GetBaseProfessionInfo: " .. table.concat(keys, ", "))
|
||||
else
|
||||
Say("GetBaseProfessionInfo: nichts (Berufsfenster offen?)")
|
||||
end
|
||||
local ids = Try(T.GetAllRecipeIDs)
|
||||
Say("GetAllRecipeIDs: " .. (type(ids) == "table" and (#ids .. " Rezepte") or "nichts"))
|
||||
local id = type(ids) == "table" and ids[1]
|
||||
if id then
|
||||
local info = Try(T.GetRecipeInfo, id)
|
||||
if type(info) == "table" then
|
||||
local keys = {}
|
||||
for k, v in pairs(info) do
|
||||
if type(v) ~= "table" then keys[#keys + 1] = k .. "=" .. tostring(v) end
|
||||
end
|
||||
table.sort(keys)
|
||||
Say("GetRecipeInfo(" .. id .. "): " .. table.concat(keys, ", "))
|
||||
else
|
||||
Say("GetRecipeInfo(" .. id .. "): nichts")
|
||||
end
|
||||
local schem = Try(T.GetRecipeSchematic, id, false)
|
||||
if type(schem) == "table" then
|
||||
local keys = {}
|
||||
for k, v in pairs(schem) do keys[#keys + 1] = k .. "=" .. (type(v) == "table" and ("#" .. #v) or tostring(v)) end
|
||||
table.sort(keys)
|
||||
Say("GetRecipeSchematic: " .. table.concat(keys, ", "))
|
||||
else
|
||||
Say("GetRecipeSchematic: nichts; NumReagents=" .. tostring(Try(T.GetRecipeNumReagents, id)) .. ", ItemLink=" .. tostring(Try(T.GetRecipeItemLink, id)))
|
||||
end
|
||||
end
|
||||
end
|
||||
local snap = R.Snapshot()
|
||||
if snap then
|
||||
local learned = 0
|
||||
for _, r in ipairs(snap.list) do if r.learned then learned = learned + 1 end end
|
||||
Say(string.format("Auslesen: %s %d/%d, %d Rezepte (%d gelernt)", snap.profession, snap.skill, snap.max, #snap.list, learned))
|
||||
local r = snap.list[1]
|
||||
if r then
|
||||
local mats = {}
|
||||
for rid, n in pairs(r.reag) do mats[#mats + 1] = rid .. "x" .. n end
|
||||
Say(string.format("Beispiel: %s, Ergebnis-Item %s, Farbe %s, Material %s", r.name, tostring(r.item), tostring(r.diff), table.concat(mats, " ")))
|
||||
end
|
||||
else
|
||||
Say("Auslesen: nichts. Bitte das Berufsfenster oeffnen und den Befehl erneut eingeben.")
|
||||
end
|
||||
local pn, rn = R.Count()
|
||||
Say("In der Datenbank: " .. pn .. " Berufe, " .. rn .. " Rezepte")
|
||||
end
|
||||
|
||||
SLASH_FREEWOODRECIPEDUMP1 = "/fwberufdump"
|
||||
SlashCmdList["FREEWOODRECIPEDUMP"] = function() R.Dump() end
|
||||
|
||||
function R:Start()
|
||||
if self.started then return end
|
||||
self.started = true
|
||||
for _, event in ipairs({ "TRADE_SKILL_SHOW", "TRADE_SKILL_UPDATE", "TRADE_SKILL_LIST_UPDATE", "TRADE_SKILL_DATA_SOURCE_CHANGED" }) do
|
||||
FW:RegisterEvent(event, OnTradeSkill, R)
|
||||
end
|
||||
end
|
||||
|
||||
function R:Stop()
|
||||
for _, event in ipairs({ "TRADE_SKILL_SHOW", "TRADE_SKILL_UPDATE", "TRADE_SKILL_LIST_UPDATE", "TRADE_SKILL_DATA_SOURCE_CHANGED" }) do
|
||||
FW:UnregisterEvent(event, R)
|
||||
end
|
||||
self.started = false
|
||||
end
|
||||
|
||||
function R.Get(profession)
|
||||
local store = Store()
|
||||
return store and store[profession] or {}
|
||||
end
|
||||
|
||||
function R.Count()
|
||||
local store = Store()
|
||||
local professions, recipes = 0, 0
|
||||
for _, prof in pairs(store or {}) do
|
||||
professions = professions + 1
|
||||
for _ in pairs(prof) do recipes = recipes + 1 end
|
||||
end
|
||||
return professions, recipes
|
||||
end
|
||||
|
||||
-- Skill-Anforderung eines Rezepts: vom Lehrer (kleinster Wert), sonst kleinster Skill, bei dem es orange war
|
||||
function R.ReqOf(profession, name)
|
||||
local best
|
||||
for _, u in pairs(Codex.V().units) do
|
||||
if u.tr and u.ro and (u.ro.proftrainer or 0) > 0 then
|
||||
for _, r in pairs(u.tr) do
|
||||
if r.nm == name and r.sn == profession and tonumber(r.sr) and (not best or r.sr < best) then best = r.sr end
|
||||
end
|
||||
end
|
||||
end
|
||||
if best then return best, true end
|
||||
local rec = R.Get(profession)[name]
|
||||
local o = rec and rec.ob and rec.ob.optimal
|
||||
if o then return o[1], false end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- Schwierigkeit bei gegebenem Skill: aus Beobachtungen, sonst geschaetzt (je 10 Skill pro Farbe).
|
||||
-- Rueckgabe: "optimal" | "medium" | "easy" | "trivial" | "locked", und ob geschaetzt (true) oder beobachtet (false)
|
||||
function R.DiffAt(profession, name, skill)
|
||||
local rec = R.Get(profession)[name]
|
||||
local ob = rec and rec.ob or {}
|
||||
for diff, range in pairs(ob) do
|
||||
if skill >= range[1] and skill <= range[2] then return diff, false end
|
||||
end
|
||||
local req = R.ReqOf(profession, name)
|
||||
if not req and not next(ob) and not (rec and rec.tv) then return nil end
|
||||
-- Schwellen: gelb, gruen, grau beginnen bei
|
||||
local base = req or (ob.optimal and ob.optimal[1]) or 0
|
||||
local tv = rec and rec.tv
|
||||
-- Ist die graue Schwelle (maxTrivialLevel) bekannt, liegen gruen und gelb je 10 Skill darunter
|
||||
local gray = ob.trivial and ob.trivial[1] or tv or math.max(base + 30, ob.easy and ob.easy[2] + 1 or 0)
|
||||
local green = ob.easy and ob.easy[1] or (tv and math.max(gray - 10, ob.medium and ob.medium[2] + 1 or 0)) or math.max(gray - 10, ob.medium and ob.medium[2] + 1 or 0)
|
||||
local yellow = ob.medium and ob.medium[1] or math.max(green - 10, ob.optimal and ob.optimal[2] + 1 or 0)
|
||||
if not req and not ob.optimal and tv then base = math.max(0, yellow - 10) end
|
||||
local first = req or (ob.optimal and ob.optimal[1]) or 0
|
||||
if skill < first then return "locked", true end
|
||||
if skill < yellow then return "optimal", true end
|
||||
if skill < green then return "medium", true end
|
||||
if skill < gray then return "easy", true end
|
||||
return "trivial", true
|
||||
end
|
||||
|
||||
-- Rezepte beim Lehrer: Liste { name, profession, req, price, trainerID, rec = Rezept oder nil }
|
||||
function R.TrainerRows()
|
||||
local out, seen = {}, {}
|
||||
for uid, u in pairs(Codex.V().units) do
|
||||
if u.tr and u.ro and (u.ro.proftrainer or 0) > 0 then
|
||||
for _, r in pairs(u.tr) do
|
||||
if r.nm and r.sn then
|
||||
local key = r.sn .. "\1" .. r.nm
|
||||
local e = seen[key]
|
||||
local price = Codex.ModePrice and Codex.ModePrice(r.pr) or nil
|
||||
if not e then
|
||||
e = { name = r.nm, profession = r.sn, req = tonumber(r.sr), price = price, trainerID = uid,
|
||||
rec = R.Get(r.sn)[r.nm] }
|
||||
seen[key] = e
|
||||
out[#out + 1] = e
|
||||
elseif price and (not e.price or price < e.price) then
|
||||
e.price, e.trainerID = price, uid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return out
|
||||
end
|
||||
+1289
File diff suppressed because it is too large
Load Diff
+708
@@ -0,0 +1,708 @@
|
||||
-- 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<id>=<Chance %>,...;o<id>=<Chance>,...;v<haendlerID>,..."
|
||||
-- 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
|
||||
+2334
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,94 @@
|
||||
-- Freewood-OctoWoW-Codex: Abschnitt "Datenbank teilen" in den Einstellungen
|
||||
|
||||
local ADDON, NS = ...
|
||||
|
||||
local FW = _G.Freewood
|
||||
local Codex = NS and NS.Codex
|
||||
local Sync = NS and NS.Sync
|
||||
local Share = NS and NS.Share
|
||||
if not FW or not Codex or not Sync or not Share or not FW.UI or not FW.UI.RegisterSettingsSection then
|
||||
return
|
||||
end
|
||||
|
||||
local UI = FW.UI
|
||||
|
||||
local function Build(parent)
|
||||
local L = FW.L
|
||||
local f, stack = UI.CreateSection(parent, L.CODEX_SHARE_HEADER)
|
||||
|
||||
local note = UI.CreateLabel(f, L.CODEX_SHARE_NOTE, { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
stack:Add(note, { fit = true, gap = 8 })
|
||||
|
||||
local box = UI.CreateCheckbox(f, L.CODEX_SHARE_SWITCH, function()
|
||||
return Sync:IsEnabled()
|
||||
end, function(on)
|
||||
Sync:SetEnabled(on)
|
||||
end)
|
||||
stack:Add(box, { fit = true, gap = 10 })
|
||||
UI.AttachTooltip(box, function() return L.CODEX_SHARE_SWITCH end, function() return L.CODEX_SHARE_SWITCH_TIP end)
|
||||
|
||||
local callButton = UI.CreateButton(f, L.CODEX_SHARE_NOW, 200, 26, function()
|
||||
if Sync:IsEnabled() then
|
||||
Sync:JoinGlobal()
|
||||
Share.Scan(true)
|
||||
Sync:SendHello()
|
||||
end
|
||||
end, "default")
|
||||
stack:Add(callButton, { height = 26, gap = 12 })
|
||||
|
||||
local clearButton
|
||||
local armed = false
|
||||
clearButton = UI.CreateButton(f, L.CODEX_SHARE_CLEAR, 240, 26, function()
|
||||
if not armed then
|
||||
armed = true
|
||||
clearButton:SetLabel(L.CODEX_SHARE_CLEAR_SURE)
|
||||
C_Timer.After(5, function()
|
||||
armed = false
|
||||
clearButton:SetLabel(L.CODEX_SHARE_CLEAR)
|
||||
end)
|
||||
return
|
||||
end
|
||||
armed = false
|
||||
clearButton:SetLabel(L.CODEX_SHARE_CLEAR)
|
||||
local n = Share.ClearRemote()
|
||||
FW:Print(string.format(L.CODEX_SHARE_CLEARED, n))
|
||||
FW:Fire("CODEX_CHANGED", "import")
|
||||
FW:Fire("CODEX_SYNC_CHANGED")
|
||||
end, "default")
|
||||
stack:Add(clearButton, { height = 26, gap = 8 })
|
||||
|
||||
local status = UI.CreateLabel(f, "", { size = 11, color = "textDim", justifyV = "TOP" })
|
||||
stack:Add(status, { fit = true, gap = 10 })
|
||||
|
||||
function f.Refresh()
|
||||
box:Refresh()
|
||||
local st = Sync:Stats()
|
||||
local text = string.format(L.CODEX_SHARE_STATS, st.sent, st.received)
|
||||
text = text .. "\n" .. string.format(L.CODEX_SHARE_SOURCES, st.senders, st.records)
|
||||
text = text .. "\n" .. string.format(L.CODEX_SHARE_HEARD, st.heard or 0)
|
||||
if Sync:IsEnabled() then
|
||||
local key = "CODEX_SHARE_GLOBAL_" .. string.upper(st.global or "off")
|
||||
text = text .. "\n" .. (L[key] or L.CODEX_SHARE_GLOBAL_OFF)
|
||||
end
|
||||
status:SetText(text)
|
||||
stack:Layout()
|
||||
end
|
||||
|
||||
FW:On("CODEX_SYNC_CHANGED", function()
|
||||
if f:IsVisible() then
|
||||
f.Refresh()
|
||||
end
|
||||
end, f)
|
||||
|
||||
stack:Layout()
|
||||
return f
|
||||
end
|
||||
|
||||
UI:RegisterSettingsSection("codex-share", {
|
||||
order = 105,
|
||||
height = 360,
|
||||
build = Build,
|
||||
refresh = function(frame)
|
||||
frame.Refresh()
|
||||
end,
|
||||
})
|
||||
Reference in New Issue
Block a user