修复猎人野兽训练展示技能所需训练点数错误问题

修复拾取问题
修复其他已知问题
修复自动下马问题以及带来的猎人守护自动关闭问题
彻底修复拾取界面问题
修复目标框架的施法条监控
修复其他已知问题
调整dps插件对仇恨的估算方式
优化dps插件
修复远程攻击条问题
This commit is contained in:
rucky
2026-03-25 00:56:49 +08:00
parent c0f1ecc713
commit c7dd0f4848
9 changed files with 393 additions and 266 deletions

198
Chat.lua
View File

@@ -374,12 +374,23 @@ function SFrames:RefreshClassColorCache()
PersistClassCache()
end
SFrames._classMissCache = {}
SFrames._classMissCacheTime = 0
function SFrames:GetClassHexForName(name)
if not name or name == "" then return nil end
local cache = self.PlayerClassColorCache
if cache[name] then return cache[name] end
if self._classMissCache[name] then return nil end
self:RefreshClassColorCache()
return cache[name]
if cache[name] then return cache[name] end
self._classMissCache[name] = true
local now = GetTime()
if now - self._classMissCacheTime > 30 then
self._classMissCacheTime = now
self._classMissCache = {}
end
return nil
end
function SFrames:GetLevelForName(name)
@@ -730,28 +741,25 @@ end
local function ParseHardcoreDeathMessage(text)
if type(text) ~= "string" or text == "" then return nil end
if not string.find(text, "硬核") and not string.find(text, "死亡") then
local lower = string.lower(text)
if not string.find(lower, "hc news") and not string.find(lower, "has fallen")
and not string.find(lower, "died") and not string.find(lower, "slain") then
return nil
end
end
local clean = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
clean = string.gsub(clean, "|r", "")
-- Check for Hardcore death signatures
if string.find(string.lower(clean), "hc news") or string.find(clean, "硬核") or string.find(clean, "死亡") or string.find(string.lower(clean), "has fallen") or string.find(string.lower(clean), "died") or string.find(string.lower(clean), "slain") then
-- Turtle English "Level 14"
local _, _, lvlStr = string.find(clean, "Level%s+(%d+)")
if lvlStr then return tonumber(lvlStr) end
-- Chinese "14级"
local _, _, lvlStr2 = string.find(clean, "(%d+)%s*级")
if lvlStr2 then return tonumber(lvlStr2) end
-- Fallback
local _, _, lvlStr3 = string.find(clean, "Level:%s+(%d+)")
if lvlStr3 then return tonumber(lvlStr3) end
-- If it matches death signatures but no level is found, return 1 as a baseline to trigger the filter.
if string.find(string.lower(clean), "hc news") or (string.find(clean, "硬核") and (string.find(clean, "死亡") or string.find(clean, "has fallen"))) then
local lower = string.lower(clean)
if string.find(lower, "hc news") or (string.find(clean, "硬核") and (string.find(clean, "死亡") or string.find(lower, "has fallen"))) then
return 1
end
end
return nil
end
@@ -1527,6 +1535,8 @@ local function EnsureDB()
if type(db.editBoxPosition) ~= "string" then db.editBoxPosition = DEFAULTS.editBoxPosition end
if type(db.editBoxX) ~= "number" then db.editBoxX = DEFAULTS.editBoxX end
if type(db.editBoxY) ~= "number" then db.editBoxY = DEFAULTS.editBoxY end
if db.translateEnabled == nil then db.translateEnabled = true end
if db.chatMonitorEnabled == nil then db.chatMonitorEnabled = true end
if type(db.layoutVersion) ~= "number" then db.layoutVersion = 1 end
if db.layoutVersion < 2 then
db.topPadding = DEFAULTS.topPadding
@@ -3137,6 +3147,7 @@ function SFrames.Chat:ToggleConfigFrame()
end
local CONFIG_PAGE_ORDER = {
{ key = "general", label = "通用", title = "通用设置", desc = "翻译引擎和聊天消息监控的总开关。", icon = "settings" },
{ key = "window", label = "窗口", title = "聊天窗口", desc = "尺寸、缩放、边框和输入框位置。", icon = "settings" },
{ key = "tabs", label = "标签", title = "标签管理", desc = "切换、重命名、新建和删除聊天标签。", icon = "chat" },
{ key = "filters", label = "过滤", title = "消息过滤", desc = "为当前标签设置消息类型和频道接收规则。", icon = "settings" },
@@ -3442,6 +3453,57 @@ function SFrames.Chat:EnsureConfigFrame()
return page
end
local generalPage = CreatePage("general")
do
local engineSection = CreateCfgSection(generalPage, "翻译引擎", 0, 0, 584, 120, fontPath)
AddControl(CreateCfgCheck(engineSection, "启用 AI 翻译引擎", 16, -30,
function() return EnsureDB().translateEnabled ~= false end,
function(checked)
EnsureDB().translateEnabled = (checked == true)
end,
function()
SFrames.Chat:RefreshConfigFrame()
end
))
local transDesc = engineSection:CreateFontString(nil, "OVERLAY")
transDesc:SetFont(fontPath, 10, "OUTLINE")
transDesc:SetPoint("TOPLEFT", engineSection, "TOPLEFT", 38, -50)
transDesc:SetWidth(520)
transDesc:SetJustifyH("LEFT")
transDesc:SetText("关闭后将完全停止调用 STranslateAPI 翻译接口,所有标签的自动翻译均不生效。")
transDesc:SetTextColor(0.7, 0.7, 0.74)
local monitorSection = CreateCfgSection(generalPage, "聊天消息监控", 0, -136, 584, 160, fontPath)
AddControl(CreateCfgCheck(monitorSection, "启用聊天消息监控与收集", 16, -30,
function() return EnsureDB().chatMonitorEnabled ~= false end,
function(checked)
EnsureDB().chatMonitorEnabled = (checked == true)
end,
function()
SFrames.Chat:RefreshConfigFrame()
end
))
local monDesc = monitorSection:CreateFontString(nil, "OVERLAY")
monDesc:SetFont(fontPath, 10, "OUTLINE")
monDesc:SetPoint("TOPLEFT", monitorSection, "TOPLEFT", 38, -50)
monDesc:SetWidth(520)
monDesc:SetJustifyH("LEFT")
monDesc:SetText("启用后将拦截聊天消息,提供消息历史缓存、右键复制 [+] 标记、频道翻译触发等功能。\n关闭后消息将原样通过,不做任何处理(翻译、复制等功能不可用)。")
monDesc:SetTextColor(0.7, 0.7, 0.74)
local reloadHint = monitorSection:CreateFontString(nil, "OVERLAY")
reloadHint:SetFont(fontPath, 10, "OUTLINE")
reloadHint:SetPoint("TOPLEFT", monitorSection, "TOPLEFT", 38, -86)
reloadHint:SetWidth(520)
reloadHint:SetJustifyH("LEFT")
reloadHint:SetText("提示:更改监控开关后建议 /reload 以确保完全生效。")
reloadHint:SetTextColor(0.9, 0.75, 0.5)
end
local windowPage = CreatePage("window")
do
local appearance = CreateCfgSection(windowPage, "窗口外观", 0, 0, 584, 274, fontPath)
@@ -6562,14 +6624,28 @@ function SFrames.Chat:Initialize()
end
end
-- Helper: save a message to the persistent cache ring buffer
local _persistWriteIdx = table.getn(db.messageCache)
local function PersistMessage(msgID, text, r, g, b, frameIndex)
local cache = EnsureDB().messageCache
if not cache then
EnsureDB().messageCache = {}
cache = EnsureDB().messageCache
end
table.insert(cache, {
_persistWriteIdx = _persistWriteIdx + 1
if _persistWriteIdx > MAX_CACHE then
_persistWriteIdx = 1
end
local entry = cache[_persistWriteIdx]
if entry then
entry.id = msgID
entry.text = text
entry.r = r
entry.g = g
entry.b = b
entry.frame = frameIndex or 1
entry.time = date("%H:%M:%S")
else
cache[_persistWriteIdx] = {
id = msgID,
text = text,
r = r,
@@ -6577,13 +6653,15 @@ function SFrames.Chat:Initialize()
b = b,
frame = frameIndex or 1,
time = date("%H:%M:%S"),
})
-- Trim to MAX_CACHE
while table.getn(cache) > MAX_CACHE do
table.remove(cache, 1)
}
end
end
local _cfTabCache = {}
for i = 1, 7 do
_cfTabCache[_G["ChatFrame" .. i]] = i
end
for i = 1, 7 do
local cf = _G["ChatFrame" .. i]
if cf and cf.AddMessage then
@@ -6594,13 +6672,28 @@ function SFrames.Chat:Initialize()
origAddMessage(self, text, r, g, b, alpha, holdTime)
return
end
if string.sub(text, 1, 10) ~= "|Hsfchat:" and string.sub(text, 1, 12) ~= "|cff888888|H" then
if EnsureDB().chatMonitorEnabled == false then
origAddMessage(self, text, r, g, b, alpha, holdTime)
return
end
local b1 = string.byte(text, 1)
if b1 == 124 then
local b2 = string.byte(text, 2)
if b2 == 72 and string.find(text, "^|Hsfchat:") then
origAddMessage(self, text, r, g, b, alpha, holdTime)
return
end
if b2 == 99 and string.find(text, "^|cff888888|H") then
origAddMessage(self, text, r, g, b, alpha, holdTime)
return
end
end
do
local db = EnsureDB()
-- Universal catch for Turtle WoW custom chat channels like [硬核]
local chanName = GetChannelNameFromChatLine(text)
if chanName and IsIgnoredChannelByDefault(chanName) then
-- Global HC kill switch override check
if db.hcGlobalDisable then
local lowerChan = string.lower(chanName)
if string.find(lowerChan, "hc") or string.find(chanName, "硬核") or string.find(lowerChan, "hardcore") then
@@ -6608,34 +6701,21 @@ function SFrames.Chat:Initialize()
end
end
local frameName = self:GetName()
local matchedTabIdx = nil
if type(frameName) == "string" and string.find(frameName, "^ChatFrame%d+$") then
matchedTabIdx = SFrames.Chat:GetTabIndexForChatFrame(self)
local matchedTabIdx = _cfTabCache[self]
or SFrames.Chat:GetTabIndexForChatFrame(self)
if not matchedTabIdx then
local _, _, frameNumStr = string.find(frameName, "^ChatFrame(%d+)$")
local frameNum = tonumber(frameNumStr)
if frameNum then
local db = EnsureDB()
if frameNum <= table.getn(db.tabs) then
matchedTabIdx = frameNum
return
end
end
end
end
if matchedTabIdx then
local tab = EnsureDB().tabs[matchedTabIdx]
local tabs = db.tabs
local tab = tabs and tabs[matchedTabIdx]
if tab then
if not SFrames.Chat:GetTabChannelFilter(matchedTabIdx, chanName) then
return
end
-- If it passed channel filter, allow translation for these bypassed channels
local shouldTranslate = SFrames.Chat:GetTabChannelTranslateFilter(matchedTabIdx, chanName)
if shouldTranslate then
local cleanText = CleanTextForTranslation(text)
-- Remove the channel prefix from translation text to be clean
cleanText = string.gsub(cleanText, "^%[.-%]%s*", "")
-- It might also have [PlayerName]:
local _, _, senderName = string.find(cleanText, "^%[([^%]]+)%]:%s*")
if senderName then
cleanText = string.gsub(cleanText, "^%[[^%]]+%]:%s*", "")
@@ -6649,13 +6729,11 @@ function SFrames.Chat:Initialize()
end)
end
end
end
else
return
end
end
-- Hardcore Death Event Overrides
if db.hcDeathDisable or (db.hcDeathLevelMin and db.hcDeathLevelMin > 1) then
local deathLvl = ParseHardcoreDeathMessage(text)
if deathLvl then
@@ -6667,47 +6745,13 @@ function SFrames.Chat:Initialize()
SFrames.Chat.MessageIndex = SFrames.Chat.MessageIndex + 1
local msgID = SFrames.Chat.MessageIndex
-- Store in runtime lookup
SFrames.Chat.MessageHistory[msgID] = text
-- Persist to SavedVariables
local frameIdx = nil
for fi = 1, 7 do
if self == _G["ChatFrame" .. fi] then
frameIdx = fi
break
end
end
PersistMessage(msgID, text, r, g, b, frameIdx)
PersistMessage(msgID, text, r, g, b, _cfTabCache[self])
-- Apply class color to player names in message
local coloredText = ColorPlayerNamesInText(text)
-- Insert the clickable button [+] at the beginning
local modifiedText = "|cff888888|Hsfchat:" .. msgID .. "|h[+]|h|r " .. coloredText
origAddMessage(self, modifiedText, r, g, b, alpha, holdTime)
-- Legacy auto-translate disabled; handled by per-tab routing above.
if false then
if self == ChatFrame1 and not string.find(text, "%[翻译%]") then
if string.find(text, "%[.-硬核.-%]") or string.find(string.lower(text), "%[.-hc.-%]") or string.find(string.lower(text), "%[.-hardcore.-%]") then
local cleanText = string.gsub(text, "|c%x%x%x%x%x%x%x%x", "")
cleanText = string.gsub(cleanText, "|r", "")
cleanText = string.gsub(cleanText, "|H.-|h(.-)|h", "%1")
if _G.STranslateAPI and _G.STranslateAPI.IsReady and _G.STranslateAPI.IsReady() then
_G.STranslateAPI.Translate(cleanText, "auto", "zh", function(result, err, meta)
if result then
DEFAULT_CHAT_FRAME:AddMessage("|cff00ffff[翻译] |cffffff00" .. tostring(result) .. "|r")
end
end, "Nanami-UI")
elseif _G.STranslate and _G.STranslate.SendIO then
_G.STranslate:SendIO(cleanText, "IN", "auto", "zh")
end
end
end
end
else
origAddMessage(self, text, r, g, b, alpha, holdTime)
end
end
end

