diff --git a/GuildSkadaHighScore.toc b/GuildSkadaHighScore.toc index 661c429..90a9b82 100644 --- a/GuildSkadaHighScore.toc +++ b/GuildSkadaHighScore.toc @@ -2,9 +2,9 @@ ## Author: Verath (Peter Eliasson) ## Version: 0.0.2 ## Title: Guild Skada High Score -## Notes: Ranking for dps/hps for raid bosses in a guild. +## Notes: Tracking and ranking of dps/hps in a guild. ## SavedVariables: GuildSkadaHighScoreDB ## Dependencies: Skada -lib/lib.xml -main.xml +lib/lib-include.xml +src/src-include.xml diff --git a/README.md b/README.md new file mode 100644 index 0000000..f99ea58 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Guild Skada High Score +A World of Warcraft addon for tracking and ranking dps/hps for encounters defeated by a guild. + diff --git a/gui.lua b/gui.lua deleted file mode 100644 index 797ff80..0000000 --- a/gui.lua +++ /dev/null @@ -1,396 +0,0 @@ -local addonName, addonTable = ... - --- Global functions for faster access -local tinsert = tinsert; -local tContains = tContains; - --- Set up module -local addon = addonTable[1]; -local gui = addon:NewModule("gui", "AceHook-3.0") -addon.gui = gui; - --- AceGUI -local AceGUI = LibStub("AceGUI-3.0"); - - -function gui:CreateHighScoreParseEntry(parse, role, rank) - local entryWidget = AceGUI:Create("SimpleGroup"); - entryWidget:SetFullWidth(true); - entryWidget:SetLayout("Flow"); - - local rankLabel = AceGUI:Create("Label"); - rankLabel:SetText(rank); - rankLabel:SetRelativeWidth(0.14); - - local dpsHpsLabel = AceGUI:Create("Label"); - local dpsHps = Skada:FormatNumber((role == "HEALER") and parse.hps or parse.dps); - dpsHpsLabel:SetText(dpsHps); - dpsHpsLabel:SetRelativeWidth(0.14); - - local nameLabel = AceGUI:Create("Label"); - nameLabel:SetText(parse.name); - nameLabel:SetRelativeWidth(0.14); - - local classLabel = AceGUI:Create("Label"); - classLabel:SetText(parse.class); - classLabel:SetRelativeWidth(0.14); - - local specLabel = AceGUI:Create("Label"); - specLabel:SetText(parse.specName or ""); - specLabel:SetRelativeWidth(0.14); - - local ilvlLabel = AceGUI:Create("Label"); - ilvlLabel:SetText(parse.itemLevel or ""); - ilvlLabel:SetRelativeWidth(0.14); - - local dateLabel = AceGUI:Create("Label"); - dateLabel:SetText(date("%m/%d/%y", parse.startTime)); - dateLabel:SetRelativeWidth(0.14); - - entryWidget:AddChild(rankLabel); - entryWidget:AddChild(dpsHpsLabel); - entryWidget:AddChild(nameLabel); - entryWidget:AddChild(classLabel); - entryWidget:AddChild(specLabel); - entryWidget:AddChild(ilvlLabel); - entryWidget:AddChild(dateLabel); - - return entryWidget; -end - -function gui:CreateGuildDropdown() - local dropdown = AceGUI:Create("Dropdown"); - self.guildDropdown = dropdown; - - dropdown:SetLabel("Guild"); - dropdown:SetRelativeWidth(0.25); - dropdown:SetCallback("OnValueChanged", function(widget, evt, guildId) - gui:SetSelectedGuild(guildId); - end) - - local guilds, numGuilds = addon.highscore:GetGuilds(); - if numGuilds > 0 then - dropdown:SetList(guilds); - else - dropdown:SetDisabled(true); - dropdown:SetText("No Guilds."); - end - - return dropdown; -end - -function gui:CreateZoneDropdown() - local dropdown = AceGUI:Create("Dropdown"); - self.zoneDropdown = dropdown; - - dropdown:SetLabel("Zone"); - dropdown:SetRelativeWidth(0.25); - dropdown:SetList(nil); - dropdown:SetDisabled(true); - dropdown:SetCallback("OnValueChanged", function(widget, evt, zoneId) - gui:SetSelectedZone(zoneId); - end) - - return dropdown; -end - -function gui:CreateDifficultyDropdown() - local dropdown = AceGUI:Create("Dropdown"); - self.difficultyDropdown = dropdown; - - dropdown:SetLabel("Difficulty"); - dropdown:SetRelativeWidth(0.25); - dropdown:SetList(nil); - dropdown:SetDisabled(true); - dropdown:SetCallback("OnValueChanged", function(widget, evt, difficultyId) - gui:SetSelectedDifficulty(difficultyId); - end) - - return dropdown; -end - -function gui:CreateEncounterDropdown() - local dropdown = AceGUI:Create("Dropdown"); - self.encounterDropdown = dropdown; - - dropdown:SetLabel("Encounter"); - dropdown:SetRelativeWidth(0.25); - dropdown:SetList(nil); - dropdown:SetDisabled(true); - dropdown:SetCallback("OnValueChanged", function(widget, evt, encounterId) - gui:SetSelectedEncounter(encounterId); - end) - - return dropdown; -end - -function gui:CreateHighScoreScrollFrame() - local scrollFrame = AceGUI:Create("ScrollFrame"); - scrollFrame:SetLayout("Flow"); - scrollFrame:SetFullWidth(true); - scrollFrame:SetFullHeight(true); - - -- Header: - -- Rank | DPS/HPS | Name | Class | Spec | Item Level | Date - local headerContainer = AceGUI:Create("SimpleGroup"); - headerContainer:SetFullWidth(true); - headerContainer:SetLayout("Flow"); - - local rankLabel = AceGUI:Create("Label"); - rankLabel:SetText("Rank"); - rankLabel:SetRelativeWidth(0.14); - local dpsHpsLabel = AceGUI:Create("Label"); - dpsHpsLabel:SetText("DPS/HPS"); - dpsHpsLabel:SetRelativeWidth(0.14); - local nameLabel = AceGUI:Create("Label"); - nameLabel:SetText("Name"); - nameLabel:SetRelativeWidth(0.14); - local classLabel = AceGUI:Create("Label"); - classLabel:SetText("Class"); - classLabel:SetRelativeWidth(0.14); - local specLabel = AceGUI:Create("Label"); - specLabel:SetText("Spec"); - specLabel:SetRelativeWidth(0.14); - local ilvlLabel = AceGUI:Create("Label"); - ilvlLabel:SetText("Item Level"); - ilvlLabel:SetRelativeWidth(0.14); - local dateLabel = AceGUI:Create("Label"); - dateLabel:SetText("Date"); - dateLabel:SetRelativeWidth(0.14); - - headerContainer:AddChild(rankLabel); - headerContainer:AddChild(dpsHpsLabel); - headerContainer:AddChild(nameLabel); - headerContainer:AddChild(classLabel); - headerContainer:AddChild(specLabel); - headerContainer:AddChild(ilvlLabel); - headerContainer:AddChild(dateLabel); - - local parsesContainer = AceGUI:Create("SimpleGroup"); - self.highScoreParsesContainer = parsesContainer; - parsesContainer:SetFullWidth(true); - parsesContainer:SetLayout("Flow"); - - scrollFrame:AddChild(headerContainer); - scrollFrame:AddChild(parsesContainer); - - return scrollFrame; -end - -function gui:CreateHighScoreTabGroup() - local container = AceGUI:Create("TabGroup"); - self.highScoreTabGroup = container; - - container:SetFullWidth(true); - container:SetFullHeight(true); - container:SetLayout("Fill"); - container:SetTabs({ - {value = "DAMAGER", text = "DPSers"}, - {value = "HEALER", text = "Healers"}, - {value = "TANK", text = "Tanks"} - }); - container:SetCallback("OnGroupSelected", function(widget, evt, roleId) - gui:SetSelectedRole(roleId); - end) - - container:AddChild(self:CreateHighScoreScrollFrame()); - - return container; -end - -function gui:CreateMainFrame() - local frame = AceGUI:Create("Frame") - self.mainFrame = frame; - - frame:Hide() - frame:SetWidth(800) - frame:SetHeight(600) - frame:SetTitle("Guild Skada High Score") - frame:SetLayout("Flow") - frame:SetCallback("OnClose", function() - gui:HideMainFrame() - end) - - frame:AddChild(self:CreateGuildDropdown()); - frame:AddChild(self:CreateZoneDropdown()); - frame:AddChild(self:CreateDifficultyDropdown()); - frame:AddChild(self:CreateEncounterDropdown()); - frame:AddChild(self:CreateHighScoreTabGroup()); - - return frame; -end - -function gui:DisplayParses() - local guildName = self.selectedGuild; - local zoneId = self.selectedZone; - local difficultyId = self.selectedDifficulty; - local encounter = self.selectedEncounter; - local roleId = self.selectedRole; - - local parsesContainer = self.highScoreParsesContainer; - parsesContainer:ReleaseChildren(); - - if guildName and zoneId and difficultyId and encounter and roleId then - local parses, numParses = addon.highscore:GetParses(guildName, - zoneId, difficultyId, encounter, roleId); - if numParses > 0 then - for rank, parse in ipairs(parses) do - local entryWidget = self:CreateHighScoreParseEntry(parse, roleId, rank); - parsesContainer:AddChild(entryWidget); - end - return; - end - end - - local noParsesLabel = AceGUI:Create("Label"); - noParsesLabel:SetText("No parses found."); - parsesContainer:AddChild(noParsesLabel); -end - -function gui:SetSelectedRole(roleId, noPropagation) - -- SelectTab, unlike SetValue for dropdowns, triggers the callback - if self.selectedRole ~= roleId then - self.selectedRole = roleId; - self.highScoreTabGroup:SelectTab(roleId); - self:DisplayParses(); - end -end - -function gui:SetSelectedEncounter(encounterId, noPropagation) - self.selectedEncounter = encounterId; - self.encounterDropdown:SetValue(encounterId); - self:DisplayParses(); -end - -function gui:SetSelectedDifficulty(difficultyId, noPropagation) - self.selectedDifficulty = difficultyId; - self.difficultyDropdown:SetValue(difficultyId); - - -- Update encounter dropdown with new guild, zone, difficulty - local encounters, numEncounters = addon.highscore:GetEncounters(self.selectedGuild, self.selectedZone, self.selectedDifficulty); - if numEncounters > 0 then - self.encounterDropdown:SetDisabled(false); - self.encounterDropdown:SetList(encounters); - else - self.encounterDropdown:SetDisabled(true); - self.encounterDropdown:SetList(nil); - self.encounterDropdown:SetText(nil); - end - - if not noPropagation then - if numEncounters == 1 then - -- If only one option, select it. - local encounterId, _ = next(encounters); - self:SetSelectedEncounter(encounterId); - else - self:SetSelectedEncounter(nil); - end - end -end - -function gui:SetSelectedZone(zoneId, noPropagation) - self.selectedZone = zoneId; - self.zoneDropdown:SetValue(zoneId); - - -- Update difficulty dropdown with new guild, zone - local difficulties, numDifficulties = addon.highscore:GetDifficulties(self.selectedGuild, self.selectedZone); - if numDifficulties > 0 then - self.difficultyDropdown:SetDisabled(false); - self.difficultyDropdown:SetList(difficulties); - else - self.difficultyDropdown:SetDisabled(true); - self.difficultyDropdown:SetList(nil); - self.difficultyDropdown:SetText(nil); - end - - if not noPropagation then - if numDifficulties == 1 then - -- If only one option, select it. - local difficultyId, _ = next(difficulties); - self:SetSelectedDifficulty(difficultyId); - else - self:SetSelectedDifficulty(nil); - end - end -end - -function gui:SetSelectedGuild(guildId, noPropagation) - self.selectedGuild = guildId; - self.guildDropdown:SetValue(guildId); - - -- Update zone dropdown for the new guild - local zones, numZones = addon.highscore:GetZones(guildId); - if numZones > 0 then - self.zoneDropdown:SetDisabled(false); - self.zoneDropdown:SetList(zones); - else - self.zoneDropdown:SetDisabled(true); - self.zoneDropdown:SetList(nil); - self.zoneDropdown:SetText(nil); - end - - if not noPropagation then - if numZones == 1 then - -- If only one option, select it. - local zoneId, _ = next(zones); - self:SetSelectedZone(zoneId); - else - self:SetSelectedZone(nil); - end - end -end - -function gui:ShowMainFrame() - if not self.mainFrame then - -- Only show if not already shown - self:CreateMainFrame():Show(); - - if self.selectedGuild then - -- Try to restore to same values as before - gui:SetSelectedGuild(self.selectedGuild, true); - gui:SetSelectedZone(self.selectedZone, true); - gui:SetSelectedDifficulty(self.selectedDifficulty, true); - gui:SetSelectedEncounter(self.selectedEncounter, true); - gui:SetSelectedRole(self.selectedRole, true); - elseif addon.guildName then - -- Try pre-selecting own guild if has one. - gui:SetSelectedGuild(addon.guildName); - end - - -- Have to do special for our tab group as it is never disabled - gui:SetSelectedRole(self.selectedRole or "DAMAGER"); - end -end - -function gui:HideMainFrame() - if self.mainFrame then - self.mainFrame:Release(); - - -- Unset references - self.mainFrame = nil; - self.guildDropdown = nil; - self.zoneDropdown = nil; - self.difficultyDropdown = nil; - self.encounterDropdown = nil; - self.highScoreTabGroup = nil; - end -end - -function gui:OnCloseSpecialWindows() - if self.mainFrame then - self:HideMainFrame() - return true - else - return self.hooks["CloseSpecialWindows"](); - end -end - - -function gui:OnEnable() - self:RawHook("CloseSpecialWindows", "OnCloseSpecialWindows"); -end - -function gui:OnDisable() - self:HideMainFrame(); - self:UnHook("CloseSpecialWindows"); -end \ No newline at end of file diff --git a/highscore.lua b/highscore.lua deleted file mode 100644 index ccd109c..0000000 --- a/highscore.lua +++ /dev/null @@ -1,326 +0,0 @@ -local addonName, addonTable = ... - --- Global functions for faster access -local tinsert = tinsert; -local tContains = tContains; -local sort = sort; -local random = random; -local format = format; - --- Set up module -local addon = addonTable[1]; -local highscore = addon:NewModule("highscore", "AceEvent-3.0", "AceTimer-3.0") -addon.highscore = highscore; - --- db defaults -addon.dbDefaults.realm.modules["highscore"] = { - ["guilds"] = { - ["*"] = { -- Guild Name - ["zones"] = { - ["*"] = { -- zoneId - ["difficulties"] = { - ["*"] = { -- difficultyId - ["encounters"] = { - ["*"] = { -- encounterId - --[[ - playerParses is a list of objects: - { - playerId = "", - role = "", - specName = "", - itemLevel = 0, - damage = 0, - healing = 0, - groupParseId = "" - } - --]] - playerParses = {} - } - } - } - } - } - } - } - }, - ["zones"] = { - ["*"] = { -- zoneId - zoneName = nil - } - }, - ["difficulties"] = { - ["*"] = { -- difficultyId - difficultyName = nil - } - }, - ["encounters"] = { - ["*"] = { -- encounterId - encounterName = nil - } - }, - ["players"] = { - ["*"] = { -- playerId - name = nil, - class = nil - } - }, - ["groupParses"] = { - --[[ - ["*"] = { -- groupParseId - startTime = 0, - duration = 0, - } - --]] - } -} - -addon.dbVersion = addon.dbVersion + 4 - --- Constants -local TRACKED_ZONE_IDS = { - 994 -- Highmaul -} - - --- Function for convering a database representation of --- a parse to a parse that can be returned to users. --- Copies all values and adds calculated once (like dps) -local function getReturnableParse(db, parse) - local parseCopy = {}; - for key, val in pairs(parse) do - parseCopy[key] = val; - end - - -- Get duration, statTime from the group parse - local groupParse = db.groupParses[parse["groupParseId"]] - parseCopy["duration"] = groupParse.duration; - parseCopy["startTime"] = groupParse.startTime; - parseCopy["groupParseId"] = nil - - -- Get player name and class - parseCopy["name"] = db.players[parse["playerId"]].name; - parseCopy["class"] = db.players[parse["playerId"]].class; - - -- Calculate dps/hps - parseCopy["dps"] = 0; - parseCopy["hps"] = 0; - if parseCopy["duration"] > 0 then - parseCopy["dps"] = parseCopy["damage"] / parseCopy["duration"]; - parseCopy["hps"] = parseCopy["healing"] / parseCopy["duration"]; - end - - return parseCopy; -end - -local function generateRandomKey() - local r1 = random(0, 1000); - local r2 = random(0, 1000); - local r3 = random(0, 1000); - -- 1000^3 = 1´000´000´000, should be enough for now... - return format("%x-%x-%x", r1, r2, r3); -end - -local function addGroupParse(db, startTime, duration) - -- Find a new unique key for the raid parse - local key = generateRandomKey(); - while db.groupParses[key] do - key = generateRandomKey(); - end - - db.groupParses[key] = { - startTime = startTime, - duration = duration - }; - - return key -end - -local function addZone(db, zoneId, zoneName) - db.zones[zoneId].zoneName = zoneName; -end - -local function addDifficulty(db, difficultyId, difficultyName) - db.difficulties[difficultyId].difficultyName = difficultyName; -end - -local function addEncounter(db, encounterId, encounterName) - db.encounters[encounterId].encounterName = encounterName; -end - -local function addPlayer(db, playerId, playerName, playerClass) - db.players[playerId].name = playerName; - db.players[playerId].class = playerClass; -end - -local function getParsesTable(db, guildName, zoneId, difficultyId, encounterId) - return db - .guilds[guildName] - .zones[zoneId] - .difficulties[difficultyId] - .encounters[encounterId] - .playerParses; -end - -local function addEncounterParseForPlayer(parsesTable, player, groupParseId) - local parse = { - playerId = player.id, - role = player.role, - specName = player.specName, - itemLevel = player.itemLevel, - damage = player.damage, - healing = player.healing, - groupParseId = groupParseId - } - tinsert(parsesTable, parse); -end - -function highscore:AddEncounterParsesForPlayers(guildName, encounter, players) - local zoneId = encounter.zoneId; - local zoneName = encounter.zoneName; - local encounterId = encounter.id; - local encounterName = encounter.name; - local difficultyId = encounter.difficultyId; - local difficultyName = encounter.difficultyName; - local startTime = encounter.startTime; - local duration = encounter.duration; - - assert(guildName) - assert(zoneId) - assert(zoneName) - assert(encounterId) - assert(encounterName) - assert(difficultyId) - assert(difficultyName) - assert(startTime) - assert(duration) - assert(players) - - if not tContains(TRACKED_ZONE_IDS, zoneId) then - self:Debug("AddEncounterParsesForPlayers: Current zone not not in tracked zones"); - return - end - - -- Add zone, difficulty and encounter info - addZone(self.db, zoneId, zoneName); - addDifficulty(self.db, difficultyId, difficultyName); - addEncounter(self.db, encounterId, encounterName); - - -- Add a group parse entry, holding data shared between all players - local groupParseId = addGroupParse(self.db, startTime, duration); - - local parsesTable = getParsesTable(self.db, guildName, zoneId, difficultyId, encounterId); - - for _, player in ipairs(players) do - self:Debug(format("addEncounterParseForPlayer: %s", player.name)); - - addPlayer(self.db, player.id, player.name, player.class); - addEncounterParseForPlayer(parsesTable, player, groupParseId) - end - -end - --- Returns (array of parses, numParses) -function highscore:GetParses(guildName, zoneId, difficultyId, encounterId, role, sortBy) - if (role ~= "TANK" and role ~= "HEALER" and role ~= "DAMAGER") then - return {}, 0; - end - - if not sortBy then - if role == "TANK" or role == "DAMAGER" then - sortBy = "dps" - elseif role == "HEALER" then - sortBy = "hps" - end - end - - local parsesTable = getParsesTable(self.db, guildName, zoneId, difficultyId, encounterId); - - -- Get a *copy* of all parses for the specified role - local parses = {}; - local numParses = 0; - for _, parse in ipairs(parsesTable) do - if parse.role == role then - local parseCopy = getReturnableParse(self.db, parse); - tinsert(parses, parseCopy); - numParses = numParses + 1; - end - end - - sort(parses, function(a, b) - return a[sortBy] > b[sortBy]; - end) - return parses, numParses; -end - --- Returns (array of {encounterId => encounterName}, numEncounters) -function highscore:GetEncounters(guildName, zoneId, difficultyId) - if not guildName or not zoneId or not difficultyId then - return {}, 0; - end - - local encounters = {}; - local numEncounters = 0; - local difficultiesTable = self.db.guilds[guildName].zones[zoneId].difficulties; - - for encounterId, _ in pairs(difficultiesTable[difficultyId].encounters) do - local encounterName = self.db.encounters[encounterId].encounterName; - encounters[encounterId] = encounterName; - numEncounters = numEncounters + 1; - end - return encounters, numEncounters; -end - --- Returns (array of {difficultyId => difficultyName}, numDifficulties) -function highscore:GetDifficulties(guildName, zoneId) - if not guildName or not zoneId then - return {}, 0; - end - - local difficulties = {}; - local numDifficulties = 0; - local difficultiesTable = self.db.guilds[guildName].zones[zoneId].difficulties; - - for difficultyId, _ in pairs(difficultiesTable) do - local difficultyName = self.db.difficulties[difficultyId].difficultyName; - difficulties[difficultyId] = difficultyName; - numDifficulties = numDifficulties + 1; - end - return difficulties, numDifficulties; -end - --- Returns (array of {zoneId => zoneName}, numZones) -function highscore:GetZones(guildName) - if not guildName then - return {}, 0; - end - - local zones = {}; - local numZones = 0; - for zoneId, _ in pairs(self.db.guilds[guildName].zones) do - local zoneName = self.db.zones[zoneId].zoneName; - zones[zoneId] = zoneName; - numZones = numZones + 1; - end - return zones, numZones; -end - --- Returns (array of {guildId => guildName}, numGuilds) -function highscore:GetGuilds() - local guildNames = {}; - local numGuilds = 0; - for guildName, _ in pairs(self.db.guilds) do - -- Actually guildId == guildName - guildNames[guildName] = guildName; - numGuilds = numGuilds + 1; - end - return guildNames, numGuilds; -end - - -function highscore:OnEnable() - self.db = addon.db.realm.modules["highscore"]; -end - -function highscore:OnDisable() - self.db = nil; -end diff --git a/inspect.lua b/inspect.lua deleted file mode 100644 index dce5075..0000000 --- a/inspect.lua +++ /dev/null @@ -1,275 +0,0 @@ -local addonName, addonTable = ... - --- Global functions for faster access -local tinsert = tinsert; -local floor = floor; -local wipe = wipe; - --- Set up module -local addon = addonTable[1]; -local inspect = addon:NewModule("inspect", "AceEvent-3.0", "AceTimer-3.0") -addon.inspect = inspect; - --- Constants -local INSPECT_CACHE_TIMEOUT = 900; -local INVENTORY_SLOT_NAMES = { - "HeadSlot","NeckSlot","ShoulderSlot","BackSlot","ChestSlot","WristSlot", - "HandsSlot","WaistSlot","LegsSlot","FeetSlot","Finger0Slot","Finger1Slot", - "Trinket0Slot","Trinket1Slot","MainHandSlot","SecondaryHandSlot" -} -local NOOP = function() end - -local playerGUID = UnitGUID("player"); -local inspectCache = {}; - - - -local function isInPVEInstance() - local isInstance, instanceType = IsInInstance(); - return inInstance and (instanceType == "party" or instanceType == "raid") -end - -local function getTalentSpec(unitName) - if unitName == "player" then - local spec = GetSpecialization(); - if spec and spec > 0 then - local _, name = GetSpecializationInfo(spec); - return name; - end - else - local spec = GetInspectSpecialization(unitName) - if spec and spec > 0 then - local role = GetSpecializationRoleByID(spec); - if role then - local _, name = GetSpecializationInfoByID(spec); - return name - end - end - end -end - -local function getItemLevel(unitName) - if unitName == "player" then - local _, equipped = GetAverageItemLevel(); - return floor(equipped); - else - local total, numItems = 0, 0; - for i = 1, #INVENTORY_SLOT_NAMES do - local slotName = INVENTORY_SLOT_NAMES[i]; - local slotId = GetInventorySlotInfo(slotName); - local itemLink = GetInventoryItemLink(unitName, slotId); - - if itemLink then - local _, _, _, itemLevel = GetItemInfo(itemLink) - if itemLevel and itemLevel > 0 then - numItems = numItems + 1; - total = total + itemLevel; - end - end - end - if total < 1 or numItems < 15 then - return nil - else - return floor(total / numItems) - end - end -end - -local function hasPlayerInspectCache(playerId, ignoreExpired) - if inspectCache[playerId] and ignoreExpired then - return true -- We have a cache, might be expired - elseif inspectCache[playerId] and inspectCache[playerId].time then - local isExpired = (GetTime() - inspectCache[playerId].time) > INSPECT_CACHE_TIMEOUT - local hasAllAttributes = inspectCache[playerId].specName and inspectCache[playerId].itemLevel; - - return (not isExpired and hasAllAttributes) - else - return false; -- No entry for player id - end -end - -local function setPlayerInspectCache(playerId, specName, itemLevel) - inspectCache[playerId] = {}; - inspectCache[playerId].time = GetTime(); - inspectCache[playerId].specName = specName; - inspectCache[playerId].itemLevel = itemLevel; -end - -function inspect:StartNotifyInspectTimer() - if not self.notifyInspectTimer then - self:Debug("Starting notifyInspectTimer"); - - self.notifyInspectTimer = self:ScheduleRepeatingTimer(function() - self:NOTIFY_INSPECT_TIMER_DONE() - end, 1); - end -end - -function inspect:StopNotifyInspectTimer() - if self.notifyInspectTimer then - self:Debug("Stopping notifyInspectTimer"); - - self:CancelTimer(self.notifyInspectTimer); - self.notifyInspectTimer = nil; - end -end - -function inspect:QueueInspect(player, callback) - self:Debug("QueueInspect", player.name) - - if not self.inspectQueue[player.id] then - self.inspectQueue[player.id] = {player = player, callbacks = {}}; - end - tinsert(self.inspectQueue[player.id].callbacks, callback); - - self:StartNotifyInspectTimer(); -end - -function inspect:SetCachedInspectDataForPlayer(player) - if player.id == playerGUID then - player.specName = getTalentSpec("player") - player.itemLevel = getItemLevel("player"); - return true - elseif hasPlayerInspectCache(player.id, false) then - player.specName = inspectCache[player.id].specName; - player.itemLevel = inspectCache[player.id].itemLevel; - return true - elseif hasPlayerInspectCache(player.id, true) then - -- Expired cache, but better than nothing - player.specName = inspectCache[player.id].specName; - player.itemLevel = inspectCache[player.id].itemLevel; - return false - else - return false - end -end - -function inspect:GetInspectDataForPlayer(player, callback) - -- Make sure we always have a callback - callback = callback and callback or NOOP; - - if self:SetCachedInspectDataForPlayer(player) then - return callback() - elseif not CanInspect(player.name, false) then - return callback() - else - self:QueueInspect(player, callback); - end -end - -function inspect:GetInspectDataForPlayers(players, callback) - local totalCallbacks = #players; - local doneCallbacks = 0; - - for _, player in ipairs(players) do - self:GetInspectDataForPlayer(player, function() - doneCallbacks = doneCallbacks + 1; - if doneCallbacks == totalCallbacks then - callback(); - end - end) - end -end - -function inspect:ResolveInspect(playerId, success) - if not self.inspectQueue[playerId] then - return - end - - local player = self.inspectQueue[playerId].player; - local callbacks = self.inspectQueue[playerId].callbacks; - self.inspectQueue[playerId] = nil; - - self:Debug("ResolveInspect", player.name, (success and "success" or "fail")) - - if success then - if not hasPlayerInspectCache(player.id) then - local specName = getTalentSpec(player.name); - local itemLevel = getItemLevel(player.name); - setPlayerInspectCache(player.id, specName, itemLevel); - end - - player.specName = inspectCache[player.id].specName; - player.itemLevel = inspectCache[player.id].itemLevel; - end - - for _,callback in ipairs(callbacks) do - callback(); - end -end - -function inspect:PreInspectGroup() - for i=1, GetNumGroupMembers() do - - local playerName = GetRaidRosterInfo(i); - if playerName and addon:IsInMyGuild(playerName) then - - local playerId = UnitGUID(playerName) - if playerId and playerId ~= playerGUID and not hasPlayerInspectCache(playerId) then - local player = {name=playerName, id=playerId} - self:QueueInspect(player, NOOP); - end - end - end -end - -function inspect:NOTIFY_INSPECT_TIMER_DONE() - -- Timeout any current inspection - if self.currentInspectPlayerId then - self:ResolveInspect(self.currentInspectPlayerId, false); - self.currentInspectPlayerId = nil; - end - - local playerId, inspectData = next(self.inspectQueue); - if playerId then - NotifyInspect(inspectData.player.name); - self.currentInspectPlayerId = playerId; - else - self:StopNotifyInspectTimer(); - end -end - -function inspect:INSPECT_READY(evt, GUID) - if self.currentInspectPlayerId == GUID then - self:ResolveInspect(self.currentInspectPlayerId, true) - self.currentInspectPlayerId = nil; - end -end - -function inspect:GROUP_ROSTER_UPDATE(evt) - if not IsInGroup() then - self:Debug("Left group, wiping inspect cache"); - wipe(inspectCache); - wipe(inspect.inspectQueue); - inspect.currentInspectPlayerId = nil; - end -end - -function inspect:ZONE_CHANGED_NEW_AREA(evt) - if isInPVEInstance() then - -- We just zoned into an instance, try pre-inspecting the group - self:PreInspectGroup(); - end -end - -function inspect:OnEnable() - inspect.inspectQueue = {}; - inspect.notifyInspectTimer = nil; - inspect.currentInspectPlayerId = nil; - - self:RegisterEvent("INSPECT_READY"); - self:RegisterEvent("GROUP_ROSTER_UPDATE"); - self:RegisterEvent("ZONE_CHANGED_NEW_AREA"); -end - -function inspect:OnDisable() - self:UnregisterEvent("INSPECT_READY"); - self:UnregisterEvent("GROUP_ROSTER_UPDATE"); - self:UnregisterEvent("ZONE_CHANGED_NEW_AREA"); - - self:StopNotifyInspectTimer(); - - wipe(inspectCache); - wipe(inspect.inspectQueue); - inspect.currentInspectPlayerId = nil; -end \ No newline at end of file diff --git a/lib/lib-include.xml b/lib/lib-include.xml new file mode 100644 index 0000000..973e258 --- /dev/null +++ b/lib/lib-include.xml @@ -0,0 +1,11 @@ +<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd"> + <Script file="Ace3\LibStub\LibStub.lua"/> + + <Include file="Ace3\AceAddon-3.0\AceAddon-3.0.xml"/> + <Include file="Ace3\AceConsole-3.0\AceConsole-3.0.xml"/> + <Include file="Ace3\AceGUI-3.0\AceGUI-3.0.xml"/> + <Include file="Ace3\AceHook-3.0\AceHook-3.0.xml"/> + <Include file="Ace3\AceTimer-3.0\AceTimer-3.0.xml"/> + <Include file="Ace3\AceDB-3.0\AceDB-3.0.xml"/> + <Include file="Ace3\AceGUI-3.0\AceGUI-3.0.xml"/> +</Ui> \ No newline at end of file diff --git a/lib/lib.xml b/lib/lib.xml deleted file mode 100644 index 973e258..0000000 --- a/lib/lib.xml +++ /dev/null @@ -1,11 +0,0 @@ -<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd"> - <Script file="Ace3\LibStub\LibStub.lua"/> - - <Include file="Ace3\AceAddon-3.0\AceAddon-3.0.xml"/> - <Include file="Ace3\AceConsole-3.0\AceConsole-3.0.xml"/> - <Include file="Ace3\AceGUI-3.0\AceGUI-3.0.xml"/> - <Include file="Ace3\AceHook-3.0\AceHook-3.0.xml"/> - <Include file="Ace3\AceTimer-3.0\AceTimer-3.0.xml"/> - <Include file="Ace3\AceDB-3.0\AceDB-3.0.xml"/> - <Include file="Ace3\AceGUI-3.0\AceGUI-3.0.xml"/> -</Ui> \ No newline at end of file diff --git a/main.lua b/main.lua deleted file mode 100644 index 91ab007..0000000 --- a/main.lua +++ /dev/null @@ -1,263 +0,0 @@ -local addonName, addonTable = ... - -local tinsert = tinsert; -local tremove = tremove; - --- Create ACE3 addon -local addon = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") - -tinsert(addonTable, addon); -_G[addonName] = addon - --- Set up a default prototype for all modules -local modPrototype = { Debug = function(self, ...) addon:Debug(...) end } -addon:SetDefaultModulePrototype(modPrototype) - --- Db default settings -addon.dbDefaults = { - realm = { - modules = {} - }, - global = { - dbVersion = 1, - debugLog = {} - } -} - --- The current db version. Clear (migrate?) the database if --- version of database doesn't match this version. -addon.dbVersion = 2 - --- Constants -DEBUG_PRINT = true; -DEBUG_LOG = false; -MAX_NUM_DEBUG_LOG_ENTRIES = 300; - -local function getDifficultyNameById(difficultyId) - if difficultyId == 7 or difficultyId == 17 then - return "LFR"; - elseif difficultyId == 1 or difficultyId == 3 or difficultyId == 4 or difficultyId == 14 then - return "Normal"; - elseif difficultyId == 2 or difficultyId == 5 or difficultyId == 6 or difficultyId == 15 then - return "Heroic"; - elseif difficultyId == 16 then - return "Mythic"; - end - - return nil -end - -function addon:Debug(...) - if DEBUG_PRINT then - self:Print(...) - end - if DEBUG_LOG then - local logData = {date("%d/%m %H:%M:%S")}; - for i=1, select('#', ...) do - local v = select(i, ...) - tinsert(logData, v); - end - tinsert(self.db.global.debugLog, logData); - end -end - -function addon:UpdateMyGuildName() - if IsInGuild() then - local guildName, _, _ = GetGuildInfo("player") - if guildName ~= nil then - self.guildName = guildName - end - else - self.guildName = nil - end -end - -function addon:UpdateCurrentZone() - local zoneId, _ = GetCurrentMapAreaID() - local zoneName = GetRealZoneText(); - self.currentZone = {id = zoneId, name = zoneName}; - - if IsInInstance() then - self:Debug("UpdateCurrentZone", zoneId, zoneName) - else - self:UnsetCurrentEncounter(); - end -end - -function addon:SetCurrentEncounter(encounterId, encounterName, difficultyId, raidSize) - local difficultyName = getDifficultyNameById(difficultyId); - - self:Debug("SetCurrentEncounter", encounterId, encounterName, difficultyId, raidSize, difficultyName); - - if difficultyName then - self.currentEncounter = { - zoneId = self.currentZone.id, - zoneName = self.currentZone.name, - id = encounterId, - name = encounterName, - difficultyId = difficultyId, - difficultyName = difficultyName, - raidSize = raidSize - } - end -end - -function addon:UnsetCurrentEncounter() - if self.currentEncounter then - self:Debug("UnsetCurrentEncounter"); - self.currentEncounter = nil - end -end - -function addon:IsInMyGuild(playerName) - if self.guildName then - local guildName, _, _ = GetGuildInfo(playerName) - return guildName == self.guildName - else - return false - end -end - -function addon:GetGuildPlayersFromSet(skadaSet) - local players = {} - for i, player in ipairs(skadaSet.players) do - local playerData; - if self:IsInMyGuild(player.name) then - playerData = {id = player.id, name = player.name, damage = player.damage, healing = player.healing}; - tinsert(players, playerData); - end - end - return players -end - -function addon:SetRoleForPlayers(players) - for _, player in ipairs(players) do - player.role = UnitGroupRolesAssigned(player.name); - end -end - -function addon:SetClassForPlayers(players) - for _, player in ipairs(players) do - player.class = UnitClass(player.name); - end -end - -function addon:PLAYER_GUILD_UPDATE(evt, unitId) - if unitId == "player" then - self:UpdateMyGuildName() - end -end - -function addon:ENCOUNTER_START(evt, encounterId, encounterName, difficultyId, raidSize) - self:Debug("ENCOUNTER_START", encounterId, encounterName, difficultyId, raidSize) - self:SetCurrentEncounter(encounterId, encounterName, difficultyId, raidSize) -end - -function addon:ENCOUNTER_END(evt, encounterId, encounterName, difficultyId, raidSize, endStatus) - self:Debug("ENCOUNTER_END", encounterId, encounterName, difficultyId, raidSize, endStatus) - if endStatus == 1 then -- Success - self:SetCurrentEncounter(encounterId, encounterName, difficultyId, raidSize) - else - self:UnsetCurrentEncounter() - end -end - -function addon:ZONE_CHANGED_NEW_AREA(evt) - self:UpdateCurrentZone(); -end - -function addon:EndSegment() - self:Debug("EndSegment") - - -- Find the skada set matching the encounter - -- Looking only at the lastest set should make sense, - -- as that set should be the boss segment we just ended. - local _, skadaSet = next(Skada:GetSets()); - local encounter = self.currentEncounter; - - if not self.guildName then - self:Debug("Not in a guild"); - return; - end - - if not encounter then - self:Debug("No current encounter"); - return - end - - if not skadaSet or not skadaSet.gotboss or skadaSet.mobname ~= encounter.name then - self:Debug("No Skada set found for boss"); - return; - end - - encounter.duration = skadaSet.time; - encounter.startTime = skadaSet.starttime; - - local players = self:GetGuildPlayersFromSet(skadaSet); - self:SetRoleForPlayers(players); - self:SetClassForPlayers(players); - self.inspect:GetInspectDataForPlayers(players, function() - self.highscore:AddEncounterParsesForPlayers(self.guildName, encounter, players); - end) - - self:UnsetCurrentEncounter(); -end - -function addon:OnInitialize() - self.db = LibStub("AceDB-3.0"):New("GuildSkadaHighScoreDB", addon.dbDefaults, true) - - -- Make sure db version is in sync - if self.db.global.dbVersion ~= self.dbVersion then - self:Debug(format("Found not matching db versions: db=%d, addon=%d", - self.db.global.dbVersion, self.dbVersion)); - self:Debug("Resetting db"); - self.db:ResetDB(); - self.db.global.dbVersion = self.dbVersion; - end - - -- Purge old logs - if DEBUG_LOG then - local numLogsToPurge = (#self.db.global.debugLog - MAX_NUM_DEBUG_LOG_ENTRIES); - while numLogsToPurge >= 0 do - tremove(self.db.global.debugLog, 1) - numLogsToPurge = numLogsToPurge - 1; - end - else - wipe(self.db.global.debugLog); - end -end - -function addon:OnEnable() - self.currentEncounter = nil; - self.currentZone = {}; - self.guildName = nil; - - self:RegisterEvent("ENCOUNTER_START") - self:RegisterEvent("ENCOUNTER_END") - self:RegisterEvent("PLAYER_GUILD_UPDATE") - self:RegisterEvent("ZONE_CHANGED_NEW_AREA") - - self:SecureHook(Skada, "EndSegment") - - self:RegisterChatCommand("gshs", function() - self.gui:ShowMainFrame(); - end) - - self:UpdateMyGuildName(); - self:UpdateCurrentZone(); -end - -function addon:OnDisable() - self.currentEncounter = nil; - self.currentZone = {}; - self.guildName = nil; - - self:UnregisterEvent("ENCOUNTER_START") - self:UnregisterEvent("ENCOUNTER_END") - self:UnregisterEvent("PLAYER_GUILD_UPDATE") - self:UnregisterEvent("ZONE_CHANGED_NEW_AREA") - - self:UnHook(Skada, "EndSegment"); - - self:UnregisterChatCommand("gshs"); -end diff --git a/main.xml b/main.xml deleted file mode 100644 index 6cdd3d7..0000000 --- a/main.xml +++ /dev/null @@ -1,6 +0,0 @@ -<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd"> - <Script file="main.lua"/> - <Script file="inspect.lua"/> - <Script file="highscore.lua"/> - <Script file="gui.lua"/> -</Ui> \ No newline at end of file diff --git a/src/gui.lua b/src/gui.lua new file mode 100644 index 0000000..797ff80 --- /dev/null +++ b/src/gui.lua @@ -0,0 +1,396 @@ +local addonName, addonTable = ... + +-- Global functions for faster access +local tinsert = tinsert; +local tContains = tContains; + +-- Set up module +local addon = addonTable[1]; +local gui = addon:NewModule("gui", "AceHook-3.0") +addon.gui = gui; + +-- AceGUI +local AceGUI = LibStub("AceGUI-3.0"); + + +function gui:CreateHighScoreParseEntry(parse, role, rank) + local entryWidget = AceGUI:Create("SimpleGroup"); + entryWidget:SetFullWidth(true); + entryWidget:SetLayout("Flow"); + + local rankLabel = AceGUI:Create("Label"); + rankLabel:SetText(rank); + rankLabel:SetRelativeWidth(0.14); + + local dpsHpsLabel = AceGUI:Create("Label"); + local dpsHps = Skada:FormatNumber((role == "HEALER") and parse.hps or parse.dps); + dpsHpsLabel:SetText(dpsHps); + dpsHpsLabel:SetRelativeWidth(0.14); + + local nameLabel = AceGUI:Create("Label"); + nameLabel:SetText(parse.name); + nameLabel:SetRelativeWidth(0.14); + + local classLabel = AceGUI:Create("Label"); + classLabel:SetText(parse.class); + classLabel:SetRelativeWidth(0.14); + + local specLabel = AceGUI:Create("Label"); + specLabel:SetText(parse.specName or ""); + specLabel:SetRelativeWidth(0.14); + + local ilvlLabel = AceGUI:Create("Label"); + ilvlLabel:SetText(parse.itemLevel or ""); + ilvlLabel:SetRelativeWidth(0.14); + + local dateLabel = AceGUI:Create("Label"); + dateLabel:SetText(date("%m/%d/%y", parse.startTime)); + dateLabel:SetRelativeWidth(0.14); + + entryWidget:AddChild(rankLabel); + entryWidget:AddChild(dpsHpsLabel); + entryWidget:AddChild(nameLabel); + entryWidget:AddChild(classLabel); + entryWidget:AddChild(specLabel); + entryWidget:AddChild(ilvlLabel); + entryWidget:AddChild(dateLabel); + + return entryWidget; +end + +function gui:CreateGuildDropdown() + local dropdown = AceGUI:Create("Dropdown"); + self.guildDropdown = dropdown; + + dropdown:SetLabel("Guild"); + dropdown:SetRelativeWidth(0.25); + dropdown:SetCallback("OnValueChanged", function(widget, evt, guildId) + gui:SetSelectedGuild(guildId); + end) + + local guilds, numGuilds = addon.highscore:GetGuilds(); + if numGuilds > 0 then + dropdown:SetList(guilds); + else + dropdown:SetDisabled(true); + dropdown:SetText("No Guilds."); + end + + return dropdown; +end + +function gui:CreateZoneDropdown() + local dropdown = AceGUI:Create("Dropdown"); + self.zoneDropdown = dropdown; + + dropdown:SetLabel("Zone"); + dropdown:SetRelativeWidth(0.25); + dropdown:SetList(nil); + dropdown:SetDisabled(true); + dropdown:SetCallback("OnValueChanged", function(widget, evt, zoneId) + gui:SetSelectedZone(zoneId); + end) + + return dropdown; +end + +function gui:CreateDifficultyDropdown() + local dropdown = AceGUI:Create("Dropdown"); + self.difficultyDropdown = dropdown; + + dropdown:SetLabel("Difficulty"); + dropdown:SetRelativeWidth(0.25); + dropdown:SetList(nil); + dropdown:SetDisabled(true); + dropdown:SetCallback("OnValueChanged", function(widget, evt, difficultyId) + gui:SetSelectedDifficulty(difficultyId); + end) + + return dropdown; +end + +function gui:CreateEncounterDropdown() + local dropdown = AceGUI:Create("Dropdown"); + self.encounterDropdown = dropdown; + + dropdown:SetLabel("Encounter"); + dropdown:SetRelativeWidth(0.25); + dropdown:SetList(nil); + dropdown:SetDisabled(true); + dropdown:SetCallback("OnValueChanged", function(widget, evt, encounterId) + gui:SetSelectedEncounter(encounterId); + end) + + return dropdown; +end + +function gui:CreateHighScoreScrollFrame() + local scrollFrame = AceGUI:Create("ScrollFrame"); + scrollFrame:SetLayout("Flow"); + scrollFrame:SetFullWidth(true); + scrollFrame:SetFullHeight(true); + + -- Header: + -- Rank | DPS/HPS | Name | Class | Spec | Item Level | Date + local headerContainer = AceGUI:Create("SimpleGroup"); + headerContainer:SetFullWidth(true); + headerContainer:SetLayout("Flow"); + + local rankLabel = AceGUI:Create("Label"); + rankLabel:SetText("Rank"); + rankLabel:SetRelativeWidth(0.14); + local dpsHpsLabel = AceGUI:Create("Label"); + dpsHpsLabel:SetText("DPS/HPS"); + dpsHpsLabel:SetRelativeWidth(0.14); + local nameLabel = AceGUI:Create("Label"); + nameLabel:SetText("Name"); + nameLabel:SetRelativeWidth(0.14); + local classLabel = AceGUI:Create("Label"); + classLabel:SetText("Class"); + classLabel:SetRelativeWidth(0.14); + local specLabel = AceGUI:Create("Label"); + specLabel:SetText("Spec"); + specLabel:SetRelativeWidth(0.14); + local ilvlLabel = AceGUI:Create("Label"); + ilvlLabel:SetText("Item Level"); + ilvlLabel:SetRelativeWidth(0.14); + local dateLabel = AceGUI:Create("Label"); + dateLabel:SetText("Date"); + dateLabel:SetRelativeWidth(0.14); + + headerContainer:AddChild(rankLabel); + headerContainer:AddChild(dpsHpsLabel); + headerContainer:AddChild(nameLabel); + headerContainer:AddChild(classLabel); + headerContainer:AddChild(specLabel); + headerContainer:AddChild(ilvlLabel); + headerContainer:AddChild(dateLabel); + + local parsesContainer = AceGUI:Create("SimpleGroup"); + self.highScoreParsesContainer = parsesContainer; + parsesContainer:SetFullWidth(true); + parsesContainer:SetLayout("Flow"); + + scrollFrame:AddChild(headerContainer); + scrollFrame:AddChild(parsesContainer); + + return scrollFrame; +end + +function gui:CreateHighScoreTabGroup() + local container = AceGUI:Create("TabGroup"); + self.highScoreTabGroup = container; + + container:SetFullWidth(true); + container:SetFullHeight(true); + container:SetLayout("Fill"); + container:SetTabs({ + {value = "DAMAGER", text = "DPSers"}, + {value = "HEALER", text = "Healers"}, + {value = "TANK", text = "Tanks"} + }); + container:SetCallback("OnGroupSelected", function(widget, evt, roleId) + gui:SetSelectedRole(roleId); + end) + + container:AddChild(self:CreateHighScoreScrollFrame()); + + return container; +end + +function gui:CreateMainFrame() + local frame = AceGUI:Create("Frame") + self.mainFrame = frame; + + frame:Hide() + frame:SetWidth(800) + frame:SetHeight(600) + frame:SetTitle("Guild Skada High Score") + frame:SetLayout("Flow") + frame:SetCallback("OnClose", function() + gui:HideMainFrame() + end) + + frame:AddChild(self:CreateGuildDropdown()); + frame:AddChild(self:CreateZoneDropdown()); + frame:AddChild(self:CreateDifficultyDropdown()); + frame:AddChild(self:CreateEncounterDropdown()); + frame:AddChild(self:CreateHighScoreTabGroup()); + + return frame; +end + +function gui:DisplayParses() + local guildName = self.selectedGuild; + local zoneId = self.selectedZone; + local difficultyId = self.selectedDifficulty; + local encounter = self.selectedEncounter; + local roleId = self.selectedRole; + + local parsesContainer = self.highScoreParsesContainer; + parsesContainer:ReleaseChildren(); + + if guildName and zoneId and difficultyId and encounter and roleId then + local parses, numParses = addon.highscore:GetParses(guildName, + zoneId, difficultyId, encounter, roleId); + if numParses > 0 then + for rank, parse in ipairs(parses) do + local entryWidget = self:CreateHighScoreParseEntry(parse, roleId, rank); + parsesContainer:AddChild(entryWidget); + end + return; + end + end + + local noParsesLabel = AceGUI:Create("Label"); + noParsesLabel:SetText("No parses found."); + parsesContainer:AddChild(noParsesLabel); +end + +function gui:SetSelectedRole(roleId, noPropagation) + -- SelectTab, unlike SetValue for dropdowns, triggers the callback + if self.selectedRole ~= roleId then + self.selectedRole = roleId; + self.highScoreTabGroup:SelectTab(roleId); + self:DisplayParses(); + end +end + +function gui:SetSelectedEncounter(encounterId, noPropagation) + self.selectedEncounter = encounterId; + self.encounterDropdown:SetValue(encounterId); + self:DisplayParses(); +end + +function gui:SetSelectedDifficulty(difficultyId, noPropagation) + self.selectedDifficulty = difficultyId; + self.difficultyDropdown:SetValue(difficultyId); + + -- Update encounter dropdown with new guild, zone, difficulty + local encounters, numEncounters = addon.highscore:GetEncounters(self.selectedGuild, self.selectedZone, self.selectedDifficulty); + if numEncounters > 0 then + self.encounterDropdown:SetDisabled(false); + self.encounterDropdown:SetList(encounters); + else + self.encounterDropdown:SetDisabled(true); + self.encounterDropdown:SetList(nil); + self.encounterDropdown:SetText(nil); + end + + if not noPropagation then + if numEncounters == 1 then + -- If only one option, select it. + local encounterId, _ = next(encounters); + self:SetSelectedEncounter(encounterId); + else + self:SetSelectedEncounter(nil); + end + end +end + +function gui:SetSelectedZone(zoneId, noPropagation) + self.selectedZone = zoneId; + self.zoneDropdown:SetValue(zoneId); + + -- Update difficulty dropdown with new guild, zone + local difficulties, numDifficulties = addon.highscore:GetDifficulties(self.selectedGuild, self.selectedZone); + if numDifficulties > 0 then + self.difficultyDropdown:SetDisabled(false); + self.difficultyDropdown:SetList(difficulties); + else + self.difficultyDropdown:SetDisabled(true); + self.difficultyDropdown:SetList(nil); + self.difficultyDropdown:SetText(nil); + end + + if not noPropagation then + if numDifficulties == 1 then + -- If only one option, select it. + local difficultyId, _ = next(difficulties); + self:SetSelectedDifficulty(difficultyId); + else + self:SetSelectedDifficulty(nil); + end + end +end + +function gui:SetSelectedGuild(guildId, noPropagation) + self.selectedGuild = guildId; + self.guildDropdown:SetValue(guildId); + + -- Update zone dropdown for the new guild + local zones, numZones = addon.highscore:GetZones(guildId); + if numZones > 0 then + self.zoneDropdown:SetDisabled(false); + self.zoneDropdown:SetList(zones); + else + self.zoneDropdown:SetDisabled(true); + self.zoneDropdown:SetList(nil); + self.zoneDropdown:SetText(nil); + end + + if not noPropagation then + if numZones == 1 then + -- If only one option, select it. + local zoneId, _ = next(zones); + self:SetSelectedZone(zoneId); + else + self:SetSelectedZone(nil); + end + end +end + +function gui:ShowMainFrame() + if not self.mainFrame then + -- Only show if not already shown + self:CreateMainFrame():Show(); + + if self.selectedGuild then + -- Try to restore to same values as before + gui:SetSelectedGuild(self.selectedGuild, true); + gui:SetSelectedZone(self.selectedZone, true); + gui:SetSelectedDifficulty(self.selectedDifficulty, true); + gui:SetSelectedEncounter(self.selectedEncounter, true); + gui:SetSelectedRole(self.selectedRole, true); + elseif addon.guildName then + -- Try pre-selecting own guild if has one. + gui:SetSelectedGuild(addon.guildName); + end + + -- Have to do special for our tab group as it is never disabled + gui:SetSelectedRole(self.selectedRole or "DAMAGER"); + end +end + +function gui:HideMainFrame() + if self.mainFrame then + self.mainFrame:Release(); + + -- Unset references + self.mainFrame = nil; + self.guildDropdown = nil; + self.zoneDropdown = nil; + self.difficultyDropdown = nil; + self.encounterDropdown = nil; + self.highScoreTabGroup = nil; + end +end + +function gui:OnCloseSpecialWindows() + if self.mainFrame then + self:HideMainFrame() + return true + else + return self.hooks["CloseSpecialWindows"](); + end +end + + +function gui:OnEnable() + self:RawHook("CloseSpecialWindows", "OnCloseSpecialWindows"); +end + +function gui:OnDisable() + self:HideMainFrame(); + self:UnHook("CloseSpecialWindows"); +end \ No newline at end of file diff --git a/src/highscore.lua b/src/highscore.lua new file mode 100644 index 0000000..ccd109c --- /dev/null +++ b/src/highscore.lua @@ -0,0 +1,326 @@ +local addonName, addonTable = ... + +-- Global functions for faster access +local tinsert = tinsert; +local tContains = tContains; +local sort = sort; +local random = random; +local format = format; + +-- Set up module +local addon = addonTable[1]; +local highscore = addon:NewModule("highscore", "AceEvent-3.0", "AceTimer-3.0") +addon.highscore = highscore; + +-- db defaults +addon.dbDefaults.realm.modules["highscore"] = { + ["guilds"] = { + ["*"] = { -- Guild Name + ["zones"] = { + ["*"] = { -- zoneId + ["difficulties"] = { + ["*"] = { -- difficultyId + ["encounters"] = { + ["*"] = { -- encounterId + --[[ + playerParses is a list of objects: + { + playerId = "", + role = "", + specName = "", + itemLevel = 0, + damage = 0, + healing = 0, + groupParseId = "" + } + --]] + playerParses = {} + } + } + } + } + } + } + } + }, + ["zones"] = { + ["*"] = { -- zoneId + zoneName = nil + } + }, + ["difficulties"] = { + ["*"] = { -- difficultyId + difficultyName = nil + } + }, + ["encounters"] = { + ["*"] = { -- encounterId + encounterName = nil + } + }, + ["players"] = { + ["*"] = { -- playerId + name = nil, + class = nil + } + }, + ["groupParses"] = { + --[[ + ["*"] = { -- groupParseId + startTime = 0, + duration = 0, + } + --]] + } +} + +addon.dbVersion = addon.dbVersion + 4 + +-- Constants +local TRACKED_ZONE_IDS = { + 994 -- Highmaul +} + + +-- Function for convering a database representation of +-- a parse to a parse that can be returned to users. +-- Copies all values and adds calculated once (like dps) +local function getReturnableParse(db, parse) + local parseCopy = {}; + for key, val in pairs(parse) do + parseCopy[key] = val; + end + + -- Get duration, statTime from the group parse + local groupParse = db.groupParses[parse["groupParseId"]] + parseCopy["duration"] = groupParse.duration; + parseCopy["startTime"] = groupParse.startTime; + parseCopy["groupParseId"] = nil + + -- Get player name and class + parseCopy["name"] = db.players[parse["playerId"]].name; + parseCopy["class"] = db.players[parse["playerId"]].class; + + -- Calculate dps/hps + parseCopy["dps"] = 0; + parseCopy["hps"] = 0; + if parseCopy["duration"] > 0 then + parseCopy["dps"] = parseCopy["damage"] / parseCopy["duration"]; + parseCopy["hps"] = parseCopy["healing"] / parseCopy["duration"]; + end + + return parseCopy; +end + +local function generateRandomKey() + local r1 = random(0, 1000); + local r2 = random(0, 1000); + local r3 = random(0, 1000); + -- 1000^3 = 1´000´000´000, should be enough for now... + return format("%x-%x-%x", r1, r2, r3); +end + +local function addGroupParse(db, startTime, duration) + -- Find a new unique key for the raid parse + local key = generateRandomKey(); + while db.groupParses[key] do + key = generateRandomKey(); + end + + db.groupParses[key] = { + startTime = startTime, + duration = duration + }; + + return key +end + +local function addZone(db, zoneId, zoneName) + db.zones[zoneId].zoneName = zoneName; +end + +local function addDifficulty(db, difficultyId, difficultyName) + db.difficulties[difficultyId].difficultyName = difficultyName; +end + +local function addEncounter(db, encounterId, encounterName) + db.encounters[encounterId].encounterName = encounterName; +end + +local function addPlayer(db, playerId, playerName, playerClass) + db.players[playerId].name = playerName; + db.players[playerId].class = playerClass; +end + +local function getParsesTable(db, guildName, zoneId, difficultyId, encounterId) + return db + .guilds[guildName] + .zones[zoneId] + .difficulties[difficultyId] + .encounters[encounterId] + .playerParses; +end + +local function addEncounterParseForPlayer(parsesTable, player, groupParseId) + local parse = { + playerId = player.id, + role = player.role, + specName = player.specName, + itemLevel = player.itemLevel, + damage = player.damage, + healing = player.healing, + groupParseId = groupParseId + } + tinsert(parsesTable, parse); +end + +function highscore:AddEncounterParsesForPlayers(guildName, encounter, players) + local zoneId = encounter.zoneId; + local zoneName = encounter.zoneName; + local encounterId = encounter.id; + local encounterName = encounter.name; + local difficultyId = encounter.difficultyId; + local difficultyName = encounter.difficultyName; + local startTime = encounter.startTime; + local duration = encounter.duration; + + assert(guildName) + assert(zoneId) + assert(zoneName) + assert(encounterId) + assert(encounterName) + assert(difficultyId) + assert(difficultyName) + assert(startTime) + assert(duration) + assert(players) + + if not tContains(TRACKED_ZONE_IDS, zoneId) then + self:Debug("AddEncounterParsesForPlayers: Current zone not not in tracked zones"); + return + end + + -- Add zone, difficulty and encounter info + addZone(self.db, zoneId, zoneName); + addDifficulty(self.db, difficultyId, difficultyName); + addEncounter(self.db, encounterId, encounterName); + + -- Add a group parse entry, holding data shared between all players + local groupParseId = addGroupParse(self.db, startTime, duration); + + local parsesTable = getParsesTable(self.db, guildName, zoneId, difficultyId, encounterId); + + for _, player in ipairs(players) do + self:Debug(format("addEncounterParseForPlayer: %s", player.name)); + + addPlayer(self.db, player.id, player.name, player.class); + addEncounterParseForPlayer(parsesTable, player, groupParseId) + end + +end + +-- Returns (array of parses, numParses) +function highscore:GetParses(guildName, zoneId, difficultyId, encounterId, role, sortBy) + if (role ~= "TANK" and role ~= "HEALER" and role ~= "DAMAGER") then + return {}, 0; + end + + if not sortBy then + if role == "TANK" or role == "DAMAGER" then + sortBy = "dps" + elseif role == "HEALER" then + sortBy = "hps" + end + end + + local parsesTable = getParsesTable(self.db, guildName, zoneId, difficultyId, encounterId); + + -- Get a *copy* of all parses for the specified role + local parses = {}; + local numParses = 0; + for _, parse in ipairs(parsesTable) do + if parse.role == role then + local parseCopy = getReturnableParse(self.db, parse); + tinsert(parses, parseCopy); + numParses = numParses + 1; + end + end + + sort(parses, function(a, b) + return a[sortBy] > b[sortBy]; + end) + return parses, numParses; +end + +-- Returns (array of {encounterId => encounterName}, numEncounters) +function highscore:GetEncounters(guildName, zoneId, difficultyId) + if not guildName or not zoneId or not difficultyId then + return {}, 0; + end + + local encounters = {}; + local numEncounters = 0; + local difficultiesTable = self.db.guilds[guildName].zones[zoneId].difficulties; + + for encounterId, _ in pairs(difficultiesTable[difficultyId].encounters) do + local encounterName = self.db.encounters[encounterId].encounterName; + encounters[encounterId] = encounterName; + numEncounters = numEncounters + 1; + end + return encounters, numEncounters; +end + +-- Returns (array of {difficultyId => difficultyName}, numDifficulties) +function highscore:GetDifficulties(guildName, zoneId) + if not guildName or not zoneId then + return {}, 0; + end + + local difficulties = {}; + local numDifficulties = 0; + local difficultiesTable = self.db.guilds[guildName].zones[zoneId].difficulties; + + for difficultyId, _ in pairs(difficultiesTable) do + local difficultyName = self.db.difficulties[difficultyId].difficultyName; + difficulties[difficultyId] = difficultyName; + numDifficulties = numDifficulties + 1; + end + return difficulties, numDifficulties; +end + +-- Returns (array of {zoneId => zoneName}, numZones) +function highscore:GetZones(guildName) + if not guildName then + return {}, 0; + end + + local zones = {}; + local numZones = 0; + for zoneId, _ in pairs(self.db.guilds[guildName].zones) do + local zoneName = self.db.zones[zoneId].zoneName; + zones[zoneId] = zoneName; + numZones = numZones + 1; + end + return zones, numZones; +end + +-- Returns (array of {guildId => guildName}, numGuilds) +function highscore:GetGuilds() + local guildNames = {}; + local numGuilds = 0; + for guildName, _ in pairs(self.db.guilds) do + -- Actually guildId == guildName + guildNames[guildName] = guildName; + numGuilds = numGuilds + 1; + end + return guildNames, numGuilds; +end + + +function highscore:OnEnable() + self.db = addon.db.realm.modules["highscore"]; +end + +function highscore:OnDisable() + self.db = nil; +end diff --git a/src/inspect.lua b/src/inspect.lua new file mode 100644 index 0000000..dce5075 --- /dev/null +++ b/src/inspect.lua @@ -0,0 +1,275 @@ +local addonName, addonTable = ... + +-- Global functions for faster access +local tinsert = tinsert; +local floor = floor; +local wipe = wipe; + +-- Set up module +local addon = addonTable[1]; +local inspect = addon:NewModule("inspect", "AceEvent-3.0", "AceTimer-3.0") +addon.inspect = inspect; + +-- Constants +local INSPECT_CACHE_TIMEOUT = 900; +local INVENTORY_SLOT_NAMES = { + "HeadSlot","NeckSlot","ShoulderSlot","BackSlot","ChestSlot","WristSlot", + "HandsSlot","WaistSlot","LegsSlot","FeetSlot","Finger0Slot","Finger1Slot", + "Trinket0Slot","Trinket1Slot","MainHandSlot","SecondaryHandSlot" +} +local NOOP = function() end + +local playerGUID = UnitGUID("player"); +local inspectCache = {}; + + + +local function isInPVEInstance() + local isInstance, instanceType = IsInInstance(); + return inInstance and (instanceType == "party" or instanceType == "raid") +end + +local function getTalentSpec(unitName) + if unitName == "player" then + local spec = GetSpecialization(); + if spec and spec > 0 then + local _, name = GetSpecializationInfo(spec); + return name; + end + else + local spec = GetInspectSpecialization(unitName) + if spec and spec > 0 then + local role = GetSpecializationRoleByID(spec); + if role then + local _, name = GetSpecializationInfoByID(spec); + return name + end + end + end +end + +local function getItemLevel(unitName) + if unitName == "player" then + local _, equipped = GetAverageItemLevel(); + return floor(equipped); + else + local total, numItems = 0, 0; + for i = 1, #INVENTORY_SLOT_NAMES do + local slotName = INVENTORY_SLOT_NAMES[i]; + local slotId = GetInventorySlotInfo(slotName); + local itemLink = GetInventoryItemLink(unitName, slotId); + + if itemLink then + local _, _, _, itemLevel = GetItemInfo(itemLink) + if itemLevel and itemLevel > 0 then + numItems = numItems + 1; + total = total + itemLevel; + end + end + end + if total < 1 or numItems < 15 then + return nil + else + return floor(total / numItems) + end + end +end + +local function hasPlayerInspectCache(playerId, ignoreExpired) + if inspectCache[playerId] and ignoreExpired then + return true -- We have a cache, might be expired + elseif inspectCache[playerId] and inspectCache[playerId].time then + local isExpired = (GetTime() - inspectCache[playerId].time) > INSPECT_CACHE_TIMEOUT + local hasAllAttributes = inspectCache[playerId].specName and inspectCache[playerId].itemLevel; + + return (not isExpired and hasAllAttributes) + else + return false; -- No entry for player id + end +end + +local function setPlayerInspectCache(playerId, specName, itemLevel) + inspectCache[playerId] = {}; + inspectCache[playerId].time = GetTime(); + inspectCache[playerId].specName = specName; + inspectCache[playerId].itemLevel = itemLevel; +end + +function inspect:StartNotifyInspectTimer() + if not self.notifyInspectTimer then + self:Debug("Starting notifyInspectTimer"); + + self.notifyInspectTimer = self:ScheduleRepeatingTimer(function() + self:NOTIFY_INSPECT_TIMER_DONE() + end, 1); + end +end + +function inspect:StopNotifyInspectTimer() + if self.notifyInspectTimer then + self:Debug("Stopping notifyInspectTimer"); + + self:CancelTimer(self.notifyInspectTimer); + self.notifyInspectTimer = nil; + end +end + +function inspect:QueueInspect(player, callback) + self:Debug("QueueInspect", player.name) + + if not self.inspectQueue[player.id] then + self.inspectQueue[player.id] = {player = player, callbacks = {}}; + end + tinsert(self.inspectQueue[player.id].callbacks, callback); + + self:StartNotifyInspectTimer(); +end + +function inspect:SetCachedInspectDataForPlayer(player) + if player.id == playerGUID then + player.specName = getTalentSpec("player") + player.itemLevel = getItemLevel("player"); + return true + elseif hasPlayerInspectCache(player.id, false) then + player.specName = inspectCache[player.id].specName; + player.itemLevel = inspectCache[player.id].itemLevel; + return true + elseif hasPlayerInspectCache(player.id, true) then + -- Expired cache, but better than nothing + player.specName = inspectCache[player.id].specName; + player.itemLevel = inspectCache[player.id].itemLevel; + return false + else + return false + end +end + +function inspect:GetInspectDataForPlayer(player, callback) + -- Make sure we always have a callback + callback = callback and callback or NOOP; + + if self:SetCachedInspectDataForPlayer(player) then + return callback() + elseif not CanInspect(player.name, false) then + return callback() + else + self:QueueInspect(player, callback); + end +end + +function inspect:GetInspectDataForPlayers(players, callback) + local totalCallbacks = #players; + local doneCallbacks = 0; + + for _, player in ipairs(players) do + self:GetInspectDataForPlayer(player, function() + doneCallbacks = doneCallbacks + 1; + if doneCallbacks == totalCallbacks then + callback(); + end + end) + end +end + +function inspect:ResolveInspect(playerId, success) + if not self.inspectQueue[playerId] then + return + end + + local player = self.inspectQueue[playerId].player; + local callbacks = self.inspectQueue[playerId].callbacks; + self.inspectQueue[playerId] = nil; + + self:Debug("ResolveInspect", player.name, (success and "success" or "fail")) + + if success then + if not hasPlayerInspectCache(player.id) then + local specName = getTalentSpec(player.name); + local itemLevel = getItemLevel(player.name); + setPlayerInspectCache(player.id, specName, itemLevel); + end + + player.specName = inspectCache[player.id].specName; + player.itemLevel = inspectCache[player.id].itemLevel; + end + + for _,callback in ipairs(callbacks) do + callback(); + end +end + +function inspect:PreInspectGroup() + for i=1, GetNumGroupMembers() do + + local playerName = GetRaidRosterInfo(i); + if playerName and addon:IsInMyGuild(playerName) then + + local playerId = UnitGUID(playerName) + if playerId and playerId ~= playerGUID and not hasPlayerInspectCache(playerId) then + local player = {name=playerName, id=playerId} + self:QueueInspect(player, NOOP); + end + end + end +end + +function inspect:NOTIFY_INSPECT_TIMER_DONE() + -- Timeout any current inspection + if self.currentInspectPlayerId then + self:ResolveInspect(self.currentInspectPlayerId, false); + self.currentInspectPlayerId = nil; + end + + local playerId, inspectData = next(self.inspectQueue); + if playerId then + NotifyInspect(inspectData.player.name); + self.currentInspectPlayerId = playerId; + else + self:StopNotifyInspectTimer(); + end +end + +function inspect:INSPECT_READY(evt, GUID) + if self.currentInspectPlayerId == GUID then + self:ResolveInspect(self.currentInspectPlayerId, true) + self.currentInspectPlayerId = nil; + end +end + +function inspect:GROUP_ROSTER_UPDATE(evt) + if not IsInGroup() then + self:Debug("Left group, wiping inspect cache"); + wipe(inspectCache); + wipe(inspect.inspectQueue); + inspect.currentInspectPlayerId = nil; + end +end + +function inspect:ZONE_CHANGED_NEW_AREA(evt) + if isInPVEInstance() then + -- We just zoned into an instance, try pre-inspecting the group + self:PreInspectGroup(); + end +end + +function inspect:OnEnable() + inspect.inspectQueue = {}; + inspect.notifyInspectTimer = nil; + inspect.currentInspectPlayerId = nil; + + self:RegisterEvent("INSPECT_READY"); + self:RegisterEvent("GROUP_ROSTER_UPDATE"); + self:RegisterEvent("ZONE_CHANGED_NEW_AREA"); +end + +function inspect:OnDisable() + self:UnregisterEvent("INSPECT_READY"); + self:UnregisterEvent("GROUP_ROSTER_UPDATE"); + self:UnregisterEvent("ZONE_CHANGED_NEW_AREA"); + + self:StopNotifyInspectTimer(); + + wipe(inspectCache); + wipe(inspect.inspectQueue); + inspect.currentInspectPlayerId = nil; +end \ No newline at end of file diff --git a/src/main.lua b/src/main.lua new file mode 100644 index 0000000..91ab007 --- /dev/null +++ b/src/main.lua @@ -0,0 +1,263 @@ +local addonName, addonTable = ... + +local tinsert = tinsert; +local tremove = tremove; + +-- Create ACE3 addon +local addon = LibStub("AceAddon-3.0"):NewAddon(addonName, "AceConsole-3.0", "AceEvent-3.0", "AceHook-3.0") + +tinsert(addonTable, addon); +_G[addonName] = addon + +-- Set up a default prototype for all modules +local modPrototype = { Debug = function(self, ...) addon:Debug(...) end } +addon:SetDefaultModulePrototype(modPrototype) + +-- Db default settings +addon.dbDefaults = { + realm = { + modules = {} + }, + global = { + dbVersion = 1, + debugLog = {} + } +} + +-- The current db version. Clear (migrate?) the database if +-- version of database doesn't match this version. +addon.dbVersion = 2 + +-- Constants +DEBUG_PRINT = true; +DEBUG_LOG = false; +MAX_NUM_DEBUG_LOG_ENTRIES = 300; + +local function getDifficultyNameById(difficultyId) + if difficultyId == 7 or difficultyId == 17 then + return "LFR"; + elseif difficultyId == 1 or difficultyId == 3 or difficultyId == 4 or difficultyId == 14 then + return "Normal"; + elseif difficultyId == 2 or difficultyId == 5 or difficultyId == 6 or difficultyId == 15 then + return "Heroic"; + elseif difficultyId == 16 then + return "Mythic"; + end + + return nil +end + +function addon:Debug(...) + if DEBUG_PRINT then + self:Print(...) + end + if DEBUG_LOG then + local logData = {date("%d/%m %H:%M:%S")}; + for i=1, select('#', ...) do + local v = select(i, ...) + tinsert(logData, v); + end + tinsert(self.db.global.debugLog, logData); + end +end + +function addon:UpdateMyGuildName() + if IsInGuild() then + local guildName, _, _ = GetGuildInfo("player") + if guildName ~= nil then + self.guildName = guildName + end + else + self.guildName = nil + end +end + +function addon:UpdateCurrentZone() + local zoneId, _ = GetCurrentMapAreaID() + local zoneName = GetRealZoneText(); + self.currentZone = {id = zoneId, name = zoneName}; + + if IsInInstance() then + self:Debug("UpdateCurrentZone", zoneId, zoneName) + else + self:UnsetCurrentEncounter(); + end +end + +function addon:SetCurrentEncounter(encounterId, encounterName, difficultyId, raidSize) + local difficultyName = getDifficultyNameById(difficultyId); + + self:Debug("SetCurrentEncounter", encounterId, encounterName, difficultyId, raidSize, difficultyName); + + if difficultyName then + self.currentEncounter = { + zoneId = self.currentZone.id, + zoneName = self.currentZone.name, + id = encounterId, + name = encounterName, + difficultyId = difficultyId, + difficultyName = difficultyName, + raidSize = raidSize + } + end +end + +function addon:UnsetCurrentEncounter() + if self.currentEncounter then + self:Debug("UnsetCurrentEncounter"); + self.currentEncounter = nil + end +end + +function addon:IsInMyGuild(playerName) + if self.guildName then + local guildName, _, _ = GetGuildInfo(playerName) + return guildName == self.guildName + else + return false + end +end + +function addon:GetGuildPlayersFromSet(skadaSet) + local players = {} + for i, player in ipairs(skadaSet.players) do + local playerData; + if self:IsInMyGuild(player.name) then + playerData = {id = player.id, name = player.name, damage = player.damage, healing = player.healing}; + tinsert(players, playerData); + end + end + return players +end + +function addon:SetRoleForPlayers(players) + for _, player in ipairs(players) do + player.role = UnitGroupRolesAssigned(player.name); + end +end + +function addon:SetClassForPlayers(players) + for _, player in ipairs(players) do + player.class = UnitClass(player.name); + end +end + +function addon:PLAYER_GUILD_UPDATE(evt, unitId) + if unitId == "player" then + self:UpdateMyGuildName() + end +end + +function addon:ENCOUNTER_START(evt, encounterId, encounterName, difficultyId, raidSize) + self:Debug("ENCOUNTER_START", encounterId, encounterName, difficultyId, raidSize) + self:SetCurrentEncounter(encounterId, encounterName, difficultyId, raidSize) +end + +function addon:ENCOUNTER_END(evt, encounterId, encounterName, difficultyId, raidSize, endStatus) + self:Debug("ENCOUNTER_END", encounterId, encounterName, difficultyId, raidSize, endStatus) + if endStatus == 1 then -- Success + self:SetCurrentEncounter(encounterId, encounterName, difficultyId, raidSize) + else + self:UnsetCurrentEncounter() + end +end + +function addon:ZONE_CHANGED_NEW_AREA(evt) + self:UpdateCurrentZone(); +end + +function addon:EndSegment() + self:Debug("EndSegment") + + -- Find the skada set matching the encounter + -- Looking only at the lastest set should make sense, + -- as that set should be the boss segment we just ended. + local _, skadaSet = next(Skada:GetSets()); + local encounter = self.currentEncounter; + + if not self.guildName then + self:Debug("Not in a guild"); + return; + end + + if not encounter then + self:Debug("No current encounter"); + return + end + + if not skadaSet or not skadaSet.gotboss or skadaSet.mobname ~= encounter.name then + self:Debug("No Skada set found for boss"); + return; + end + + encounter.duration = skadaSet.time; + encounter.startTime = skadaSet.starttime; + + local players = self:GetGuildPlayersFromSet(skadaSet); + self:SetRoleForPlayers(players); + self:SetClassForPlayers(players); + self.inspect:GetInspectDataForPlayers(players, function() + self.highscore:AddEncounterParsesForPlayers(self.guildName, encounter, players); + end) + + self:UnsetCurrentEncounter(); +end + +function addon:OnInitialize() + self.db = LibStub("AceDB-3.0"):New("GuildSkadaHighScoreDB", addon.dbDefaults, true) + + -- Make sure db version is in sync + if self.db.global.dbVersion ~= self.dbVersion then + self:Debug(format("Found not matching db versions: db=%d, addon=%d", + self.db.global.dbVersion, self.dbVersion)); + self:Debug("Resetting db"); + self.db:ResetDB(); + self.db.global.dbVersion = self.dbVersion; + end + + -- Purge old logs + if DEBUG_LOG then + local numLogsToPurge = (#self.db.global.debugLog - MAX_NUM_DEBUG_LOG_ENTRIES); + while numLogsToPurge >= 0 do + tremove(self.db.global.debugLog, 1) + numLogsToPurge = numLogsToPurge - 1; + end + else + wipe(self.db.global.debugLog); + end +end + +function addon:OnEnable() + self.currentEncounter = nil; + self.currentZone = {}; + self.guildName = nil; + + self:RegisterEvent("ENCOUNTER_START") + self:RegisterEvent("ENCOUNTER_END") + self:RegisterEvent("PLAYER_GUILD_UPDATE") + self:RegisterEvent("ZONE_CHANGED_NEW_AREA") + + self:SecureHook(Skada, "EndSegment") + + self:RegisterChatCommand("gshs", function() + self.gui:ShowMainFrame(); + end) + + self:UpdateMyGuildName(); + self:UpdateCurrentZone(); +end + +function addon:OnDisable() + self.currentEncounter = nil; + self.currentZone = {}; + self.guildName = nil; + + self:UnregisterEvent("ENCOUNTER_START") + self:UnregisterEvent("ENCOUNTER_END") + self:UnregisterEvent("PLAYER_GUILD_UPDATE") + self:UnregisterEvent("ZONE_CHANGED_NEW_AREA") + + self:UnHook(Skada, "EndSegment"); + + self:UnregisterChatCommand("gshs"); +end diff --git a/src/src-include.xml b/src/src-include.xml new file mode 100644 index 0000000..6cdd3d7 --- /dev/null +++ b/src/src-include.xml @@ -0,0 +1,6 @@ +<Ui xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\..\FrameXML\UI.xsd"> + <Script file="main.lua"/> + <Script file="inspect.lua"/> + <Script file="highscore.lua"/> + <Script file="gui.lua"/> +</Ui> \ No newline at end of file