-------------------------------------------------------------------------------- -- 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()