View File

@@ -509,7 +509,6 @@ local function EnsureDB()
if type(SFramesDB.Tweaks) ~= "table" then SFramesDB.Tweaks = {} end
if SFramesDB.Tweaks.autoStance == nil then SFramesDB.Tweaks.autoStance = true end
if SFramesDB.Tweaks.autoDismount == nil then SFramesDB.Tweaks.autoDismount = true end
if SFramesDB.Tweaks.superWoW == nil then SFramesDB.Tweaks.superWoW = true end
if SFramesDB.Tweaks.turtleCompat == nil then SFramesDB.Tweaks.turtleCompat = true end
if SFramesDB.Tweaks.cooldownNumbers == nil then SFramesDB.Tweaks.cooldownNumbers = true end
@@ -1090,14 +1089,7 @@ function SFrames.ConfigUI:BuildUIPage()
CreateDesc(tweaksSection, "施法需要特定姿态时自动切换(如人形按熊掌→自动变熊)", 36, -50, font, 218)
table.insert(controls, CreateCheckBox(tweaksSection,
"自动取消变形/下马", 270, -34,
function() return SFramesDB.Tweaks.autoDismount ~= false end,
function(checked) SFramesDB.Tweaks.autoDismount = checked end
))
CreateDesc(tweaksSection, "变形/骑马时施法自动取消(如熊形按治疗→自动回人形)", 292, -50, font, 218)
table.insert(controls, CreateCheckBox(tweaksSection,
"乌龟服兼容修改", 14, -80,
"乌龟服兼容修改", 270, -34,
function() return SFramesDB.Tweaks.turtleCompat ~= false end,
function(checked) SFramesDB.Tweaks.turtleCompat = checked end
))

