修改优化等
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
167
Chat.lua
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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级", "闪避"},
|
||||||
|
|||||||
18
ConfigUI.lua
18
ConfigUI.lua
@@ -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)
|
||||||
|
|||||||
218
FlightData.lua
218
FlightData.lua
@@ -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,
|
||||||
}
|
}
|
||||||
541
GearScore.lua
541
GearScore.lua
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -411,6 +411,7 @@ local function EnsureBuyPopup()
|
|||||||
|
|
||||||
BuyPopup:Show()
|
BuyPopup:Show()
|
||||||
editbox:SetFocus()
|
editbox:SetFocus()
|
||||||
|
editbox:HighlightText()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
263
SocialUI.lua
263
SocialUI.lua
@@ -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
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
|||||||
99
Trade.lua
99
Trade.lua
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
255
Units/Player.lua
255
Units/Player.lua
@@ -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)
|
||||||
|
|
||||||
|
|||||||
26
Whisper.lua
26
Whisper.lua
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user