diff --git a/DRAGONUI_NEWERA.md b/DRAGONUI_NEWERA.md new file mode 100644 index 0000000..807d265 --- /dev/null +++ b/DRAGONUI_NEWERA.md @@ -0,0 +1,82 @@ +# DragonUI_NewEra compatibility + +Compatibility was audited against `ghbset/DragonUI_NewEra` commit +`37a29d640f97545ff1bb7d4b0ba7a5329cce1b8c` (2026-08-09). + +## Status + +DragonUI_NewEra is bundled in `patch-ruRU-5` as a required MoonWell system +component. It is enabled by default and hidden from the AddOns list together +with DragonUI and `!!!ClassicAPI`. + +The MoonWell DragonUI exposes the integration seams used by NewEra: + +- global `DragonUI` addon table; +- `ModuleRegistry`; +- `MoversSystem` and the editable-frame API; +- `OptionsPanel` and `PanelControls` after `DragonUI_Options` is loaded; +- `Fonts`, `SafeCall`, `CombatQueue`, and global `SetAtlasTexture`. + +NewEra remains a separate addon folder rather than being merged into DragonUI. +Its TOC retains: + +```toc +## Interface: 30300 +## Dependencies: DragonUI, !!!ClassicAPI +## OptionalDeps: DragonUI_Options +``` + +`DragonUI_NewEra` is an extension, not a replacement for the HUD modules. It +does not need `X-DragonUI-Overrides` unless a future NewEra module explicitly +takes ownership of `mainbars`, `minimap`, `buffs`, or another registered HUD +module. + +## Bundled sources + +The system package contains: + +```text +Interface/AddOns/!!!ClassicAPI/ b3da3000bba23a2c31aa9c9b7c0232d5973789e6 +Interface/AddOns/DragonUI_NewEra/ 37a29d640f97545ff1bb7d4b0ba7a5329cce1b8c +``` + +Upstream Git metadata, screenshots, and top-level build tools are excluded. +Runtime sources, assets, and the upstream QA harness are retained. The exact +revisions are also recorded in addon TOC metadata as `X-Upstream-Commit`. + +Neither audited upstream contains an explicit repository-wide redistribution +license. MoonWell maintainers must obtain and archive redistribution permission +before a public release. The source revisions above preserve provenance but do +not replace that permission. + +## Known ownership conflict + +Both MoonWellClient and NewEra provide an Encounter Journal / Adventure Guide +and the `ToggleEncounterJournal` entry point. MoonWell's journal is currently +authoritative because it receives server-backed data. NewEra's encounter +journal files are intentionally omitted from its system TOC, and its dead +options toggle is hidden. + +The NewEra combined-bag module is marked work in progress upstream. Treat it as +opt-in and do not use it as the default replacement for DragonUI/Blizzard bags +without a separate gameplay test pass. + +NewEra ships 94 power-of-two BLP atlases with one dimension above the project's +conservative 512-pixel authoring limit; the largest are 2048x2048. Upstream +marks their BLP2 encodings as 3.3.5a-loadable and they package successfully, +but every affected panel still requires an in-client render pass on MoonWell's +exact executable before release. If that client rejects them, split the atlases +without changing their public atlas names instead of silently scaling UI +geometry. + +## Release checklist + +1. Update only from reviewed, pinned DragonUI_NewEra and ClassicAPI revisions. +2. Obtain and record licenses or permissions for code, fonts, sounds, and BLPs. +3. Keep DragonUI_NewEra as its own folder and TOC; do not merge it into + DragonUI or FrameXML. +4. Keep NewEra Encounter Journal disabled until ownership is deliberately + migrated from MoonWellClient. +5. Test Character, Spellbook, Talents, Professions, Auction House, LFG, Social, + Collections, and the DragonUI options tab on build 12340. +6. Test login with NewEra disabled: the default DragonUI must remain complete. diff --git a/DRAGONUI_OVERRIDES.md b/DRAGONUI_OVERRIDES.md new file mode 100644 index 0000000..4ad28b4 --- /dev/null +++ b/DRAGONUI_OVERRIDES.md @@ -0,0 +1,105 @@ +# DragonUI: replacement addons + +MoonWell ships DragonUI as the default interface. Players can replace an +individual DragonUI module without copying or editing the bundled addon. + +## Replacement addon structure + +The replacement must use its own folder and addon name. Do not create another +folder named `DragonUI`; files inside an MPQ and loose files with the same +virtual path do not form a supported update mechanism. + +```text +MyDragonUIMinimap/ +|-- MyDragonUIMinimap.toc +`-- MyDragonUIMinimap.lua +``` + +`MyDragonUIMinimap.toc`: + +```toc +## Interface: 30300 +## Title: My DragonUI Minimap +## Dependencies: DragonUI +## X-DragonUI-Overrides: minimap +## X-DragonUI-Override-Priority: 100 + +MyDragonUIMinimap.lua +``` + +The `X-DragonUI-Overrides` declaration is required. DragonUI reads it before +dependent addons execute and does not apply the bundled implementation. A list +may contain several comma-separated module identifiers. + +`MyDragonUIMinimap.lua`: + +```lua +local provider = {} + +function provider:Activate(dragonUI, moduleName) + -- Create or show the replacement here. +end + +function provider:Deactivate(dragonUI, moduleName) + -- Hide frames and unregister events created by this provider. +end + +local ok, reason = DragonUI:RegisterModuleOverride( + "minimap", + "MyDragonUIMinimap", + provider, + { priority = 100 } +) + +if not ok then + DEFAULT_CHAT_FRAME:AddMessage("MyDragonUIMinimap: " .. tostring(reason)) +end +``` + +The owner passed to `RegisterModuleOverride` must match the addon folder/TOC +name. Supported provider activation names are `Activate`, `Apply`, `Enable`, or +`OnEnable`; cleanup names are `Deactivate`, `Restore`, `Disable`, or +`OnDisable`. + +## Public API (version 1) + +```lua +DragonUI.PUBLIC_API_VERSION +DragonUI:RegisterModuleOverride(moduleName, owner, provider[, options]) +DragonUI:UnregisterModuleOverride(moduleName, owner) +DragonUI:IsModuleOverridden(moduleName) +DragonUI:GetModuleOverride(moduleName) -- owner, provider +``` + +DragonUI also sends the Ace message `DRAGONUI_OVERRIDE_CHANGED` with +`moduleName, owner`. `owner` is nil after the final provider is removed. + +Registering a replacement at runtime without TOC metadata is supported only +for a module that can be restored safely. For a load-once module the call +returns `true, "RELOAD_REQUIRED"`; add the metadata and reload the UI. + +## Bundled module identifiers + +The registry currently exposes these identifiers: + +```text +bagsort, boss, buffs, buttons, castbar, chatmods, combuctor, cooldowns, +darkmode, itemquality, keybinding, mainbars, micromenu, minimap, multicast, +noop, petbar, player, questtracker, rage_indicator, stance, tooltip, +unitframe_layers, vehicle +``` + +Target/focus/party frames still contain legacy startup code and will be added +to the public registry before they are advertised as independently replaceable. + +## Conflict resolution + +When multiple enabled addons claim the same module, the highest numeric +`X-DragonUI-Override-Priority` wins. Equal priorities are resolved by addon +name to make the result deterministic. A declaration that never registers a +provider is rejected at `PLAYER_LOGIN`, and DragonUI restores its bundled +module so the player is not left without that UI element. + +Module suppression is session-only. DragonUI restores the player's saved +enabled/disabled choice during `PLAYER_LOGOUT`, so removing a replacement addon +automatically brings back the previous DragonUI configuration. diff --git a/README.md b/README.md index 62ce41d..6015579 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,18 @@ WarcraftXL не требуется и в объектное хранилище - `patch-ruRU-5` — русская локализация и интерфейс; - `patch-Z` — Mythic+ ресурсы и клиентские данные. +## DragonUI и пользовательские замены + +DragonUI поставляется как включённый системный интерфейс. Игрок не заменяет +файлы из MPQ: отдельный аддон может заранее объявить, какие модули он берёт под +свой контроль, и зарегистрировать собственную реализацию после загрузки +DragonUI. Формат TOC, Lua API, приоритеты и рабочий пример описаны в +[`DRAGONUI_OVERRIDES.md`](DRAGONUI_OVERRIDES.md). + +DragonUI_NewEra и его `!!!ClassicAPI` входят в системный интерфейс MoonWell. +Зафиксированные upstream-версии, состав поставки и решение конфликта двух +Encounter Journal описаны в [`DRAGONUI_NEWERA.md`](DRAGONUI_NEWERA.md). + ## Лицензирование WarcraftXL распространяется по GPL-3.0 и подключён как отдельный неизменённый upstream submodule. diff --git a/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/!!!ClassicAPI.toc b/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/!!!ClassicAPI.toc new file mode 100644 index 0000000..b3b895c --- /dev/null +++ b/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/!!!ClassicAPI.toc @@ -0,0 +1,10 @@ +## Interface: 30300 +## Title: !!!ClassicAPI +## Notes: Blizzard Classic API Emulation +## Version: 1.1.64 +## Author: Tsoukie +## DefaultState: Enabled +## X-MoonWell-System: 1 +## X-Upstream-Commit: b3da3000bba23a2c31aa9c9b7c0232d5973789e6 +Lib\Load.xml +Load.xml diff --git a/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/Lib/Ace3/CallbackHandler-1.0/CallbackHandler-1.0.lua b/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/Lib/Ace3/CallbackHandler-1.0/CallbackHandler-1.0.lua new file mode 100644 index 0000000..479a118 --- /dev/null +++ b/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/Lib/Ace3/CallbackHandler-1.0/CallbackHandler-1.0.lua @@ -0,0 +1,238 @@ +--[[ $Id: CallbackHandler-1.0.lua 18 2014-10-16 02:52:20Z mikk $ ]] +local MAJOR, MINOR = "CallbackHandler-1.0", 7 +local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR) + +if not CallbackHandler then return end -- No upgrade needed + +local meta = {__index = function(tbl, key) tbl[key] = {} return tbl[key] end} + +-- Lua APIs +local tconcat = table.concat +local assert, error, loadstring = assert, error, loadstring +local setmetatable, rawset, rawget = setmetatable, rawset, rawget +local next, select, pairs, type, tostring = next, select, pairs, type, tostring + +-- Global vars/functions that we don't upvalue since they might get hooked, or upgraded +-- List them here for Mikk's FindGlobals script +-- GLOBALS: geterrorhandler + +local xpcall = xpcall + +local function errorhandler(err) + return geterrorhandler()(err) +end + +local function CreateDispatcher(argCount) + local code = [[ + local next, xpcall, eh = ... + + local method, ARGS + local function call() method(ARGS) end + + local function dispatch(handlers, ...) + local index + index, method = next(handlers) + if not method then return end + local OLD_ARGS = ARGS + ARGS = ... + repeat + xpcall(call, eh) + index, method = next(handlers, index) + until not method + ARGS = OLD_ARGS + end + + return dispatch + ]] + + local ARGS, OLD_ARGS = {}, {} + for i = 1, argCount do ARGS[i], OLD_ARGS[i] = "arg"..i, "old_arg"..i end + code = code:gsub("OLD_ARGS", tconcat(OLD_ARGS, ", ")):gsub("ARGS", tconcat(ARGS, ", ")) + return assert(loadstring(code, "safecall Dispatcher["..argCount.."]"))(next, xpcall, errorhandler) +end + +local Dispatchers = setmetatable({}, {__index=function(self, argCount) + local dispatcher = CreateDispatcher(argCount) + rawset(self, argCount, dispatcher) + return dispatcher +end}) + +-------------------------------------------------------------------------- +-- CallbackHandler:New +-- +-- target - target object to embed public APIs in +-- RegisterName - name of the callback registration API, default "RegisterCallback" +-- UnregisterName - name of the callback unregistration API, default "UnregisterCallback" +-- UnregisterAllName - name of the API to unregister all callbacks, default "UnregisterAllCallbacks". false == don't publish this API. + +function CallbackHandler:New(target, RegisterName, UnregisterName, UnregisterAllName) + + RegisterName = RegisterName or "RegisterCallback" + UnregisterName = UnregisterName or "UnregisterCallback" + if UnregisterAllName==nil then -- false is used to indicate "don't want this method" + UnregisterAllName = "UnregisterAllCallbacks" + end + + -- we declare all objects and exported APIs inside this closure to quickly gain access + -- to e.g. function names, the "target" parameter, etc + + + -- Create the registry object + local events = setmetatable({}, meta) + local registry = { recurse=0, events=events } + + -- registry:Fire() - fires the given event/message into the registry + function registry:Fire(eventname, ...) + if not rawget(events, eventname) or not next(events[eventname]) then return end + local oldrecurse = registry.recurse + registry.recurse = oldrecurse + 1 + + Dispatchers[select('#', ...) + 1](events[eventname], eventname, ...) + + registry.recurse = oldrecurse + + if registry.insertQueue and oldrecurse==0 then + -- Something in one of our callbacks wanted to register more callbacks; they got queued + for eventname,callbacks in pairs(registry.insertQueue) do + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + for self,func in pairs(callbacks) do + events[eventname][self] = func + -- fire OnUsed callback? + if first and registry.OnUsed then + registry.OnUsed(registry, target, eventname) + first = nil + end + end + end + registry.insertQueue = nil + end + end + + -- Registration of a callback, handles: + -- self["method"], leads to self["method"](self, ...) + -- self with function ref, leads to functionref(...) + -- "addonId" (instead of self) with function ref, leads to functionref(...) + -- all with an optional arg, which, if present, gets passed as first argument (after self if present) + target[RegisterName] = function(self, eventname, method, ... --[[actually just a single arg]]) + if type(eventname) ~= "string" then + error("Usage: "..RegisterName.."(eventname, method[, arg]): 'eventname' - string expected.", 2) + end + + method = method or eventname + + local first = not rawget(events, eventname) or not next(events[eventname]) -- test for empty before. not test for one member after. that one member may have been overwritten. + + if type(method) ~= "string" and type(method) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - string or function expected.", 2) + end + + local regfunc + + if type(method) == "string" then + -- self["method"] calling style + if type(self) ~= "table" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): self was not a table?", 2) + elseif self==target then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): do not use Library:"..RegisterName.."(), use your own 'self'", 2) + elseif type(self[method]) ~= "function" then + error("Usage: "..RegisterName.."(\"eventname\", \"methodname\"): 'methodname' - method '"..tostring(method).."' not found on self.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) self[method](self,arg,...) end + else + regfunc = function(...) self[method](self,...) end + end + else + -- function ref with self=object or self="addonId" or self=thread + if type(self)~="table" and type(self)~="string" and type(self)~="thread" then + error("Usage: "..RegisterName.."(self or \"addonId\", eventname, method): 'self or addonId': table or string or thread expected.", 2) + end + + if select("#",...)>=1 then -- this is not the same as testing for arg==nil! + local arg=select(1,...) + regfunc = function(...) method(arg,...) end + else + regfunc = method + end + end + + + if events[eventname][self] or registry.recurse<1 then + -- if registry.recurse<1 then + -- we're overwriting an existing entry, or not currently recursing. just set it. + events[eventname][self] = regfunc + -- fire OnUsed callback? + if registry.OnUsed and first then + registry.OnUsed(registry, target, eventname) + end + else + -- we're currently processing a callback in this registry, so delay the registration of this new entry! + -- yes, we're a bit wasteful on garbage, but this is a fringe case, so we're picking low implementation overhead over garbage efficiency + registry.insertQueue = registry.insertQueue or setmetatable({},meta) + registry.insertQueue[eventname][self] = regfunc + end + end + + -- Unregister a callback + target[UnregisterName] = function(self, eventname) + if not self or self==target then + error("Usage: "..UnregisterName.."(eventname): bad 'self'", 2) + end + if type(eventname) ~= "string" then + error("Usage: "..UnregisterName.."(eventname): 'eventname' - string expected.", 2) + end + if rawget(events, eventname) and events[eventname][self] then + events[eventname][self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(events[eventname]) then + registry.OnUnused(registry, target, eventname) + end + end + if registry.insertQueue and rawget(registry.insertQueue, eventname) and registry.insertQueue[eventname][self] then + registry.insertQueue[eventname][self] = nil + end + end + + -- OPTIONAL: Unregister all callbacks for given selfs/addonIds + if UnregisterAllName then + target[UnregisterAllName] = function(...) + if select("#",...)<1 then + error("Usage: "..UnregisterAllName.."([whatFor]): missing 'self' or \"addonId\" to unregister events for.", 2) + end + if select("#",...)==1 and ...==target then + error("Usage: "..UnregisterAllName.."([whatFor]): supply a meaningful 'self' or \"addonId\"", 2) + end + + + for i=1,select("#",...) do + local self = select(i,...) + if registry.insertQueue then + for eventname, callbacks in pairs(registry.insertQueue) do + if callbacks[self] then + callbacks[self] = nil + end + end + end + for eventname, callbacks in pairs(events) do + if callbacks[self] then + callbacks[self] = nil + -- Fire OnUnused callback? + if registry.OnUnused and not next(callbacks) then + registry.OnUnused(registry, target, eventname) + end + end + end + end + end + end + + return registry +end + + +-- CallbackHandler purposefully does NOT do explicit embedding. Nor does it +-- try to upgrade old implicit embeds since the system is selfcontained and +-- relies on closures to work. + diff --git a/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/Lib/Ace3/CallbackHandler-1.0/CallbackHandler-1.0.xml b/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/Lib/Ace3/CallbackHandler-1.0/CallbackHandler-1.0.xml new file mode 100644 index 0000000..876df83 --- /dev/null +++ b/src/Data/ruRU/patch-ruRU-5/Interface/AddOns/!!!ClassicAPI/Lib/Ace3/CallbackHandler-1.0/CallbackHandler-1.0.xml @@ -0,0 +1,4 @@ + +