View File

@@ -83,9 +83,11 @@ end
-- Event Dispatcher
SFrames.eventFrame:SetScript("OnEvent", function()
if SFrames.events[event] then
for i, func in ipairs(SFrames.events[event]) do
func(event)
local handlers = SFrames.events[event]
if handlers then
local n = table.getn(handlers)
for i = 1, n do
handlers[i](event)
end
end
end)
@@ -100,8 +102,8 @@ end
function SFrames:UnregisterEvent(event, func)
if self.events[event] then
for i, f in ipairs(self.events[event]) do
if f == func then
for i = 1, table.getn(self.events[event]) do
if self.events[event][i] == func then
table.remove(self.events[event], i)
break
end

View File

@@ -147,7 +147,11 @@ function SFrames.FloatingTooltip:Initialize()
bg:SetAllPoints(bgFrame)
GameTooltip._nanamiBGTex = bg
bgFrame._ttVisCheck = 0
bgFrame:SetScript("OnUpdate", function()
this._ttVisCheck = this._ttVisCheck + arg1
if this._ttVisCheck < 0.2 then return end
this._ttVisCheck = 0
if not GameTooltip:IsVisible() then
this:Hide()
end

View File

@@ -812,10 +812,20 @@ local function UpdateFilters()
MainFrame.filterUsed:SetActive(currentFilter == "used")
end
local _isUpdating = false
local function FullUpdate()
if _isUpdating then return end
_isUpdating = true
local ok, err = pcall(function()
UpdateFilters()
UpdateList()
UpdateDetail()
end)
_isUpdating = false
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI TrainerUI: FullUpdate error: " .. tostring(err) .. "|r")
end
end
local function SelectService(index)
@@ -1181,12 +1191,6 @@ function TUI:Initialize()
ClassTrainerFrame:EnableMouse(false)
end
if SetTrainerServiceTypeFilter then
SetTrainerServiceTypeFilter("available", 1)
SetTrainerServiceTypeFilter("unavailable", 1)
SetTrainerServiceTypeFilter("used", 1)
end
selectedIndex = nil
currentFilter = "all"
collapsedCats = {}
@@ -1196,6 +1200,15 @@ function TUI:Initialize()
npcName = npcName .. " - 专业训练"
end
MainFrame.npcNameFS:SetText(npcName)
_isUpdating = true
if SetTrainerServiceTypeFilter then
pcall(SetTrainerServiceTypeFilter, "available", 1)
pcall(SetTrainerServiceTypeFilter, "unavailable", 1)
pcall(SetTrainerServiceTypeFilter, "used", 1)
end
_isUpdating = false
MainFrame:Show()
BuildDisplayList()
for _, entry in ipairs(displayList) do

View File

@@ -6,8 +6,7 @@
-- 4. Cooldown Numbers - show remaining cooldown time as text overlay
-- 5. Dark UI - darken the entire interface
-- 6. WorldMap Window - turn fullscreen map into a movable/scalable window
-- 7. Auto Dismount - cancel shapeshift/mount when casting incompatible spells
-- 8. Hunter Aspect Guard - auto switch to Hawk when taking damage with Cheetah/Pack
-- 7. Hunter Aspect Guard - cancel Cheetah/Pack when taking damage in combat (avoid OOC false positives)
--------------------------------------------------------------------------------
SFrames.Tweaks = SFrames.Tweaks or {}
@@ -75,120 +74,6 @@ local function InitAutoStance()
end)
end
--------------------------------------------------------------------------------
-- Auto Dismount / Cancel Shapeshift
-- When casting a spell that fails because you are mounted or shapeshifted,
-- automatically cancel the mount/shapeshift buff so the next cast succeeds.
--------------------------------------------------------------------------------
local function InitAutoDismount()
local dismount = CreateFrame("Frame", "NanamiAutoDismount")
local _, playerClass = UnitClass("player")
local scanner = CreateFrame("GameTooltip", "NanamiDismountScan", nil, "GameTooltipTemplate")
scanner:SetOwner(WorldFrame, "ANCHOR_NONE")
scanner:SetAlpha(0)
scanner:Hide()
local mountStrings = {
"^Increases speed by (.+)%%",
"^Erhöht Tempo um (.+)%%",
"^Aumenta la velocidad en un (.+)%%",
"^Augmente la vitesse de (.+)%%",
"^Скорость увеличена на (.+)%%",
"^이동 속도 (.+)%%만큼 증가",
"^速度提高(.+)%%", "^移动速度提高(.+)%%",
"speed based on", "Slow and steady...", "Riding",
"Lento y constante...", "Aumenta la velocidad según tu habilidad de Montar.",
"根据您的骑行技能提高速度。", "根据骑术技能提高速度。", "又慢又稳......",
}
local shapeshiftIcons = {
"ability_racial_bearform", "ability_druid_catform",
"ability_druid_travelform", "spell_nature_forceofnature",
"ability_druid_aquaticform", "spell_nature_spiritwolf",
"ability_druid_treeoflife", "ability_druid_stagform",
}
local hunterAspectIcons = {
"ability_mount_jungletiger",
"ability_mount_packhorse",
}
local errorStrings = {}
local errorGlobals = {
"SPELL_FAILED_NOT_MOUNTED", "ERR_ATTACK_MOUNTED", "ERR_TAXIPLAYERALREADYMOUNTED",
"ERR_NOT_WHILE_MOUNTED",
"SPELL_FAILED_NOT_SHAPESHIFT", "SPELL_FAILED_NO_ITEMS_WHILE_SHAPESHIFTED",
"SPELL_NOT_SHAPESHIFTED", "SPELL_NOT_SHAPESHIFTED_NOSPACE",
"ERR_CANT_INTERACT_SHAPESHIFTED", "ERR_NOT_WHILE_SHAPESHIFTED",
"ERR_NO_ITEMS_WHILE_SHAPESHIFTED", "ERR_TAXIPLAYERSHAPESHIFTED",
"ERR_MOUNT_SHAPESHIFTED",
}
for _, name in pairs(errorGlobals) do
local val = getfenv(0)[name]
if val then table.insert(errorStrings, val) end
end
dismount:RegisterEvent("UI_ERROR_MESSAGE")
dismount:SetScript("OnEvent", function()
if arg1 == SPELL_FAILED_NOT_STANDING then
SitOrStand()
return
end
local matched = false
for _, err in pairs(errorStrings) do
if arg1 == err then matched = true break end
end
if not matched then return end
for i = 0, 31 do
local buff = GetPlayerBuffTexture(i)
if buff then
local lowerBuff = string.lower(buff)
local skip = false
if playerClass == "HUNTER" then
for _, tex in pairs(hunterAspectIcons) do
if string.find(lowerBuff, tex) then
skip = true
break
end
end
end
if not skip then
scanner:ClearLines()
scanner:SetPlayerBuff(i)
for line = 1, scanner:NumLines() do
local text = getfenv(0)["NanamiDismountScanTextLeft" .. line]
if text and text:GetText() then
for _, str in pairs(mountStrings) do
if string.find(text:GetText(), str) then
CancelPlayerBuff(i)
return
end
end
end
end
for _, icon in pairs(shapeshiftIcons) do
if string.find(lowerBuff, icon) then
CancelPlayerBuff(i)
return
end
end
if string.find(lowerBuff, "ability_mount_") then
CancelPlayerBuff(i)
return
end
end
end
end
end)
end
--------------------------------------------------------------------------------
-- SuperWoW Compatibility
-- Provides GUID-based cast/channel data when SuperWoW client mod is active.
@@ -389,6 +274,78 @@ local function TimeConvert(remaining)
end
end
local _activeCooldowns = {}
local _cdTickTimer = 0
local _cdUpdaterFrame
local function CooldownSharedUpdate()
_cdTickTimer = _cdTickTimer + arg1
if _cdTickTimer < 0.1 then return end
_cdTickTimer = 0
local now = GetTime()
local sysTime = time()
local n = table.getn(_activeCooldowns)
local i = 1
while i <= n do
local cdFrame = _activeCooldowns[i]
if cdFrame and cdFrame:IsShown() then
local parent = cdFrame:GetParent()
if parent then
cdFrame:SetAlpha(parent:GetAlpha())
end
if cdFrame.start < now then
local remaining = cdFrame.duration - (now - cdFrame.start)
if remaining > 0 then
cdFrame.text:SetText(TimeConvert(remaining))
else
cdFrame:Hide()
_activeCooldowns[i] = _activeCooldowns[n]
_activeCooldowns[n] = nil
n = n - 1
i = i - 1
end
else
local startupTime = sysTime - now
local cdTime = (2 ^ 32) / 1000 - cdFrame.start
local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + cdFrame.duration
local remaining = cdEndTime - sysTime
if remaining >= 0 then
cdFrame.text:SetText(TimeConvert(remaining))
else
cdFrame:Hide()
_activeCooldowns[i] = _activeCooldowns[n]
_activeCooldowns[n] = nil
n = n - 1
i = i - 1
end
end
i = i + 1
else
_activeCooldowns[i] = _activeCooldowns[n]
_activeCooldowns[n] = nil
n = n - 1
end
end
if n == 0 and _cdUpdaterFrame then
_cdUpdaterFrame:Hide()
end
end
local function RegisterCooldownFrame(cdFrame)
for i = 1, table.getn(_activeCooldowns) do
if _activeCooldowns[i] == cdFrame then return end
end
table.insert(_activeCooldowns, cdFrame)
if not _cdUpdaterFrame then
_cdUpdaterFrame = CreateFrame("Frame", "NanamiCDSharedUpdater", UIParent)
_cdUpdaterFrame:SetScript("OnUpdate", CooldownSharedUpdate)
end
_cdUpdaterFrame:Show()
end
local function CooldownOnUpdate()
local parent = this:GetParent()
if not parent then this:Hide() return end
@@ -494,7 +451,7 @@ local function CreateCoolDown(cooldown, start, duration)
cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
end
cooldown.cooldowntext:SetScript("OnUpdate", CooldownOnUpdate)
RegisterCooldownFrame(cooldown.cooldowntext)
end
local function SetCooldown(frame, start, duration, enable)
@@ -520,12 +477,13 @@ local function SetCooldown(frame, start, duration, enable)
if start > 0 and duration > 0 and (not enable or enable > 0) then
if frame.cooldownmask then frame.cooldownmask:Show() end
frame.cooldowntext:Show()
frame.cooldowntext.start = start
frame.cooldowntext.duration = duration
RegisterCooldownFrame(frame.cooldowntext)
else
if frame.cooldownmask then frame.cooldownmask:Hide() end
frame.cooldowntext:Hide()
end
frame.cooldowntext.start = start
frame.cooldowntext.duration = duration
end
end
@@ -1061,8 +1019,8 @@ end
--------------------------------------------------------------------------------
-- Hunter Aspect Guard
-- When a Hunter takes damage with Aspect of the Cheetah or Aspect of the Pack
-- active, automatically cancel the aspect to prevent repeated dazing.
-- When a Hunter takes damage in combat with Aspect of the Cheetah or Pack
-- active, cancel the aspect to reduce daze chains. OOC HP changes are ignored.
--------------------------------------------------------------------------------
local function InitHunterAspectGuard()
local _, playerClass = UnitClass("player")
@@ -1104,7 +1062,7 @@ local function InitHunterAspectGuard()
return
end
if lastHP > 0 and hp < lastHP then
if lastHP > 0 and hp < lastHP and UnitAffectingCombat("player") then
if GetTime() - lastCancel >= 1.0 then
if CancelDangerousAspect() then
lastCancel = GetTime()
@@ -1129,13 +1087,6 @@ function Tweaks:Initialize()
end
end
if cfg.autoDismount ~= false then
local ok, err = pcall(InitAutoDismount)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: AutoDismount init failed: " .. tostring(err) .. "|r")
end
end
if cfg.superWoW ~= false then
local ok, err = pcall(InitSuperWoW)
if not ok then

