-- 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, MAX_WIPES = 15, 5 local fights = CreateFrame("Frame") ShaguDPS.fights = fights -- newest first: { id, name, kill, duration, ended, damage, heal, classes } fights.list = {} fights.next_id = 1 -- Keep the existing saved list/IDs, with independent retention per result. fights.Trim = function(self) local kills, wipes, i = 0, 0, 1 while i <= table.getn(self.list) do local fight = self.list[i] if fight.kill then kills = kills + 1 else wipes = wipes + 1 end if (fight.kill and kills > MAX_FIGHTS) or (not fight.kill and wipes > MAX_WIPES) then table.remove(self.list, i) else i = i + 1 end end end 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) while table.getn(self.list) > 0 do table.remove(self.list) 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) fights:Trim() 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 fights:Trim() 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)