Skip to content

Commit b9cae85

Browse files
committed
fix(runtime): harden cached stats and repair scans
Closes StatsPro AUDIT T2-48, T2-51, T3-45, T3-47, T3-49.
1 parent 94c2a76 commit b9cae85

2 files changed

Lines changed: 348 additions & 19 deletions

File tree

StatsPro.lua

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -696,13 +696,13 @@ cached = {
696696
-- secret/nil reads do not render as a real 0.0%.
697697
versTotal = nil,
698698
versTotalRating = 0,
699-
armorDR = 0,
699+
armorDR = nil,
700700
itemLevelOverall = nil,
701701
itemLevelEquipped = nil,
702702
durabilityValue = 100, -- holds avg or min depending on cached.useWorstDurability
703703
repairCost = 0, -- live repair cost in copper (sum from per-slot tooltip scan)
704704
-- WARNING: GetUnitSpeed returns secret values in combat → arithmetic taints. Cache OOC.
705-
speedPct = 0,
705+
speedPct = nil,
706706
speedWasSwimming = nil,
707707
-- Last clean hide-zero decision per stat row. Secret combat reads cannot be safely
708708
-- compared to 0, so they reuse this instead of making absent rows appear.
@@ -2093,6 +2093,7 @@ local function ScanDurabilityAndCost()
20932093
local sum, count, totalCost = 0, 0, 0
20942094
local minPct
20952095
local repairCostPending = false
2096+
local repairCostKnownCount = 0
20962097
for slot = DURABILITY_SLOT_MIN, DURABILITY_SLOT_MAX do
20972098
if not DURABILITY_SKIP_SLOTS[slot] then
20982099
local cur, max = GetInventoryItemDurability(slot)
@@ -2109,6 +2110,7 @@ local function ScanDurabilityAndCost()
21092110
end
21102111
local cost = data.repairCost
21112112
if SAFE_NUM.IsCleanFiniteNumber(cost) and not issecretvalue(cost) then
2113+
repairCostKnownCount = repairCostKnownCount + 1
21122114
if cost > 0 then
21132115
totalCost = totalCost + cost
21142116
end
@@ -2122,8 +2124,8 @@ local function ScanDurabilityAndCost()
21222124
end
21232125
end
21242126
end
2125-
if count == 0 then return 100, 100, 0, repairCostPending end
2126-
return sum / count, minPct, totalCost, repairCostPending
2127+
if count == 0 then return 100, 100, 0, repairCostPending, repairCostKnownCount end
2128+
return sum / count, minPct, totalCost, repairCostPending, repairCostKnownCount
21272129
end
21282130

21292131
-- WARNING: repairCost can lag behind durability: C_TooltipInfo may return nil
@@ -2134,10 +2136,10 @@ end
21342136
local durabilityRetryScheduled = false
21352137

21362138
local function RefreshDurabilityCache()
2137-
local avg, mn, cost, repairCostPending = ScanDurabilityAndCost()
2139+
local avg, mn, cost, repairCostPending, repairCostKnownCount = ScanDurabilityAndCost()
21382140
cached.durabilityValue = cached.useWorstDurability and mn or avg
2139-
if repairCostPending and cost <= 0 then
2140-
cached.repairCost = cached.repairCost or 0
2141+
if repairCostPending and (repairCostKnownCount or 0) == 0 then
2142+
cached.repairCost = 0
21412143
else
21422144
cached.repairCost = cost
21432145
end
@@ -2572,8 +2574,7 @@ end
25722574
-- WARNING: GetStringWidth/GetStringHeight on a FontString whose text contains in-combat
25732575
-- secret-tainted substrings return secret-tainted numbers. Arithmetic on those errors.
25742576
-- Mitigation: keep last non-secret measurement; refresh cache only if current read is
2575-
-- non-secret. Used for all 4 measurement points in SetTextSafe (label/rating/value
2576-
-- widths + label height for repair Y positioning).
2577+
-- non-secret. Used for column widths and row-height reads in SetTextSafe.
25772578
local function MeasuredOrCached(fs, current_cache, method)
25782579
local v = fs[method](fs)
25792580
if v and not issecretvalue(v) then
@@ -2612,6 +2613,8 @@ function Panel:SetTextSafe(labelStr, ratingStr, valueStr, lineCount, repairStr,
26122613
if hasRows then
26132614
self.cachedRatingW = MeasuredOrCached(self.ratingText, self.cachedRatingW, "GetStringWidth")
26142615
self.cachedValueW = MeasuredOrCached(self.valueText, self.cachedValueW, "GetStringWidth")
2616+
self.cachedRatingH = MeasuredOrCached(self.ratingText, self.cachedRatingH, "GetStringHeight")
2617+
self.cachedValueH = MeasuredOrCached(self.valueText, self.cachedValueH, "GetStringHeight")
26152618
if not labelsHidden then
26162619
self.cachedLabelW = MeasuredOrCached(self.labelText, self.cachedLabelW, "GetStringWidth")
26172620
-- labelText height drives Repair-row Y positioning; cache same way as widths.
@@ -2634,8 +2637,15 @@ function Panel:SetTextSafe(labelStr, ratingStr, valueStr, lineCount, repairStr,
26342637
-- column space without overlapping stat content rows.
26352638
local repairLabelW = 0
26362639
local lineH = GetNumberDB("fontSize")
2637-
if hasRows and not labelsHidden and self.cachedLabelH then
2638-
lineH = self.cachedLabelH / lineCount
2640+
if hasRows then
2641+
if labelsHidden then
2642+
local renderedH = 0
2643+
if hasRating and self.cachedRatingH then renderedH = math.max(renderedH, self.cachedRatingH) end
2644+
if hasValue and self.cachedValueH then renderedH = math.max(renderedH, self.cachedValueH) end
2645+
if renderedH > 0 then lineH = renderedH / lineCount end
2646+
elseif self.cachedLabelH then
2647+
lineH = self.cachedLabelH / lineCount
2648+
end
26392649
end
26402650
self.lastLineH = lineH
26412651

@@ -2775,6 +2785,8 @@ function Panel:ApplyStyle(font, size, force)
27752785
-- lineCount back). lastLineCount is intentionally NOT reset here — Panel:Reflow
27762786
-- relies on it as the cached content-line-count for re-feeding SetTextSafe.
27772787
self.cachedLabelH = nil
2788+
self.cachedRatingH = nil
2789+
self.cachedValueH = nil
27782790
self.heightDirty = true
27792791
end
27802792

@@ -3630,11 +3642,23 @@ local function OnPlayerEnteringWorld()
36303642
if next(db) == nil then
36313643
local swiftStatsDB = type(_G.SwiftStatsDB) == "table" and _G.SwiftStatsDB or nil
36323644
local swiftStatsLocalDB = type(_G.SwiftStatsLocalDB) == "table" and _G.SwiftStatsLocalDB or nil
3633-
local source = (swiftStatsDB and next(swiftStatsDB) ~= nil and swiftStatsDB)
3634-
or (swiftStatsLocalDB and next(swiftStatsLocalDB) ~= nil and swiftStatsLocalDB)
3645+
local source, sourceIsLocal
3646+
if swiftStatsDB and next(swiftStatsDB) ~= nil then
3647+
source = swiftStatsDB
3648+
sourceIsLocal = false
3649+
elseif swiftStatsLocalDB and next(swiftStatsLocalDB) ~= nil then
3650+
source = swiftStatsLocalDB
3651+
sourceIsLocal = true
3652+
end
36353653
if source then
36363654
for k, v in pairs(source) do
3637-
db[k] = (type(v) == "table") and CopyTable(v) or v
3655+
local copyKey = k ~= "dbVersion"
3656+
if not copyKey and sourceIsLocal and NormalizeDBVersion(v) <= CURRENT_DB_VERSION then
3657+
copyKey = true
3658+
end
3659+
if copyKey then
3660+
db[k] = (type(v) == "table") and CopyTable(v) or v
3661+
end
36383662
end
36393663
end
36403664
end
@@ -5638,12 +5662,16 @@ function addon:PrintDebugDump()
56385662
tostring(cached.showArmor), tostring(cached.showStagger)))
56395663