View File

@@ -493,7 +493,7 @@ function SFrames.Party:Initialize()
if not self._globalUpdateFrame then
self._globalUpdateFrame = CreateFrame("Frame", nil, UIParent)
self._globalUpdateFrame:SetScript("OnUpdate", function()
self._globalUpdateFrame._onUpdateFunc = function()
if SFrames.Party.testing then return end
local dt = arg1
local frames = SFrames.Party.frames
@@ -533,7 +533,8 @@ function SFrames.Party:Initialize()
end
end
end
end)
end
self._globalUpdateFrame:Hide()
end
self:ApplyConfig()
@@ -745,20 +746,35 @@ function SFrames.Party:UpdateAll()
self.frames[i].frame:Hide()
end
end
if self._globalUpdateFrame then
self._globalUpdateFrame:SetScript("OnUpdate", nil)
self._globalUpdateFrame:Hide()
end
return
end
if self.testing then return end
local numParty = GetNumPartyMembers()
local hasVisible = false
for i = 1, 4 do
local data = self.frames[i]
local f = data.frame
if i <= numParty then
f:Show()
self:UpdateFrame(data.unit)
hasVisible = true
else
f:Hide()
end
end
if self._globalUpdateFrame then
if hasVisible then
self._globalUpdateFrame:SetScript("OnUpdate", self._globalUpdateFrame._onUpdateFunc)
self._globalUpdateFrame:Show()
else
self._globalUpdateFrame:SetScript("OnUpdate", nil)
self._globalUpdateFrame:Hide()
end
end
end
function SFrames.Party:UpdateFrame(unit)

