From 24767ddd9a0369bdf0f360d34ff899110ef88cc7 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:40:41 +0200 Subject: [PATCH 1/8] Add ClassicAPI loot history bridges --- api.lua | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/api.lua b/api.lua index 3a2db42..d114546 100644 --- a/api.lua +++ b/api.lua @@ -103,6 +103,16 @@ API.merchantiteminfo = type(_G.C_MerchantFrame) == "table" API.buybackitemid = type(_G.C_MerchantFrame) == "table" and type(_G.C_MerchantFrame.GetBuybackItemID) == "function" +API.loothistory = API.classicapi_version >= 11202 + and type(_G.C_LootHistory) == "table" + and type(_G.C_LootHistory.GetNumItems) == "function" + and type(_G.C_LootHistory.GetItem) == "function" + and type(_G.C_LootHistory.GetPlayerInfo) == "function" +API.loothistoryevents = API.loothistory and API.eventutils + and _G.C_EventUtils.IsEventValid("LOOT_HISTORY_ROLL_CHANGED") + and _G.C_EventUtils.IsEventValid("LOOT_HISTORY_ROLL_COMPLETE") + and _G.C_EventUtils.IsEventValid("LOOT_HISTORY_FULL_UPDATE") + API.overridebindings = type(_G.SetOverrideBindingClick) == "function" and type(_G.ClearOverrideBindings) == "function" @@ -517,6 +527,25 @@ API.GetBuybackItemID = function(index) end end +API.GetLootHistoryNumItems = function() + if API.loothistory then + return _G.C_LootHistory.GetNumItems() + end + return 0 +end + +API.GetLootHistoryItem = function(index) + if API.loothistory and index then + return _G.C_LootHistory.GetItem(index) + end +end + +API.GetLootHistoryPlayerInfo = function(itemIndex, playerIndex) + if API.loothistory and itemIndex and playerIndex then + return _G.C_LootHistory.GetPlayerInfo(itemIndex, playerIndex) + end +end + API.GetMerchantItemID = function(index) if type(_G.GetMerchantItemID) == "function" then return _G.GetMerchantItemID(index) From 411559794c62f0827fa261510de1563a1840314b Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:42:14 +0200 Subject: [PATCH 2/8] Use ClassicAPI loot history for roll tracking --- mods/improved-roll-frames.lua | 478 +++++++++++++++++++--------------- 1 file changed, 275 insertions(+), 203 deletions(-) diff --git a/mods/improved-roll-frames.lua b/mods/improved-roll-frames.lua index 8799282..5544a16 100644 --- a/mods/improved-roll-frames.lua +++ b/mods/improved-roll-frames.lua @@ -1,130 +1,165 @@ 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, - }) + 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) - local _G = ShaguTweaks.GetGlobalEnv() - local font_default, font_size = "Fonts\\skurri.TTF", 15 - local ROLL_FRAME_COUNT = 4 + if not API.loothistoryevents or not API.modifierkeys then return end - ShaguTweaks.roll = CreateFrame("Frame", "STLootRoll", UIParent) - ShaguTweaks.roll.frames = {} + local _G = ShaguTweaks.GetGlobalEnv() + local font_default, font_size = "Fonts\\skurri.TTF", 15 + local ROLL_FRAME_COUNT = 4 - -- squash vanilla item placeholders - local LOOT_ROLL_GREED = string.gsub(LOOT_ROLL_GREED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s") - local LOOT_ROLL_NEED = string.gsub(LOOT_ROLL_NEED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s") - local LOOT_ROLL_PASSED = string.gsub(LOOT_ROLL_PASSED, "%%s|Hitem:%%d:%%d:%%d:%%d|h%[%%s%]|h%%s", "%%s") + ShaguTweaks.roll = CreateFrame("Frame", "STLootRoll", UIParent) + ShaguTweaks.roll.frames = {} - -- try to detect the everyone string - local _, _, everyone, _ = strfind(LOOT_ROLL_ALL_PASSED, LOOT_ROLL_PASSED) - ShaguTweaks.roll.blacklist = {} - if YOU then ShaguTweaks.roll.blacklist[YOU] = true end - if everyone then ShaguTweaks.roll.blacklist[everyone] = true end + local function FindHistoryIndex(historyRollID) + if not historyRollID then return end - ShaguTweaks.roll.cache = {} + local numItems = API.GetLootHistoryNumItems() + for i=1,numItems do + local rollID = API.GetLootHistoryItem(i) + if rollID == historyRollID then + return i + end + end + end - ShaguTweaks.roll.scan = CreateFrame("Frame", "STLootRollMonitor", UIParent) - ShaguTweaks.roll.scan:RegisterEvent("CHAT_MSG_LOOT") - ShaguTweaks.roll.scan:SetScript("OnEvent", function() - local player, item = ShaguTweaks.cmatch(arg1, LOOT_ROLL_GREED) - if player and item then - ShaguTweaks.roll:AddCache(item, player, "GREED") - return + local function LinksMatch(nativeLink, historyLink) + if not nativeLink or not historyLink then return false end + if nativeLink == historyLink then return true end + return string.find(nativeLink, historyLink, 1, true) and true or false + end + + local function FindUnclaimedHistory(nativeLink, claimed) + 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) + if historyRollID and not claimed[historyRollID] + and LinksMatch(nativeLink, historyLink) 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) + if name and not isMe and rollType == wantedType then + table.insert(players, name) + end end - local player, item = ShaguTweaks.cmatch(arg1, LOOT_ROLL_NEED) - if player and item then - ShaguTweaks.roll:AddCache(item, player, "NEED") - return - end + return players + end - local player, item = ShaguTweaks.cmatch(arg1, LOOT_ROLL_PASSED) - if player and item then - ShaguTweaks.roll:AddCache(item, player, "PASS") - return - end - end) + local function RefreshHistory(frame, itemIndex) + if not frame or not frame.historyRollID then return end - function ShaguTweaks.roll:AddCache(hyperlink, name, roll) - -- skip invalid names - if not hyperlink or not name then return end - if ShaguTweaks.roll.blacklist[name] then return end + itemIndex = itemIndex or FindHistoryIndex(frame.historyRollID) + if not itemIndex then return end - local _, _, itemLink = string.find(hyperlink, "(item:%d+:%d+:%d+:%d+)") - if not itemLink then return end - local itemName = GetItemInfo(itemLink) - if not itemName then return end + local historyRollID, _, numPlayers = API.GetLootHistoryItem(itemIndex) + if historyRollID ~= frame.historyRollID then return end - local now = GetTime() - local itemCache = ShaguTweaks.roll.cache[itemName] + local need, greed, pass = 0, 0, 0 - -- delete obsolete tables - if itemCache and itemCache["TIMESTAMP"] < now - 60 then - ShaguTweaks.roll.cache[itemName] = nil - itemCache = nil - end + for i=1,(numPlayers or 0) do + local name, class, rollType, roll, isWinner, isMe = + API.GetLootHistoryPlayerInfo(itemIndex, i) - -- initialize itemtable - if not itemCache then - itemCache = { ["GREED"] = {}, ["NEED"] = {}, ["PASS"] = {}, ["TIMESTAMP"] = now } - ShaguTweaks.roll.cache[itemName] = itemCache - end - - -- ignore already listed names - for _, existing in pairs(itemCache[roll]) do - if name == existing then return end - end - - table.insert(itemCache[roll], name) - - local count_greed, count_need, count_pass - - for id=1,ROLL_FRAME_COUNT do - if ShaguTweaks.roll.frames[id]:IsVisible() and ShaguTweaks.roll.frames[id].itemname == itemName then - if not count_greed then - count_greed = table.getn(itemCache["GREED"]) - count_need = table.getn(itemCache["NEED"]) - count_pass = table.getn(itemCache["PASS"]) + -- The original chat parser deliberately ignored "You"; preserve that + -- behavior by not counting the local player's own roll. + if name and not isMe then + if rollType == 1 then + need = need + 1 + elseif rollType == 2 then + greed = greed + 1 + elseif rollType == 0 then + pass = pass + 1 end - ShaguTweaks.roll.frames[id].greed.count:SetText(count_greed > 0 and count_greed or "") - ShaguTweaks.roll.frames[id].need.count:SetText(count_need > 0 and count_need or "") - ShaguTweaks.roll.frames[id].pass.count:SetText(count_pass > 0 and count_pass or "") - end - end + end end - function ShaguTweaks.roll:CreateLootRoll(id) + 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 = {} + + 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 nativeLink = frame.historyLink or GetLootRollItemLink(frame.rollID) + local historyRollID, itemIndex = + FindUnclaimedHistory(nativeLink, 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 rawborder, border = GetBorderSize() local border = 4 local esize = 22 local f = CreateFrame("Frame", "STLootRollFrame" .. id, UIParent) - local function CreateBackdrop(f,b,a) - if not f then return end - f.backdrop = CreateFrame("Frame", nil, f) - f.backdrop:SetPoint("TOPLEFT", f, "TOPLEFT", -b, b) - f.backdrop:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", b, -b) - f.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 }, - }) + 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 }, + }) - f.backdrop:SetBackdropColor(0, 0, 0, a) - f.backdrop:SetBackdropBorderColor(1, 1, 1, a) + frame.backdrop:SetBackdropColor(0, 0, 0, a) + frame.backdrop:SetBackdropBorderColor(1, 1, 1, a) end CreateBackdrop(f, border, .1) - -- CreateBackdrop(f, nil, nil, .8) - -- CreateBackdropShadow(f) f.backdrop:SetFrameStrata("BACKGROUND") f.hasItem = 1 @@ -142,25 +177,29 @@ module.enable = function(self) f.icon.tex:SetAllPoints(f.icon) f.icon:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetLootRollItem(this:GetParent().rollID) - CursorUpdate() + GameTooltip:SetOwner(this, "ANCHOR_RIGHT") + GameTooltip:SetLootRollItem(this:GetParent().rollID) + CursorUpdate() end) f.icon:SetScript("OnLeave", function() - GameTooltip:Hide() + GameTooltip:Hide() end) f.icon:SetScript("OnClick", function() - if IsControlKeyDown() then - DressUpItemLink(GetLootRollItemLink(this:GetParent().rollID)) - elseif IsShiftKeyDown() then + 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(GetLootRollItemLink(this:GetParent().rollID)) + ChatEdit_InsertLink(link) elseif ChatFrameEditBox:IsVisible() then - ChatFrameEditBox:Insert(GetLootRollItemLink(this:GetParent().rollID)) - end + ChatFrameEditBox:Insert(link) end + end end) f.need = CreateFrame("Button", "STLootRollFrame" .. id .. "Need", f) @@ -176,20 +215,18 @@ module.enable = function(self) f.need.count:SetFont(font_default, font_size, "OUTLINE") f.need:SetScript("OnClick", function() - RollOnLoot(this:GetParent().rollID, 1) + RollOnLoot(this:GetParent().rollID, 1) end) f.need:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText("|cff33ffcc" .. NEED) - if f.itemname and ShaguTweaks.roll.cache[f.itemname] then - for _, player in pairs(ShaguTweaks.roll.cache[f.itemname]["NEED"]) do - GameTooltip:AddLine(player) - end - end - GameTooltip:Show() + 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() + GameTooltip:Hide() end) f.greed = CreateFrame("Button", "STLootRollFrame" .. id .. "Greed", f) @@ -197,7 +234,7 @@ module.enable = function(self) 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:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Dice-Highlight") f.greed.count = f.greed:CreateFontString("GREED") f.greed.count:SetPoint("CENTER", f.greed, "CENTER", 0, 1) @@ -205,20 +242,18 @@ module.enable = function(self) f.greed.count:SetFont(font_default, font_size, "OUTLINE") f.greed:SetScript("OnClick", function() - RollOnLoot(this:GetParent().rollID, 2) + RollOnLoot(this:GetParent().rollID, 2) end) f.greed:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText("|cff33ffcc" .. GREED) - if f.itemname and ShaguTweaks.roll.cache[f.itemname] then - for _, player in pairs(ShaguTweaks.roll.cache[f.itemname]["GREED"]) do - GameTooltip:AddLine(player) - end - end - GameTooltip:Show() + 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() + GameTooltip:Hide() end) f.pass = CreateFrame("Button", "STLootRollFrame" .. id .. "Pass", f) @@ -234,20 +269,18 @@ module.enable = function(self) f.pass.count:SetFont(font_default, font_size, "OUTLINE") f.pass:SetScript("OnClick", function() - RollOnLoot(this:GetParent().rollID, 0) + RollOnLoot(this:GetParent().rollID, 0) end) f.pass:SetScript("OnEnter", function() - GameTooltip:SetOwner(this, "ANCHOR_RIGHT") - GameTooltip:SetText("|cff33ffcc" .. PASS) - if f.itemname and ShaguTweaks.roll.cache[f.itemname] then - for _, player in pairs(ShaguTweaks.roll.cache[f.itemname]["PASS"]) do - GameTooltip:AddLine(player) - end - end - GameTooltip:Show() + 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() + GameTooltip:Hide() end) f.boe = CreateFrame("Frame", "STLootRollFrame" .. id .. "BOE", f) @@ -272,104 +305,143 @@ module.enable = function(self) 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 = CreateFrame( + "StatusBar", "STLootRollFrame" .. id .. "TimeBar", f.time) f.time.bar:SetAllPoints(f.time) - -- f.time.bar:SetStatusBarTexture(pfUI.media["img:bar"]) - f.time.bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar") + f.time.bar:SetStatusBarTexture( + "Interface\\TargetingFrame\\UI-StatusBar") f.time.bar:SetMinMaxValues(0, 100) - -- local r, g, b, a = strsplit(",", C.appearance.border.color) - local r, g, b, a = 255/255, 210/255, 0/255, 1 - -- local r, g, b, a = 1, 1, 1, 1 - f.time.bar:SetStatusBarColor(r, g, b) + f.time.bar:SetStatusBarColor(1, 210/255, 0) f.time.bar:SetValue(20) - f.time.bar.rollFrame = f - f.time.bar:SetScript("OnUpdate", function() - local frame = this.rollFrame - if not frame.rollID then return end - local left = GetLootRollTimeLeft(frame.rollID) - if left < 0 or left > frame.rollTime then left = 0 end - this:SetValue(left) - end) 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 - ShaguTweaks.roll:RegisterEvent("CANCEL_LOOT_ROLL") - ShaguTweaks.roll:SetScript("OnEvent", function() - for i=1,ROLL_FRAME_COUNT do - if ShaguTweaks.roll.frames[i].rollID == arg1 then - ShaguTweaks.roll.frames[i]:Hide() - ShaguTweaks.roll.frames[i].rollID = nil - return + 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.historyLink = 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) + end) + + function _G.GroupLootFrame_OpenNewFrame(id, rollTime) + local available - function _G.GroupLootFrame_OpenNewFrame(id, rollTime) - local visible, available for i=1,ROLL_FRAME_COUNT do - local isVisible = ShaguTweaks.roll.frames[i]:IsVisible() - visible = visible or isVisible - available = available or not isVisible and i + if not ShaguTweaks.roll.frames[i]:IsVisible() and not available then + available = i + end end - -- clear cache if possible - if not visible then ShaguTweaks.roll.cache = {} end - - -- setup the first available roll frame if available then - ShaguTweaks.roll.frames[available].rollID = id - ShaguTweaks.roll.frames[available].rollTime = rollTime - ShaguTweaks.roll:UpdateLootRoll(available) - end + local frame = ShaguTweaks.roll.frames[available] + frame.rollID = id + frame.rollTime = rollTime + frame.historyRollID = nil + frame.historyLink = GetLootRollItemLink(id) + ShaguTweaks.roll:UpdateLootRoll(available) end + end - function ShaguTweaks.roll:UpdateLootRoll(id) + function ShaguTweaks.roll:UpdateLootRoll(id) local frame = ShaguTweaks.roll.frames[id] - local texture, name, count, quality, bop = GetLootRollItemInfo(frame.rollID) + local texture, name, count, quality, bop = + GetLootRollItemInfo(frame.rollID) local color = ITEM_QUALITY_COLORS[quality] frame.itemname = name - - local itemCache = ShaguTweaks.roll.cache[name] - local count_greed = itemCache and table.getn(itemCache["GREED"]) or 0 - local count_need = itemCache and table.getn(itemCache["NEED"]) or 0 - local count_pass = itemCache and table.getn(itemCache["PASS"]) or 0 - - frame.greed.count:SetText(count_greed > 0 and count_greed or "") - frame.need.count:SetText(count_need > 0 and count_need or "") - frame.pass.count:SetText(count_pass > 0 and count_pass or "") + 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) - - -- if C.loot.raritytimer == "1" then - frame.time.bar:SetStatusBarColor(color.r, color.g, color.b, .5) - -- end + frame.time.bar:SetStatusBarColor(color.r, color.g, color.b, .5) if bop then - -- frame.boe.text:SetText(T["BoP"]) - frame.boe.text:SetText("BoP") - frame.boe.text:SetTextColor(1,.3,.3,1) + frame.boe.text:SetText("BoP") + frame.boe.text:SetTextColor(1,.3,.3,1) else - -- frame.boe.text:SetText(T["BoE"]) - frame.boe.text:SetText("BoE") - frame.boe.text:SetTextColor(.3,1,.3,1) + frame.boe.text:SetText("BoE") + frame.boe.text:SetTextColor(.3,1,.3,1) end frame:Show() - end + ShaguTweaks.roll.timer:Show() - for i=1,ROLL_FRAME_COUNT do + -- If ClassicAPI already has the matching history row (for example after + -- another addon reopens the frame), bind immediately. Normal START_ROLL + -- ordering binds a moment later on LOOT_HISTORY_FULL_UPDATE. + BindVisibleFrames() + 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", 0, -i*35) - ShaguTweaks.roll.frames[i]:SetPoint("CENTER", 15, i*35) - -- UpdateMovable(ShaguTweaks.roll.frames[i]) - ShaguTweaks.roll.frames[i]:Hide() - end + 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 From 7b78540c32152c3d59b85839472f7fe95a85562d Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:42:58 +0200 Subject: [PATCH 3/8] Bind roll history only after native start completes --- mods/improved-roll-frames.lua | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mods/improved-roll-frames.lua b/mods/improved-roll-frames.lua index 5544a16..a216525 100644 --- a/mods/improved-roll-frames.lua +++ b/mods/improved-roll-frames.lua @@ -431,10 +431,9 @@ module.enable = function(self) frame:Show() ShaguTweaks.roll.timer:Show() - -- If ClassicAPI already has the matching history row (for example after - -- another addon reopens the frame), bind immediately. Normal START_ROLL - -- ordering binds a moment later on LOOT_HISTORY_FULL_UPDATE. - BindVisibleFrames() + -- 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 From 7a7208f8748293910417bfbc085e2932e005f6f3 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:45:09 +0200 Subject: [PATCH 4/8] Preserve original greed roll highlight --- mods/improved-roll-frames.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mods/improved-roll-frames.lua b/mods/improved-roll-frames.lua index a216525..1531888 100644 --- a/mods/improved-roll-frames.lua +++ b/mods/improved-roll-frames.lua @@ -234,7 +234,7 @@ module.enable = function(self) f.greed:SetWidth(esize) f.greed:SetHeight(esize) f.greed:SetNormalTexture("Interface\\Buttons\\UI-GroupLoot-Coin-Up") - f.greed:SetHighlightTexture("Interface\\Buttons\\UI-GroupLoot-Dice-Highlight") + 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) From e1e2bbcdedab5db1402d946ab930af87e260eabf Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:47:27 +0200 Subject: [PATCH 5/8] Use ClassicAPI item IDs for loot history matching --- api.lua | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/api.lua b/api.lua index d114546..607d160 100644 --- a/api.lua +++ b/api.lua @@ -52,6 +52,8 @@ API.containeriteminfo = type(_G.C_Container) == "table" API.items = type(_G.C_Item) == "table" API.iteminfo = API.items and type(_G.C_Item.GetItemInfo) == "function" +API.iteminfoinstant = API.items + and type(_G.C_Item.GetItemInfoInstant) == "function" API.itemname = API.items and type(_G.C_Item.GetItemNameByID) == "function" API.itemquality = API.items and type(_G.C_Item.GetItemQualityByID) == "function" API.itemprice = API.items and type(_G.C_Item.GetItemSellPriceByID) == "function" @@ -112,6 +114,7 @@ API.loothistoryevents = API.loothistory and API.eventutils and _G.C_EventUtils.IsEventValid("LOOT_HISTORY_ROLL_CHANGED") and _G.C_EventUtils.IsEventValid("LOOT_HISTORY_ROLL_COMPLETE") and _G.C_EventUtils.IsEventValid("LOOT_HISTORY_FULL_UPDATE") +API.lootrollitemid = type(_G.GetLootRollItemID) == "function" API.overridebindings = type(_G.SetOverrideBindingClick) == "function" and type(_G.ClearOverrideBindings) == "function" @@ -445,6 +448,12 @@ API.GetItemInfo = function(item) return _G.GetItemInfo(item) end +API.GetItemInfoInstant = function(item) + if API.iteminfoinstant and item then + return _G.C_Item.GetItemInfoInstant(item) + end +end + API.GetItemNameByID = function(itemID) if not itemID then return end if API.itemname then From 9ef89c863d50a7ed11dab35097338163f0b8acb4 Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:48:05 +0200 Subject: [PATCH 6/8] Match roll history through ClassicAPI item IDs --- mods/improved-roll-frames.lua | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/mods/improved-roll-frames.lua b/mods/improved-roll-frames.lua index 1531888..a8534db 100644 --- a/mods/improved-roll-frames.lua +++ b/mods/improved-roll-frames.lua @@ -10,7 +10,10 @@ local module = ShaguTweaks:register({ }) module.enable = function(self) - if not API.loothistoryevents or not API.modifierkeys then return end + 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 @@ -31,21 +34,19 @@ module.enable = function(self) end end - local function LinksMatch(nativeLink, historyLink) - if not nativeLink or not historyLink then return false end - if nativeLink == historyLink then return true end - return string.find(nativeLink, historyLink, 1, true) and true or false - end + local function FindUnclaimedHistory(itemID, claimed) + if not itemID then return end - local function FindUnclaimedHistory(nativeLink, claimed) 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) - if historyRollID and not claimed[historyRollID] - and LinksMatch(nativeLink, historyLink) then + local historyItemID = historyLink and API.GetItemInfoInstant(historyLink) + + if historyRollID and historyItemID == itemID + and not claimed[historyRollID] then return historyRollID, i end end @@ -115,9 +116,9 @@ module.enable = function(self) local frame = ShaguTweaks.roll.frames[i] if frame and frame:IsVisible() and frame.rollID and not frame.historyRollID then - local nativeLink = frame.historyLink or GetLootRollItemLink(frame.rollID) + local itemID = frame.itemID or API.GetLootRollItemID(frame.rollID) local historyRollID, itemIndex = - FindUnclaimedHistory(nativeLink, claimed) + FindUnclaimedHistory(itemID, claimed) if historyRollID then frame.historyRollID = historyRollID @@ -351,7 +352,7 @@ module.enable = function(self) frame:Hide() frame.rollID = nil frame.historyRollID = nil - frame.historyLink = nil + frame.itemID = nil frame.need.count:SetText("") frame.greed.count:SetText("") frame.pass.count:SetText("") @@ -397,7 +398,7 @@ module.enable = function(self) frame.rollID = id frame.rollTime = rollTime frame.historyRollID = nil - frame.historyLink = GetLootRollItemLink(id) + frame.itemID = API.GetLootRollItemID(id) ShaguTweaks.roll:UpdateLootRoll(available) end end From 4aa49f3a38a0fc5bf0ff35f8ad0c2a39ece2293a Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:49:33 +0200 Subject: [PATCH 7/8] Harden ClassicAPI roll history binding --- mods/improved-roll-frames.lua | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/mods/improved-roll-frames.lua b/mods/improved-roll-frames.lua index a8534db..b1b6e87 100644 --- a/mods/improved-roll-frames.lua +++ b/mods/improved-roll-frames.lua @@ -61,7 +61,10 @@ module.enable = function(self) for i=1,(numPlayers or 0) do local name, class, rollType, roll, isWinner, isMe = API.GetLootHistoryPlayerInfo(itemIndex, i) - if name and not isMe and rollType == wantedType then + -- 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 @@ -86,7 +89,7 @@ module.enable = function(self) -- The original chat parser deliberately ignored "You"; preserve that -- behavior by not counting the local player's own roll. - if name and not isMe then + if name and not isMe and not (isWinner and roll == 0) then if rollType == 1 then need = need + 1 elseif rollType == 2 then @@ -105,6 +108,19 @@ module.enable = function(self) 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 From 1b38cb5e52c1dc4c03f225ae98c2fdd777b1919e Mon Sep 17 00:00:00 2001 From: Dusk-92 Date: Thu, 3 Sep 2026 18:50:17 +0200 Subject: [PATCH 8/8] Keep roll tracking on direct ClassicAPI item IDs --- mods/improved-roll-frames.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mods/improved-roll-frames.lua b/mods/improved-roll-frames.lua index b1b6e87..cb6b5c7 100644 --- a/mods/improved-roll-frames.lua +++ b/mods/improved-roll-frames.lua @@ -132,7 +132,7 @@ module.enable = function(self) local frame = ShaguTweaks.roll.frames[i] if frame and frame:IsVisible() and frame.rollID and not frame.historyRollID then - local itemID = frame.itemID or API.GetLootRollItemID(frame.rollID) + local itemID = frame.itemID or _G.GetLootRollItemID(frame.rollID) local historyRollID, itemIndex = FindUnclaimedHistory(itemID, claimed) @@ -414,7 +414,7 @@ module.enable = function(self) frame.rollID = id frame.rollTime = rollTime frame.historyRollID = nil - frame.itemID = API.GetLootRollItemID(id) + frame.itemID = _G.GetLootRollItemID(id) ShaguTweaks.roll:UpdateLootRoll(available) end end