Freewood-OctoWoW-Core 0.1.136

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016onJCSHLwhs9udhC9ZLzk9
This commit is contained in:
Lacustat
2026-10-27 01:48:55 +01:00
co-authored by Claude Sonnet 5.5
commit 3ba78b0b1d
15 changed files with 5486 additions and 0 deletions
+1324
View File
File diff suppressed because it is too large Load Diff
+273
View File
@@ -0,0 +1,273 @@
-- Freewood-OctoWoW-Core: Export und Import als kurzer Code
--
-- Format: FW1:<art>:<pruefsumme>:<koerper>
-- art kleingeschriebene Datenart, z. B. "pets" oder "beasts"
-- pruefsumme 8 Hex-Zeichen ueber den Koerper (erkennt Tippfehler und abgeschnittene Codes)
-- koerper Base64( Komprimiert( CBOR( { v, k, t, a, d } ) ) )
-- Das ist Komprimierung und Kodierung, keine Verschluesselung.
--
-- Sicherheit beim Import:
-- * Es wird nie Code ausgefuehrt, nur Daten gelesen (CBOR).
-- * Groessenlimit, Tiefenlimit, Knotenlimit, nur Text/Zahl/Wahrheitswert/Tabelle.
-- * Das zustaendige Modul prueft die Daten zusaetzlich (validate) und fuegt sie
-- selbst zusammen (import). Regel fuer Module: importierte Eintraege mit
-- src = "import" markieren und eigene Daten nie ueberschreiben.
--
-- Ein Modul meldet sich so an:
-- Freewood.Export.RegisterHandler("pets", {
-- label = "Jaeger-Begleiter" (Text oder Funktion),
-- export = function() return <Tabelle> end,
-- validate = function(data, payload) return true end, -- optional
-- import = function(data, payload) return "3 neu, 1 ergaenzt" end,
-- })
local ADDON, FW = ...
local Export = {}
FW.Export = Export
local Util = FW.Util
local FORMAT_VERSION = 1
local MAX_ENCODED = 150000
local HARD_MAX = 400000 -- groesstes erlaubtes Limit eines Handlers (handler.maxEncoded)
local LIMITS = {
maxDepth = 12,
maxNodes = 200000,
maxString = 4096,
}
Export.handlers = {}
Export.handlerList = {}
function Export.RegisterHandler(kind, handler)
assert(type(kind) == "string" and kind:match("^[a-z0-9]+$"),
"Freewood.Export: Datenart muss aus a-z und 0-9 bestehen")
assert(type(handler) == "table", "Freewood.Export: Handler fehlt")
handler.kind = kind
if not Export.handlers[kind] then
Export.handlerList[#Export.handlerList + 1] = handler
else
for i, h in ipairs(Export.handlerList) do
if h.kind == kind then
Export.handlerList[i] = handler
end
end
end
Export.handlers[kind] = handler
end
function Export.GetLabel(handler)
local label = handler.label
if type(label) == "function" then
label = label()
end
return label or handler.kind
end
local function GetEncoder()
local E = C_EncodingUtil
if E and E.SerializeCBOR and E.DeserializeCBOR and E.CompressString
and E.DecompressString and E.EncodeBase64 and E.DecodeBase64 then
return E
end
return nil
end
-- Prueft eine entpackte Datenstruktur auf harmlose Form.
function Export.CheckStructure(data)
if type(data) ~= "table" then
return false
end
local nodes = 0
local seen = {}
local stack = { { data, 1 } }
while #stack > 0 do
local entry = table.remove(stack)
local tbl, depth = entry[1], entry[2]
if depth > LIMITS.maxDepth or seen[tbl] then
return false
end
seen[tbl] = true
for k, v in pairs(tbl) do
nodes = nodes + 1
if nodes > LIMITS.maxNodes then
return false
end
local kt = type(k)
if kt ~= "string" and kt ~= "number" then
return false
end
if kt == "string" and #k > LIMITS.maxString then
return false
end
local vt = type(v)
if vt == "string" then
if #v > LIMITS.maxString then
return false
end
elseif vt == "number" then
if v ~= v or v == math.huge or v == -math.huge then
return false
end
elseif vt == "table" then
stack[#stack + 1] = { v, depth + 1 }
elseif vt ~= "boolean" then
return false
end
end
end
return true
end
-- Erzeugt den Code. Rueckgabe: text oder nil, fehlerSchluessel
function Export.Encode(kind, data)
local E = GetEncoder()
if not E then
return nil, "ERR_NOAPI"
end
if type(kind) ~= "string" or not kind:match("^[a-z0-9]+$") or type(data) ~= "table" then
return nil, "ERR_INVALID"
end
local payload = { v = FORMAT_VERSION, k = kind, t = time(), a = FW.version, d = data }
local ok, cbor = pcall(E.SerializeCBOR, payload)
if not ok or type(cbor) ~= "string" then
return nil, "ERR_INVALID"
end
local ok2, packed = pcall(E.CompressString, cbor)
if not ok2 or type(packed) ~= "string" then
return nil, "ERR_INVALID"
end
local ok3, body = pcall(E.EncodeBase64, packed)
if not ok3 or type(body) ~= "string" then
return nil, "ERR_INVALID"
end
local hLimit = Export.handlers[kind] and tonumber(Export.handlers[kind].maxEncoded) or MAX_ENCODED
if #body > math.min(hLimit, HARD_MAX) then
return nil, "ERR_TOO_LARGE"
end
return string.format("FW%d:%s:%s:%s", FORMAT_VERSION, kind, Util.Hash(body), body)
end
-- Liest einen Code. Rueckgabe: payload oder nil, fehlerSchluessel
function Export.Decode(text)
if type(text) ~= "string" then
return nil, "ERR_FORMAT"
end
text = text:gsub("%s+", "")
if text == "" then
return nil, "ERR_FORMAT"
end
if #text > HARD_MAX + 64 then
return nil, "ERR_TOO_LARGE"
end
local version = text:match("^FW(%d+):")
if not version then
return nil, "ERR_FORMAT"
end
if tonumber(version) > FORMAT_VERSION then
return nil, "ERR_VERSION"
end
local kind, crc, body = text:match("^FW%d+:([a-z0-9]+):(%x%x%x%x%x%x%x%x):(.+)$")
if not kind then
return nil, "ERR_FORMAT"
end
if body:find("[^%w%+/=_%-]") then
return nil, "ERR_FORMAT"
end
local hl = Export.handlers[kind] and tonumber(Export.handlers[kind].maxEncoded) or MAX_ENCODED
if #body > math.min(hl, HARD_MAX) then
return nil, "ERR_TOO_LARGE"
end
if Util.Hash(body) ~= crc:lower() then
return nil, "ERR_CHECKSUM"
end
local E = GetEncoder()
if not E then
return nil, "ERR_NOAPI"
end
local ok, packed = pcall(E.DecodeBase64, body)
if not ok or type(packed) ~= "string" then
return nil, "ERR_DECODE"
end
local ok2, cbor = pcall(E.DecompressString, packed)
if not ok2 or type(cbor) ~= "string" then
return nil, "ERR_DECODE"
end
local ok3, payload = pcall(E.DeserializeCBOR, cbor)
if not ok3 or type(payload) ~= "table" then
return nil, "ERR_DECODE"
end
if payload.v ~= FORMAT_VERSION then
if type(payload.v) == "number" and payload.v > FORMAT_VERSION then
return nil, "ERR_VERSION"
end
return nil, "ERR_INVALID"
end
if payload.k ~= kind or type(payload.d) ~= "table" then
return nil, "ERR_INVALID"
end
if not Export.CheckStructure(payload.d) then
return nil, "ERR_INVALID"
end
return payload
end
-- Exportiert die Daten eines Moduls. Rueckgabe: text oder nil, fehlerSchluessel, extra
function Export.ExportKind(kind)
local handler = Export.handlers[kind]
if not handler or not handler.export then
return nil, "ERR_NO_HANDLER", kind
end
local ok, data = pcall(handler.export)
if not ok or type(data) ~= "table" then
return nil, "ERR_INVALID"
end
if next(data) == nil then
return nil, "EXCHANGE_NOTHING_EXPORT"
end
-- encodeAs: ein Handler darf unter der Art eines anderen Handlers exportieren (dann importiert dieser)
return Export.Encode(handler.encodeAs or kind, data)
end
-- Importiert einen Code ueber das zustaendige Modul.
-- Rueckgabe: true, statistik, art oder false, fehlerSchluessel, extra
function Export.Import(text)
local payload, err = Export.Decode(text)
if not payload then
return false, err
end
local handler = Export.handlers[payload.k]
if not handler or not handler.import then
return false, "ERR_NO_HANDLER", payload.k
end
if handler.validate then
local ok, valid, message = pcall(handler.validate, payload.d, payload)
if not ok or not valid then
return false, "ERR_INVALID", type(message) == "string" and message or nil
end
end
local ok, result = pcall(handler.import, payload.d, payload)
if not ok then
FW:Debug("Import fehlgeschlagen:", result)
return false, "ERR_INVALID"
end
return true, result, payload.k
end
+493
View File
@@ -0,0 +1,493 @@
-- 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 = true }
-- 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)
+51
View File
@@ -0,0 +1,51 @@
-- Freewood-OctoWoW-Core: Slash-Befehle (/freewood und /fw)
local ADDON, FW = ...
SLASH_FREEWOOD1 = "/freewood"
SLASH_FREEWOOD2 = "/fw"
local function PrintHelp()
local L = FW.L
FW:Print(L.SLASH_HELP_TITLE)
for i = 1, 7 do
print(" " .. L["SLASH_HELP_" .. i])
end
end
local function Handle(msg)
local L = FW.L
msg = strtrim(msg or ""):lower()
local cmd = msg:match("^(%S*)")
if cmd == "" then
FW.UI:Toggle()
elseif cmd == "settings" or cmd == "config" or cmd == "options" then
FW.UI:Show("settings")
elseif cmd == "exchange" or cmd == "export" or cmd == "import" then
FW.UI:Show("exchange")
elseif cmd == "minimap" then
FW.Minimap:SetHidden(not FW.settings.minimap.hide)
elseif cmd == "modules" then
if #FW.moduleList == 0 then
FW:Print(L.SLASH_NO_MODULES)
else
for _, mod in ipairs(FW.moduleList) do
FW:Print(mod.name, mod.version and ("v" .. mod.version) or "", "(" .. mod.state .. ")")
end
end
elseif cmd == "debug" then
FW.settings.debug = not FW.settings.debug
FW:Print(FW.settings.debug and L.SLASH_DEBUG_ON or L.SLASH_DEBUG_OFF)
elseif cmd == "reset" then
FW.UI:ResetPosition()
FW:Print(L.SLASH_RESET_DONE)
elseif cmd == "version" then
local _, _, _, toc = GetBuildInfo()
FW:Print(string.format(L.SLASH_VERSION, FW.version, FW.API_VERSION, tostring(toc or "?")))
else
PrintHelp()
end
end
SlashCmdList["FREEWOOD"] = Handle
+303
View File
@@ -0,0 +1,303 @@
-- Freewood-OctoWoW-Core: Hilfsfunktionen
-- Kopieren/Zusammenfuehren von Tabellen, Geld, Items (asynchron), Pruefsumme,
-- NPC-ID aus GUID, Spielerposition.
local ADDON, FW = ...
local Util = {}
FW.Util = Util
------------------------------------------------------------------------
-- Tabellen
------------------------------------------------------------------------
function Util.CopyTable(src, seen)
if type(src) ~= "table" then
return src
end
seen = seen or {}
if seen[src] then
return seen[src]
end
local dst = {}
seen[src] = dst
for k, v in pairs(src) do
dst[k] = Util.CopyTable(v, seen)
end
return dst
end
-- Geheime Werte (secret values): das Spiel gibt manche Angaben, vor allem zu fremden Einheiten,
-- als "geheimen" Wert zurueck. Damit darf Addon-Code nichts rechnen, vergleichen oder indizieren.
function Util.IsSecret(v)
local fn = _G.issecretvalue
if type(fn) == "function" then
local ok, res = pcall(fn, v)
return ok and res and true or false
end
return false
end
-- Gibt v zurueck, aber nil, wenn der Wert geheim ist
function Util.Clean(v)
if Util.IsSecret(v) then
return nil
end
return v
end
-- Ergaenzt fehlende Schluessel in target aus defaults. Vorhandene Werte bleiben.
function Util.MergeDefaults(target, defaults)
for k, v in pairs(defaults) do
if type(v) == "table" then
if type(target[k]) ~= "table" then
target[k] = Util.CopyTable(v)
else
Util.MergeDefaults(target[k], v)
end
elseif target[k] == nil then
target[k] = v
end
end
return target
end
function Util.Count(t)
local n = 0
for _ in pairs(t) do
n = n + 1
end
return n
end
------------------------------------------------------------------------
-- Zahlen und Geld
------------------------------------------------------------------------
function Util.Round(n, decimals)
local mult = 10 ^ (decimals or 0)
return math.floor(n * mult + 0.5) / mult
end
function Util.Clamp(n, lo, hi)
if n < lo then
return lo
end
if n > hi then
return hi
end
return n
end
function Util.FormatNumber(n)
local text = string.format("%.0f", math.floor(math.abs(n) + 0.5))
local sep = (FW.activeLocale == "deDE") and "." or ","
local count
repeat
text, count = text:gsub("^(%d+)(%d%d%d)", "%1" .. sep .. "%2")
until count == 0
if n < 0 then
text = "-" .. text
end
return text
end
local MONEY_COLORS = { g = "f0bf5c", s = "c4c8d0", c = "c8793c" }
-- Kupfer -> "12G 34S 56K". opts.plain: ohne Farben, opts.noCopper: Kupfer weglassen
function Util.FormatMoney(copper, opts)
opts = opts or {}
copper = tonumber(copper) or 0
local negative = copper < 0
if negative then
copper = -copper
end
copper = math.floor(copper)
local gold = math.floor(copper / 10000)
local silver = math.floor((copper % 10000) / 100)
local rest = copper % 100
local L = FW.L
local function unit(key, symbol)
if opts.plain then
return symbol
end
return "|cff" .. MONEY_COLORS[key] .. symbol .. "|r"
end
local parts = {}
if gold > 0 then
parts[#parts + 1] = Util.FormatNumber(gold) .. unit("g", L.MONEY_G)
end
if silver > 0 then
parts[#parts + 1] = silver .. unit("s", L.MONEY_S)
end
if rest > 0 and not (opts.noCopper and gold > 0) then
parts[#parts + 1] = rest .. unit("c", L.MONEY_C)
end
if #parts == 0 then
parts[1] = "0" .. unit("c", L.MONEY_C)
end
local text = table.concat(parts, " ")
if negative then
text = "-" .. text
end
return text
end
------------------------------------------------------------------------
-- Pruefsumme (Adler-32-Variante, nur Rechnen mit kleinen Zahlen)
------------------------------------------------------------------------
function Util.Hash(text)
local a, b = 1, 0
for i = 1, #text do
a = (a + text:byte(i)) % 65521
b = (b + a) % 65521
end
return string.format("%04x%04x", b, a)
end
------------------------------------------------------------------------
-- Items (mit asynchronem Nachladen)
------------------------------------------------------------------------
local function RawItemInfo(idOrLink)
if C_Item and C_Item.GetItemInfo then
return C_Item.GetItemInfo(idOrLink)
end
return GetItemInfo(idOrLink)
end
-- Liefert eine Tabelle oder nil, wenn die Daten noch nicht im Cache sind.
function Util.GetItem(idOrLink)
local name, link, quality, itemLevel, minLevel, itemType, itemSubType,
stackCount, equipLoc, icon, sellPrice, classID, subClassID = RawItemInfo(idOrLink)
if not name then
return nil
end
local id = tonumber(idOrLink)
if not id and type(link) == "string" then
id = tonumber(link:match("item:(%d+)"))
end
return {
id = id,
name = name,
link = link,
quality = quality or 1,
itemLevel = itemLevel,
minLevel = minLevel,
type = itemType,
subType = itemSubType,
stackCount = stackCount,
equipLoc = equipLoc,
icon = icon,
sellPrice = sellPrice or 0,
classID = classID,
subClassID = subClassID,
}
end
local pendingItems = {}
-- Ruft callback(info) auf, sobald die Item-Daten da sind (sofort, falls schon im Cache).
-- info ist nil, wenn das Item nicht geladen werden konnte.
function Util.RequestItem(itemID, callback)
local info = Util.GetItem(itemID)
if info then
FW.SafeCall(callback, info)
return
end
local list = pendingItems[itemID]
if not list then
list = {}
pendingItems[itemID] = list
end
list[#list + 1] = callback
if C_Item and C_Item.RequestLoadItemDataByID then
C_Item.RequestLoadItemDataByID(itemID)
end
end
FW:RegisterEvent("GET_ITEM_INFO_RECEIVED", function(itemID, success)
local list = pendingItems[itemID]
if not list then
return
end
pendingItems[itemID] = nil
local info = success and Util.GetItem(itemID) or nil
for _, callback in ipairs(list) do
FW.SafeCall(callback, info)
end
end)
------------------------------------------------------------------------
-- Kreaturen und Orte
------------------------------------------------------------------------
-- GUID "Creature-0-4621-1-27730-4107-0000477262" -> 4107 (sechstes Feld)
-- Octo/1.12: GUID als Hex "0xF130003A5C0012AB" = Typ (F13 Kreatur, F14 Begleiter,
-- F11 Objekt, F15 Fahrzeug), Eintrags-ID (6 Hex), Zaehler (6 Hex).
-- Rueckgabe: "Creature" / "Pet" / "GameObject" / "Vehicle", ID; sonst nil.
local HEX_KIND = { F13 = "Creature", F14 = "Pet", F11 = "GameObject", F15 = "Vehicle" }
function Util.ParseHexGUID(guid)
if type(guid) ~= "string" then
return nil
end
local hex = guid:match("^0[xX](%x+)$")
if not hex or #hex ~= 16 then
return nil
end
local kind = HEX_KIND[hex:sub(1, 3):upper()]
if not kind then
return nil
end
return kind, tonumber(hex:sub(5, 10), 16)
end
function Util.GetNpcID(guid)
if type(guid) ~= "string" then
return nil
end
local hk, hid = Util.ParseHexGUID(guid)
if hk then
if hk == "Creature" or hk == "Vehicle" then
return hid
end
return nil
end
local kind, _, _, _, _, id = strsplit("-", guid)
if kind == "Creature" or kind == "Vehicle" then
return tonumber(id)
end
return nil
end
-- Aktuelle Position des Spielers. x und y sind 0..1 auf der Karte, koennen
-- in Instanzen fehlen. Die Koordinaten sind die des Spielers, nicht des Ziels.
function Util.GetPlayerLocation()
local mapID = C_Map and C_Map.GetBestMapForUnit and C_Map.GetBestMapForUnit("player")
local x, y
if mapID then
local pos = C_Map.GetPlayerMapPosition(mapID, "player")
if pos then
x, y = pos:GetXY()
end
end
return {
mapID = mapID,
x = x and Util.Round(x, 4) or nil,
y = y and Util.Round(y, 4) or nil,
zone = GetZoneText(),
subzone = GetSubZoneText(),
t = time(),
}
end
function Util.GetPlayerKey()
local name = UnitName("player")
local realm = GetRealmName() or ""
return name .. "-" .. (realm:gsub("%s", ""))
end
+20
View File
@@ -0,0 +1,20 @@
## Interface: 11200
## Title: Freewood |cffc89b3cCore|r
## Notes: Base addon of the Freewood family: data storage, UI kit, export/import and the main window.
## Notes-deDE: Basis-Addon der Freewood-Familie: Datenspeicher, UI-Bausteine, Export/Import und Hauptfenster.
## Author: Lacustat
## Version: 0.1.136
## SavedVariables: FreewoodDB
## SavedVariablesPerCharacter: FreewoodCharDB
Core\Compat.lua
Locales.lua
Core\Init.lua
Core\Util.lua
Core\Export.lua
UI\Kit.lua
UI\MainFrame.lua
UI\MainWindow.lua
UI\Tabs.lua
UI\Minimap.lua
Core\Slash.lua
+674
View File
@@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.
+186
View File
@@ -0,0 +1,186 @@
-- Freewood-OctoWoW-Core: Texte (Deutsch / Englisch)
-- Fehlende Schluessel fallen auf Englisch und dann auf den Schluessel selbst zurueck.
-- Module koennen eigene Texte mit Freewood:AddLocale("deDE", { ... }) ergaenzen.
local ADDON, FW = ...
FW.locales = { enUS = {}, deDE = {} }
FW.activeLocale = "enUS"
function FW:AddLocale(code, strings)
local target = self.locales[code]
if not target then
target = {}
self.locales[code] = target
end
for key, value in pairs(strings) do
target[key] = value
end
end
function FW:SetLocale(code)
if not self.locales[code] then
code = "enUS"
end
self.activeLocale = code
end
FW.L = setmetatable({}, {
__index = function(_, key)
local active = FW.locales[FW.activeLocale]
local value = active and active[key]
if value == nil then
value = FW.locales.enUS[key]
end
if value == nil then
return key
end
return value
end,
})
FW:AddLocale("enUS", {
TITLE = "Freewood",
TAB_OVERVIEW = "Overview",
TAB_EXCHANGE = "Exchange",
TAB_SETTINGS = "Settings",
MONEY_G = "g",
MONEY_S = "s",
MONEY_C = "c",
OVERVIEW_HEADER = "Installed modules",
OVERVIEW_NONE = "No modules installed yet. Modules appear here as soon as they are loaded (for example Freewood-OctoWoW-Pet).",
OVERVIEW_INFO = "Core %s | Game %s (build %s)",
MODULE_ACTIVE = "active",
MODULE_INACTIVE = "inactive",
EXCHANGE_INTRO = "Export your data as a short code, or paste a code from another player to import it. Imported data never overwrites your own entries.",
EXCHANGE_NO_HANDLERS = "No module offers export or import yet.",
EXCHANGE_EXPORT = "Export",
EXCHANGE_IMPORT = "Import",
EXCHANGE_CLEAR = "Clear",
EXCHANGE_EXPORTED = "Exported: %d characters. Copy the code with Ctrl+C.",
EXCHANGE_IMPORTED = "Import finished.",
EXCHANGE_EMPTY = "Nothing to import. Paste a code first.",
EXCHANGE_NOTHING_EXPORT = "There is nothing to export for this type yet.",
ERR_FORMAT = "This is not a valid Freewood code.",
ERR_TOO_LARGE = "The code is too large.",
ERR_CHECKSUM = "The code is damaged or incomplete (checksum does not match).",
ERR_DECODE = "The code could not be read.",
ERR_VERSION = "The code was created with a newer data format. Please update Freewood.",
ERR_NOAPI = "This game version does not offer the required encoding functions.",
ERR_INVALID = "The data in the code is invalid.",
ERR_NO_HANDLER = "No installed module can handle data of the type \"%s\".",
SETTINGS_HEADER = "Settings",
SETTINGS_MINIMAP = "Show minimap button",
SETTINGS_DEBUG = "Show debug messages in chat",
SETTINGS_SCALE = "Window scale",
SETTINGS_LANGUAGE = "Language",
SETTINGS_LANGUAGE_NOTE = "A language change takes effect after /reload.",
SETTINGS_RESET_POS = "Reset window position and size",
WINDOW_RESIZE = "Resize",
WINDOW_RESIZE_TIP = "Drag to change the window size. The size is remembered.",
WINDOW_SCALE = "Window scale",
WINDOW_SCALE_TIP = "Drag to scale the whole window from 50 % to 150 %. The value is applied when you let go and is remembered.",
LANG_AUTO = "Automatic",
LANG_DE = "Deutsch",
LANG_EN = "English",
MINIMAP_TIP_LEFT = "Left click: open or close the window",
MINIMAP_TIP_RIGHT = "Right click: settings",
MINIMAP_TIP_DRAG = "Drag: move the button",
SLASH_HELP_TITLE = "Commands:",
SLASH_HELP_1 = "/fw - open or close the window",
SLASH_HELP_2 = "/fw settings - open the settings",
SLASH_HELP_3 = "/fw exchange - open export and import",
SLASH_HELP_4 = "/fw minimap - show or hide the minimap button",
SLASH_HELP_5 = "/fw modules - list loaded modules",
SLASH_HELP_6 = "/fw debug - toggle debug messages",
SLASH_HELP_7 = "/fw reset - reset the window position",
SLASH_VERSION = "Core %s, API %d, interface %s",
SLASH_NO_MODULES = "No modules loaded.",
SLASH_DEBUG_ON = "Debug messages enabled.",
SLASH_DEBUG_OFF = "Debug messages disabled.",
SLASH_RESET_DONE = "Window position reset.",
DB_NEWER = "The saved data was written by a newer Freewood version. Features are limited until you update.",
API_TOO_OLD = "Module %s needs Freewood-OctoWoW-Core API %d, installed is %d. Please update Freewood-OctoWoW-Core.",
})
FW:AddLocale("deDE", {
TITLE = "Freewood",
TAB_OVERVIEW = "Übersicht",
TAB_EXCHANGE = "Austausch",
TAB_SETTINGS = "Einstellungen",
MONEY_G = "G",
MONEY_S = "S",
MONEY_C = "K",
OVERVIEW_HEADER = "Installierte Module",
OVERVIEW_NONE = "Noch keine Module installiert. Module erscheinen hier, sobald sie geladen sind (zum Beispiel Freewood-OctoWoW-Pet).",
OVERVIEW_INFO = "Core %s | Spiel %s (Build %s)",
MODULE_ACTIVE = "aktiv",
MODULE_INACTIVE = "inaktiv",
EXCHANGE_INTRO = "Exportiere deine Daten als kurzen Code oder füge den Code eines anderen Spielers ein, um ihn zu importieren. Importierte Daten überschreiben nie deine eigenen Einträge.",
EXCHANGE_NO_HANDLERS = "Noch kein Modul bietet Export oder Import an.",
EXCHANGE_EXPORT = "Exportieren",
EXCHANGE_IMPORT = "Importieren",
EXCHANGE_CLEAR = "Leeren",
EXCHANGE_EXPORTED = "Exportiert: %d Zeichen. Kopiere den Code mit Strg+C.",
EXCHANGE_IMPORTED = "Import abgeschlossen.",
EXCHANGE_EMPTY = "Nichts zu importieren. Füge zuerst einen Code ein.",
EXCHANGE_NOTHING_EXPORT = "Für diese Datenart gibt es noch nichts zu exportieren.",
ERR_FORMAT = "Das ist kein gültiger Freewood-Code.",
ERR_TOO_LARGE = "Der Code ist zu groß.",
ERR_CHECKSUM = "Der Code ist beschädigt oder unvollständig (Prüfsumme stimmt nicht).",
ERR_DECODE = "Der Code konnte nicht gelesen werden.",
ERR_VERSION = "Der Code stammt aus einem neueren Datenformat. Bitte aktualisiere Freewood.",
ERR_NOAPI = "Diese Spielversion bietet die nötigen Kodierfunktionen nicht.",
ERR_INVALID = "Die Daten im Code sind ungültig.",
ERR_NO_HANDLER = "Kein installiertes Modul kann Daten der Art „%s“ verarbeiten.",
SETTINGS_HEADER = "Einstellungen",
SETTINGS_MINIMAP = "Minimap-Knopf anzeigen",
SETTINGS_DEBUG = "Debug-Meldungen im Chat anzeigen",
SETTINGS_SCALE = "Fenstergröße",
SETTINGS_LANGUAGE = "Sprache",
SETTINGS_LANGUAGE_NOTE = "Eine Sprachänderung wirkt nach /reload.",
SETTINGS_RESET_POS = "Position und Größe zurücksetzen",
WINDOW_RESIZE = "Größe ändern",
WINDOW_RESIZE_TIP = "Ziehen, um die Fenstergröße zu ändern. Die Größe wird gespeichert.",
WINDOW_SCALE = "Fenstergröße",
WINDOW_SCALE_TIP = "Ziehen, um das ganze Fenster von 50 % bis 150 % zu skalieren. Der Wert gilt beim Loslassen und wird gespeichert.",
LANG_AUTO = "Automatisch",
LANG_DE = "Deutsch",
LANG_EN = "English",
MINIMAP_TIP_LEFT = "Linksklick: Fenster öffnen oder schließen",
MINIMAP_TIP_RIGHT = "Rechtsklick: Einstellungen",
MINIMAP_TIP_DRAG = "Ziehen: Knopf verschieben",
SLASH_HELP_TITLE = "Befehle:",
SLASH_HELP_1 = "/fw - Fenster öffnen oder schließen",
SLASH_HELP_2 = "/fw settings - Einstellungen öffnen",
SLASH_HELP_3 = "/fw exchange - Export und Import öffnen",
SLASH_HELP_4 = "/fw minimap - Minimap-Knopf ein- oder ausblenden",
SLASH_HELP_5 = "/fw modules - geladene Module auflisten",
SLASH_HELP_6 = "/fw debug - Debug-Meldungen umschalten",
SLASH_HELP_7 = "/fw reset - Fensterposition zurücksetzen",
SLASH_VERSION = "Core %s, API %d, Interface %s",
SLASH_NO_MODULES = "Keine Module geladen.",
SLASH_DEBUG_ON = "Debug-Meldungen eingeschaltet.",
SLASH_DEBUG_OFF = "Debug-Meldungen ausgeschaltet.",
SLASH_RESET_DONE = "Fensterposition zurückgesetzt.",
DB_NEWER = "Die gespeicherten Daten stammen von einer neueren Freewood-Version. Der Funktionsumfang ist bis zum Update eingeschränkt.",
API_TOO_OLD = "Modul %s benötigt Freewood-OctoWoW-Core API %d, installiert ist %d. Bitte aktualisiere Freewood-OctoWoW-Core.",
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

+2
View File
@@ -0,0 +1,2 @@
# Freewood-OctoWoW-Core
Freewood-OctoWoW-Core
+1039
View File
File diff suppressed because it is too large Load Diff
+127
View File
@@ -0,0 +1,127 @@
-- Freewood-OctoWoW-Core: Aufbau des Hauptfensters (OctoWoW)
--
-- Ersetzt MainWindow.xml. OctoWoW (1.12.1) bricht beim Laden ab, sobald ein <Size> nur x ODER y hat
-- ("No x or y attributes in element: Size"); dann entstehen keine Unterframes und das ganze Fenster fehlt.
-- Der Aufbau in Lua ist unabhaengig vom XML-Parser des Clients. Die Struktur ist dieselbe wie vorher:
-- FreewoodMainFrame.Header (Bg, Accent, Logo, Title, Subtitle)
-- FreewoodMainFrame.TabBar (Bg, Line)
-- FreewoodMainFrame.Footer (Bg, Left)
-- FreewoodMainFrame.Content
-- Farben: Hintergrund #0f131f, Kopfzeile #0c0e14, Leiste #141824, Gold #c89b3c, Bronze #9d7826.
local ADDON, FW = ...
if _G.FreewoodMainFrame then
return
end
local function Solid(tex, r, g, b, a)
tex:SetTexture(r, g, b, a)
end
local function NewTexture(parent, layer, r, g, b, a)
local t = parent:CreateTexture(nil, layer)
Solid(t, r, g, b, a)
return t
end
local frame = CreateFrame("Frame", "FreewoodMainFrame", UIParent)
frame:SetFrameStrata("HIGH")
frame:SetToplevel(true)
frame:SetMovable(true)
frame:SetClampedToScreen(true)
frame:EnableMouse(true)
frame:SetWidth(960)
frame:SetHeight(760)
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
frame:Hide()
-- Hintergrund und Rahmen
frame.Bg = NewTexture(frame, "BACKGROUND", 0.059, 0.075, 0.122, 0.97)
frame.Bg:SetAllPoints(frame)
local gold = { 0.616, 0.471, 0.149, 1 }
frame.BorderTop = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderTop:SetHeight(1)
frame.BorderTop:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
frame.BorderTop:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
frame.BorderBottom = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderBottom:SetHeight(1)
frame.BorderBottom:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
frame.BorderBottom:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
frame.BorderLeft = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderLeft:SetWidth(1)
frame.BorderLeft:SetPoint("TOPLEFT", frame, "TOPLEFT", 0, 0)
frame.BorderLeft:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 0, 0)
frame.BorderRight = NewTexture(frame, "BORDER", gold[1], gold[2], gold[3], gold[4])
frame.BorderRight:SetWidth(1)
frame.BorderRight:SetPoint("TOPRIGHT", frame, "TOPRIGHT", 0, 0)
frame.BorderRight:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", 0, 0)
-- Kopfzeile mit Titel
local header = CreateFrame("Frame", nil, frame)
header:SetHeight(48)
header:SetPoint("TOPLEFT", frame, "TOPLEFT", 1, -1)
header:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -1, -1)
frame.Header = header
header.Bg = NewTexture(header, "BACKGROUND", 0.047, 0.055, 0.078, 1)
header.Bg:SetAllPoints(header)
header.Accent = NewTexture(header, "ARTWORK", 0.784, 0.608, 0.235, 0.55)
header.Accent:SetHeight(1)
header.Accent:SetPoint("BOTTOMLEFT", header, "BOTTOMLEFT", 0, 0)
header.Accent:SetPoint("BOTTOMRIGHT", header, "BOTTOMRIGHT", 0, 0)
header.Logo = header:CreateTexture(nil, "ARTWORK")
header.Logo:SetTexture("Interface\\AddOns\\Freewood-OctoWoW-Core\\Media\\FreeWood_Logo")
header.Logo:SetWidth(40)
header.Logo:SetHeight(40)
header.Logo:SetPoint("LEFT", header, "LEFT", 14, 0)
header.Title = header:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
header.Title:SetJustifyH("LEFT")
header.Title:SetPoint("LEFT", header.Logo, "RIGHT", 10, 0)
header.Subtitle = header:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
header.Subtitle:SetJustifyH("LEFT")
header.Subtitle:SetPoint("LEFT", header.Title, "RIGHT", 12, -2)
-- Leiste fuer die Tabs
local tabBar = CreateFrame("Frame", nil, frame)
tabBar:SetHeight(36)
tabBar:SetPoint("TOPLEFT", header, "BOTTOMLEFT", 0, 0)
tabBar:SetPoint("TOPRIGHT", header, "BOTTOMRIGHT", 0, 0)
frame.TabBar = tabBar
tabBar.Bg = NewTexture(tabBar, "BACKGROUND", 0.078, 0.094, 0.141, 1)
tabBar.Bg:SetAllPoints(tabBar)
tabBar.Line = NewTexture(tabBar, "ARTWORK", 0.616, 0.471, 0.149, 0.5)
tabBar.Line:SetHeight(1)
tabBar.Line:SetPoint("BOTTOMLEFT", tabBar, "BOTTOMLEFT", 0, 0)
tabBar.Line:SetPoint("BOTTOMRIGHT", tabBar, "BOTTOMRIGHT", 0, 0)
-- Fusszeile
local footer = CreateFrame("Frame", nil, frame)
footer:SetHeight(26)
footer:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 1, 1)
footer:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -1, 1)
frame.Footer = footer
footer.Bg = NewTexture(footer, "BACKGROUND", 0.047, 0.055, 0.078, 1)
footer.Bg:SetAllPoints(footer)
footer.Left = footer:CreateFontString(nil, "ARTWORK", "GameFontNormalSmall")
footer.Left:SetJustifyH("LEFT")
footer.Left:SetPoint("LEFT", footer, "LEFT", 16, 0)
-- Inhaltsbereich (hier setzen die Tabs ihre Frames ein)
local content = CreateFrame("Frame", nil, frame)
content:SetPoint("TOPLEFT", tabBar, "BOTTOMLEFT", 20, -18)
content:SetPoint("BOTTOMRIGHT", footer, "TOPRIGHT", -20, 12)
frame.Content = content
+353
View File
@@ -0,0 +1,353 @@
-- Freewood-OctoWoW-Core: Verhalten des Hauptfensters
-- Aufbau und Farben stehen in MainWindow.xml. Hier: Ziehen, Position merken,
-- Tabs registrieren und umschalten, Oeffnen/Schliessen.
local ADDON, FW = ...
local UI = FW.UI
local frame = _G.FreewoodMainFrame
if not frame then
return
end
UI.window = frame
table.insert(UISpecialFrames, "FreewoodMainFrame") -- Escape schliesst das Fenster
local tabs = {} -- Reihenfolge der Tabs
local tabsById = {}
local activeId
local built = false
------------------------------------------------------------------------
-- Kopf, Ziehen, Position
------------------------------------------------------------------------
local header = frame.Header
UI.SetFont(header.Title, "heading", 20)
header.Title:SetTextColor(UI.Color("goldLight"))
header.Title:SetText("Freewood")
UI.SetFont(header.Subtitle, "body", 11)
header.Subtitle:SetTextColor(UI.Color("textDim"))
UI.SetFont(frame.Footer.Left, "body", 11)
frame.Footer.Left:SetTextColor(UI.Color("textDim"))
local closeButton = UI.CreateButton(header, "X", 30, 30, function()
frame:Hide()
end, "ghost")
closeButton:SetPoint("RIGHT", header, "RIGHT", -10, 0)
-- Regler fuer die Fenstergroesse (50 bis 150 Prozent) in der Kopfzeile
local scaleText = UI.CreateLabel(header, "100%", { size = 11, color = "textDim", justify = "RIGHT" })
scaleText:SetWidth(40)
scaleText:SetPoint("RIGHT", closeButton, "LEFT", -12, 0)
local scaleSlider = UI.CreateSlider(header, 150, {
min = 50, max = 150, step = 5,
get = function() return math.floor(UI:GetScale() * 100 + 0.5) end,
set = function(v) UI:SetScale(v / 100) end,
})
scaleSlider:SetPoint("RIGHT", scaleText, "LEFT", -8, 0)
scaleSlider.onText = function(v) scaleText:SetText(string.format("%d%%", v)) end
UI.AttachTooltip(scaleSlider, function() return FW.L.WINDOW_SCALE end, function() return FW.L.WINDOW_SCALE_TIP end)
UI.scaleSlider = scaleSlider
local scaleListeners = {}
-- Andere Bedienelemente (Einstellungen: - / +) melden sich an, um bei einer Aenderung nachzuziehen
function UI:OnScaleChanged(fn)
scaleListeners[#scaleListeners + 1] = fn
end
header:EnableMouse(true)
header:RegisterForDrag("LeftButton")
header:SetScript("OnDragStart", function()
frame:StartMoving()
end)
header:SetScript("OnDragStop", function()
frame:StopMovingOrSizing()
local point, _, relativePoint, x, y = frame:GetPoint(1)
if point and FW.settings then
FW.settings.windowPos = { point = point, relativePoint = relativePoint, x = x, y = y }
end
end)
function UI:ApplyPosition()
local pos = FW.settings and FW.settings.windowPos
frame:ClearAllPoints()
if type(pos) == "table" and pos.point then
frame:SetPoint(pos.point, UIParent, pos.relativePoint or pos.point, pos.x or 0, pos.y or 0)
else
frame:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
end
end
------------------------------------------------------------------------
-- Groesse: Standard, Grenzen, Ziehgriff unten rechts
------------------------------------------------------------------------
local DEFAULT_W, DEFAULT_H = 1240, 840
local MIN_W, MIN_H = 1240, 840 -- so gross, dass die Einstellungen komplett passen
local MAX_W, MAX_H = 1500, 1100
-- Grenzen passend zum Bildschirm (Fenster soll nicht groesser als der Bildschirm werden)
local function SizeLimits()
local scale = frame:GetScale() or 1
local sw = ((UIParent and UIParent:GetWidth()) or 1920) / scale
local sh = ((UIParent and UIParent:GetHeight()) or 1080) / scale
local maxW = math.max(MIN_W, math.min(MAX_W, sw - 20))
local maxH = math.max(MIN_H, math.min(MAX_H, sh - 20))
return maxW, maxH
end
local function ClampSize(w, h)
local maxW, maxH = SizeLimits()
w = math.min(math.max(tonumber(w) or DEFAULT_W, MIN_W), maxW)
h = math.min(math.max(tonumber(h) or DEFAULT_H, MIN_H), maxH)
return w, h
end
function UI:ApplySize()
local saved = FW.settings and FW.settings.windowSize
local w, h = DEFAULT_W, DEFAULT_H
if type(saved) == "table" then
w, h = saved.w or w, saved.h or h
end
frame:SetSize(ClampSize(w, h))
end
local function SetupResize()
if frame.SetResizable then
frame:SetResizable(true)
end
local maxW, maxH = SizeLimits()
if frame.SetResizeBounds then
frame:SetResizeBounds(MIN_W, MIN_H, maxW, maxH)
else
if frame.SetMinResize then frame:SetMinResize(MIN_W, MIN_H) end
if frame.SetMaxResize then frame:SetMaxResize(maxW, maxH) end
end
local grip = CreateFrame("Button", nil, frame)
grip:SetSize(18, 18)
grip:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -3, 3)
grip:SetFrameLevel((frame:GetFrameLevel() or 1) + 30)
grip:SetNormalTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Up")
grip:SetPushedTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Down")
grip:SetHighlightTexture("Interface\\ChatFrame\\UI-ChatIM-SizeGrabber-Highlight")
grip:SetScript("OnMouseDown", function()
local mw, mh = SizeLimits()
if frame.SetResizeBounds then
frame:SetResizeBounds(MIN_W, MIN_H, mw, mh)
end
frame:StartSizing("BOTTOMRIGHT")
end)
grip:SetScript("OnMouseUp", function()
frame:StopMovingOrSizing()
local w, h = ClampSize(frame:GetWidth(), frame:GetHeight())
frame:SetSize(w, h)
if FW.settings then
FW.settings.windowSize = { w = math.floor(w + 0.5), h = math.floor(h + 0.5) }
end
local point, _, relativePoint, x, y = frame:GetPoint(1)
if point and FW.settings then
FW.settings.windowPos = { point = point, relativePoint = relativePoint, x = x, y = y }
end
end)
UI.AttachTooltip(grip, function() return FW.L.WINDOW_RESIZE end, function() return FW.L.WINDOW_RESIZE_TIP end)
frame.resizeGrip = grip
end
function UI:ResetPosition()
if FW.settings then
FW.settings.windowPos = false
FW.settings.windowSize = false
end
self:ApplySize()
self:ApplyPosition()
end
function UI:SetScale(scale)
scale = FW.Util.Clamp(math.floor(scale * 20 + 0.5) / 20, 0.5, 1.5)
if FW.settings then
FW.settings.scale = scale
end
frame:SetScale(scale)
self:ApplySize()
scaleSlider:Refresh()
for _, fn in ipairs(scaleListeners) do
pcall(fn, scale)
end
end
function UI:GetScale()
return (FW.settings and FW.settings.scale) or 1
end
FW:On("FW_DB_READY", function()
frame:SetScale(UI:GetScale())
scaleSlider:Refresh()
SetupResize()
UI:ApplySize()
UI:ApplyPosition()
header.Subtitle:SetText("v" .. FW.version)
end)
------------------------------------------------------------------------
-- Tabs
-- def.label Text oder Funktion, die den Text liefert
-- def.order Reihenfolge (kleiner = weiter links)
-- def.build function(content) -> Frame (wird beim ersten Anzeigen aufgerufen)
-- def.onShow function(tabFrame) (bei jedem Anzeigen)
------------------------------------------------------------------------
local function ResolveLabel(def)
local label = def.label
if type(label) == "function" then
label = label()
end
return label or "?"
end
local TAB_ROW_H = 34
-- Tabs in Zeilen umbrechen, wenn sie nicht in eine Zeile passen; die Leiste waechst mit,
-- der Inhaltsbereich haengt an ihrem unteren Rand und rutscht mit.
local function LayoutTabs()
table.sort(tabs, function(a, b)
if a.order ~= b.order then
return a.order < b.order
end
return a.seq < b.seq
end)
local barW = frame.TabBar:GetWidth()
if not barW or barW < 100 then
barW = (frame:GetWidth() or 1000) - 2
end
local limit = barW - 12
local x, row = 12, 0
local rowOf = {}
for _, tab in ipairs(tabs) do
if tab.button then
local w = tab.button:GetWidth()
if x + w > limit and x > 12 then
row = row + 1
x = 12
end
rowOf[tab] = { row = row, x = x }
x = x + w + 4
end
end
local rows = row + 1
frame.TabBar:SetHeight(rows * TAB_ROW_H + 2)
for _, tab in ipairs(tabs) do
local at = rowOf[tab]
if at then
tab.button:ClearAllPoints()
tab.button:SetPoint("BOTTOMLEFT", frame.TabBar, "BOTTOMLEFT", at.x, 1 + (rows - 1 - at.row) * TAB_ROW_H)
end
end
end
local function CreateTabButton(tab)
local button = UI.CreateTabButton(frame.TabBar, ResolveLabel(tab.def))
button:SetScript("OnClick", function()
UI:SelectTab(tab.id)
end)
tab.button = button
end
function UI:RegisterTab(id, def)
if tabsById[id] then
return tabsById[id]
end
local tab = { id = id, def = def, order = def.order or 100, seq = #tabs + 1 }
tabsById[id] = tab
tabs[#tabs + 1] = tab
if built then
CreateTabButton(tab)
LayoutTabs()
end
return tab
end
function UI:SelectTab(id)
local tab = tabsById[id]
if not tab then
return
end
for _, other in ipairs(tabs) do
if other.frame then
other.frame:Hide()
end
if other.button then
other.button:SetActive(other == tab)
end
end
if not tab.frame and tab.def.build then
tab.frame = tab.def.build(frame.Content)
if tab.frame then
tab.frame:SetAllPoints(frame.Content)
end
end
if tab.frame then
tab.frame:Show()
if tab.def.onShow then
FW.SafeCall(tab.def.onShow, tab.frame)
end
end
activeId = id
end
local function BuildOnce()
if built then
return
end
built = true
for _, tab in ipairs(tabs) do
CreateTabButton(tab)
end
LayoutTabs()
frame:HookScript("OnSizeChanged", function()
LayoutTabs()
end)
end
frame:SetScript("OnShow", function()
BuildOnce()
local _, _, _, toc = GetBuildInfo()
local left = FW.L.TITLE .. " | " .. string.format(FW.L.SLASH_VERSION, FW.version, FW.API_VERSION, tostring(toc or "?"))
frame.Footer.Left:SetText(left)
UI:SelectTab(activeId or (tabs[1] and tabs[1].id))
end)
------------------------------------------------------------------------
-- Oeffnen und Schliessen
------------------------------------------------------------------------
function UI:Show(tabId)
if tabId then
activeId = tabId
end
if frame:IsShown() then
if tabId then
self:SelectTab(tabId)
end
else
frame:Show() -- OnShow waehlt den Tab
end
end
function UI:Hide()
frame:Hide()
end
function UI:Toggle(tabId)
if frame:IsShown() then
frame:Hide()
else
self:Show(tabId)
end
end
+100
View File
@@ -0,0 +1,100 @@
-- Freewood-OctoWoW-Core: Minimap-Knopf
-- Links: Fenster, Rechts: Einstellungen, Ziehen: um die Minimap verschieben.
-- Annahme: runde Minimap.
local ADDON, FW = ...
local UI = FW.UI
local Minimap_ = _G.Minimap
local M = {}
FW.Minimap = M
local button = CreateFrame("Button", "FreewoodMinimapButton", Minimap_)
button:SetSize(32, 32)
button:SetFrameStrata("MEDIUM")
button:SetFrameLevel(8)
button:RegisterForClicks("LeftButtonUp", "RightButtonUp")
button:RegisterForDrag("LeftButton")
local overlay = button:CreateTexture(nil, "OVERLAY")
overlay:SetSize(53, 53)
overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
overlay:SetPoint("TOPLEFT", button, "TOPLEFT", 0, 0)
local background = button:CreateTexture(nil, "BACKGROUND")
background:SetSize(20, 20)
background:SetTexture("Interface\\Minimap\\UI-Minimap-Background")
background:SetPoint("TOPLEFT", button, "TOPLEFT", 7, -5)
local icon = button:CreateTexture(nil, "ARTWORK")
icon:SetSize(17, 17)
icon:SetTexture("Interface\\AddOns\\Freewood-OctoWoW-Core\\Media\\FreeWood_Logo.tga")
icon:SetPoint("TOPLEFT", button, "TOPLEFT", 7, -6)
button:SetHighlightTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
local atan2 = math.atan2 or math.atan
local function UpdatePosition()
local angle = math.rad((FW.settings and FW.settings.minimap.angle) or 215)
local radius = (Minimap_:GetWidth() / 2) + 5
button:ClearAllPoints()
button:SetPoint("CENTER", Minimap_, "CENTER", math.cos(angle) * radius, math.sin(angle) * radius)
end
button:SetScript("OnDragStart", function(self)
self:SetScript("OnUpdate", function()
local mx, my = Minimap_:GetCenter()
local px, py = GetCursorPosition()
local scale = Minimap_:GetEffectiveScale()
px, py = px / scale, py / scale
FW.settings.minimap.angle = math.deg(atan2(py - my, px - mx)) % 360
UpdatePosition()
end)
end)
button:SetScript("OnDragStop", function(self)
self:SetScript("OnUpdate", nil)
end)
button:SetScript("OnClick", function(_, mouseButton)
if mouseButton == "RightButton" then
UI:Show("settings")
else
UI:Toggle()
end
end)
button:SetScript("OnEnter", function(self)
local L = FW.L
GameTooltip:SetOwner(self, "ANCHOR_LEFT")
GameTooltip:SetText("Freewood", 0.94, 0.75, 0.36)
GameTooltip:AddLine(L.MINIMAP_TIP_LEFT, 0.8, 0.8, 0.8)
GameTooltip:AddLine(L.MINIMAP_TIP_RIGHT, 0.8, 0.8, 0.8)
GameTooltip:AddLine(L.MINIMAP_TIP_DRAG, 0.6, 0.6, 0.6)
GameTooltip:Show()
end)
button:SetScript("OnLeave", function()
GameTooltip:Hide()
end)
function M:SetHidden(hidden)
FW.settings.minimap.hide = hidden and true or false
if hidden then
button:Hide()
else
button:Show()
end
end
M.button = button
FW:On("FW_DB_READY", function()
UpdatePosition()
if FW.settings.minimap.hide then
button:Hide()
else
button:Show()
end
end)
+541
View File
@@ -0,0 +1,541 @@
-- Freewood-OctoWoW-Core: die drei eigenen Tabs (Uebersicht, Austausch, Einstellungen)
-- Module registrieren ihre Tabs ueber Freewood.UI:RegisterTab(...), siehe MainWindow.lua.
local ADDON, FW = ...
local UI = FW.UI
local Export = FW.Export
------------------------------------------------------------------------
-- Uebersicht: geladene Module
------------------------------------------------------------------------
local function BuildOverview(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
local title = UI.CreateLabel(f, L.OVERVIEW_HEADER, { font = "heading", size = 16, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local _, build, _, toc = GetBuildInfo()
local gameVersion = (GetBuildInfo())
local info = UI.CreateLabel(f,
string.format(L.OVERVIEW_INFO, FW.version, tostring(gameVersion), tostring(build)),
{ size = 11, color = "textDim", justify = "RIGHT" })
info:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -2)
local divider = UI.CreateDivider(f)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -30)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -30)
local scroll = UI.CreateScrollFrame(f)
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -44)
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
local empty = UI.CreateLabel(scroll.child, L.OVERVIEW_NONE, { size = 12, color = "textDim", justifyV = "TOP" })
empty:SetPoint("TOPLEFT", scroll.child, "TOPLEFT", 0, 0)
empty:SetPoint("TOPRIGHT", scroll.child, "TOPRIGHT", 0, 0)
local ROW_H, GAP = 58, 8
local rows = {}
local function GetRow(i)
local row = rows[i]
if row then
return row
end
row = UI.CreatePanel(scroll.child, "card", "bronze")
row:SetHeight(ROW_H)
row.name = UI.CreateLabel(row, "", { font = "heading", size = 14, color = "goldLight" })
row.name:SetPoint("TOPLEFT", row, "TOPLEFT", 14, -10)
row.version = UI.CreateLabel(row, "", { size = 11, color = "textDim" })
row.version:SetPoint("LEFT", row.name, "RIGHT", 10, 0)
row.desc = UI.CreateLabel(row, "", { size = 12, color = "text" })
row.desc:SetPoint("TOPLEFT", row.name, "BOTTOMLEFT", 0, -6)
row.status = UI.CreateLabel(row, "", { size = 12, justify = "RIGHT" })
row.status:SetPoint("RIGHT", row, "RIGHT", -14, 0)
rows[i] = row
return row
end
function f.Refresh()
local list = FW.moduleList
if #list == 0 then
empty:Show()
else
empty:Hide()
end
for i, mod in ipairs(list) do
local row = GetRow(i)
local y = (i - 1) * (ROW_H + GAP)
row:ClearAllPoints()
row:SetPoint("TOPLEFT", scroll.child, "TOPLEFT", 0, -y)
row:SetPoint("TOPRIGHT", scroll.child, "TOPRIGHT", 0, -y)
row.name:SetText(mod.title or mod.name)
row.version:SetText(mod.version and ("v" .. mod.version) or "")
row.desc:SetText(mod.description or "")
if mod:IsEnabled() then
row.status:SetText("|cff" .. UI.Hex("success") .. L.MODULE_ACTIVE .. "|r")
else
row.status:SetText("|cff" .. UI.Hex("textDim") .. L.MODULE_INACTIVE .. "|r")
end
row:Show()
end
for i = #list + 1, #rows do
rows[i]:Hide()
end
scroll:SetContentHeight(math.max(#list * (ROW_H + GAP), 40))
end
f.Refresh()
return f
end
------------------------------------------------------------------------
-- Austausch: Export und Import
------------------------------------------------------------------------
local function BuildExchange(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
f.selected = nil
local intro = UI.CreateLabel(f, L.EXCHANGE_INTRO, { size = 12, color = "textDim", justifyV = "TOP" })
intro:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
intro:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, 0)
local kinds = CreateFrame("Frame", nil, f)
kinds:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -46)
kinds:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -46)
kinds:SetHeight(28)
local none = UI.CreateLabel(kinds, L.EXCHANGE_NO_HANDLERS, { size = 12, color = "textDim" })
none:SetPoint("LEFT", kinds, "LEFT", 0, 0)
local area = UI.CreateTextArea(f)
area:SetPoint("TOPLEFT", kinds, "BOTTOMLEFT", 0, -12)
area:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 46)
local status = UI.CreateLabel(f, "", { size = 12 })
status:SetWidth(520)
local function SetStatus(message, colorKey)
status:SetText("|cff" .. UI.Hex(colorKey or "text") .. message .. "|r")
end
local function ErrorText(key, extra)
local text = L[key]
if key == "ERR_NO_HANDLER" then
text = string.format(text, tostring(extra))
elseif key == "ERR_INVALID" and type(extra) == "string" then
text = text .. " (" .. extra .. ")"
end
return text
end
local kindButtons = {}
local function RefreshKindButtons()
for _, b in ipairs(kindButtons) do
b:SetStyle(b.kind == f.selected and "primary" or "default")
end
end
local function RebuildKinds()
for _, b in ipairs(kindButtons) do
b:Hide()
end
local list = Export.handlerList
if #list == 0 then
none:Show()
f.selected = nil
return
end
none:Hide()
local found = false
for _, h in ipairs(list) do
if h.kind == f.selected then
found = true
end
end
if not found then
f.selected = list[1].kind
end
local x = 0
for i, handler in ipairs(list) do
local b = kindButtons[i]
if not b then
b = UI.CreateButton(kinds, "", 150, 28, function(self)
f.selected = self.kind
RefreshKindButtons()
end)
kindButtons[i] = b
end
b.kind = handler.kind
local label = Export.GetLabel(handler)
b:SetLabel(label)
b:SetWidth(math.max(110, b.label:GetStringWidth() + 32))
b:ClearAllPoints()
b:SetPoint("LEFT", kinds, "LEFT", x, 0)
b:Show()
x = x + b:GetWidth() + 8
end
RefreshKindButtons()
end
local exportButton = UI.CreateButton(f, L.EXCHANGE_EXPORT, 130, 30, function()
if not f.selected then
return -- der Hinweis "kein Modul" steht schon ueber dem Textfeld
end
local code, err, extra = Export.ExportKind(f.selected)
if code then
area:SetText(code)
area:SelectAll()
SetStatus(string.format(L.EXCHANGE_EXPORTED, #code), "success")
else
SetStatus(ErrorText(err, extra), "danger")
end
end, "primary")
exportButton:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 0, 0)
local importButton = UI.CreateButton(f, L.EXCHANGE_IMPORT, 130, 30, function()
local text = area:GetText() or ""
if not text:find("%S") then
SetStatus(L.EXCHANGE_EMPTY, "danger")
return
end
local ok, result, extra = Export.Import(text)
if ok then
local message = type(result) == "string" and result ~= "" and result or L.EXCHANGE_IMPORTED
SetStatus(message, "success")
else
SetStatus(ErrorText(result, extra), "danger")
end
end)
importButton:SetPoint("LEFT", exportButton, "RIGHT", 8, 0)
local clearButton = UI.CreateButton(f, L.EXCHANGE_CLEAR, 100, 30, function()
area:SetText("")
status:SetText("")
end, "ghost")
clearButton:SetPoint("LEFT", importButton, "RIGHT", 8, 0)
status:SetPoint("LEFT", clearButton, "RIGHT", 16, 0)
function f.Refresh()
RebuildKinds()
end
f.Refresh()
return f
end
------------------------------------------------------------------------
-- Einstellungen
------------------------------------------------------------------------
-- Module koennen eigene Abschnitte in den Einstellungen anmelden:
-- UI:RegisterSettingsSection(id, { order = 100, height = 260, build = function(parent) return frame end, refresh = function(frame) end })
-- Die Abschnitte stehen in zwei Spalten rechts neben den allgemeinen Einstellungen. Die Hoehe eines Abschnitts
-- ist der Wert frame.flowH, wenn er ihn meldet (UI.CreateSection tut das), sonst def.height. Die ganze Seite
-- scrollt, wenn sie hoeher ist als das Fenster; die Breiten richten sich nach der Fensterbreite.
local settingsSections = {}
local settingsFrame, settingsBody, settingsScroll, settingsLeft
local vline1, vline2
-- Abschnitt mit Ueberschrift, Trennlinie und einem Stapel darunter: local f, stack = UI.CreateSection(parent, "Titel")
function UI.CreateSection(parent, titleText)
local f = CreateFrame("Frame", nil, parent)
local title = UI.CreateLabel(f, titleText, { font = "heading", size = 14, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local divider = UI.CreateDivider(f)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -26)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -26)
local stack = UI.CreateStack(f, { y = 30, gap = 6, pad = 0 })
stack.onHeight = function(h)
f.flowH = h + 14
f:SetHeight(f.flowH)
if f.onFlow then f.onFlow() end
end
f.stack = stack
f.title = title
return f, stack
end
local SECTION_GAP = 36 -- Abstand zwischen den beiden Spalten
local LEFT_GAP = 40 -- Abstand zwischen allgemeinen Einstellungen und Abschnitten
local function LeftWidth(W)
return FW.Util.Clamp(math.floor(W * 0.28), 300, 420)
end
local layingOut, pendingLayout, layoutDepth = false, false, 0
local function LayoutSections()
if not settingsBody then
return
end
if layingOut then
pendingLayout = true -- eine Hoehe hat sich waehrend des Anordnens geaendert: danach noch einmal
return
end
layingOut = true
local W = settingsBody:GetWidth() or 0
if W < 200 then
W = 1000
end
local leftW = LeftWidth(W)
local x1 = leftW + LEFT_GAP
local colW = math.max(200, math.floor((W - x1 - SECTION_GAP) / 2))
local x2 = x1 + colW + SECTION_GAP
settingsLeft:SetWidth(leftW)
local ypos = { 0, 0 }
for i, def in ipairs(settingsSections) do
def.col = (ypos[1] >= ypos[2]) and 1 or 2 -- jeweils in die kuerzere Spalte
local h = (def.frame and def.frame.flowH) or def.height or 260
def.y = ypos[def.col]
ypos[def.col] = ypos[def.col] - h - 10
local frame = def.frame
if frame then
frame:ClearAllPoints()
frame:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", def.col == 2 and x2 or x1, def.y)
frame:SetWidth(colW)
frame:SetHeight(h)
end
end
local contentH = math.max(-ypos[1], -ypos[2], settingsLeft.flowH or 0) + 20
settingsBody.contentH = contentH
settingsBody:SetHeight(contentH)
settingsScroll:SetContentHeight(contentH)
vline1:ClearAllPoints()
vline1:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", x1 - 20, 0)
vline1:SetPoint("BOTTOMLEFT", settingsBody, "TOPLEFT", x1 - 20, -contentH)
vline2:ClearAllPoints()
vline2:SetPoint("TOPLEFT", settingsBody, "TOPLEFT", x2 - SECTION_GAP / 2, 0)
vline2:SetPoint("BOTTOMLEFT", settingsBody, "TOPLEFT", x2 - SECTION_GAP / 2, -contentH)
layingOut = false
if pendingLayout and layoutDepth < 4 then
pendingLayout = false
layoutDepth = layoutDepth + 1
LayoutSections()
layoutDepth = layoutDepth - 1
end
pendingLayout = false
end
local function BuildSection(def)
if def.frame or not settingsBody then
return
end
local ok, frame = pcall(def.build, settingsBody)
if ok and frame then
def.frame = frame
frame.onFlow = LayoutSections
if def.refresh then
pcall(def.refresh, frame)
end
end
end
function UI:RegisterSettingsSection(id, def)
def.id = id
def.order = def.order or 100
settingsSections[#settingsSections + 1] = def
table.sort(settingsSections, function(a, b)
return a.order < b.order
end)
BuildSection(def)
LayoutSections()
end
local function BuildSettings(content)
local L = FW.L
local f = CreateFrame("Frame", nil, content)
local title = UI.CreateLabel(f, L.SETTINGS_HEADER, { font = "heading", size = 16, color = "goldLight" })
title:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
local divider = UI.CreateDivider(f)
divider:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -30)
divider:SetPoint("TOPRIGHT", f, "TOPRIGHT", 0, -30)
-- Alles darunter scrollt, wenn das Fenster zu niedrig ist
local scroll = UI.CreateScrollFrame(f)
scroll:SetPoint("TOPLEFT", f, "TOPLEFT", 0, -42)
scroll:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0)
local body = scroll.child
settingsScroll, settingsBody, settingsFrame = scroll, body, f
-- allgemeine Einstellungen links (Stapel)
local lf = CreateFrame("Frame", nil, body)
lf:SetPoint("TOPLEFT", body, "TOPLEFT", 0, 0)
lf:SetWidth(400)
settingsLeft = lf
local stack = UI.CreateStack(lf, { y = 0, gap = 8 })
stack.onHeight = function(h)
lf.flowH = h
lf:SetHeight(h)
end
local minimapBox = UI.CreateCheckbox(lf, L.SETTINGS_MINIMAP,
function()
return not FW.settings.minimap.hide
end,
function(value)
FW.Minimap:SetHidden(not value)
end)
stack:Add(minimapBox, { fit = true })
local debugBox = UI.CreateCheckbox(lf, L.SETTINGS_DEBUG,
function()
return FW.settings.debug and true or false
end,
function(value)
FW.settings.debug = value and true or false
end)
stack:Add(debugBox, { fit = true })
-- Fenstergroesse: Zeile mit - / Prozent / +
local scaleLabel = UI.CreateLabel(lf, L.SETTINGS_SCALE, { size = 12 })
stack:Add(scaleLabel, { height = 16, gap = 18 })
local scaleRow = CreateFrame("Frame", nil, lf)
scaleRow:SetHeight(26)
stack:Add(scaleRow, { height = 26, gap = 4, stretch = true })
local scaleValue = UI.CreateLabel(scaleRow, "", { size = 12, color = "goldLight", justify = "CENTER" })
scaleValue:SetWidth(60)
local function RefreshScale()
scaleValue:SetText(string.format("%d%%", math.floor(UI:GetScale() * 100 + 0.5)))
end
UI:OnScaleChanged(function() RefreshScale() end)
local minus = UI.CreateButton(scaleRow, "-", 28, 26, function()
UI:SetScale(UI:GetScale() - 0.05)
end)
minus:SetPoint("LEFT", scaleRow, "LEFT", 0, 0)
scaleValue:SetPoint("LEFT", minus, "RIGHT", 4, 0)
local plus = UI.CreateButton(scaleRow, "+", 28, 26, function()
UI:SetScale(UI:GetScale() + 0.05)
end)
plus:SetPoint("LEFT", scaleValue, "RIGHT", 4, 0)
-- Sprache: Beschriftung oben, drei Knoepfe gleich breit darunter
local langLabel = UI.CreateLabel(lf, L.SETTINGS_LANGUAGE, { size = 12 })
stack:Add(langLabel, { height = 16, gap = 18 })
local langRow = CreateFrame("Frame", nil, lf)
langRow:SetHeight(26)
stack:Add(langRow, { height = 26, gap = 4, stretch = true })
local langButtons = {}
local function RefreshLang()
for _, b in ipairs(langButtons) do
b:SetStyle(FW.settings.locale == b.code and "primary" or "default")
end
end
local options = {
{ code = "auto", text = L.LANG_AUTO },
{ code = "deDE", text = L.LANG_DE },
{ code = "enUS", text = L.LANG_EN },
}
for i, option in ipairs(options) do
local b = UI.CreateButton(langRow, option.text, 110, 26, function(self)
FW.settings.locale = self.code
RefreshLang()
end)
b.code = option.code
langButtons[#langButtons + 1] = b
end
local function LayoutLang(w)
w = w or langRow:GetWidth() or 0
if w < 60 then return end
local bw = math.floor((w - 12) / 3)
for i, b in ipairs(langButtons) do
b:ClearAllPoints()
b:SetWidth(bw)
b:SetPoint("LEFT", langRow, "LEFT", (i - 1) * (bw + 6), 0)
end
end
langRow:SetScript("OnSizeChanged", function(self) LayoutLang(self:GetWidth()) end)
LayoutLang(stack:Width())
local note = UI.CreateLabel(lf, L.SETTINGS_LANGUAGE_NOTE, { size = 11, color = "textDim", justifyV = "TOP" })
stack:Add(note, { fit = true, gap = 8 })
local reset = UI.CreateButton(lf, L.SETTINGS_RESET_POS, 220, 28, function()
UI:ResetPosition()
end)
stack:Add(reset, { height = 28, gap = 16, stretch = true })
function f.Refresh()
minimapBox:Refresh()
debugBox:Refresh()
RefreshScale()
RefreshLang()
for _, def in ipairs(settingsSections) do
if def.frame and def.refresh then
pcall(def.refresh, def.frame)
end
end
stack:Layout()
LayoutSections()
end
-- senkrechte Trennstriche zwischen den Spalten
local function VLine()
local line = body:CreateTexture(nil, "ARTWORK")
line:SetWidth(1)
line:SetColorTexture(UI.Color("gold", 0.35))
return line
end
vline1, vline2 = VLine(), VLine()
body:SetScript("OnSizeChanged", function(self)
local w = self:GetWidth()
if type(w) == "number" and w > 0 then
stack:Layout()
LayoutSections()
end
end)
for _, def in ipairs(settingsSections) do
BuildSection(def)
end
stack:Layout()
LayoutSections()
f.Refresh()
return f
end
------------------------------------------------------------------------
-- Anmelden
------------------------------------------------------------------------
UI:RegisterTab("overview", {
label = function() return FW.L.TAB_OVERVIEW end,
order = 10,
build = BuildOverview,
onShow = function(f) f.Refresh() end,
})
UI:RegisterTab("exchange", {
label = function() return FW.L.TAB_EXCHANGE end,
order = 110,
build = BuildExchange,
onShow = function(f) f.Refresh() end,
})
UI:RegisterTab("settings", {
label = function() return FW.L.TAB_SETTINGS end,
order = 20,
build = BuildSettings,
onShow = function(f) f.Refresh() end,
})