View File

@@ -539,11 +539,13 @@ function SFrames.Raid:EnsureFrames()
if not self._globalUpdateFrame then
self._globalUpdateFrame = CreateFrame("Frame", nil, UIParent)
self._globalUpdateFrame:SetScript("OnUpdate", function()
self._globalUpdateFrame._onUpdateFunc = function()
if SFrames.Raid.testing then return end
local dt = arg1
local frames = SFrames.Raid.frames
if not frames then return end
local visibleCount = SFrames.Raid._visibleCount or 0
if visibleCount == 0 then return end
for i = 1, 40 do
local entry = frames[i]
if entry then
@@ -573,7 +575,8 @@ function SFrames.Raid:EnsureFrames()
end
end
end
end)
end
self._globalUpdateFrame:Hide()
end
self:ApplyLayout()
@@ -602,6 +605,7 @@ function SFrames.Raid:UpdateAll()
groupSlots[g] = 0
end
local visibleCount = 0
for i = 1, 40 do
local name, rank, subgroup = GetRaidRosterInfo(i)
if name and subgroup and subgroup >= 1 and subgroup <= 8 then
@@ -615,9 +619,16 @@ function SFrames.Raid:UpdateAll()
self.frames[frameIndex].unit = "raid" .. i
f:Show()
self:UpdateFrame("raid" .. i)
visibleCount = visibleCount + 1
end
end
end
self._visibleCount = visibleCount
if self._globalUpdateFrame then
self._globalUpdateFrame:SetScript("OnUpdate", self._globalUpdateFrame._onUpdateFunc)
self._globalUpdateFrame:Show()
end
-- Show/hide group labels based on whether each group has members
local showLabel = SFramesDB and SFramesDB.raidShowGroupLabel ~= false
@@ -632,6 +643,11 @@ function SFrames.Raid:UpdateAll()
end
end
else
self._visibleCount = 0
if self._globalUpdateFrame then
self._globalUpdateFrame:SetScript("OnUpdate", nil)
self._globalUpdateFrame:Hide()
end
if not self.testing and self._framesBuilt then
for i = 1, 40 do
self.frames[i].frame:Hide()

