-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathhardcore_state.lua
More file actions
388 lines (316 loc) · 11.8 KB
/
hardcore_state.lua
File metadata and controls
388 lines (316 loc) · 11.8 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
--[[
hardcore_state.lua
Deathlog-specific hardcore status tracking and external addon detection.
Provides the isUnitTracked callback for DeathNotificationLib.AttachAddon()
by combining multiple signals:
1. Native Hardcore realms (always hardcore)
2. Soul of Iron realms (BCA — buff scanning via C_UnitAuras)
3. External addon detection (Hardcore, HardcoreTBC, UltraHardcore)
Also provides Deathlog_getHardcoreCharacterState() for the info button.
All Soul of Iron / Tarnished Soul / External Hardcore state is managed
here in a local table. The player's record is bound to the
deathlog_char_data SavedVariable at login for persistence.
Uses only the DeathNotificationLib public API — no Internal access.
IMPORTANT: This file must load AFTER DeathNotificationLib.xml so that
the DNL public API is available.
--]]
if not DeathNotificationLib then return end
---------------------------------------------------------------------------
-- Constants (all from public API)
---------------------------------------------------------------------------
local HC_STATE = DeathNotificationLib.HC_STATE
local IS_HARDCORE_REALM = DeathNotificationLib.IS_HARDCORE_REALM
local IS_SOUL_OF_IRON_REALM = DeathNotificationLib.IS_SOUL_OF_IRON_REALM
--- Whether external addon detection has run
local external_detection_done = false
---------------------------------------------------------------------------
-- HC state storage
--
-- hc_states["player"] is bound to deathlog_char_data (SavedVariable) at
-- PLAYER_LOGIN so that SoI / External HC fields persist across sessions.
-- Non-player units use ephemeral tables.
---------------------------------------------------------------------------
local hc_states = {} ---@type { [UnitToken]: table }
--- Get or create the HC state record for a unit.
---@param unit UnitToken
---@return table
local function getHcState(unit)
if not hc_states[unit] then
hc_states[unit] = {}
end
local name = UnitName(unit)
local guid = UnitGUID(unit)
if (name and hc_states[unit]._name ~= name) or (guid and hc_states[unit]._guid ~= guid) then
if unit ~= "player" then
hc_states[unit] = { _name = name, _guid = guid }
else
for k in pairs(hc_states[unit]) do
hc_states[unit][k] = nil
end
hc_states[unit]._name = name
hc_states[unit]._guid = guid
end
end
return hc_states[unit]
end
---------------------------------------------------------------------------
-- Soul of Iron / Tarnished Soul buff detection
---------------------------------------------------------------------------
---@param unit UnitToken
---@return boolean
local function unitHasSoulOfIron(unit)
for i = 1, 40 do
local auraData = C_UnitAuras.GetBuffDataByIndex(unit, i)
if not auraData then break end
if auraData.spellId == 364001 then
return true
end
end
return false
end
---@param unit UnitToken
---@return boolean
local function unitHasTarnishedSoul(unit)
for i = 1, 40 do
local auraData = C_UnitAuras.GetDebuffDataByIndex(unit, i)
if not auraData then break end
if auraData.spellId == 364226 then
return true
end
end
return false
end
--- Update Soul of Iron / Tarnished Soul fields on the HC state record.
---@param unit UnitToken
local function updateSoulOfIronTracking(unit)
if not IS_SOUL_OF_IRON_REALM then return end
local unitState = getHcState(unit)
if unitHasSoulOfIron(unit) then
unitState.hasSoulOfIron = true
unitState.hadSoulOfIron = true
else
unitState.hasSoulOfIron = false
end
if unitHasTarnishedSoul(unit) then
unitState.hasTarnishedSoul = true
unitState.hadTarnishedSoul = true
else
unitState.hasTarnishedSoul = false
end
end
---------------------------------------------------------------------------
-- External addon detectors
-- Each returns true only if the addon is loaded AND the player qualifies.
---------------------------------------------------------------------------
local isLoaded = C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded
--- Hardcore (Classic Era) — per-character SavedVariable check
local function detectHardcore()
if not isLoaded("Hardcore") then return false end
if type(Hardcore_Character) ~= "table" then return false end
local playerGUID = UnitGUID("player")
if not playerGUID then return false end
if Hardcore_Character.guid and Hardcore_Character.guid ~= playerGUID then
return false
end
if not Hardcore_Character.first_recorded then
return false
end
if type(Hardcore_Character.deaths) == "table" and #Hardcore_Character.deaths > 0 then
return false
end
if Hardcore_Character.verification_status == "FAIL" then
return false
end
return true
end
--- HardcoreTBC — audit data + distributed log death check
local function detectHardcoreTBC()
if not isLoaded("HardcoreTBC") then return false end
if type(HardcoreTBC_Saved) ~= "table" then return false end
local playerGUID = UnitGUID("player")
if not playerGUID then return false end
if not (HardcoreTBC_Saved.ga and HardcoreTBC_Saved.ga[playerGUID]) then
return false
end
if type(HardcoreTBC_DistributedLogDB) == "table" then
for _, guildData in pairs(HardcoreTBC_DistributedLogDB) do
if guildData.events then
for _, event in pairs(guildData.events) do
if event.type == 10
and not event.ignored
and event.guid == playerGUID
then
return false
end
end
end
end
end
if IsInGuild() then
local playerName = UnitName("player") .. "-" .. GetNormalizedRealmName()
for i = 1, GetNumGuildMembers() do
local name, _, _, _, _, _, publicNote, _, _, _, _, _, _, _, _, _, guid = GetGuildRosterInfo(i)
if guid == playerGUID or name == playerName then
if publicNote and string.match(publicNote, "^%[D%]") then
return false
end
break
end
end
end
return true
end
--- UltraHardcore — GUID-keyed character settings check
local function detectUltraHardcore()
if not isLoaded("UltraHardcore") then return false end
if type(UltraHardcoreDB) ~= "table" then return false end
local playerGUID = UnitGUID("player")
if not playerGUID then return false end
if not (UltraHardcoreDB.characterSettings and UltraHardcoreDB.characterSettings[playerGUID]) then
return false
end
if UltraHardcoreDB.characterStats
and UltraHardcoreDB.characterStats[playerGUID]
and (UltraHardcoreDB.characterStats[playerGUID].playerDeaths or 0) > 0
then
return false
end
return true
end
local detectors = {
{ name = "Hardcore", detect = detectHardcore },
{ name = "HardcoreTBC", detect = detectHardcoreTBC },
{ name = "UltraHardcore", detect = detectUltraHardcore },
}
---------------------------------------------------------------------------
-- External HC marking
---------------------------------------------------------------------------
--- Mark the player as hardcore via an external addon.
--- Writes to the duck-typed fields on the player's unit state.
---@param addonName string
---@return boolean
local function markExternalHardcore(addonName)
if not addonName or type(addonName) ~= "string" then return false end
local state = getHcState("player")
if state.hasExternalHardcore or state.hadExternalHardcore then return false end
state.hasExternalHardcore = addonName
return true
end
---------------------------------------------------------------------------
-- Run external addon detection (called once at login)
---------------------------------------------------------------------------
local function runExternalDetection()
if external_detection_done then return end
external_detection_done = true
-- Skip detection on native HC realms where status is already known
if IS_HARDCORE_REALM then return end
-- Also skip if status is already determined via other means (e.g. Soul of Iron buff)
local state = Deathlog_getHardcoreCharacterState("player")
if state == HC_STATE.HARDCORE or state == HC_STATE.NOT_HARDCORE_ANYMORE then
return
end
for _, entry in ipairs(detectors) do
if entry.detect() then
markExternalHardcore(entry.name)
return
end
end
end
---------------------------------------------------------------------------
-- State query
---------------------------------------------------------------------------
--- Returns the hardcore state for the given unit.
--- Reads duck-typed fields (hasSoulOfIron, hasExternalHardcore, etc.) from
--- the unit state table. These fields are managed by this file, not by DNL.
---@param unit UnitToken|nil defaults to "player"
---@return integer One of HC_STATE values
function Deathlog_getHardcoreCharacterState(unit)
unit = unit or "player"
if IS_HARDCORE_REALM then
return HC_STATE.HARDCORE
end
local unitState = getHcState(unit)
if unitState.hadExternalHardcore then
return HC_STATE.NOT_HARDCORE_ANYMORE
end
if unitState.hasExternalHardcore then
return HC_STATE.HARDCORE
end
if IS_SOUL_OF_IRON_REALM then
if unitState.hadTarnishedSoul then
return HC_STATE.NOT_HARDCORE_ANYMORE
end
if unitState.hasSoulOfIron then
return HC_STATE.HARDCORE
end
return HC_STATE.UNDETERMINED
end
return HC_STATE.NOT_HARDCORE
end
---------------------------------------------------------------------------
-- isUnitTracked callback for AttachAddon
---------------------------------------------------------------------------
--- Returns the name of the external HC addon the player had, or nil.
---@return string|nil
function Deathlog_getExternalHardcoreAddon()
return getHcState("player").hadExternalHardcore
end
--- Returns true if the unit should have its deaths tracked by Deathlog.
--- This is the callback passed to DeathNotificationLib.AttachAddon().
---@param unit string Unit token (e.g. "player", "party1")
---@return boolean
function Deathlog_isUnitTracked(unit)
return Deathlog_getHardcoreCharacterState(unit) == HC_STATE.HARDCORE
end
---------------------------------------------------------------------------
-- Death-time state transitions
--
-- When the player dies, flip external-HC "has" → "had" and mark SoI
-- as tainted so future isUnitTracked calls return false.
-- This runs in a separate PLAYER_DEAD handler AFTER DNL's handler
-- (because this frame is created later), so the broadcast has already
-- been decided by the time we flip the state.
---------------------------------------------------------------------------
local deathFrame = CreateFrame("Frame")
deathFrame:RegisterEvent("PLAYER_DEAD")
deathFrame:SetScript("OnEvent", function()
local unitState = getHcState("player")
-- External HC: they died, so they HAD it but no longer HAVE it
if unitState.hasExternalHardcore then
unitState.hadExternalHardcore = unitState.hasExternalHardcore
unitState.hasExternalHardcore = nil
end
-- SoI realm: mark tainted on any death so we don't broadcast again
if IS_SOUL_OF_IRON_REALM then
unitState.hadSoulOfIron = true
unitState.hadTarnishedSoul = true
end
end)
---------------------------------------------------------------------------
-- UNIT_AURA tracking (SoI realms only)
---------------------------------------------------------------------------
if IS_SOUL_OF_IRON_REALM then
local auraFrame = CreateFrame("Frame")
auraFrame:RegisterEvent("UNIT_AURA")
auraFrame:SetScript("OnEvent", function(self, event, unit)
if unit == "player" or UnitInParty(unit) or UnitInRaid(unit) then
updateSoulOfIronTracking(unit)
end
end)
end
---------------------------------------------------------------------------
-- Initialization — run at login
---------------------------------------------------------------------------
local initFrame = CreateFrame("Frame")
initFrame:RegisterEvent("PLAYER_LOGIN")
initFrame:SetScript("OnEvent", function(self, event)
self:UnregisterAllEvents()
-- Bind the per-character SavedVariable as the player's HC state so
-- Soul of Iron / Tarnished Soul / External HC fields persist across sessions.
hc_states["player"] = deathlog_char_data
updateSoulOfIronTracking("player")
-- Delay slightly so other addons' PLAYER_LOGIN handlers have run
C_Timer.After(0.2, function()
runExternalDetection()
end)
end)