494 lines
13 KiB
Lua
494 lines
13 KiB
Lua
-- Freewood-OctoWoW-Core: Grundgeruest
|
|
-- Namensraum, Ereignissystem, Modulverwaltung, Datenspeicher.
|
|
-- Andere Addons der Familie greifen ueber die globale Variable "Freewood" darauf zu.
|
|
|
|
local ADDON, FW = ...
|
|
|
|
_G.Freewood = FW
|
|
|
|
FW.ADDON = ADDON
|
|
|
|
-- OctoWoW-Fassung (Port von Freewood 0.56.0, das fuer "Forever" geschrieben wurde).
|
|
-- testBypass: NUR fuer die Testfassungen. Umgeht die Berechtigungsliste (Dev-Sperre), damit wir alles pruefen koennen.
|
|
-- Zum Abschalten: testBypass = false (eine Stelle, gilt fuer alle Module).
|
|
FW.OCTO = { name = "OctoWoW", based = "Freewood 0.56.0", testBypass = false }
|
|
|
|
-- Fehler, die schon beim Laden der Dateien auftreten (bevor Freewood-OctoWoW-Dev da ist), werden hier gesammelt.
|
|
-- Freewood-OctoWoW-Dev holt sie spaeter ins Log (Dev.Probe).
|
|
FW.earlyErrors = {}
|
|
do
|
|
local getH, setH = _G.geterrorhandler, _G.seterrorhandler
|
|
if getH and setH then
|
|
local old = getH()
|
|
FW.earlyHandler = function(err)
|
|
local s = tostring(err)
|
|
if s:find("Freewood", 1, true) then
|
|
local list = FW.earlyErrors
|
|
if #list < 200 then
|
|
list[#list + 1] = { t = _G.GetTime and _G.GetTime() or 0, m = s }
|
|
end
|
|
end
|
|
if old then
|
|
return old(err)
|
|
end
|
|
end
|
|
setH(FW.earlyHandler)
|
|
end
|
|
end
|
|
|
|
FW.API_VERSION = 1 -- wird erhoeht, wenn sich die Modul-Schnittstelle inkompatibel aendert
|
|
FW.DB_VERSION = 1 -- Version des gespeicherten Datenformats
|
|
|
|
local function GetMeta(field)
|
|
local fn = (C_AddOns and C_AddOns.GetAddOnMetadata) or GetAddOnMetadata
|
|
if fn then
|
|
local ok, value = pcall(fn, ADDON, field)
|
|
if ok then
|
|
return value
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
FW.version = GetMeta("Version") or "dev"
|
|
|
|
------------------------------------------------------------------------
|
|
-- Ausgabe und Fehlerbehandlung
|
|
------------------------------------------------------------------------
|
|
|
|
local PREFIX = "|cffc89b3cFreewood|r"
|
|
|
|
local function Join(...)
|
|
local parts = {}
|
|
for i = 1, select("#", ...) do
|
|
parts[i] = tostring((select(i, ...)))
|
|
end
|
|
return table.concat(parts, " ")
|
|
end
|
|
|
|
function FW:Print(...)
|
|
print(PREFIX .. ": " .. Join(...))
|
|
end
|
|
|
|
function FW:Debug(...)
|
|
if self.db and self.db.settings and self.db.settings.debug then
|
|
print(PREFIX .. " |cff8c8a84[debug]|r " .. Join(...))
|
|
end
|
|
end
|
|
|
|
-- Ruft fn geschuetzt auf. Ein Fehler wird an den Standard-Fehlerhandler
|
|
-- weitergereicht und bricht den Aufrufer nicht ab.
|
|
local function SafeCall(fn, ...)
|
|
local ok, err = pcall(fn, ...)
|
|
if not ok then
|
|
local handler = geterrorhandler and geterrorhandler()
|
|
if handler then
|
|
handler(err)
|
|
else
|
|
print(err)
|
|
end
|
|
end
|
|
return ok
|
|
end
|
|
FW.SafeCall = SafeCall
|
|
|
|
------------------------------------------------------------------------
|
|
-- Interne Nachrichten (Ereignissystem des Cores)
|
|
------------------------------------------------------------------------
|
|
|
|
local callbacks = {}
|
|
|
|
function FW:On(event, fn, owner)
|
|
local list = callbacks[event]
|
|
if not list then
|
|
list = {}
|
|
callbacks[event] = list
|
|
end
|
|
list[#list + 1] = { fn = fn, owner = owner }
|
|
end
|
|
|
|
function FW:Off(event, owner)
|
|
local list = callbacks[event]
|
|
if not list then
|
|
return
|
|
end
|
|
for i = #list, 1, -1 do
|
|
if list[i].owner == owner then
|
|
table.remove(list, i)
|
|
end
|
|
end
|
|
end
|
|
|
|
function FW:Fire(event, ...)
|
|
local list = callbacks[event]
|
|
if not list then
|
|
return
|
|
end
|
|
local snapshot = {}
|
|
for i = 1, #list do
|
|
snapshot[i] = list[i]
|
|
end
|
|
for i = 1, #snapshot do
|
|
local cb = snapshot[i]
|
|
if cb.owner then
|
|
SafeCall(cb.fn, cb.owner, ...)
|
|
else
|
|
SafeCall(cb.fn, ...)
|
|
end
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------
|
|
-- Spielereignisse (ein gemeinsamer Frame fuer alle Module)
|
|
------------------------------------------------------------------------
|
|
|
|
local eventFrame = CreateFrame("Frame")
|
|
local gameHandlers = {}
|
|
-- OctoWoW (1.12): Ereignis -> echte Ereignisse, die es dort gibt
|
|
local EVENT_ALIAS = {
|
|
GROUP_ROSTER_UPDATE = { "PARTY_MEMBERS_CHANGED", "RAID_ROSTER_UPDATE" },
|
|
PET_STATS_UPDATE = { "UNIT_HAPPINESS", "UNIT_PET_EXPERIENCE" },
|
|
}
|
|
local aliasTarget = {}
|
|
|
|
function FW:RegisterEvent(event, fn, owner)
|
|
local list = gameHandlers[event]
|
|
if not list then
|
|
local ok = pcall(eventFrame.RegisterEvent, eventFrame, event)
|
|
if not ok then
|
|
self:Debug("Ereignis nicht verfuegbar:", event)
|
|
return false
|
|
end
|
|
list = {}
|
|
gameHandlers[event] = list
|
|
-- OctoWoW: moderne Ereignisse, die der 1.12-Client nicht kennt, ueber die echten Ereignisse abbilden
|
|
local alias = EVENT_ALIAS[event]
|
|
if alias then
|
|
for ai = 1, #alias do
|
|
local real = alias[ai]
|
|
if pcall(eventFrame.RegisterEvent, eventFrame, real) then
|
|
aliasTarget[real] = event
|
|
end
|
|
end
|
|
end
|
|
end
|
|
list[#list + 1] = { fn = fn, owner = owner }
|
|
return true
|
|
end
|
|
|
|
function FW:UnregisterEvent(event, owner)
|
|
local list = gameHandlers[event]
|
|
if not list then
|
|
return
|
|
end
|
|
-- OctoWoW: while-Schleife statt "for i = #list, 1, -1" (die Rueckwaerts-Schleife lieferte hier nil-Eintraege)
|
|
local i = #list
|
|
while i >= 1 do
|
|
local h = list[i]
|
|
if h and h.owner == owner then
|
|
table.remove(list, i)
|
|
end
|
|
i = i - 1
|
|
end
|
|
if #list == 0 then
|
|
gameHandlers[event] = nil
|
|
eventFrame:UnregisterEvent(event)
|
|
local alias = EVENT_ALIAS[event]
|
|
if alias then
|
|
for ai = 1, #alias do
|
|
if aliasTarget[alias[ai]] == event then
|
|
aliasTarget[alias[ai]] = nil
|
|
eventFrame:UnregisterEvent(alias[ai])
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local curEvent
|
|
local function Dispatch(list, ...)
|
|
local snapshot = {}
|
|
for i = 1, #list do
|
|
snapshot[i] = list[i]
|
|
end
|
|
-- Zeitmesser (Dev-Reiter): FW.HandlerTimer(event, ms, owner), nur wenn gesetzt
|
|
local hook, prof = FW.HandlerTimer, _G.debugprofilestop
|
|
for i = 1, #snapshot do
|
|
local h = snapshot[i]
|
|
local t0 = hook and prof and prof()
|
|
if h.owner then
|
|
SafeCall(h.fn, h.owner, ...)
|
|
else
|
|
SafeCall(h.fn, ...)
|
|
end
|
|
if t0 then hook(curEvent, prof() - t0, h.owner) end
|
|
end
|
|
end
|
|
|
|
-- Von der Kanal-Schicht (Compat) genutzt: ein Ereignis wie vom Spiel an die angemeldeten Module geben
|
|
function FW:InjectEvent(event, ...)
|
|
local list = gameHandlers[event]
|
|
if list then
|
|
curEvent = event
|
|
Dispatch(list, ...)
|
|
end
|
|
end
|
|
|
|
eventFrame:SetScript("OnEvent", function(_, event, ...)
|
|
local list = gameHandlers[event]
|
|
if list then
|
|
curEvent = event
|
|
Dispatch(list, ...)
|
|
end
|
|
local target = aliasTarget[event]
|
|
if target and gameHandlers[target] then
|
|
curEvent = event
|
|
Dispatch(gameHandlers[target], ...)
|
|
end
|
|
end)
|
|
|
|
------------------------------------------------------------------------
|
|
-- Datenspeicher
|
|
-- FreewoodDB : gilt fuer den ganzen Account
|
|
-- FreewoodCharDB : gilt nur fuer den aktuellen Charakter
|
|
-- Module legen ihre Daten unter db.modules[<Modulname>] ab (siehe NewModule).
|
|
------------------------------------------------------------------------
|
|
|
|
FW.defaults = {
|
|
global = {
|
|
dbVersion = FW.DB_VERSION,
|
|
installed = 0,
|
|
settings = {
|
|
minimap = { hide = false, angle = 215 },
|
|
scale = 1,
|
|
locale = "auto",
|
|
debug = false,
|
|
windowPos = false,
|
|
windowSize = false,
|
|
},
|
|
disabledModules = {},
|
|
modules = {},
|
|
},
|
|
char = {
|
|
dbVersion = FW.DB_VERSION,
|
|
modules = {},
|
|
},
|
|
}
|
|
|
|
-- Migrationen: FW.migrations[n] hebt Daten von Version n-1 auf n.
|
|
FW.migrations = {}
|
|
|
|
local function Migrate(db, kind)
|
|
local from = tonumber(db.dbVersion) or 0
|
|
for version = from + 1, FW.DB_VERSION do
|
|
local migration = FW.migrations[version]
|
|
if migration then
|
|
SafeCall(migration, db, kind)
|
|
end
|
|
end
|
|
db.dbVersion = FW.DB_VERSION
|
|
end
|
|
|
|
function FW:InitDB()
|
|
if type(FreewoodDB) ~= "table" then
|
|
FreewoodDB = {}
|
|
end
|
|
if type(FreewoodCharDB) ~= "table" then
|
|
FreewoodCharDB = {}
|
|
end
|
|
|
|
self.dbNewer = false
|
|
|
|
local sets = {
|
|
{ db = FreewoodDB, defaults = self.defaults.global, kind = "global" },
|
|
{ db = FreewoodCharDB, defaults = self.defaults.char, kind = "char" },
|
|
}
|
|
|
|
for _, set in ipairs(sets) do
|
|
local db = set.db
|
|
local fresh = next(db) == nil
|
|
local stored = tonumber(db.dbVersion)
|
|
|
|
if fresh then
|
|
db.dbVersion = self.DB_VERSION
|
|
elseif stored and stored > self.DB_VERSION then
|
|
-- Daten einer neueren Version: nichts migrieren, nichts abwerten.
|
|
self.dbNewer = true
|
|
else
|
|
Migrate(db, set.kind)
|
|
end
|
|
|
|
self.Util.MergeDefaults(db, set.defaults)
|
|
end
|
|
|
|
if FreewoodDB.installed == 0 then
|
|
FreewoodDB.installed = time()
|
|
end
|
|
|
|
self.db = FreewoodDB
|
|
self.settings = FreewoodDB.settings
|
|
self.chardb = FreewoodCharDB
|
|
end
|
|
|
|
function FW:ApplyLocale()
|
|
local setting = self.settings and self.settings.locale or "auto"
|
|
local code
|
|
if setting == "deDE" or setting == "enUS" then
|
|
code = setting
|
|
else
|
|
code = (GetLocale() == "deDE") and "deDE" or "enUS"
|
|
end
|
|
self:SetLocale(code)
|
|
end
|
|
|
|
------------------------------------------------------------------------
|
|
-- Module
|
|
-- Ablauf eines Moduls: NewModule -> OnInitialize (einmal, Daten bereit)
|
|
-- -> OnEnable (nach PLAYER_LOGIN) -> optional OnDisable
|
|
------------------------------------------------------------------------
|
|
|
|
FW.modules = {}
|
|
FW.moduleList = {}
|
|
|
|
function FW:RequireAPI(minVersion, who)
|
|
if (self.API_VERSION or 0) >= minVersion then
|
|
return true
|
|
end
|
|
self:Print(string.format(self.L.API_TOO_OLD, tostring(who or "?"), minVersion, self.API_VERSION))
|
|
return false
|
|
end
|
|
|
|
function FW:NewModule(name, opts)
|
|
assert(type(name) == "string" and name ~= "", "Freewood:NewModule - Name fehlt")
|
|
if self.modules[name] then
|
|
error("Freewood: Modul '" .. name .. "' ist bereits registriert", 2)
|
|
end
|
|
|
|
local mod = opts or {}
|
|
mod.name = name
|
|
mod.state = "registered"
|
|
mod.core = self
|
|
|
|
function mod:IsEnabled()
|
|
return self.state == "enabled"
|
|
end
|
|
|
|
self.modules[name] = mod
|
|
self.moduleList[#self.moduleList + 1] = mod
|
|
|
|
if self.ready then
|
|
self:InitModule(mod)
|
|
if self.loggedIn then
|
|
self:EnableModule(mod)
|
|
end
|
|
end
|
|
|
|
self:Fire("FW_MODULE_REGISTERED", mod)
|
|
return mod
|
|
end
|
|
|
|
function FW:GetModule(name)
|
|
return self.modules[name]
|
|
end
|
|
|
|
function FW:InitModule(mod)
|
|
if mod.state ~= "registered" then
|
|
return
|
|
end
|
|
|
|
local globalData = self.db.modules
|
|
local charData = self.chardb.modules
|
|
globalData[mod.name] = globalData[mod.name] or {}
|
|
charData[mod.name] = charData[mod.name] or {}
|
|
|
|
if mod.defaults then
|
|
self.Util.MergeDefaults(globalData[mod.name], mod.defaults)
|
|
end
|
|
if mod.charDefaults then
|
|
self.Util.MergeDefaults(charData[mod.name], mod.charDefaults)
|
|
end
|
|
|
|
mod.db = globalData[mod.name]
|
|
mod.chardb = charData[mod.name]
|
|
mod.state = "initialized"
|
|
|
|
if mod.OnInitialize then
|
|
SafeCall(mod.OnInitialize, mod)
|
|
end
|
|
end
|
|
|
|
function FW:EnableModule(mod)
|
|
if mod.state ~= "initialized" and mod.state ~= "disabled" then
|
|
return
|
|
end
|
|
if self.db.disabledModules[mod.name] then
|
|
mod.state = "disabled"
|
|
return
|
|
end
|
|
mod.state = "enabled"
|
|
if mod.OnEnable then
|
|
SafeCall(mod.OnEnable, mod)
|
|
end
|
|
self:Fire("FW_MODULE_ENABLED", mod)
|
|
end
|
|
|
|
function FW:DisableModule(mod)
|
|
if mod.state ~= "enabled" then
|
|
return
|
|
end
|
|
mod.state = "disabled"
|
|
if mod.OnDisable then
|
|
SafeCall(mod.OnDisable, mod)
|
|
end
|
|
end
|
|
|
|
function FW:SetModuleEnabled(name, enabled)
|
|
local mod = self.modules[name]
|
|
self.db.disabledModules[name] = (not enabled) or nil
|
|
if mod then
|
|
if enabled then
|
|
self:EnableModule(mod)
|
|
else
|
|
self:DisableModule(mod)
|
|
end
|
|
end
|
|
end
|
|
|
|
------------------------------------------------------------------------
|
|
-- Start
|
|
------------------------------------------------------------------------
|
|
|
|
function FW:OnAddonLoaded(name)
|
|
if name ~= ADDON then
|
|
return
|
|
end
|
|
self:UnregisterEvent("ADDON_LOADED", self)
|
|
|
|
self:InitDB()
|
|
self:ApplyLocale()
|
|
self.ready = true
|
|
|
|
if self.dbNewer then
|
|
self:Print(self.L.DB_NEWER)
|
|
end
|
|
|
|
self:Fire("FW_DB_READY")
|
|
|
|
-- Module, die vor der Datenbank registriert wurden (sollte nicht vorkommen)
|
|
for _, mod in ipairs(self.moduleList) do
|
|
self:InitModule(mod)
|
|
end
|
|
end
|
|
|
|
function FW:OnLogin()
|
|
self.loggedIn = true
|
|
self:Fire("FW_LOGIN")
|
|
for _, mod in ipairs(self.moduleList) do
|
|
self:InitModule(mod)
|
|
self:EnableModule(mod)
|
|
end
|
|
end
|
|
|
|
FW:RegisterEvent("ADDON_LOADED", FW.OnAddonLoaded, FW)
|
|
FW:RegisterEvent("PLAYER_LOGIN", FW.OnLogin, FW)
|