Files
Nanami-UI/Roll.lua
2026-03-16 13:48:46 +08:00

549 lines
20 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

local AddOnName = "Nanami-UI"
SFrames = SFrames or {}
-- ============================================================
-- Roll Tracker Data
-- ============================================================
local currentRolls = {} -- [itemName] = { [playerName] = { action, roll } }
local _A = SFrames.ActiveTheme
local _bg = _A and _A.panelBg or { 0.08, 0.08, 0.10, 0.95 }
local _bd = _A and _A.panelBorder or { 0.3, 0.3, 0.35, 1 }
local RollUI = CreateFrame("Frame")
RollUI:RegisterEvent("CHAT_MSG_LOOT")
RollUI:RegisterEvent("CHAT_MSG_SYSTEM")
-- ============================================================
-- String helpers (Lua 5.0 only - NO string.match!)
-- ============================================================
local function StripLinks(msg)
return string.gsub(msg, "|c%x%x%x%x%x%x%x%x|H.-|h(.-)|h|r", "%1")
end
local function GetBracketItem(text)
local _, _, name = string.find(text, "%[(.-)%]")
if name then return name end
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
text = string.gsub(text, "%.$", "")
return text
end
local function TrimStr(s)
s = string.gsub(s, "^%s+", "")
s = string.gsub(s, "%s+$", "")
return s
end
-- ============================================================
-- Parse loot roll chat messages
-- ============================================================
local function TrackRollEvent(msg)
if not msg or msg == "" then return nil end
local clean = StripLinks(msg)
local player, rawItem, rollType, rollNum
-- Patterns based on ACTUAL Turtle WoW Chinese message format:
-- "Buis选择了贪婪取向[物品名]"
-- "Buis选择了需求取向[物品名]"
local _, _, p, i
-- === PRIMARY: Turtle WoW Chinese format "选择了需求取向" / "选择了贪婪取向" ===
_, _, p, i = string.find(clean, "^(.+)选择了需求取向:(.+)$")
if p and i then player = p; rawItem = i; rollType = "Need" end
if not player then
_, _, p, i = string.find(clean, "^(.+)选择了需求取向: (.+)$")
if p and i then player = p; rawItem = i; rollType = "Need" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+)选择了贪婪取向:(.+)$")
if p and i then player = p; rawItem = i; rollType = "Greed" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+)选择了贪婪取向: (.+)$")
if p and i then player = p; rawItem = i; rollType = "Greed" end
end
-- Pass: "放弃了" format
if not player then
_, _, p, i = string.find(clean, "^(.+)放弃了:(.+)$")
if p and i then player = p; rawItem = i; rollType = "Pass" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+)放弃了: (.+)$")
if p and i then player = p; rawItem = i; rollType = "Pass" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+)放弃了(.+)$")
if p and i then player = p; rawItem = i; rollType = "Pass" end
end
-- Roll (Chinese): "掷出 85 (1-100)"
if not player then
local r
_, _, p, r, i = string.find(clean, "^(.+)掷出%s*(%d+).-(.+)$")
if p and r and i then player = p; rawItem = i; rollType = "Roll"; rollNum = r end
end
if not player then
local r
_, _, p, r, i = string.find(clean, "^(.+)掷出%s*(%d+).-: (.+)$")
if p and r and i then player = p; rawItem = i; rollType = "Roll"; rollNum = r end
end
-- === FALLBACK: Other Chinese formats ===
if not player then
_, _, p, i = string.find(clean, "^(.+)需求了:(.+)$")
if p and i then player = p; rawItem = i; rollType = "Need" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+)贪婪了:(.+)$")
if p and i then player = p; rawItem = i; rollType = "Greed" end
end
-- === ENGLISH patterns ===
if not player then
_, _, p, i = string.find(clean, "^(.+) selected Need for: (.+)$")
if p and i then player = p; rawItem = i; rollType = "Need" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+) selected Greed for: (.+)$")
if p and i then player = p; rawItem = i; rollType = "Greed" end
end
if not player then
_, _, p, i = string.find(clean, "^(.+) passed on: (.+)$")
if p and i then player = p; rawItem = i; rollType = "Pass" end
end
if not player then
local r
_, _, p, r, i = string.find(clean, "^(.+) rolls (%d+) .+for: (.+)$")
if p and r and i then player = p; rawItem = i; rollType = "Roll"; rollNum = r end
end
if not player then
_, _, p, i = string.find(clean, "^(.+) passes on (.+)$")
if p and i then player = p; rawItem = i; rollType = "Pass" end
end
if player and rawItem then
player = TrimStr(player)
local itemName = GetBracketItem(rawItem)
if itemName == "" or player == "" then return nil end
if not currentRolls[itemName] then currentRolls[itemName] = {} end
if not currentRolls[itemName][player] then currentRolls[itemName][player] = {} end
if rollType == "Roll" then
currentRolls[itemName][player].roll = rollNum
else
currentRolls[itemName][player].action = rollType
end
return itemName
end
return nil
end
-- ============================================================
-- Update the tracker text on visible roll frames
-- ============================================================
local function UpdateRollTrackers()
for idx = 1, 4 do
local frame = _G["GroupLootFrame"..idx]
if frame and frame:IsVisible() and frame.rollID then
local _, itemName = GetLootRollItemInfo(frame.rollID)
if itemName and frame.sfNeedFS then
local data = currentRolls[itemName]
local needText, greedText, passText = "", "", ""
if data then
local needs, greeds, passes = {}, {}, {}
for pl, info in pairs(data) do
local hex = SFrames and SFrames:GetClassHexForName(pl)
local coloredPl = hex and ("|cff" .. hex .. pl .. "|r") or pl
local rollStr = ""
if info.roll then rollStr = "|cffaaaaaa(" .. info.roll .. ")|r" end
if info.action == "Need" then
table.insert(needs, coloredPl .. rollStr)
elseif info.action == "Greed" then
table.insert(greeds, coloredPl .. rollStr)
elseif info.action == "Pass" then
table.insert(passes, coloredPl)
end
end
if table.getn(needs) > 0 then
needText = "|cffff5555需求|r " .. table.concat(needs, " ")
end
if table.getn(greeds) > 0 then
greedText = "|cff55ff55贪婪|r " .. table.concat(greeds, " ")
end
if table.getn(passes) > 0 then
passText = "|cff888888放弃|r " .. table.concat(passes, " ")
end
end
frame.sfNeedFS:SetText(needText)
frame.sfGreedFS:SetText(greedText)
frame.sfPassFS:SetText(passText)
end
end
end
end
-- ============================================================
-- Event Handler
-- ============================================================
RollUI:SetScript("OnEvent", function()
if event == "CHAT_MSG_LOOT" or event == "CHAT_MSG_SYSTEM" then
local matched = TrackRollEvent(arg1)
if matched then
UpdateRollTrackers()
end
end
end)
-- ============================================================
-- Kill ALL Blizzard textures on the main frame only
-- ============================================================
local function NukeBlizzTextures(frame)
local regions = {frame:GetRegions()}
for _, r in ipairs(regions) do
if r:IsObjectType("Texture") and not r.sfKeep then
r:SetTexture(nil)
r:SetAlpha(0)
r:Hide()
-- Don't override Show! SetBackdrop needs it.
-- Instead mark as nuked and move off-screen
if not r.sfNuked then
r.sfNuked = true
r:ClearAllPoints()
r:SetPoint("CENTER", UIParent, "CENTER", 9999, 9999)
end
end
end
end
-- ============================================================
-- Style the GroupLootFrame
-- ============================================================
local function StyleGroupLootFrame(frame)
if frame.sfSkinned then return end
frame.sfSkinned = true
local fname = frame:GetName()
-- 1) Kill all Blizz textures on main frame
NukeBlizzTextures(frame)
-- 2) Alt-Drag
frame:SetMovable(true)
frame:EnableMouse(true)
frame:RegisterForDrag("LeftButton")
frame:SetScript("OnDragStart", function()
if IsAltKeyDown() then
this:StartMoving()
end
end)
frame:SetScript("OnDragStop", function()
this:StopMovingOrSizing()
end)
-- 3) Rounded backdrop
frame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
frame:SetBackdropColor(_bg[1], _bg[2], _bg[3], _bg[4])
frame:SetBackdropBorderColor(_bd[1], _bd[2], _bd[3], _bd[4])
-- Tag all backdrop regions so NukeBlizzTextures won't kill them
local bdRegions = {frame:GetRegions()}
for _, r in ipairs(bdRegions) do
r.sfKeep = true
end
-- 5) Frame size (taller for tracker area)
frame:SetWidth(300)
frame:SetHeight(100)
-- 6) Icon
local iconFrame = _G[fname.."IconFrame"]
if iconFrame then
iconFrame:ClearAllPoints()
iconFrame:SetPoint("TOPLEFT", frame, "TOPLEFT", 12, -10)
iconFrame:SetWidth(36)
iconFrame:SetHeight(36)
-- Kill IconFrame's own textures
local iRegions = {iconFrame:GetRegions()}
for _, r in ipairs(iRegions) do
if r:IsObjectType("Texture") then
local tName = r:GetName() or ""
if string.find(tName, "Icon") then
r:SetTexCoord(0.08, 0.92, 0.08, 0.92)
r:ClearAllPoints()
r:SetAllPoints(iconFrame)
r.sfKeep = true
else
r:SetTexture(nil)
r:SetAlpha(0)
r:Hide()
r.sfNuked = true
r:ClearAllPoints()
r:SetPoint("CENTER", UIParent, "CENTER", 9999, 9999)
end
end
end
-- Clean icon border
local iconBorder = frame:CreateTexture(nil, "ARTWORK")
iconBorder:SetTexture("Interface\\Buttons\\WHITE8X8")
iconBorder:SetVertexColor(0.4, 0.4, 0.45, 1)
iconBorder:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", -2, 2)
iconBorder:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", 2, -2)
iconBorder.sfKeep = true
local qualGlow = frame:CreateTexture(nil, "OVERLAY")
qualGlow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
qualGlow:SetBlendMode("ADD")
qualGlow:SetAlpha(0.8)
qualGlow:SetWidth(68)
qualGlow:SetHeight(68)
qualGlow:SetPoint("CENTER", iconFrame, "CENTER", 0, 0)
qualGlow:Hide()
qualGlow.sfKeep = true
frame.sfQualGlow = qualGlow
end
-- 7) Item Name (shorter width to make room for buttons)
local itemNameFS = _G[fname.."Name"]
if itemNameFS then
itemNameFS:ClearAllPoints()
itemNameFS:SetPoint("LEFT", iconFrame, "RIGHT", 10, 0)
itemNameFS:SetWidth(145)
itemNameFS:SetHeight(36)
itemNameFS:SetJustifyH("LEFT")
itemNameFS:SetJustifyV("MIDDLE")
end
-- 8) Buttons: right side, vertically centered with icon
local need = _G[fname.."NeedButton"]
local greed = _G[fname.."GreedButton"]
local pass = _G[fname.."PassButton"]
-- Create our own close/pass button since Blizz keeps hiding PassButton
local closeBtn = CreateFrame("Button", nil, frame)
closeBtn:SetWidth(20)
closeBtn:SetHeight(20)
closeBtn:SetPoint("TOPRIGHT", frame, "TOPRIGHT", -6, -6)
closeBtn:SetFrameLevel(frame:GetFrameLevel() + 10)
-- Rounded backdrop matching UI
closeBtn:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 8,
insets = { left = 2, right = 2, top = 2, bottom = 2 }
})
local _cbg = _A and _A.closeBtnBg or { 0.5, 0.1, 0.1, 0.85 }
local _cbd = _A and _A.closeBtnBorder or { 0.6, 0.2, 0.2, 0.8 }
local _cbgH = _A and _A.closeBtnHoverBg or { 0.7, 0.15, 0.15, 1 }
local _cbdH = _A and _A.closeBtnHoverBorder or { 0.9, 0.3, 0.3, 1 }
closeBtn:SetBackdropColor(_cbg[1], _cbg[2], _cbg[3], _cbg[4])
closeBtn:SetBackdropBorderColor(_cbd[1], _cbd[2], _cbd[3], _cbd[4])
-- "X" text
local closeText = closeBtn:CreateFontString(nil, "OVERLAY")
closeText:SetFont(SFrames:GetFont() or "Fonts\\ARKai_T.ttf", 11, "OUTLINE")
closeText:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
closeText:SetText("X")
closeText:SetTextColor(0.9, 0.8, 0.8)
closeBtn:SetScript("OnClick", function()
local parent = this:GetParent()
if parent and parent.rollID then
ConfirmLootRoll(parent.rollID, 0)
end
end)
closeBtn:SetScript("OnEnter", function()
this:SetBackdropColor(_cbgH[1], _cbgH[2], _cbgH[3], _cbgH[4])
this:SetBackdropBorderColor(_cbdH[1], _cbdH[2], _cbdH[3], _cbdH[4])
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:SetText("放弃 / Pass")
GameTooltip:Show()
end)
closeBtn:SetScript("OnLeave", function()
this:SetBackdropColor(_cbg[1], _cbg[2], _cbg[3], _cbg[4])
this:SetBackdropBorderColor(_cbd[1], _cbd[2], _cbd[3], _cbd[4])
GameTooltip:Hide()
end)
-- Position need/greed to the left of close button
if need and greed then
greed:ClearAllPoints()
greed:SetPoint("RIGHT", closeBtn, "LEFT", -6, 0)
greed:SetWidth(22); greed:SetHeight(22)
greed:Show()
need:ClearAllPoints()
need:SetPoint("RIGHT", greed, "LEFT", -4, 0)
need:SetWidth(22); need:SetHeight(22)
need:Show()
end
-- Hide Blizz pass button
if pass then
pass:ClearAllPoints()
pass:SetPoint("CENTER", UIParent, "CENTER", 9999, 9999)
pass:SetAlpha(0)
end
-- 9) HIDE Blizz timer completely
local blizzTimer = _G[fname.."Timer"]
if blizzTimer then
blizzTimer:SetAlpha(0)
blizzTimer:ClearAllPoints()
blizzTimer:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 0, 500)
end
-- 10) Our own timer bar
local myTimer = CreateFrame("StatusBar", nil, frame)
myTimer:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 12, 8)
myTimer:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -12, 8)
myTimer:SetHeight(6)
myTimer:SetMinMaxValues(0, 1)
myTimer:SetValue(1)
myTimer:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
myTimer:SetStatusBarColor(0.9, 0.7, 0.15)
local myTimerBg = myTimer:CreateTexture(nil, "BACKGROUND")
myTimerBg:SetTexture("Interface\\Buttons\\WHITE8X8")
myTimerBg:SetVertexColor(_A.slotBg[1], _A.slotBg[2], _A.slotBg[3], _A.slotBg[4] or 1)
myTimerBg:SetAllPoints(myTimer)
frame.sfTimer = myTimer
frame.sfTimerMax = nil -- will be set on first OnUpdate
-- 11) OnUpdate: record max time on first tick, then animate
frame:SetScript("OnUpdate", function()
if this.rollID and this.sfTimer then
local timeLeft = GetLootRollTimeLeft(this.rollID)
if timeLeft and timeLeft > 0 then
if not this.sfTimerMax or this.sfTimerMax < timeLeft then
this.sfTimerMax = timeLeft
end
this.sfTimer:SetMinMaxValues(0, this.sfTimerMax)
this.sfTimer:SetValue(timeLeft)
else
this.sfTimer:SetValue(0)
end
end
end)
-- 12) Clean button textures so they don't extend beyond bounds
if need then
local nRegions = {need:GetRegions()}
for _, r in ipairs(nRegions) do
if r:IsObjectType("Texture") then
r:ClearAllPoints()
r:SetAllPoints(need)
end
end
end
if greed then
local gRegions = {greed:GetRegions()}
for _, r in ipairs(gRegions) do
if r:IsObjectType("Texture") then
r:ClearAllPoints()
r:SetAllPoints(greed)
end
end
end
-- 13) Three FontStrings for Need / Greed / Pass (vertical stack, full width)
local textWidth = 276
local needFS = frame:CreateFontString(nil, "OVERLAY")
needFS:SetFont(SFrames:GetFont() or "Fonts\\ARKai_T.ttf", 10, "OUTLINE")
needFS:SetPoint("TOPLEFT", iconFrame, "BOTTOMLEFT", 0, -4)
needFS:SetWidth(textWidth)
needFS:SetJustifyH("LEFT")
needFS:SetJustifyV("TOP")
needFS:SetTextColor(1, 1, 1)
frame.sfNeedFS = needFS
local greedFS = frame:CreateFontString(nil, "OVERLAY")
greedFS:SetFont(SFrames:GetFont() or "Fonts\\ARKai_T.ttf", 10, "OUTLINE")
greedFS:SetPoint("TOPLEFT", needFS, "BOTTOMLEFT", 0, -1)
greedFS:SetWidth(textWidth)
greedFS:SetJustifyH("LEFT")
greedFS:SetJustifyV("TOP")
greedFS:SetTextColor(1, 1, 1)
frame.sfGreedFS = greedFS
local passFS = frame:CreateFontString(nil, "OVERLAY")
passFS:SetFont(SFrames:GetFont() or "Fonts\\ARKai_T.ttf", 10, "OUTLINE")
passFS:SetPoint("TOPLEFT", greedFS, "BOTTOMLEFT", 0, -1)
passFS:SetWidth(textWidth)
passFS:SetJustifyH("LEFT")
passFS:SetJustifyV("TOP")
passFS:SetTextColor(1, 1, 1)
frame.sfPassFS = passFS
end
-- ============================================================
-- Hooks
-- ============================================================
local function RestoreBackdrop(frame)
if not frame.sfSkinned then return end
frame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 }
})
frame:SetBackdropColor(_bg[1], _bg[2], _bg[3], _bg[4])
frame:SetBackdropBorderColor(_bd[1], _bd[2], _bd[3], _bd[4])
end
local function UpdateRollQualityGlow(frame)
if frame.sfQualGlow and frame.rollID then
local _, _, _, quality = GetLootRollItemInfo(frame.rollID)
if quality and quality > 1 then
local r, g, b = GetItemQualityColor(quality)
frame.sfQualGlow:SetVertexColor(r, g, b)
frame.sfQualGlow:Show()
else
frame.sfQualGlow:Hide()
end
end
end
local Hook_GroupLootFrame_OnShow = GroupLootFrame_OnShow
function GroupLootFrame_OnShow()
if Hook_GroupLootFrame_OnShow then Hook_GroupLootFrame_OnShow() end
StyleGroupLootFrame(this)
NukeBlizzTextures(this)
RestoreBackdrop(this)
UpdateRollQualityGlow(this)
UpdateRollTrackers()
end
local Hook_GroupLootFrame_Update = GroupLootFrame_Update
function GroupLootFrame_Update()
if Hook_GroupLootFrame_Update then Hook_GroupLootFrame_Update() end
for idx = 1, 4 do
local f = _G["GroupLootFrame"..idx]
if f and f:IsVisible() and f.sfSkinned then
NukeBlizzTextures(f)
RestoreBackdrop(f)
UpdateRollQualityGlow(f)
end
end
UpdateRollTrackers()
end