-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathDB.lua
More file actions
450 lines (418 loc) · 19.1 KB
/
Copy pathDB.lua
File metadata and controls
450 lines (418 loc) · 19.1 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
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
-- DB.lua - SavedVariables schema, defaults, and migrations.
-- Author: Serv
-- Source: https://github.com/powerfulqa/BarWarden
-- License: see LICENSE; attribution preservation is required.
local addonName, ns = ...
-- ============================================================================
-- DB.lua - BarWardenDB schema, defaults, visual presets, SavedVariables init
-- ============================================================================
ns.DEFAULTS = {
-- Schema version: increment when a migration pass is needed
schemaVersion = 5,
-- Global settings
global = {
enabled = true,
locked = true,
-- Notify when a newer BarWarden version is seen on a peer (Comms.lua).
-- Additive key, backfilled by MergeDefaults on existing saves.
versionAlerts = true,
-- Help-tab section collapse state: sectionKey -> true when collapsed.
-- Seeded so Getting Started is open and the rest start collapsed.
-- Additive key, backfilled by MergeDefaults on existing saves (no
-- schema bump needed; see InitDB). Section keys match HELP_ENTRIES
-- in Options_Help.lua. MergeDefaults fills nil keys only, so a user's
-- later expand/collapse choices and any newly added section are
-- preserved / default-collapsed respectively.
helpCollapsed = {
trackingModes = true,
conditions = true,
visuals = true,
profiles = true,
activity = true,
troubleshooting = true,
},
},
-- Minimap icon state owned by LibDBIcon-1.0. The layout (hide / minimapPos)
-- is what LibDBIcon's Register/Show/Hide/Refresh read + mutate directly,
-- so keep these field names exactly.
minimap = {
hide = false,
minimapPos = 220,
},
-- Visual settings (global defaults)
visual = {
texture = "Flat",
customTexture = "",
barWidth = 200,
barHeight = 20,
iconSize = 20,
showIcon = true,
iconPosition = "LEFT",
barSpacing = 2,
font = "Fonts\\FRIZQT__.TTF",
fontSize = 11,
textEnabled = true,
textPosition = "INSIDE_LEFT",
textFormat = "NAME_DURATION",
durationStyle = "DECIMAL",
colorMode = "CLASS",
defaultColor = { r = 0.2, g = 0.6, b = 1.0 },
trackModeColors = {
Cooldown = { r = 0.4, g = 0.6, b = 1.0 },
Buff = { r = 0.0, g = 0.8, b = 0.0 },
Debuff = { r = 1.0, g = 0.2, b = 0.2 },
Proc = { r = 1.0, g = 0.8, b = 0.0 },
Item = { r = 0.6, g = 0.2, b = 0.8 },
Enchant = { r = 0.2, g = 0.8, b = 0.6 },
["Enchant MH"] = { r = 0.2, g = 0.8, b = 0.6 },
["Enchant OH"] = { r = 0.2, g = 0.8, b = 0.6 },
Totem = { r = 0.8, g = 0.4, b = 0.2 },
-- Class resources
["Combo Points"] = { r = 1.0, g = 0.6, b = 0.2 },
["Runic Power"] = { r = 0.4, g = 0.2, b = 0.8 },
["Soul Shards"] = { r = 0.8, g = 0.2, b = 0.8 },
["Runes"] = { r = 0.7, g = 0.2, b = 0.2 },
},
activeAlpha = 1.0,
inactiveAlpha = 0.3,
showSpark = true,
-- Icon crop
iconCrop = true,
-- Radial cooldown spiral overlay on bar icons (TellMeWhen-style)
showCooldownSpiral = true,
-- Spell tooltip when hovering a bar's icon (off by default)
showBarTooltip = false,
},
-- Frames (groups of bars). Empty by default: the auto-prompt at
-- PLAYER_LOGIN (Core.lua CheckFirstLoginStarter) offers to load a
-- curated class starter on first login, replacing the old sample
-- Hearthstone bar that just added clutter.
frames = {},
-- Activity tracker: passive spell/aura/cooldown monitoring (persistent across sessions)
activity = {},
-- Active profile name (profiles themselves are stored account-wide in BarWardenAccountDB)
activeProfile = nil,
}
-- One-time migration to canonicalise legacy field names. Runs only when
-- BarWardenDB.schemaVersion is absent or below CURRENT_SCHEMA. Only writes
-- to nil keys; never overwrites existing user data.
local CURRENT_SCHEMA = 5
local function MigrateDB()
local savedVersion = BarWardenDB.schemaVersion or 0
if savedVersion >= CURRENT_SCHEMA then return end
-- Snapshot the layout before we mutate it, so a bad upgrade is recoverable.
ns:BackupFrames("schema " .. savedVersion .. " to " .. CURRENT_SCHEMA)
-- v0 → v1: rename legacy bar config fields to canonical names.
-- spell (number) → spellId / itemId spell (string) → spellName
-- spellInput → spellName target → unit
if savedVersion < 1 then
for _, frameData in ipairs(BarWardenDB.frames or {}) do
for _, bar in ipairs(frameData.bars or {}) do
-- Migrate spell field
local s = bar.spell
if s ~= nil then
if type(s) == "number" then
if bar.trackMode == "Item" then
if bar.itemId == nil then bar.itemId = s end
else
if bar.spellId == nil then bar.spellId = s end
end
elseif type(s) == "string" and s ~= "" then
if bar.spellName == nil then bar.spellName = s end
end
bar.spell = nil
end
-- Migrate spellInput field
if bar.spellInput ~= nil then
if bar.spellName == nil then bar.spellName = bar.spellInput end
bar.spellInput = nil
end
-- Migrate target → unit
if bar.target ~= nil then
if bar.unit == nil then bar.unit = bar.target end
bar.target = nil
end
end
end
end
-- v1 → v2: fix saves corrupted by MergeDefaults recursing into user frames.
-- The old InitDB merged the sample default frame (spell=6948 Hearthstone)
-- into user bars. v1 migration then turned that into spellId=6948 on
-- Cooldown bars even when the user had set spellName (e.g. "Evasion").
-- Fix: for non-Item bars, if both spellName and spellId are set, the spellId
-- was injected by the bug (the UI only sets one or the other). Clear it.
--
-- WARNING: this is a one-shot heuristic specific to the pre-v2 bug. If a
-- future schema ever makes dual spellName+spellId a legitimate state, this
-- block must be revisited; running it under that schema would silently
-- delete user data. The guard is `savedVersion < 2` so new installs never
-- hit it, but keep this in mind before bumping CURRENT_SCHEMA.
if savedVersion < 2 then
for _, frameData in ipairs(BarWardenDB.frames or {}) do
for _, bar in ipairs(frameData.bars or {}) do
if bar.trackMode ~= "Item"
and bar.spellName and bar.spellName ~= ""
and bar.spellId ~= nil then
bar.spellId = nil
end
end
end
end
-- v2 → v3: ensure sortMode exists on all frames (new field).
-- Per-bar display fields are additive nils so no migration needed for those.
if savedVersion < 3 then
for _, frameData in ipairs(BarWardenDB.frames or {}) do
if frameData.sortMode == nil then
frameData.sortMode = "manual"
end
end
end
-- v3 → v4: add activity tracker table for passive spell monitoring.
if savedVersion < 4 then
if not BarWardenDB.activity then
BarWardenDB.activity = {}
end
end
-- v4 → v5: housekeeping pass for keys that pre-v1.10.2 persisted but
-- no code ever reads, plus the switch from the hand-rolled minimap
-- button to LibDBIcon (which owns its own db sub-table).
-- * bar `_tokenCache` / `_tokenCacheKey` : the parsed-token cache used
-- to live on the bar config but is now module-local in Trackers.lua.
-- * visual `fadeWhenInactive` / `fadeSpeed` : exposed in the Visuals
-- tab and seeded in defaults, but BarEngine always did a hard
-- SetAlpha, so the toggles never animated anything. UI removed.
-- * global `minimapIcon` / `minimapIconPos` : replaced by the
-- LibDBIcon-shaped `minimap = { hide, minimapPos }` sub-table.
-- We invert `minimapIcon` (show-on) into `hide` (hide-on).
if savedVersion < 5 then
for _, frameData in ipairs(BarWardenDB.frames or {}) do
for _, bar in ipairs(frameData.bars or {}) do
bar._tokenCache = nil
bar._tokenCacheKey = nil
end
end
if type(BarWardenDB.visual) == "table" then
BarWardenDB.visual.fadeWhenInactive = nil
BarWardenDB.visual.fadeSpeed = nil
end
if type(BarWardenDB.global) == "table" then
if not BarWardenDB.minimap then
BarWardenDB.minimap = {
hide = BarWardenDB.global.minimapIcon == false,
minimapPos = BarWardenDB.global.minimapIconPos or 220,
}
end
BarWardenDB.global.minimapIcon = nil
BarWardenDB.global.minimapIconPos = nil
end
end
BarWardenDB.schemaVersion = CURRENT_SCHEMA
end
-- Canonicalise + backfill a frames array, in place. Idempotent, so it is safe
-- to run on every load and on ANY frame source - the live DB, a loaded
-- profile, an imported string, a starter preset - not just the schema-
-- versioned live DB. This closes the v1 gap where profile-load / import
-- bypassed migration and arrived with legacy fields (bars that never tracked).
-- It only renames legacy fields and fills nil sub-tables; it NEVER clears an
-- identity field (spellName / spellId / itemId / trackMode).
function ns:MigrateFrames(frames)
if type(frames) ~= "table" then return frames end
for _, f in ipairs(frames) do
if type(f) == "table" then
if f.sortMode == nil then f.sortMode = "manual" end
f.bars = f.bars or {}
for _, bar in ipairs(f.bars) do
if type(bar) == "table" then
-- Legacy field renames (idempotent: only act if present).
local s = bar.spell
if s ~= nil then
if type(s) == "number" then
if bar.trackMode == "Item" then
bar.itemId = bar.itemId or s
else
bar.spellId = bar.spellId or s
end
elseif type(s) == "string" and s ~= "" then
bar.spellName = bar.spellName or s
end
bar.spell = nil
end
if bar.spellInput ~= nil then
bar.spellName = bar.spellName or bar.spellInput
bar.spellInput = nil
end
if bar.target ~= nil then
bar.unit = bar.unit or bar.target
bar.target = nil
end
-- Per-bar default backfill: guarantee the sub-tables the
-- engine reads exist, so adding a new per-bar field can
-- never strand a bar saved before that field existed.
-- Fills nils only; identity fields untouched.
if bar.conditions == nil then bar.conditions = {} end
if bar.display == nil then bar.display = {} end
end
end
end
end
return frames
end
-- Pre-migration safety net: snapshot the current frames into a small ring of
-- timestamped backups before anything mutates them, so a bad upgrade or a
-- destructive load is recoverable via ns:RestoreLastBackup(). Kept to the
-- last few; skips empty layouts (nothing worth saving).
local MAX_BACKUPS = 3
function ns:BackupFrames(reason)
if not BarWardenDB or type(BarWardenDB.frames) ~= "table" then return end
if #BarWardenDB.frames == 0 then return end
BarWardenDB.backups = BarWardenDB.backups or {}
table.insert(BarWardenDB.backups, 1, {
t = (time and time()) or 0,
reason = reason or "migration",
frames = ns:CopyTable(BarWardenDB.frames),
})
while #BarWardenDB.backups > MAX_BACKUPS do
table.remove(BarWardenDB.backups)
end
end
-- Restore the most recent frames backup. Returns true if one was restored.
function ns:RestoreLastBackup()
if not BarWardenDB or type(BarWardenDB.backups) ~= "table" then return false end
local b = BarWardenDB.backups[1]
if not b or type(b.frames) ~= "table" then return false end
BarWardenDB.frames = ns:CopyTable(b.frames)
return true
end
-- ----------------------------------------------------------------------------
-- Import from a sibling "BarWarden v1" install
--
-- The parallel BarWarden V2 test build runs alongside the live v1 addon in the
-- same client, so v2 can read v1's in-memory SavedVariables and offer a
-- one-click layout import - no export/paste needed.
--
-- V1_DB_NAME is written split ("BarWarden" .. "DB") ON PURPOSE: the v2-test
-- deploy script rewrites the contiguous token BarWardenDB -> BarWardenV2DB, but
-- must NOT rewrite this, so it keeps pointing at v1's data. In the normal
-- single-addon release, this name equals our own DB global, so GetV1Layout
-- finds "the same table" and returns nil (nothing separate to import).
-- ----------------------------------------------------------------------------
local V1_DB_NAME = "BarWarden" .. "DB"
-- Return v1's saved layout (a migrated copy of its frames + visual) if a
-- SEPARATE v1 install is loaded, else nil (including the release case where
-- v1's DB is our DB).
function ns:GetV1Layout()
local v1 = _G[V1_DB_NAME]
if type(v1) ~= "table" then return nil end
if v1 == BarWardenDB then return nil end -- same addon: nothing separate
if type(v1.frames) ~= "table" or #v1.frames == 0 then return nil end
local layout = { frames = ns:CopyTable(v1.frames) }
if type(v1.visual) == "table" then layout.visual = ns:CopyTable(v1.visual) end
ns:MigrateFrames(layout.frames)
return layout
end
-- Copy v1's layout into this addon's DB (backs up the current layout first).
-- Returns the number of bars imported, or nil if there was nothing to import.
function ns:ImportFromV1()
local layout = ns:GetV1Layout()
if not layout then return nil end
ns:BackupFrames("before import from v1")
BarWardenDB.frames = layout.frames
if layout.visual then
BarWardenDB.visual = layout.visual
ns:MergeDefaults(BarWardenDB.visual, ns.DEFAULTS.visual)
end
ns.db = BarWardenDB
local bars = 0
for _, f in ipairs(BarWardenDB.frames) do bars = bars + #(f.bars or {}) end
if ns.FireCallback then ns:FireCallback("OnProfileChanged", nil) end
return bars
end
function ns:InitDB()
-- Wiping the visual-table cache here covers the cold-start case where
-- ns:GetVisual() was called before BarWardenDB existed and cached the
-- DEFAULTS pointer. After this function returns, callers get the live
-- SavedVariables table instead.
ns:InvalidateVisualCache()
if not BarWardenDB then
BarWardenDB = ns:CopyTable(ns.DEFAULTS)
else
-- Merge only config tables (global, visual) so new settings added in
-- future versions are picked up. Do NOT merge into "frames": that is
-- user data (an array) and MergeDefaults would corrupt it by injecting
-- the sample-frame defaults into the user's first group/bar.
if type(BarWardenDB.global) ~= "table" then
BarWardenDB.global = ns:CopyTable(ns.DEFAULTS.global)
else
ns:MergeDefaults(BarWardenDB.global, ns.DEFAULTS.global)
end
if type(BarWardenDB.visual) ~= "table" then
BarWardenDB.visual = ns:CopyTable(ns.DEFAULTS.visual)
else
ns:MergeDefaults(BarWardenDB.visual, ns.DEFAULTS.visual)
end
-- Ensure frames array exists (but never merge into it)
if type(BarWardenDB.frames) ~= "table" then
BarWardenDB.frames = ns:CopyTable(ns.DEFAULTS.frames)
end
-- Do NOT default schemaVersion here. A pre-schema save (missing
-- field) must be treated as version 0 so MigrateDB's `or 0` fallback
-- kicks in and the v0 -> v1 legacy-field rename actually runs.
-- Stamping CURRENT_SCHEMA here would skip every migration for
-- users upgrading from a pre-schemaVersion release. MigrateDB
-- writes the final schemaVersion when it finishes.
MigrateDB()
-- Minimap state lives in its own sub-table because LibDBIcon-1.0
-- expects to own the `hide` + `minimapPos` keys directly. Runs
-- AFTER MigrateDB so the v4 → v5 migration gets a chance to seed
-- the table from legacy `global.minimapIcon*` before defaults
-- backfill anything missing.
if type(BarWardenDB.minimap) ~= "table" then
BarWardenDB.minimap = ns:CopyTable(ns.DEFAULTS.minimap)
else
ns:MergeDefaults(BarWardenDB.minimap, ns.DEFAULTS.minimap)
end
end
ns.db = BarWardenDB
-- Canonicalise + backfill the live frames every load (idempotent). This
-- guarantees old bars gain any newly-added per-bar sub-table, and is the
-- same entry point profile-load / import / starter route through, so no
-- frame source can arrive un-migrated.
ns:MigrateFrames(BarWardenDB.frames)
-- Remove legacy stats table (replaced by ActivityTracker)
BarWardenDB.stats = nil
-- Ensure activity table exists for passive activity tracking
if not BarWardenDB.activity then
BarWardenDB.activity = {}
end
-- Account-wide profile storage (shared across all characters)
if not BarWardenAccountDB then
BarWardenAccountDB = {
profiles = {
["Default"] = {
description = "Default starter profile",
lastModified = time(),
data = {
frames = ns:CopyTable(ns.DEFAULTS.frames),
visual = ns:CopyTable(ns.DEFAULTS.visual),
},
},
},
}
end
if not BarWardenAccountDB.profiles then
BarWardenAccountDB.profiles = {}
end
-- One-time migration: move any per-character profiles into the account store.
-- Check with next() because an empty table {} is truthy in Lua but has nothing
-- to migrate, so we still clear the key so it isn't re-checked every login.
if type(BarWardenDB.profiles) == "table" then
for name, profile in pairs(BarWardenDB.profiles) do
if not BarWardenAccountDB.profiles[name] then
BarWardenAccountDB.profiles[name] = profile
end
end
BarWardenDB.profiles = nil
end
-- Point ns.profiles at the account-wide table for all profile operations
ns.profiles = BarWardenAccountDB.profiles
end