Repository navigation
Expand file tree
/
Copy pathCore.lua
More file actions
369 lines (322 loc) · 12.5 KB
/
Copy pathCore.lua
File metadata and controls
369 lines (322 loc) · 12.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
--[[
Copperwise: Core.lua
Patterns: Module, Observer (Event Bus), Singleton
Addon core:
- Module system for registering and accessing modules
- Event bus with a dispatch table (Observer pattern)
- Slash commands /cw and /copperwise
- Initialisation on ADDON_LOADED
]]
local ADDON_NAME, ns = ...
local L = ns.L
-------------------------------------------------------------------------------
-- Singleton: the addon's global namespace
-------------------------------------------------------------------------------
local Copperwise = {}
Copperwise.name = ADDON_NAME
Copperwise.version = C_AddOns.GetAddOnMetadata(ADDON_NAME, "Version") or "1.0.0"
-- Export to global and namespace
_G.Copperwise = Copperwise
ns.Copperwise = Copperwise
-------------------------------------------------------------------------------
-- Module Pattern: registering and accessing modules
-------------------------------------------------------------------------------
local modules = {}
--- Registers a module
--- @param name string Module name
--- @param module table Module table
function Copperwise:RegisterModule(name, module)
if modules[name] then
error(("Copperwise: Module '%s' already registered"):format(name))
end
modules[name] = module
module.name = name
return module
end
--- Returns a registered module
--- @param name string Module name
--- @return table|nil
function Copperwise:GetModule(name)
return modules[name]
end
--- Calls a method on every module that has it
--- @param method string Method name
--- @param ... any Arguments
function Copperwise:CallModules(method, ...)
for _, mod in pairs(modules) do
if type(mod[method]) == "function" then
mod[method](mod, ...)
end
end
end
-------------------------------------------------------------------------------
-- Observer Pattern: event bus with a dispatch table
-------------------------------------------------------------------------------
local eventFrame = CreateFrame("Frame")
local eventHandlers = {} -- { [event] = { callback1, callback2, ... } }
--- Registers a handler for a WoW event
--- @param event string WoW event name
--- @param callback function Handler
function Copperwise:RegisterEvent(event, callback)
if not eventHandlers[event] then
eventHandlers[event] = {}
eventFrame:RegisterEvent(event)
end
table.insert(eventHandlers[event], callback)
end
--- Removes all handlers for an event
--- @param event string WoW event name
function Copperwise:UnregisterEvent(event)
eventHandlers[event] = nil
eventFrame:UnregisterEvent(event)
end
-- Central dispatch: one OnEvent for every event
eventFrame:SetScript("OnEvent", function(_, event, ...)
local handlers = eventHandlers[event]
if handlers then
for _, callback in ipairs(handlers) do
callback(event, ...)
end
end
end)
-------------------------------------------------------------------------------
-- Internal Event Bus: Copperwise.Events:On/Off/Emit for cross-module messages
-------------------------------------------------------------------------------
local busSubscribers = {} -- { [event_name] = { fn1, fn2, ... } }
Copperwise.Events = {}
function Copperwise.Events:On(name, handler)
busSubscribers[name] = busSubscribers[name] or {}
table.insert(busSubscribers[name], handler)
return handler
end
function Copperwise.Events:Off(name, handler)
local list = busSubscribers[name]
if not list then return end
for i, fn in ipairs(list) do
if fn == handler then table.remove(list, i) return end
end
end
--- Public wrapper: register a handler for a WoW-native event (PLAYER_MONEY etc.)
--- Equivalent to Copperwise:RegisterEvent but on the Events namespace for discoverability.
function Copperwise.Events:RegisterWoWEvent(event, handler)
return Copperwise:RegisterEvent(event, handler)
end
function Copperwise.Events:Emit(name, data)
local list = busSubscribers[name]
if not list then return end
for _, fn in ipairs(list) do
local ok, err = pcall(fn, data)
if not ok then
Copperwise:Debug("Events", "subscriber error on", name, "→", tostring(err))
end
end
end
-------------------------------------------------------------------------------
-- CreateLocale: helper to build a string table for feature modules
-- Usage: local L = Copperwise:CreateLocale({ enUS = {...} })
-------------------------------------------------------------------------------
function Copperwise:CreateLocale(tables)
local enUS = tables.enUS or {}
return setmetatable({}, { __index = function(_, key) return enUS[key] or key end })
end
-------------------------------------------------------------------------------
-- Initialization: ADDON_LOADED
-------------------------------------------------------------------------------
Copperwise:RegisterEvent("ADDON_LOADED", function(event, loadedAddon)
if loadedAddon ~= ADDON_NAME then return end
-- SavedVariables initialisation with defaults
Copperwise:CallModules("OnInitialize")
-- Feature modules init (loaded after core via .toc order)
Copperwise:CallFeatures("OnInitialize")
-- Welcome message (deferred to avoid taint)
C_Timer.After(0, function()
print(L["ADDON_LOADED"])
end)
-- This event is no longer needed
Copperwise:UnregisterEvent("ADDON_LOADED")
end)
-- PLAYER_LOGIN: modules can now access game data
Copperwise:RegisterEvent("PLAYER_LOGIN", function()
Copperwise:CallModules("OnEnable")
Copperwise:CallFeatures("OnEnable")
Copperwise:UnregisterEvent("PLAYER_LOGIN")
end)
-------------------------------------------------------------------------------
-- Feature Module System (for addon features like Auction, History, etc.)
-------------------------------------------------------------------------------
local features = {}
local featureSlashCommands = {}
local headerButtons = {}
--- Registers a feature module (features, not infrastructure)
--- @param name string Unique feature name (e.g. "auction")
--- @param module table Module table with OnInitialize/OnEnable callbacks
function Copperwise:RegisterFeature(name, module)
features[name] = module
module.name = name
return module
end
--- Returns a feature module by name
function Copperwise:GetFeature(name)
return features[name]
end
--- Calls a method on every feature module
function Copperwise:CallFeatures(method, ...)
for _, mod in pairs(features) do
if type(mod[method]) == "function" then
mod[method](mod, ...)
end
end
end
--- Registers a /cw <subcommand> for a feature module
function Copperwise:RegisterSlashCommand(subcommand, callback)
featureSlashCommands[subcommand:lower()] = callback
end
--- Registers a button in the main window's header bar
--- @param name string Button ID
--- @param label string|function Button text, or a function returning it
--- @param callback function OnClick handler
function Copperwise:RegisterHeaderButton(name, label, callback)
headerButtons[#headerButtons + 1] = { name = name, label = label, callback = callback }
end
--- Unregisters a header button by name.
--- @return boolean true if the button was found and removed
function Copperwise:UnregisterHeaderButton(name)
for i, btn in ipairs(headerButtons) do
if btn.name == name then
table.remove(headerButtons, i)
return true
end
end
return false
end
--- Unregisters a slash subcommand.
function Copperwise:UnregisterSlashCommand(subcommand)
if subcommand then
featureSlashCommands[subcommand:lower()] = nil
end
end
--- Unregisters a feature module and removes its header buttons and slash commands.
--- Used to switch a feature off at runtime (without a UI reload).
function Copperwise:UnregisterFeature(name)
if not features[name] then return false end
features[name] = nil
-- Header buttons are often registered under the same key as the feature
self:UnregisterHeaderButton(name)
return true
end
--- Returns the registered header buttons (for UI/MainFrame)
function Copperwise:GetHeaderButtons()
return headerButtons
end
-------------------------------------------------------------------------------
-- Debug mode flag (declared BEFORE slash handler so `not debugMode` resolves
-- to this local, not a stray global. Setter/printers live further down.)
-------------------------------------------------------------------------------
local debugMode = false
-------------------------------------------------------------------------------
-- Slash Commands
-------------------------------------------------------------------------------
SLASH_COPPERWISE1 = "/cw"
SLASH_COPPERWISE2 = "/copperwise"
--- Finds a feature slash callback for cmd. Two forms are supported:
--- 1. Exact match ("calc" → featureSlashCommands["calc"])
--- 2. Prefix and a space ("goal 1000" → featureSlashCommands["goal"], arg = "1000")
--- Returns (callback, arg_string_or_nil), or nil.
local function findFeatureSlash(cmd)
if featureSlashCommands[cmd] then
return featureSlashCommands[cmd], nil
end
-- Search registered keys for "key " prefix; longest match wins.
local matchedKey, matchedFn
for key, fn in pairs(featureSlashCommands) do
if cmd:sub(1, #key + 1) == (key .. " ") then
if not matchedKey or #key > #matchedKey then
matchedKey, matchedFn = key, fn
end
end
end
if matchedFn then
return matchedFn, strtrim(cmd:sub(#matchedKey + 1))
end
return nil, nil
end
SlashCmdList["COPPERWISE"] = function(msg)
local cmd = strtrim(msg):lower()
if cmd == "reset" then
local Data = Copperwise:GetModule("Data")
if Data then
Data:ResetCharacterData()
print("|cffb87333Copperwise:|r " .. L["RESET_CONFIRM"])
end
elseif cmd == "debug" then
Copperwise:SetDebug(not debugMode)
elseif cmd == "dump" then
local Data = Copperwise:GetModule("Data")
if Data then
Copperwise:DumpTable("Daily", Data:GetDailySummary())
Copperwise:DumpTable("Monthly", Data:GetMonthlySummary())
Copperwise:DumpTable("Settings", Data:GetSettings())
print("|cff888888[GL:Dump]|r Entries: " .. #Data:GetRecentEntries(999))
end
elseif cmd == "settings" or cmd == "config" then
local UI = Copperwise:GetModule("UI")
if UI then UI:ToggleSettingsFrame() end
elseif cmd == "help" then
print("|cffb87333Copperwise:|r " .. L["SLASH_HELP"])
else
local fn, arg = findFeatureSlash(cmd)
if fn then
-- Built-in slash commands take precedence over feature dispatch
-- so "reset"/"help" etc. cannot be hijacked by a feature.
fn(arg)
else
-- Default: toggle main window
local UI = Copperwise:GetModule("UI")
if UI then
UI:ToggleMainFrame()
end
end
end
end
-------------------------------------------------------------------------------
-- Debug System (debugMode local is declared above the slash handler)
-------------------------------------------------------------------------------
--- Turns debug mode on or off
function Copperwise:SetDebug(enabled)
debugMode = enabled
print("|cffb87333Copperwise:|r debug " .. (enabled and "|cff00ff00ON|r" or "|cffff4444OFF|r"))
end
--- Prints a debug message to chat (only when debug is on)
--- @param module string Module name
--- @param ... any Values to join
function Copperwise:Debug(module, ...)
if not debugMode then return end
local args = {...}
local parts = {}
for i = 1, #args do
parts[i] = tostring(args[i])
end
print(("|cff888888[GL:%s]|r %s"):format(module, table.concat(parts, " ")))
end
--- Dumps a table to chat (debug mode)
--- @param label string Label
--- @param tbl table Table to print
function Copperwise:DumpTable(label, tbl)
if not debugMode then return end
print("|cff888888[GL:Dump]|r " .. label .. ":")
if type(tbl) ~= "table" then
print(" " .. tostring(tbl))
return
end
for k, v in pairs(tbl) do
if type(v) == "table" then
print((" %s = {%d items}"):format(tostring(k), #v > 0 and #v or 0))
else
print((" %s = %s"):format(tostring(k), tostring(v)))
end
end
end
-------------------------------------------------------------------------------
-- Utility: expose L on the namespace for convenience
-------------------------------------------------------------------------------
Copperwise.L = L