local T = ShaguTweaks.T local API = ShaguTweaks.API local module = ShaguTweaks:register({ title = T["Improved Roll Frames"], description = T["Smaller roll frames with roll tracking."], expansions = { ["vanilla"] = true }, category = T["Loot"], enabled = nil, }) module.enable = function(self) if not API.loothistoryevents or not API.modifierkeys or not API.iteminfoinstant or not API.lootrollitemid then return end local _G = ShaguTweaks.GetGlobalEnv() local font_default, font_size = "Fonts\\skurri.TTF", 15 local ROLL_FRAME_COUNT = 4 ShaguTweaks.roll = CreateFrame("Frame", "STLootRoll", UIParent) ShaguTweaks.roll.frames = {} local function FindHistoryIndex(historyRollID) if not historyRollID then return end local numItems = API.GetLootHistoryNumItems() for i=1,numItems do local rollID = API.GetLootHistoryItem(i) if rollID == historyRollID then return i end end end local function FindUnclaimedHistory(itemID, claimed) if not itemID then return end local numItems = API.GetLootHistoryNumItems() -- New group rolls are appended to C_LootHistory. Search newest first so -- the frame opened by the current START_ROLL binds to that new entry. for i=numItems,1,-1 do local historyRollID, historyLink = API.GetLootHistoryItem(i) local historyItemID = historyLink and API.GetItemInfoInstant(historyLink) if historyRollID and historyItemID == itemID and not claimed[historyRollID] then return historyRollID, i end end end local function GetRollPlayers(frame, wantedType) local players = {} local itemIndex = FindHistoryIndex(frame.historyRollID) if not itemIndex then return players end local _, _, numPlayers = API.GetLootHistoryItem(itemIndex) for i=1,(numPlayers or 0) do local name, class, rollType, roll, isWinner, isMe = API.GetLootHistoryPlayerInfo(itemIndex, i) -- C_LootHistory may synthesize a winner row when its individual roll -- packet never arrived. That row has roll=0; don't mislabel it as Pass. if name and not isMe and not (isWinner and roll == 0) and rollType == wantedType then table.insert(players, name) end end return players end local function RefreshHistory(frame, itemIndex) if not frame or not frame.historyRollID then return end itemIndex = itemIndex or FindHistoryIndex(frame.historyRollID) if not itemIndex then return end local historyRollID, _, numPlayers = API.GetLootHistoryItem(itemIndex) if historyRollID ~= frame.historyRollID then return end local need, greed, pass = 0, 0, 0 for i=1,(numPlayers or 0) do local name, class, rollType, roll, isWinner, isMe = API.GetLootHistoryPlayerInfo(itemIndex, i) -- The original chat parser deliberately ignored "You"; preserve that -- behavior by not counting the local player's own roll. if name and not isMe and not (isWinner and roll == 0) then if rollType == 1 then need = need + 1 elseif rollType == 2 then greed = greed + 1 elseif rollType == 0 then pass = pass + 1 end end end frame.need.count:SetText(need > 0 and need or "") frame.greed.count:SetText(greed > 0 and greed or "") frame.pass.count:SetText(pass > 0 and pass or "") end local function BindVisibleFrames() local claimed = {} -- FULL_UPDATE can also mean the history was cleared or the 128-entry ring -- evicted an old row. Drop stale bindings before assigning new ones. for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame and frame.historyRollID and not FindHistoryIndex(frame.historyRollID) then frame.historyRollID = nil frame.need.count:SetText("") frame.greed.count:SetText("") frame.pass.count:SetText("") end end for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame and frame.historyRollID then claimed[frame.historyRollID] = true end end for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame and frame:IsVisible() and frame.rollID and not frame.historyRollID then local itemID = frame.itemID or _G.GetLootRollItemID(frame.rollID) local historyRollID, itemIndex = FindUnclaimedHistory(itemID, claimed) if historyRollID then frame.historyRollID = historyRollID claimed[historyRollID] = true RefreshHistory(frame, itemIndex) end end end end local function RefreshAllHistory() for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame and frame:IsVisible() then RefreshHistory(frame) end end end function ShaguTweaks.roll:CreateLootRoll(id) local size = 22 local border = 4 local esize = 22 local f = CreateFrame("Frame", "STLootRollFrame" .. id, UIParent) local function CreateBackdrop(frame, b, a) if not frame then return end frame.backdrop = CreateFrame("Frame", nil, frame) frame.backdrop:SetPoint("TOPLEFT", frame, "TOPLEFT", -b, b) frame.backdrop:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", b, -b) frame.backdrop:SetBackdrop({ bgFile = "Interface/Tooltips/UI-Tooltip-Background", edgeFile = "Interface/Tooltips/UI-Tooltip-Border", edgeSize = 12, insets = { left = 4, right = 4, top = 4, bottom = 4 }, }) frame.backdrop:SetBackdropColor(0, 0, 0, a) frame.backdrop:SetBackdropBorderColor(1, 1, 1, a) end CreateBackdrop(f, border, .1) f.backdrop:SetFrameStrata("BACKGROUND") f.hasItem = 1 f:SetWidth(385) f:SetHeight(size) f.icon = CreateFrame("Button", "STLootRollFrame" .. id .. "Icon", f) CreateBackdrop(f.icon, border, .1) f.icon:SetPoint("LEFT", f, "LEFT", -30, 0) f.icon:SetWidth(esize*1.2) f.icon:SetHeight(esize*1.2) f.icon.tex = f.icon:CreateTexture("OVERLAY") f.icon.tex:SetTexCoord(.08, .92, .08, .92) f.icon.tex:SetAllPoints(f.icon) f.icon:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetLootRollItem(this:GetParent().rollID) CursorUpdate() end) f.icon:SetScript("OnLeave", function() GameTooltip:Hide() end) f.icon:SetScript("OnClick", function() local parent = this:GetParent() local link = parent and parent.rollID and GetLootRollItemLink(parent.rollID) if link and API.IsControlKeyDown() then DressUpItemLink(link) elseif link and API.IsShiftKeyDown() then if ChatEdit_InsertLink then ChatEdit_InsertLink(link) elseif ChatFrameEditBox:IsVisible() then ChatFrameEditBox:Insert(link) end end end) f.need = CreateFrame("Button", "STLootRollFrame" .. id .. "Need", f) f.need:SetPoint("LEFT", f.icon, "RIGHT", border*3, -1) f.need:SetWidth(esize) f.need:SetHeight(esize) f.need:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Dice-Up") f.need:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Dice-Highlight") f.need.count = f.need:CreateFontString("NEED") f.need.count:SetPoint("CENTER", f.need, "CENTER", 0, 0) f.need.count:SetJustifyH("CENTER") f.need.count:SetFont(font_default, font_size, "OUTLINE") f.need:SetScript("OnClick", function() RollOnLoot(this:GetParent().rollID, 1) end) f.need:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetText("|cff33ffcc" .. NEED) for _, player in pairs(GetRollPlayers(f, 1)) do GameTooltip:AddLine(player) end GameTooltip:Show() end) f.need:SetScript("OnLeave", function() GameTooltip:Hide() end) f.greed = CreateFrame("Button", "STLootRollFrame" .. id .. "Greed", f) f.greed:SetPoint("LEFT", f.icon, "RIGHT", border*5+esize, -2) f.greed:SetWidth(esize) f.greed:SetHeight(esize) f.greed:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Coin-Up") f.greed:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Coin-Highlight") f.greed.count = f.greed:CreateFontString("GREED") f.greed.count:SetPoint("CENTER", f.greed, "CENTER", 0, 1) f.greed.count:SetJustifyH("CENTER") f.greed.count:SetFont(font_default, font_size, "OUTLINE") f.greed:SetScript("OnClick", function() RollOnLoot(this:GetParent().rollID, 2) end) f.greed:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetText("|cff33ffcc" .. GREED) for _, player in pairs(GetRollPlayers(f, 2)) do GameTooltip:AddLine(player) end GameTooltip:Show() end) f.greed:SetScript("OnLeave", function() GameTooltip:Hide() end) f.pass = CreateFrame("Button", "STLootRollFrame" .. id .. "Pass", f) f.pass:SetPoint("LEFT", f.icon, "RIGHT", border*7+esize*2, 0) f.pass:SetWidth(esize) f.pass:SetHeight(esize) f.pass:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Pass-Up") f.pass:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Coin-Highlight") f.pass.count = f.pass:CreateFontString("PASS") f.pass.count:SetPoint("CENTER", f.pass, "CENTER", 0, -1) f.pass.count:SetJustifyH("CENTER") f.pass.count:SetFont(font_default, font_size, "OUTLINE") f.pass:SetScript("OnClick", function() RollOnLoot(this:GetParent().rollID, 0) end) f.pass:SetScript("OnEnter", function() GameTooltip:SetOwner(this, "ANCHOR_RIGHT") GameTooltip:SetText("|cff33ffcc" .. PASS) for _, player in pairs(GetRollPlayers(f, 0)) do GameTooltip:AddLine(player) end GameTooltip:Show() end) f.pass:SetScript("OnLeave", function() GameTooltip:Hide() end) f.boe = CreateFrame("Frame", "STLootRollFrame" .. id .. "BOE", f) f.boe:SetPoint("LEFT", f.icon, "RIGHT", border*9+esize*3, 0) f.boe:SetWidth(esize*2) f.boe:SetHeight(esize) f.boe.text = f.boe:CreateFontString("BOE") f.boe.text:SetAllPoints(f.boe) f.boe.text:SetJustifyH("LEFT") f.boe.text:SetFont(font_default, font_size, "OUTLINE") f.name = CreateFrame("Frame", "STLootRollFrame" .. id .. "Name", f) f.name:SetPoint("LEFT", f.icon, "RIGHT", border*11+esize*4, 0) f.name:SetPoint("RIGHT", f, "RIGHT", border*2, 0) f.name:SetHeight(esize) f.name.text = f.name:CreateFontString("NAME") f.name.text:SetAllPoints(f.name) f.name.text:SetJustifyH("LEFT") f.name.text:SetFont(font_default, font_size, "OUTLINE") f.time = CreateFrame("Frame", "STLootRollFrame" .. id .. "Time", f) f.time:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0) f.time:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", 0, 0) f.time:SetFrameStrata("LOW") f.time.bar = CreateFrame( "StatusBar", "STLootRollFrame" .. id .. "TimeBar", f.time) f.time.bar:SetAllPoints(f.time) f.time.bar:SetStatusBarTexture( "Interface\\TargetingFrame\\UI-StatusBar") f.time.bar:SetMinMaxValues(0, 100) f.time.bar:SetStatusBarColor(1, 210/255, 0) f.time.bar:SetValue(20) return f end -- One shared updater drives every visible countdown bar. The original -- module installed one OnUpdate on every roll frame. ShaguTweaks.roll.timer = CreateFrame("Frame") ShaguTweaks.roll.timer:Hide() ShaguTweaks.roll.timer:SetScript("OnUpdate", function() local anyVisible = false for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame and frame:IsVisible() and frame.rollID then anyVisible = true local left = GetLootRollTimeLeft(frame.rollID) if left < 0 or left > frame.rollTime then left = 0 end frame.time.bar:SetValue(left) end end if not anyVisible then this:Hide() end end) ShaguTweaks.roll:RegisterEvent("CANCEL_LOOT_ROLL") ShaguTweaks.roll:RegisterEvent("LOOT_HISTORY_ROLL_CHANGED") ShaguTweaks.roll:RegisterEvent("LOOT_HISTORY_ROLL_COMPLETE") ShaguTweaks.roll:RegisterEvent("LOOT_HISTORY_FULL_UPDATE") ShaguTweaks.roll:SetScript("OnEvent", function() if event == "CANCEL_LOOT_ROLL" then for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame.rollID == arg1 then frame:Hide() frame.rollID = nil frame.historyRollID = nil frame.itemID = nil frame.need.count:SetText("") frame.greed.count:SetText("") frame.pass.count:SetText("") return end end elseif event == "LOOT_HISTORY_FULL_UPDATE" then BindVisibleFrames() RefreshAllHistory() elseif event == "LOOT_HISTORY_ROLL_CHANGED" or event == "LOOT_HISTORY_ROLL_COMPLETE" then local itemIndex = arg1 local historyRollID = itemIndex and API.GetLootHistoryItem(itemIndex) if historyRollID then for i=1,ROLL_FRAME_COUNT do local frame = ShaguTweaks.roll.frames[i] if frame and frame.historyRollID == historyRollID then RefreshHistory(frame, itemIndex) return end end end -- A structural update normally binds first; this also self-heals if an -- addon changed the native roll-frame opening order. BindVisibleFrames() RefreshAllHistory() end end) function _G.GroupLootFrame_OpenNewFrame(id, rollTime) local available for i=1,ROLL_FRAME_COUNT do if not ShaguTweaks.roll.frames[i]:IsVisible() and not available then available = i end end if available then local frame = ShaguTweaks.roll.frames[available] frame.rollID = id frame.rollTime = rollTime frame.historyRollID = nil frame.itemID = _G.GetLootRollItemID(id) ShaguTweaks.roll:UpdateLootRoll(available) end end function ShaguTweaks.roll:UpdateLootRoll(id) local frame = ShaguTweaks.roll.frames[id] local texture, name, count, quality, bop = GetLootRollItemInfo(frame.rollID) local color = ITEM_QUALITY_COLORS[quality] frame.itemname = name frame.need.count:SetText("") frame.greed.count:SetText("") frame.pass.count:SetText("") frame.name.text:SetText(name) frame.name.text:SetTextColor(color.r, color.g, color.b, 1) frame.icon.tex:SetTexture(texture) frame.backdrop:SetBackdropBorderColor(color.r, color.g, color.b) frame.time.bar:SetMinMaxValues(0, frame.rollTime) frame.time.bar:SetStatusBarColor(color.r, color.g, color.b, .5) if bop then frame.boe.text:SetText("BoP") frame.boe.text:SetTextColor(1,.3,.3,1) else frame.boe.text:SetText("BoE") frame.boe.text:SetTextColor(.3,1,.3,1) end frame:Show() ShaguTweaks.roll.timer:Show() -- ClassicAPI records the new history row after the native START_ROLL -- handler returns. Binding happens on the ensuing FULL_UPDATE; doing it -- here could accidentally attach a repeated item to an older history row. end for i=1,ROLL_FRAME_COUNT do if not ShaguTweaks.roll.frames[i] then ShaguTweaks.roll.frames[i] = ShaguTweaks.roll:CreateLootRoll(i) ShaguTweaks.roll.frames[i]:SetPoint("CENTER", 15, i*35) ShaguTweaks.roll.frames[i]:Hide() end end end