Files
Nanami-UI/LootDisplay.lua
rucky ec9e3c29d6 完成多出修改
修复拾取界面点击无效问题
修复宠物训练界面不显示训练点问题
新增天赋分享到聊天界面
修复飞行界面无法关闭问题
修复术士宠物的显示问题
为天赋界面添加默认数据库支持
框架现在也可自主选择是否启用
玩家框架和目标框架可取消显示3D头像以及透明度修改
背包和银行也添加透明度自定义支持
优化tooltip性能和背包物品显示方式
修复布局模式在ui缩放后不能正常定位的问题
添加硬核模式危险和死亡的工会通报
添加拾取和已拾取的框体
等等
2026-03-23 10:25:25 +08:00

817 lines
28 KiB
Lua
Raw 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.

--------------------------------------------------------------------------------
-- Nanami-UI: Loot Display (拾取界面 + 已拾取提示)
--------------------------------------------------------------------------------
SFrames = SFrames or {}
SFrames.LootDisplay = SFrames.LootDisplay or {}
local LD = SFrames.LootDisplay
--------------------------------------------------------------------------------
-- Constants
--------------------------------------------------------------------------------
local ROW_HEIGHT = 32
local ROW_WIDTH = 180
local ROW_GAP = 2
local ICON_SIZE = 26
local TITLE_HEIGHT = 22
local ALERT_WIDTH = 240
local ALERT_HEIGHT = 32
local ALERT_GAP = 3
local ALERT_ICON = 24
local ALERT_FADE_DUR = 0.6
local ALERT_FLOAT = 22
local ALERT_HOLD = 3.5
local ALERT_STAGGER = 0.25
local MAX_ALERTS = 10
local QUALITY_COLORS = {
[0] = { 0.62, 0.62, 0.62 },
[1] = { 0.92, 0.92, 0.88 },
[2] = { 0.12, 1.00, 0.00 },
[3] = { 0.00, 0.44, 0.87 },
[4] = { 0.64, 0.21, 0.93 },
[5] = { 1.00, 0.50, 0.00 },
[6] = { 0.90, 0.80, 0.50 },
}
-- Shared rounded backdrop template (matches rest of Nanami-UI)
local ROUND_BACKDROP = {
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 },
}
local ROUND_BACKDROP_SMALL = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
}
local ROUND_BACKDROP_SHADOW = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 16,
insets = { left = 4, right = 4, top = 4, bottom = 4 },
}
local ICON_BACKDROP = {
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 10,
insets = { left = 2, right = 2, top = 2, bottom = 2 },
}
local lootRows = {}
local activeAlerts = {}
local alertAnchor = nil
local alertPool = {}
--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function T()
return SFrames.ActiveTheme or {}
end
local function Font()
return (SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
end
local function GetDB()
if not SFramesDB then SFramesDB = {} end
if type(SFramesDB.lootDisplay) ~= "table" then
SFramesDB.lootDisplay = {
enable = true,
alertEnable = true,
alertFadeDelay = ALERT_HOLD,
scale = 1.0,
}
end
return SFramesDB.lootDisplay
end
local function QColor(quality)
local q = tonumber(quality) or 1
local c = QUALITY_COLORS[q]
if c then return c[1], c[2], c[3] end
return 0.92, 0.92, 0.88
end
local function ColorHex(r, g, b)
return string.format("%02x%02x%02x", r * 255, g * 255, b * 255)
end
local function GetItemTexture(itemIdOrLink)
if not itemIdOrLink then return nil end
local a1, a2, a3, a4, a5, a6, a7, a8, a9, a10 = GetItemInfo(itemIdOrLink)
if a10 and type(a10) == "string" then return a10 end
if a9 and type(a9) == "string" and string.find(a9, "Interface") then return a9 end
if a8 and type(a8) == "string" and string.find(a8, "Interface") then return a8 end
return nil
end
local function ParseItemLink(link)
if not link then return nil end
local _, _, colorHex, itemId, itemName = string.find(link, "|c(%x+)|Hitem:(%d+).-|h%[(.-)%]|h|r")
if not itemName then return nil end
local id = tonumber(itemId) or 0
local _, _, quality = GetItemInfo(id)
return itemName, quality or 1, id
end
--------------------------------------------------------------------------------
-- ▸ LOOT FRAME (拾取窗口)
--------------------------------------------------------------------------------
local lootFrame = nil
local function CreateLootFrame()
if lootFrame then return lootFrame end
local th = T()
local bg = th.panelBg or { 0.06, 0.05, 0.09, 0.95 }
local bd = th.panelBorder or { 0.30, 0.25, 0.42, 0.90 }
local acc = th.accent or { 1, 0.5, 0.8, 0.98 }
lootFrame = CreateFrame("Frame", "NanamiLootFrame", UIParent)
lootFrame:SetFrameStrata("FULLSCREEN_DIALOG")
lootFrame:SetFrameLevel(50)
lootFrame:SetWidth(ROW_WIDTH + 14)
lootFrame:SetHeight(TITLE_HEIGHT + 3 * (ROW_HEIGHT + ROW_GAP) + 10)
lootFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", 50, -200)
lootFrame:SetClampedToScreen(true)
lootFrame:SetMovable(true)
lootFrame:EnableMouse(false)
-- Shadow
local shadow = CreateFrame("Frame", nil, lootFrame)
shadow:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", -5, 5)
shadow:SetPoint("BOTTOMRIGHT", lootFrame, "BOTTOMRIGHT", 5, -5)
shadow:SetFrameLevel(math.max(lootFrame:GetFrameLevel() - 1, 0))
shadow:SetBackdrop(ROUND_BACKDROP_SHADOW)
shadow:SetBackdropColor(0, 0, 0, 0.55)
shadow:SetBackdropBorderColor(0, 0, 0, 0.40)
shadow:EnableMouse(false)
-- Background blocker: prevents clicks passing through to frames behind the loot window
local blocker = CreateFrame("Frame", nil, lootFrame)
blocker:SetAllPoints(lootFrame)
blocker:SetFrameLevel(lootFrame:GetFrameLevel())
blocker:EnableMouse(true)
-- Main panel (rounded)
lootFrame:SetBackdrop(ROUND_BACKDROP)
lootFrame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 0.95)
lootFrame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 0.90)
-- Top accent line
local acLine = lootFrame:CreateTexture(nil, "ARTWORK")
acLine:SetTexture("Interface\\Buttons\\WHITE8x8")
acLine:SetHeight(2)
acLine:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 5, -5)
acLine:SetPoint("TOPRIGHT", lootFrame, "TOPRIGHT", -5, -5)
acLine:SetVertexColor(acc[1], acc[2], acc[3], 0.65)
-- Title bar: handles dragging the entire loot frame
local titleBar = CreateFrame("Frame", nil, lootFrame)
titleBar:SetHeight(TITLE_HEIGHT)
titleBar:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 0, 0)
titleBar:SetPoint("TOPRIGHT", lootFrame, "TOPRIGHT", 0, 0)
titleBar:SetFrameLevel(lootFrame:GetFrameLevel() + 1)
titleBar:EnableMouse(true)
titleBar:RegisterForDrag("LeftButton")
titleBar:SetScript("OnDragStart", function() lootFrame:StartMoving() end)
titleBar:SetScript("OnDragStop", function() lootFrame:StopMovingOrSizing() end)
-- Title text
local titleFS = titleBar:CreateFontString(nil, "OVERLAY")
titleFS:SetFont(Font(), 11, "OUTLINE")
titleFS:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 10, -8)
local tc = th.title or { 1, 0.88, 1 }
titleFS:SetTextColor(tc[1], tc[2], tc[3], 0.95)
titleFS:SetText("拾取")
lootFrame._title = titleFS
-- Close button
local closeBtn = CreateFrame("Button", nil, lootFrame)
closeBtn:SetWidth(18)
closeBtn:SetHeight(18)
closeBtn:SetPoint("TOPRIGHT", lootFrame, "TOPRIGHT", -5, -5)
closeBtn:SetFrameLevel(lootFrame:GetFrameLevel() + 5)
closeBtn:RegisterForClicks("LeftButtonUp")
closeBtn:SetBackdrop(ROUND_BACKDROP_SMALL)
local cbg = th.buttonBg or { 0.35, 0.08, 0.12, 0.85 }
local cbd = th.buttonBorder or { 0.50, 0.18, 0.22, 0.80 }
closeBtn:SetBackdropColor(cbg[1], cbg[2], cbg[3], cbg[4] or 0.85)
closeBtn:SetBackdropBorderColor(cbd[1], cbd[2], cbd[3], cbd[4] or 0.80)
local closeFS = closeBtn:CreateFontString(nil, "OVERLAY")
closeFS:SetFont(Font(), 10, "OUTLINE")
closeFS:SetPoint("CENTER", 0, 0)
closeFS:SetText("×")
closeFS:SetTextColor(0.9, 0.65, 0.65, 1)
closeBtn:SetScript("OnClick", function() CloseLoot() end)
closeBtn:SetScript("OnEnter", function()
this:SetBackdropColor(0.55, 0.12, 0.15, 0.95)
this:SetBackdropBorderColor(0.9, 0.30, 0.35, 1)
closeFS:SetTextColor(1, 1, 1, 1)
end)
closeBtn:SetScript("OnLeave", function()
this:SetBackdropColor(cbg[1], cbg[2], cbg[3], cbg[4] or 0.85)
this:SetBackdropBorderColor(cbd[1], cbd[2], cbd[3], cbd[4] or 0.80)
closeFS:SetTextColor(0.9, 0.65, 0.65, 1)
end)
lootFrame:Hide()
return lootFrame
end
--------------------------------------------------------------------------------
-- Loot row
--------------------------------------------------------------------------------
local function CreateLootRow(parent, index)
local th = T()
local slotBg = th.slotBg or { 0.07, 0.06, 0.10, 0.85 }
local slotBd = th.slotBorder or { 0.18, 0.16, 0.28, 0.60 }
local hoverBd = th.slotHover or { 0.38, 0.40, 0.90 }
local acc = th.accent or { 1, 0.5, 0.8 }
local row = CreateFrame("Button", "NanamiLootRow" .. index, parent)
row:SetWidth(ROW_WIDTH)
row:SetHeight(ROW_HEIGHT)
row:SetFrameLevel(parent:GetFrameLevel() + 2)
row:RegisterForClicks("LeftButtonUp")
row:SetBackdrop(ROUND_BACKDROP_SMALL)
row:SetBackdropColor(slotBg[1], slotBg[2], slotBg[3], slotBg[4] or 0.85)
row:SetBackdropBorderColor(slotBd[1], slotBd[2], slotBd[3], slotBd[4] or 0.60)
-- Left quality accent bar
local qBar = row:CreateTexture(nil, "OVERLAY")
qBar:SetTexture("Interface\\Buttons\\WHITE8X8")
qBar:SetWidth(2)
qBar:SetPoint("TOPLEFT", row, "TOPLEFT", 3, -3)
qBar:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 3, 3)
qBar:SetVertexColor(1, 1, 1, 0.6)
row.qBar = qBar
-- Icon frame (rounded border)
local iconFrame = CreateFrame("Frame", nil, row)
iconFrame:SetWidth(ICON_SIZE + 4)
iconFrame:SetHeight(ICON_SIZE + 4)
iconFrame:SetPoint("LEFT", row, "LEFT", 7, 0)
iconFrame:SetFrameLevel(row:GetFrameLevel() + 1)
iconFrame:SetBackdrop(ICON_BACKDROP)
iconFrame:SetBackdropColor(0, 0, 0, 0.60)
iconFrame:SetBackdropBorderColor(slotBd[1], slotBd[2], slotBd[3], 0.70)
iconFrame:EnableMouse(false)
row.iconFrame = iconFrame
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 2, -2)
icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2)
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
row.icon = icon
-- Name
local nameFS = row:CreateFontString(nil, "OVERLAY")
nameFS:SetFont(Font(), 10, "OUTLINE")
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 4, 0)
nameFS:SetPoint("RIGHT", row, "RIGHT", -6, 0)
nameFS:SetJustifyH("LEFT")
row.nameFS = nameFS
-- Count (bottom-right of icon)
local countFS = row:CreateFontString(nil, "OVERLAY")
countFS:SetFont(Font(), 8, "OUTLINE")
countFS:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2)
countFS:SetJustifyH("RIGHT")
countFS:SetTextColor(1, 1, 1, 0.95)
row.countFS = countFS
row._slotBg = slotBg
row._slotBd = slotBd
row._hoverBd = hoverBd
row._acc = acc
row:EnableMouse(false)
row:Hide()
return row
end
--------------------------------------------------------------------------------
-- Update loot frame
--------------------------------------------------------------------------------
local function UpdateLootFrame()
local db = GetDB()
if not db.enable then return end
local numItems = GetNumLootItems()
if not numItems or numItems == 0 then
if lootFrame then lootFrame:Hide() end
return
end
CreateLootFrame()
local validSlots = {}
for i = 1, numItems do
local texture, itemName, quantity, quality = GetLootSlotInfo(i)
if texture then
table.insert(validSlots, i)
end
end
local numValid = table.getn(validSlots)
if numValid == 0 then
if lootFrame then lootFrame:Hide() end
return
end
local totalH = TITLE_HEIGHT + (numValid * (ROW_HEIGHT + ROW_GAP)) + 10
lootFrame:SetWidth(ROW_WIDTH + 14)
lootFrame:SetHeight(totalH)
lootFrame:SetScale(db.scale or 1.0)
while table.getn(lootRows) < numValid do
local idx = table.getn(lootRows) + 1
lootRows[idx] = CreateLootRow(lootFrame, idx)
end
for i = 1, table.getn(lootRows) do lootRows[i]:Hide() end
for displayIdx = 1, numValid do
local slotIdx = validSlots[displayIdx]
local row = lootRows[displayIdx]
if not row then break end
row:ClearAllPoints()
row:SetPoint("TOPLEFT", lootFrame, "TOPLEFT", 7, -(TITLE_HEIGHT + 2 + (displayIdx - 1) * (ROW_HEIGHT + ROW_GAP)))
row:SetWidth(ROW_WIDTH)
local texture, itemName, quantity, quality = GetLootSlotInfo(slotIdx)
row.slotIndex = slotIdx
row.icon:SetTexture(texture or "Interface\\Icons\\INV_Misc_QuestionMark")
local r, g, b = QColor(quality)
row._qualColor = { r, g, b }
row.qBar:SetVertexColor(r, g, b, 0.90)
row.iconFrame:SetBackdropBorderColor(r, g, b, 0.65)
row:SetBackdropBorderColor(r, g, b, 0.30)
if itemName then
row.nameFS:SetText("|cff" .. ColorHex(r, g, b) .. itemName .. "|r")
else
row.nameFS:SetText("")
end
if quantity and quantity > 1 then
row.countFS:SetText(tostring(quantity))
else
row.countFS:SetText("")
end
row:Show()
-- Overlay the Blizzard LootButton on top for click handling
local maxBtns = LOOTFRAME_NUMITEMS or 4
if displayIdx <= maxBtns then
local blizzBtn = _G["LootButton" .. displayIdx]
if blizzBtn then
blizzBtn:SetID(slotIdx)
blizzBtn:SetParent(lootFrame)
blizzBtn:ClearAllPoints()
blizzBtn:SetAllPoints(row)
blizzBtn:SetFrameStrata("FULLSCREEN_DIALOG")
blizzBtn:SetFrameLevel(row:GetFrameLevel() + 10)
blizzBtn:SetAlpha(0)
blizzBtn:EnableMouse(true)
blizzBtn:Show()
local rowRef = row
blizzBtn._nanamiRow = rowRef
blizzBtn:SetScript("OnEnter", function()
local rw = this._nanamiRow
if rw and rw._acc then
rw:SetBackdropBorderColor(rw._acc[1], rw._acc[2], rw._acc[3], 0.85)
rw:SetBackdropColor(rw._hoverBd[1], rw._hoverBd[2], rw._hoverBd[3], 0.35)
end
if rw and rw.slotIndex then
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
if LootSlotIsItem(rw.slotIndex) then
GameTooltip:SetLootItem(rw.slotIndex)
else
local t, n = GetLootSlotInfo(rw.slotIndex)
if n then GameTooltip:SetText(n) end
end
GameTooltip:Show()
end
end)
blizzBtn:SetScript("OnLeave", function()
local rw = this._nanamiRow
if rw and rw._slotBg then
rw:SetBackdropColor(rw._slotBg[1], rw._slotBg[2], rw._slotBg[3], rw._slotBg[4] or 0.85)
if rw._qualColor then
rw:SetBackdropBorderColor(rw._qualColor[1], rw._qualColor[2], rw._qualColor[3], 0.35)
else
rw:SetBackdropBorderColor(rw._slotBd[1], rw._slotBd[2], rw._slotBd[3], rw._slotBd[4] or 0.60)
end
end
GameTooltip:Hide()
end)
end
end
end
-- Hide unused Blizzard buttons
local maxBtns = LOOTFRAME_NUMITEMS or 4
for i = numValid + 1, maxBtns do
local blizzBtn = _G["LootButton" .. i]
if blizzBtn then blizzBtn:Hide() end
end
-- Position: use saved mover position if exists, otherwise follow cursor
if not lootFrame._posApplied then
local hasSaved = false
if SFrames.Movers and SFrames.Movers.ApplyPosition then
hasSaved = SFrames.Movers:ApplyPosition("LootFrame", lootFrame,
"TOPLEFT", "UIParent", "TOPLEFT", 50, -200)
end
if hasSaved then
lootFrame._posApplied = true
end
end
if not lootFrame._posApplied then
local cx, cy = GetCursorPosition()
local uiS = UIParent:GetEffectiveScale()
lootFrame:ClearAllPoints()
lootFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", cx / uiS - 30, cy / uiS + 16)
end
lootFrame:Show()
end
local function CloseLootFrame()
-- Return Blizzard buttons to LootFrame
local maxBtns = LOOTFRAME_NUMITEMS or 4
for i = 1, maxBtns do
local blizzBtn = _G["LootButton" .. i]
if blizzBtn and LootFrame then
blizzBtn:SetParent(LootFrame)
blizzBtn:SetAlpha(1)
blizzBtn:Hide()
blizzBtn._nanamiRow = nil
end
end
if lootFrame then lootFrame:Hide() end
for i = 1, table.getn(lootRows) do lootRows[i]:Hide() end
end
--------------------------------------------------------------------------------
-- ▸ LOOT ALERTS (已拾取提示)
--------------------------------------------------------------------------------
local function CreateAlertAnchor()
if alertAnchor then return alertAnchor end
alertAnchor = CreateFrame("Frame", "NanamiLootAlertAnchor", UIParent)
alertAnchor:SetWidth(ALERT_WIDTH)
alertAnchor:SetHeight(ALERT_HEIGHT)
alertAnchor:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 40, 340)
alertAnchor:EnableMouse(false)
if SFrames.Movers and SFrames.Movers.ApplyPosition then
SFrames.Movers:ApplyPosition("LootAlert", alertAnchor,
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 40, 340)
end
return alertAnchor
end
local function CreateAlertFrame()
local th = T()
local idx = table.getn(alertPool) + 1
local bg = th.panelBg or { 0.06, 0.05, 0.09, 0.92 }
local bd = th.panelBorder or { 0.30, 0.25, 0.42, 0.80 }
local f = CreateFrame("Frame", "NanamiLootAlert" .. idx, UIParent)
f:SetFrameStrata("HIGH")
f:SetFrameLevel(20)
f:SetWidth(ALERT_WIDTH)
f:SetHeight(ALERT_HEIGHT)
-- Rounded backdrop matching UI theme
f:SetBackdrop(ROUND_BACKDROP_SMALL)
f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 0.92)
f:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 0.80)
-- Left quality accent bar
local qBar = f:CreateTexture(nil, "OVERLAY")
qBar:SetTexture("Interface\\Buttons\\WHITE8X8")
qBar:SetWidth(2)
qBar:SetPoint("TOPLEFT", f, "TOPLEFT", 3, -3)
qBar:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 3, 3)
qBar:SetVertexColor(1, 1, 1, 0.7)
f.qBar = qBar
-- Icon with rounded border
local iconFrame = CreateFrame("Frame", nil, f)
iconFrame:SetWidth(ALERT_ICON + 4)
iconFrame:SetHeight(ALERT_ICON + 4)
iconFrame:SetPoint("LEFT", f, "LEFT", 7, 0)
iconFrame:SetFrameLevel(f:GetFrameLevel() + 1)
iconFrame:SetBackdrop(ICON_BACKDROP)
iconFrame:SetBackdropColor(0, 0, 0, 0.55)
iconFrame:SetBackdropBorderColor(0.25, 0.22, 0.30, 0.6)
f.iconFrame = iconFrame
local icon = iconFrame:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", iconFrame, "TOPLEFT", 2, -2)
icon:SetPoint("BOTTOMRIGHT", iconFrame, "BOTTOMRIGHT", -2, 2)
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
f.icon = icon
-- Name
local nameFS = f:CreateFontString(nil, "OVERLAY")
nameFS:SetFont(Font(), 10, "OUTLINE")
nameFS:SetPoint("LEFT", iconFrame, "RIGHT", 5, 0)
nameFS:SetPoint("RIGHT", f, "RIGHT", -30, 0)
nameFS:SetJustifyH("LEFT")
f.nameFS = nameFS
-- Count (right side)
local countFS = f:CreateFontString(nil, "OVERLAY")
countFS:SetFont(Font(), 10, "OUTLINE")
countFS:SetPoint("RIGHT", f, "RIGHT", -6, 0)
countFS:SetJustifyH("RIGHT")
local dim = th.dimText or { 0.55, 0.55, 0.60 }
countFS:SetTextColor(dim[1], dim[2], dim[3], 0.95)
f.countFS = countFS
f:Hide()
table.insert(alertPool, f)
return f
end
local function GetAlertFrame()
for i = 1, table.getn(alertPool) do
if not alertPool[i]._inUse then return alertPool[i] end
end
return CreateAlertFrame()
end
local function LayoutAlerts()
CreateAlertAnchor()
for i = 1, table.getn(activeAlerts) do
local af = activeAlerts[i]
if af._fadeState ~= "fading" then
af:ClearAllPoints()
af:SetPoint("BOTTOMLEFT", alertAnchor, "BOTTOMLEFT", 0, (i - 1) * (ALERT_HEIGHT + ALERT_GAP))
end
end
end
local function RemoveAlert(frame)
frame._inUse = false
frame:SetAlpha(0)
frame:Hide()
frame:SetScript("OnUpdate", nil)
local newActive = {}
for i = 1, table.getn(activeAlerts) do
if activeAlerts[i] ~= frame then
table.insert(newActive, activeAlerts[i])
end
end
activeAlerts = newActive
LayoutAlerts()
end
local function StartAlertFade(frame, delay)
frame._fadeState = "waiting"
frame._fadeElapsed = 0
frame._fadeDelay = delay
frame:SetScript("OnUpdate", function()
this._fadeElapsed = (this._fadeElapsed or 0) + arg1
if this._fadeState == "waiting" then
if this._fadeElapsed >= this._fadeDelay then
this._fadeState = "fading"
this._fadeElapsed = 0
this._baseY = 0
for idx = 1, table.getn(activeAlerts) do
if activeAlerts[idx] == this then
this._baseY = (idx - 1) * (ALERT_HEIGHT + ALERT_GAP)
break
end
end
end
elseif this._fadeState == "fading" then
local p = this._fadeElapsed / ALERT_FADE_DUR
if p >= 1 then
RemoveAlert(this)
else
this:SetAlpha(1 - p)
this:ClearAllPoints()
this:SetPoint("BOTTOMLEFT", alertAnchor, "BOTTOMLEFT",
0, this._baseY + p * ALERT_FLOAT)
end
end
end)
end
local function PruneAlerts()
while table.getn(activeAlerts) > MAX_ALERTS do
RemoveAlert(activeAlerts[1])
end
end
local function ShowLootAlert(texture, name, quality, quantity, link)
local db = GetDB()
if not db.alertEnable then return end
for i = 1, table.getn(activeAlerts) do
local af = activeAlerts[i]
if af._itemName == name and af._fadeState == "waiting" then
af._quantity = (af._quantity or 1) + (quantity or 1)
if af._quantity > 1 then
af.countFS:SetText("x" .. af._quantity)
end
af._fadeElapsed = 0
return
end
end
PruneAlerts()
CreateAlertAnchor()
local f = GetAlertFrame()
f._inUse = true
f._itemName = name
f._quantity = quantity or 1
f._link = link
-- Set icon texture
local iconTex = texture or "Interface\\Icons\\INV_Misc_QuestionMark"
f.icon:SetTexture(iconTex)
local r, g, b = QColor(quality)
f.qBar:SetVertexColor(r, g, b, 0.90)
f.iconFrame:SetBackdropBorderColor(r, g, b, 0.60)
f.nameFS:SetText("|cff" .. ColorHex(r, g, b) .. (name or "???") .. "|r")
if f._quantity > 1 then
f.countFS:SetText("x" .. f._quantity)
else
f.countFS:SetText("")
end
-- Re-apply theme backdrop colors (pool reuse)
local th = T()
local bg = th.panelBg or { 0.06, 0.05, 0.09, 0.92 }
local bd = th.panelBorder or { 0.30, 0.25, 0.42, 0.80 }
f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 0.92)
f:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 0.80)
f:SetAlpha(1)
f:Show()
table.insert(activeAlerts, f)
LayoutAlerts()
local hold = db.alertFadeDelay or ALERT_HOLD
local stagger = table.getn(activeAlerts) * ALERT_STAGGER
StartAlertFade(f, hold + stagger)
end
--------------------------------------------------------------------------------
-- Parse CHAT_MSG_LOOT
--------------------------------------------------------------------------------
local function ParseLootMessage(msg)
if not msg then return end
local _, _, link, countStr = string.find(msg, "(|c%x+|Hitem.-%[.-%]|h|r)x(%d+)")
if not link then
_, _, link = string.find(msg, "(|c%x+|Hitem.-%[.-%]|h|r)")
end
if link then
local itemName, quality, itemId = ParseItemLink(link)
if itemName then
local count = tonumber(countStr) or 1
-- Try full link first (most reliable), then itemId, then itemString
local tex = GetItemTexture(link)
if not tex and itemId and itemId > 0 then
tex = GetItemTexture(itemId)
end
if not tex and itemId and itemId > 0 then
tex = GetItemTexture("item:" .. itemId .. ":0:0:0")
end
ShowLootAlert(tex, itemName, quality, count, link)
end
return
end
-- Money
local isMoney = false
if string.find(msg, "") or string.find(msg, "") or string.find(msg, "")
or string.find(msg, "[Cc]opper") or string.find(msg, "[Ss]ilver") or string.find(msg, "[Gg]old") then
if string.find(msg, "拾取") or string.find(msg, "获得") or string.find(msg, "loot") or string.find(msg, "receive") then
isMoney = true
end
end
if isMoney then
local cleanMsg = string.gsub(msg, "|c%x+", "")
cleanMsg = string.gsub(cleanMsg, "|r", "")
local _, _, moneyPart = string.find(cleanMsg, "[:]%s*(.+)$")
if not moneyPart then
_, _, moneyPart = string.find(cleanMsg, "loot%s+(.+)$")
end
if not moneyPart then
_, _, moneyPart = string.find(cleanMsg, "获得了?%s*(.+)$")
end
if moneyPart then
moneyPart = string.gsub(moneyPart, "[%.。]+$", "")
ShowLootAlert("Interface\\Icons\\INV_Misc_Coin_01", moneyPart, 1, 1, nil)
end
end
end
--------------------------------------------------------------------------------
-- Initialize
--------------------------------------------------------------------------------
function LD:Initialize()
local db = GetDB()
if not db.enable then return end
CreateLootFrame()
CreateAlertAnchor()
-- Apply saved positions so frames have valid coordinates for the Mover system
if SFrames.Movers and SFrames.Movers.ApplyPosition then
local applied = SFrames.Movers:ApplyPosition("LootFrame", lootFrame,
"TOPLEFT", "UIParent", "TOPLEFT", 50, -200)
if applied then lootFrame._posApplied = true end
SFrames.Movers:ApplyPosition("LootAlert", alertAnchor,
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 40, 340)
end
if SFrames.Movers and SFrames.Movers.RegisterMover then
SFrames.Movers:RegisterMover("LootFrame", lootFrame, "拾取窗口",
"TOPLEFT", "UIParent", "TOPLEFT", 50, -200)
SFrames.Movers:RegisterMover("LootAlert", alertAnchor, "已拾取提示",
"BOTTOMLEFT", "UIParent", "BOTTOMLEFT", 40, 340)
end
SFrames:RegisterEvent("LOOT_OPENED", function()
if GetDB().enable then UpdateLootFrame() end
end)
SFrames:RegisterEvent("LOOT_SLOT_CLEARED", function()
if GetDB().enable and lootFrame and lootFrame:IsShown() then
UpdateLootFrame()
end
end)
SFrames:RegisterEvent("LOOT_CLOSED", function()
CloseLootFrame()
end)
SFrames:RegisterEvent("CHAT_MSG_LOOT", function()
local playerName = UnitName("player")
if not playerName then return end
local msg = arg1 or ""
if string.find(msg, playerName) or string.find(msg, "你获得") or string.find(msg, "你拾取") or string.find(msg, "You receive") then
ParseLootMessage(msg)
end
end)
local function HideBlizzardLoot()
if LootFrame then
LootFrame:EnableMouse(false)
LootFrame:SetAlpha(0)
LootFrame:ClearAllPoints()
LootFrame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -10000, 10000)
local origShow = LootFrame.Show
LootFrame.Show = function(self)
origShow(self)
self:EnableMouse(false)
self:SetAlpha(0)
self:ClearAllPoints()
self:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -10000, 10000)
end
end
end
HideBlizzardLoot()
local lootHook = CreateFrame("Frame")
lootHook:RegisterEvent("ADDON_LOADED")
lootHook:SetScript("OnEvent", function()
if arg1 == "Blizzard_Loot" then HideBlizzardLoot() end
end)
end