Files
ShaguDPS/fights.lua
T
2026-10-30 16:39:13 +00:00

164 lines
5.5 KiB
Lua

-- Fight history: a copy of the "current" segment of each boss fight, kept
-- in ShaguDPS_Fights (per character, so it survives a reload or logout).
--
-- A fight counts as a boss fight when somebody in the group targeted a boss
-- (classification "worldboss" or a skull level) while it ran. It is named
-- after that boss, and marked as a kill when the boss died during it.
local parser = ShaguDPS.parser
local data = ShaguDPS.data
local MAX_FIGHTS = 10
local fights = CreateFrame("Frame")
ShaguDPS.fights = fights
-- newest first: { id, name, kill, duration, ended, damage, heal, classes }
fights.list = {}
fights.next_id = 1
local boss, boss_dead, boss_guid
local dispels_recorded
local structured_deaths = CombatLogGetCurrentEventInfo and UnitGUID
local function IsBoss(unit)
if not UnitExists(unit) or UnitIsPlayer(unit) or UnitIsFriend("player", unit) then return nil end
-- Post-fight heals can open another parser segment while someone still
-- targets the corpse. Only an engaged, living boss identifies a new attempt.
if UnitIsDead(unit) or not UnitAffectingCombat(unit) then return nil end
return UnitClassification(unit) == "worldboss" or UnitLevel(unit) == -1
end
-- the units whose targets are checked for a boss
local scan_units = { "target", "targettarget" }
for i = 1, 4 do table.insert(scan_units, "party" .. i .. "target") end
for i = 1, 40 do table.insert(scan_units, "raid" .. i .. "target") end
local function ScanForBoss()
local raid = GetNumRaidMembers()
for _, unit in pairs(scan_units) do
-- skip raid units of a smaller raid
local _, _, n = string.find(unit, "^raid(%d+)")
if not n or tonumber(n) <= raid then
if IsBoss(unit) then
local guid = UnitGUID and UnitGUID(unit)
if type(guid) ~= "string" or guid == "" then guid = nil end
return UnitName(unit), guid
end
end
end
end
local function Copy(t)
local c = {}
for k, v in pairs(t) do
c[k] = type(v) == "table" and Copy(v) or v
end
return c
end
-- class and pet owner of everyone in the fight, so colors and pet names
-- still work after a reload
local function Classes(damage, heal, done, received)
local c = {}
for _, segment in pairs({ damage, heal, done or {}, received or {} }) do
for name in pairs(segment) do
c[name] = data.classes[name]
local owner = data.classes[name]
if owner and data.classes[owner] then c[owner] = data.classes[owner] end
end
end
return c
end
fights.Get = function(self, id)
for _, fight in pairs(self.list) do
if fight.id == id then return fight end
end
end
fights.Reset = function(self)
for k in pairs(self.list) do self.list[k] = nil end
end
-- the fight shown by a window: segment 2 with config.fight = id
fights.ForWindow = function(self, wconfig)
local fight = self:Get(wconfig.fight)
if fight then
-- restore class colors of fights from an earlier session
for name, class in pairs(fight.classes) do
if not data.classes[name] then data.classes[name] = class end
end
end
return fight
end
parser.OnFightStart = function()
dispels_recorded = ShaguDPS.dispels and ShaguDPS.dispels.available and true or false
boss, boss_dead, boss_guid = nil, nil, nil
end
parser.OnFightEnd = function(duration)
if not boss or duration < 5 then return end
if not next(data.damage[1]) and not next(data.heal[1])
and not next(data.dispels_done[1]) and not next(data.dispels_received[1]) then return end
local fight = {
id = fights.next_id,
name = boss,
kill = boss_dead,
duration = duration,
ended = date and date("%H:%M") or "",
damage = Copy(data.damage[1]),
heal = Copy(data.heal[1]),
dispels_recorded = dispels_recorded,
dispels_done = Copy(data.dispels_done[1]),
dispels_received = Copy(data.dispels_received[1]),
}
fight.classes = Classes(fight.damage, fight.heal, fight.dispels_done, fight.dispels_received)
fights.next_id = fights.next_id + 1
table.insert(fights.list, 1, fight)
while table.getn(fights.list) > MAX_FIGHTS do
table.remove(fights.list)
end
boss, boss_dead, boss_guid = nil, nil, nil
end
-- look for a boss once a second while a fight runs
fights:SetScript("OnUpdate", function()
if (this.tick or 0) > GetTime() then return end
this.tick = GetTime() + 1
if not parser.fight.active or boss then return end
boss, boss_guid = ScanForBoss()
end)
-- "Onyxia dies."
local death_pattern
if UNITDIESOTHER then
-- escape the pattern characters of the text, then turn "%s" into a capture
death_pattern = string.gsub(UNITDIESOTHER, "([%.%-%+%*%?%[%]%^%$%(%)])", "%%%1")
death_pattern = "^" .. string.gsub(death_pattern, "%%s", "(.+)") .. "$"
end
fights:RegisterEvent("CHAT_MSG_COMBAT_HOSTILE_DEATH")
fights:RegisterEvent("VARIABLES_LOADED")
if structured_deaths then fights:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED") end
fights:SetScript("OnEvent", function()
if event == "VARIABLES_LOADED" then
ShaguDPS_Fights = ShaguDPS_Fights or {}
fights.list = ShaguDPS_Fights
for _, fight in pairs(fights.list) do
if fight.id >= fights.next_id then fights.next_id = fight.id + 1 end
end
elseif event == "COMBAT_LOG_EVENT_UNFILTERED" then
-- Death events often have no source; match the destination GUID directly.
if boss_guid and arg2 == "UNIT_DIED" and arg8 == boss_guid then boss_dead = true end
elseif event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" and boss and death_pattern and arg1
and not (structured_deaths and boss_guid) then
local _, _, name = string.find(arg1, death_pattern)
if name == boss then boss_dead = true end
end
end)