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

1591 lines
59 KiB
Lua

--------------------------------------------------------------------------------
-- Nanami-UI: Inspect Panel (InspectPanel.lua)
-- Equipment-focused inspect panel with modern rounded UI
-- Talent viewing delegates to native InspectFrame
--------------------------------------------------------------------------------
SFrames.InspectPanel = {}
local IP = SFrames.InspectPanel
local ipanel, equipPage
local inspectUnit = nil
--------------------------------------------------------------------------------
-- Theme (matches CharacterPanel)
--------------------------------------------------------------------------------
local T = SFrames.ActiveTheme
local FRAME_W = 340
local FRAME_H = 380
local HEADER_H = 28
local CONTENT_TOP = -(HEADER_H + 2)
local SIDE_PAD = 8
local INNER_PAD = 4
local SLOT_SIZE = 32
local SLOT_GAP = 2
local QUALITY_COLORS = {
[0] = { 0.62, 0.62, 0.62 }, [1] = { 1, 1, 1 },
[2] = { 0.12, 1, 0 }, [3] = { 0.0, 0.44, 0.87 },
[4] = { 0.64, 0.21, 0.93 }, [5] = { 1, 0.5, 0 },
}
local SLOT_LABEL = {
HeadSlot = "头部", NeckSlot = "颈部", ShoulderSlot = "肩部",
BackSlot = "背部", ChestSlot = "胸部", ShirtSlot = "衬衣",
WristSlot = "手腕", HandsSlot = "手套", WaistSlot = "腰带",
LegsSlot = "腿部", FeetSlot = "脚部",
Finger0Slot = "戒指1", Finger1Slot = "戒指2",
Trinket0Slot = "饰品1", Trinket1Slot = "饰品2",
MainHandSlot = "主手", SecondaryHandSlot = "副手",
RangedSlot = "远程", AmmoSlot = "弹药", TabardSlot = "战袍",
}
local EQUIP_SLOTS_LEFT = {
{ id = 1, name = "HeadSlot" }, { id = 2, name = "NeckSlot" },
{ id = 3, name = "ShoulderSlot" }, { id = 15, name = "BackSlot" },
{ id = 5, name = "ChestSlot" }, { id = 4, name = "ShirtSlot" },
{ id = 19, name = "TabardSlot" }, { id = 9, name = "WristSlot" },
}
local EQUIP_SLOTS_RIGHT = {
{ id = 10, name = "HandsSlot" }, { id = 6, name = "WaistSlot" },
{ id = 7, name = "LegsSlot" }, { id = 8, name = "FeetSlot" },
{ id = 11, name = "Finger0Slot" }, { id = 12, name = "Finger1Slot" },
{ id = 13, name = "Trinket0Slot" }, { id = 14, name = "Trinket1Slot" },
}
local EQUIP_SLOTS_BOTTOM = {
{ id = 16, name = "MainHandSlot" }, { id = 17, name = "SecondaryHandSlot" },
{ id = 18, name = "RangedSlot" }, { id = 0, name = "AmmoSlot" },
}
local widgetId = 0
--------------------------------------------------------------------------------
-- Helpers
--------------------------------------------------------------------------------
local function GetFont()
if SFrames and SFrames.GetFont then return SFrames:GetFont() end
return "Fonts\\ARIALN.TTF"
end
local function SetRoundBackdrop(frame, bgColor, borderColor)
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 }
})
local bg = bgColor or T.bg
local bd = borderColor or T.border
frame:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1)
frame:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1)
end
local function SetPixelBackdrop(frame, bgColor, borderColor)
frame:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, tileSize = 0, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 }
})
if bgColor then frame:SetBackdropColor(bgColor[1], bgColor[2], bgColor[3], bgColor[4] or 1) end
if borderColor then frame:SetBackdropBorderColor(borderColor[1], borderColor[2], borderColor[3], borderColor[4] or 1) end
end
local function CreateShadow(parent, size)
local s = CreateFrame("Frame", nil, parent)
local sz = size or 4
s:SetPoint("TOPLEFT", parent, "TOPLEFT", -sz, sz)
s:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", sz, -sz)
s:SetFrameLevel(math.max(parent:GetFrameLevel() - 1, 0))
s:SetBackdrop({
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 }
})
s:SetBackdropColor(0, 0, 0, 0.6)
s:SetBackdropBorderColor(0, 0, 0, 0.45)
return s
end
local function MakeSep(parent, x1, y1, x2, y2)
local sep = parent:CreateTexture(nil, "ARTWORK")
sep:SetTexture("Interface\\Buttons\\WHITE8X8")
sep:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
sep:SetHeight(1)
sep:SetPoint("TOPLEFT", parent, "TOPLEFT", x1, y1)
sep:SetPoint("TOPRIGHT", parent, "TOPRIGHT", x2, y1)
return sep
end
local function MakeFS(parent, size, justifyH, color)
local fs = parent:CreateFontString(nil, "OVERLAY")
fs:SetFont(GetFont(), size or 11, "OUTLINE")
fs:SetJustifyH(justifyH or "LEFT")
local c = color or T.valueText
fs:SetTextColor(c[1], c[2], c[3])
return fs
end
local function NextName(p)
widgetId = widgetId + 1
return "SFramesIP" .. (p or "") .. tostring(widgetId)
end
local function GetItemQualityFromLink(link)
if not link then return nil end
local _, _, q = string.find(link, "|c(%x+)|H")
if not q then return nil end
local m = {
["ff9d9d9d"] = 0, ["ffffffff"] = 1, ["ff1eff00"] = 2,
["ff0070dd"] = 3, ["ffa335ee"] = 4, ["ffff8000"] = 5,
}
return m[q]
end
local function GetInspectUnit()
return inspectUnit or "target"
end
--------------------------------------------------------------------------------
-- Scroll helper
--------------------------------------------------------------------------------
local function CreateScrollFrame(parent, width, height)
local holder = CreateFrame("Frame", NextName("SH"), parent)
holder:SetWidth(width)
holder:SetHeight(height)
local scroll = CreateFrame("ScrollFrame", NextName("SF"), holder)
scroll:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0)
scroll:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -10, 0)
local child = CreateFrame("Frame", NextName("SC"), scroll)
child:SetWidth(width - 14)
child:SetHeight(1)
scroll:SetScrollChild(child)
local slider = CreateFrame("Slider", NextName("SB"), holder)
slider:SetWidth(6)
slider:SetPoint("TOPRIGHT", holder, "TOPRIGHT", -1, -2)
slider:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -1, 2)
slider:SetOrientation("VERTICAL")
slider:SetMinMaxValues(0, 1)
slider:SetValue(0)
SetPixelBackdrop(slider, { 0.1, 0.1, 0.12, 0.4 }, { 0.15, 0.15, 0.18, 0.3 })
local thumb = slider:CreateTexture(nil, "OVERLAY")
thumb:SetTexture("Interface\\Buttons\\WHITE8X8")
thumb:SetVertexColor(0.4, 0.4, 0.48, 0.7)
thumb:SetWidth(6)
thumb:SetHeight(28)
slider:SetThumbTexture(thumb)
slider:SetScript("OnValueChanged", function()
scroll:SetVerticalScroll(this:GetValue())
end)
scroll:EnableMouseWheel(1)
scroll:SetScript("OnMouseWheel", function()
local cur = slider:GetValue()
local step = 28
local _, mx = slider:GetMinMaxValues()
if arg1 > 0 then
slider:SetValue(math.max(cur - step, 0))
else
slider:SetValue(math.min(cur + step, mx))
end
end)
holder.scroll = scroll
holder.child = child
holder.slider = slider
holder.SetContentHeight = function(self, h)
child:SetHeight(h)
local visH = scroll:GetHeight()
local maxS = math.max(h - visH, 0)
slider:SetMinMaxValues(0, maxS)
if maxS == 0 then slider:Hide() else slider:Show() end
slider:SetValue(math.min(slider:GetValue(), maxS))
end
return holder
end
--------------------------------------------------------------------------------
-- Main Frame (no tabs, single equipment view)
--------------------------------------------------------------------------------
local function CreateMainFrame()
if ipanel then return ipanel end
local f = CreateFrame("Frame", "SFramesInspectPanel", UIParent)
f:SetWidth(FRAME_W)
f:SetHeight(FRAME_H)
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
f:SetFrameStrata("HIGH")
f:EnableMouse(true)
f:SetMovable(true)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function() this:StartMoving() end)
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
f:SetClampedToScreen(true)
SetRoundBackdrop(f, T.bg, T.border)
f.shadow = CreateShadow(f, 4)
f:Hide()
f:SetScript("OnHide", function()
if not IP.switchingToNative then
if ClearInspectPlayer then ClearInspectPlayer() end
inspectUnit = nil
end
IP:HideSummary()
end)
table.insert(UISpecialFrames, "SFramesInspectPanel")
f.classIcon = SFrames:CreateClassIcon(f, 16)
f.classIcon.overlay:SetPoint("TOPLEFT", f, "TOPLEFT", SIDE_PAD + 2, -6)
f.titleText = MakeFS(f, 12, "LEFT", T.gold)
f.titleText:SetPoint("LEFT", f.classIcon.overlay, "RIGHT", 4, 0)
f.subtitleText = MakeFS(f, 9, "LEFT", T.dimText)
f.subtitleText:SetPoint("LEFT", f.titleText, "RIGHT", 6, 0)
local function MakeHeaderBtn(parent, label, tooltip, offsetX, onClick)
local btn = CreateFrame("Button", nil, parent)
btn:SetWidth(46)
btn:SetHeight(16)
btn:SetPoint("TOPRIGHT", parent, "TOPRIGHT", offsetX, -6)
btn:SetFrameLevel(parent:GetFrameLevel() + 3)
SetRoundBackdrop(btn, T.btnBg, T.btnBorder)
local txt = MakeFS(btn, 9, "CENTER", T.dimText)
txt:SetPoint("CENTER", btn, "CENTER", 0, 0)
txt:SetText(label)
btn:SetScript("OnClick", onClick)
btn:SetScript("OnEnter", function()
this:SetBackdropColor(T.btnHover[1], T.btnHover[2], T.btnHover[3], T.btnHover[4])
GameTooltip:SetOwner(this, "ANCHOR_BOTTOM")
GameTooltip:AddLine(tooltip, 1, 1, 1)
GameTooltip:Show()
end)
btn:SetScript("OnLeave", function()
this:SetBackdropColor(T.btnBg[1], T.btnBg[2], T.btnBg[3], T.btnBg[4])
GameTooltip:Hide()
end)
return btn
end
MakeHeaderBtn(f, "荣誉", "打开原生荣誉面板", -77, function() IP:OpenNativeTab("honor") end)
MakeHeaderBtn(f, "天赋", "打开原生天赋面板", -27, function() IP:OpenNativeTab("talent") end)
local closeBtn = CreateFrame("Button", nil, f)
closeBtn:SetWidth(16)
closeBtn:SetHeight(16)
closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -7, -6)
closeBtn:SetFrameLevel(f:GetFrameLevel() + 3)
SetRoundBackdrop(closeBtn, T.buttonDownBg, T.btnBorder)
local closeTxt = MakeFS(closeBtn, 9, "CENTER", T.title)
closeTxt:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
closeTxt:SetText("x")
closeBtn:SetScript("OnClick", function() f:Hide() end)
closeBtn:SetScript("OnEnter", function()
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
end)
closeBtn:SetScript("OnLeave", function()
this:SetBackdropColor(T.buttonDownBg[1], T.buttonDownBg[2], T.buttonDownBg[3], T.buttonDownBg[4])
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
end)
MakeSep(f, 6, -HEADER_H, -6, -HEADER_H)
equipPage = CreateFrame("Frame", NextName("Page"), f)
equipPage:SetPoint("TOPLEFT", f, "TOPLEFT", 4, CONTENT_TOP)
equipPage:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -4, 4)
ipanel = f
return f
end
--------------------------------------------------------------------------------
-- Title
--------------------------------------------------------------------------------
function IP:UpdateTitle()
if not ipanel then return end
local unit = GetInspectUnit()
local name = UnitName(unit) or "未知"
local level = UnitLevel(unit) or "?"
local classLocal, classEn = UnitClass(unit)
classLocal = classLocal or ""
classEn = classEn or ""
local raceLocal = UnitRace(unit) or ""
local cc = SFrames.Config and SFrames.Config.colors and SFrames.Config.colors.class and SFrames.Config.colors.class[classEn]
if equipPage and equipPage.modelNameText then
if cc then
equipPage.modelNameText:SetTextColor(cc.r, cc.g, cc.b)
else
equipPage.modelNameText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
end
equipPage.modelNameText:SetText(name)
end
if equipPage and equipPage.modelGuildText then
local guildName = GetGuildInfo and GetGuildInfo(unit) or nil
if guildName and guildName ~= "" then
equipPage.modelGuildText:SetText("<" .. guildName .. ">")
else
equipPage.modelGuildText:SetText("")
end
end
local parts = {}
table.insert(parts, "Lv." .. tostring(level))
table.insert(parts, raceLocal .. classLocal)
if equipPage and equipPage.avgIlvl then
table.insert(parts, string.format("iLvl:%.1f", equipPage.avgIlvl))
end
if cc then
ipanel.titleText:SetTextColor(cc.r, cc.g, cc.b)
else
ipanel.titleText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
end
ipanel.titleText:SetText(table.concat(parts, " "))
ipanel.subtitleText:SetText("")
if ipanel.classIcon then
SFrames:SetClassIcon(ipanel.classIcon, classEn)
end
end
--------------------------------------------------------------------------------
-- Equipment Slot creation
--------------------------------------------------------------------------------
function IP:CreateEquipSlot(parent, slotID, slotName)
local frame = CreateFrame("Button", NextName("Slot"), parent)
frame:SetWidth(SLOT_SIZE)
frame:SetHeight(SLOT_SIZE)
SetRoundBackdrop(frame, T.slotBg, T.slotBorder)
local icon = frame:CreateTexture(nil, "ARTWORK")
icon:SetPoint("TOPLEFT", frame, "TOPLEFT", 4, -4)
icon:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -4, 4)
icon:SetTexCoord(0.08, 0.92, 0.08, 0.92)
frame.icon = icon
local ilvlText = frame:CreateFontString(nil, "OVERLAY")
ilvlText:SetFont(GetFont(), 8, "OUTLINE")
ilvlText:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -2, 2)
ilvlText:SetTextColor(1, 0.82, 0)
ilvlText:SetText("")
frame.ilvlText = ilvlText
local glow = frame:CreateTexture(nil, "OVERLAY")
glow:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
glow:SetBlendMode("ADD")
glow:SetAlpha(0.8)
glow:SetWidth(SLOT_SIZE * 2)
glow:SetHeight(SLOT_SIZE * 2)
glow:SetPoint("CENTER", frame, "CENTER", 0, 0)
glow:Hide()
frame.qualGlow = glow
frame.slotID = slotID
frame.slotName = slotName
local _, emptyTex = GetInventorySlotInfo(slotName)
frame.emptyTexture = emptyTex
frame:SetScript("OnEnter", function()
this:SetBackdropBorderColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4])
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
local unit = GetInspectUnit()
if GetInventoryItemLink(unit, this.slotID) then
GameTooltip:SetInventoryItem(unit, this.slotID)
else
local label = SLOT_LABEL[this.slotName] or this.slotName
GameTooltip:AddLine(label .. " - 未装备", 0.5, 0.5, 0.5)
end
GameTooltip:Show()
end)
frame:SetScript("OnLeave", function()
this:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
GameTooltip:Hide()
end)
frame:SetScript("OnClick", function()
if IsShiftKeyDown() then
local unit = GetInspectUnit()
local link = GetInventoryItemLink(unit, this.slotID)
if link and ChatFrameEditBox and ChatFrameEditBox:IsVisible() then
ChatFrameEditBox:Insert(link)
elseif link then
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage(link)
end
end
end
end)
return frame
end
--------------------------------------------------------------------------------
-- Equipment Page
--------------------------------------------------------------------------------
function IP:BuildEquipmentPage()
if not equipPage or equipPage.built then return end
equipPage.built = true
local CONTENT_W = FRAME_W - INNER_PAD * 2
local SCROLL_W = CONTENT_W - 10
local contentH = FRAME_H - HEADER_H - 2 - INNER_PAD - 4
local scrollArea = CreateScrollFrame(equipPage, CONTENT_W, contentH)
scrollArea:SetPoint("TOPLEFT", equipPage, "TOPLEFT", 0, 0)
equipPage.scrollArea = scrollArea
local child = scrollArea.child
local cw = SCROLL_W
local slotColW = SLOT_SIZE + 6
local modelW = cw - slotColW * 2 - 26
if modelW < 80 then modelW = 80 end
local modelH = 8 * (SLOT_SIZE + SLOT_GAP) - SLOT_GAP
local modelBg = CreateFrame("Frame", nil, child)
modelBg:SetWidth(modelW)
modelBg:SetHeight(modelH)
modelBg:SetPoint("TOP", child, "TOP", 0, -4)
SetRoundBackdrop(modelBg, T.modelBg, T.modelBorder)
equipPage.modelBgFrame = modelBg
local modelFrame = CreateFrame("Frame", nil, equipPage)
modelFrame:SetWidth(modelW - 8)
modelFrame:SetHeight(modelH - 24)
modelFrame:SetPoint("TOP", modelBg, "TOP", 0, -4)
modelFrame:SetFrameLevel(equipPage:GetFrameLevel() + 5)
local model = CreateFrame("PlayerModel", NextName("Model"), modelFrame)
model:SetAllPoints(modelFrame)
equipPage.model = model
equipPage.modelFrame = modelFrame
model:EnableMouse(true)
model:EnableMouseWheel(1)
model.rotating = false
model.panning = false
model.curFacing = 0.4
model.curScale = 1.0
model.posX = 0
model.posY = 0
model:SetScript("OnMouseDown", function()
if arg1 == "LeftButton" then
this.rotating = true
this.startX = GetCursorPosition()
this.startFacing = this.curFacing or 0
elseif arg1 == "RightButton" then
this.panning = true
local cx, cy = GetCursorPosition()
this.panStartX = cx
this.panStartY = cy
this.panOriginX = this.posX or 0
this.panOriginY = this.posY or 0
end
end)
model:SetScript("OnMouseUp", function()
if arg1 == "LeftButton" then
this.rotating = false
elseif arg1 == "RightButton" then
this.panning = false
end
end)
model:SetScript("OnMouseWheel", function()
local step = 0.1
local ns = (this.curScale or 1) + arg1 * step
if ns < 0.2 then ns = 0.2 end
if ns > 4.0 then ns = 4.0 end
this.curScale = ns
this:SetModelScale(ns)
end)
model:SetScript("OnUpdate", function()
if this.rotating then
local cx = GetCursorPosition()
local diff = (cx - (this.startX or cx)) * 0.01
local nf = (this.startFacing or 0) + diff
this.curFacing = nf
this:SetFacing(nf)
elseif this.panning then
local cx, cy = GetCursorPosition()
local es = this:GetEffectiveScale()
if es < 0.01 then es = 1 end
local dx = (cx - (this.panStartX or cx)) / (es * 35)
local dy = (cy - (this.panStartY or cy)) / (es * 35)
this.posX = (this.panOriginX or 0) + dx
this.posY = (this.panOriginY or 0) + dy
this:SetPosition(this.posY, 0, this.posX)
elseif this.autoRotateDir then
local speed = 1.5 * (arg1 or 0.016)
this.curFacing = (this.curFacing or 0) + speed * this.autoRotateDir
this:SetFacing(this.curFacing)
end
end)
model.ResetView = function(self)
self.curFacing = 0.4
self.curScale = 1.0
self.posX = 0
self.posY = 0
self:SetFacing(0.4)
self:SetModelScale(1.0)
self:SetPosition(0, 0, 0)
end
-- Name/guild overlay floats on top of 3D model
local nameOverlay = CreateFrame("Frame", nil, equipPage)
nameOverlay:SetWidth(modelW)
nameOverlay:SetHeight(30)
nameOverlay:SetPoint("TOP", modelBg, "TOP", 0, -4)
nameOverlay:SetFrameLevel(equipPage:GetFrameLevel() + 7)
nameOverlay:EnableMouse(false)
local modelNameText = nameOverlay:CreateFontString(nil, "OVERLAY")
modelNameText:SetFont(GetFont(), 11, "OUTLINE")
modelNameText:SetPoint("TOP", nameOverlay, "TOP", 0, -5)
modelNameText:SetJustifyH("CENTER")
modelNameText:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
equipPage.modelNameText = modelNameText
local modelGuildText = nameOverlay:CreateFontString(nil, "OVERLAY")
modelGuildText:SetFont(GetFont(), 9, "OUTLINE")
modelGuildText:SetPoint("TOP", modelNameText, "BOTTOM", 0, -1)
modelGuildText:SetJustifyH("CENTER")
modelGuildText:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
equipPage.modelGuildText = modelGuildText
-- Model toolbar: unified strip with 3 segments (matches CharacterPanel)
local tbH = 14
local segW = 24
local tbW = segW * 3 + 2
local toolbar = CreateFrame("Frame", nil, equipPage)
toolbar:SetWidth(tbW)
toolbar:SetHeight(tbH)
toolbar:SetPoint("BOTTOM", modelBg, "BOTTOM", 0, 5)
toolbar:SetFrameLevel(equipPage:GetFrameLevel() + 6)
SetPixelBackdrop(toolbar, { 0.04, 0.04, 0.06, 0.75 }, { 0.22, 0.22, 0.28, 0.5 })
equipPage.modelToolbar = toolbar
local function MakeToolBtn(parent, w, text, ox)
local btn = CreateFrame("Button", nil, parent)
btn:SetWidth(w)
btn:SetHeight(tbH - 2)
btn:SetPoint("LEFT", parent, "LEFT", ox, 0)
btn:SetFrameLevel(parent:GetFrameLevel() + 1)
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
tile = false, tileSize = 0, edgeSize = 0,
})
btn:SetBackdropColor(0, 0, 0, 0)
local fs = MakeFS(btn, 9, "CENTER", { 0.5, 0.5, 0.55 })
fs:SetPoint("CENTER", btn, "CENTER", 0, 0)
fs:SetText(text)
btn.label = fs
btn:SetScript("OnEnter", function()
this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
this.label:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
end)
btn:SetScript("OnLeave", function()
this:SetBackdropColor(0, 0, 0, 0)
this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
end)
return btn
end
local rotLeft = MakeToolBtn(toolbar, segW, "<", 1)
rotLeft:SetScript("OnMouseDown", function()
model.autoRotateDir = 1
this:SetBackdropColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4] or 0.6)
end)
rotLeft:SetScript("OnMouseUp", function()
model.autoRotateDir = nil
this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
end)
rotLeft:SetScript("OnLeave", function()
this:SetBackdropColor(0, 0, 0, 0)
this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
model.autoRotateDir = nil
end)
equipPage.rotLeft = rotLeft
local sep1 = toolbar:CreateTexture(nil, "OVERLAY")
sep1:SetTexture("Interface\\Buttons\\WHITE8X8")
sep1:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4] or 0.4)
sep1:SetWidth(1)
sep1:SetHeight(tbH - 4)
sep1:SetPoint("LEFT", toolbar, "LEFT", segW + 1, 0)
local resetBtn = MakeToolBtn(toolbar, segW, "O", segW + 1)
resetBtn:SetScript("OnClick", function() model:ResetView() end)
resetBtn:SetScript("OnEnter", function()
this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
this.label:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
GameTooltip:SetOwner(this, "ANCHOR_BOTTOM")
GameTooltip:AddLine("重置视角", 1, 1, 1)
GameTooltip:Show()
end)
resetBtn:SetScript("OnLeave", function()
this:SetBackdropColor(0, 0, 0, 0)
this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
GameTooltip:Hide()
end)
equipPage.resetBtn = resetBtn
local sep2 = toolbar:CreateTexture(nil, "OVERLAY")
sep2:SetTexture("Interface\\Buttons\\WHITE8X8")
sep2:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4] or 0.4)
sep2:SetWidth(1)
sep2:SetHeight(tbH - 4)
sep2:SetPoint("LEFT", toolbar, "LEFT", segW * 2 + 1, 0)
local rotRight = MakeToolBtn(toolbar, segW, ">", segW * 2 + 1)
rotRight:SetScript("OnMouseDown", function()
model.autoRotateDir = -1
this:SetBackdropColor(T.slotHover[1], T.slotHover[2], T.slotHover[3], T.slotHover[4] or 0.6)
end)
rotRight:SetScript("OnMouseUp", function()
model.autoRotateDir = nil
this:SetBackdropColor(T.slotBg[1], T.slotBg[2], T.slotBg[3], T.slotBg[4] or 0.5)
end)
rotRight:SetScript("OnLeave", function()
this:SetBackdropColor(0, 0, 0, 0)
this.label:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
model.autoRotateDir = nil
end)
equipPage.rotRight = rotRight
local slotY = -6
equipPage.equipSlots = {}
for idx, si in ipairs(EQUIP_SLOTS_LEFT) do
local slot = self:CreateEquipSlot(child, si.id, si.name)
slot:SetPoint("TOPLEFT", child, "TOPLEFT", 2, slotY - (idx - 1) * (SLOT_SIZE + SLOT_GAP))
equipPage.equipSlots[si.id] = slot
end
for idx, si in ipairs(EQUIP_SLOTS_RIGHT) do
local slot = self:CreateEquipSlot(child, si.id, si.name)
slot:SetPoint("TOPRIGHT", child, "TOPRIGHT", -2, slotY - (idx - 1) * (SLOT_SIZE + SLOT_GAP))
equipPage.equipSlots[si.id] = slot
end
local bottomY = slotY - table.getn(EQUIP_SLOTS_LEFT) * (SLOT_SIZE + SLOT_GAP) - 4
local bottomCount = table.getn(EQUIP_SLOTS_BOTTOM)
local totalBW = bottomCount * SLOT_SIZE + (bottomCount - 1) * (SLOT_GAP + 2)
local bsx = math.max((cw - totalBW) / 2, 4)
for idx, si in ipairs(EQUIP_SLOTS_BOTTOM) do
local slot = self:CreateEquipSlot(child, si.id, si.name)
slot:SetPoint("TOPLEFT", child, "TOPLEFT", bsx + (idx - 1) * (SLOT_SIZE + SLOT_GAP + 1), bottomY)
equipPage.equipSlots[si.id] = slot
end
local infoY = bottomY - SLOT_SIZE - 2
equipPage.totalContentH = math.abs(infoY) + 8
scrollArea:SetContentHeight(equipPage.totalContentH)
end
local ALL_EQUIP_SLOTS = nil
local function GetAllEquipSlots()
if not ALL_EQUIP_SLOTS then
ALL_EQUIP_SLOTS = {}
for _, t in ipairs({ EQUIP_SLOTS_LEFT, EQUIP_SLOTS_RIGHT, EQUIP_SLOTS_BOTTOM }) do
for _, s in ipairs(t) do table.insert(ALL_EQUIP_SLOTS, s) end
end
end
return ALL_EQUIP_SLOTS
end
function IP:UpdateEquipment()
if not equipPage or not equipPage.built then return end
local unit = GetInspectUnit()
if equipPage.model then
equipPage.model:Show()
if equipPage.modelFrame then equipPage.modelFrame:Show() end
if equipPage.modelBgFrame then equipPage.modelBgFrame:Show() end
if equipPage.modelToolbar then equipPage.modelToolbar:Show() end
equipPage.model:SetUnit(unit)
equipPage.model:SetFacing(equipPage.model.curFacing or 0.4)
equipPage.model:SetModelScale(equipPage.model.curScale or 1.0)
equipPage.model:SetPosition(equipPage.model.posY or 0, 0, equipPage.model.posX or 0)
end
local allSlots = GetAllEquipSlots()
local showIlvl = (not SFramesDB or SFramesDB.showItemLevel ~= false) and LibItem_Level
local totalIlvl, slotCount = 0, 0
local scoreSlots = { [1]=true,[2]=true,[3]=true,[5]=true,[6]=true,[7]=true,[8]=true,
[9]=true,[10]=true,[11]=true,[12]=true,[13]=true,[14]=true,[15]=true,[16]=true,[17]=true,[18]=true }
for _, si in ipairs(allSlots) do
local slot = equipPage.equipSlots[si.id]
if slot then
local tex = GetInventoryItemTexture(unit, si.id)
local link = GetInventoryItemLink(unit, si.id)
if tex then
slot.icon:SetTexture(tex)
slot.icon:SetVertexColor(1, 1, 1)
if slot.ilvlText then
if showIlvl and link then
local _, _, itemId = string.find(link, "item:(%d+)")
local ilvl = itemId and LibItem_Level[tonumber(itemId)]
slot.ilvlText:SetText(ilvl and tostring(ilvl) or "")
if ilvl and ilvl > 0 and scoreSlots[si.id] then
totalIlvl = totalIlvl + ilvl
slotCount = slotCount + 1
end
else
slot.ilvlText:SetText("")
end
end
local quality = GetItemQualityFromLink(link)
if quality and quality >= 2 and QUALITY_COLORS[quality] then
local qc = QUALITY_COLORS[quality]
slot.qualGlow:SetVertexColor(qc[1], qc[2], qc[3])
slot.qualGlow:Show()
else
slot.qualGlow:Hide()
end
else
if slot.emptyTexture then
slot.icon:SetTexture(slot.emptyTexture)
slot.icon:SetVertexColor(T.emptySlot[1], T.emptySlot[2], T.emptySlot[3], T.emptySlot[4])
else slot.icon:SetTexture(nil) end
slot:SetBackdropBorderColor(T.slotBorder[1], T.slotBorder[2], T.slotBorder[3], T.slotBorder[4])
slot.currentBorderColor = nil
slot.qualGlow:Hide()
if slot.ilvlText then slot.ilvlText:SetText("") end
end
end
end
equipPage.avgIlvl = nil
if slotCount > 0 then
equipPage.avgIlvl = totalIlvl / slotCount
end
IP:UpdateTitle()
end
--------------------------------------------------------------------------------
-- Inspect Summary Side Panel (gear stats + equipment list)
--------------------------------------------------------------------------------
local inspSummary
local SUMMARY_W = 220
local SUMMARY_H = 380
local SUMMARY_HEADER_H = 24
local SUMMARY_ROW_H = 14
local SUMMARY_SECTION_GAP = 4
local ENCHANTABLE_INSPECT_SLOTS = {
{ id = 1, name = "HeadSlot", label = "头部" },
{ id = 3, name = "ShoulderSlot", label = "肩部" },
{ id = 15, name = "BackSlot", label = "背部" },
{ id = 5, name = "ChestSlot", label = "胸部" },
{ id = 9, name = "WristSlot", label = "手腕" },
{ id = 10, name = "HandsSlot", label = "手套" },
{ id = 7, name = "LegsSlot", label = "腿部" },
{ id = 8, name = "FeetSlot", label = "脚部" },
{ id = 11, name = "Finger0Slot", label = "戒指1" },
{ id = 12, name = "Finger1Slot", label = "戒指2" },
{ id = 16, name = "MainHandSlot", label = "主手" },
{ id = 17, name = "SecondaryHandSlot", label = "副手" },
{ id = 18, name = "RangedSlot", label = "远程" },
}
local inspScanTip
local function EnsureInspTip()
if not inspScanTip then
inspScanTip = CreateFrame("GameTooltip", "SFramesIPScanTip", nil, "GameTooltipTemplate")
end
inspScanTip:SetOwner(UIParent, "ANCHOR_NONE")
return inspScanTip
end
local STAT_PATS = {
{ p = "%+(%d+)%s*力量", k = "str" },
{ p = "%+(%d+)%s*敏捷", k = "agi" },
{ p = "%+(%d+)%s*耐力", k = "sta" },
{ p = "%+(%d+)%s*智力", k = "int" },
{ p = "%+(%d+)%s*精神", k = "spi" },
{ p = "%+(%d+)%s*Strength", k = "str" },
{ p = "%+(%d+)%s*Agility", k = "agi" },
{ p = "%+(%d+)%s*Stamina", k = "sta" },
{ p = "%+(%d+)%s*Intellect", k = "int" },
{ p = "%+(%d+)%s*Spirit", k = "spi" },
{ p = "%+(%d+)%s*火焰抗性", k = "fireres" },
{ p = "%+(%d+)%s*自然抗性", k = "natureres" },
{ p = "%+(%d+)%s*冰霜抗性", k = "frostres" },
{ p = "%+(%d+)%s*暗影抗性", k = "shadowres" },
{ p = "%+(%d+)%s*奥术抗性", k = "arcaneres" },
{ p = "%+(%d+)%s*所有抗性", k = "allres" },
{ p = "%+(%d+)%s*Fire Resistance", k = "fireres" },
{ p = "%+(%d+)%s*Nature Resistance", k = "natureres" },
{ p = "%+(%d+)%s*Frost Resistance", k = "frostres" },
{ p = "%+(%d+)%s*Shadow Resistance", k = "shadowres" },
{ p = "%+(%d+)%s*Arcane Resistance", k = "arcaneres" },
{ p = "%+(%d+)%s*All Resistances", k = "allres" },
{ p = "%+(%d+)%s*攻击强度", k = "ap" },
{ p = "%+(%d+)%s*Attack Power", k = "ap" },
}
local EQUIP_PATS = {
{ p = "伤害和治疗效果.-最多(%d+)点", k = "spelldmg" },
{ p = "法术造成的治疗效果.-最多(%d+)点", k = "healing" },
{ p = "击中几率提高(%d+)%%", k = "hit" },
{ p = "暴击几率提高(%d+)%%", k = "crit" },
{ p = "法术暴击几率提高(%d+)%%", k = "spellcrit" },
{ p = "法术命中几率提高(%d+)%%", k = "spellhit" },
{ p = "每5秒恢复(%d+)点法力", k = "mp5" },
{ p = "防御技能提高(%d+)", k = "defense" },
{ p = "躲闪几率提高(%d+)%%", k = "dodge" },
{ p = "招架几率提高(%d+)%%", k = "parry" },
{ p = "格挡几率提高(%d+)%%", k = "block" },
{ p = "damage and healing done by magical spells.-by up to (%d+)", k = "spelldmg" },
{ p = "healing done by spells.-by up to (%d+)", k = "healing" },
{ p = "chance to hit by (%d+)%%", k = "hit" },
{ p = "critical strike by (%d+)%%", k = "crit" },
{ p = "spell critical chance by (%d+)%%", k = "spellcrit" },
{ p = "Restores (%d+) mana per 5 sec", k = "mp5" },
{ p = "defense skill by (%d+)", k = "defense" },
{ p = "chance to dodge.-by (%d+)%%", k = "dodge" },
{ p = "chance to parry.-by (%d+)%%", k = "parry" },
{ p = "chance to block.-by (%d+)%%", k = "block" },
}
local PROC_INSP_ENCHANTS = {
"十字军", "Crusader", "吸取生命", "Lifestealing",
"灼热武器", "Fiery Weapon", "火焰武器",
"寒冰", "Icy Chill", "邪恶武器", "Unholy Weapon",
"恶魔杀手", "Demonslaying", "无法被缴械", "Cannot be Disarmed",
}
local function IsInspEnchantLine(txt)
if string.find(txt, "%+%d") then return true end
if string.find(txt, "%d+%%") then return true end
for i = 1, table.getn(PROC_INSP_ENCHANTS) do
if string.find(txt, PROC_INSP_ENCHANTS[i]) then return true end
end
return false
end
local function ScanInspectGearStats()
local unit = GetInspectUnit()
local stats = {}
local allSlots = GetAllEquipSlots()
local tip = EnsureInspTip()
for _, si in ipairs(allSlots) do
local link = GetInventoryItemLink(unit, si.id)
if link then
tip:ClearLines()
tip:SetInventoryItem(unit, si.id)
local n = tip:NumLines()
if n and n > 0 then
for li = 1, n do
local obj = _G["SFramesIPScanTipTextLeft" .. li]
if obj then
local txt = obj:GetText()
if txt and txt ~= "" then
for _, sp in ipairs(STAT_PATS) do
local _, _, val = string.find(txt, sp.p)
if val then
stats[sp.k] = (stats[sp.k] or 0) + tonumber(val)
end
end
local r, g, b = obj:GetTextColor()
if g > 0.8 and r < 0.5 and b < 0.5 then
for _, ep in ipairs(EQUIP_PATS) do
local _, _, val = string.find(txt, ep.p)
if val then
stats[ep.k] = (stats[ep.k] or 0) + tonumber(val)
end
end
end
end
end
end
end
end
end
if stats.allres and stats.allres > 0 then
local ar = stats.allres
stats.fireres = (stats.fireres or 0) + ar
stats.natureres = (stats.natureres or 0) + ar
stats.frostres = (stats.frostres or 0) + ar
stats.shadowres = (stats.shadowres or 0) + ar
stats.arcaneres = (stats.arcaneres or 0) + ar
stats.allres = nil
end
return stats
end
local function HasAnyStats(stats)
if not stats then return false end
for k, v in pairs(stats) do
if v and v > 0 then return true end
end
return false
end
local function GetInspectEnchant(slotId)
local unit = GetInspectUnit()
local tip = EnsureInspTip()
tip:ClearLines()
tip:SetInventoryItem(unit, slotId)
local n = tip:NumLines()
if not n or n < 2 then return false, nil end
for i = 2, n do
local obj = _G["SFramesIPScanTipTextLeft" .. i]
if obj then
local txt = obj:GetText()
if txt and txt ~= "" then
local r, g, b = obj:GetTextColor()
if g > 0.8 and r < 0.5 and b < 0.5 then
local skip = false
if string.find(txt, "装备:") or string.find(txt, "装备:") or string.find(txt, "Equip:") then
skip = true
elseif string.find(txt, "使用:") or string.find(txt, "使用:") or string.find(txt, "Use:") then
skip = true
elseif string.find(txt, "击中时可能") or string.find(txt, "Chance on hit") then
skip = true
elseif string.find(txt, "^%(") then
skip = true
elseif string.find(txt, "套装:") or string.find(txt, "套装:") or string.find(txt, "Set:") then
skip = true
end
if not skip and IsInspEnchantLine(txt) then
return true, txt
end
end
end
end
end
return false, nil
end
-- Row helpers for summary panel
local function HideSummaryRows(parent)
if parent._r then
for i = 1, table.getn(parent._r) do
local r = parent._r[i]
if r and r.Hide then r:Hide() end
end
end
parent._r = {}
end
local function AddSummaryHeader(p, txt, y, clr)
local f1 = MakeFS(p, 10, "LEFT", clr or T.sectionTitle)
f1:SetPoint("TOPLEFT", p, "TOPLEFT", 4, y)
f1:SetText(txt)
table.insert(p._r, f1)
local s = p:CreateTexture(nil, "ARTWORK")
s:SetTexture("Interface\\Buttons\\WHITE8X8")
s:SetVertexColor(T.sepColor[1], T.sepColor[2], T.sepColor[3], T.sepColor[4])
s:SetHeight(1)
s:SetPoint("TOPLEFT", p, "TOPLEFT", 4, y - 13)
s:SetPoint("TOPRIGHT", p, "TOPRIGHT", -4, y - 13)
table.insert(p._r, s)
return y - 16
end
local function AddSummaryRow(p, lbl, val, y, lc, vc)
local f1 = MakeFS(p, 9, "LEFT", lc or T.labelText)
f1:SetPoint("TOPLEFT", p, "TOPLEFT", 8, y)
f1:SetText(lbl)
table.insert(p._r, f1)
local f2 = MakeFS(p, 9, "RIGHT", vc or T.valueText)
f2:SetPoint("TOPRIGHT", p, "TOPRIGHT", -12, y)
f2:SetWidth(80)
f2:SetJustifyH("RIGHT")
f2:SetText(val)
table.insert(p._r, f2)
return y - SUMMARY_ROW_H
end
local INSP_STAT_COLORS = {
str = { 0.78, 0.61, 0.43 }, agi = { 0.52, 1, 0.52 },
sta = { 0.75, 0.55, 0.25 }, int = { 0.41, 0.80, 0.94 },
spi = { 1, 1, 1 },
}
local INSP_RESIST_COLORS = {
arcaneres = { 0.95, 0.90, 0.40 }, fireres = { 1, 0.50, 0.15 },
natureres = { 0.35, 0.90, 0.25 }, frostres = { 0.45, 0.70, 1.00 },
shadowres = { 0.60, 0.35, 0.90 },
}
local INSP_PHYS_COLOR = { 0.90, 0.75, 0.55 }
local INSP_SPELL_COLOR = { 0.60, 0.80, 1.00 }
local INSP_REGEN_COLOR = { 0.40, 0.90, 0.70 }
local INSP_DEF_COLOR = { 0.35, 0.90, 0.25 }
local INSP_ENCHANTED = { 0.30, 1, 0.30 }
local INSP_NO_ENCHANT = { 1, 0.35, 0.35 }
local function BuildInspectSummary()
if inspSummary then return inspSummary end
local f = CreateFrame("Frame", "SFramesInspectSummary", UIParent)
f:SetWidth(SUMMARY_W)
f:SetHeight(SUMMARY_H)
f:SetFrameStrata("HIGH")
f:EnableMouse(true)
f:SetMovable(true)
f:RegisterForDrag("LeftButton")
f:SetScript("OnDragStart", function() this:StartMoving() end)
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
f:SetClampedToScreen(true)
SetRoundBackdrop(f, T.bg, T.border)
CreateShadow(f, 4)
f:Hide()
local hdr = MakeFS(f, 11, "LEFT", T.gold)
hdr:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -5)
hdr:SetText("装备属性")
local tabW = 52
f.tabs = {}
f.curTab = 1
local tNames = { "属性", "装备" }
for i = 1, 2 do
local btn = CreateFrame("Button", NextName("IST"), f)
btn:SetWidth(tabW)
btn:SetHeight(16)
btn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(8 + (2 - i) * (tabW + 2) + 18), -4)
btn:SetFrameLevel(f:GetFrameLevel() + 2)
SetPixelBackdrop(btn, T.btnBg, T.btnBorder)
local lbl = MakeFS(btn, 9, "CENTER", T.dimText)
lbl:SetPoint("CENTER", btn, "CENTER", 0, 0)
lbl:SetText(tNames[i])
btn.lbl = lbl
btn.idx = i
btn:SetScript("OnClick", function() IP:SetSummaryTab(this.idx) end)
btn:SetScript("OnEnter", function()
if this.idx ~= (inspSummary.curTab or 1) then
this:SetBackdropColor(T.btnHover[1], T.btnHover[2], T.btnHover[3], T.btnHover[4])
end
end)
btn:SetScript("OnLeave", function()
if this.idx ~= (inspSummary.curTab or 1) then
SetPixelBackdrop(this, T.btnBg, T.btnBorder)
end
end)
f.tabs[i] = btn
end
local closeBtn = CreateFrame("Button", nil, f)
closeBtn:SetWidth(14)
closeBtn:SetHeight(14)
closeBtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -5)
closeBtn:SetFrameLevel(f:GetFrameLevel() + 3)
SetRoundBackdrop(closeBtn, T.buttonDownBg, T.btnBorder)
local closeTxt = MakeFS(closeBtn, 8, "CENTER", T.title)
closeTxt:SetPoint("CENTER", closeBtn, "CENTER", 0, 0)
closeTxt:SetText("x")
closeBtn:SetScript("OnClick", function() f:Hide() end)
closeBtn:SetScript("OnEnter", function()
this:SetBackdropColor(T.btnHoverBg[1], T.btnHoverBg[2], T.btnHoverBg[3], T.btnHoverBg[4])
this:SetBackdropBorderColor(T.btnHoverBd[1], T.btnHoverBd[2], T.btnHoverBd[3], T.btnHoverBd[4])
end)
closeBtn:SetScript("OnLeave", function()
this:SetBackdropColor(T.buttonDownBg[1], T.buttonDownBg[2], T.buttonDownBg[3], T.buttonDownBg[4])
this:SetBackdropBorderColor(T.btnBorder[1], T.btnBorder[2], T.btnBorder[3], T.btnBorder[4])
end)
MakeSep(f, 4, -SUMMARY_HEADER_H, -4, -SUMMARY_HEADER_H)
local cH = SUMMARY_H - SUMMARY_HEADER_H - 8
local cW = SUMMARY_W - 8
f.statsScroll = CreateScrollFrame(f, cW, cH)
f.statsScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -(SUMMARY_HEADER_H + 2))
f.equipScroll = CreateScrollFrame(f, cW, cH)
f.equipScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -(SUMMARY_HEADER_H + 2))
f.equipScroll:Hide()
inspSummary = f
return f
end
function IP:SetSummaryTab(idx)
if not inspSummary then return end
inspSummary.curTab = idx
for i = 1, 2 do
local btn = inspSummary.tabs[i]
if i == idx then
SetPixelBackdrop(btn, T.tabActiveBg, T.tabActiveBorder)
btn.lbl:SetTextColor(T.gold[1], T.gold[2], T.gold[3])
else
SetPixelBackdrop(btn, T.btnBg, T.btnBorder)
btn.lbl:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3])
end
end
if idx == 1 then
inspSummary.statsScroll:Show()
inspSummary.equipScroll:Hide()
local ok, err = pcall(function() IP:BuildInspectStats() end)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444IP BuildStats err: " .. tostring(err) .. "|r")
end
else
inspSummary.statsScroll:Hide()
inspSummary.equipScroll:Show()
local ok, err = pcall(function() IP:BuildInspectEquipList() end)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444IP BuildEquip err: " .. tostring(err) .. "|r")
end
end
end
function IP:BuildInspectStats()
if not inspSummary then return end
local child = inspSummary.statsScroll.child
HideSummaryRows(child)
local y = -4
local stats = ScanInspectGearStats()
local pNames = {
{ k = "str", n = "力量" }, { k = "agi", n = "敏捷" },
{ k = "sta", n = "耐力" }, { k = "int", n = "智力" },
{ k = "spi", n = "精神" },
}
local hasP = false
for _, p in ipairs(pNames) do
if stats[p.k] and stats[p.k] > 0 then hasP = true; break end
end
if hasP then
y = AddSummaryHeader(child, "装备主属性:", y)
for _, p in ipairs(pNames) do
local v = stats[p.k] or 0
if v > 0 then
local c = INSP_STAT_COLORS[p.k] or T.valueText
y = AddSummaryRow(child, p.n, "+" .. tostring(v), y, c, c)
end
end
y = y - SUMMARY_SECTION_GAP
end
local hasPhys = (stats.ap and stats.ap > 0) or (stats.hit and stats.hit > 0) or (stats.crit and stats.crit > 0)
if hasPhys then
y = AddSummaryHeader(child, "物理:", y, INSP_PHYS_COLOR)
if stats.ap and stats.ap > 0 then
y = AddSummaryRow(child, "攻击强度", "+" .. tostring(stats.ap), y, nil, INSP_PHYS_COLOR)
end
if stats.hit and stats.hit > 0 then
y = AddSummaryRow(child, "命中", "+" .. tostring(stats.hit) .. "%", y)
end
if stats.crit and stats.crit > 0 then
y = AddSummaryRow(child, "暴击", "+" .. tostring(stats.crit) .. "%", y)
end
y = y - SUMMARY_SECTION_GAP
end
local hasSpell = (stats.spelldmg and stats.spelldmg > 0) or (stats.healing and stats.healing > 0)
or (stats.spellcrit and stats.spellcrit > 0) or (stats.spellhit and stats.spellhit > 0)
if hasSpell then
y = AddSummaryHeader(child, "法术:", y, INSP_SPELL_COLOR)
if stats.spelldmg and stats.spelldmg > 0 then
y = AddSummaryRow(child, "法术伤害", "+" .. tostring(stats.spelldmg), y, nil, INSP_SPELL_COLOR)
end
if stats.healing and stats.healing > 0 then
y = AddSummaryRow(child, "法术治疗", "+" .. tostring(stats.healing), y)
end
if stats.spellcrit and stats.spellcrit > 0 then
y = AddSummaryRow(child, "法术暴击", "+" .. tostring(stats.spellcrit) .. "%", y)
end
if stats.spellhit and stats.spellhit > 0 then
y = AddSummaryRow(child, "法术命中", "+" .. tostring(stats.spellhit) .. "%", y)
end
y = y - SUMMARY_SECTION_GAP
end
local hasDef = (stats.defense and stats.defense > 0) or (stats.dodge and stats.dodge > 0)
or (stats.parry and stats.parry > 0) or (stats.block and stats.block > 0)
if hasDef then
y = AddSummaryHeader(child, "防御:", y, INSP_DEF_COLOR)
if stats.defense and stats.defense > 0 then
y = AddSummaryRow(child, "防御技能", "+" .. tostring(stats.defense), y, nil, INSP_DEF_COLOR)
end
if stats.dodge and stats.dodge > 0 then
y = AddSummaryRow(child, "躲闪", "+" .. tostring(stats.dodge) .. "%", y)
end
if stats.parry and stats.parry > 0 then
y = AddSummaryRow(child, "招架", "+" .. tostring(stats.parry) .. "%", y)
end
if stats.block and stats.block > 0 then
y = AddSummaryRow(child, "格挡", "+" .. tostring(stats.block) .. "%", y)
end
y = y - SUMMARY_SECTION_GAP
end
if stats.mp5 and stats.mp5 > 0 then
y = AddSummaryHeader(child, "回复:", y, INSP_REGEN_COLOR)
y = AddSummaryRow(child, "装备回蓝", tostring(stats.mp5) .. " MP/5s", y, nil, INSP_REGEN_COLOR)
y = y - SUMMARY_SECTION_GAP
end
local resInfo = {
{ k = "arcaneres", n = "奥术抗性" }, { k = "fireres", n = "火焰抗性" },
{ k = "natureres", n = "自然抗性" }, { k = "frostres", n = "冰霜抗性" },
{ k = "shadowres", n = "暗影抗性" },
}
local hasRes = false
for _, ri in ipairs(resInfo) do
if stats[ri.k] and stats[ri.k] > 0 then hasRes = true; break end
end
if hasRes then
y = AddSummaryHeader(child, "抗性:", y, INSP_RESIST_COLORS.arcaneres)
for _, ri in ipairs(resInfo) do
local v = stats[ri.k] or 0
if v > 0 then
local c = INSP_RESIST_COLORS[ri.k] or T.valueText
y = AddSummaryRow(child, ri.n, "+" .. tostring(v), y, c, c)
end
end
y = y - SUMMARY_SECTION_GAP
end
if not HasAnyStats(stats) then
y = AddSummaryHeader(child, "提示:", y, T.dimText)
local noData = MakeFS(child, 9, "LEFT", T.dimText)
noData:SetPoint("TOPLEFT", child, "TOPLEFT", 8, y)
noData:SetText("未获取到装备属性数据")
table.insert(child._r, noData)
y = y - SUMMARY_ROW_H
end
inspSummary.statsScroll:SetContentHeight(math.abs(y) + 12)
end
function IP:BuildInspectEquipList()
if not inspSummary then return end
local child = inspSummary.equipScroll.child
HideSummaryRows(child)
local y = -4
y = AddSummaryHeader(child, "装备列表 & 附魔检查:", y, T.gold)
local unit = GetInspectUnit()
local totalSlots = 0
local enchCount = 0
for si = 1, table.getn(ENCHANTABLE_INSPECT_SLOTS) do
local slot = ENCHANTABLE_INSPECT_SLOTS[si]
local link = GetInventoryItemLink(unit, slot.id)
if link then
totalSlots = totalSlots + 1
local _, _, rawName = string.find(link, "%[(.-)%]")
local itemName = rawName or slot.label
local quality = nil
local _, _, qH = string.find(link, "|c(%x+)|H")
local qMap = {
["ff9d9d9d"] = 0, ["ffffffff"] = 1, ["ff1eff00"] = 2,
["ff0070dd"] = 3, ["ffa335ee"] = 4, ["ffff8000"] = 5,
}
if qH then quality = qMap[qH] end
local nc = (quality and QUALITY_COLORS[quality]) or T.valueText
local sf = MakeFS(child, 8, "LEFT", T.dimText)
sf:SetPoint("TOPLEFT", child, "TOPLEFT", 4, y)
sf:SetText(slot.label)
sf:SetWidth(32)
table.insert(child._r, sf)
local nf = MakeFS(child, 9, "LEFT", nc)
nf:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
nf:SetWidth(120)
if string.len(itemName) > 16 then
itemName = string.sub(itemName, 1, 14) .. ".."
end
nf:SetText(itemName)
table.insert(child._r, nf)
local hasE, eTxt = false, nil
local eOk, eR1, eR2 = pcall(GetInspectEnchant, slot.id)
if eOk then hasE = eR1; eTxt = eR2 end
local ico = MakeFS(child, 9, "RIGHT")
ico:SetPoint("TOPRIGHT", child, "TOPRIGHT", -8, y)
if hasE then
ico:SetTextColor(INSP_ENCHANTED[1], INSP_ENCHANTED[2], INSP_ENCHANTED[3])
ico:SetText("*")
enchCount = enchCount + 1
else
ico:SetTextColor(INSP_NO_ENCHANT[1], INSP_NO_ENCHANT[2], INSP_NO_ENCHANT[3])
ico:SetText("-")
end
table.insert(child._r, ico)
y = y - SUMMARY_ROW_H
if hasE and eTxt then
local ef = MakeFS(child, 8, "LEFT", INSP_ENCHANTED)
ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
ef:SetWidth(145)
ef:SetText(" " .. eTxt)
table.insert(child._r, ef)
y = y - 12
elseif not hasE then
local ef = MakeFS(child, 8, "LEFT", INSP_NO_ENCHANT)
ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
ef:SetText(" 未附魔")
table.insert(child._r, ef)
y = y - 12
end
y = y - 2
else
local sf = MakeFS(child, 8, "LEFT", T.dimText)
sf:SetPoint("TOPLEFT", child, "TOPLEFT", 4, y)
sf:SetText(slot.label)
table.insert(child._r, sf)
local ef = MakeFS(child, 9, "LEFT", T.dimText)
ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y)
ef:SetText("-- 未装备 --")
table.insert(child._r, ef)
y = y - SUMMARY_ROW_H - 2
end
end
y = y - SUMMARY_SECTION_GAP
y = AddSummaryHeader(child, "附魔统计:", y, T.gold)
local sc2 = (enchCount == totalSlots and totalSlots > 0) and INSP_ENCHANTED or INSP_NO_ENCHANT
y = AddSummaryRow(child, "已附魔/总装备", enchCount .. "/" .. totalSlots, y, nil, sc2)
if enchCount < totalSlots then
y = AddSummaryRow(child, "缺少附魔", tostring(totalSlots - enchCount) .. "", y, INSP_NO_ENCHANT, INSP_NO_ENCHANT)
end
inspSummary.equipScroll:SetContentHeight(math.abs(y) + 12)
end
function IP:ShowSummary()
BuildInspectSummary()
if ipanel and ipanel:IsShown() then
inspSummary:ClearAllPoints()
inspSummary:SetPoint("TOPLEFT", ipanel, "TOPRIGHT", 2, 0)
else
inspSummary:ClearAllPoints()
inspSummary:SetPoint("CENTER", UIParent, "CENTER", 200, 0)
end
inspSummary:Show()
IP:SetSummaryTab(2)
end
function IP:HideSummary()
if inspSummary then inspSummary:Hide() end
end
function IP:RefreshSummary()
if not inspSummary or not inspSummary:IsShown() then return end
IP:SetSummaryTab(inspSummary.curTab or 1)
end
--------------------------------------------------------------------------------
-- Open native InspectFrame for a specific tab (honor / talent)
-- Turtle WoW tabs: 1=角色, 2=荣誉, 3=天赋, ...
--------------------------------------------------------------------------------
local NATIVE_TAB_INDEX = { honor = 2, talent = 3 }
function IP:OpenNativeTab(which)
local unit = inspectUnit or "target"
if not UnitExists(unit) then return end
local tabIdx = NATIVE_TAB_INDEX[which]
self.switchingToNative = true
self.allowNativeFrame = true
if ipanel then ipanel:Hide() end
self.switchingToNative = false
if not IsAddOnLoaded("Blizzard_InspectUI") then
pcall(function() LoadAddOn("Blizzard_InspectUI") end)
end
local inspF = _G["InspectFrame"]
if inspF and inspF._origShow then
inspF.Show = inspF._origShow
inspF._origShow = nil
end
if IP._origInspectUnit then
IP._origInspectUnit(unit)
end
inspF = _G["InspectFrame"]
if inspF then
inspF:SetAlpha(1)
inspF:Show()
end
if tabIdx then
local tab = _G["InspectFrameTab" .. tabIdx]
if tab and tab.Click then
tab:Click()
elseif tab and tab:GetScript("OnClick") then
tab:GetScript("OnClick")()
end
end
if not self._resetFrame then
self._resetFrame = CreateFrame("Frame", nil, UIParent)
end
self._resetFrame.elapsed = 0
self._resetFrame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0)
if this.elapsed > 1.0 then
IP.allowNativeFrame = false
this:SetScript("OnUpdate", nil)
end
end)
end
--------------------------------------------------------------------------------
-- Open / Close / Toggle
--------------------------------------------------------------------------------
function IP:Open(unit)
inspectUnit = unit or "target"
if not UnitExists(inspectUnit) then
if SFrames and SFrames.Print then
SFrames:Print("没有有效的观察目标。")
end
return
end
if not UnitIsPlayer(inspectUnit) then
if SFrames and SFrames.Print then
SFrames:Print("只能观察其他玩家。")
end
return
end
CreateMainFrame()
self:BuildEquipmentPage()
NotifyInspect(inspectUnit)
ipanel:Show()
self:UpdateTitle()
self:UpdateEquipment()
self:ShowSummary()
end
function IP:Close()
self:HideSummary()
if ipanel then ipanel:Hide() end
if ClearInspectPlayer then ClearInspectPlayer() end
inspectUnit = nil
end
function IP:Toggle(unit)
if ipanel and ipanel:IsShown() then
self:Close()
else
self:Open(unit)
end
end
--------------------------------------------------------------------------------
-- Events & Hooking
--------------------------------------------------------------------------------
local eventFrame = CreateFrame("Frame", "SFramesIPEvents", UIParent)
eventFrame:RegisterEvent("UNIT_INVENTORY_CHANGED")
eventFrame:RegisterEvent("ADDON_LOADED")
eventFrame:SetScript("OnEvent", function()
if event == "ADDON_LOADED" then
-- Re-hook InspectUnit whenever a new addon loads (in case it replaces our hook)
if type(InspectUnit) == "function" and InspectUnit ~= IP._ourHook then
IP._origInspectUnit = InspectUnit
local hook = function(unit)
if (SFramesDB and SFramesDB.enableInspect == false) or IP.allowNativeFrame then
if IP._origInspectUnit then
IP._origInspectUnit(unit)
end
return
end
IP:Open(unit or "target")
end
IP._ourHook = hook
InspectUnit = hook
end
return
end
if event == "UNIT_INVENTORY_CHANGED" then
if not ipanel or not ipanel:IsShown() then return end
local unit = GetInspectUnit()
if arg1 and UnitIsUnit(arg1, unit) then
IP:UpdateEquipment()
IP:RefreshSummary()
end
end
end)
-- Hook UnitPopup_OnClick to catch right-click menu "Inspect"
if UnitPopup_OnClick then
local orig_UnitPopup_OnClick = UnitPopup_OnClick
UnitPopup_OnClick = function()
local button = this and this.value
if button == "INSPECT" and not (SFramesDB and SFramesDB.enableInspect == false) then
local dropdownFrame = _G[UIDROPDOWNMENU_INIT_MENU]
local unit = (dropdownFrame and dropdownFrame.unit) or "target"
IP:Open(unit)
return
end
return orig_UnitPopup_OnClick()
end
end
-- Hook InspectUnit
local function HookInspectUnit()
if type(InspectUnit) ~= "function" then return end
if InspectUnit == IP._ourHook then return end
IP._origInspectUnit = InspectUnit
local hook = function(unit)
if (SFramesDB and SFramesDB.enableInspect == false) or IP.allowNativeFrame then
if IP._origInspectUnit then
IP._origInspectUnit(unit)
end
return
end
IP:Open(unit or "target")
end
IP._ourHook = hook
InspectUnit = hook
end
-- Hook ShowUIPanel to block native InspectFrame when we don't want it
if ShowUIPanel then
local _origShowUIPanel = ShowUIPanel
ShowUIPanel = function(frame)
if frame and frame.GetName and not IP.allowNativeFrame and not (SFramesDB and SFramesDB.enableInspect == false) then
local name = frame:GetName() or ""
if string.find(name, "Inspect") then
return
end
end
return _origShowUIPanel(frame)
end
end
HookInspectUnit()