-------------------------------------------------------------------------------- -- Nanami-UI: Stat Summary & Equipment List (StatSummary.lua) -------------------------------------------------------------------------------- SFrames.StatSummary = {} local SS = SFrames.StatSummary local summaryFrame -------------------------------------------------------------------------------- -- Theme (reuse CharacterPanel colors) -------------------------------------------------------------------------------- local T = SFrames.Theme:Extend({ enchanted = { 0.30, 1, 0.30 }, noEnchant = { 1, 0.35, 0.35 }, setColor = { 1, 0.85, 0.0 }, defColor = { 0.35, 0.90, 0.25 }, physColor = { 0.90, 0.75, 0.55 }, spellColor = { 0.60, 0.80, 1.00 }, regenColor = { 0.40, 0.90, 0.70 }, statColors = { 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 }, }, resistColors = { arcane = { 0.95, 0.90, 0.40 }, fire = { 1, 0.50, 0.15 }, nature = { 0.35, 0.90, 0.25 }, frost = { 0.45, 0.70, 1.00 }, shadow = { 0.60, 0.35, 0.90 }, }, }) local PANEL_W = 220 local PANEL_H = 490 local HEADER_H = 24 local ROW_H = 14 local SECTION_GAP = 4 local SIDE_PAD = 6 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 ENCHANTABLE_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 widgetId = 0 -------------------------------------------------------------------------------- -- Helpers -------------------------------------------------------------------------------- local function GetFont() if SFrames and SFrames.GetFont then return SFrames:GetFont() end return "Fonts\\ARIALN.TTF" end local function NN(p) widgetId = widgetId + 1 return "SFramesSS" .. (p or "") .. widgetId end local function SetBD(f, bg, bd) f: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 b = bg or T.bg local d = bd or T.border f:SetBackdropColor(b[1], b[2], b[3], b[4] or 1) f:SetBackdropBorderColor(d[1], d[2], d[3], d[4] or 1) end local function SetPBD(f, bg, bd) f: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 bg then f:SetBackdropColor(bg[1], bg[2], bg[3], bg[4] or 1) end if bd then f:SetBackdropBorderColor(bd[1], bd[2], bd[3], bd[4] or 1) end end local function FS(parent, size, jh, color) local fs = parent:CreateFontString(nil, "OVERLAY") fs:SetFont(GetFont(), size or 10, "OUTLINE") fs:SetJustifyH(jh or "LEFT") local c = color or T.valueText fs:SetTextColor(c[1], c[2], c[3]) return fs end -------------------------------------------------------------------------------- -- Stat helpers -------------------------------------------------------------------------------- local function GetIBL() if AceLibrary and AceLibrary.HasInstance and AceLibrary:HasInstance("ItemBonusLib-1.0") then local ok, lib = pcall(function() return AceLibrary("ItemBonusLib-1.0") end) if ok and lib then return lib end end return nil end local function GearB(key) local lib = GetIBL() if lib and lib.GetBonus then return lib:GetBonus(key) or 0 end return 0 end local function TryAPI(names) for i = 1, table.getn(names) do local fn = _G[names[i]] if fn then local ok, val = pcall(fn) if ok and type(val) == "number" and val > 0 then return val end end end return 0 end local function TryAPIa(names, a1) for i = 1, table.getn(names) do local fn = _G[names[i]] if fn then local ok, val = pcall(fn, a1) if ok and type(val) == "number" and val > 0 then return val end end end return 0 end local function GetCS() if SFrames and SFrames.CharacterPanel and SFrames.CharacterPanel.CS then return SFrames.CharacterPanel.CS end return nil end -------------------------------------------------------------------------------- -- Scroll frame -------------------------------------------------------------------------------- local function MakeScroll(parent, w, h) local holder = CreateFrame("Frame", NN("H"), parent) holder:SetWidth(w) holder:SetHeight(h) local sf = CreateFrame("ScrollFrame", NN("S"), holder) sf:SetPoint("TOPLEFT", holder, "TOPLEFT", 0, 0) sf:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -8, 0) local child = CreateFrame("Frame", NN("C"), sf) child:SetWidth(w - 12) child:SetHeight(1) sf:SetScrollChild(child) local sb = CreateFrame("Slider", NN("B"), holder) sb:SetWidth(5) sb:SetPoint("TOPRIGHT", holder, "TOPRIGHT", -1, -2) sb:SetPoint("BOTTOMRIGHT", holder, "BOTTOMRIGHT", -1, 2) sb:SetOrientation("VERTICAL") sb:SetMinMaxValues(0, 1) sb:SetValue(0) SetPBD(sb, { 0.1, 0.1, 0.12, 0.4 }, { 0.15, 0.15, 0.18, 0.3 }) local thumb = sb:CreateTexture(nil, "OVERLAY") thumb:SetTexture("Interface\\Buttons\\WHITE8X8") thumb:SetVertexColor(0.4, 0.4, 0.48, 0.7) thumb:SetWidth(5) thumb:SetHeight(24) sb:SetThumbTexture(thumb) sb:SetScript("OnValueChanged", function() sf:SetVerticalScroll(this:GetValue()) end) holder:EnableMouseWheel(1) holder:SetScript("OnMouseWheel", function() local cur = sb:GetValue() local step = 24 local lo, mx = sb:GetMinMaxValues() if arg1 > 0 then sb:SetValue(math.max(cur - step, 0)) else sb:SetValue(math.min(cur + step, mx)) end end) holder.sf = sf holder.child = child holder.sb = sb holder.UpdateH = function(_, ch) child:SetHeight(ch) local visH = sf:GetHeight() local maxS = math.max(ch - visH, 0) sb:SetMinMaxValues(0, maxS) if maxS == 0 then sb:Hide() else sb:Show() end sb:SetValue(math.min(sb:GetValue(), maxS)) end return holder end -------------------------------------------------------------------------------- -- Enchant detection (comprehensive for vanilla 1.12 / Turtle WoW) -- -- Vanilla WoW tooltip green text types: -- 1. Enchant effects (what we detect) -- 2. "装备:" / "Equip:" item innate effects -- 3. "使用:" / "Use:" item use effects -- 4. "击中时可能:" / "Chance on hit:" proc effects -- 5. "(X) 套装:" active set bonuses -- Strategy: exclude #2-5, then positive-match enchant patterns. -- -- Covers: standard enchanting, libram/arcanum (head/legs), ZG class enchants, -- ZG/Naxx shoulder augments, armor kits, weapon chains, scopes, spurs, -- counterweights, and Turtle WoW custom enchants. -------------------------------------------------------------------------------- local scanTip local function EnsureTip() if not scanTip then scanTip = CreateFrame("GameTooltip", "SFramesSScanTip", nil, "GameTooltipTemplate") end scanTip:SetOwner(UIParent, "ANCHOR_NONE") return scanTip end local PROC_ENCHANTS = { "十字军", "Crusader", "吸取生命", "Lifestealing", "灼热武器", "Fiery Weapon", "火焰武器", "寒冰", "Icy Chill", "邪恶武器", "Unholy Weapon", "恶魔杀手", "Demonslaying", "无法被缴械", "Cannot be Disarmed", } local function IsEnchantLine(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_ENCHANTS) do if string.find(txt, PROC_ENCHANTS[i]) then return true end end return false end local function GetEnchant(slotId) local tip = EnsureTip() tip:ClearLines() tip:SetInventoryItem("player", slotId) local n = tip:NumLines() if not n or n < 2 then return false, nil end for i = 2, n do local obj = _G["SFramesSScanTipTextLeft" .. 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 IsEnchantLine(txt) then return true, txt end end end end end return false, nil end -------------------------------------------------------------------------------- -- Set bonus detection -------------------------------------------------------------------------------- local function GetSets() local tip = EnsureTip() local sets = {} local seen = {} local slots = { 1,2,3,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19 } for si = 1, table.getn(slots) do local sid = slots[si] local link = GetInventoryItemLink("player", sid) if link then tip:ClearLines() tip:SetOwner(UIParent, "ANCHOR_NONE") tip:SetInventoryItem("player", sid) local nl = tip:NumLines() if nl then for li = 1, nl do local obj = _G["SFramesSScanTipTextLeft" .. li] if obj then local txt = obj:GetText() if txt then local a, b, sn, sc, sm a, b, sn, sc, sm = string.find(txt, "^(.-)%s*%((%d+)/(%d+)%)$") if sn and sc and sm then sn = string.gsub(sn, "^%s+", "") sn = string.gsub(sn, "%s+$", "") if sn ~= "" and not seen[sn] then seen[sn] = true table.insert(sets, { name = sn, current = tonumber(sc) or 0, max = tonumber(sm) or 0, }) end end end end end end end end return sets end -------------------------------------------------------------------------------- -- Build the panel -------------------------------------------------------------------------------- local function BuildPanel() if summaryFrame then return summaryFrame end local f = CreateFrame("Frame", "SFramesStatSummary", UIParent) f:SetWidth(PANEL_W) f:SetHeight(PANEL_H) f:SetPoint("CENTER", UIParent, "CENTER", 200, 0) f:SetFrameStrata("DIALOG") f:SetFrameLevel(100) 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) SetBD(f, T.bg, T.border) f:Hide() -- Header local hdr = FS(f, 11, "LEFT", T.gold) hdr:SetPoint("TOPLEFT", f, "TOPLEFT", SIDE_PAD + 2, -6) hdr:SetText("属性总览") -- Tab buttons local tabW = 60 f.tabs = {} f.curTab = 1 local tNames = { "属性", "装备" } for i = 1, 2 do local btn = CreateFrame("Button", NN("T"), f) btn:SetWidth(tabW) btn:SetHeight(16) btn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -(SIDE_PAD + (2 - i) * (tabW + 2) + 20), -5) btn:SetFrameLevel(f:GetFrameLevel() + 2) SetPBD(btn, T.tabBg, T.tabBorder) local lbl = FS(btn, 9, "CENTER", T.tabText) lbl:SetPoint("CENTER", btn, "CENTER", 0, 0) lbl:SetText(tNames[i]) btn.lbl = lbl btn.idx = i btn:SetScript("OnClick", function() SS:SetTab(this.idx) end) f.tabs[i] = btn end -- Close local cb = CreateFrame("Button", nil, f) cb:SetWidth(14) cb:SetHeight(14) cb:SetPoint("TOPRIGHT", f, "TOPRIGHT", -5, -5) cb:SetFrameLevel(f:GetFrameLevel() + 3) SetBD(cb, T.buttonDownBg or { 0.35, 0.06, 0.06, 0.85 }, T.btnBorder or { 0.45, 0.1, 0.1, 0.6 }) local cx = FS(cb, 8, "CENTER", { 1, 0.7, 0.7 }) cx:SetPoint("CENTER", cb, "CENTER", 0, 0) cx:SetText("x") cb:SetScript("OnClick", function() f:Hide() end) -- Sep local sep = f: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", f, "TOPLEFT", 4, -HEADER_H) sep:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -HEADER_H) -- Content local cH = PANEL_H - HEADER_H - 8 local cW = PANEL_W - 8 f.statsScroll = MakeScroll(f, cW, cH) f.statsScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -(HEADER_H + 2)) f.equipScroll = MakeScroll(f, cW, cH) f.equipScroll:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -(HEADER_H + 2)) f.equipScroll:Hide() summaryFrame = f return f end -------------------------------------------------------------------------------- -- Tab switching -------------------------------------------------------------------------------- function SS:SetTab(idx) if not summaryFrame then return end summaryFrame.curTab = idx for i = 1, 2 do local btn = summaryFrame.tabs[i] if i == idx then SetPBD(btn, T.tabActiveBg, T.tabActiveBorder) btn.lbl:SetTextColor(T.tabActiveText[1], T.tabActiveText[2], T.tabActiveText[3]) else SetPBD(btn, T.tabBg, T.tabBorder) btn.lbl:SetTextColor(T.tabText[1], T.tabText[2], T.tabText[3]) end end if idx == 1 then summaryFrame.statsScroll:Show() summaryFrame.equipScroll:Hide() local ok, err = pcall(function() SS:BuildStats() end) if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444SS BuildStats err: " .. tostring(err) .. "|r") end else summaryFrame.statsScroll:Hide() summaryFrame.equipScroll:Show() local ok, err = pcall(function() SS:BuildEquip() end) if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444SS BuildEquip err: " .. tostring(err) .. "|r") end end end -------------------------------------------------------------------------------- -- Helpers for building rows -------------------------------------------------------------------------------- local function HideRows(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 AddHeader(p, txt, y, clr) local f1 = FS(p, 10, "LEFT", clr or T.sectionTitle) f1:SetPoint("TOPLEFT", p, "TOPLEFT", 4, y) f1:SetText(txt) tinsert(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) tinsert(p._r, s) return y - 16 end local function AddRow(p, lbl, val, y, lc, vc) local f1 = FS(p, 9, "LEFT", lc or T.labelText) f1:SetPoint("TOPLEFT", p, "TOPLEFT", 8, y) f1:SetText(lbl) tinsert(p._r, f1) local f2 = FS(p, 9, "RIGHT", vc or T.valueText) f2:SetPoint("TOPRIGHT", p, "TOPRIGHT", -12, y) f2:SetWidth(100) f2:SetJustifyH("RIGHT") f2:SetText(val) tinsert(p._r, f2) return y - ROW_H end -------------------------------------------------------------------------------- -- Stats page -------------------------------------------------------------------------------- function SS:BuildStats() local child = summaryFrame.statsScroll.child HideRows(child) local y = -4 -- Primary Stats y = AddHeader(child, "主属性:", y) local sn = { "力量", "敏捷", "耐力", "智力", "精神" } local sc = { T.statColors.str, T.statColors.agi, T.statColors.sta, T.statColors.int, T.statColors.spi } for i = 1, 5 do local _, eff = UnitStat("player", i) y = AddRow(child, sn[i], tostring(math.floor(eff or 0)), y, sc[i], sc[i]) end y = y - SECTION_GAP -- Defense y = AddHeader(child, "防御属性:", y, T.defColor) local hp = UnitHealthMax("player") or 0 y = AddRow(child, "生命值", tostring(hp), y, nil, T.defColor) local dodge = 0 if GetDodgeChance then dodge = GetDodgeChance() or 0 end if dodge == 0 then dodge = GearB("DODGE") end y = AddRow(child, "躲闪", string.format("%.2f%%", dodge), y) local baseA, effA = UnitArmor("player") effA = math.floor(effA or baseA or 0) y = AddRow(child, "护甲", tostring(effA), y) local lvl = UnitLevel("player") or 60 local k1 = 400 + 85 * lvl local p1 = effA / (effA + k1) * 100 if p1 > 75 then p1 = 75 end y = AddRow(child, "物理免伤同级", string.format("%.2f%%", p1), y) local k2 = 400 + 85 * (lvl + 3) local p2 = effA / (effA + k2) * 100 if p2 > 75 then p2 = 75 end y = AddRow(child, "物理免伤骷髅级", string.format("%.2f%%", p2), y) y = y - SECTION_GAP -- Resistances y = AddHeader(child, "抗性:", y, T.resistColors.arcane) local rInfo = { { 6, "奥术抗性", T.resistColors.arcane }, { 2, "火焰抗性", T.resistColors.fire }, { 3, "自然抗性", T.resistColors.nature }, { 4, "冰霜抗性", T.resistColors.frost }, { 5, "暗影抗性", T.resistColors.shadow }, } for i = 1, table.getn(rInfo) do local ri = rInfo[i] local _, tot = UnitResistance("player", ri[1]) y = AddRow(child, ri[2], tostring(math.floor(tot or 0)), y, ri[3], ri[3]) end y = y - SECTION_GAP -- Physical y = AddHeader(child, "物理:", y, T.physColor) local mb, mp, mn = UnitAttackPower("player") local mAP = math.floor((mb or 0) + (mp or 0) + (mn or 0)) local rb, rp, rn = UnitRangedAttackPower("player") local rAP = math.floor((rb or 0) + (rp or 0) + (rn or 0)) y = AddRow(child, "近战/远程攻强", mAP .. "/" .. rAP, y, nil, T.physColor) local cs = GetCS() local mHit = cs and cs.SafeGetMeleeHit and cs.SafeGetMeleeHit() or 0 if mHit == 0 then mHit = TryAPI({ "GetHitModifier", "GetMeleeHitModifier" }) if mHit == 0 then mHit = GearB("TOHIT") end end y = AddRow(child, "近战/远程命中", string.format("+%d%%/+%d%%", mHit, mHit), y) local mCrit = cs and cs.SafeGetMeleeCrit and cs.SafeGetMeleeCrit() or 0 if mCrit == 0 then mCrit = TryAPI({ "GetCritChance", "GetMeleeCritChance" }) if mCrit == 0 then mCrit = GearB("CRIT") end end local rCrit = cs and cs.SafeGetRangedCrit and cs.SafeGetRangedCrit() or 0 if rCrit == 0 then rCrit = TryAPI({ "GetRangedCritChance" }) if rCrit == 0 then rCrit = mCrit end end y = AddRow(child, "近战/远程暴击", string.format("%.2f%%/%.2f%%", mCrit, rCrit), y) y = y - SECTION_GAP -- Spell y = AddHeader(child, "法术:", y, T.spellColor) local mana = UnitManaMax("player") or 0 y = AddRow(child, "法力值", tostring(mana), y, nil, T.spellColor) local sDmg = 0 if GetSpellBonusDamage then for s = 2, 7 do local d = GetSpellBonusDamage(s) or 0 if d > sDmg then sDmg = d end end end if sDmg == 0 then local lib = GetIBL() if lib and lib.GetBonus then local baseDmg = lib:GetBonus("DMG") or 0 sDmg = baseDmg local schools = { "FIREDMG","FROSTDMG","SHADOWDMG","ARCANEDMG","NATUREDMG","HOLYDMG" } for _, sk in ipairs(schools) do local sv = baseDmg + (lib:GetBonus(sk) or 0) if sv > sDmg then sDmg = sv end end end end y = AddRow(child, "法术伤害", tostring(math.floor(sDmg)), y) local sHeal = 0 if GetSpellBonusHealing then sHeal = GetSpellBonusHealing() or 0 end if sHeal == 0 then sHeal = GearB("HEAL") end y = AddRow(child, "法术治疗", tostring(math.floor(sHeal)), y) local sCrit = cs and cs.SafeGetSpellCrit and cs.SafeGetSpellCrit() or 0 if sCrit == 0 then sCrit = TryAPIa({ "GetSpellCritChance" }, 2) if sCrit == 0 then sCrit = GearB("SPELLCRIT") end end y = AddRow(child, "法术暴击", string.format("%.2f%%", sCrit), y) local sHit = cs and cs.SafeGetSpellHit and cs.SafeGetSpellHit() or 0 if sHit == 0 then sHit = TryAPI({ "GetSpellHitModifier" }) if sHit == 0 then sHit = GearB("SPELLTOHIT") end end y = AddRow(child, "法术命中", string.format("+%d%%", sHit), y) local sHaste = GearB("SPELLHASTE") if sHaste > 0 then y = AddRow(child, "急速", string.format("+%.2f%%", sHaste), y) end local sPen = GearB("SPELLPEN") if sPen > 0 then y = AddRow(child, "法术穿透", "+" .. tostring(math.floor(sPen)), y) end y = y - SECTION_GAP -- Regen y = AddHeader(child, "回复:", y, T.regenColor) local mp5 = GearB("MANAREG") y = AddRow(child, "装备回蓝", tostring(math.floor(mp5)) .. " MP/5s", y, nil, T.regenColor) local _, spi = UnitStat("player", 5) spi = math.floor(spi or 0) local spReg = math.floor(15 + spi / 5) y = AddRow(child, "精神回蓝", spReg .. " MP/2s", y) y = y - SECTION_GAP -- Set bonuses local ok2, sets = pcall(GetSets) if ok2 and sets and table.getn(sets) > 0 then y = AddHeader(child, "套装:", y, T.setColor) for i = 1, table.getn(sets) do local s = sets[i] y = AddRow(child, s.name, "(" .. s.current .. "/" .. s.max .. ")", y, T.setColor, T.setColor) end end summaryFrame.statsScroll:UpdateH(math.abs(y) + 12) end -------------------------------------------------------------------------------- -- Equipment page -------------------------------------------------------------------------------- function SS:BuildEquip() local child = summaryFrame.equipScroll.child HideRows(child) local y = -4 y = AddHeader(child, "装备列表 & 附魔检查:", y, T.gold) local totalSlots = 0 local enchCount = 0 for si = 1, table.getn(ENCHANTABLE_SLOTS) do local slot = ENCHANTABLE_SLOTS[si] local link = GetInventoryItemLink("player", 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 = {} qMap["ff9d9d9d"] = 0 qMap["ffffffff"] = 1 qMap["ff1eff00"] = 2 qMap["ff0070dd"] = 3 qMap["ffa335ee"] = 4 qMap["ffff8000"] = 5 if qH then quality = qMap[qH] end local nc = (quality and QUALITY_COLORS[quality]) or T.valueText -- Slot label local sf = FS(child, 8, "LEFT", T.dimText) sf:SetPoint("TOPLEFT", child, "TOPLEFT", 4, y) sf:SetText(slot.label) sf:SetWidth(32) tinsert(child._r, sf) -- Item name local nf = FS(child, 9, "LEFT", nc) nf:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y) nf:SetWidth(130) if string.len(itemName) > 18 then itemName = string.sub(itemName, 1, 16) .. ".." end nf:SetText(itemName) tinsert(child._r, nf) -- Enchant check local hasE, eTxt = false, nil local eOk, eR1, eR2 = pcall(GetEnchant, slot.id) if eOk then hasE = eR1; eTxt = eR2 end local ico = FS(child, 9, "RIGHT") ico:SetPoint("TOPRIGHT", child, "TOPRIGHT", -8, y) if hasE then ico:SetTextColor(T.enchanted[1], T.enchanted[2], T.enchanted[3]) ico:SetText("*") enchCount = enchCount + 1 else ico:SetTextColor(T.noEnchant[1], T.noEnchant[2], T.noEnchant[3]) ico:SetText("-") end tinsert(child._r, ico) y = y - ROW_H if hasE and eTxt then local ef = FS(child, 8, "LEFT", T.enchanted) ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y) ef:SetWidth(155) ef:SetText(" " .. eTxt) tinsert(child._r, ef) y = y - 12 elseif not hasE then local ef = FS(child, 8, "LEFT", T.noEnchant) ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y) ef:SetText(" 未附魔") tinsert(child._r, ef) y = y - 12 end y = y - 2 else local sf = FS(child, 8, "LEFT", T.dimText) sf:SetPoint("TOPLEFT", child, "TOPLEFT", 4, y) sf:SetText(slot.label) tinsert(child._r, sf) local ef = FS(child, 9, "LEFT", T.dimText) ef:SetPoint("TOPLEFT", child, "TOPLEFT", 38, y) ef:SetText("-- 未装备 --") tinsert(child._r, ef) y = y - ROW_H - 2 end end y = y - SECTION_GAP y = AddHeader(child, "附魔统计:", y, T.gold) local sc2 = (enchCount == totalSlots) and T.enchanted or T.noEnchant y = AddRow(child, "已附魔/总装备", enchCount .. "/" .. totalSlots, y, nil, sc2) if enchCount < totalSlots then y = AddRow(child, "缺少附魔", tostring(totalSlots - enchCount) .. " 件", y, T.noEnchant, T.noEnchant) end summaryFrame.equipScroll:UpdateH(math.abs(y) + 12) end -------------------------------------------------------------------------------- -- Public API -------------------------------------------------------------------------------- function SS:Toggle() local ok, err = pcall(function() BuildPanel() if summaryFrame:IsShown() then summaryFrame:Hide() return end local cpf = _G["SFramesCharacterPanel"] if cpf and cpf:IsShown() then summaryFrame:ClearAllPoints() summaryFrame:SetPoint("TOPLEFT", cpf, "TOPRIGHT", 2, 0) else summaryFrame:ClearAllPoints() summaryFrame:SetPoint("CENTER", UIParent, "CENTER", 200, 0) end local scale = SFramesDB and SFramesDB.charPanelScale or 1.0 summaryFrame:SetScale(scale) summaryFrame:Show() SS:SetTab(summaryFrame.curTab or 1) end) if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami-UI] SS:Toggle error: " .. tostring(err) .. "|r") end end function SS:Show() local ok, err = pcall(function() BuildPanel() local cpf = _G["SFramesCharacterPanel"] if cpf and cpf:IsShown() then summaryFrame:ClearAllPoints() summaryFrame:SetPoint("TOPLEFT", cpf, "TOPRIGHT", 2, 0) end local scale = SFramesDB and SFramesDB.charPanelScale or 1.0 summaryFrame:SetScale(scale) summaryFrame:Show() SS:SetTab(summaryFrame.curTab or 1) end) if not ok then DEFAULT_CHAT_FRAME:AddMessage("|cffff4444[Nanami-UI] SS:Show error: " .. tostring(err) .. "|r") end end function SS:Hide() if summaryFrame then summaryFrame:Hide() end end function SS:IsShown() return summaryFrame and summaryFrame:IsShown() end function SS:Refresh() if not summaryFrame or not summaryFrame:IsShown() then return end SS:SetTab(summaryFrame.curTab or 1) end -------------------------------------------------------------------------------- -- Events -------------------------------------------------------------------------------- local evf = CreateFrame("Frame", "SFramesSSEv", UIParent) evf:RegisterEvent("UNIT_INVENTORY_CHANGED") evf:RegisterEvent("PLAYER_AURAS_CHANGED") evf:RegisterEvent("UNIT_ATTACK_POWER") evf:RegisterEvent("UNIT_RESISTANCES") evf:SetScript("OnEvent", function() if summaryFrame and summaryFrame:IsShown() then SS:Refresh() end end) DEFAULT_CHAT_FRAME:AddMessage("SF: Loading StatSummary.lua...")