完成多出修改

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

816
LootDisplay.lua Normal file
View File

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