diff --git a/README.md b/README.md index cb577bb..e233c5a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ - Sound plays when loot is announced (Turn off/on from the options menu) - Added a striking system for naughty players type ``/strike`` to open te panel - Added a simple UI to place players on the correct side during Gnarlmoon. Type ``/matrix`` to open +- Added an AFK check for the bench group Type ``/bench`` to open # shootyepgp diff --git a/bench.lua b/bench.lua new file mode 100644 index 0000000..382a3c4 --- /dev/null +++ b/bench.lua @@ -0,0 +1,980 @@ +BenchTracker = {} +local mainFrame = nil +local playerFrames = {} +local logEntries = {} +local currentCheckData = {} +local isListening = false +local listenTimer = nil +local listenElapsed = 0 +local checkCounter = 0 +local activeTab = "afk" + +-- Class colors (same as raidstrike) +local CLASS_COLORS = { + ["WARRIOR"] = {r = 0.78, g = 0.61, b = 0.43}, + ["PALADIN"] = {r = 0.96, g = 0.55, b = 0.73}, + ["HUNTER"] = {r = 0.67, g = 0.83, b = 0.45}, + ["ROGUE"] = {r = 1.0, g = 0.96, b = 0.41}, + ["PRIEST"] = {r = 1.0, g = 1.0, b = 1.0}, + ["SHAMAN"] = {r = 0.0, g = 0.44, b = 0.87}, + ["MAGE"] = {r = 0.41, g = 0.8, b = 0.94}, + ["WARLOCK"] = {r = 0.58, g = 0.51, b = 0.79}, + ["DRUID"] = {r = 1.0, g = 0.49, b = 0.04} +} + +local LOCALIZED_CLASS_LOOKUP = { + ["Warrior"] = "WARRIOR", ["Paladin"] = "PALADIN", ["Hunter"] = "HUNTER", + ["Rogue"] = "ROGUE", ["Priest"] = "PRIEST", ["Shaman"] = "SHAMAN", + ["Mage"] = "MAGE", ["Warlock"] = "WARLOCK", ["Druid"] = "DRUID", + ["Krieger"] = "WARRIOR", ["Jager"] = "HUNTER", + ["Schurke"] = "ROGUE", ["Priester"] = "PRIEST", ["Schamane"] = "SHAMAN", + ["Magier"] = "MAGE", ["Hexenmeister"] = "WARLOCK", ["Druide"] = "DRUID", + ["Guerrier"] = "WARRIOR", ["Chasseur"] = "HUNTER", + ["Voleur"] = "ROGUE", ["Chaman"] = "SHAMAN", +} + +function BenchTracker:GetEnglishClass(localizedClass) + if not localizedClass then return nil end + local upperClass = string.upper(localizedClass) + if CLASS_COLORS[upperClass] then + return upperClass + end + local englishClass = LOCALIZED_CLASS_LOOKUP[localizedClass] + if englishClass then + return englishClass + end + return upperClass +end + +function BenchTracker:IsOfficer() + local numMembers = GetNumGuildMembers() + local playerName = UnitName("player") + for i = 1, numMembers do + local name, rank, rankIndex = GetGuildRosterInfo(i) + if name == playerName then + if rank == "Officer" or rank == "Supreme Leader" then + return true + end + return false + end + end + return false +end + +function BenchTracker:GetTimestamp() + local dateInfo = date("*t") + local day = dateInfo.day + local month = dateInfo.month + local year = dateInfo.year + local hour = dateInfo.hour + local min = dateInfo.min + local dayStr = day + local monthStr = month + if day < 10 then dayStr = "0" .. day end + if month < 10 then monthStr = "0" .. month end + local hourStr = hour + local minStr = min + if hour < 10 then hourStr = "0" .. hour end + if min < 10 then minStr = "0" .. min end + return "[" .. dayStr .. "/" .. monthStr .. "/" .. year .. " - " .. hourStr .. ":" .. minStr .. "]" +end + +-- Get list of raid members (bench group) +function BenchTracker:GetRaidMembers() + local members = {} + local raidSize = GetNumRaidMembers() + if raidSize == 0 then return members end + for i = 1, raidSize do + local name, rank, subgroup, level, class = GetRaidRosterInfo(i) + if name then + table.insert(members, {name = name, class = class, subgroup = subgroup}) + end + end + -- Sort alphabetically + table.sort(members, function(a, b) + return a.name < b.name + end) + return members +end + +---------------------------------------------------------------- +-- MAIN FRAME +---------------------------------------------------------------- +function BenchTracker:CreateMainFrame() + if mainFrame then return end + + mainFrame = CreateFrame("Frame", "BenchTrackerFrame", UIParent) + mainFrame:SetWidth(280) + mainFrame:SetHeight(380) + mainFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 0) + mainFrame:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 16, edgeSize = 8, + insets = {left = 2, right = 2, top = 2, bottom = 2} + }) + mainFrame:SetBackdropColor(0.05, 0.05, 0.05, 0.95) + mainFrame:SetBackdropBorderColor(0.3, 0.3, 0.3, 1) + mainFrame:SetMovable(true) + mainFrame:EnableMouse(true) + mainFrame:RegisterForDrag("LeftButton") + mainFrame:SetScript("OnDragStart", function() this:StartMoving() end) + mainFrame:SetScript("OnDragStop", function() this:StopMovingOrSizing() end) + mainFrame:SetFrameStrata("DIALOG") + mainFrame:Hide() + + -- Title + local title = mainFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal") + title:SetPoint("TOP", mainFrame, "TOP", 0, -8) + title:SetText("Bench Tracker") + title:SetTextColor(0.9, 0.9, 0.9, 1) + + -- Close button + local closeButton = CreateFrame("Button", nil, mainFrame) + closeButton:SetWidth(12) + closeButton:SetHeight(12) + closeButton:SetPoint("TOPRIGHT", mainFrame, "TOPRIGHT", -6, -6) + closeButton:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + closeButton:SetBackdropColor(0.8, 0.2, 0.2, 0.8) + closeButton:SetBackdropBorderColor(0.6, 0.6, 0.6, 1) + local closeText = closeButton:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + closeText:SetPoint("CENTER", closeButton, "CENTER", 0, 0) + closeText:SetText("x") + closeText:SetTextColor(1, 1, 1, 1) + closeButton:SetScript("OnEnter", function() + this:SetBackdropColor(1, 0.3, 0.3, 1) + end) + closeButton:SetScript("OnLeave", function() + this:SetBackdropColor(0.8, 0.2, 0.2, 0.8) + end) + closeButton:SetScript("OnClick", function() mainFrame:Hide() end) + + -- Tab buttons + BenchTracker:CreateTabButtons() + + -- AFK Check content area + local afkContent = CreateFrame("Frame", "BenchAfkContent", mainFrame) + afkContent:SetPoint("TOPLEFT", mainFrame, "TOPLEFT", 6, -42) + afkContent:SetPoint("BOTTOMRIGHT", mainFrame, "BOTTOMRIGHT", -6, 6) + afkContent:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = nil, tile = true, tileSize = 16, + insets = {left = 0, right = 0, top = 0, bottom = 0} + }) + afkContent:SetBackdropColor(0.08, 0.08, 0.08, 0.5) + mainFrame.afkContent = afkContent + + -- Column labels above the list (centered over the radio buttons) + local readyLabel = afkContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + readyLabel:SetPoint("TOP", afkContent, "TOPRIGHT", -50, -2) + readyLabel:SetText("|cFF00CC00R|r") + readyLabel:SetTextColor(0.0, 0.8, 0.0, 1) + mainFrame.colReadyLabel = readyLabel + + local afkLabel = afkContent:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + afkLabel:SetPoint("TOP", afkContent, "TOPRIGHT", -32, -2) + afkLabel:SetText("|cFFCC0000A|r") + afkLabel:SetTextColor(0.8, 0.0, 0.0, 1) + mainFrame.colAfkLabel = afkLabel + + -- Scroll frame for AFK list + local scrollFrame = CreateFrame("ScrollFrame", "BenchAfkScrollFrame", afkContent, "UIPanelScrollFrameTemplate") + scrollFrame:SetPoint("TOPLEFT", afkContent, "TOPLEFT", 2, -14) + scrollFrame:SetPoint("BOTTOMRIGHT", afkContent, "BOTTOMRIGHT", -2, 26) + + local scrollChild = CreateFrame("Frame", "BenchAfkScrollChild", scrollFrame) + scrollChild:SetWidth(245) + scrollChild:SetHeight(1) + scrollFrame:SetScrollChild(scrollChild) + mainFrame.afkScrollFrame = scrollFrame + mainFrame.afkScrollChild = scrollChild + + -- Style scrollbar + BenchTracker:StyleScrollbar(scrollFrame) + + -- AFK Check button (bottom-left of afk content) + local afkBtn = CreateFrame("Button", nil, afkContent) + afkBtn:SetWidth(85) + afkBtn:SetHeight(18) + afkBtn:SetPoint("BOTTOMLEFT", afkContent, "BOTTOMLEFT", 4, 4) + afkBtn:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + afkBtn:SetBackdropColor(0.6, 0.3, 0.0, 0.9) + afkBtn:SetBackdropBorderColor(0.5, 0.5, 0.5, 1) + local afkBtnText = afkBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + afkBtnText:SetPoint("CENTER", afkBtn, "CENTER", 0, 0) + afkBtnText:SetText("AFK Check") + afkBtnText:SetTextColor(1, 1, 1, 1) + afkBtn:SetScript("OnEnter", function() + this:SetBackdropColor(0.8, 0.4, 0.0, 1) + end) + afkBtn:SetScript("OnLeave", function() + this:SetBackdropColor(0.6, 0.3, 0.0, 0.9) + end) + afkBtn:SetScript("OnClick", function() + BenchTracker:StartAFKCheck() + end) + mainFrame.afkButton = afkBtn + + -- Add to Log button (bottom-right of afk content) + local logBtn = CreateFrame("Button", nil, afkContent) + logBtn:SetWidth(85) + logBtn:SetHeight(18) + logBtn:SetPoint("BOTTOMRIGHT", afkContent, "BOTTOMRIGHT", -4, 4) + logBtn:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + logBtn:SetBackdropColor(0.2, 0.4, 0.6, 0.9) + logBtn:SetBackdropBorderColor(0.5, 0.5, 0.5, 1) + local logBtnText = logBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + logBtnText:SetPoint("CENTER", logBtn, "CENTER", 0, 0) + logBtnText:SetText("Add to Log") + logBtnText:SetTextColor(1, 1, 1, 1) + logBtn:SetScript("OnEnter", function() + this:SetBackdropColor(0.3, 0.5, 0.7, 1) + end) + logBtn:SetScript("OnLeave", function() + this:SetBackdropColor(0.2, 0.4, 0.6, 0.9) + end) + logBtn:SetScript("OnClick", function() + BenchTracker:ManualLogAFKCheck() + end) + mainFrame.logButton = logBtn + + -- Log content area + local logContent = CreateFrame("Frame", "BenchLogContent", mainFrame) + logContent:SetPoint("TOPLEFT", mainFrame, "TOPLEFT", 6, -42) + logContent:SetPoint("BOTTOMRIGHT", mainFrame, "BOTTOMRIGHT", -6, 6) + logContent:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = nil, tile = true, tileSize = 16, + insets = {left = 0, right = 0, top = 0, bottom = 0} + }) + logContent:SetBackdropColor(0.08, 0.08, 0.08, 0.5) + logContent:Hide() + mainFrame.logContent = logContent + + -- Clear Log button (bottom of log content) + local clearLogBtn = CreateFrame("Button", nil, logContent) + clearLogBtn:SetWidth(70) + clearLogBtn:SetHeight(18) + clearLogBtn:SetPoint("BOTTOM", logContent, "BOTTOM", 0, 4) + clearLogBtn:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + clearLogBtn:SetBackdropColor(0.5, 0.15, 0.15, 0.9) + clearLogBtn:SetBackdropBorderColor(0.5, 0.5, 0.5, 1) + local clearLogText = clearLogBtn:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + clearLogText:SetPoint("CENTER", clearLogBtn, "CENTER", 0, 0) + clearLogText:SetText("Clear Log") + clearLogText:SetTextColor(1, 1, 1, 1) + clearLogBtn:SetScript("OnEnter", function() + this:SetBackdropColor(0.7, 0.2, 0.2, 1) + end) + clearLogBtn:SetScript("OnLeave", function() + this:SetBackdropColor(0.5, 0.15, 0.15, 0.9) + end) + clearLogBtn:SetScript("OnClick", function() + BenchTracker:ClearLog() + end) + mainFrame.clearLogButton = clearLogBtn + + -- Scroll frame for Log + local logScrollFrame = CreateFrame("ScrollFrame", "BenchLogScrollFrame", logContent, "UIPanelScrollFrameTemplate") + logScrollFrame:SetPoint("TOPLEFT", logContent, "TOPLEFT", 2, -2) + logScrollFrame:SetPoint("BOTTOMRIGHT", logContent, "BOTTOMRIGHT", -2, 26) + + local logScrollChild = CreateFrame("Frame", "BenchLogScrollChild", logScrollFrame) + logScrollChild:SetWidth(245) + logScrollChild:SetHeight(1) + logScrollFrame:SetScrollChild(logScrollChild) + mainFrame.logScrollFrame = logScrollFrame + mainFrame.logScrollChild = logScrollChild + + BenchTracker:StyleScrollbar(logScrollFrame) +end + +function BenchTracker:StyleScrollbar(scrollFrame) + local scrollBar = getglobal(scrollFrame:GetName() .. "ScrollBar") + if scrollBar then + scrollBar:ClearAllPoints() + scrollBar:SetPoint("TOPRIGHT", scrollFrame, "TOPRIGHT", 0, -16) + scrollBar:SetPoint("BOTTOMRIGHT", scrollFrame, "BOTTOMRIGHT", 0, 16) + scrollBar:SetWidth(5) + scrollBar:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 2, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + scrollBar:SetBackdropColor(0.15, 0.15, 0.15, 0.8) + scrollBar:SetBackdropBorderColor(0.3, 0.3, 0.3, 0.6) + local thumb = getglobal(scrollBar:GetName() .. "ThumbTexture") + if thumb then + thumb:SetTexture("Interface\\Tooltips\\UI-Tooltip-Background") + thumb:SetVertexColor(0.9, 0.9, 0.9, 0.9) + thumb:SetWidth(8) + end + local upButton = getglobal(scrollBar:GetName() .. "ScrollUpButton") + local downButton = getglobal(scrollBar:GetName() .. "ScrollDownButton") + if upButton then upButton:SetWidth(0) end + if downButton then downButton:SetWidth(0) end + end +end + +---------------------------------------------------------------- +-- TAB BUTTONS +---------------------------------------------------------------- +function BenchTracker:CreateTabButtons() + -- AFK Check tab + local tabAfk = CreateFrame("Button", "BenchTabAfk", mainFrame) + tabAfk:SetWidth(80) + tabAfk:SetHeight(16) + tabAfk:SetPoint("TOPLEFT", mainFrame, "TOPLEFT", 8, -22) + tabAfk:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + local tabAfkText = tabAfk:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + tabAfkText:SetPoint("CENTER", tabAfk, "CENTER", 0, 0) + tabAfkText:SetText("AFK Check") + tabAfk:SetScript("OnClick", function() + BenchTracker:SwitchTab("afk") + end) + mainFrame.tabAfk = tabAfk + mainFrame.tabAfkText = tabAfkText + + -- Log tab + local tabLog = CreateFrame("Button", "BenchTabLog", mainFrame) + tabLog:SetWidth(80) + tabLog:SetHeight(16) + tabLog:SetPoint("LEFT", tabAfk, "RIGHT", 4, 0) + tabLog:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + local tabLogText = tabLog:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + tabLogText:SetPoint("CENTER", tabLog, "CENTER", 0, 0) + tabLogText:SetText("Log") + tabLog:SetScript("OnClick", function() + BenchTracker:SwitchTab("log") + end) + mainFrame.tabLog = tabLog + mainFrame.tabLogText = tabLogText +end + +function BenchTracker:SwitchTab(tab) + activeTab = tab + if tab == "afk" then + mainFrame.afkContent:Show() + mainFrame.logContent:Hide() + mainFrame.tabAfk:SetBackdropColor(0.3, 0.3, 0.5, 1) + mainFrame.tabAfk:SetBackdropBorderColor(0.6, 0.6, 0.8, 1) + mainFrame.tabAfkText:SetTextColor(1, 1, 1, 1) + mainFrame.tabLog:SetBackdropColor(0.15, 0.15, 0.15, 0.8) + mainFrame.tabLog:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) + mainFrame.tabLogText:SetTextColor(0.6, 0.6, 0.6, 1) + BenchTracker:UpdateAFKDisplay() + else + mainFrame.afkContent:Hide() + mainFrame.logContent:Show() + mainFrame.tabLog:SetBackdropColor(0.3, 0.3, 0.5, 1) + mainFrame.tabLog:SetBackdropBorderColor(0.6, 0.6, 0.8, 1) + mainFrame.tabLogText:SetTextColor(1, 1, 1, 1) + mainFrame.tabAfk:SetBackdropColor(0.15, 0.15, 0.15, 0.8) + mainFrame.tabAfk:SetBackdropBorderColor(0.4, 0.4, 0.4, 1) + mainFrame.tabAfkText:SetTextColor(0.6, 0.6, 0.6, 1) + BenchTracker:UpdateLogDisplay() + end +end + +---------------------------------------------------------------- +-- PLAYER ROW (AFK TAB) +---------------------------------------------------------------- +function BenchTracker:CreatePlayerRow(parent, index) + local frame = CreateFrame("Frame", "BenchPlayerRow" .. index, parent) + frame:SetWidth(245) + frame:SetHeight(16) + frame:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = nil, tile = true, tileSize = 16, + insets = {left = 0, right = 0, top = 0, bottom = 0} + }) + frame:SetBackdropColor(0.15, 0.15, 0.15, 0.6) + frame:EnableMouse(true) + frame:SetScript("OnEnter", function() + this:SetBackdropColor(0.25, 0.35, 0.45, 0.8) + end) + frame:SetScript("OnLeave", function() + this:SetBackdropColor(0.15, 0.15, 0.15, 0.6) + end) + + -- Player name + local nameText = frame:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + nameText:SetPoint("LEFT", frame, "LEFT", 4, 0) + nameText:SetWidth(140) + nameText:SetJustifyH("LEFT") + frame.nameText = nameText + + -- Ready button (green) + local readyBtn = CreateFrame("Button", nil, frame) + readyBtn:SetWidth(14) + readyBtn:SetHeight(14) + readyBtn:SetPoint("RIGHT", frame, "RIGHT", -22, 0) + readyBtn:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + readyBtn:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("Ready", 0.2, 0.8, 0.2) + GameTooltip:Show() + end) + readyBtn:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + frame.readyBtn = readyBtn + + -- AFK button (red) + local afkBtn = CreateFrame("Button", nil, frame) + afkBtn:SetWidth(14) + afkBtn:SetHeight(14) + afkBtn:SetPoint("RIGHT", frame, "RIGHT", -4, 0) + afkBtn:SetBackdrop({ + bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", + edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", + tile = true, tileSize = 8, edgeSize = 4, + insets = {left = 1, right = 1, top = 1, bottom = 1} + }) + afkBtn:SetScript("OnEnter", function() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetText("AFK", 0.8, 0.2, 0.2) + GameTooltip:Show() + end) + afkBtn:SetScript("OnLeave", function() + GameTooltip:Hide() + end) + frame.afkBtn = afkBtn + + frame:Hide() + return frame +end + +function BenchTracker:SetRadioState(frame, state) + -- state: "ready", "afk", or "none" + if state == "ready" then + frame.readyBtn:SetBackdropColor(0.1, 0.7, 0.1, 0.9) + frame.readyBtn:SetBackdropBorderColor(0.1, 0.5, 0.1, 1) + frame.afkBtn:SetBackdropColor(0.25, 0.25, 0.25, 0.6) + frame.afkBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.8) + elseif state == "afk" then + frame.readyBtn:SetBackdropColor(0.25, 0.25, 0.25, 0.6) + frame.readyBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.8) + frame.afkBtn:SetBackdropColor(0.8, 0.1, 0.1, 0.9) + frame.afkBtn:SetBackdropBorderColor(0.6, 0.1, 0.1, 1) + else + frame.readyBtn:SetBackdropColor(0.25, 0.25, 0.25, 0.6) + frame.readyBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.8) + frame.afkBtn:SetBackdropColor(0.25, 0.25, 0.25, 0.6) + frame.afkBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.8) + end +end + +---------------------------------------------------------------- +-- AFK DISPLAY +---------------------------------------------------------------- +function BenchTracker:UpdateAFKDisplay() + if not mainFrame or not mainFrame:IsVisible() then return end + if activeTab ~= "afk" then return end + + local isOfficer = BenchTracker:IsOfficer() + local members = BenchTracker:GetRaidMembers() + local scrollChild = mainFrame.afkScrollChild + + -- Show/hide buttons based on officer status + if mainFrame.afkButton then + if isOfficer then + mainFrame.afkButton:Show() + else + mainFrame.afkButton:Hide() + end + end + if mainFrame.logButton then + if isOfficer then + mainFrame.logButton:Show() + else + mainFrame.logButton:Hide() + end + end + + -- Hide all existing rows + for i = 1, table.getn(playerFrames) do + playerFrames[i]:Hide() + end + + local yOffset = 0 + for i = 1, table.getn(members) do + local member = members[i] + + if not playerFrames[i] then + playerFrames[i] = BenchTracker:CreatePlayerRow(scrollChild, i) + end + + local row = playerFrames[i] + row:ClearAllPoints() + row:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 0, yOffset) + row:Show() + + row.nameText:SetText(member.name) + local englishClass = BenchTracker:GetEnglishClass(member.class) + local color = CLASS_COLORS[englishClass] or {r = 0.5, g = 0.5, b = 0.5} + row.nameText:SetTextColor(color.r, color.g, color.b, 1) + + row.playerName = member.name + + -- Determine current status + local status = "none" + if currentCheckData[member.name] then + status = currentCheckData[member.name] + end + BenchTracker:SetRadioState(row, status) + + -- Button click handlers + if isOfficer then + row.readyBtn:SetScript("OnClick", function() + local pName = this:GetParent().playerName + currentCheckData[pName] = "ready" + BenchTracker:UpdateAFKDisplay() + end) + row.afkBtn:SetScript("OnClick", function() + local pName = this:GetParent().playerName + currentCheckData[pName] = "afk" + BenchTracker:UpdateAFKDisplay() + end) + row.readyBtn:EnableMouse(true) + row.afkBtn:EnableMouse(true) + else + row.readyBtn:EnableMouse(false) + row.afkBtn:EnableMouse(false) + end + + yOffset = yOffset - 17 + end + + local contentHeight = table.getn(members) * 17 + 10 + scrollChild:SetHeight(contentHeight) + if mainFrame.afkScrollFrame then + mainFrame.afkScrollFrame:UpdateScrollChildRect() + end +end + +---------------------------------------------------------------- +-- AFK CHECK LOGIC +---------------------------------------------------------------- +function BenchTracker:StartAFKCheck() + if not BenchTracker:IsOfficer() then return end + if isListening then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFF0000BenchTracker: An AFK check is already in progress!|r") + return + end + + -- Reset status for current raid members + currentCheckData = {} + local members = BenchTracker:GetRaidMembers() + for i = 1, table.getn(members) do + currentCheckData[members[i].name] = "afk" + end + + checkCounter = checkCounter + 1 + isListening = true + listenElapsed = 0 + + -- Send announcements + SendChatMessage("BENCH GROUP: Type in [Guild Chat] or in [#General on Discord] within 30sec if you're here.", "GUILD") + SendChatMessage("BENCH GROUP: Type in [Guild Chat] or in [#General on Discord] within 30sec if you're here.", "RAID_WARNING") + + -- Trigger /pull 30 (BigWigs Pulltimer) + if SlashCmdList["BWPT_SHORTHAND"] then + SlashCmdList["BWPT_SHORTHAND"]("30") + end + + -- Start 30 second timer + BenchTracker:StartListenTimer() + + BenchTracker:UpdateAFKDisplay() + DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00BenchTracker: AFK check started! Listening for 30 seconds...|r") +end + +function BenchTracker:StartListenTimer() + if not listenTimer then + listenTimer = CreateFrame("Frame", "BenchListenTimer", UIParent) + end + listenElapsed = 0 + listenTimer:SetScript("OnUpdate", function() + listenElapsed = listenElapsed + arg1 + if listenElapsed >= 30 then + BenchTracker:StopListening() + end + end) + listenTimer:Show() +end + +function BenchTracker:StopListening() + isListening = false + if listenTimer then + listenTimer:SetScript("OnUpdate", nil) + listenTimer:Hide() + end + + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00BenchTracker: AFK check listening ended. Review results and click 'Add to Log' when ready.|r") + BenchTracker:UpdateAFKDisplay() +end + +function BenchTracker:OnChatMessage(playerName) + if not isListening then return end + + -- Check if this player is in our bench raid + if currentCheckData[playerName] then + currentCheckData[playerName] = "ready" + BenchTracker:UpdateAFKDisplay() + end +end + +---------------------------------------------------------------- +-- LOGGING +---------------------------------------------------------------- +function BenchTracker:LogAFKCheck() + local timestamp = BenchTracker:GetTimestamp() + local readyList = {} + local afkList = {} + + for name, status in pairs(currentCheckData) do + if status == "ready" then + table.insert(readyList, name) + else + table.insert(afkList, name) + end + end + + table.sort(readyList) + table.sort(afkList) + + local entry = { + entryType = "afk", + timestamp = timestamp, + checkNum = checkCounter, + ready = readyList, + afk = afkList + } + table.insert(logEntries, entry) + BenchTrackerDB.log = logEntries + BenchTrackerDB.checkCounter = checkCounter + + if activeTab == "log" then + BenchTracker:UpdateLogDisplay() + end +end + +function BenchTracker:ManualLogAFKCheck() + if not BenchTracker:IsOfficer() then return end + + -- Check if there is any data to log + local hasData = false + for name, status in pairs(currentCheckData) do + hasData = true + break + end + if not hasData then + DEFAULT_CHAT_FRAME:AddMessage("|cFFFF0000BenchTracker: Nothing to log. Run an AFK check first.|r") + return + end + + BenchTracker:LogAFKCheck() + + -- Reset radio buttons + currentCheckData = {} + + DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00BenchTracker: AFK check results added to log.|r") + BenchTracker:UpdateAFKDisplay() + if activeTab == "log" then + BenchTracker:UpdateLogDisplay() + end +end + +function BenchTracker:LogEPAward(epAmount) + local timestamp = BenchTracker:GetTimestamp() + local members = BenchTracker:GetRaidMembers() + local nameList = {} + for i = 1, table.getn(members) do + table.insert(nameList, members[i].name) + end + table.sort(nameList) + + local entry = { + entryType = "ep", + timestamp = timestamp, + ep = epAmount, + players = nameList + } + table.insert(logEntries, entry) + BenchTrackerDB.log = logEntries + + if activeTab == "log" then + BenchTracker:UpdateLogDisplay() + end +end + +---------------------------------------------------------------- +-- LOG DISPLAY +---------------------------------------------------------------- +local logFontStrings = {} + +function BenchTracker:ClearLog() + if not BenchTracker:IsOfficer() then return end + logEntries = {} + checkCounter = 0 + BenchTrackerDB.log = logEntries + BenchTrackerDB.checkCounter = 0 + DEFAULT_CHAT_FRAME:AddMessage("|cFFFFFF00BenchTracker: Log cleared.|r") + BenchTracker:UpdateLogDisplay() +end + +function BenchTracker:UpdateLogDisplay() + if not mainFrame or not mainFrame:IsVisible() then return end + if activeTab ~= "log" then return end + + -- Show/hide Clear Log button based on officer status + if mainFrame.clearLogButton then + if BenchTracker:IsOfficer() then + mainFrame.clearLogButton:Show() + else + mainFrame.clearLogButton:Hide() + end + end + + local scrollChild = mainFrame.logScrollChild + + -- Hide all existing log text + for i = 1, table.getn(logFontStrings) do + logFontStrings[i]:Hide() + end + + local yOffset = 0 + local fsIndex = 0 + + local function getFS() + fsIndex = fsIndex + 1 + if not logFontStrings[fsIndex] then + logFontStrings[fsIndex] = scrollChild:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") + end + local fs = logFontStrings[fsIndex] + fs:ClearAllPoints() + fs:Show() + return fs + end + + -- Render entries in reverse order (newest first) + local numEntries = table.getn(logEntries) + for idx = numEntries, 1, -1 do + local entry = logEntries[idx] + + if entry.entryType == "afk" then + -- Header + local header = getFS() + header:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 2, yOffset) + header:SetWidth(240) + header:SetJustifyH("LEFT") + header:SetText("|cFFFFCC00" .. entry.timestamp .. " AFK Check #" .. entry.checkNum .. "|r") + header:SetTextColor(1, 0.8, 0, 1) + yOffset = yOffset - 12 + + -- Ready list + if table.getn(entry.ready) > 0 then + local readyLabel = getFS() + readyLabel:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 6, yOffset) + readyLabel:SetWidth(236) + readyLabel:SetJustifyH("LEFT") + local readyStr = "|cFF00CC00Ready:|r " + for i = 1, table.getn(entry.ready) do + if i > 1 then readyStr = readyStr .. ", " end + readyStr = readyStr .. entry.ready[i] + end + readyLabel:SetText(readyStr) + readyLabel:SetTextColor(0.8, 0.8, 0.8, 1) + yOffset = yOffset - 12 + end + + -- AFK list + if table.getn(entry.afk) > 0 then + local afkLabel = getFS() + afkLabel:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 6, yOffset) + afkLabel:SetWidth(236) + afkLabel:SetJustifyH("LEFT") + local afkStr = "|cFFCC0000AFK:|r " + for i = 1, table.getn(entry.afk) do + if i > 1 then afkStr = afkStr .. ", " end + afkStr = afkStr .. entry.afk[i] + end + afkLabel:SetText(afkStr) + afkLabel:SetTextColor(0.8, 0.8, 0.8, 1) + yOffset = yOffset - 12 + end + + yOffset = yOffset - 4 + + elseif entry.entryType == "ep" then + -- EP award header + local header = getFS() + header:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 2, yOffset) + header:SetWidth(240) + header:SetJustifyH("LEFT") + header:SetText("|cFF33BBFF" .. entry.timestamp .. " EP Award: +" .. entry.ep .. " EP|r") + header:SetTextColor(0.2, 0.7, 1, 1) + yOffset = yOffset - 12 + + -- Player list + local playerLabel = getFS() + playerLabel:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 6, yOffset) + playerLabel:SetWidth(236) + playerLabel:SetJustifyH("LEFT") + local pStr = "" + for i = 1, table.getn(entry.players) do + if i > 1 then pStr = pStr .. ", " end + pStr = pStr .. entry.players[i] + end + playerLabel:SetText(pStr) + playerLabel:SetTextColor(0.7, 0.7, 0.7, 1) + yOffset = yOffset - 12 + + yOffset = yOffset - 4 + end + end + + if numEntries == 0 then + local emptyMsg = getFS() + emptyMsg:SetPoint("TOPLEFT", scrollChild, "TOPLEFT", 2, yOffset) + emptyMsg:SetWidth(240) + emptyMsg:SetJustifyH("LEFT") + emptyMsg:SetText("|cFF666666No log entries yet.|r") + emptyMsg:SetTextColor(0.4, 0.4, 0.4, 1) + yOffset = yOffset - 14 + end + + local contentHeight = math.abs(yOffset) + 10 + scrollChild:SetHeight(contentHeight) + if mainFrame.logScrollFrame then + mainFrame.logScrollFrame:UpdateScrollChildRect() + end +end + +---------------------------------------------------------------- +-- SLASH COMMAND +---------------------------------------------------------------- +SLASH_BENCH1 = "/bench" +SlashCmdList["BENCH"] = function(msg) + if not mainFrame then + BenchTracker:CreateMainFrame() + end + + if mainFrame:IsVisible() then + mainFrame:Hide() + else + mainFrame:Show() + BenchTracker:SwitchTab(activeTab) + end +end + +---------------------------------------------------------------- +-- EVENT HANDLING +---------------------------------------------------------------- +local benchEventFrame = CreateFrame("Frame", "BenchEventFrame", UIParent) +benchEventFrame:RegisterEvent("RAID_ROSTER_UPDATE") +benchEventFrame:RegisterEvent("PARTY_MEMBERS_CHANGED") +benchEventFrame:RegisterEvent("CHAT_MSG_GUILD") +benchEventFrame:RegisterEvent("CHAT_MSG_RAID") +benchEventFrame:RegisterEvent("CHAT_MSG_RAID_LEADER") +benchEventFrame:RegisterEvent("ADDON_LOADED") + +benchEventFrame:SetScript("OnEvent", function() + if event == "ADDON_LOADED" and arg1 == "shootyepgp" then + -- Load saved log data + if not BenchTrackerDB then + BenchTrackerDB = {} + end + if BenchTrackerDB.log then + logEntries = BenchTrackerDB.log + else + BenchTrackerDB.log = {} + end + if BenchTrackerDB.checkCounter then + checkCounter = BenchTrackerDB.checkCounter + else + BenchTrackerDB.checkCounter = 0 + end + DEFAULT_CHAT_FRAME:AddMessage("|cFF00FF00BenchTracker loaded! Type /bench to open.|r") + + elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then + if mainFrame and mainFrame:IsVisible() and activeTab == "afk" then + BenchTracker:UpdateAFKDisplay() + end + + elseif event == "CHAT_MSG_GUILD" then + if isListening and arg1 and arg2 then + BenchTracker:OnChatMessage(arg2) + end + + elseif event == "CHAT_MSG_RAID" or event == "CHAT_MSG_RAID_LEADER" then + if isListening and arg1 and arg2 then + BenchTracker:OnChatMessage(arg2) + end + end +end) + +---------------------------------------------------------------- +-- HOOK: EP AWARDS +---------------------------------------------------------------- +local original_award_raid_ep = nil +local original_award_reserve_ep = nil + +local epHookFrame = CreateFrame("Frame", "BenchEPHookFrame", UIParent) +epHookFrame:RegisterEvent("ADDON_LOADED") +epHookFrame:SetScript("OnEvent", function() + if event == "ADDON_LOADED" and arg1 == "shootyepgp" then + -- Hook after sepgp is available + if sepgp and sepgp.award_reserve_ep then + original_award_reserve_ep = sepgp.award_reserve_ep + sepgp.award_reserve_ep = function(self, ep) + original_award_reserve_ep(self, ep) + BenchTracker:LogEPAward(ep) + end + end + if sepgp and sepgp.award_raid_ep then + original_award_raid_ep = sepgp.award_raid_ep + sepgp.award_raid_ep = function(self, ep) + original_award_raid_ep(self, ep) + BenchTracker:LogEPAward(ep) + end + end + end +end) diff --git a/shootyepgp.toc b/shootyepgp.toc index 90c5c47..2dc637a 100644 --- a/shootyepgp.toc +++ b/shootyepgp.toc @@ -7,10 +7,10 @@ ## X-Website: https://github.com/cinecom/shootyepgp/ ## X-Category: Guild ## X-Credits: shooty, Qcat(zhCN) -## Version: 10.18.2 +## Version: 10.18.3 ## X-Alpha: ## OptionalDeps: Ace2 -## SavedVariables: sepgp_saychannel, sepgp_groupbyclass, sepgp_groupbyarmor, sepgp_groupbyrole, sepgp_raidonly, sepgp_dbver, sepgp_debug, sepgp_fubar, sepgp_sound, RaidStrikeDB, RaidStrikeResetTime +## SavedVariables: sepgp_saychannel, sepgp_groupbyclass, sepgp_groupbyarmor, sepgp_groupbyrole, sepgp_raidonly, sepgp_dbver, sepgp_debug, sepgp_fubar, sepgp_sound, RaidStrikeDB, RaidStrikeResetTime, BenchTrackerDB ## SavedVariablesPerCharacter: sepgp_main, sepgp_log, sepgp_looted, sepgp_decay, sepgp_minep, sepgp_reservechannel, sepgp_progress, sepgp_discount, sepgp_altspool, sepgp_altpercent Libs\AceLibrary\AceLibrary.lua @@ -46,4 +46,5 @@ reserves.lua alts.lua logs.lua matrix.lua -raidstrike.lua \ No newline at end of file +raidstrike.lua +bench.lua \ No newline at end of file