修改优化等

This commit is contained in:
rucky
2026-03-18 02:01:36 +08:00
parent 2a55dd6dad
commit 923a1f9ce2
15 changed files with 1578 additions and 371 deletions

View File

@@ -110,6 +110,22 @@ end
function AFK:Build() function AFK:Build()
if self.frame then return end if self.frame then return end
local existing = getglobal("NanamiAFKScreen")
if existing then
existing:SetScript("OnUpdate", nil)
existing:SetScript("OnKeyDown", nil)
existing:SetScript("OnMouseDown", nil)
existing:EnableKeyboard(false)
existing:EnableMouse(false)
existing:Hide()
end
local existingWatcher = getglobal("NanamiAFKWatcher")
if existingWatcher then
existingWatcher:SetScript("OnUpdate", nil)
existingWatcher:Hide()
end
local f = CreateFrame("Frame", "NanamiAFKScreen", WorldFrame) local f = CreateFrame("Frame", "NanamiAFKScreen", WorldFrame)
f:SetFrameStrata("FULLSCREEN_DIALOG") f:SetFrameStrata("FULLSCREEN_DIALOG")
f:SetFrameLevel(100) f:SetFrameLevel(100)
@@ -947,7 +963,7 @@ function AFK:ScanWorldBuffs()
local matched = {} local matched = {}
if not self._buffTip then if not self._buffTip then
self._buffTip = CreateFrame("GameTooltip", "NanamiAFKBuffTip", WorldFrame, "GameTooltipTemplate") self._buffTip = getglobal("NanamiAFKBuffTip") or CreateFrame("GameTooltip", "NanamiAFKBuffTip", UIParent, "GameTooltipTemplate")
end end
-- Build texture → remaining time mapping -- Build texture → remaining time mapping
@@ -1149,7 +1165,7 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function AFK:OnUpdate(elapsed) function AFK:OnUpdate(elapsed)
if not elapsed then return end if not elapsed or not self.frame then return end
-- Fade logic -- Fade logic
if self.fadeDirection then if self.fadeDirection then
@@ -1209,6 +1225,7 @@ function AFK:OnUpdate(elapsed)
-- Particle animation -- Particle animation
local now = GetTime() local now = GetTime()
if not self.particles then return end
local sw = self.frame:GetWidth() local sw = self.frame:GetWidth()
local sh = self.frame:GetHeight() local sh = self.frame:GetHeight()
if sw < 100 then sw = 1024 end if sw < 100 then sw = 1024 end
@@ -1329,6 +1346,39 @@ function AFK:ForceHide()
UIParent:Show() UIParent:Show()
end end
function AFK:Cleanup()
if self.model and self.model.ClearModel then
pcall(function() self.model:ClearModel() end)
end
if self.frame then
self.frame:SetScript("OnUpdate", nil)
self.frame:SetScript("OnKeyDown", nil)
self.frame:SetScript("OnMouseDown", nil)
self.frame:EnableKeyboard(false)
self.frame:EnableMouse(false)
self.frame:Hide()
self.frame:SetAlpha(0)
end
local watcher = getglobal("NanamiAFKWatcher")
if watcher then
watcher:SetScript("OnUpdate", nil)
watcher:Hide()
end
if self._buffTip then
self._buffTip:Hide()
end
self.isShowing = false
self.fadeDirection = nil
self._exiting = false
self._danceWait = nil
UIParent:Show()
end
function AFK:RequestExit() function AFK:RequestExit()
if self._exiting then return end if self._exiting then return end
self:Hide() self:Hide()
@@ -1413,6 +1463,9 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
function AFK:Initialize() function AFK:Initialize()
UIParent:Show()
self:Cleanup()
self:Build() self:Build()
self._isAFK = false self._isAFK = false
self._lastActivity = GetTime() self._lastActivity = GetTime()
@@ -1421,7 +1474,9 @@ function AFK:Initialize()
AFK._lastActivity = GetTime() AFK._lastActivity = GetTime()
end end
-- Hook action bar usage if not self._hooked then
self._hooked = true
local origUseAction = UseAction local origUseAction = UseAction
UseAction = function(a1, a2, a3) UseAction = function(a1, a2, a3)
MarkActive() MarkActive()
@@ -1443,8 +1498,8 @@ function AFK:Initialize()
return origJump() return origJump()
end end
end end
end
-- Events that indicate LOCAL player activity (not other players)
local activityEvents = { local activityEvents = {
"PLAYER_STARTED_MOVING", "PLAYER_STOPPED_MOVING", "PLAYER_STARTED_MOVING", "PLAYER_STOPPED_MOVING",
"SPELLCAST_START", "SPELLCAST_STOP", "SPELLCAST_START", "SPELLCAST_STOP",
@@ -1457,17 +1512,16 @@ function AFK:Initialize()
SFrames:RegisterEvent(ev, function() AFK:ResetIdleTimer() end) SFrames:RegisterEvent(ev, function() AFK:ResetIdleTimer() end)
end end
-- Watcher frame: tracks cursor movement + checks idle time local watcher = getglobal("NanamiAFKWatcher") or CreateFrame("Frame", "NanamiAFKWatcher", UIParent)
local watcher = CreateFrame("Frame", "NanamiAFKWatcher", UIParent)
watcher._checkTimer = 0 watcher._checkTimer = 0
watcher._lastCursorX = 0 watcher._lastCursorX = 0
watcher._lastCursorY = 0 watcher._lastCursorY = 0
watcher:Show()
watcher:SetScript("OnUpdate", function() watcher:SetScript("OnUpdate", function()
this._checkTimer = (this._checkTimer or 0) + arg1 this._checkTimer = (this._checkTimer or 0) + arg1
if this._checkTimer < 1 then return end if this._checkTimer < 1 then return end
this._checkTimer = 0 this._checkTimer = 0
-- Detect mouse cursor movement (catches all mouse activity)
local cx, cy = GetCursorPosition() local cx, cy = GetCursorPosition()
if cx ~= this._lastCursorX or cy ~= this._lastCursorY then if cx ~= this._lastCursorX or cy ~= this._lastCursorY then
this._lastCursorX = cx this._lastCursorX = cx
@@ -1494,7 +1548,6 @@ function AFK:Initialize()
end end
end) end)
-- Server AFK message as secondary instant trigger
SFrames:RegisterEvent("CHAT_MSG_SYSTEM", function() SFrames:RegisterEvent("CHAT_MSG_SYSTEM", function()
AFK:OnSystemMessage(arg1) AFK:OnSystemMessage(arg1)
end) end)
@@ -1526,4 +1579,8 @@ function AFK:Initialize()
end end
end end
end) end)
SFrames:RegisterEvent("PLAYER_LEAVING_WORLD", function()
AFK:Cleanup()
end)
end end

View File

@@ -3109,7 +3109,28 @@ function CP:BuildSkillsPage()
page.skillRows = {} page.skillRows = {}
end end
function CP:UpdateSkills() do
local TRADE_HEADERS = { ["Trade Skills"] = true, ["专业技能"] = true, ["Professions"] = true,
["商业技能"] = true, ["专业"] = true }
local pendingAbandonIndex
StaticPopupDialogs = StaticPopupDialogs or {}
StaticPopupDialogs["NANAMI_ABANDON_SKILL"] = {
text = "确定要遗弃 %s 吗?\n该操作不可撤销!",
button1 = "确定",
button2 = "取消",
OnAccept = function()
if pendingAbandonIndex and AbandonSkill then
AbandonSkill(pendingAbandonIndex)
pendingAbandonIndex = nil
CP:UpdateSkills()
end
end,
OnCancel = function() pendingAbandonIndex = nil end,
timeout = 0, whileDead = true, hideOnEscape = true,
}
function CP:UpdateSkills()
local page = pages[3] local page = pages[3]
if not page or not page.built then return end if not page or not page.built then return end
local child = page.scrollArea.child local child = page.scrollArea.child
@@ -3121,13 +3142,16 @@ function CP:UpdateSkills()
local numSkills = GetNumSkillLines and GetNumSkillLines() or 0 local numSkills = GetNumSkillLines and GetNumSkillLines() or 0
local y = -6 local y = -6
local rowH, barH, headerH = 28, 7, 24 local rowH, barH, headerH = 28, 7, 24
local currentHeader = ""
local delBtnSize = 14
for i = 1, numSkills do for i = 1, numSkills do
local sn, isH, isE, sr, nt, sm, smr local sn, isH, isE, sr, nt, sm, smr, isAbandonable
if GetSkillLineInfo then sn, isH, isE, sr, nt, sm, smr = GetSkillLineInfo(i) end if GetSkillLineInfo then sn, isH, isE, sr, nt, sm, smr, isAbandonable = GetSkillLineInfo(i) end
if not sn then break end if not sn then break end
if isH then if isH then
currentHeader = sn or ""
local hf = CreateFrame("Button", nil, child) local hf = CreateFrame("Button", nil, child)
hf:SetWidth(SCROLL_W - 16) hf:SetWidth(SCROLL_W - 16)
hf:SetHeight(headerH) hf:SetHeight(headerH)
@@ -3146,6 +3170,9 @@ function CP:UpdateSkills()
table.insert(page.skillRows, { frame = hf }) table.insert(page.skillRows, { frame = hf })
y = y - headerH y = y - headerH
else else
local canAbandon = TRADE_HEADERS[currentHeader]
local rightPad = canAbandon and (delBtnSize + 6) or 0
local sf = CreateFrame("Frame", nil, child) local sf = CreateFrame("Frame", nil, child)
sf:SetWidth(SCROLL_W - 24) sf:SetWidth(SCROLL_W - 24)
sf:SetHeight(rowH) sf:SetHeight(rowH)
@@ -3156,26 +3183,62 @@ function CP:UpdateSkills()
local rt = tostring(sr or 0) local rt = tostring(sr or 0)
if smr and smr > 0 then rt = rt .. "/" .. tostring(smr) end if smr and smr > 0 then rt = rt .. "/" .. tostring(smr) end
local rfs = MakeFS(sf, 8, "RIGHT", T.dimText) local rfs = MakeFS(sf, 8, "RIGHT", T.dimText)
rfs:SetPoint("TOPRIGHT", sf, "TOPRIGHT", 0, -2) rfs:SetPoint("TOPRIGHT", sf, "TOPRIGHT", -rightPad, -2)
rfs:SetText(rt) rfs:SetText(rt)
if smr and smr > 0 then if smr and smr > 0 then
local bf = CreateFrame("Frame", nil, sf) local bf = CreateFrame("Frame", nil, sf)
bf:SetHeight(barH) bf:SetHeight(barH)
bf:SetPoint("BOTTOMLEFT", sf, "BOTTOMLEFT", 0, 2) bf:SetPoint("BOTTOMLEFT", sf, "BOTTOMLEFT", 0, 2)
bf:SetPoint("BOTTOMRIGHT", sf, "BOTTOMRIGHT", 0, 2) bf:SetPoint("BOTTOMRIGHT", sf, "BOTTOMRIGHT", -rightPad, 2)
SetPixelBackdrop(bf, T.barBg, { 0.15, 0.15, 0.18, 0.5 }) SetPixelBackdrop(bf, T.barBg, { 0.15, 0.15, 0.18, 0.5 })
local bar = bf:CreateTexture(nil, "ARTWORK") local bar = bf:CreateTexture(nil, "ARTWORK")
bar:SetTexture(SFrames:GetTexture()) bar:SetTexture(SFrames:GetTexture())
bar:SetVertexColor(0.4, 0.65, 0.85, 0.85) bar:SetVertexColor(0.4, 0.65, 0.85, 0.85)
bar:SetPoint("TOPLEFT", bf, "TOPLEFT", 1, -1) bar:SetPoint("TOPLEFT", bf, "TOPLEFT", 1, -1)
bar:SetPoint("BOTTOMLEFT", bf, "BOTTOMLEFT", 1, 1) bar:SetPoint("BOTTOMLEFT", bf, "BOTTOMLEFT", 1, 1)
bar:SetWidth(math.max((SCROLL_W - 26) * Clamp((sr or 0) / smr, 0, 1), 1)) bar:SetWidth(math.max((SCROLL_W - 26 - rightPad) * Clamp((sr or 0) / smr, 0, 1), 1))
end end
if canAbandon then
local db = CreateFrame("Button", nil, sf)
db:SetWidth(delBtnSize)
db:SetHeight(delBtnSize)
db:SetPoint("RIGHT", sf, "RIGHT", 0, 0)
db:SetFrameLevel(sf:GetFrameLevel() + 2)
SetPixelBackdrop(db, { 0.25, 0.08, 0.08, 0.7 }, { 0.5, 0.15, 0.15, 0.6 })
local ico = SFrames:CreateIcon(db, "close", 8)
ico:SetDrawLayer("OVERLAY")
ico:SetPoint("CENTER", db, "CENTER", 0, 0)
ico:SetVertexColor(0.9, 0.4, 0.4)
db.skillIndex = i
db.skillName = sn
db:SetScript("OnClick", function()
pendingAbandonIndex = this.skillIndex
if StaticPopup_Show then
StaticPopup_Show("NANAMI_ABANDON_SKILL", this.skillName)
end
end)
db:SetScript("OnEnter", function()
this:SetBackdropColor(0.45, 0.1, 0.1, 0.9)
this:SetBackdropBorderColor(0.8, 0.2, 0.2, 0.9)
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
GameTooltip:AddLine("遗弃 " .. (this.skillName or ""), 1, 0.4, 0.4)
GameTooltip:AddLine("点击遗弃该技能", 0.7, 0.7, 0.7)
GameTooltip:Show()
end)
db:SetScript("OnLeave", function()
this:SetBackdropColor(0.25, 0.08, 0.08, 0.7)
this:SetBackdropBorderColor(0.5, 0.15, 0.15, 0.6)
GameTooltip:Hide()
end)
end
table.insert(page.skillRows, { frame = sf }) table.insert(page.skillRows, { frame = sf })
y = y - rowH y = y - rowH
end end
end end
page.scrollArea:SetContentHeight(math.abs(y) + 16) page.scrollArea:SetContentHeight(math.abs(y) + 16)
end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

167
Chat.lua
View File

