869 lines
30 KiB
Lua
869 lines
30 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- 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...")
|