-
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCore.lua
More file actions
240 lines (218 loc) · 9.54 KB
/
Core.lua
File metadata and controls
240 lines (218 loc) · 9.54 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
local addonName, ns = ...
ns.state = {
classFile = nil,
snapshot = nil,
activeBuffs = {},
activeBuffsMeta = {},
activeBuffsFingerprint = "",
recalcScheduled = false,
}
local CHAT_PREFIX = "|cff55ff55UH:|r "
local CHAT_PREFIX_WARN = "|cffffcc55UH:|r "
local CHAT_PREFIX_ERROR = "|cffff5555UH:|r "
local function migrateSavedVariables()
UncrushableHelperDB = UncrushableHelperDB or {}
UncrushableHelperPerCharDB = UncrushableHelperPerCharDB or {}
local db = UncrushableHelperDB
local perChar = UncrushableHelperPerCharDB
db.global = db.global or {}
if db.global.closeOnOutsideClick == nil then
db.global.closeOnOutsideClick = false
end
-- v0.1.0-dev left an `enabledClasses` opt-out table here; it's no
-- longer used since the addon now works for any class. The field is
-- left in place if present (harmless) but not created fresh.
perChar.minimap = perChar.minimap or {}
perChar.mainFrame = perChar.mainFrame or { shown = false }
perChar.plannedBuffs = perChar.plannedBuffs or {}
-- `perChar.targetBossLevelDiff` is a legacy field from an earlier
-- iteration that exposed a target-level selector. The addon now
-- always calculates against +3 raid bosses; the field is left in SV
-- where already present (harmless) but no longer initialized or read.
db.schemaVersion = 1
perChar.schemaVersion = 1
end
-- Re-build the snapshot and fan it out to UI. Callers should hit us via
-- ns:RequestRecalc() so bursts of UNIT_AURA/COMBAT_RATING_UPDATE events
-- collapse to a single render per frame.
function ns:Publish()
if not ns.calc or not ns.calc.ComputeSnapshot then return end
local perChar = UncrushableHelperPerCharDB
local snap = ns.calc:ComputeSnapshot({
classFile = ns.state.classFile,
activeSet = ns.state.activeBuffs,
plannedSet = perChar and perChar.plannedBuffs,
})
ns.state.snapshot = snap
if ns.ui and ns.ui.OnSnapshotChanged then
ns.ui:OnSnapshotChanged(snap)
end
end
-- Coalesces a flurry of same-tick events into one Publish. A ~100 ms delay
-- (instead of next-frame 0) gives WoW time to finish propagating stat
-- updates after PLAYER_EQUIPMENT_CHANGED: the event fires *before* the
-- client recomputes GetDodgeChance/GetParryChance/GetBlockChance, so a 0-
-- delay recalc would read stale values on gear swaps. 100 ms is
-- imperceptible in play and lets UNIT_STATS/COMBAT_RATING_UPDATE settle.
function ns:RequestRecalc()
if ns.state.recalcScheduled then return end
ns.state.recalcScheduled = true
C_Timer.After(0.1, function()
ns.state.recalcScheduled = false
ns:Publish()
end)
end
local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("PLAYER_LOGIN")
eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
eventFrame:RegisterEvent("UNIT_AURA")
eventFrame:RegisterEvent("COMBAT_RATING_UPDATE")
eventFrame:RegisterEvent("UNIT_STATS")
eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
eventFrame:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
-- TBC uses CHARACTER_POINTS_CHANGED for talent spends; PLAYER_TALENT_UPDATE
-- is a retail-era rename. Registering both keeps the addon forward-compatible
-- without forking the event frame, and the throttle de-dupes.
eventFrame:RegisterEvent("CHARACTER_POINTS_CHANGED")
if eventFrame.RegisterEvent then
pcall(eventFrame.RegisterEvent, eventFrame, "PLAYER_TALENT_UPDATE")
end
eventFrame:SetScript("OnEvent", function(_, event, arg1)
if event == "PLAYER_LOGIN" then
migrateSavedVariables()
ns.state.classFile = select(2, UnitClass("player"))
UncrushableHelperPerCharDB.lastKnownClass = ns.state.classFile
if ns.aura and ns.aura.RefreshActiveBuffs then
ns.aura:RefreshActiveBuffs()
end
ns:RequestRecalc()
print(CHAT_PREFIX .. "loaded (" .. tostring(ns.state.classFile) .. "). /uh debug for a snapshot.")
elseif event == "UNIT_AURA" then
if arg1 ~= "player" then return end
local changed = true
if ns.aura and ns.aura.RefreshActiveBuffs then
changed = ns.aura:RefreshActiveBuffs()
end
if changed then ns:RequestRecalc() end
elseif event == "PLAYER_ENTERING_WORLD"
or event == "COMBAT_RATING_UPDATE"
or event == "UNIT_STATS"
or event == "PLAYER_EQUIPMENT_CHANGED"
or event == "UPDATE_SHAPESHIFT_FORM"
or event == "CHARACTER_POINTS_CHANGED"
or event == "PLAYER_TALENT_UPDATE" then
ns:RequestRecalc()
end
end)
local function formatPct(v)
return string.format("%.2f%%", v or 0)
end
local function printSnapshot()
local snap = ns.state.snapshot
if not snap then
print(CHAT_PREFIX_WARN .. "no snapshot yet — try again after login finishes.")
return
end
print(CHAT_PREFIX .. "snapshot (" .. (snap.classInfo and snap.classInfo.label or snap.classFile or "?") .. " vs +3 raid boss)")
print(" Defense Skill: " .. tostring(snap.defenseSkill or 0))
print(" Miss: " .. formatPct(snap.miss))
print(" Dodge: " .. formatPct(snap.dodge))
print(" Parry: " .. formatPct(snap.parry))
if snap.components.block.applicable then
print(" Block: " .. formatPct(snap.block))
elseif snap.mode == "druid-special" then
print(" Block: n/a (druid — no block on attack table)")
else
print(" Block: n/a (no shield equipped)")
end
print(" Total: " .. formatPct(snap.total))
if snap.mode == "block" then
if snap.isUncrushable then
print(" |cff33ff55UNCRUSHABLE|r (>= " .. formatPct(ns.TARGET_CAP) .. ")")
else
print(" |cffff5555CRUSHABLE|r — short by " .. formatPct(snap.shortBy or 0))
end
elseif snap.mode == "druid-special" then
print(" |cffbbbb99Druid mode|r — anti-crit goal below replaces the cap verdict.")
else
print(" |cffbbbb99Informational only|r — no shield, cap verdict not applicable.")
end
if snap.antiCrit then
local ac = snap.antiCrit
local statusTag = ac.ok and "|cff33ff55OK|r" or "|cffff5555short by " .. formatPct(ac.shortBy) .. "|r"
print((" Anti-crit goal: %s needed %s"):format(formatPct(ac.target), statusTag))
print((" Defense (%d above 350): -%s"):format(
math.max(0, (snap.defenseSkill or 0) - 350), formatPct(ac.fromDefense)))
if ac.fromTalents > 0 then
print((" Survival of the Fittest: -%s"):format(formatPct(ac.fromTalents)))
end
print((" Resilience (%d rating): -%s"):format(ac.resilienceRating or 0, formatPct(ac.fromResilience)))
print((" Total reduction: -%s"):format(formatPct(ac.total)))
end
if ns.aura and ns.aura.ListTrackedForUI then
local rows = ns.aura:ListTrackedForUI()
if rows and #rows > 0 then
print(" Tracked buffs:")
for _, row in ipairs(rows) do
local tag = row.active and "|cff33ff55ACTIVE|r"
or row.planned and "|cff5599ffplanned|r"
or "|cff777777none|r"
print(" " .. tag .. " " .. row.label)
end
end
end
if snap.simulated and snap.simulated.delta and snap.simulated.delta.count > 0 then
local s = snap.simulated
print((" With %d planned buffs: total %s (+%s miss, +%s dodge, +%s parry, +%s block)"):format(
s.delta.count, formatPct(s.total),
formatPct(s.delta.miss), formatPct(s.delta.dodge),
formatPct(s.delta.parry), formatPct(s.delta.block)))
if snap.mode == "block" then
if s.isUncrushable then
print(" \194\183 projected |cff33ff55UNCRUSHABLE|r")
else
print(" \194\183 projected |cffff5555CRUSHABLE|r — still short by " .. formatPct(s.shortBy or 0))
end
end
end
for _, note in ipairs(snap.notes or {}) do
print(" note: " .. note)
end
end
SLASH_UNCRUSHABLEHELPER1 = "/uh"
SLASH_UNCRUSHABLEHELPER2 = "/uncrush"
SlashCmdList["UNCRUSHABLEHELPER"] = function(msg)
local raw = (msg or ""):match("^%s*(.-)%s*$") or ""
local lower = raw:lower()
if lower == "debug" then
ns:RequestRecalc()
-- RequestRecalc schedules for next tick; to give the user fresh
-- numbers we also compute synchronously here before printing.
ns:Publish()
printSnapshot()
elseif lower == "reset" then
if UncrushableHelperPerCharDB and UncrushableHelperPerCharDB.mainFrame then
UncrushableHelperPerCharDB.mainFrame.point = nil
end
if ns.ui and ns.ui.ResetPosition then ns.ui:ResetPosition() end
print(CHAT_PREFIX .. "frame position reset.")
elseif lower == "config" or lower == "settings" or lower == "options" then
if ns.OpenSettings then
ns:OpenSettings()
else
print(CHAT_PREFIX_WARN .. "settings panel not implemented yet.")
end
elseif lower == "show" then
if ns.ShowMain then ns:ShowMain() else print(CHAT_PREFIX_WARN .. "UI not implemented yet.") end
elseif lower == "hide" then
if ns.HideMain then ns:HideMain() else print(CHAT_PREFIX_WARN .. "UI not implemented yet.") end
elseif lower == "" or lower == "toggle" then
if ns.ToggleMain then
ns:ToggleMain()
else
print(CHAT_PREFIX_WARN .. "UI not implemented yet — try /uh debug for the raw snapshot.")
end
else
print(CHAT_PREFIX .. "commands: /uh, /uh show, /uh hide, /uh toggle, /uh config, /uh debug, /uh reset")
end
end