@@ -74,8 +74,8 @@ local DEFAULT_FILTERS = {
raid = true, raid = true,
whisper = true, whisper = true,
system = true, system = true,
loot = false, loot = true,
money = false, money = true,
} }
local AUTO_TRANSLATE_TARGET_LANG = "zh" local AUTO_TRANSLATE_TARGET_LANG = "zh"
@@ -737,6 +737,9 @@ local function GetChannelNameFromChatLine(text)
local _, _, label = string.find(text, "|Hchannel:[^|]+|h%[([^%]]+)%]|h") local _, _, label = string.find(text, "|Hchannel:[^|]+|h%[([^%]]+)%]|h")
if not label then if not label then
if string.byte(text, 1) ~= 124 and string.byte(text, 1) ~= 91 then
return nil
end
local raw = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "") local raw = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
raw = string.gsub(raw, "|r", "") raw = string.gsub(raw, "|r", "")
raw = string.gsub(raw, "^%s+", "") raw = string.gsub(raw, "^%s+", "")
@@ -1296,7 +1299,7 @@ end
local function BuildDefaultTab(id, name) local function BuildDefaultTab(id, name)
return { return {
id = id, id = id,
name = name or ("Tab" .. tostring(id)), name = name or ("标签" .. tostring(id)),
filters = CopyTable(DEFAULT_FILTERS), filters = CopyTable(DEFAULT_FILTERS),
channelFilters = {}, channelFilters = {},
translateFilters = BuildDefaultTranslateFilters(), translateFilters = BuildDefaultTranslateFilters(),
@@ -1340,11 +1343,11 @@ local function SanitizeTab(tab, fallbackId, fallbackName)
if type(tab.kind) ~= "string" then tab.kind = nil end if type(tab.kind) ~= "string" then tab.kind = nil end
if type(tab.name) ~= "string" or tab.name == "" then if type(tab.name) ~= "string" or tab.name == "" then
tab.name = fallbackName or ("Tab" .. tostring(tab.id)) tab.name = fallbackName or ("标签" .. tostring(tab.id))
else else
tab.name = Trim(tab.name) tab.name = Trim(tab.name)
if tab.name == "" then if tab.name == "" then
tab.name = fallbackName or ("Tab" .. tostring(tab.id)) tab.name = fallbackName or ("标签" .. tostring(tab.id))
end end
end end
@@ -1570,7 +1573,7 @@ local function EnsureDB()
local maxId = 0 local maxId = 0
for i = 1, table.getn(db.tabs) do for i = 1, table.getn(db.tabs) do
db.tabs[i] = SanitizeTab(db.tabs[i], i, "Tab" .. tostring(i)) db.tabs[i] = SanitizeTab(db.tabs[i], i, "标签" .. tostring(i))
if IsCombatTab(db.tabs[i]) then if IsCombatTab(db.tabs[i]) then
db.tabs[i].kind = "combat" db.tabs[i].kind = "combat"
end end
@@ -1670,6 +1673,26 @@ local function FocusPopupEdit(popup)
if eb.HighlightText then eb:HighlightText() end if eb.HighlightText then eb:HighlightText() end
end end
local function SkinPopupEditBox(popup)
local eb = GetPopupEditBox(popup)
if not eb or eb._sfSkinned then return end
eb._sfSkinned = true
local regions = { eb:GetRegions() }
for _, r in ipairs(regions) do
if r and r:GetObjectType() == "Texture" then
r:SetAlpha(0)
end
end
eb:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 12,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
eb:SetBackdropColor(0.08, 0.06, 0.1, 0.95)
eb:SetBackdropBorderColor(0.5, 0.4, 0.55, 0.8)
end
local function ResolvePopupFrame(whichKey, dialog) local function ResolvePopupFrame(whichKey, dialog)
if dialog and dialog.GetParent then if dialog and dialog.GetParent then
local parent = dialog:GetParent() local parent = dialog:GetParent()
@@ -1740,6 +1763,7 @@ local function EnsurePopupDialogs()
OnShow = function(dialog) OnShow = function(dialog)
local popup = ResolvePopupFrame("SFRAMES_CHAT_NEW_TAB", dialog) local popup = ResolvePopupFrame("SFRAMES_CHAT_NEW_TAB", dialog)
if not popup then return end if not popup then return end
SkinPopupEditBox(popup)
local suggested = "Tab" local suggested = "Tab"
if SFrames and SFrames.Chat and SFrames.Chat.GetNextTabName then if SFrames and SFrames.Chat and SFrames.Chat.GetNextTabName then
suggested = SFrames.Chat:GetNextTabName() suggested = SFrames.Chat:GetNextTabName()
@@ -1781,6 +1805,7 @@ local function EnsurePopupDialogs()
OnShow = function(dialog, data) OnShow = function(dialog, data)
local popup = ResolvePopupFrame("SFRAMES_CHAT_RENAME_TAB", dialog) local popup = ResolvePopupFrame("SFRAMES_CHAT_RENAME_TAB", dialog)
if not popup then return end if not popup then return end
SkinPopupEditBox(popup)
local idx = tonumber(data or (popup and popup.data) or (SFrames and SFrames.Chat and SFrames.Chat.pendingRenameIndex)) local idx = tonumber(data or (popup and popup.data) or (SFrames and SFrames.Chat and SFrames.Chat.pendingRenameIndex))
local name = "" local name = ""
if SFrames and SFrames.Chat then if SFrames and SFrames.Chat then
@@ -1895,7 +1920,7 @@ end
function SFrames.Chat:GetNextTabName() function SFrames.Chat:GetNextTabName()
local db = EnsureDB() local db = EnsureDB()
local id = db.nextTabId or (table.getn(db.tabs) + 1) local id = db.nextTabId or (table.getn(db.tabs) + 1)
return "Tab" .. tostring(id) return "标签" .. tostring(id)
end end
function SFrames.Chat:IsTabProtected(index) function SFrames.Chat:IsTabProtected(index)
@@ -2275,7 +2300,7 @@ function SFrames.Chat:ResetPosition()
if not SFramesDB.Positions then SFramesDB.Positions = {} end if not SFramesDB.Positions then SFramesDB.Positions = {} end
SFramesDB.Positions["ChatFrame"] = nil SFramesDB.Positions["ChatFrame"] = nil
self.frame:ClearAllPoints() self.frame:ClearAllPoints()
self.frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 30, 30) self.frame:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 0, 0)
self:SavePosition() self:SavePosition()
if SFrames and SFrames.Print then if SFrames and SFrames.Print then
SFrames:Print("Chat frame position reset.") SFrames:Print("Chat frame position reset.")
@@ -2637,12 +2662,12 @@ function SFrames.Chat:RefreshTranslateConfigFrame()
if not self.translateConfigFrame then return end if not self.translateConfigFrame then return end
if self.translateCurrentTabText then if self.translateCurrentTabText then
self.translateCurrentTabText:SetText("Current Tab: " .. self:GetConfigFrameActiveTabName()) self.translateCurrentTabText:SetText("当前标签: " .. self:GetConfigFrameActiveTabName())
end end
if self.translateChannelHint then if self.translateChannelHint then
local channels = self:GetJoinedChannels() local channels = self:GetJoinedChannels()
self.translateChannelHint:SetText("Joined Channels: " .. tostring(table.getn(channels))) self.translateChannelHint:SetText("已加入频道: " .. tostring(table.getn(channels)))
end end
if self.translateConfigControls then if self.translateConfigControls then
@@ -2690,7 +2715,7 @@ function SFrames.Chat:EnsureTranslateConfigFrame()
local title = panel:CreateFontString(nil, "OVERLAY") local title = panel:CreateFontString(nil, "OVERLAY")
title:SetFont(fontPath, 14, "OUTLINE") title:SetFont(fontPath, 14, "OUTLINE")
title:SetPoint("TOP", panel, "TOP", 0, -12) title:SetPoint("TOP", panel, "TOP", 0, -12)
title:SetText("Chat AI Translate") title:SetText("聊天 AI 翻译")
title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3]) title:SetTextColor(CFG_THEME.title[1], CFG_THEME.title[2], CFG_THEME.title[3])
local closeBtn = CreateFrame("Button", nil, panel, "UIPanelCloseButton") local closeBtn = CreateFrame("Button", nil, panel, "UIPanelCloseButton")
@@ -2701,24 +2726,24 @@ function SFrames.Chat:EnsureTranslateConfigFrame()
table.insert(controls, ctrl) table.insert(controls, ctrl)
end end
local tabSection = CreateCfgSection(panel, "Tab", 10, -36, 520, 92, fontPath) local tabSection = CreateCfgSection(panel, "标签页", 10, -36, 520, 92, fontPath)
self.translateCurrentTabText = tabSection:CreateFontString(nil, "OVERLAY") self.translateCurrentTabText = tabSection:CreateFontString(nil, "OVERLAY")
self.translateCurrentTabText:SetFont(fontPath, 11, "OUTLINE") self.translateCurrentTabText:SetFont(fontPath, 11, "OUTLINE")
self.translateCurrentTabText:SetPoint("TOPLEFT", tabSection, "TOPLEFT", 14, -28) self.translateCurrentTabText:SetPoint("TOPLEFT", tabSection, "TOPLEFT", 14, -28)
self.translateCurrentTabText:SetText("Current Tab: " .. self:GetConfigFrameActiveTabName()) self.translateCurrentTabText:SetText("当前标签: " .. self:GetConfigFrameActiveTabName())
CreateCfgButton(tabSection, "Prev", 14, -48, 92, 22, function() CreateCfgButton(tabSection, "上一个", 14, -48, 92, 22, function()
SFrames.Chat:StepTab(-1) SFrames.Chat:StepTab(-1)
SFrames.Chat:RefreshConfigFrame() SFrames.Chat:RefreshConfigFrame()
SFrames.Chat:RefreshTranslateConfigFrame() SFrames.Chat:RefreshTranslateConfigFrame()
end) end)
CreateCfgButton(tabSection, "Next", 112, -48, 92, 22, function() CreateCfgButton(tabSection, "下一个", 112, -48, 92, 22, function()
SFrames.Chat:StepTab(1) SFrames.Chat:StepTab(1)
SFrames.Chat:RefreshConfigFrame() SFrames.Chat:RefreshConfigFrame()
SFrames.Chat:RefreshTranslateConfigFrame() SFrames.Chat:RefreshTranslateConfigFrame()
end) end)
local filterSection = CreateCfgSection(panel, "Message Translate", 10, -134, 520, 126, fontPath) local filterSection = CreateCfgSection(panel, "消息翻译", 10, -134, 520, 126, fontPath)
for i = 1, table.getn(TRANSLATE_FILTER_ORDER) do for i = 1, table.getn(TRANSLATE_FILTER_ORDER) do
local key = TRANSLATE_FILTER_ORDER[i] local key = TRANSLATE_FILTER_ORDER[i]
local col = math.mod(i - 1, 2) local col = math.mod(i - 1, 2)
@@ -2739,13 +2764,13 @@ function SFrames.Chat:EnsureTranslateConfigFrame()
)) ))
end end
local channelSection = CreateCfgSection(panel, "Channel Translate", 10, -266, 520, 198, fontPath) local channelSection = CreateCfgSection(panel, "频道翻译", 10, -266, 520, 198, fontPath)
self.translateChannelChecks = {} self.translateChannelChecks = {}
self.translateChannelHint = channelSection:CreateFontString(nil, "OVERLAY") self.translateChannelHint = channelSection:CreateFontString(nil, "OVERLAY")
self.translateChannelHint:SetFont(fontPath, 10, "OUTLINE") self.translateChannelHint:SetFont(fontPath, 10, "OUTLINE")
self.translateChannelHint:SetPoint("BOTTOMLEFT", channelSection, "BOTTOMLEFT", 14, 8) self.translateChannelHint:SetPoint("BOTTOMLEFT", channelSection, "BOTTOMLEFT", 14, 8)
self.translateChannelHint:SetTextColor(0.84, 0.8, 0.86) self.translateChannelHint:SetTextColor(0.84, 0.8, 0.86)
self.translateChannelHint:SetText("Joined Channels:") self.translateChannelHint:SetText("已加入频道:")
local maxChannelChecks = 15 local maxChannelChecks = 15
for i = 1, maxChannelChecks do for i = 1, maxChannelChecks do
@@ -2813,10 +2838,10 @@ function SFrames.Chat:EnsureTranslateConfigFrame()
local tip = channelSection:CreateFontString(nil, "OVERLAY") local tip = channelSection:CreateFontString(nil, "OVERLAY")
tip:SetFont(fontPath, 10, "OUTLINE") tip:SetFont(fontPath, 10, "OUTLINE")
tip:SetPoint("TOPLEFT", channelSection, "TOPLEFT", 14, -150) tip:SetPoint("TOPLEFT", channelSection, "TOPLEFT", 14, -150)
tip:SetText("Only active receiving channels can auto-translate.") tip:SetText("仅已启用接收的频道可自动翻译")
tip:SetTextColor(0.7, 0.7, 0.74) tip:SetTextColor(0.7, 0.7, 0.74)
local close = CreateCfgButton(panel, "Close", 200, -474, 140, 26, function() local close = CreateCfgButton(panel, "关闭", 200, -474, 140, 26, function()
SFrames.Chat.translateConfigFrame:Hide() SFrames.Chat.translateConfigFrame:Hide()
end) end)
StyleCfgButton(close) StyleCfgButton(close)
@@ -3961,18 +3986,36 @@ function SFrames.Chat:EnsureConfigFrame()
do do
local hcControls = CreateCfgSection(hcPage, "硬核生存服务器专属", 0, 0, 584, 182, fontPath) local hcControls = CreateCfgSection(hcPage, "硬核生存服务器专属", 0, 0, 584, 182, fontPath)
local hcStatusText = hcControls:CreateFontString(nil, "OVERLAY")
hcStatusText:SetFont(fontPath, 10, "OUTLINE")
AddControl(CreateCfgCheck(hcControls, "全局彻底关闭硬核频道接收", 16, -30, AddControl(CreateCfgCheck(hcControls, "全局彻底关闭硬核频道接收", 16, -30,
function() return EnsureDB().hcGlobalDisable == true end, function() return EnsureDB().hcGlobalDisable == true end,
function(checked) EnsureDB().hcGlobalDisable = (checked == true) end, function(checked) EnsureDB().hcGlobalDisable = (checked == true) end,
function() SFrames.Chat:RefreshConfigFrame() end function(checked)
SendChatMessage(".hcc", "SAY")
if checked then
hcStatusText:SetText("HC Chat is now |cffff4444OFF|r")
hcStatusText:SetTextColor(1, 0.4, 0.4)
else
hcStatusText:SetText("HC Chat is now |cff44ff44ON|r")
hcStatusText:SetTextColor(0.4, 1, 0.4)
end
SFrames.Chat:RefreshConfigFrame()
end
)) ))
hcStatusText:SetPoint("TOPLEFT", hcControls, "TOPLEFT", 230, -32)
hcStatusText:SetWidth(200)
hcStatusText:SetJustifyH("LEFT")
hcStatusText:SetText("")
local hcTip = hcControls:CreateFontString(nil, "OVERLAY") local hcTip = hcControls:CreateFontString(nil, "OVERLAY")
hcTip:SetFont(fontPath, 10, "OUTLINE") hcTip:SetFont(fontPath, 10, "OUTLINE")
hcTip:SetPoint("TOPLEFT", hcControls, "TOPLEFT", 16, -56) hcTip:SetPoint("TOPLEFT", hcControls, "TOPLEFT", 16, -56)
hcTip:SetWidth(540) hcTip:SetWidth(540)
hcTip:SetJustifyH("LEFT") hcTip:SetJustifyH("LEFT")
hcTip:SetText("彻底无视HC频道的强制聊天推送。勾选后所有标签都不会收到硬核频道内容。") hcTip:SetText("彻底无视HC频道的强制聊天推送。勾选后所有标签都不会收到硬核频道内容。(即时生效)")
hcTip:SetTextColor(0.8, 0.7, 0.7) hcTip:SetTextColor(0.8, 0.7, 0.7)
AddControl(CreateCfgCheck(hcControls, "全局屏蔽玩家死亡/满级信息", 16, -86, AddControl(CreateCfgCheck(hcControls, "全局屏蔽玩家死亡/满级信息", 16, -86,
@@ -3990,7 +4033,7 @@ function SFrames.Chat:EnsureConfigFrame()
deathTip:SetTextColor(0.8, 0.7, 0.7) deathTip:SetTextColor(0.8, 0.7, 0.7)
AddControl(CreateCfgSlider(hcControls, "最低死亡通报等级", 340, -82, 210, 0, 60, 1, AddControl(CreateCfgSlider(hcControls, "最低死亡通报等级", 340, -82, 210, 0, 60, 1,
function() return EnsureDB().hcDeathLevelMin or 0 end, function() return EnsureDB().hcDeathLevelMin or 10 end,
function(v) EnsureDB().hcDeathLevelMin = v end, function(v) EnsureDB().hcDeathLevelMin = v end,
function(v) return (v == 0) and "所有击杀" or (tostring(v) .. " 级及以上") end, function(v) return (v == 0) and "所有击杀" or (tostring(v) .. " 级及以上") end,
function() SFrames.Chat:RefreshConfigFrame() end function() SFrames.Chat:RefreshConfigFrame() end
@@ -4001,12 +4044,6 @@ function SFrames.Chat:EnsureConfigFrame()
SFrames.Chat.configFrame:Hide() SFrames.Chat.configFrame:Hide()
local db = EnsureDB() local db = EnsureDB()
-- Send Hardcore specific commands on Save
if SFrames.Chat.initialHcGlobalDisable ~= nil and db.hcGlobalDisable ~= SFrames.Chat.initialHcGlobalDisable then
SendChatMessage(".hcc", "SAY")
SFrames.Chat.initialHcGlobalDisable = db.hcGlobalDisable
end
if db.hcDeathDisable then if db.hcDeathDisable then
SendChatMessage(".hcm 60", "SAY") SendChatMessage(".hcm 60", "SAY")
elseif db.hcDeathLevelMin then elseif db.hcDeathLevelMin then
@@ -4052,7 +4089,6 @@ end
function SFrames.Chat:OpenConfigFrame(pageKey) function SFrames.Chat:OpenConfigFrame(pageKey)
self:EnsureConfigFrame() self:EnsureConfigFrame()
if not self.configFrame then return end if not self.configFrame then return end
self.initialHcGlobalDisable = EnsureDB().hcGlobalDisable
self:ShowConfigPage(pageKey or self.configActivePage or "window") self:ShowConfigPage(pageKey or self.configActivePage or "window")
self:RefreshConfigFrame() self:RefreshConfigFrame()
self.configFrame:Show() self.configFrame:Show()
@@ -4245,7 +4281,7 @@ function SFrames.Chat:CreateContainer()
local f = CreateFrame("Frame", "SFramesChatContainer", UIParent) local f = CreateFrame("Frame", "SFramesChatContainer", UIParent)
f:SetWidth(DEFAULTS.width) f:SetWidth(DEFAULTS.width)
f:SetHeight(DEFAULTS.height) f:SetHeight(DEFAULTS.height)
f:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 30, 30) f:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 0, 0)
f:SetMovable(true) f:SetMovable(true)
f:EnableMouse(true) f:EnableMouse(true)
f:RegisterForDrag("LeftButton") f:RegisterForDrag("LeftButton")
@@ -4455,7 +4491,42 @@ function SFrames.Chat:CreateContainer()
hint:Hide() hint:Hide()
f.hint = hint f.hint = hint
local leftCat = SFrames:CreateIcon(f, "logo", 14) local titleBtn = CreateFrame("Button", nil, f)
titleBtn:SetPoint("TOPLEFT", f, "TOPLEFT", 4, -2)
titleBtn:SetHeight(20)
titleBtn:SetFrameStrata("HIGH")
titleBtn:SetFrameLevel(f:GetFrameLevel() + 20)
titleBtn:RegisterForClicks("LeftButtonUp")
local titleBtnThrottle = 0
titleBtn:SetScript("OnUpdate", function()
titleBtnThrottle = titleBtnThrottle + arg1
if titleBtnThrottle < 0.5 then return end
titleBtnThrottle = 0
local tw = title:GetStringWidth() or 40
this:SetWidth(tw + 28)
end)
titleBtn:SetScript("OnClick", function()
if SFrames and SFrames.ConfigUI then
SFrames.ConfigUI:OpenUI()
end
end)
titleBtn:SetScript("OnEnter", function()
title:SetTextColor(1, 0.92, 1)
if f.leftCat then f.leftCat:SetVertexColor(1, 0.92, 1, 1) end
GameTooltip:SetOwner(this, "ANCHOR_BOTTOMRIGHT")
GameTooltip:ClearLines()
GameTooltip:AddLine("Nanami UI 设置", 1, 0.84, 0.94)
GameTooltip:AddLine("点击打开主设置面板", 0.85, 0.85, 0.85)
GameTooltip:Show()
end)
titleBtn:SetScript("OnLeave", function()
title:SetTextColor(1, 0.82, 0.93)
if f.leftCat then f.leftCat:SetVertexColor(1, 0.82, 0.9, 0.8) end
GameTooltip:Hide()
end)
f.titleBtn = titleBtn
local leftCat = SFrames:CreateIcon(titleBtn, "logo", 14)
leftCat:SetDrawLayer("OVERLAY") leftCat:SetDrawLayer("OVERLAY")
leftCat:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -5) leftCat:SetPoint("TOPLEFT", f, "TOPLEFT", 8, -5)
leftCat:SetVertexColor(1, 0.82, 0.9, 0.8) leftCat:SetVertexColor(1, 0.82, 0.9, 0.8)
@@ -4476,7 +4547,7 @@ function SFrames.Chat:CreateContainer()
local tabBar = CreateFrame("Frame", nil, f) local tabBar = CreateFrame("Frame", nil, f)
tabBar:SetPoint("LEFT", title, "RIGHT", 10, -1) tabBar:SetPoint("LEFT", title, "RIGHT", 10, -1)
tabBar:SetPoint("RIGHT", configButton, "LEFT", -8, -1) tabBar:SetPoint("RIGHT", configButton, "LEFT", -28, -1)
tabBar:SetHeight(18) tabBar:SetHeight(18)
f.tabBar = tabBar f.tabBar = tabBar
@@ -4571,7 +4642,7 @@ function SFrames.Chat:CreateContainer()
hiddenConfigButton:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 14, 132) hiddenConfigButton:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 14, 132)
hiddenConfigButton:SetFrameStrata("DIALOG") hiddenConfigButton:SetFrameStrata("DIALOG")
hiddenConfigButton:SetFrameLevel(220) hiddenConfigButton:SetFrameLevel(220)
hiddenConfigButton:SetText("Chat Set") hiddenConfigButton:SetText("聊天设置")
hiddenConfigButton:SetScript("OnClick", function() hiddenConfigButton:SetScript("OnClick", function()
if SFrames and SFrames.Chat then if SFrames and SFrames.Chat then
SFrames.Chat:ToggleConfigFrame() SFrames.Chat:ToggleConfigFrame()
@@ -4580,9 +4651,9 @@ function SFrames.Chat:CreateContainer()
hiddenConfigButton:SetScript("OnEnter", function() hiddenConfigButton:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT") GameTooltip:SetOwner(this, "ANCHOR_TOPLEFT")
GameTooltip:ClearLines() GameTooltip:ClearLines()
GameTooltip:AddLine("Chat Settings", 1, 0.84, 0.94) GameTooltip:AddLine("聊天设置", 1, 0.84, 0.94)
GameTooltip:AddLine("Shown while chat UI is hidden.", 0.86, 0.86, 0.86) GameTooltip:AddLine("聊天界面隐藏时显示此按钮", 0.86, 0.86, 0.86)
GameTooltip:AddLine("Click to open Nanami chat config.", 0.86, 0.86, 0.86) GameTooltip:AddLine("点击打开 Nanami 聊天配置", 0.86, 0.86, 0.86)
GameTooltip:Show() GameTooltip:Show()
end) end)
hiddenConfigButton:SetScript("OnLeave", function() hiddenConfigButton:SetScript("OnLeave", function()
@@ -4599,7 +4670,7 @@ function SFrames.Chat:CreateContainer()
f:ClearAllPoints() f:ClearAllPoints()
f:SetPoint(saved.point, UIParent, saved.relativePoint, saved.xOfs, saved.yOfs) f:SetPoint(saved.point, UIParent, saved.relativePoint, saved.xOfs, saved.yOfs)
else else
f:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 30, 30) f:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", 0, 0)
end end
f:SetWidth(Clamp(db.width, 320, 900)) f:SetWidth(Clamp(db.width, 320, 900))
f:SetHeight(Clamp(db.height, 120, 460)) f:SetHeight(Clamp(db.height, 120, 460))
@@ -5507,8 +5578,8 @@ function SFrames.Chat:RefreshTabButtons()
if not self.addTabButton then if not self.addTabButton then
local addBtn = CreateFrame("Button", nil, self.frame.tabBar) local addBtn = CreateFrame("Button", nil, self.frame.tabBar)
addBtn:SetHeight(18) addBtn:SetHeight(20)
addBtn:SetWidth(20) addBtn:SetWidth(28)
addBtn:RegisterForClicks("LeftButtonUp") addBtn:RegisterForClicks("LeftButtonUp")
EnsureButtonSkin(addBtn) EnsureButtonSkin(addBtn)
addBtn.sfText:SetText("+") addBtn.sfText:SetText("+")
@@ -5535,8 +5606,8 @@ function SFrames.Chat:RefreshTabButtons()
if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.btnHoverBg[1], CFG_THEME.btnHoverBg[2], CFG_THEME.btnHoverBg[3], 0.96) end if this.SetBackdropColor then this:SetBackdropColor(CFG_THEME.btnHoverBg[1], CFG_THEME.btnHoverBg[2], CFG_THEME.btnHoverBg[3], 0.96) end
GameTooltip:SetOwner(this, "ANCHOR_TOP") GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:ClearLines() GameTooltip:ClearLines()
GameTooltip:AddLine("New Tab", 1, 0.84, 0.94) GameTooltip:AddLine("新建标签", 1, 0.84, 0.94)
GameTooltip:AddLine("Create chat tab", 0.85, 0.85, 0.85) GameTooltip:AddLine("创建聊天标签页", 0.85, 0.85, 0.85)
GameTooltip:Show() GameTooltip:Show()
end) end)
addBtn:SetScript("OnLeave", function() addBtn:SetScript("OnLeave", function()
@@ -5551,7 +5622,7 @@ function SFrames.Chat:RefreshTabButtons()
end end
local gap = 3 local gap = 3
local addWidth = 20 local addWidth = 28
local barWidth = self.frame.tabBar:GetWidth() or 0 local barWidth = self.frame.tabBar:GetWidth() or 0
if barWidth <= 0 then if barWidth <= 0 then
barWidth = (self.frame:GetWidth() or DEFAULTS.width) - 180 barWidth = (self.frame:GetWidth() or DEFAULTS.width) - 180
@@ -5579,7 +5650,7 @@ function SFrames.Chat:RefreshTabButtons()
btn:SetPoint("LEFT", self.frame.tabBar, "LEFT", x, 0) btn:SetPoint("LEFT", self.frame.tabBar, "LEFT", x, 0)
btn:SetWidth(buttonWidth) btn:SetWidth(buttonWidth)
if btn.sfText then if btn.sfText then
btn.sfText:SetText(ShortText(tab.name or ("Tab" .. tostring(i)), maxChars)) btn.sfText:SetText(ShortText(tab.name or ("标签" .. tostring(i)), maxChars))
end end
local idx = i local idx = i
@@ -5601,9 +5672,9 @@ function SFrames.Chat:RefreshTabButtons()
end end
GameTooltip:SetOwner(this, "ANCHOR_TOP") GameTooltip:SetOwner(this, "ANCHOR_TOP")
GameTooltip:ClearLines() GameTooltip:ClearLines()
GameTooltip:AddLine(tab.name or ("Tab" .. tostring(idx)), 1, 0.84, 0.94) GameTooltip:AddLine(tab.name or ("标签" .. tostring(idx)), 1, 0.84, 0.94)
GameTooltip:AddLine("Left: switch", 0.82, 0.82, 0.82) GameTooltip:AddLine("左键: 切换标签", 0.82, 0.82, 0.82)
GameTooltip:AddLine("Right: menu", 1, 0.68, 0.79) GameTooltip:AddLine("右键: 打开菜单", 1, 0.68, 0.79)
GameTooltip:Show() GameTooltip:Show()
end) end)
btn:SetScript("OnLeave", function() btn:SetScript("OnLeave", function()
@@ -6174,7 +6245,7 @@ function SFrames.Chat:ApplyConfig()
if self.frame.tabBar and self.frame.title and self.frame.configButton then if self.frame.tabBar and self.frame.title and self.frame.configButton then
self.frame.tabBar:ClearAllPoints() self.frame.tabBar:ClearAllPoints()
self.frame.tabBar:SetPoint("LEFT", self.frame.title, "RIGHT", 10, -1) self.frame.tabBar:SetPoint("LEFT", self.frame.title, "RIGHT", 10, -1)
self.frame.tabBar:SetPoint("RIGHT", self.frame.configButton, "LEFT", -8, -1) self.frame.tabBar:SetPoint("RIGHT", self.frame.configButton, "LEFT", -28, -1)
self.frame.tabBar:SetHeight(18) self.frame.tabBar:SetHeight(18)
end end

View File

@@ -93,6 +93,7 @@ SFrames.ClassSkillData = {
[60] = {"治疗宠物 7级", "奥术射击 8级", "扰乱射击 6级", "冰冻陷阱 3级", "摔绊 3级"}, [60] = {"治疗宠物 7级", "奥术射击 8级", "扰乱射击 6级", "冰冻陷阱 3级", "摔绊 3级"},
}, },
ROGUE = { ROGUE = {
[2] = {"潜行"},
[4] = {"背刺", "搜索"}, [4] = {"背刺", "搜索"},
[6] = {"邪恶攻击 2级", "凿击"}, [6] = {"邪恶攻击 2级", "凿击"},
[8] = {"刺骨 2级", "闪避"}, [8] = {"刺骨 2级", "闪避"},

View File

@@ -471,6 +471,7 @@ local function EnsureDB()
if SFramesDB.afkOutsideRest == nil then SFramesDB.afkOutsideRest = false end if SFramesDB.afkOutsideRest == nil then SFramesDB.afkOutsideRest = false end
if SFramesDB.trainerReminder == nil then SFramesDB.trainerReminder = true end if SFramesDB.trainerReminder == nil then SFramesDB.trainerReminder = true end
if SFramesDB.trainerCache == nil then SFramesDB.trainerCache = {} end
if SFramesDB.smoothBars == nil then SFramesDB.smoothBars = true end if SFramesDB.smoothBars == nil then SFramesDB.smoothBars = true end
if SFramesDB.mobRealHealth == nil then SFramesDB.mobRealHealth = true end if SFramesDB.mobRealHealth == nil then SFramesDB.mobRealHealth = true end
@@ -3021,6 +3022,23 @@ function SFrames.ConfigUI:BuildPersonalizePage()
if SFrames.Player and SFrames.Player.ShowTrainerReminder then if SFrames.Player and SFrames.Player.ShowTrainerReminder then
local testLevel = UnitLevel("player") local testLevel = UnitLevel("player")
if mod(testLevel, 2) ~= 0 then testLevel = testLevel + 1 end if mod(testLevel, 2) ~= 0 then testLevel = testLevel + 1 end
local _, classEn = UnitClass("player")
local cachedSkills = SFramesDB and SFramesDB.trainerCache and SFramesDB.trainerCache[classEn]
local staticSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn]
local function hasData(lv)
if cachedSkills then
if cachedSkills[lv] then return true end
if lv > 1 and cachedSkills[lv - 1] then return true end
end
if staticSkills and staticSkills[lv] then return true end
return false
end
if not hasData(testLevel) then
while testLevel <= 60 and not hasData(testLevel) do
testLevel = testLevel + 2
end
if testLevel > 60 then testLevel = 60 end
end
SFrames.Player:ShowTrainerReminder(testLevel) SFrames.Player:ShowTrainerReminder(testLevel)
end end
end) end)

View File

@@ -941,72 +941,232 @@ FTCData = {
-- Turtle WoW self-learned flight times (name-based, cross-account) -- Turtle WoW self-learned flight times (name-based, cross-account)
-- Synced from SavedVariables; shared across all accounts via addon file -- Synced from SavedVariables; shared across all accounts via addon file
-- Last sync: 2026-03-09 -- Last sync: 2026-03-16
NanamiLearnedFlights = { NanamiLearnedFlights = {
-- 铁炉堡 / 铁炉堡机场 区域 -- 铁炉堡机场, 丹莫罗
["铁炉堡机场, 丹莫罗->米奈希尔港,湿地"] = 46, ["铁炉堡机场, 丹莫罗->米奈希尔港,湿地"] = 46,
["米奈希尔港,湿地->铁炉堡机场, 丹莫罗"] = 43,
["铁炉堡机场, 丹莫罗->丹阿格拉斯, 湿地"] = 25, ["铁炉堡机场, 丹莫罗->丹阿格拉斯, 湿地"] = 25,
["丹阿格拉斯, 湿地->铁炉堡机场, 丹莫罗"] = 29,
["铁炉堡机场, 丹莫罗->铁炉堡,丹莫罗"] = 80, ["铁炉堡机场, 丹莫罗->铁炉堡,丹莫罗"] = 80,
["铁炉堡,丹莫罗->铁炉堡机场, 丹莫罗"] = 80,
["铁炉堡机场, 丹莫罗->南海镇,希尔斯布莱德"] = 138, ["铁炉堡机场, 丹莫罗->南海镇,希尔斯布莱德"] = 138,
["铁炉堡机场, 丹莫罗->暴风城,艾尔文森林"] = 237, ["铁炉堡机场, 丹莫罗->暴风城,艾尔文森林"] = 237,
["铁炉堡机场, 丹莫罗->哨兵岭,西部荒野"] = 297, ["铁炉堡机场, 丹莫罗->哨兵岭,西部荒野"] = 297,
["铁炉堡机场, 丹莫罗->丹基塔斯,冷酷海岸"] = 193, ["铁炉堡机场, 丹莫罗->丹基塔斯,冷酷海岸"] = 193,
["铁炉堡机场, 丹莫罗->塞尔萨玛,洛克莫丹"] = 86, ["铁炉堡机场, 丹莫罗->塞尔萨玛,洛克莫丹"] = 86,
["铁炉堡机场, 丹莫罗->阿尔萨拉斯"] = 479, ["铁炉堡机场, 丹莫罗->阿尔萨拉斯"] = 479,
["铁炉堡机场, 丹莫罗->鹰巢山,辛特兰"] = 203,
["铁炉堡机场, 丹莫罗->圣光之愿礼拜堂,东瘟疫之地"] = 356,
["铁炉堡机场, 丹莫罗->湖畔镇,赤脊山"] = 230,
["铁炉堡机场, 丹莫罗->卡兰之墓,拉匹迪斯之岛"] = 575,
["铁炉堡机场, 丹莫罗->瑟银哨塔,灼热峡谷"] = 121,
["铁炉堡机场, 丹莫罗->摩根的岗哨,燃烧平原"] = 203,
["铁炉堡机场, 丹莫罗->避难谷地,阿拉希高地"] = 143,
["铁炉堡机场, 丹莫罗->藏宝海湾,荆棘谷"] = 454,
["铁炉堡机场, 丹莫罗->寒风营地,西瘟疫之地\t"] = 212,
-- 铁炉堡,丹莫罗
["铁炉堡,丹莫罗->铁炉堡机场, 丹莫罗"] = 80,
["铁炉堡,丹莫罗->丹阿格拉斯, 湿地"] = 107, ["铁炉堡,丹莫罗->丹阿格拉斯, 湿地"] = 107,
["铁炉堡,丹莫罗->暴风城,艾尔文森林"] = 198, ["铁炉堡,丹莫罗->暴风城,艾尔文森林"] = 198,
["铁炉堡,丹莫罗->丹基塔斯,冷酷海岸"] = 202, ["铁炉堡,丹莫罗->丹基塔斯,冷酷海岸"] = 202,
-- 暴风城 区域 ["铁炉堡,丹莫罗->南海镇,希尔斯布莱德"] = 249,
["铁炉堡,丹莫罗->卡兰之墓,拉匹迪斯之岛"] = 537,
["铁炉堡,丹莫罗->阿尔萨拉斯"] = 451,
-- 暴风城,艾尔文森林
["暴风城,艾尔文森林->铁炉堡机场, 丹莫罗"] = 286, ["暴风城,艾尔文森林->铁炉堡机场, 丹莫罗"] = 286,
["暴风城,艾尔文森林->铁炉堡,丹莫罗"] = 244, ["暴风城,艾尔文森林->铁炉堡,丹莫罗"] = 244,
["暴风城,艾尔文森林->藏宝海湾,荆棘谷"] = 230, ["暴风城,艾尔文森林->藏宝海湾,荆棘谷"] = 230,
["暴风城,艾尔文森林->摩根的岗哨,燃烧平原"] = 148, ["暴风城,艾尔文森林->摩根的岗哨,燃烧平原"] = 148,
["暴风城,艾尔文森林->丹阿格拉斯, 湿地"] = 309, ["暴风城,艾尔文森林->丹阿格拉斯, 湿地"] = 309,
["暴风城,艾尔文森林->丹基塔斯,冷酷海岸"] = 406, ["暴风城,艾尔文森林->丹基塔斯,冷酷海岸"] = 406,
-- 丹阿格拉斯 区域 ["暴风城,艾尔文森林->南海镇,希尔斯布莱德"] = 418,
["暴风城,艾尔文森林->湖畔镇,赤脊山"] = 106,
["暴风城,艾尔文森林->米奈希尔港,湿地"] = 323,
["暴风城,艾尔文森林->夜色镇,暮色森林"] = 109,
["暴风城,艾尔文森林->哨兵岭,西部荒野"] = 73,
["暴风城,艾尔文森林->卡兰之墓,拉匹迪斯之岛"] = 351,
["暴风城,艾尔文森林->阿尔萨拉斯"] = 654,
-- 丹阿格拉斯, 湿地
["丹阿格拉斯, 湿地->米奈希尔港,湿地"] = 20, ["丹阿格拉斯, 湿地->米奈希尔港,湿地"] = 20,
["丹阿格拉斯, 湿地->铁炉堡机场, 丹莫罗"] = 29,
["丹阿格拉斯, 湿地->铁炉堡,丹莫罗"] = 110, ["丹阿格拉斯, 湿地->铁炉堡,丹莫罗"] = 110,
["丹阿格拉斯, 湿地->藏宝海湾,荆棘谷"] = 484, ["丹阿格拉斯, 湿地->藏宝海湾,荆棘谷"] = 484,
["丹阿格拉斯, 湿地->卡兰之墓,拉匹迪斯之岛"] = 605, ["丹阿格拉斯, 湿地->卡兰之墓,拉匹迪斯之岛"] = 605,
["丹阿格拉斯, 湿地->塞尔萨玛,洛克莫丹"] = 150, ["丹阿格拉斯, 湿地->塞尔萨玛,洛克莫丹"] = 150,
["丹阿格拉斯, 湿地->暴风城,艾尔文森林"] = 266, ["丹阿格拉斯, 湿地->暴风城,艾尔文森林"] = 266,
-- 米奈希尔港 区域 ["丹阿格拉斯, 湿地->南海镇,希尔斯布莱德"] = 113,
["丹阿格拉斯, 湿地->丹基塔斯,冷酷海岸"] = 257,
["丹阿格拉斯, 湿地->避难谷地,阿拉希高地"] = 118,
["丹阿格拉斯, 湿地->哨兵岭,西部荒野"] = 327,
-- 米奈希尔港,湿地
["米奈希尔港,湿地->丹阿格拉斯, 湿地"] = 28, ["米奈希尔港,湿地->丹阿格拉斯, 湿地"] = 28,
-- 南海镇 区域 ["米奈希尔港,湿地->铁炉堡机场, 丹莫罗"] = 43,
["米奈希尔港,湿地->卡兰之墓,拉匹迪斯之岛"] = 584,
["米奈希尔港,湿地->丹基塔斯,冷酷海岸"] = 260,
-- 南海镇,希尔斯布莱德
["南海镇,希尔斯布莱德->丹阿格拉斯, 湿地"] = 129, ["南海镇,希尔斯布莱德->丹阿格拉斯, 湿地"] = 129,
-- 摩根的岗哨 / 瑟银哨塔 区域 ["南海镇,希尔斯布莱德->铁炉堡机场, 丹莫罗"] = 144,
["摩根的岗哨,燃烧平原->瑟银哨塔,灼热峡谷"] = 97, ["南海镇,希尔斯布莱德->卡兰之墓,拉匹迪斯之岛"] = 683,
["瑟银哨塔,灼热峡谷->摩根的岗哨,燃烧平原"] = 90, ["南海镇,希尔斯布莱德->阿尔萨拉斯"] = 343,
["瑟银哨塔,灼热峡谷->藏宝海湾,荆棘谷"] = 329, ["南海镇,希尔斯布莱德->丹基塔斯,冷酷海岸"] = 336,
-- 藏宝海湾 区域 -- 丹基塔斯,冷酷海岸
["藏宝海湾,荆棘谷->铁炉堡机场, 丹莫罗"] = 460,
["藏宝海湾,荆棘谷->卡兰之墓,拉匹迪斯之岛"] = 121,
["藏宝海湾,荆棘谷->丹阿格拉斯, 湿地"] = 483,
["卡兰之墓,拉匹迪斯之岛->藏宝海湾,荆棘谷"] = 121,
-- 加基森 / 塞拉摩 区域
["加基森,塔纳利斯->塞拉摩,尘泥沼泽"] = 144,
["塞拉摩,尘泥沼泽->加基森,塔纳利斯"] = 147,
-- 安伯郡 / 哨兵岭 区域
["安伯郡,北风领->暴风城,艾尔文森林"] = 107,
["哨兵岭,西部荒野->丹阿格拉斯, 湿地"] = 377,
["哨兵岭,西部荒野->铁炉堡机场, 丹莫罗"] = 354,
-- 丹基塔斯 (Turtle WoW 冷酷海岸)
["丹基塔斯,冷酷海岸->铁炉堡,丹莫罗"] = 210, ["丹基塔斯,冷酷海岸->铁炉堡,丹莫罗"] = 210,
["丹基塔斯,冷酷海岸->铁炉堡机场, 丹莫罗"] = 204, ["丹基塔斯,冷酷海岸->铁炉堡机场, 丹莫罗"] = 204,
["丹基塔斯,冷酷海岸->暴风城,艾尔文森林"] = 371, ["丹基塔斯,冷酷海岸->暴风城,艾尔文森林"] = 371,
["丹基塔斯,冷酷海岸->哨兵岭,西部荒野"] = 431, ["丹基塔斯,冷酷海岸->哨兵岭,西部荒野"] = 431,
["丹基塔斯,冷酷海岸->塞尔萨玛,洛克莫丹"] = 107, ["丹基塔斯,冷酷海岸->塞尔萨玛,洛克莫丹"] = 107,
-- 塞尔萨玛 区域 ["丹基塔斯,冷酷海岸->丹阿格拉斯, 湿地"] = 263,
["丹基塔斯,冷酷海岸->米奈希尔港,湿地"] = 251,
["丹基塔斯,冷酷海岸->摩根的岗哨,燃烧平原"] = 333,
["丹基塔斯,冷酷海岸->南海镇,希尔斯布莱德"] = 343,
["丹基塔斯,冷酷海岸->鹰巢山,辛特兰"] = 329,
["丹基塔斯,冷酷海岸->卡兰之墓,拉匹迪斯之岛"] = 709,
["丹基塔斯,冷酷海岸->藏宝海湾,荆棘谷"] = 588,
["丹基塔斯,冷酷海岸->瑟银哨塔,灼热峡谷"] = 252,
["丹基塔斯,冷酷海岸->避难谷地,阿拉希高地"] = 261,
-- 塞尔萨玛,洛克莫丹
["塞尔萨玛,洛克莫丹->铁炉堡机场, 丹莫罗"] = 96, ["塞尔萨玛,洛克莫丹->铁炉堡机场, 丹莫罗"] = 96,
-- 阿尔萨拉斯 / 圣光之愿 / 鹰巢山 区域 ["塞尔萨玛,洛克莫丹->丹基塔斯,冷酷海岸"] = 107,
-- 哨兵岭,西部荒野
["哨兵岭,西部荒野->丹阿格拉斯, 湿地"] = 377,
["哨兵岭,西部荒野->铁炉堡机场, 丹莫罗"] = 354,
["哨兵岭,西部荒野->暴风城,艾尔文森林"] = 81,
["哨兵岭,西部荒野->南海镇,希尔斯布莱德"] = 486,
["哨兵岭,西部荒野->丹基塔斯,冷酷海岸"] = 474,
["哨兵岭,西部荒野->卡兰之墓,拉匹迪斯之岛"] = 296,
-- 安伯郡,北风领
["安伯郡,北风领->暴风城,艾尔文森林"] = 107,
-- 湖畔镇,赤脊山
["湖畔镇,赤脊山->铁炉堡机场, 丹莫罗"] = 262,
["湖畔镇,赤脊山->卡兰之墓,拉匹迪斯之岛"] = 336,
["湖畔镇,赤脊山->守望堡,诅咒之地"] = 137,
-- 夜色镇,暮色森林
["夜色镇,暮色森林->湖畔镇,赤脊山"] = 56,
["夜色镇,暮色森林->哨兵岭,西部荒野"] = 87,
["夜色镇,暮色森林->米奈希尔港,湿地"] = 405,
["夜色镇,暮色森林->卡兰之墓,拉匹迪斯之岛"] = 283,
-- 避难谷地,阿拉希高地
["避难谷地,阿拉希高地->丹阿格拉斯, 湿地"] = 146,
["避难谷地,阿拉希高地->丹基塔斯,冷酷海岸"] = 267,
["避难谷地,阿拉希高地->铁炉堡机场, 丹莫罗"] = 161,
-- 守望堡,诅咒之地
["守望堡,诅咒之地->铁炉堡机场, 丹莫罗"] = 452,
-- 摩根的岗哨,燃烧平原
["摩根的岗哨,燃烧平原->瑟银哨塔,灼热峡谷"] = 97,
["摩根的岗哨,燃烧平原->铁炉堡机场, 丹莫罗"] = 218,
["摩根的岗哨,燃烧平原->卡兰之墓,拉匹迪斯之岛"] = 393,
-- 瑟银哨塔,灼热峡谷
["瑟银哨塔,灼热峡谷->摩根的岗哨,燃烧平原"] = 90,
["瑟银哨塔,灼热峡谷->藏宝海湾,荆棘谷"] = 329,
["瑟银哨塔,灼热峡谷->湖畔镇,赤脊山"] = 117,
["瑟银哨塔,灼热峡谷->铁炉堡机场, 丹莫罗"] = 132,
["瑟银哨塔,灼热峡谷->莫尔奥格避难所,吉利吉姆之岛"] = 520,
["瑟银哨塔,灼热峡谷->碎风哨站,巴洛"] = 551,
-- 阿尔萨拉斯
["阿尔萨拉斯->鹰巢山,辛特兰"] = 276, ["阿尔萨拉斯->鹰巢山,辛特兰"] = 276,
["阿尔萨拉斯->圣光之愿礼拜堂,东瘟疫之地"] = 125, ["阿尔萨拉斯->圣光之愿礼拜堂,东瘟疫之地"] = 125,
["阿尔萨拉斯->暴风城,艾尔文森林"] = 635,
["阿尔萨拉斯->铁炉堡,丹莫罗"] = 472,
["阿尔萨拉斯->寒风营地,西瘟疫之地\t"] = 263,
-- 圣光之愿礼拜堂,东瘟疫之地
["圣光之愿礼拜堂,东瘟疫之地->阿尔萨拉斯"] = 124, ["圣光之愿礼拜堂,东瘟疫之地->阿尔萨拉斯"] = 124,
["圣光之愿礼拜堂,东瘟疫之地->哨兵岭,西部荒野"] = 571,
-- 鹰巢山,辛特兰
["鹰巢山,辛特兰->丹阿格拉斯, 湿地"] = 190, ["鹰巢山,辛特兰->丹阿格拉斯, 湿地"] = 190,
-- 月光林地 / 石爪峰 区域 ["鹰巢山,辛特兰->丹基塔斯,冷酷海岸"] = 338,
["鹰巢山,辛特兰->铁炉堡机场, 丹莫罗"] = 205,
-- 寒风营地,西瘟疫之地
["寒风营地,西瘟疫之地\t->丹阿格拉斯, 湿地"] = 199,
["寒风营地,西瘟疫之地\t->铁炉堡机场, 丹莫罗"] = 215,
-- 藏宝海湾,荆棘谷
["藏宝海湾,荆棘谷->铁炉堡机场, 丹莫罗"] = 460,
["藏宝海湾,荆棘谷->卡兰之墓,拉匹迪斯之岛"] = 121,
["藏宝海湾,荆棘谷->丹阿格拉斯, 湿地"] = 483,
["藏宝海湾,荆棘谷->丹基塔斯,冷酷海岸"] = 580,
["藏宝海湾,荆棘谷->莫尔奥格避难所,吉利吉姆之岛"] = 61,
-- 卡兰之墓,拉匹迪斯之岛
["卡兰之墓,拉匹迪斯之岛->藏宝海湾,荆棘谷"] = 121,
["卡兰之墓,拉匹迪斯之岛->丹基塔斯,冷酷海岸"] = 702,
["卡兰之墓,拉匹迪斯之岛->暴风城,艾尔文森林"] = 328,
["卡兰之墓,拉匹迪斯之岛->夜色镇,暮色森林"] = 286,
["卡兰之墓,拉匹迪斯之岛->塞尔萨玛,洛克莫丹"] = 595,
["卡兰之墓,拉匹迪斯之岛->湖畔镇,赤脊山"] = 338,
["卡兰之墓,拉匹迪斯之岛->米奈希尔港,湿地"] = 619,
["卡兰之墓,拉匹迪斯之岛->哨兵岭,西部荒野"] = 293,
["卡兰之墓,拉匹迪斯之岛->丹阿格拉斯, 湿地"] = 605,
["卡兰之墓,拉匹迪斯之岛->铁炉堡,丹莫罗"] = 540,
["卡兰之墓,拉匹迪斯之岛->铁炉堡机场, 丹莫罗"] = 582,
-- 碎刃哨岗,冷酷海岸 (部落)
["碎刃哨岗,冷酷海岸->塔伦米尔,希尔斯布莱德"] = 298,
["碎刃哨岗,冷酷海岸->恶齿村,辛特兰"] = 273,
["碎刃哨岗,冷酷海岸->卡加斯,荒芜之地"] = 432,
["碎刃哨岗,冷酷海岸->格罗姆高,荆棘谷"] = 726,
["碎刃哨岗,冷酷海岸->寂静守卫教堂, 吉尔尼斯"] = 374,
["碎刃哨岗,冷酷海岸->瑟伯切尔,银松森林"] = 390,
["碎刃哨岗,冷酷海岸->幽暗城,提瑞斯法林地"] = 430,
["碎刃哨岗,冷酷海岸->落锤镇,阿拉希高地"] = 187,
-- 寂静守卫教堂, 吉尔尼斯 (部落)
["寂静守卫教堂, 吉尔尼斯->瑟伯切尔,银松森林"] = 171,
["寂静守卫教堂, 吉尔尼斯->恶齿村,辛特兰"] = 263,
["寂静守卫教堂, 吉尔尼斯->碎刃哨岗,冷酷海岸"] = 374,
["寂静守卫教堂, 吉尔尼斯->格罗姆高,荆棘谷"] = 728,
["寂静守卫教堂, 吉尔尼斯->幽暗城,提瑞斯法林地"] = 211,
["寂静守卫教堂, 吉尔尼斯->卡加斯,荒芜之地"] = 434,
["寂静守卫教堂, 吉尔尼斯->落锤镇,阿拉希高地"] = 190,
["寂静守卫教堂, 吉尔尼斯->塔伦米尔,希尔斯布莱德"] = 78,
-- 莫尔奥格避难所,吉利吉姆之岛 (部落)
["莫尔奥格避难所,吉利吉姆之岛->藏宝海湾,荆棘谷"] = 61,
["莫尔奥格避难所,吉利吉姆之岛->斯通纳德,悲伤沼泽"] = 313,
["莫尔奥格避难所,吉利吉姆之岛->落锤镇,阿拉希高地"] = 674,
["莫尔奥格避难所,吉利吉姆之岛->格罗姆高,荆棘谷"] = 158,
["莫尔奥格避难所,吉利吉姆之岛->烈焰峰,燃烧平原"] = 499,
["莫尔奥格避难所,吉利吉姆之岛->卡加斯,荒芜之地"] = 454,
["莫尔奥格避难所,吉利吉姆之岛->幽暗城,提瑞斯法林地"] = 897,
["莫尔奥格避难所,吉利吉姆之岛->瑟银哨塔,灼热峡谷"] = 507,
["莫尔奥格避难所,吉利吉姆之岛->碎风哨站,巴洛"] = 349,
-- 格罗姆高,荆棘谷 (部落)
["格罗姆高,荆棘谷->碎刃哨岗,冷酷海岸"] = 711,
["格罗姆高,荆棘谷->莫尔奥格避难所,吉利吉姆之岛"] = 137,
["格罗姆高,荆棘谷->斯通纳德,悲伤沼泽"] = 194,
["格罗姆高,荆棘谷->寂静守卫教堂, 吉尔尼斯"] = 714,
["格罗姆高,荆棘谷->碎风哨站,巴洛"] = 191,
["格罗姆高,荆棘谷->瑟银哨塔,灼热峡谷"] = 361,
-- 碎风哨站,巴洛 (部落)
["碎风哨站,巴洛->藏宝海湾,荆棘谷"] = 267,
["碎风哨站,巴洛->格罗姆高,荆棘谷"] = 191,
["碎风哨站,巴洛->莫尔奥格避难所,吉利吉姆之岛"] = 329,
["碎风哨站,巴洛->卡加斯,荒芜之地"] = 500,
["碎风哨站,巴洛->斯通纳德,悲伤沼泽"] = 385,
-- 卡加斯,荒芜之地 (部落)
["卡加斯,荒芜之地->碎刃哨岗,冷酷海岸"] = 431,
["卡加斯,荒芜之地->莫尔奥格避难所,吉利吉姆之岛"] = 455,
["卡加斯,荒芜之地->碎风哨站,巴洛"] = 486,
["卡加斯,荒芜之地->寂静守卫教堂, 吉尔尼斯"] = 433,
-- 瑟伯切尔,银松森林 (部落)
["瑟伯切尔,银松森林->寂静守卫教堂, 吉尔尼斯"] = 166,
["瑟伯切尔,银松森林->碎刃哨岗,冷酷海岸"] = 383,
-- 幽暗城,提瑞斯法林地 (部落)
["幽暗城,提瑞斯法林地->寂静守卫教堂, 吉尔尼斯"] = 209,
["幽暗城,提瑞斯法林地->碎刃哨岗,冷酷海岸"] = 428,
["幽暗城,提瑞斯法林地->莫尔奥格避难所,吉利吉姆之岛"] = 914,
-- 落锤镇,阿拉希高地 (部落)
["落锤镇,阿拉希高地->寂静守卫教堂, 吉尔尼斯"] = 186,
["落锤镇,阿拉希高地->莫尔奥格避难所,吉利吉姆之岛"] = 698,
["落锤镇,阿拉希高地->碎刃哨岗,冷酷海岸"] = 187,
-- 斯通纳德,悲伤沼泽 (部落)
["斯通纳德,悲伤沼泽->莫尔奥格避难所,吉利吉姆之岛"] = 307,
["斯通纳德,悲伤沼泽->碎风哨站,巴洛"] = 369,
-- 塔伦米尔,希尔斯布莱德 (部落)
["塔伦米尔,希尔斯布莱德->恶齿村,辛特兰"] = 185,
["塔伦米尔,希尔斯布莱德->寂静守卫教堂, 吉尔尼斯"] = 78,
-- 恶齿村,辛特兰 (部落)
["恶齿村,辛特兰->寂静守卫教堂, 吉尔尼斯"] = 227,
["恶齿村,辛特兰->碎刃哨岗,冷酷海岸"] = 270,
["恶齿村,辛特兰->莫尔奥格避难所,吉利吉姆之岛"] = 767,
-- 烈焰峰,燃烧平原 (部落)
["烈焰峰,燃烧平原->莫尔奥格避难所,吉利吉姆之岛"] = 507,
-- 加基森 / 塞拉摩
["加基森,塔纳利斯->塞拉摩,尘泥沼泽"] = 144,
["塞拉摩,尘泥沼泽->加基森,塔纳利斯"] = 147,
-- 卡利姆多
["月光林地->石爪峰,石爪山"] = 304, ["月光林地->石爪峰,石爪山"] = 304,
["石爪峰,石爪山->塔伦迪斯营地,艾萨拉"] = 285, ["石爪峰,石爪山->塔伦迪斯营地,艾萨拉"] = 285,
} }

View File

@@ -24,6 +24,7 @@ local BUDGET_COST = {
HEALTHREG = 2.0, MANAREG = 2.0, HEALTHREG = 2.0, MANAREG = 2.0,
HEALTH = 0.07, MANA = 0.07, HEALTH = 0.07, MANA = 0.07,
WEAPONDPS = 3.0, WEAPONDPS = 3.0,
WEAPONSPEED = 1.5,
} }
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -31,175 +32,204 @@ local BUDGET_COST = {
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local WEIGHTS = { local WEIGHTS = {
-- ================================================================
-- Pawn-style normalization: primary stat = 1.0
-- TOHIT/CRIT per 1%; PDF: 1%hit≈18AP, 1%crit≈25AP, 1%spellhit≈14SP
-- ================================================================
WARRIOR = { WARRIOR = {
specs = { specs = {
{ name = "武器", tab = 1, color = "ffC79C6E", { name = "武器", tab = 1, color = "ffC79C6E",
w = { STR=2.0, AGI=1.4, STA=0.1, TOHIT=18, CRIT=25, ATTACKPOWER=1.0, w = { STR=1.0, AGI=0.7, STA=0.5, TOHIT=9, CRIT=12,
HEALTH=0.1, WEAPONDPS=14, BASEARMOR=0.01 } }, ATTACKPOWER=0.5, HEALTH=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
{ name = "狂怒", tab = 2, color = "ffC79C6E", { name = "狂怒", tab = 2, color = "ffC79C6E",
w = { STR=2.2, AGI=1.6, STA=0.1, TOHIT=20, CRIT=22, ATTACKPOWER=1.0, w = { STR=1.0, AGI=0.6, STA=0.5, TOHIT=10, CRIT=11,
HEALTH=0.1, WEAPONDPS=12, BASEARMOR=0.01 } }, ATTACKPOWER=0.5, HEALTH=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
{ name = "防护", tab = 3, color = "ff69CCF0", { name = "防护", tab = 3, color = "ff69CCF0",
w = { STR=1.0, AGI=1.8, STA=2.5, TOHIT=10, CRIT=3, ATTACKPOWER=0.5, w = { STA=1.0, STR=0.5, AGI=0.7, TOHIT=5, CRIT=3, ATTACKPOWER=0.2,
DEFENSE=1.5, DODGE=12, PARRY=12, BLOCK=8, BLOCKVALUE=0.5, DEFENSE=0.8, DODGE=8, PARRY=7, BLOCK=6, BLOCKVALUE=0.35,
ARMOR=0.12, HEALTH=0.25, HEALTHREG=0.5, WEAPONDPS=4, BASEARMOR=0.05 } }, ARMOR=0.02, HEALTH=0.1, HEALTHREG=1.0, WEAPONDPS=3, BASEARMOR=0.03 } },
}, },
-- 硬核物理(战士型): 耐 > 力 > 敏 > 攻强 -- 硬核物理(战士型): 耐 > 力 > 敏 > 攻强
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, STR=2.0, AGI=1.5, ATTACKPOWER=1.0, w = { STA=1.5, STR=1.0, AGI=0.8, ATTACKPOWER=0.5,
TOHIT=5, CRIT=5, DEFENSE=1.0, DODGE=8, PARRY=5, BLOCK=4, BLOCKVALUE=0.3, TOHIT=3, CRIT=3, DEFENSE=0.5, DODGE=5, PARRY=3, BLOCK=3, BLOCKVALUE=0.3,
ARMOR=0.08, HEALTH=0.2, HEALTHREG=2.0, WEAPONDPS=5, BASEARMOR=0.03 } }, ARMOR=0.05, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=3, BASEARMOR=0.02 } },
}, },
PALADIN = { PALADIN = {
specs = { specs = {
{ name = "神圣", tab = 1, color = "ff00FF96", { name = "神圣", tab = 1, color = "ff00FF96",
w = { INT=0.35, SPI=0.4, STA=0.05, HEAL=1.0, DMG=0.3, SPELLCRIT=6, MANAREG=3.0, w = { INT=1.0, SPI=0.3, STA=0.5, HEAL=0.55, DMG=0.1,
MANA=0.02, BASEARMOR=0.005 } }, SPELLCRIT=5, MANAREG=1.3, MANA=0.01, BASEARMOR=0.005 } },
{ name = "防护", tab = 2, color = "ff69CCF0", { name = "防护", tab = 2, color = "ff69CCF0",
w = { STR=1.2, AGI=1.0, STA=2.5, INT=0.3, TOHIT=10, CRIT=5, ATTACKPOWER=0.5, DMG=0.4, w = { STA=1.0, STR=0.5, AGI=0.5, INT=0.3, TOHIT=5, CRIT=3,
DEFENSE=1.5, DODGE=12, PARRY=12, BLOCK=10, BLOCKVALUE=0.5, ATTACKPOWER=0.2, DMG=0.4,
ARMOR=0.12, HEALTH=0.25, MANAREG=1.5, WEAPONDPS=3, BASEARMOR=0.05 } }, DEFENSE=0.7, DODGE=7, PARRY=6, BLOCK=6, BLOCKVALUE=0.15,
ARMOR=0.02, HEALTH=0.1, MANAREG=1.0, WEAPONDPS=2, BASEARMOR=0.03 } },
{ name = "惩戒", tab = 3, color = "ffF58CBA", { name = "惩戒", tab = 3, color = "ffF58CBA",
w = { STR=2.0, AGI=1.0, STA=0.1, INT=0.25, TOHIT=16, CRIT=20, SPELLCRIT=10, w = { STR=1.0, AGI=0.6, STA=0.5, INT=0.3,
ATTACKPOWER=1.0, DMG=0.6, HEAL=0.05, WEAPONDPS=12, BASEARMOR=0.01 } }, TOHIT=8, CRIT=10, SPELLCRIT=5,
ATTACKPOWER=0.5, DMG=0.3, HEAL=0.05, WEAPONDPS=5, BASEARMOR=0.005 } },
}, },
-- 硬核圣骑士: 耐 > 力 > 智 = 敏 = 精 -- 硬核圣骑士: 耐 > 力 > 智 = 敏 = 精
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, STR=2.0, INT=1.5, AGI=1.5, SPI=1.5, w = { STA=1.5, STR=1.0, INT=0.8, AGI=0.8, SPI=0.8,
TOHIT=5, CRIT=5, ATTACKPOWER=0.5, HEAL=0.8, DMG=0.3, TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.2,
DEFENSE=0.8, DODGE=5, ARMOR=0.06, HEALTH=0.2, DEFENSE=0.5, DODGE=3, ARMOR=0.04, HEALTH=0.1,
HEALTHREG=1.5, MANAREG=1.5, WEAPONDPS=4, BASEARMOR=0.03 } }, HEALTHREG=1.0, MANAREG=1.0, WEAPONDPS=2, BASEARMOR=0.02 } },
}, },
HUNTER = { HUNTER = {
specs = { specs = {
{ name = "野兽", tab = 1, color = "ffABD473", { name = "野兽", tab = 1, color = "ffABD473",
w = { AGI=2.4, STR=0.3, STA=0.1, INT=0.2, TOHIT=18, CRIT=22, RANGEDCRIT=22, w = { AGI=1.0, STR=0.05, STA=0.5, INT=0.8,
ATTACKPOWER=0.8, RANGEDATTACKPOWER=1.0, MANAREG=1.5, WEAPONDPS=10, BASEARMOR=0.005 } }, TOHIT=10, CRIT=10, RANGEDCRIT=10,
ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, MANAREG=2.0,
WEAPONDPS=4, BASEARMOR=0.005 } },
{ name = "射击", tab = 2, color = "ffABD473", { name = "射击", tab = 2, color = "ffABD473",
w = { AGI=2.4, STR=0.3, STA=0.1, INT=0.2, TOHIT=18, CRIT=22, RANGEDCRIT=22, w = { AGI=1.0, STR=0.05, STA=0.5, INT=0.9,
ATTACKPOWER=0.8, RANGEDATTACKPOWER=1.0, MANAREG=1.5, WEAPONDPS=10, BASEARMOR=0.005 } }, TOHIT=10, CRIT=10, RANGEDCRIT=10,
ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, MANAREG=2.0,
WEAPONDPS=4, BASEARMOR=0.005 } },
{ name = "生存", tab = 3, color = "ffABD473", { name = "生存", tab = 3, color = "ffABD473",
w = { AGI=2.0, STR=0.8, STA=0.3, INT=0.2, SPI=0.3, TOHIT=16, CRIT=18, RANGEDCRIT=18, w = { AGI=1.0, STR=0.4, STA=0.5, INT=0.3, SPI=0.3,
ATTACKPOWER=1.0, RANGEDATTACKPOWER=1.0, MANAREG=1.0, WEAPONDPS=8, BASEARMOR=0.008 } }, TOHIT=8, CRIT=8, RANGEDCRIT=8,
ATTACKPOWER=0.5, RANGEDATTACKPOWER=0.5, MANAREG=1.0,
WEAPONDPS=3, BASEARMOR=0.005 } },
}, },
-- 硬核猎人: 耐 > 敏 > 智 > 力 -- 硬核猎人: 耐 > 敏 > 智 > 力 (精神配合灵魂链接有价值)
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, AGI=2.0, INT=1.0, STR=0.5, SPI=1.5, w = { STA=1.5, AGI=1.0, INT=0.5, STR=0.3, SPI=0.8,
TOHIT=5, CRIT=5, RANGEDCRIT=5, ATTACKPOWER=0.4, RANGEDATTACKPOWER=0.5, TOHIT=3, CRIT=3, RANGEDCRIT=3,
ARMOR=0.06, HEALTH=0.2, HEALTHREG=2.5, WEAPONDPS=3, BASEARMOR=0.02 } }, ATTACKPOWER=0.2, RANGEDATTACKPOWER=0.3,
ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=2, BASEARMOR=0.01 } },
}, },
ROGUE = { ROGUE = {
specs = { specs = {
{ name = "刺杀", tab = 1, color = "ffFFF569", { name = "刺杀", tab = 1, color = "ffFFF569",
w = { AGI=2.0, STR=1.0, STA=0.1, TOHIT=18, CRIT=25, ATTACKPOWER=1.0, w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=10, CRIT=12,
WEAPONDPS=10, BASEARMOR=0.008 } }, ATTACKPOWER=0.45, WEAPONDPS=5, BASEARMOR=0.005 } },
{ name = "战斗", tab = 2, color = "ffFFF569", { name = "战斗", tab = 2, color = "ffFFF569",
w = { AGI=2.0, STR=1.0, STA=0.1, TOHIT=20, CRIT=22, ATTACKPOWER=1.0, w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=10, CRIT=11,
WEAPONDPS=14, BASEARMOR=0.008 } }, ATTACKPOWER=0.45, WEAPONDPS=6, BASEARMOR=0.005 } },
{ name = "敏锐", tab = 3, color = "ffFFF569", { name = "敏锐", tab = 3, color = "ffFFF569",
w = { AGI=2.2, STR=1.0, STA=0.5, TOHIT=16, CRIT=20, ATTACKPOWER=1.0, DODGE=5, w = { AGI=1.0, STR=0.5, STA=0.5, TOHIT=8, CRIT=10,
WEAPONDPS=8, BASEARMOR=0.01 } }, ATTACKPOWER=0.45, DODGE=3, WEAPONDPS=4, BASEARMOR=0.005 } },
}, },
-- 硬核潜行者: 耐 > 敏 > 力 > 攻强 -- 硬核潜行者: 耐 > 敏 > 力 > 攻强
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, AGI=2.5, STR=1.5, ATTACKPOWER=1.0, w = { STA=1.5, AGI=1.2, STR=0.8, ATTACKPOWER=0.5,
TOHIT=5, CRIT=5, DODGE=5, ARMOR=0.06, TOHIT=3, CRIT=3, DODGE=3, ARMOR=0.04,
HEALTH=0.2, HEALTHREG=2.0, WEAPONDPS=5, BASEARMOR=0.02 } }, HEALTH=0.1, HEALTHREG=1.5, WEAPONDPS=3, BASEARMOR=0.01 } },
}, },
PRIEST = { PRIEST = {
specs = { specs = {
{ name = "戒律", tab = 1, color = "ff00FF96", { name = "戒律", tab = 1, color = "ff00FF96",
w = { INT=0.35, SPI=0.55, STA=0.05, HEAL=1.0, DMG=0.3, SPELLCRIT=5, MANAREG=3.0, w = { INT=1.0, SPI=0.5, STA=0.5, HEAL=0.7, DMG=0.1,
MANA=0.02, BASEARMOR=0.003 } }, SPELLCRIT=4, MANAREG=1.2, MANA=0.01,
WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
{ name = "神圣", tab = 2, color = "ff00FF96", { name = "神圣", tab = 2, color = "ff00FF96",
w = { INT=0.35, SPI=0.55, STA=0.05, HEAL=1.0, DMG=0.3, SPELLCRIT=5, MANAREG=3.0, w = { INT=1.0, SPI=0.7, STA=0.5, HEAL=0.8, DMG=0.1,
MANA=0.02, BASEARMOR=0.003 } }, SPELLCRIT=3, MANAREG=1.35, MANA=0.01,
WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
{ name = "暗影", tab = 3, color = "ff9482C9", { name = "暗影", tab = 3, color = "ff9482C9",
w = { INT=0.15, SPI=0.35, STA=0.05, DMG=1.0, HEAL=0.05, SPELLCRIT=6, SPELLTOHIT=14, w = { DMG=1.0, INT=0.2, SPI=0.2, STA=0.5,
MANAREG=1.5, BASEARMOR=0.003 } }, SPELLTOHIT=14, SPELLCRIT=8, MANAREG=1.0,
WEAPONDPS=2.5, WEAPONSPEED=0.6, BASEARMOR=0.003 } },
}, },
-- 硬核法系(牧师): 耐 > 智 = 治疗 > 精 -- 硬核法系(牧师): 耐 > 智 = 治疗 > 精
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, INT=2.0, HEAL=2.0, DMG=1.5, SPI=1.5, w = { STA=1.5, INT=1.0, HEAL=1.0, DMG=0.8, SPI=0.8,
SPELLCRIT=3, SPELLTOHIT=5, MANAREG=2.5, SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.5,
ARMOR=0.06, HEALTH=0.15, HEALTHREG=1.0, BASEARMOR=0.02 } }, WEAPONDPS=1.5, WEAPONSPEED=0.3,
ARMOR=0.04, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
}, },
SHAMAN = { SHAMAN = {
specs = { specs = {
{ name = "元素", tab = 1, color = "ff0070DE", { name = "元素", tab = 1, color = "ff0070DE",
w = { INT=0.2, SPI=0.1, STA=0.05, DMG=1.0, SPELLCRIT=8, SPELLTOHIT=14, w = { DMG=1.0, INT=0.3, SPI=0.1, STA=0.5,
ATTACKPOWER=0.1, MANAREG=2.0, BASEARMOR=0.005 } }, SPELLTOHIT=10, SPELLCRIT=8, ATTACKPOWER=0.1,
MANAREG=1.1, BASEARMOR=0.005 } },
{ name = "增强", tab = 2, color = "ff0070DE", { name = "增强", tab = 2, color = "ff0070DE",
w = { STR=2.0, AGI=1.6, STA=0.1, INT=0.2, TOHIT=18, CRIT=22, ATTACKPOWER=1.0, w = { STR=1.0, AGI=0.9, STA=0.5, INT=0.3,
DMG=0.3, SPELLCRIT=5, MANAREG=1.0, WEAPONDPS=14, BASEARMOR=0.01 } }, TOHIT=10, CRIT=11, ATTACKPOWER=0.5,
DMG=0.3, SPELLCRIT=3, MANAREG=1.0,
WEAPONDPS=5, BASEARMOR=0.005 } },
{ name = "恢复", tab = 3, color = "ff00FF96", { name = "恢复", tab = 3, color = "ff00FF96",
w = { INT=0.35, SPI=0.3, STA=0.05, HEAL=1.0, DMG=0.2, SPELLCRIT=5, MANAREG=3.5, w = { INT=1.0, SPI=0.3, STA=0.5, HEAL=0.9, DMG=0.1,
MANA=0.02, BASEARMOR=0.005 } }, SPELLCRIT=5, MANAREG=1.7, MANA=0.01, BASEARMOR=0.005 } },
}, },
-- 硬核萨满(混合): 耐 > 力=敏 > 智=精 -- 硬核萨满(混合): 耐 > 力=敏 > 智=精
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, STR=1.5, AGI=1.5, INT=1.0, SPI=1.0, w = { STA=1.5, STR=0.8, AGI=0.8, INT=0.5, SPI=0.5,
TOHIT=5, CRIT=5, ATTACKPOWER=0.5, HEAL=0.8, DMG=0.5, TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.3,
MANAREG=2.0, ARMOR=0.06, HEALTH=0.2, HEALTHREG=1.5, MANAREG=1.0, ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.0,
WEAPONDPS=4, BASEARMOR=0.02 } }, WEAPONDPS=2, BASEARMOR=0.01 } },
}, },
MAGE = { MAGE = {
specs = { specs = {
{ name = "奥术", tab = 1, color = "ff69CCF0", { name = "奥术", tab = 1, color = "ff69CCF0",
w = { INT=0.2, SPI=0.15, STA=0.4, DMG=1.0, SPELLCRIT=7, SPELLTOHIT=14, MANAREG=2.0, w = { DMG=1.0, INT=0.46, SPI=0.6, STA=0.3,
MANA=0.01, BASEARMOR=0.003 } }, SPELLTOHIT=10, SPELLCRIT=7, MANAREG=1.1,
MANA=0.04, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
{ name = "火焰", tab = 2, color = "ff69CCF0", { name = "火焰", tab = 2, color = "ff69CCF0",
w = { INT=0.15, SPI=0.1, STA=0.4, DMG=1.0, SPELLCRIT=9, SPELLTOHIT=14, MANAREG=2.0, w = { DMG=1.0, INT=0.44, SPI=0.07, STA=0.3,
MANA=0.01, BASEARMOR=0.003 } }, SPELLTOHIT=12, SPELLCRIT=9, MANAREG=1.0,
MANA=0.04, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
{ name = "冰霜", tab = 3, color = "ff69CCF0", { name = "冰霜", tab = 3, color = "ff69CCF0",
w = { INT=0.15, SPI=0.1, STA=0.4, DMG=1.0, SPELLCRIT=7, SPELLTOHIT=14, MANAREG=2.0, w = { DMG=1.0, INT=0.37, SPI=0.06, STA=0.3,
MANA=0.01, BASEARMOR=0.003 } }, SPELLTOHIT=12, SPELLCRIT=7, MANAREG=0.8,
MANA=0.03, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
}, },
-- 硬核法系(法师): 耐 > 智 = 法伤 > 精 -- 硬核法系(法师): 耐 > 智 = 法伤 > 精
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, INT=2.0, DMG=2.0, SPI=1.5, w = { STA=1.5, INT=1.0, DMG=1.0, SPI=0.8,
SPELLCRIT=3, SPELLTOHIT=5, MANAREG=2.5, SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.5,
ARMOR=0.08, HEALTH=0.15, HEALTHREG=1.0, BASEARMOR=0.02 } }, WEAPONDPS=1.5, WEAPONSPEED=0.3,
ARMOR=0.05, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
}, },
WARLOCK = { WARLOCK = {
specs = { specs = {
{ name = "痛苦", tab = 1, color = "ff9482C9", { name = "痛苦", tab = 1, color = "ff9482C9",
w = { INT=0.15, SPI=0.1, STA=0.4, DMG=1.0, SPELLCRIT=4, SPELLTOHIT=14, MANAREG=1.0, w = { DMG=1.0, INT=0.4, SPI=0.1, STA=0.5,
HEALTH=0.08, BASEARMOR=0.003 } }, SPELLTOHIT=12, SPELLCRIT=4, MANAREG=1.0,
HEALTH=0.05, WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
{ name = "恶魔", tab = 2, color = "ff9482C9", { name = "恶魔", tab = 2, color = "ff9482C9",
w = { INT=0.15, SPI=0.1, STA=0.4, DMG=1.0, SPELLCRIT=6, SPELLTOHIT=14, MANAREG=1.5, w = { DMG=1.0, INT=0.4, SPI=0.5, STA=0.5,
BASEARMOR=0.003 } }, SPELLTOHIT=12, SPELLCRIT=7, MANAREG=1.0,
WEAPONDPS=2, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
{ name = "毁灭", tab = 3, color = "ff9482C9", { name = "毁灭", tab = 3, color = "ff9482C9",
w = { INT=0.15, SPI=0.1, STA=0.4, DMG=1.0, SPELLCRIT=8, SPELLTOHIT=14, MANAREG=1.5, w = { DMG=1.0, INT=0.34, SPI=0.25, STA=0.5,
BASEARMOR=0.003 } }, SPELLTOHIT=14, SPELLCRIT=9, MANAREG=0.65,
WEAPONDPS=2.5, WEAPONSPEED=0.5, BASEARMOR=0.003 } },
}, },
-- 硬核法系(术士): 耐 > 智 = 法伤 > 精 -- 硬核法系(术士): 耐 > 智 = 法伤 > 精 (精神低于法师因有生命分流)
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, INT=2.0, DMG=2.0, SPI=1.0, w = { STA=1.5, INT=1.0, DMG=1.0, SPI=0.5,
SPELLCRIT=3, SPELLTOHIT=5, MANAREG=2.0, SPELLCRIT=2, SPELLTOHIT=3, MANAREG=1.0,
ARMOR=0.06, HEALTH=0.15, HEALTHREG=1.0, BASEARMOR=0.02 } }, WEAPONDPS=1.5, WEAPONSPEED=0.3,
ARMOR=0.04, HEALTH=0.08, HEALTHREG=0.8, BASEARMOR=0.01 } },
}, },
DRUID = { DRUID = {
specs = { specs = {
{ name = "平衡", tab = 1, color = "ffFF7D0A", { name = "平衡", tab = 1, color = "ffFF7D0A",
w = { INT=0.2, SPI=0.15, STA=0.05, DMG=1.0, HEAL=0.1, SPELLCRIT=7, SPELLTOHIT=14, w = { DMG=1.0, INT=0.38, SPI=0.34, STA=0.5,
MANAREG=2.0, MANA=0.01, BASEARMOR=0.005 } }, HEAL=0.1, SPELLTOHIT=12, SPELLCRIT=7,
MANAREG=0.6, MANA=0.03, BASEARMOR=0.005 } },
{ name = "野猫", tab = 2, color = "ffFF7D0A", { name = "野猫", tab = 2, color = "ffFF7D0A",
w = { STR=2.4, AGI=1.4, STA=0.1, TOHIT=18, CRIT=22, ATTACKPOWER=1.0, DODGE=2, w = { STR=1.2, AGI=1.0, STA=0.5, TOHIT=10, CRIT=11,
WEAPONDPS=2, BASEARMOR=0.008 } }, ATTACKPOWER=0.5, DODGE=1, WEAPONDPS=0.5, BASEARMOR=0.005 } },
{ name = "野熊", tab = 2, color = "ff69CCF0", { name = "野熊", tab = 2, color = "ff69CCF0",
w = { STR=1.5, AGI=2.0, STA=2.5, TOHIT=8, CRIT=5, ATTACKPOWER=0.5, w = { STA=1.0, AGI=0.5, STR=0.2, TOHIT=3, CRIT=3, ATTACKPOWER=0.35,
DEFENSE=1.2, DODGE=12, ARMOR=0.12, HEALTH=0.25, BASEARMOR=0.05 } }, DEFENSE=0.5, DODGE=6, ARMOR=0.1, HEALTH=0.08, BASEARMOR=0.04 } },
{ name = "恢复", tab = 3, color = "ff00FF96", { name = "恢复", tab = 3, color = "ff00FF96",
w = { INT=0.35, SPI=0.45, STA=0.05, HEAL=1.0, DMG=0.2, SPELLCRIT=5, MANAREG=3.0, w = { INT=1.0, SPI=0.87, STA=0.5, HEAL=1.2, DMG=0.1,
MANA=0.02, BASEARMOR=0.005 } }, SPELLCRIT=4, MANAREG=1.7, MANA=0.01, BASEARMOR=0.005 } },
}, },
-- 硬核德鲁伊(混合偏生存): 耐 > 敏 > 力 > 智 = 精 -- 硬核德鲁伊(混合偏生存): 耐 > 敏 > 力 > 智 = 精
hc = { name = "硬核", color = "ffFF4444", hc = { name = "硬核", color = "ffFF4444",
w = { STA=3.0, AGI=2.0, STR=1.5, INT=1.0, SPI=1.0, w = { STA=1.5, AGI=1.0, STR=0.8, INT=0.5, SPI=0.5,
TOHIT=5, CRIT=5, ATTACKPOWER=0.5, HEAL=0.8, DMG=0.5, TOHIT=3, CRIT=3, ATTACKPOWER=0.3, HEAL=0.4, DMG=0.3,
DODGE=5, ARMOR=0.06, HEALTH=0.2, HEALTHREG=2.0, DODGE=3, ARMOR=0.04, HEALTH=0.1, HEALTHREG=1.0,
WEAPONDPS=2, BASEARMOR=0.02 } }, WEAPONDPS=1, BASEARMOR=0.01 } },
}, },
} }
@@ -284,36 +314,46 @@ local function AdjustWeightsForLevel(baseWeights, level, isHC)
for k, v in pairs(baseWeights) do adj[k] = v end for k, v in pairs(baseWeights) do adj[k] = v end
if isHC then if isHC then
-- HC: STA stays high at all levels (survival is the point) -- HC: STA always stays high (survival is the whole point)
-- Only reduce hit/crit which are less useful at low levels
if level <= 20 then if level <= 20 then
adj.SPI = (adj.SPI or 0) * 1.3 adj.SPI = math.max((adj.SPI or 0) * 1.3, 0.3)
adj.TOHIT = (adj.TOHIT or 0) * 0.2 adj.TOHIT = (adj.TOHIT or 0) * 0.2
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2 adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2
adj.HEALTHREG = (adj.HEALTHREG or 0) + 1.0 adj.CRIT = (adj.CRIT or 0) * 0.3
adj.ARMOR = (adj.ARMOR or 0) + 0.03 adj.SPELLCRIT = (adj.SPELLCRIT or 0) * 0.3
elseif level <= 40 then adj.HEALTHREG = (adj.HEALTHREG or 0) + 0.8
adj.SPI = (adj.SPI or 0) * 1.15
adj.ARMOR = (adj.ARMOR or 0) + 0.02 adj.ARMOR = (adj.ARMOR or 0) + 0.02
elseif level <= 40 then
adj.TOHIT = (adj.TOHIT or 0) * 0.5 adj.TOHIT = (adj.TOHIT or 0) * 0.5
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.5 adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.5
adj.ARMOR = (adj.ARMOR or 0) + 0.01
else else
adj.TOHIT = (adj.TOHIT or 0) * 0.8 adj.TOHIT = (adj.TOHIT or 0) * 0.8
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.8 adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.8
end end
else else
if level <= 20 then if level <= 20 then
adj.SPI = (adj.SPI or 0) * 2.5 + 0.5 -- PDF: 精神至上 at 1-20; SPI matters for all classes (regen)
adj.STA = (adj.STA or 0) * 0.3 adj.SPI = math.max((adj.SPI or 0) * 1.5, 0.3)
adj.STA = (adj.STA or 0) * 0.7
-- SP/HEAL don't exist on items at this level
adj.DMG = (adj.DMG or 0) * 0.15
adj.HEAL = (adj.HEAL or 0) * 0.15
adj.TOHIT = (adj.TOHIT or 0) * 0.2 adj.TOHIT = (adj.TOHIT or 0) * 0.2
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2 adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.2
adj.HEALTHREG = (adj.HEALTHREG or 0) + 1.5 adj.CRIT = (adj.CRIT or 0) * 0.2
adj.ARMOR = (adj.ARMOR or 0) + 0.03 adj.SPELLCRIT = (adj.SPELLCRIT or 0) * 0.2
elseif level <= 40 then adj.RANGEDCRIT = (adj.RANGEDCRIT or 0) * 0.2
adj.SPI = (adj.SPI or 0) * 1.5 + 0.2 adj.HEALTHREG = (adj.HEALTHREG or 0) + 0.8
adj.ARMOR = (adj.ARMOR or 0) + 0.02 adj.ARMOR = (adj.ARMOR or 0) + 0.02
elseif level <= 40 then
-- PDF: 40级解锁板甲/锁甲, SP开始出现
adj.SPI = math.max((adj.SPI or 0) * 1.2, 0.15)
adj.DMG = (adj.DMG or 0) * 0.5
adj.HEAL = (adj.HEAL or 0) * 0.5
adj.TOHIT = (adj.TOHIT or 0) * 0.6 adj.TOHIT = (adj.TOHIT or 0) * 0.6
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.6 adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.6
adj.ARMOR = (adj.ARMOR or 0) + 0.01
else else
adj.TOHIT = (adj.TOHIT or 0) * 0.85 adj.TOHIT = (adj.TOHIT or 0) * 0.85
adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.85 adj.SPELLTOHIT = (adj.SPELLTOHIT or 0) * 0.85
@@ -517,6 +557,10 @@ local STAT_PATTERNS = {
{ p = "每秒伤害(%d+%.?%d*)", s = "WEAPONDPS" }, { p = "每秒伤害(%d+%.?%d*)", s = "WEAPONDPS" },
{ p = "每秒(%d+%.?%d*)点伤害", s = "WEAPONDPS" }, { p = "每秒(%d+%.?%d*)点伤害", s = "WEAPONDPS" },
-- Weapon attack speed ("速度 1.50" / "Speed 1.50") — stored as raw value
{ p = "^速度 (%d+%.?%d*)", s = "WEAPONSPEED" },
{ p = "^Speed (%d+%.?%d*)", s = "WEAPONSPEED" },
-- Base armor value ("63点护甲" / "123 护甲" / "500 Armor", no + prefix) -- Base armor value ("63点护甲" / "123 护甲" / "500 Armor", no + prefix)
{ p = "^(%d+)点护甲", s = "BASEARMOR" }, { p = "^(%d+)点护甲", s = "BASEARMOR" },
{ p = "^(%d+) 点护甲", s = "BASEARMOR" }, { p = "^(%d+) 点护甲", s = "BASEARMOR" },
@@ -544,16 +588,25 @@ local STAT_PATTERNS = {
{ p = "critical strike with spells by (%d+)%%", s = "SPELLCRIT" }, { p = "critical strike with spells by (%d+)%%", s = "SPELLCRIT" },
{ p = "法术暴击.-(%d+)%%", s = "SPELLCRIT" }, { p = "法术暴击.-(%d+)%%", s = "SPELLCRIT" },
{ p = "法术.-致命一击.-(%d+)%%", s = "SPELLCRIT" }, { p = "法术.-致命一击.-(%d+)%%", s = "SPELLCRIT" },
{ p = "法术.-爆击.-(%d+)%%", s = "SPELLCRIT" },
{ p = "critical strike with ranged weapons by (%d+)%%", s = "RANGEDCRIT" }, { p = "critical strike with ranged weapons by (%d+)%%", s = "RANGEDCRIT" },
{ p = "远程暴击.-(%d+)%%", s = "RANGEDCRIT" }, { p = "远程暴击.-(%d+)%%", s = "RANGEDCRIT" },
{ p = "远程.-致命一击.-(%d+)%%", s = "RANGEDCRIT" },
{ p = "critical strike by (%d+)%%", s = "CRIT" }, { p = "critical strike by (%d+)%%", s = "CRIT" },
{ p = "致命一击几率.-(%d+)%%", s = "CRIT" },
{ p = "致命一击.-提高(%d+)%%", s = "CRIT" },
{ p = "致命一击.-(%d+)%%", s = "CRIT" }, { p = "致命一击.-(%d+)%%", s = "CRIT" },
{ p = "暴击几率.-(%d+)%%", s = "CRIT" },
{ p = "暴击.-(%d+)%%", s = "CRIT" }, { p = "暴击.-(%d+)%%", s = "CRIT" },
-- Hit -- Hit (green equip effects: "使你击中目标的几率提高X%", "chance to hit by X%")
{ p = "hit with spells by (%d+)%%", s = "SPELLTOHIT" }, { p = "hit with spells by (%d+)%%", s = "SPELLTOHIT" },
{ p = "法术击中.-(%d+)%%", s = "SPELLTOHIT" },
{ p = "法术命中.-(%d+)%%", s = "SPELLTOHIT" }, { p = "法术命中.-(%d+)%%", s = "SPELLTOHIT" },
{ p = "用法术击中.-几率.-(%d+)%%", s = "SPELLTOHIT" },
{ p = "chance to hit by (%d+)%%", s = "TOHIT" }, { p = "chance to hit by (%d+)%%", s = "TOHIT" },
{ p = "击中目标.-几率.-(%d+)%%", s = "TOHIT" },
{ p = "击中.-提高(%d+)%%", s = "TOHIT" },
{ p = "命中.-(%d+)%%", s = "TOHIT" }, { p = "命中.-(%d+)%%", s = "TOHIT" },
-- Attack Power -- Attack Power
@@ -591,23 +644,30 @@ local STAT_PATTERNS = {
{ p = "每5秒恢复(%d+)点生命", s = "HEALTHREG" }, { p = "每5秒恢复(%d+)点生命", s = "HEALTHREG" },
{ p = "每5秒回复(%d+)点生命", s = "HEALTHREG" }, { p = "每5秒回复(%d+)点生命", s = "HEALTHREG" },
-- Defense -- Defense (green equip: "提高你的防御技能X点", "+X Defense")
{ p = "Increased Defense %+(%d+)", s = "DEFENSE" }, { p = "Increased Defense %+(%d+)", s = "DEFENSE" },
{ p = "Defense %+(%d+)", s = "DEFENSE" }, { p = "Defense %+(%d+)", s = "DEFENSE" },
{ p = "%+(%d+) Defense", s = "DEFENSE" }, { p = "%+(%d+) Defense", s = "DEFENSE" },
{ p = "防御技能提高(%d+)", s = "DEFENSE" }, { p = "防御技能提高(%d+)", s = "DEFENSE" },
{ p = "防御等级提高(%d+)", s = "DEFENSE" }, { p = "防御等级提高(%d+)", s = "DEFENSE" },
{ p = "防御.-提高(%d+)", s = "DEFENSE" },
-- Avoidance -- Avoidance (green equip: "使你的躲闪几率提高X%")
{ p = "dodge.-by (%d+)%%", s = "DODGE" }, { p = "dodge.-by (%d+)%%", s = "DODGE" },
{ p = "躲闪几率.-(%d+)%%", s = "DODGE" },
{ p = "躲闪.-提高(%d+)%%", s = "DODGE" },
{ p = "躲闪.-(%d+)%%", s = "DODGE" }, { p = "躲闪.-(%d+)%%", s = "DODGE" },
{ p = "parry.-by (%d+)%%", s = "PARRY" }, { p = "parry.-by (%d+)%%", s = "PARRY" },
{ p = "招架几率.-(%d+)%%", s = "PARRY" },
{ p = "招架.-提高(%d+)%%", s = "PARRY" },
{ p = "招架.-(%d+)%%", s = "PARRY" }, { p = "招架.-(%d+)%%", s = "PARRY" },
{ p = "block attacks.-by (%d+)%%", s = "BLOCK" }, { p = "block attacks.-by (%d+)%%", s = "BLOCK" },
{ p = "格挡几率.-(%d+)%%", s = "BLOCK" }, { p = "格挡几率.-(%d+)%%", s = "BLOCK" },
{ p = "格挡.-提高(%d+)%%", s = "BLOCK" },
{ p = "格挡率.-(%d+)%%", s = "BLOCK" }, { p = "格挡率.-(%d+)%%", s = "BLOCK" },
{ p = "block value.-by (%d+)", s = "BLOCKVALUE" }, { p = "block value.-by (%d+)", s = "BLOCKVALUE" },
{ p = "格挡值.-(%d+)", s = "BLOCKVALUE" }, { p = "格挡值.-(%d+)", s = "BLOCKVALUE" },
{ p = "盾牌格挡值.-(%d+)", s = "BLOCKVALUE" },
-- HP/Mana -- HP/Mana
{ p = "%+(%d+) Health", s = "HEALTH" }, { p = "%+(%d+) Health", s = "HEALTH" },
@@ -725,27 +785,67 @@ end
-- ideal_EP = total_budget * best_efficiency_for_spec -- ideal_EP = total_budget * best_efficiency_for_spec
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function GetBestEfficiency(weights) -- Slot-specific and non-standard stats excluded from reference efficiency
local best = 0 local EFF_EXCLUDE = { WEAPONDPS=true, WEAPONSPEED=true, BASEARMOR=true, HEALTH=true, MANA=true }
-- Stats that only appear on higher-level items
local EFF_LATE_GAME = { DMG=true, HEAL=true, SPELLTOHIT=true, SPELLCRIT=true,
TOHIT=true, CRIT=true, RANGEDCRIT=true }
local EFF_MID_GAME = { DMG=true, HEAL=true }
local function GetRefEfficiency(weights, level)
local effs = {}
for stat, w in pairs(weights) do for stat, w in pairs(weights) do
if not EFF_EXCLUDE[stat] then
local cost = BUDGET_COST[stat] local cost = BUDGET_COST[stat]
if cost and cost > 0 and w > 0 then if cost and cost > 0 and w > 0 then
local eff = w / cost local skip = false
if eff > best then best = eff end if level and level < 25 and EFF_LATE_GAME[stat] then skip = true end
if level and level >= 25 and level < 40 and EFF_MID_GAME[stat] then skip = true end
if not skip then
table.insert(effs, w / cost)
end end
end end
return best end
end
table.sort(effs, function(a,b) return a > b end)
local n = table.getn(effs)
local ref = 0
if n >= 3 then
ref = effs[1] * 0.45 + effs[2] * 0.30 + effs[3] * 0.25
elseif n == 2 then
ref = effs[1] * 0.55 + effs[2] * 0.45
elseif n == 1 then
ref = effs[1]
end
if ref <= 0 then ref = 1.0 end
return ref
end end
local function CalcRawEP(bonuses, weights) local DPS_DAMPEN_MELEE = 0.40
local DPS_DAMPEN_RANGED = 0.70
local function CalcRawEP(bonuses, weights, dpsDampen)
if not bonuses or not weights then return 0 end if not bonuses or not weights then return 0 end
dpsDampen = dpsDampen or DPS_DAMPEN_MELEE
local ep = 0 local ep = 0
for stat, value in pairs(bonuses) do for stat, value in pairs(bonuses) do
local w = weights[stat] local w = weights[stat]
if w and w > 0 then if w and w > 0 then
if stat == "WEAPONDPS" then
ep = ep + value * w * dpsDampen
elseif stat == "WEAPONSPEED" then
local speedBonus = 3.0 - value
if speedBonus > 0 then
ep = ep + speedBonus * w
end
else
ep = ep + value * w ep = ep + value * w
end end
end end
end
return ep return ep
end end
@@ -755,21 +855,106 @@ local function CalcTotalBudget(bonuses)
for stat, value in pairs(bonuses) do for stat, value in pairs(bonuses) do
local cost = BUDGET_COST[stat] local cost = BUDGET_COST[stat]
if cost and cost > 0 then if cost and cost > 0 then
if stat == "WEAPONSPEED" then
local speedBonus = 3.0 - value
if speedBonus > 0 then
total = total + speedBonus * cost
end
else
total = total + math.abs(value) * cost total = total + math.abs(value) * cost
end end
end end
end
return total return total
end end
local function CalcNormalizedScore(bonuses, weights, armorCompat, slotCompat) local GS_RANGED_LOCS = {
local rawEP = CalcRawEP(bonuses, weights) INVTYPE_RANGED = true, INVTYPE_RANGEDRIGHT = true, INVTYPE_THROWN = true,
local totalBudget = CalcTotalBudget(bonuses) }
local bestEff = GetBestEfficiency(weights)
if totalBudget <= 0 or bestEff <= 0 then return 0 end --------------------------------------------------------------------------------
-- Horizontal comparison: reference EP for the best rare item at level/slot
--------------------------------------------------------------------------------
local idealEP = totalBudget * bestEff local SLOT_BUDGET_MOD = {
local rawScore = (rawEP / idealEP) * 10 INVTYPE_HEAD = 1.0, INVTYPE_CHEST = 1.0, INVTYPE_ROBE = 1.0, INVTYPE_LEGS = 1.0,
INVTYPE_SHOULDER = 0.77, INVTYPE_HAND = 0.77, INVTYPE_WAIST = 0.77, INVTYPE_FEET = 0.77,
INVTYPE_WRIST = 0.56, INVTYPE_CLOAK = 0.56,
INVTYPE_NECK = 0.56, INVTYPE_FINGER = 0.56, INVTYPE_TRINKET = 0.56,
INVTYPE_WEAPON = 0.42, INVTYPE_WEAPONMAINHAND = 0.42, INVTYPE_WEAPONOFFHAND = 0.36,
INVTYPE_2HWEAPON = 1.0,
INVTYPE_SHIELD = 0.56, INVTYPE_HOLDABLE = 0.42,
INVTYPE_RANGED = 0.32, INVTYPE_RANGEDRIGHT = 0.32, INVTYPE_THROWN = 0.32,
INVTYPE_RELIC = 0.32, INVTYPE_TABARD = 0, INVTYPE_BODY = 0,
}
local WEAPON_EQUIP_LOCS = {
INVTYPE_WEAPON = true, INVTYPE_WEAPONMAINHAND = true, INVTYPE_WEAPONOFFHAND = true,
INVTYPE_2HWEAPON = true, INVTYPE_RANGED = true, INVTYPE_RANGEDRIGHT = true,
INVTYPE_THROWN = true,
}
local ARMOR_EQUIP_LOCS = {
INVTYPE_HEAD = true, INVTYPE_CHEST = true, INVTYPE_ROBE = true, INVTYPE_LEGS = true,
INVTYPE_SHOULDER = true, INVTYPE_HAND = true, INVTYPE_WAIST = true, INVTYPE_FEET = true,
INVTYPE_WRIST = true, INVTYPE_SHIELD = true,
}
local function GetRefWeaponDPS(equipLoc, level)
local dps
if level >= 58 then dps = 48
elseif level >= 40 then dps = 28 + (level - 40) * 0.55
elseif level >= 20 then dps = 13 + (level - 20) * 0.75
elseif level >= 10 then dps = 6 + (level - 10) * 0.7
else dps = 3 + level * 0.3
end
if equipLoc == "INVTYPE_2HWEAPON" then dps = dps * 1.3 end
return dps
end
local function GetReferenceEP(equipLoc, level, weights, refEff)
local slotMod = SLOT_BUDGET_MOD[equipLoc] or 0.56
local refIlvl
if level >= 58 then refIlvl = 63
elseif level >= 40 then refIlvl = level + 7
elseif level >= 20 then refIlvl = level + 5
else refIlvl = level + 3
end
local statBudget = refIlvl * 0.65 * slotMod
local ep = statBudget * refEff
if WEAPON_EQUIP_LOCS[equipLoc] then
local wDPS = weights.WEAPONDPS
if wDPS and wDPS > 0 then
local refDPS = GetRefWeaponDPS(equipLoc, level)
local dampen = GS_RANGED_LOCS[equipLoc] and DPS_DAMPEN_RANGED or DPS_DAMPEN_MELEE
ep = ep + refDPS * wDPS * dampen
end
end
if ARMOR_EQUIP_LOCS[equipLoc] then
local wArmor = weights.BASEARMOR
if wArmor and wArmor > 0 then
local refArmor = level * 1.5 * slotMod
ep = ep + refArmor * wArmor
end
end
return ep
end
local function CalcNormalizedScore(bonuses, weights, armorCompat, slotCompat, level, equipLoc)
local dpsDampen = DPS_DAMPEN_MELEE
if equipLoc and GS_RANGED_LOCS[equipLoc] then dpsDampen = DPS_DAMPEN_RANGED end
local rawEP = CalcRawEP(bonuses, weights, dpsDampen)
local refEff = GetRefEfficiency(weights, level)
local refEP = GetReferenceEP(equipLoc, level, weights, refEff)
if refEP <= 0 then return 0 end
local rawScore = (rawEP / refEP) * 10
local compat = (armorCompat or 1.0) * (slotCompat or 1.0) local compat = (armorCompat or 1.0) * (slotCompat or 1.0)
local finalScore = rawScore * compat local finalScore = rawScore * compat
@@ -800,10 +985,57 @@ local function ScoreLabel(score)
return "不适" return "不适"
end end
--------------------------------------------------------------------------------
-- Score tooltip (uses GameTooltipTemplate — proven reliable in this addon)
-- Created lazily on first use (nil parent like Trade.lua pattern)
--------------------------------------------------------------------------------
local GSTip
local function GS_ShowTip(parentTip, scoreLines)
if not scoreLines or not parentTip then return end
if not GSTip then
GSTip = CreateFrame("GameTooltip", "NanamiGSTooltip", nil, "GameTooltipTemplate")
GSTip:SetFrameStrata("TOOLTIP")
GSTip:SetClampedToScreen(true)
end
GSTip:SetOwner(parentTip, "ANCHOR_NONE")
GSTip:ClearAllPoints()
local bottom = parentTip:GetBottom()
if bottom and bottom > 80 then
GSTip:SetPoint("TOPLEFT", parentTip, "BOTTOMLEFT", 0, 2)
else
GSTip:SetPoint("BOTTOMLEFT", parentTip, "TOPLEFT", 0, -2)
end
for _, entry in ipairs(scoreLines) do
if entry.left and entry.right then
GSTip:AddDoubleLine(entry.left, entry.right,
entry.lr or 1, entry.lg or 1, entry.lb or 1,
entry.rr or 1, entry.rg or 1, entry.rb or 1)
else
GSTip:AddLine(entry.text or "", entry.r or 1, entry.g or 0.84, entry.b or 0)
end
end
GSTip:Show()
end
local function GS_HideFrame()
if GSTip then GSTip:Hide() end
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Main tooltip function -- Main tooltip function
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local GS_SCORE_CACHE = {}
local GS_CACHE_SIZE = 0
local GS_CACHE_MAX = 200
function GS:AddScoreToTooltip(tooltip, link) function GS:AddScoreToTooltip(tooltip, link)
if not tooltip or not link then return end if not tooltip or not link then return end
if SFramesDB and SFramesDB.gearScore == false then return end if SFramesDB and SFramesDB.gearScore == false then return end
@@ -814,9 +1046,20 @@ function GS:AddScoreToTooltip(tooltip, link)
local classData = WEIGHTS[classToken] local classData = WEIGHTS[classToken]
if not classData then return end if not classData then return end
local cacheKey = classToken .. "|" .. link
local cached = GS_SCORE_CACHE[cacheKey]
if cached then
tooltip._gsScoreAdded = true
if cached.scoreLines then
GS_ShowTip(tooltip, cached.scoreLines)
else
GS_HideFrame()
end
return
end
GSDebug("Processing: " .. tostring(link)) GSDebug("Processing: " .. tostring(link))
-- Step 0: Check item quality — skip gray, penalize white
local quality = GetQualityFromLink(link) local quality = GetQualityFromLink(link)
if quality < 0 then if quality < 0 then
quality = GetQualityFromTooltip(tooltip) quality = GetQualityFromTooltip(tooltip)
@@ -828,7 +1071,6 @@ function GS:AddScoreToTooltip(tooltip, link)
end end
GSDebug("Quality=" .. quality .. " mult=" .. qualityMult) GSDebug("Quality=" .. quality .. " mult=" .. qualityMult)
-- Step 1: Try GetItemInfo with extracted item string (more reliable than full link)
local equipLoc, itemClass, itemSubClass local equipLoc, itemClass, itemSubClass
pcall(function() pcall(function()
local itemStr = ExtractItemString(link) local itemStr = ExtractItemString(link)
@@ -850,7 +1092,6 @@ function GS:AddScoreToTooltip(tooltip, link)
.. " class=" .. tostring(itemClass) .. " sub=" .. tostring(itemSubClass)) .. " class=" .. tostring(itemClass) .. " sub=" .. tostring(itemSubClass))
end) end)
-- Step 2: If GetItemInfo failed, parse equip slot from tooltip text
if not equipLoc then if not equipLoc then
local ttEquip, ttArmor, ttClass = ParseEquipLocFromTooltip(tooltip) local ttEquip, ttArmor, ttClass = ParseEquipLocFromTooltip(tooltip)
if ttEquip then if ttEquip then
@@ -861,19 +1102,15 @@ function GS:AddScoreToTooltip(tooltip, link)
end end
end end
-- Skip bags, ammo, tabards
if equipLoc == "INVTYPE_BAG" or equipLoc == "INVTYPE_AMMO" or equipLoc == "INVTYPE_TABARD" then if equipLoc == "INVTYPE_BAG" or equipLoc == "INVTYPE_AMMO" or equipLoc == "INVTYPE_TABARD" then
GSDebug("Skipped: bag/ammo/tabard") GSDebug("Skipped: bag/ammo/tabard")
return return
end end
-- Skip non-equippable items (only if we got valid info)
if equipLoc and equipLoc ~= "" and not GS_EQUIP_LOCS[equipLoc] then if equipLoc and equipLoc ~= "" and not GS_EQUIP_LOCS[equipLoc] then
GSDebug("Skipped: not equippable (" .. tostring(equipLoc) .. ")") GSDebug("Skipped: not equippable (" .. tostring(equipLoc) .. ")")
return return
end end
-- Step 3: Parse stats - try library first, then scan visible tooltip text
local bonuses = ParseItemWithLib(link) local bonuses = ParseItemWithLib(link)
if bonuses then if bonuses then
GSDebug("Stats from ItemBonusLib") GSDebug("Stats from ItemBonusLib")
@@ -889,7 +1126,6 @@ function GS:AddScoreToTooltip(tooltip, link)
return return
end end
-- Step 4: No equipLoc = not equipment (potion, food, etc.) → skip
if not equipLoc or equipLoc == "" then if not equipLoc or equipLoc == "" then
GSDebug("No equipLoc, not equipment, skipping") GSDebug("No equipLoc, not equipment, skipping")
return return
@@ -900,7 +1136,8 @@ function GS:AddScoreToTooltip(tooltip, link)
local isHC = IsPlayerHardcore() local isHC = IsPlayerHardcore()
local armorCompat = GetArmorCompat(classToken, itemClass, itemSubClass, level) local armorCompat = GetArmorCompat(classToken, itemClass, itemSubClass, level)
GSDebug("ArmorCompat=" .. armorCompat .. " class=" .. classToken) GSDebug("ArmorCompat=" .. armorCompat .. " class=" .. classToken
.. " slot=" .. tostring(equipLoc) .. " lv=" .. level)
local specs = classData.specs local specs = classData.specs
if not specs or table.getn(specs) == 0 then return end if not specs or table.getn(specs) == 0 then return end
@@ -910,7 +1147,11 @@ function GS:AddScoreToTooltip(tooltip, link)
for i, spec in ipairs(specs) do for i, spec in ipairs(specs) do
local w = AdjustWeightsForLevel(spec.w, level, false) local w = AdjustWeightsForLevel(spec.w, level, false)
local slotCompat = GetSlotCompat(classToken, i, equipLoc) local slotCompat = GetSlotCompat(classToken, i, equipLoc)
local s = CalcNormalizedScore(bonuses, w, armorCompat, slotCompat) local refEff = GetRefEfficiency(w, level)
local refEP = GetReferenceEP(equipLoc, level, w, refEff)
local s = CalcNormalizedScore(bonuses, w, armorCompat, slotCompat, level, equipLoc)
GSDebug(" " .. spec.name .. ": rawScore=" .. string.format("%.2f", s)
.. " refEP=" .. string.format("%.1f", refEP) .. " refEff=" .. string.format("%.3f", refEff))
s = math.floor(s * qualityMult * 10 + 0.5) / 10 s = math.floor(s * qualityMult * 10 + 0.5) / 10
if s < 1.0 and s > 0 then s = 1.0 end if s < 1.0 and s > 0 then s = 1.0 end
table.insert(scores, { table.insert(scores, {
@@ -926,38 +1167,55 @@ function GS:AddScoreToTooltip(tooltip, link)
local hcScore = 0 local hcScore = 0
if classData.hc then if classData.hc then
local hw = AdjustWeightsForLevel(classData.hc.w, level, true) local hw = AdjustWeightsForLevel(classData.hc.w, level, true)
hcScore = CalcNormalizedScore(bonuses, hw, armorCompat, 1.0) hcScore = CalcNormalizedScore(bonuses, hw, armorCompat, 1.0, level, equipLoc)
if not bonuses.STA or bonuses.STA <= 0 then
hcScore = hcScore * 0.35
end
hcScore = math.floor(hcScore * qualityMult * 10 + 0.5) / 10 hcScore = math.floor(hcScore * qualityMult * 10 + 0.5) / 10
if hcScore < 1.0 and hcScore > 0 then hcScore = 1.0 end if hcScore < 1.0 and hcScore > 0 then hcScore = 1.0 end
if hcScore > 0 then anyShow = true end if hcScore > 0 then anyShow = true end
end end
if not anyShow then return end if not anyShow then
GS_SCORE_CACHE[cacheKey] = { scoreLines = nil }
GS_CACHE_SIZE = GS_CACHE_SIZE + 1
GS_HideFrame()
return
end
tooltip._gsScoreAdded = true tooltip._gsScoreAdded = true
tooltip:AddLine(" ") local scoreLines = {}
tooltip:AddLine("|cffffd700── 装备评分 ──|r") table.insert(scoreLines, { text = "── 装备评分 ──", r = 1, g = 0.84, b = 0 })
for _, sd in ipairs(scores) do for _, sd in ipairs(scores) do
local star = sd.isPrimary and "" or " " local star = sd.isPrimary and "" or " "
local sStr = string.format("%.1f", sd.score) local sStr = string.format("%.1f", sd.score)
local sColor = ScoreColorHex(sd.score) local sColor = ScoreColorHex(sd.score)
local left = star .. "|c" .. sd.color .. sd.name .. "|r" table.insert(scoreLines, {
local right = "|c" .. sColor .. sStr .. " " .. sd.label .. "|r" left = star .. "|c" .. sd.color .. sd.name .. "|r",
tooltip:AddDoubleLine(left, right, 1,1,1, 1,1,1) right = "|c" .. sColor .. sStr .. " " .. sd.label .. "|r",
})
end end
if classData.hc and hcScore > 0 then if classData.hc and hcScore > 0 then
local hcSColor = ScoreColorHex(hcScore) local hcSColor = ScoreColorHex(hcScore)
local hcStar = isHC and "" or " " local hcStar = isHC and "" or " "
local left = hcStar .. "|c" .. classData.hc.color .. "硬核|r" table.insert(scoreLines, {
local right = "|c" .. hcSColor .. string.format("%.1f", hcScore) left = hcStar .. "|c" .. classData.hc.color .. "硬核|r",
.. " " .. ScoreLabel(hcScore) .. "|r" right = "|c" .. hcSColor .. string.format("%.1f", hcScore)
tooltip:AddDoubleLine(left, right, 1,1,1, 1,1,1) .. " " .. ScoreLabel(hcScore) .. "|r",
})
end end
tooltip:Show() GS_ShowTip(tooltip, scoreLines)
if GS_CACHE_SIZE >= GS_CACHE_MAX then
GS_SCORE_CACHE = {}
GS_CACHE_SIZE = 0
end
GS_SCORE_CACHE[cacheKey] = { scoreLines = scoreLines }
GS_CACHE_SIZE = GS_CACHE_SIZE + 1
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -976,6 +1234,7 @@ function GS:HookTooltips()
local origHide = GameTooltip:GetScript("OnHide") local origHide = GameTooltip:GetScript("OnHide")
GameTooltip:SetScript("OnHide", function() GameTooltip:SetScript("OnHide", function()
this._gsScoreAdded = nil this._gsScoreAdded = nil
GS_HideFrame()
if origHide then origHide() end if origHide then origHide() end
end) end)

View File

@@ -411,6 +411,7 @@ local function EnsureBuyPopup()
BuyPopup:Show() BuyPopup:Show()
editbox:SetFocus() editbox:SetFocus()
editbox:HighlightText()
end end
end end

View File

@@ -349,7 +349,7 @@ local function GetDefaultChoices()
mapRevealEnabled = true, mapRevealEnabled = true,
mapRevealAlpha = 0.7, mapRevealAlpha = 0.7,
worldMapEnabled = true, worldMapEnabled = true,
hcGlobalDisable = true, hcGlobalDisable = false,
iconSet = "icon", iconSet = "icon",
} }
end end

View File

@@ -61,6 +61,63 @@ local function BuildClassReverseLookup()
end end
end end
--------------------------------------------------------------------------------
-- Chinese -> English search-term translation for SendWho
--------------------------------------------------------------------------------
local WHO_ZH_TO_EN = {
-- Classes
["战士"] = "Warrior", ["法师"] = "Mage", ["盗贼"] = "Rogue",
["德鲁伊"] = "Druid", ["猎人"] = "Hunter", ["萨满祭司"] = "Shaman",
["萨满"] = "Shaman", ["牧师"] = "Priest", ["术士"] = "Warlock",
["圣骑士"] = "Paladin",
-- Races
["人类"] = "Human", ["矮人"] = "Dwarf", ["暗夜精灵"] = "Night Elf",
["侏儒"] = "Gnome", ["兽人"] = "Orc", ["巨魔"] = "Troll",
["亡灵"] = "Undead", ["牛头人"] = "Tauren", ["高等精灵"] = "High Elf",
["哥布林"] = "Goblin",
-- Zones (Alliance)
["暴风城"] = "Stormwind", ["铁炉堡"] = "Ironforge", ["达纳苏斯"] = "Darnassus",
["艾尔文森林"] = "Elwynn Forest", ["西部荒野"] = "Westfall",
["丹莫罗"] = "Dun Morogh", ["洛克莫丹"] = "Loch Modan",
["湿地"] = "Wetlands", ["赤脊山"] = "Redridge Mountains",
["暮色森林"] = "Duskwood", ["荆棘谷"] = "Stranglethorn Vale",
["泰达希尔"] = "Teldrassil", ["黑海岸"] = "Darkshore",
["灰谷"] = "Ashenvale", ["石爪山脉"] = "Stonetalon Mountains",
-- Zones (Horde)
["奥格瑞玛"] = "Orgrimmar", ["雷霆崖"] = "Thunder Bluff",
["幽暗城"] = "Undercity", ["杜隆塔尔"] = "Durotar",
["莫高雷"] = "Mulgore", ["贫瘠之地"] = "The Barrens",
["银松森林"] = "Silverpine Forest", ["提瑞斯法林地"] = "Tirisfal Glades",
["希尔斯布莱德丘陵"] = "Hillsbrad Foothills",
-- Zones (Contested / High-level)
["塔纳利斯"] = "Tanaris", ["菲拉斯"] = "Feralas",
["凄凉之地"] = "Desolace", ["尘泥沼泽"] = "Dustwallow Marsh",
["千针石林"] = "Thousand Needles", ["辛特兰"] = "The Hinterlands",
["阿拉希高地"] = "Arathi Highlands", ["荒芜之地"] = "Badlands",
["灼热峡谷"] = "Searing Gorge", ["燃烧平原"] = "Burning Steppes",
["西瘟疫之地"] = "Western Plaguelands", ["东瘟疫之地"] = "Eastern Plaguelands",
["费伍德森林"] = "Felwood", ["冬泉谷"] = "Winterspring",
["安戈洛环形山"] = "Un'Goro Crater", ["希利苏斯"] = "Silithus",
["艾萨拉"] = "Azshara", ["诅咒之地"] = "Blasted Lands",
["逆风小径"] = "Deadwind Pass", ["悲伤沼泽"] = "Swamp of Sorrows",
-- Dungeons / Raids
["熔火之心"] = "Molten Core", ["黑翼之巢"] = "Blackwing Lair",
["奥妮克希亚的巢穴"] = "Onyxia's Lair", ["祖尔格拉布"] = "Zul'Gurub",
["安其拉"] = "Ahn'Qiraj", ["纳克萨玛斯"] = "Naxxramas",
["黑石深渊"] = "Blackrock Depths", ["黑石塔"] = "Blackrock Spire",
["斯坦索姆"] = "Stratholme", ["通灵学院"] = "Scholomance",
["厄运之槌"] = "Dire Maul", ["玛拉顿"] = "Maraudon",
["祖尔法拉克"] = "Zul'Farrak",
}
local function TranslateWhoQuery(text)
if not text then return "" end
for zh, en in pairs(WHO_ZH_TO_EN) do
text = string.gsub(text, zh, en)
end
return text
end
local CLASS_ICON_PATH = "Interface\\AddOns\\Nanami-UI\\img\\UI-Classes-Circles" local CLASS_ICON_PATH = "Interface\\AddOns\\Nanami-UI\\img\\UI-Classes-Circles"
local CLASS_ICON_TCOORDS = { local CLASS_ICON_TCOORDS = {
["WARRIOR"] = { 0, 0.25, 0, 0.25 }, ["WARRIOR"] = { 0, 0.25, 0, 0.25 },
@@ -446,6 +503,12 @@ local function CreateScrollArea(parent, w, h)
return container return container
end end
local function WhoDebug(msg)
if DEFAULT_CHAT_FRAME then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffcc[Who调试]|r " .. msg)
end
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Hide Blizzard FriendsFrame -- Hide Blizzard FriendsFrame
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -458,6 +521,55 @@ local function HideBlizzardFriends()
FriendsFrame:ClearAllPoints() FriendsFrame:ClearAllPoints()
FriendsFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000) FriendsFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMRIGHT", 2000, 2000)
FriendsFrame.Show = function() end FriendsFrame.Show = function() end
if SetWhoToUI then
local origSetWhoToUI = SetWhoToUI
SetWhoToUI = function(flag)
if flag ~= 1 then
WhoDebug("拦截 SetWhoToUI(" .. tostring(flag) .. ") -> 强制为1")
end
origSetWhoToUI(1)
end
end
end
--------------------------------------------------------------------------------
-- Who query helper
--------------------------------------------------------------------------------
local whoQueryPending = false
local whoTimeoutFrame = nil
local function DoSendWho(query)
if whoQueryPending and whoTimeoutFrame then
WhoDebug("取消上次挂起的查询")
whoQueryPending = false
whoTimeoutFrame:SetScript("OnUpdate", nil)
end
WhoDebug("发送查询: \"" .. (query or "") .. "\"")
if SetWhoToUI then SetWhoToUI(1) end
whoQueryPending = true
SendWho(query or "")
WhoDebug("SendWho() 已调用, 等待 WHO_LIST_UPDATE...")
if not whoTimeoutFrame then
whoTimeoutFrame = CreateFrame("Frame", nil, UIParent)
end
whoTimeoutFrame.elapsed = 0
whoTimeoutFrame:SetScript("OnUpdate", function()
this.elapsed = (this.elapsed or 0) + (arg1 or 0.016)
if not whoQueryPending then
this:SetScript("OnUpdate", nil)
return
end
if this.elapsed >= 6 then
local n = GetNumWhoResults()
WhoDebug("超时! 6秒未收到事件, 强制刷新, 当前结果=" .. tostring(n))
whoQueryPending = false
this:SetScript("OnUpdate", nil)
SUI:UpdateWhoList()
end
end)
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -902,20 +1014,54 @@ local function BuildWhoPage(page)
searchBar:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0) searchBar:SetPoint("TOPLEFT", page, "TOPLEFT", 0, 0)
searchBar:SetPoint("TOPRIGHT", page, "TOPRIGHT", 0, 0) searchBar:SetPoint("TOPRIGHT", page, "TOPRIGHT", 0, 0)
local editBox = MakeEditBox(searchBar, CONTENT_W - 70, 22) local editBox = MakeEditBox(searchBar, CONTENT_W - 110, 22)
editBox:SetPoint("LEFT", searchBar, "LEFT", 0, 0) editBox:SetPoint("LEFT", searchBar, "LEFT", 0, 0)
editBox:SetScript("OnEnterPressed", function()
local placeholder = editBox:CreateFontString(nil, "ARTWORK")
placeholder:SetFont(GetFont(), 10, "OUTLINE")
placeholder:SetPoint("LEFT", editBox, "LEFT", 6, 0)
placeholder:SetTextColor(T.dimText[1], T.dimText[2], T.dimText[3], 0.6)
placeholder:SetText("名称/等级/职业/种族/区域")
editBox.placeholder = placeholder
editBox:SetScript("OnTextChanged", function()
local text = this:GetText() local text = this:GetText()
if text and text ~= "" then SendWho(text) end if text and text ~= "" then
this.placeholder:Hide()
else
this.placeholder:Show()
end
end)
editBox:SetScript("OnEditFocusGained", function()
if this:GetText() == "" then this.placeholder:Show() end
end)
editBox:SetScript("OnEditFocusLost", function()
if this:GetText() == "" then this.placeholder:Show() end
end)
editBox:SetScript("OnEnterPressed", function()
local text = this:GetText() or ""
SUI:ClearWhoList()
DoSendWho(text)
this:ClearFocus() this:ClearFocus()
end) end)
page.editBox = editBox page.editBox = editBox
local clearBtn = MakeButton(searchBar, "X", 28, 22)
clearBtn:SetPoint("LEFT", editBox, "RIGHT", 2, 0)
clearBtn:SetScript("OnClick", function()
if page.editBox then
page.editBox:SetText("")
page.editBox:SetFocus()
end
end)
local searchBtn = MakeButton(searchBar, "搜索", 64, 22) local searchBtn = MakeButton(searchBar, "搜索", 64, 22)
searchBtn:SetPoint("LEFT", editBox, "RIGHT", 4, 0) searchBtn:SetPoint("LEFT", clearBtn, "RIGHT", 2, 0)
searchBtn:SetScript("OnClick", function() searchBtn:SetScript("OnClick", function()
local text = page.editBox:GetText() local text = page.editBox:GetText() or ""
if text and text ~= "" then SendWho(text) end SUI:ClearWhoList()
DoSendWho(text)
end) end)
-- Column headers -- Column headers
@@ -935,12 +1081,13 @@ local function BuildWhoPage(page)
end end
MakeSep(page, -46) MakeSep(page, -46)
-- Results scroll -- Results scroll (leave 16px above btnBar for totalFS)
local listArea = CreateFrame("Frame", nil, page) local listArea = CreateFrame("Frame", nil, page)
listArea:SetPoint("TOPLEFT", page, "TOPLEFT", 0, -48) listArea:SetPoint("TOPLEFT", page, "TOPLEFT", 0, -48)
listArea:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, BOTTOM_H) listArea:SetPoint("BOTTOMRIGHT", page, "BOTTOMRIGHT", 0, BOTTOM_H + 18)
local wScroll = CreateScrollArea(listArea, CONTENT_W, FRAME_H - HEADER_H - TAB_BAR_H - 48 - BOTTOM_H - 16) local scrollH = FRAME_H - HEADER_H - TAB_BAR_H - 48 - BOTTOM_H - 18
local wScroll = CreateScrollArea(listArea, CONTENT_W, scrollH)
wScroll:SetPoint("TOPLEFT", listArea, "TOPLEFT", 0, 0) wScroll:SetPoint("TOPLEFT", listArea, "TOPLEFT", 0, 0)
page.wScroll = wScroll page.wScroll = wScroll
@@ -1032,6 +1179,27 @@ local function BuildWhoPage(page)
end) end)
end end
function SUI:ClearWhoList()
selectedWho = nil
for i = 1, 50 do
local row = whoRows[i]
if not row then break end
row.nameFS:SetText("")
row.lvlFS:SetText("")
row.classFS:SetText("")
row.zoneFS:SetText("")
SetRowNormal(row)
HideSelHighlight(row)
row:Hide()
end
if pages[2] and pages[2].wScroll then
pages[2].wScroll:SetContentHeight(4)
end
if pages[2] and pages[2].totalFS then
pages[2].totalFS:SetText("搜索中...")
end
end
function SUI:UpdateWhoList() function SUI:UpdateWhoList()
local numWho, totalCount = GetNumWhoResults() local numWho, totalCount = GetNumWhoResults()
local totalH = 0 local totalH = 0
@@ -1120,11 +1288,12 @@ local function BuildGuildPage(page)
end) end)
page.gSearchBox = gSearchBox page.gSearchBox = gSearchBox
local offlineToggle = MakeButton(toolBar, "隐藏离线", 56, 16) local offlineToggle = MakeButton(toolBar, guildHideOffline and "显示离线" or "隐藏离线", 56, 16)
offlineToggle:SetPoint("TOPRIGHT", toolBar, "TOPRIGHT", 0, 0) offlineToggle:SetPoint("TOPRIGHT", toolBar, "TOPRIGHT", 0, 0)
offlineToggle.text:SetFont(GetFont(), 9, "OUTLINE") offlineToggle.text:SetFont(GetFont(), 9, "OUTLINE")
offlineToggle:SetScript("OnClick", function() offlineToggle:SetScript("OnClick", function()
guildHideOffline = not guildHideOffline guildHideOffline = not guildHideOffline
SFramesDB.guildHideOffline = guildHideOffline
if guildHideOffline then if guildHideOffline then
this.text:SetText("显示离线") this.text:SetText("显示离线")
else else
@@ -2099,6 +2268,7 @@ local function ShowPage(tabIdx)
if origShowFriendsAPI then origShowFriendsAPI() end if origShowFriendsAPI then origShowFriendsAPI() end
SUI:UpdateFriendsPage() SUI:UpdateFriendsPage()
elseif tabIdx == 2 then elseif tabIdx == 2 then
if SetWhoToUI then SetWhoToUI(1) end
SUI:UpdateWhoList() SUI:UpdateWhoList()
elseif tabIdx == 3 then elseif tabIdx == 3 then
SUI:UpdateGuildList() SUI:UpdateGuildList()
@@ -2108,8 +2278,17 @@ local function ShowPage(tabIdx)
if MainFrame and MainFrame.titleFS then if MainFrame and MainFrame.titleFS then
local titles = { "好友名单", "查询玩家", "公会", "团队" } local titles = { "好友名单", "查询玩家", "公会", "团队" }
if tabIdx == 3 and IsInGuild() then
local guildName = GetGuildInfo("player")
if guildName and guildName ~= "" then
MainFrame.titleFS:SetText("< " .. guildName .. " >")
else
MainFrame.titleFS:SetText(titles[tabIdx])
end
else
MainFrame.titleFS:SetText(titles[tabIdx] or "社交") MainFrame.titleFS:SetText(titles[tabIdx] or "社交")
end end
end
end end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -2260,6 +2439,8 @@ function SUI:Initialize()
if initialized then return end if initialized then return end
initialized = true initialized = true
guildHideOffline = SFramesDB.guildHideOffline or false
BuildClassReverseLookup() BuildClassReverseLookup()
HideBlizzardFriends() HideBlizzardFriends()
BuildMainFrame() BuildMainFrame()
@@ -2272,11 +2453,29 @@ function SUI:Initialize()
ef:RegisterEvent("RAID_ROSTER_UPDATE") ef:RegisterEvent("RAID_ROSTER_UPDATE")
ef:RegisterEvent("PARTY_MEMBERS_CHANGED") ef:RegisterEvent("PARTY_MEMBERS_CHANGED")
ef:SetScript("OnEvent", function() ef:SetScript("OnEvent", function()
if event == "WHO_LIST_UPDATE" then
local n, t = GetNumWhoResults()
WhoDebug("收到 WHO_LIST_UPDATE! 结果=" .. tostring(n) .. " 总计=" .. tostring(t) .. " pending=" .. tostring(whoQueryPending))
local wasPending = whoQueryPending
whoQueryPending = false
if SetWhoToUI then SetWhoToUI(1) end
if MainFrame and MainFrame:IsShown() and currentMainTab == 2 then
WhoDebug("更新列表显示")
SUI:UpdateWhoList()
else
WhoDebug("自动打开查询页显示结果")
if not MainFrame then BuildMainFrame() end
if MainFrame then
MainFrame:Show()
ShowPage(2)
SUI:UpdateWhoList()
end
end
return
end
if not MainFrame or not MainFrame:IsShown() then return end if not MainFrame or not MainFrame:IsShown() then return end
if event == "FRIENDLIST_UPDATE" or event == "IGNORELIST_UPDATE" then if event == "FRIENDLIST_UPDATE" or event == "IGNORELIST_UPDATE" then
if currentMainTab == 1 then SUI:UpdateFriendsPage() end if currentMainTab == 1 then SUI:UpdateFriendsPage() end
elseif event == "WHO_LIST_UPDATE" then
if currentMainTab == 2 then SUI:UpdateWhoList() end
elseif event == "GUILD_ROSTER_UPDATE" then elseif event == "GUILD_ROSTER_UPDATE" then
if currentMainTab == 3 then SUI:UpdateGuildList() end if currentMainTab == 3 then SUI:UpdateGuildList() end
elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then elseif event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" then
@@ -2314,6 +2513,46 @@ if ShowFriends then
end end
end end
--------------------------------------------------------------------------------
-- Hook SetItemRef: shift-click player name -> WHO query in our panel
--------------------------------------------------------------------------------
do
local origSetItemRef_SUI = SetItemRef
SetItemRef = function(link, text, button)
if link and IsShiftKeyDown and IsShiftKeyDown() then
local playerName = nil
if string.sub(link, 1, 7) == "player:" then
playerName = string.sub(link, 8)
local colonPos = string.find(playerName, ":")
if colonPos then playerName = string.sub(playerName, 1, colonPos - 1) end
end
if playerName and playerName ~= "" then
if SFramesDB and SFramesDB.enableSocial ~= false and initialized then
WhoDebug("Shift点击玩家: " .. playerName .. ", 发起WHO查询")
local query = "n-\"" .. playerName .. "\""
if not MainFrame then BuildMainFrame() end
if MainFrame then
MainFrame:Show()
ShowPage(2)
end
if pages[2] and pages[2].editBox then
pages[2].editBox:SetText(playerName)
if pages[2].editBox.placeholder then
pages[2].editBox.placeholder:Hide()
end
end
SUI:ClearWhoList()
DoSendWho(query)
return
end
end
end
if origSetItemRef_SUI then
origSetItemRef_SUI(link, text, button)
end
end
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- Bootstrap -- Bootstrap
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------

View File

@@ -177,11 +177,18 @@ local function SendTradeWhisper()
return return
end end
table.insert(outLines, "=== 濞存嚎鍊栧Σ妤冩媼閺夎法绉?===") local useCN = (SFramesDB.TradeWhisperLang == "ZH")
if playerMoneyStr then table.insert(outLines, "濞寸姵锚閸ゎ參鏌岄幋婵堫伈: " .. playerMoneyStr) end local header = useCN and "=== 交易完成清单 ===" or "=== Trade Summary ==="
if giveItems ~= "" then table.insert(outLines, "濞寸姵锚閸ゎ參鎮ч埡浣规儌: " .. giveItems) end local lblGiveG = useCN and "我方金币: " or "I gave gold: "
if targetMoneyStr then table.insert(outLines, "闁衡偓閹澘绠梺鍙夊灥缁? " .. targetMoneyStr) end local lblGiveI = useCN and "我方物品: " or "I gave items: "
if getItems ~= "" then table.insert(outLines, "闁衡偓閹澘绠柣妞绘櫅閹? " .. getItems) end local lblGotG = useCN and "对方金币: " or "I got gold: "
local lblGotI = useCN and "对方物品: " or "I got items: "
table.insert(outLines, header)
if playerMoneyStr then table.insert(outLines, lblGiveG .. playerMoneyStr) end
if giveItems ~= "" then table.insert(outLines, lblGiveI .. giveItems) end
if targetMoneyStr then table.insert(outLines, lblGotG .. targetMoneyStr) end
if getItems ~= "" then table.insert(outLines, lblGotI .. getItems) end
for _, line in ipairs(outLines) do for _, line in ipairs(outLines) do
SendLine(line, channel, target) SendLine(line, channel, target)
@@ -195,11 +202,14 @@ local function ClearTradeData()
TRADE_DATA.targetMoney = 0 TRADE_DATA.targetMoney = 0
end end
local tradeWhisperSent = false
local function IsTradeCompleteMsg(msg) local function IsTradeCompleteMsg(msg)
if not msg then return false end if not msg then return false end
if string.find(msg, "Trade successful") then return true end if string.find(msg, "Trade successful") then return true end
if string.find(msg, "Trade complete") then return true end if string.find(msg, "Trade complete") then return true end
if string.find(msg, "Trade complete") then return true end if string.find(msg, "交易完成") then return true end
if string.find(msg, "交易成功") then return true end
return false return false
end end
@@ -216,6 +226,7 @@ end
TradeUI:SetScript("OnEvent", function() TradeUI:SetScript("OnEvent", function()
if event == "TRADE_SHOW" then if event == "TRADE_SHOW" then
tradeWhisperSent = false
TRADE_DATA.active = true TRADE_DATA.active = true
TRADE_DATA.targetName = UnitName("NPC") or "" TRADE_DATA.targetName = UnitName("NPC") or ""
TRADE_DATA.playerItems = {} TRADE_DATA.playerItems = {}
@@ -243,16 +254,23 @@ TradeUI:SetScript("OnEvent", function()
SaveTradeState() SaveTradeState()
ForceRefreshTradeVisuals() ForceRefreshTradeVisuals()
elseif event == "TRADE_CLOSED" then elseif event == "TRADE_CLOSED" then
if TRADE_DATA.playerAccepted and TRADE_DATA.targetAccepted and not tradeWhisperSent then
tradeWhisperSent = true
SendTradeWhisper()
ClearTradeData()
end
TRADE_DATA.active = false TRADE_DATA.active = false
TRADE_DATA.playerAccepted = false TRADE_DATA.playerAccepted = false
TRADE_DATA.targetAccepted = false TRADE_DATA.targetAccepted = false
elseif event == "UI_INFO_MESSAGE" then elseif event == "UI_INFO_MESSAGE" then
if IsTradeCompleteMsg(arg1) then if IsTradeCompleteMsg(arg1) and not tradeWhisperSent then
tradeWhisperSent = true
SendTradeWhisper() SendTradeWhisper()
ClearTradeData() ClearTradeData()
end end
elseif event == "CHAT_MSG_SYSTEM" then elseif event == "CHAT_MSG_SYSTEM" then
if IsTradeCompleteMsg(arg1) then if IsTradeCompleteMsg(arg1) and not tradeWhisperSent then
tradeWhisperSent = true
SendTradeWhisper() SendTradeWhisper()
ClearTradeData() ClearTradeData()
end end
@@ -305,7 +323,7 @@ local function ScanItemLevelFromTooltip()
local text = line:GetText() local text = line:GetText()
if text then if text then
local _, _, ilvl = string.find(text, "(%d+)") local _, _, ilvl = string.find(text, "(%d+)")
if string.find(text, "Item Level") or string.find(text, "iLvl") or string.find(text, "ilvl") or string.find(text, "鐗╁搧绛夌骇") then if string.find(text, "Item Level") or string.find(text, "iLvl") or string.find(text, "ilvl") or string.find(text, "物品等级") then
if ilvl then return tonumber(ilvl) end if ilvl then return tonumber(ilvl) end
end end
end end
@@ -1025,17 +1043,19 @@ local function SkinTradeFrame()
SFramesDB = SFramesDB or {} SFramesDB = SFramesDB or {}
local selected = SFramesDB.TradeWhisperChannel or "WHISPER" local selected = SFramesDB.TradeWhisperChannel or "WHISPER"
for _, info in ipairs(channels) do for _, info in ipairs(channels) do
local capturedText = info.text
local capturedValue = info.value
local d = {} local d = {}
d.text = info.text d.text = capturedText
d.value = info.value d.value = capturedValue
d.func = function() d.func = function()
SFramesDB = SFramesDB or {} SFramesDB = SFramesDB or {}
SFramesDB.TradeWhisperChannel = this.value SFramesDB.TradeWhisperChannel = capturedValue
UIDropDownMenu_SetSelectedValue(drop, this.value) UIDropDownMenu_SetSelectedValue(drop, capturedValue)
local txt = _G[drop:GetName() .. "Text"] local txt = _G[drop:GetName() .. "Text"]
if txt then txt:SetText(info.text) end if txt then txt:SetText(capturedText) end
end end
d.checked = (info.value == selected) d.checked = (capturedValue == selected)
UIDropDownMenu_AddButton(d) UIDropDownMenu_AddButton(d)
end end
end end
@@ -1051,6 +1071,55 @@ local function SkinTradeFrame()
end end
end end
end end
-- Language dropdown (EN / ZH)
local langDrop = CreateFrame("Frame", "SFramesTradeLangObj", TradeFrame, "UIDropDownMenuTemplate")
langDrop:SetPoint("LEFT", drop, "RIGHT", -16, 0)
UIDropDownMenu_SetWidth(50, langDrop)
local langDropText = _G[langDrop:GetName() .. "Text"]
if langDropText then
langDropText:SetFont(GetFont(), 10, "OUTLINE")
langDropText:SetTextColor(T.nameText[1], T.nameText[2], T.nameText[3])
end
local langs = {
{ text = "EN", value = "EN" },
{ text = "中文", value = "ZH" },
}
local function TradeLangDropInit()
SFramesDB = SFramesDB or {}
local selected = SFramesDB.TradeWhisperLang or "EN"
for _, info in ipairs(langs) do
local capText = info.text
local capValue = info.value
local d = {}
d.text = capText
d.value = capValue
d.func = function()
SFramesDB = SFramesDB or {}
SFramesDB.TradeWhisperLang = capValue
UIDropDownMenu_SetSelectedValue(langDrop, capValue)
local txt = _G[langDrop:GetName() .. "Text"]
if txt then txt:SetText(capText) end
end
d.checked = (capValue == selected)
UIDropDownMenu_AddButton(d)
end
end
UIDropDownMenu_Initialize(langDrop, TradeLangDropInit)
SFramesDB = SFramesDB or {}
UIDropDownMenu_SetSelectedValue(langDrop, SFramesDB.TradeWhisperLang or "EN")
if langDropText then
for _, info in ipairs(langs) do
if info.value == (SFramesDB.TradeWhisperLang or "EN") then
langDropText:SetText(info.text)
break
end
end
end
end end
-- Close button -- Close button

View File

@@ -333,6 +333,18 @@ local function CreateActionBtn(parent, text, w)
return btn return btn
end end
--------------------------------------------------------------------------------
-- Quality cache (lazy per-row instead of bulk tooltip scan)
--------------------------------------------------------------------------------
local qualityCache = {}
local function GetCachedServiceQuality(index)
if qualityCache[index] ~= nil then return qualityCache[index] end
local q = GetServiceQuality(index)
qualityCache[index] = q or false
return q
end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
-- List Row Factory (reusable for both headers and services) -- List Row Factory (reusable for both headers and services)
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
@@ -530,8 +542,9 @@ local function CreateListRow(parent, idx)
self.icon:SetVertexColor(T.passive[1], T.passive[2], T.passive[3]) self.icon:SetVertexColor(T.passive[1], T.passive[2], T.passive[3])
end end
local qc = QUALITY_COLORS[svc.quality] local quality = GetCachedServiceQuality(svc.index)
if qc and svc.quality and svc.quality >= 2 then local qc = QUALITY_COLORS[quality]
if qc and quality and quality >= 2 then
self.qualGlow:SetVertexColor(qc[1], qc[2], qc[3]) self.qualGlow:SetVertexColor(qc[1], qc[2], qc[3])
self.qualGlow:Show() self.qualGlow:Show()
self.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1) self.iconFrame:SetBackdropBorderColor(qc[1], qc[2], qc[3], 1)
@@ -607,7 +620,6 @@ local function BuildDisplayList()
name = name, name = name,
subText = subText or "", subText = subText or "",
category = category or "unavailable", category = category or "unavailable",
quality = GetServiceQuality(i),
}) })
end end
end end
@@ -1172,19 +1184,22 @@ function TUI:Initialize()
selectedIndex = nil selectedIndex = nil
currentFilter = "all" currentFilter = "all"
collapsedCats = {} collapsedCats = {}
qualityCache = {}
local npcName = UnitName("npc") or "训练师" local npcName = UnitName("npc") or "训练师"
if IsTradeskillTrainer and IsTradeskillTrainer() then if IsTradeskillTrainer and IsTradeskillTrainer() then
npcName = npcName .. " - 专业训练" npcName = npcName .. " - 专业训练"
end end
MainFrame.npcNameFS:SetText(npcName) MainFrame.npcNameFS:SetText(npcName)
MainFrame:Show() MainFrame:Show()
FullUpdate() BuildDisplayList()
for _, entry in ipairs(displayList) do for _, entry in ipairs(displayList) do
if entry.type == "service" then if entry.type == "service" then
SelectService(entry.data.index) selectedIndex = entry.data.index
pcall(SelectTrainerService, entry.data.index)
break break
end end
end end
FullUpdate()
MainFrame._hideBlizzTimer = 0 MainFrame._hideBlizzTimer = 0
MainFrame:SetScript("OnUpdate", function() MainFrame:SetScript("OnUpdate", function()

View File

@@ -438,6 +438,36 @@ function SFrames.Player:Initialize()
self:ShowTrainerReminder(arg1) self:ShowTrainerReminder(arg1)
end end
end) end)
SFrames:RegisterEvent("TRAINER_SHOW", function()
SFrames.Player.trainerScannedThisVisit = nil
SFrames.Player.trainerShowPending = true
SFrames.Player.trainerRetryCount = 0
if not SFrames.Player.trainerRetryFrame then
SFrames.Player.trainerRetryFrame = CreateFrame("Frame")
end
SFrames.Player.trainerRetryFrame:SetScript("OnUpdate", function()
if not this.elapsed then this.elapsed = 0 end
this.elapsed = this.elapsed + arg1
if this.elapsed < 0.3 then return end
this.elapsed = 0
if SFrames.Player.trainerScannedThisVisit then
this:SetScript("OnUpdate", nil)
return
end
SFrames.Player.trainerRetryCount = (SFrames.Player.trainerRetryCount or 0) + 1
if SFrames.Player.trainerRetryCount > 10 then
SFrames.Player:ScanTrainer()
this:SetScript("OnUpdate", nil)
return
end
SFrames.Player:ScanTrainer()
end)
end)
SFrames:RegisterEvent("TRAINER_UPDATE", function()
if not SFrames.Player.trainerScannedThisVisit then
SFrames.Player:ScanTrainer()
end
end)
SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() self:UpdateLeaderIcon() end) SFrames:RegisterEvent("PARTY_MEMBERS_CHANGED", function() self:UpdateLeaderIcon() end)
SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateLeaderIcon() end) SFrames:RegisterEvent("PARTY_LEADER_CHANGED", function() self:UpdateLeaderIcon() end)
SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end) SFrames:RegisterEvent("RAID_TARGET_UPDATE", function() self:UpdateRaidIcon() end)
@@ -458,11 +488,13 @@ function SFrames.Player:Initialize()
end end
function SFrames.Player:HasSpellInBook(spellName) function SFrames.Player:HasSpellInBook(spellName)
local baseName = string.gsub(spellName, " 等级 %d+$", "")
baseName = string.gsub(baseName, " %d+级$", "")
local i = 1 local i = 1
while true do while true do
local name = GetSpellName(i, BOOKTYPE_SPELL) local name = GetSpellName(i, BOOKTYPE_SPELL)
if not name then return false end if not name then return false end
if name == spellName then return true end if name == spellName or name == baseName then return true end
i = i + 1 i = i + 1
end end
end end
@@ -483,6 +515,167 @@ function SFrames.Player:GetSpellIcon(skillDisplayName)
return nil return nil
end end
function SFrames.Player:ParseTrainerTooltipLevel(serviceIndex)
if not self.trainerScanTip then
local tt = CreateFrame("GameTooltip", "NanamiTrainerScanTip", nil, "GameTooltipTemplate")
tt:SetOwner(UIParent, "ANCHOR_NONE")
self.trainerScanTip = tt
end
local tt = self.trainerScanTip
tt:ClearLines()
if not tt.SetTrainerService then return nil end
tt:SetTrainerService(serviceIndex)
for j = 2, tt:NumLines() do
local textObj = getglobal("NanamiTrainerScanTipTextLeft" .. j)
if textObj then
local text = textObj:GetText()
if text then
local _, _, lvl = string.find(text, "需要等级%s*(%d+)")
if not lvl then
_, _, lvl = string.find(text, "Requires Level (%d+)")
end
if lvl then return tonumber(lvl) end
end
end
end
return nil
end
function SFrames.Player:FindSkillLevelInStaticData(classEn, skillName)
local staticData = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn]
if not staticData then return nil end
local baseName = string.gsub(skillName, " %d+级$", "")
baseName = string.gsub(baseName, "(等级 %d+$", "")
baseName = string.gsub(baseName, "%s+$", "")
for level, skills in pairs(staticData) do
for _, s in ipairs(skills) do
local sBase = string.gsub(s, " %d+级$", "")
sBase = string.gsub(sBase, "(等级 %d+$", "")
sBase = string.gsub(sBase, "%s+$", "")
if baseName == sBase or skillName == s then
return level
end
end
end
local talentData = SFrames.TalentTrainerSkills and SFrames.TalentTrainerSkills[classEn]
if talentData then
for level, entries in pairs(talentData) do
for _, entry in ipairs(entries) do
local tBase = string.gsub(entry[1], " %d+级$", "")
if baseName == tBase or skillName == entry[1] then
return level
end
end
end
end
return nil
end
function SFrames.Player:ScanTrainer()
if self.scanningTrainer then return end
self.scanningTrainer = true
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
if not GetNumTrainerServices then
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff6666训练师扫描失败: GetNumTrainerServices 不存在|r")
self.scanningTrainer = nil
return
end
if IsTradeskillTrainer and IsTradeskillTrainer() then
self.scanningTrainer = nil
return
end
local _, classEn = UnitClass("player")
if not classEn or not SFramesDB then self.scanningTrainer = nil return end
local cache = {}
local numServices = GetNumTrainerServices()
if not numServices or numServices == 0 then
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff6666训练师扫描: 未检测到技能列表 (0项),将在数据加载后重试|r")
self.scanningTrainer = nil
return
end
local hasLevelAPI = (GetTrainerServiceLevelReq ~= nil)
local noLevelCount = 0
local totalAdded = 0
local iconMissCount = 0
for i = 1, numServices do
local name, subText, serviceType = GetTrainerServiceInfo(i)
if name and subText and subText ~= "" then
local icon = GetTrainerServiceIcon and GetTrainerServiceIcon(i)
if (not icon or icon == "") and ClassTrainerFrame then
local skillButton = getglobal("ClassTrainerSkill" .. i)
if skillButton then
local iconTex = getglobal("ClassTrainerSkill" .. i .. "Icon")
if iconTex and iconTex.GetTexture then
icon = iconTex:GetTexture()
end
end
end
if not icon or icon == "" then
iconMissCount = iconMissCount + 1
end
local reqLevel
if hasLevelAPI then
reqLevel = GetTrainerServiceLevelReq(i)
end
if not reqLevel or reqLevel == 0 then
reqLevel = self:ParseTrainerTooltipLevel(i)
end
if not reqLevel or reqLevel == 0 then
local lookupName = name .. " " .. subText
reqLevel = self:FindSkillLevelInStaticData(classEn, lookupName)
if not reqLevel then
reqLevel = self:FindSkillLevelInStaticData(classEn, name)
end
end
if not reqLevel or reqLevel == 0 then
noLevelCount = noLevelCount + 1
end
if reqLevel and reqLevel > 0 then
local displayName = name .. " " .. subText
if not cache[reqLevel] then
cache[reqLevel] = {}
end
table.insert(cache[reqLevel], {
name = displayName,
icon = icon or "",
})
totalAdded = totalAdded + 1
end
end
end
if not SFramesDB.trainerCache then
SFramesDB.trainerCache = {}
end
SFramesDB.trainerCache[classEn] = cache
self.trainerScannedThisVisit = true
local levelCount = 0
for _ in pairs(cache) do levelCount = levelCount + 1 end
local iconOk = totalAdded - iconMissCount
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 已从训练师更新技能数据(" .. levelCount .. " 个等级," .. totalAdded .. " 项技能," .. iconOk .. " 个图标)")
if noLevelCount > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff9900有 " .. noLevelCount .. " 项技能无法确定等级要求,已跳过|r")
end
if iconMissCount > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cffff9900有 " .. iconMissCount .. " 项技能未获取到图标|r")
end
self.scanningTrainer = nil
end
function SFrames.Player:ShowTrainerReminder(newLevel) function SFrames.Player:ShowTrainerReminder(newLevel)
local _, classEn = UnitClass("player") local _, classEn = UnitClass("player")
local classNames = { local classNames = {
@@ -507,7 +700,31 @@ function SFrames.Player:ShowTrainerReminder(newLevel)
local allSkills = {} local allSkills = {}
local allIcons = {} local allIcons = {}
local talentSkills = {}
local usedCache = false
local classCache = SFramesDB and SFramesDB.trainerCache and SFramesDB.trainerCache[classEn]
if classCache then
local lowLevel = newLevel - 1
if lowLevel < 1 then lowLevel = 1 end
for lv = lowLevel, newLevel do
if classCache[lv] then
usedCache = true
for _, entry in ipairs(classCache[lv]) do
if not self:HasSpellInBook(entry.name) then
table.insert(allSkills, entry.name)
local ico = entry.icon
if not ico or ico == "" then
ico = self:GetSpellIcon(entry.name) or fallbackIcon
end
table.insert(allIcons, ico)
end
end
end
end
end
if not usedCache then
local baseSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn] and SFrames.ClassSkillData[classEn][newLevel] local baseSkills = SFrames.ClassSkillData and SFrames.ClassSkillData[classEn] and SFrames.ClassSkillData[classEn][newLevel]
if baseSkills then if baseSkills then
for _, s in ipairs(baseSkills) do for _, s in ipairs(baseSkills) do
@@ -517,7 +734,6 @@ function SFrames.Player:ShowTrainerReminder(newLevel)
end end
local talentData = SFrames.TalentTrainerSkills and SFrames.TalentTrainerSkills[classEn] and SFrames.TalentTrainerSkills[classEn][newLevel] local talentData = SFrames.TalentTrainerSkills and SFrames.TalentTrainerSkills[classEn] and SFrames.TalentTrainerSkills[classEn][newLevel]
local talentSkills = {}
if talentData then if talentData then
for _, entry in ipairs(talentData) do for _, entry in ipairs(talentData) do
local displayName = entry[1] local displayName = entry[1]
@@ -529,10 +745,16 @@ function SFrames.Player:ShowTrainerReminder(newLevel)
end end
end end
end end
end
local mountQuest = SFrames.ClassMountQuests and SFrames.ClassMountQuests[classEn] and SFrames.ClassMountQuests[classEn][newLevel] local mountQuest = SFrames.ClassMountQuests and SFrames.ClassMountQuests[classEn] and SFrames.ClassMountQuests[classEn][newLevel]
local skillCount = table.getn(allSkills) local skillCount = table.getn(allSkills)
if skillCount == 0 and not mountQuest then
return
end
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9" local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
SFrames:Print(string.format("已达到 %d 级!你的%s训练师有新技能可以学习。", newLevel, className)) SFrames:Print(string.format("已达到 %d 级!你的%s训练师有新技能可以学习。", newLevel, className))
@@ -551,7 +773,7 @@ function SFrames.Player:ShowTrainerReminder(newLevel)
end end
end end
end end
if table.getn(talentSkills) > 0 then if not usedCache and table.getn(talentSkills) > 0 then
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cff00ff00(天赋)|r " .. table.concat(talentSkills, ", ")) DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r |cff00ff00(天赋)|r " .. table.concat(talentSkills, ", "))
end end
if mountQuest then if mountQuest then
@@ -648,6 +870,7 @@ function SFrames.Player:ShowTrainerReminder(newLevel)
end end
local iconCount = 0 local iconCount = 0
for i = 1, skillCount do for i = 1, skillCount do
if iconCount >= 13 then break end if iconCount >= 13 then break end
iconCount = iconCount + 1 iconCount = iconCount + 1
@@ -662,28 +885,32 @@ function SFrames.Player:ShowTrainerReminder(newLevel)
fr.skillBorders[iconCount]:Show() fr.skillBorders[iconCount]:Show()
end end
if iconCount > 0 then
fr:SetHeight(106)
else
fr:SetHeight(72)
end
fr.title:SetText(string.format("已达到 |cffffffff%d|r 级 — %s训练师有新技能", newLevel, className)) fr.title:SetText(string.format("已达到 |cffffffff%d|r 级 — %s训练师有新技能", newLevel, className))
if skillCount > 0 or mountQuest then if skillCount > 0 or mountQuest then
local countText = "" local preview = ""
if skillCount > 0 then if skillCount > 0 then
countText = skillCount .. " 项技能" preview = "|cffffd100" .. allSkills[1] .. "|r"
if skillCount > 1 then preview = preview .. ", |cffffd100" .. allSkills[2] .. "|r" end
if skillCount > 2 then preview = preview .. ", |cffffd100" .. allSkills[3] .. "|r" end
if skillCount > 3 then preview = preview .. "" .. skillCount .. "" end
end end
if mountQuest then if mountQuest then
if countText ~= "" then countText = countText .. " + " end if preview ~= "" then preview = preview .. " | " end
countText = countText .. mountQuest preview = preview .. "|cffffff00" .. mountQuest .. "|r"
end end
fr.subtitle:SetText(countText) fr.subtitle:SetText(preview)
fr.detail:SetText("详见聊天窗口") fr.detail:SetText("详见聊天窗口")
else else
fr.subtitle:SetText("") fr.subtitle:SetText("")
fr.detail:SetText("前往职业训练师查看可学习的技能") fr.detail:SetText("前往职业训练师查看可学习的技能")
end end
if iconCount > 0 then
fr:SetHeight(106)
else
fr:SetHeight(80)
end
fr:Show() fr:Show()
fr:SetAlpha(0) fr:SetAlpha(0)