56405664
-- Panel positions: nil-guard (DB may be partial in pre-PEW edge cases)
5641-
local function PosLine(label, p, rp, x, y)
5665+
local function PosLine(label, p, rp, x, y, fallbackY)
56425666
if not p then return label..": <unset>" end
5643-
return string.format("%s: %s/%s %+d/%+d", label, p, rp, x or 0, y or 0)
5667+
local point = NormalizeAnchorPoint(p, "CENTER")
5668+
local relativePoint = NormalizeAnchorPoint(rp, point)
5669+
local xOfs = NormalizePositionOffset(x, 0)
5670+
local yOfs = NormalizePositionOffset(y, fallbackY or 0)
5671+
return string.format("%s: %s/%s %+.0f/%+.0f", label, point, relativePoint, xOfs, yOfs)
56445672
end
5645-
PrintMsg(PosLine("main", GetDB("point"), GetDB("relativePoint"), GetDB("xOfs"), GetDB("yOfs")))
5646-
PrintMsg(PosLine("side", GetDB("defensive_point"), GetDB("defensive_relativePoint"), GetDB("defensive_xOfs"), GetDB("defensive_yOfs")))
5673+
PrintMsg(PosLine("main", GetDB("point"), GetDB("relativePoint"), GetDB("xOfs"), GetDB("yOfs"), defaults.yOfs))
5674+
PrintMsg(PosLine("side", GetDB("defensive_point"), GetDB("defensive_relativePoint"), GetDB("defensive_xOfs"), GetDB("defensive_yOfs"), defaults.defensive_yOfs))
56475675
end
56485676

