diff --git a/Core/Defaults.lua b/Core/Defaults.lua index 3349293c..5bbb56dd 100644 --- a/Core/Defaults.lua +++ b/Core/Defaults.lua @@ -82,6 +82,7 @@ addon.defaults = { sendHistoryToGuildChannel = false, savePersonalLoot = true, saveBonusRolls = true, + saveSessionResponses = true, -- ML - General - Usage usage = { -- State of enabledness diff --git a/Locale/enUS.lua b/Locale/enUS.lua index d2af3a0c..6b8202ca 100644 --- a/Locale/enUS.lua +++ b/Locale/enUS.lua @@ -541,6 +541,9 @@ L.chat_command_start_error_onlyUseInRaids = "Cannot start: you're in a party and L.chat_restrictions_enabled = "Not currently possible due to Addon Restrictions." L.history_export_sheets_tip = "Tab delimited export for Google Sheets and English version of Excel that uses ';' as formula delimiter." L.history_export_excel_international_tip = "Tab delimited export for international version of Excel that uses ',' as formula delimiter." +L.history_sessionResponses_tip = "Click to show everyone's response to this item." +L.opt_saveSessionResponses_name = "Save Session Responses" +L.opt_saveSessionResponses_desc = "Check to store everyone's response to awarded items in the loot history, viewable through the history's dice button. As Master Looter this also controls whether responses are collected and sent to the group at all. This will increase the storage used by the loot history." L.response_NOTELIGIBLE = "Not eligible for this item" L["opt_addButton_desc"] = "Add a new button group for the selected slot." L.opt_announceAward_WHISPER_WINNER = "/w winner" diff --git a/Modules/History/lootHistory.lua b/Modules/History/lootHistory.lua index 058aa2f8..23ab2bca 100644 --- a/Modules/History/lootHistory.lua +++ b/Modules/History/lootHistory.lua @@ -62,6 +62,7 @@ function LootHistory:OnInitialize() {name = L["Item"], width = 250, comparesort = self.ItemSort, defaultsort = 1, sortnext = 2}, -- Item string {name = L["Reason"], width = 220, comparesort = self.ResponseSort, defaultsort = 1, sortnext = 2}, -- Response aka the text supplied to lootDB...response { name = L["Notes"], width = 40, }, + {name = "", width = ROW_HEIGHT}, -- Session responses button {name = "", width = ROW_HEIGHT}, -- Delete button } filterMenu = _G.MSA_DropDownMenu_Create("RCLootCouncil_LootHistory_FilterMenu", UIParent) @@ -107,6 +108,7 @@ function LootHistory:Hide() self.frame:Hide() self.moreInfo:Hide() moreInfo = false + if self.sessionResponsesFrame then self.sessionResponsesFrame:Hide() end end function LootHistory:SubscribeToPermanentComms () @@ -132,6 +134,9 @@ function LootHistory:OnHistoryReceived (name, history) if not db.saveBonusRolls and history.responseID == "BONUS_ROLL" then return addon.Log:D("Not storing bonus rolls", history.lootWon) end + if not db.saveSessionResponses then -- Both the ML (collection) and each receiver (storage) must opt in + history.sessionResponses = nil + end -- v3.15.4 check for old date formats local d, m, y = strsplit("/", history.date, 3) if #tostring(d) < 4 then @@ -226,6 +231,7 @@ function LootHistory:BuildData() {value = i.lootWon}, {DoCellUpdate = self.SetCellResponse, args = {color = i.color, response = i.response, responseID = i.responseID or 0, isAwardReason = i.isAwardReason}}, { DoCellUpdate = self.SetCellNote }, + {DoCellUpdate = self.SetCellSessionResponses}, {DoCellUpdate = self.SetCellDelete}, } } @@ -486,6 +492,118 @@ function LootHistory.SetCellNote(rowFrame, frame, data, cols, row, realrow, colu frame.noteBtn = f end +function LootHistory.SetCellSessionResponses(rowFrame, frame, data, cols, row, realrow, column, fShow, table, ...) + if not data then return end + local row = data[realrow] + local entry = lootDB[row.name] and lootDB[row.name][row.num] + local f = frame.sessionBtn or CreateFrame("Button", nil, frame) + f:SetSize(ROW_HEIGHT, ROW_HEIGHT) + f:SetPoint("CENTER", frame, "CENTER") + if entry and entry.sessionResponses then + f:SetNormalTexture("Interface/Buttons/UI-GroupLoot-Dice-Up") + f:SetHighlightTexture("Interface/Buttons/UI-GroupLoot-Dice-Highlight") + f:SetScript("OnEnter", function() addon:CreateTooltip(L["Responses"], L["history_sessionResponses_tip"]) end) + f:SetScript("OnLeave", function() addon:HideTooltip() end) + f:SetScript("OnClick", function() LootHistory:ShowSessionResponses(row.name, entry) end) + f:Show() + else + f:Hide() + end + frame.sessionBtn = f +end + +function LootHistory.SetCellWinnerIndicator(rowFrame, frame, data, cols, row, realrow, column, fShow, table, ...) + if not frame.winnerTex then + frame.winnerTex = frame:CreateTexture(nil, "OVERLAY") + frame.winnerTex:SetPoint("CENTER", frame, "CENTER") + frame.winnerTex:SetSize(ROW_HEIGHT - 6, ROW_HEIGHT - 6) + frame.winnerTex:SetTexture("Interface/RaidFrame/ReadyCheck-Ready") + end + frame.winnerTex:SetShown(data[realrow].cols[column].args.isWinner or false) +end + +-- Same as SetCellNote, except the note is delivered in args instead of being fetched from the lootDB. +function LootHistory.SetCellSessionNote(rowFrame, frame, data, cols, row, realrow, column, fShow, table, ...) + local note = data[realrow].cols[column].args.note + local f = frame.noteBtn or CreateFrame("Button", nil, frame) + f:SetSize(ROW_HEIGHT, ROW_HEIGHT) + f:SetPoint("CENTER", frame, "CENTER") + if note then + f:SetNormalTexture("Interface/BUTTONS/UI-GuildButton-PublicNote-Up.png") + f:SetScript("OnEnter", function() addon:CreateTooltip(_G.LABEL_NOTE, note) end) + f:SetScript("OnLeave", function() addon:HideTooltip() end) + else + f:SetScript("OnEnter", nil) + f:SetNormalTexture("Interface/BUTTONS/UI-GuildButton-PublicNote-Disabled.png") + end + frame.noteBtn = f +end + +function LootHistory:GetSessionResponsesFrame() + if self.sessionResponsesFrame then return self.sessionResponsesFrame end + local f = addon.UI:NewNamed("RCFrame", UIParent, "RCLootHistorySessionResponsesFrame", L["Responses"], nil, 260) + addon.UI:RegisterForEscapeClose(f, function() f:Hide() end) + local st = LibStub("ScrollingTable"):CreateST({ + {name = "", width = ROW_HEIGHT,}, -- Class icon + {name = "", width = ROW_HEIGHT,}, -- Winner indicator + {name = _G.NAME, width = 110, sort = 1,}, + {name = L["Reason"], width = 140,}, + {name = L["Notes"], width = 40,}, + {name = _G.ITEM_LEVEL_ABBR, width = 45,}, + {name = _G.ROLL, width = 40,}, + {name = L["Votes"], width = 45,}, + }, 10, ROW_HEIGHT, nil, f.content) + st.frame:SetPoint("TOPLEFT", f, "TOPLEFT", 10, -30) + f:SetWidth(st.frame:GetWidth() + 20) + f:SetHeight(st.frame:GetHeight() + 75) + local close = addon:CreateButton(_G.CLOSE, f.content) + close:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 10) + close:SetScript("OnClick", function() f:Hide() end) + f.st = st + self.sessionResponsesFrame = f + return f +end + +--- Displays everyone's response to a specific history entry, as collected on award. +---@param winner string Name of the history entry's owner. +---@param entry table The history entry containing sessionResponses. +function LootHistory:ShowSessionResponses(winner, entry) + local f = self:GetSessionResponsesFrame() + local rows = {} + for name, v in pairs(entry.sessionResponses) do + local isWinner = addon:UnitIsUnit(name, winner) + local class, response, color, responseID, note, votes + if isWinner then + -- The winner's data lives on the history entry itself + class, response, color, responseID, note, votes = + entry.class, entry.response, entry.color, entry.responseID, entry.note, entry.votes + else + local r = addon:GetResponse(entry.typeCode or "default", v.response) + class, response, color, responseID, note, votes = v.class, r.text, r.color, v.response, v.note, v.votes + end + tinsert(rows, { + cols = { + {DoCellUpdate = addon.SetCellClassIcon, args = {class}, value = class or ""}, + {DoCellUpdate = self.SetCellWinnerIndicator, args = {isWinner = isWinner}, value = isWinner and 1 or 0}, + {value = addon.Ambiguate(name), color = addon:GetClassColor(class)}, + {DoCellUpdate = self.SetCellResponse, args = {color = color, response = response, responseID = responseID or 0, isAwardReason = isWinner and entry.isAwardReason}}, + {DoCellUpdate = self.SetCellSessionNote, args = {note = note}, value = note and 1 or 0}, + {value = v.ilvl or ""}, + {value = v.roll or ""}, + {value = votes or 0}, + }, + }) + end + -- Winner on top, then by votes + table.sort(rows, function(a, b) + if a.cols[2].value ~= b.cols[2].value then return a.cols[2].value > b.cols[2].value end + return (a.cols[8].value or 0) > (b.cols[8].value or 0) + end) + f.st:SetData(rows) + f:Show() + f:Raise() +end + function LootHistory.SetCellDelete(rowFrame, frame, data, cols, row, realrow, column, fShow, table, ...) if not frame.created then frame:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") diff --git a/Modules/options.lua b/Modules/options.lua index 97a05d0f..2694b794 100644 --- a/Modules/options.lua +++ b/Modules/options.lua @@ -553,6 +553,12 @@ function addon:OptionsTable() desc = L.opt_saveBonusRolls_Desc, type = "toggle", }, + saveSessionResponses = { + order = 3.4, + name = L.opt_saveSessionResponses_name, + desc = L.opt_saveSessionResponses_desc, + type = "toggle", + }, header = { order = 4, type = "header", diff --git a/ml_core.lua b/ml_core.lua index 49bca9a4..a3dd77b0 100644 --- a/ml_core.lua +++ b/ml_core.lua @@ -1242,6 +1242,38 @@ end local history_table = {} local historyCounter = 0 -- Used to generate history table entry unique id + +--- Collects everyone's response to a session for storage in the loot history. +--- Only candidates with an actual (numeric) response are included - passes and status texts are not. +--- The winner's response, note and votes are already on the history entry, so only their ilvl and roll is stored. +---@param session integer The session to collect responses from. +---@param winner string The winner of the session. +---@return table? #Candidate name -> response data, or nil if there's none. +function RCLootCouncilML:GetSessionResponses(session, winner) + if not db.saveSessionResponses then return end -- Opt-in due to the storage increase + if not session then return end + local lootTable = addon:GetActiveModule("votingframe"):GetLootTable() + local sessionData = lootTable and lootTable[session] + if not (sessionData and sessionData.candidates) then return end + local sessionResponses = {} + for name, data in pairs(sessionData.candidates) do + -- On re-awards the winner's response has been replaced with "AWARDED"; their real response is kept seperately. + local response = data.response == "AWARDED" and data.real_response or data.response + if addon:UnitIsUnit(name, winner) then + sessionResponses[name] = { ilvl = data.ilvl, roll = data.roll, } + elseif type(response) == "number" then -- An actual response, i.e. not a pass or status text + sessionResponses[name] = { + response = response, + class = data.class, + ilvl = data.ilvl, + roll = data.roll, + votes = data.votes ~= 0 and data.votes or nil, + note = data.note, + } + end + end + return next(sessionResponses) and sessionResponses or nil +end -- REVIEW Updated with recent changes in v2.9+. -- This should be refactored in v3.0 as several of the sources are no longer viable, and were ment to be used with ML. -- v2.19.0: Boss is included in lootTable, but kept as arg for backwards compatibility. @@ -1289,6 +1321,7 @@ function RCLootCouncilML:TrackAndLogLoot(winner, link, responseID, boss, reason, history_table["id"] = GetServerTime().."-"..historyCounter -- New in v2.7+. A unique id for the history entry. history_table["owner"] = owner or self.lootTable[session] and self.lootTable[session].owner or winner -- New in v2.9+. history_table["typeCode"] = self.lootTable[session] and self.lootTable[session].typeCode -- New in v2.15+. + history_table["sessionResponses"] = self:GetSessionResponses(session, winner) -- New in v3.22+. historyCounter = historyCounter + 1