View File

@@ -857,6 +857,31 @@ function SFrames.Whisper:Toggle()
end end
end end
-- Filter out addon sync/data messages that abuse the whisper channel.
-- These are not real conversations and should not appear in the whisper UI.
local ADDON_WHISPER_PATTERNS = {
"^%[%u+:%u+%]", -- [ST:HB], [GS:REQ], etc.
"^%u+:%u+:%d", -- ADDON:CMD:data
"^<.->.+", -- <AddonName>data
"^%$%u+%$", -- $ADDON$
"^##%u+##", -- ##ADDON##
"^{.-}", -- {json-like data}
"^LVGS:", -- LevelGearSync
"^EEQ:", -- EquipExchange
"^GS:%d", -- GearScore sync
"^ST:%u", -- SpellTimer
}
local function IsAddonWhisper(text)
if not text or text == "" then return false end
for _, pat in ipairs(ADDON_WHISPER_PATTERNS) do
if string.find(text, pat) then return true end
end
-- Pure numeric data (e.g. "30343.996") with no real words
if string.find(text, "^[%d%.%s%-]+$") then return true end
return false
end
-- Hook Events -- Hook Events
local eventFrame = CreateFrame("Frame") local eventFrame = CreateFrame("Frame")
eventFrame:RegisterEvent("CHAT_MSG_WHISPER") eventFrame:RegisterEvent("CHAT_MSG_WHISPER")
@@ -871,6 +896,7 @@ eventFrame:SetScript("OnEvent", function()
local text = arg1 local text = arg1
local sender = arg2 local sender = arg2
if not sender or sender == "" then return end if not sender or sender == "" then return end
if IsAddonWhisper(text) then return end
sender = string.gsub(sender, "-.*", "") -- remove realm name if attached sender = string.gsub(sender, "-.*", "") -- remove realm name if attached

View File

@@ -1259,6 +1259,7 @@ end
-------------------------------------------------------------------------------- --------------------------------------------------------------------------------
local function TransformMapLinks(text) local function TransformMapLinks(text)
if not text or type(text) ~= "string" then return text end if not text or type(text) ~= "string" then return text end
if not string.find(text, "<npin:", 1, true) then return text end
local result = string.gsub(text, "<npin:(%d+):(%d+):([%d%.]+):([%d%.]+):([^>]*)>", local result = string.gsub(text, "<npin:(%d+):(%d+):([%d%.]+):([%d%.]+):([^>]*)>",
function(c, z, x, y, name) function(c, z, x, y, name)
local display = name local display = name