334 lines
14 KiB
Lua
334 lines
14 KiB
Lua
-- 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
|