forked from zarnivoop/skada
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSecretValueHelper.lua
More file actions
384 lines (333 loc) · 9.77 KB
/
Copy pathSecretValueHelper.lua
File metadata and controls
384 lines (333 loc) · 9.77 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
--[[
SecretValueHelper.lua
Utility module for handling WoW 12.0 secret values.
Secret values are returned by combat APIs during combat and cannot be used for
arithmetic or comparisons, but CAN be used with:
- FontString:SetText()
- StatusBar:SetValue()
- string.format() / string.concat()
]]
local _, Skada = ...
local SecretHelper = {}
Skada.SecretHelper = SecretHelper
-- Check if issecretvalue exists (WoW 12.0+)
local hasSecretAPI = issecretvalue ~= nil
--[[
Check if a value is a secret value
@param value - The value to check
@return boolean - true if value is secret
]]
function SecretHelper:IsSecret(value)
if not hasSecretAPI then return false end
return issecretvalue(value)
end
--[[
Check if we can access/operate on a value
@param value - The value to check
@return boolean - true if we can do arithmetic/comparisons
]]
function SecretHelper:CanAccess(value)
if not hasSecretAPI then return true end
if canaccessvalue then
return canaccessvalue(value)
end
-- If canaccessvalue doesn't exist, assume we can access non-secrets
return not issecretvalue(value)
end
--[[
Format a value for text display.
Works with both regular and secret values.
@param value - The value to format
@param suffix - Optional suffix to append (e.g., " DPS")
@return string - Formatted string for display
]]
function SecretHelper:FormatForDisplay(value, suffix)
if value == nil then return "--" end
suffix = suffix or ""
-- string.format works with secret values
return string.format("%s%s", value, suffix)
end
--[[
Get a safe number value for arithmetic/comparisons.
Returns nil if the value is secret (cannot be used for math).
@param value - The value to convert
@return number|nil - The number, or nil if secret
]]
function SecretHelper:GetSafeNumber(value)
if value == nil then return 0 end
if hasSecretAPI and issecretvalue(value) then
return nil -- Signal that this is secret and can't be used for math
end
return tonumber(value) or 0
end
--[[
Get value for bar sizing. For secrets, returns the order-based fallback.
@param value - The actual value (may be secret)
@param orderFallback - Fallback value based on order (for when value is secret)
@return number, boolean - The value to use for sizing, and whether it's a fallback
]]
function SecretHelper:GetBarValue(value, orderFallback)
if value == nil then
return orderFallback or 0, true
end
if hasSecretAPI and issecretvalue(value) then
return orderFallback or 0, true
end
local num = tonumber(value)
if num then
return num, false
end
return orderFallback or 0, true
end
--[[
Safely compare two values. Returns nil if comparison would fail.
@param a - First value
@param b - Second value
@return boolean|nil - Comparison result, or nil if cannot compare
]]
function SecretHelper:SafeCompare(a, b)
if hasSecretAPI then
if issecretvalue(a) or issecretvalue(b) then
return nil
end
end
-- Use pcall as additional safety
local ok, result = pcall(function() return a > b end)
if ok then
return result
end
return nil
end
--[[
Format a number safely, handling secret values
@param value - The value to format
@return string - Formatted number or secret representation
]]
function SecretHelper:FormatNumber(value)
if value == nil then return "0" end
if hasSecretAPI and issecretvalue(value) then
-- Secret values can be passed to SetText but we can't do math on them
-- Return as-is, the display layer will handle it
return tostring(value)
end
local num = tonumber(value)
if num then
return Skada:FormatNumber(num)
end
return "0"
end
--[[
Check if the WoW client has secret API (WoW 12.0+)
@return boolean - true if issecretvalue exists
]]
function SecretHelper:HasSecretAPI()
return hasSecretAPI
end
--[[
Detect if any values in a table are secrets
@param dataTable - Table to check
@param valueKey - Key to check in each item (default "totalAmount")
@return boolean - true if any secret values found
]]
function SecretHelper:DetectSecrets(dataTable, valueKey)
if not hasSecretAPI then return false end
if not dataTable then return false end
valueKey = valueKey or "totalAmount"
for _, item in pairs(dataTable) do
if type(item) == "table" then
local value = item[valueKey]
if value and issecretvalue(value) then
return true
end
end
end
return false
end
--[[
Get player name safely
@param player - Player table from NativeAPI
@return string - Player name or nil
]]
function SecretHelper:GetPlayerName(player)
if not player then return nil end
-- Handle secret values for name
local rawName = player.name or player.unitName
if rawName and type(rawName) == "string" then
return rawName
end
return nil
end
--[[
Get player class safely
@param player - Player table from NativeAPI
@return string - Player class or nil
]]
function SecretHelper:GetPlayerClass(player)
if not player then return nil end
local rawClass = player.class or player.classFilename
if rawClass and type(rawClass) == "string" then
return rawClass
end
return nil
end
--[[
Update window metadata for secret state changes
Handles the common pattern of wiping window and setting metadata
@param win - Window object
@param hasSecrets - Boolean indicating if current data has secrets
]]
function SecretHelper:UpdateWindowMetadata(win, hasSecrets)
if not win or not win.metadata then return end
-- Wipe window if secret state changed and force refresh
if win.metadata.wasSecretValues ~= nil and win.metadata.wasSecretValues ~= hasSecrets then
win:Wipe()
win.changed = true
end
win.metadata.wasSecretValues = hasSecrets
win.metadata.ordersort = hasSecrets
end
--[[
Get display value for a bar, handling secrets
@param value - The actual value (may be secret)
@param order - Order/index for fallback when secret
@return number - Value to use for bar sizing
]]
function SecretHelper:GetDisplayValue(value, order)
if hasSecretAPI and value and issecretvalue(value) then
return 1000 - (order or 1)
end
return tonumber(value) or 0
end
--[[
Get max value for window metadata
@param hasSecrets - Boolean indicating if data has secrets
@param max - Calculated max value (for non-secret case)
@param count - Number of items (for secret case)
@return number - Max value for metadata
]]
function SecretHelper:GetMaxValue(hasSecrets, max, count)
if hasSecrets then
return 1000 - 1
end
return (max > 0 and max or 1)
end
--[[
Safe tonumber that returns 0 for secrets instead of crashing
@param value - Value to convert
@return number - 0 if secret or nil, otherwise tonumber result
]]
function SecretHelper:SafeNumber(value)
if value == nil then return 0 end
if hasSecretAPI and issecretvalue(value) then return 0 end
return tonumber(value) or 0
end
--[[
Format a value for display with optional percentage
@param value - The value to format
@param includePercent - Whether to include percentage
@param total - Total for percentage calculation
@return string - Formatted text
]]
function SecretHelper:FormatValueText(value, includePercent, total)
if hasSecretAPI and value and issecretvalue(value) then
return Skada:FormatNumberSecret(value)
end
local num = tonumber(value) or 0
local text = Skada:FormatNumber(num)
if includePercent and total and total > 0 then
local percent = (num / total) * 100
text = text .. string.format(" (%02.1f%%)", percent)
end
return text
end
-- Spell cache for performance optimization
-- Key: spellID, Value: {name, iconID, lastAccess}
SecretHelper.spellCache = {}
local spellCacheMaxSize = 500
local spellCacheTTL = 300 -- 5 minutes TTL
--[[
Cached version of Skada:GetSpellIcon that avoids repeated API calls
]]
function SecretHelper:GetSpellIcon(spellID)
if not spellID or spellID == 0 then return nil end
local now = GetTime()
local cached = self.spellCache[spellID]
-- Return cached result if valid
if cached and (now - cached.lastAccess) < spellCacheTTL then
cached.lastAccess = now
return cached.iconID
end
-- Fetch from API
local info = C_Spell.GetSpellInfo(spellID)
if info then
-- Cache the result
self.spellCache[spellID] = {
name = info.name,
iconID = info.iconID,
lastAccess = now
}
-- Cleanup old entries if cache is too large (simple LRU: remove 10 oldest)
local cacheSize = 0
for _ in pairs(self.spellCache) do
cacheSize = cacheSize + 1
end
if cacheSize > spellCacheMaxSize then
-- Remove oldest entries
local sorted = {}
for k, v in pairs(self.spellCache) do
table.insert(sorted, {key = k, access = v.lastAccess})
end
table.sort(sorted, function(a, b) return a.access < b.access end)
-- Remove 10% of cache
local toRemove = math.floor(spellCacheMaxSize * 0.1)
for i = 1, toRemove do
if sorted[i] then
self.spellCache[sorted[i].key] = nil
end
end
end
return info.iconID
end
return nil
end
--[[
Per-frame cache for secret detection to avoid redundant scans
Multiple modules scanning the same data in the same frame is wasteful
]]
SecretHelper.secretDetectionCache = {
frame = 0,
results = {}
}
--[[
Detect secrets with per-frame caching
This prevents multiple modules from scanning the same data in one update cycle
]]
function SecretHelper:DetectSecretsCached(cacheKey, dataTable, valueKey)
local currentFrame = GetTime()
local cache = self.secretDetectionCache
-- Clear cache if we're in a new frame
if currentFrame ~= cache.frame then
cache.frame = currentFrame
wipe(cache.results)
end
-- Return cached result if available
if cache.results[cacheKey] ~= nil then
return cache.results[cacheKey]
end
-- Perform detection
local hasSecrets = false
if dataTable and hasSecretAPI then
for _, item in pairs(dataTable) do
if type(item) == "table" then
local value = item[valueKey]
if value and issecretvalue(value) then
hasSecrets = true
break
end
end
end
end
-- Cache the result
cache.results[cacheKey] = hasSecrets
return hasSecrets
end