56495677
local function PrintDebugPerf()
@@ -6048,12 +6076,21 @@ if addon and addon.__statsproSmoke == true then
60486076
}
60496077
end,
60506078
panelVisualState = function()
6079+
local firstOverlay = mainPanel.tooltipOverlays and mainPanel.tooltipOverlays[1] or nil
6080+
local secondOverlay = mainPanel.tooltipOverlays and mainPanel.tooltipOverlays[2] or nil
60516081
return {
60526082
textOutlineStyle = cached.textOutlineStyle,
60536083
mainFrameHeight = mainPanel.frame:GetHeight(),
6084+
mainLastLineH = mainPanel.lastLineH,
60546085
mainBackgroundAlpha = mainPanel.frame.backdropColor and mainPanel.frame.backdropColor.a or nil,
60556086
mainBackgroundTextureAlpha = mainPanel.backgroundTexture and mainPanel.backgroundTexture.colorTexture and mainPanel.backgroundTexture.colorTexture.a or nil,
60566087
mainBackgroundTexturePoints = mainPanel.backgroundTexture and mainPanel.backgroundTexture.points or nil,
6088+
mainRepairPoints = mainPanel.repairText.points,
6089+
mainRepairLabelPoints = mainPanel.repairLabelText.points,
6090+
mainFirstOverlayHeight = firstOverlay and firstOverlay:GetHeight() or nil,
6091+
mainFirstOverlayPoints = firstOverlay and firstOverlay.points or nil,
6092+
mainSecondOverlayHeight = secondOverlay and secondOverlay:GetHeight() or nil,
6093+
mainSecondOverlayPoints = secondOverlay and secondOverlay.points or nil,
60576094
mainLabelFlags = mainPanel.labelText.fontFlags,
60586095
mainRatingFlags = mainPanel.ratingText.fontFlags,
60596096
mainValueFlags = mainPanel.valueText.fontFlags,
@@ -6073,6 +6110,10 @@ if addon and addon.__statsproSmoke == true then
60736110
renderMainPanelForSmoke = function(labelStr, ratingStr, valueStr, lineCount, repairStr, repairLabelStr, targetRows)
60746111
mainPanel:SetTextSafe(labelStr, ratingStr, valueStr, lineCount, repairStr, repairLabelStr, targetRows)
60756112
end,
6113+
setMainPanelStringHeightMultiplier = function(column, multiplier)
6114+
local fs = ({ label = mainPanel.labelText, rating = mainPanel.ratingText, value = mainPanel.valueText })[column]
6115+
if fs then fs.statsProStringHeightMultiplier = multiplier end
6116+
end,
60766117
mainPanelTooltipState = function()
60776118
return {
60786119
overlayCount = #(mainPanel.tooltipOverlays or {}),

0 commit comments

Comments
 (0)