View File

@@ -0,0 +1,89 @@
# 自动下马 / 取消变形Auto Dismount技术总结
## 功能概述
骑乘或变形状态下尝试施法时,自动取消坐骑 / 变形 buff使下次施法直接生效。
## 检测架构(三层)
### 第 1 层 — 错误消息匹配
监听 `UI_ERROR_MESSAGE` 事件:
| 方式 | 说明 |
|---|---|
| 哈希表精确匹配 | `ERR_ATTACK_MOUNTED`, `ERR_NOT_WHILE_MOUNTED`, `ERR_MOUNT_SHAPESHIFTED` 等标准全局变量 |
| 关键词模糊匹配 | `mounted`, `shapeshifted`, `坐骑`, `骑乘`, `变形`, `形态`(兜底自定义错误消息) |
> Turtle WoW 使用自定义错误 `"你正在骑乘状态"`,不在标准全局变量中,通过关键词 `"骑乘"` 命中。
### 第 2 层 — Dismount() API
客户端若提供 `Dismount()` 函数则先调用(`pcall` 包裹),**不 early return**,继续 buff 扫描作为后备。
### 第 3 层 — Buff 扫描0-39
遍历玩家 buff逐个按优先级检测
| 优先级 | 检测方式 | 目标 | 示例 |
|---|---|---|---|
| 1 | 变形图标匹配 | 德鲁伊 / 萨满形态 | `ability_druid_catform`, `spell_nature_spiritwolf` |
| 2 | 坐骑图标模式 | 坐骑 buff 图标 | `ability_mount_*`, `inv_pet_speedy` |
| 3 | Tooltip 速度文本 | 坐骑速度描述(多语言) | `"又慢又稳"`, `"Increases speed by"` |
| 4 | Buff 名称关键词 | Tooltip 首行含坐骑关键词 | `"骑乘"`, `"riding"` |
### 猎人守护跳过
猎人的猎豹 / 豹群守护使用 `ability_mount_*` 图标,需特殊跳过以免误取消。
## 调试关键发现
### Tooltip 扫描在 Turtle WoW 中失效
`GameTooltip:SetPlayerBuff(index)` 对所有 buff 均返回 **0 行**
即使重新 `SetOwner`、尝试 `GetPlayerBuff` 返回的 buffId结果相同。
**影响**tooltip 检测路径完全失效,必须依赖图标模式匹配。
### 非标准坐骑图标
| 坐骑 | 实际图标纹理 |
|---|---|
| 骑乘乌龟 | `inv_pet_speedy`(不匹配 `ability_mount_*` |
| 标准坐骑 | `Ability_Mount_*` |
| AQ 坐骑 | `inv_misc_qirajicrystal_*` |
## 完整图标模式清单
```lua
-- 坐骑
mountIconPatterns = {
"ability_mount_",
"inv_misc_qirajicrystal",
"inv_pet_speedy",
}
-- 变形
shapeshiftIcons = {
"ability_racial_bearform", -- 熊形态
"ability_druid_catform", -- 猫形态
"ability_druid_travelform", -- 旅行形态
"spell_nature_forceofnature", -- 枭兽形态
"ability_druid_aquaticform", -- 水栖形态
"spell_nature_spiritwolf", -- 幽魂之狼
"ability_druid_treeoflife", -- 生命之树
"ability_druid_stagform", -- 鹿形态
"ability_druid_flightform", -- 飞行形态
}
-- 猎人守护(跳过)
hunterAspectIcons = {
"ability_mount_jungletiger", -- 猎豹守护
"ability_mount_packhorse", -- 豹群守护
}
```
## 扩展方法
遇到新坐骑无法自动下马时:
1.`InitAutoDismount` 中将 `_debug` 设为 `true`
2. 骑上坐骑施法,查看聊天窗口中的 `[Nanami-DBG] buff[x]=` 输出
3. 将新坐骑的图标关键字添加到 `mountIconPatterns`