This commit is contained in:
rucky
2026-03-16 13:48:46 +08:00
commit 2a55dd6dad
93 changed files with 75075 additions and 0 deletions

548
Roll.lua Normal file
View File

@@ -0,0 +1,548 @@
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