Files
Nanami-UI/Core.lua
2026-04-09 09:46:47 +08:00

1056 lines
42 KiB
Lua
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- S-Frames Core Initialize
SFrames = {}
DEFAULT_CHAT_FRAME:AddMessage("SF: Loading Core.lua...")
do
local _orig_wipe = wipe
if _orig_wipe then
wipe = function(t)
if t == nil then return end
return _orig_wipe(t)
end
end
local _orig_tinsert = tinsert
if _orig_tinsert then
tinsert = function(t, a2, a3)
if type(t) ~= "table" then return end
if a3 ~= nil then
return _orig_tinsert(t, a2, a3)
else
return _orig_tinsert(t, a2)
end
end
end
end
do
local _orig_UIFrameFade = UIFrameFade
if _orig_UIFrameFade then
UIFrameFade = function(frame, fadeInfo)
if not frame or not fadeInfo then return end
return _orig_UIFrameFade(frame, fadeInfo)
end
end
-- 保护 ComboFrame防止 fadeInfo 为 nil 导致的报错
-- (ComboFrame.lua:46: attempt to index local 'fadeInfo' (a nil value))
if ComboFrame then
if not ComboFrame.fadeInfo then
ComboFrame.fadeInfo = {}
end
local origComboScript = ComboFrame.GetScript and ComboFrame:GetScript("OnUpdate")
if origComboScript then
ComboFrame:SetScript("OnUpdate", function(elapsed)
if ComboFrame.fadeInfo then
origComboScript(elapsed)
end
end)
end
end
if ComboFrame_Update then
local _orig_ComboUpdate = ComboFrame_Update
ComboFrame_Update = function()
if ComboFrame and not ComboFrame.fadeInfo then
ComboFrame.fadeInfo = {}
end
return _orig_ComboUpdate()
end
end
if ComboFrame_OnUpdate then
local _orig_ComboOnUpdate = ComboFrame_OnUpdate
ComboFrame_OnUpdate = function(elapsed)
if ComboFrame and not ComboFrame.fadeInfo then
ComboFrame.fadeInfo = {}
end
return _orig_ComboOnUpdate(elapsed)
end
end
local origOnUpdate = UIParent and UIParent.GetScript and UIParent:GetScript("OnUpdate")
if origOnUpdate then
UIParent:SetScript("OnUpdate", function()
pcall(origOnUpdate)
end)
end
end
BINDING_HEADER_NANAMI_UI = "Nanami-UI"
BINDING_NAME_NANAMI_TOGGLE_NAV = "切换导航地图"
BINDING_HEADER_NANAMI_EXTRABAR = "Nanami-UI 额外动作条"
for _i = 1, 48 do
_G["BINDING_NAME_NANAMI_EXTRABAR" .. _i] = "额外动作条 按钮" .. _i
end
SFrames.eventFrame = CreateFrame("Frame", "SFramesEventFrame", UIParent)
SFrames.events = {}
function SFrames:GetIncomingHeals(unit)
-- Resolve pet unitIds: if "target" points to a pet in our group, use the
-- concrete petN / partypetN id so heal-prediction libraries can match it.
local resolvedUnit = unit
if unit == "target" or unit == "targettarget" then
if UnitExists(unit) and UnitPlayerControlled(unit) and not UnitIsPlayer(unit) then
-- It's a player-controlled non-player unit (pet). Find the real unitId.
if UnitIsUnit(unit, "pet") then
resolvedUnit = "pet"
else
for i = 1, 4 do
if UnitIsUnit(unit, "partypet" .. i) then
resolvedUnit = "partypet" .. i
break
end
end
end
end
end
-- Source 1: ShaguTweaks libpredict
if ShaguTweaks and ShaguTweaks.libpredict and ShaguTweaks.libpredict.UnitGetIncomingHeals then
local lp = ShaguTweaks.libpredict
if lp.UnitGetIncomingHealsBreakdown then
local ok, total, mine, others = pcall(function()
return lp:UnitGetIncomingHealsBreakdown(resolvedUnit, UnitName("player"))
end)
if ok then
return math.max(0, tonumber(total) or 0),
math.max(0, tonumber(mine) or 0),
math.max(0, tonumber(others) or 0)
end
end
local ok, amount = pcall(function() return lp:UnitGetIncomingHeals(resolvedUnit) end)
if ok then
amount = math.max(0, tonumber(amount) or 0)
return amount, 0, amount
end
end
-- Source 2: HealComm-1.0 (AceLibrary)
if AceLibrary and AceLibrary.HasInstance and AceLibrary:HasInstance("HealComm-1.0") then
local ok, HC = pcall(function() return AceLibrary("HealComm-1.0") end)
if ok and HC and HC.getHeal then
local name = UnitName(resolvedUnit)
if name then
local total = HC:getHeal(name) or 0
total = math.max(0, tonumber(total) or 0)
return total, 0, total
end
end
end
return 0, 0, 0
end
-- Event Dispatcher
SFrames.eventFrame:SetScript("OnEvent", function()
local handlers = SFrames.events[event]
if handlers then
local n = table.getn(handlers)
for i = 1, n do
handlers[i](event)
end
end
end)
function SFrames:RegisterEvent(event, func)
if not self.events[event] then
self.events[event] = {}
self.eventFrame:RegisterEvent(event)
end
table.insert(self.events[event], func)
end
function SFrames:UnregisterEvent(event, func)
if self.events[event] 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
end
if table.getn(self.events[event]) == 0 then
self.events[event] = nil
self.eventFrame:UnregisterEvent(event)
end
end
end
-- Print Helper
function SFrames:Print(msg)
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r " .. tostring(msg))
end
local function IsBattlefieldMinimapVisible()
if BattlefieldMinimap and BattlefieldMinimap.IsVisible and BattlefieldMinimap:IsVisible() then
return true
end
if BattlefieldMinimapFrame and BattlefieldMinimapFrame ~= BattlefieldMinimap
and BattlefieldMinimapFrame.IsVisible and BattlefieldMinimapFrame:IsVisible() then
return true
end
return false
end
function SFrames:CaptureBattlefieldMinimapState()
return IsBattlefieldMinimapVisible()
end
function SFrames:RestoreBattlefieldMinimapState(wasVisible)
if wasVisible then
return
end
local frames = { BattlefieldMinimap, BattlefieldMinimapFrame }
local hidden = {}
for i = 1, table.getn(frames) do
local frame = frames[i]
if frame and not hidden[frame] and frame.Hide and frame.IsVisible and frame:IsVisible() then
hidden[frame] = true
pcall(frame.Hide, frame)
end
end
end
function SFrames:CallWithPreservedBattlefieldMinimap(func, a1, a2, a3, a4, a5, a6, a7, a8)
if type(func) ~= "function" then
return
end
local state = self:CaptureBattlefieldMinimapState()
local results = { pcall(func, a1, a2, a3, a4, a5, a6, a7, a8) }
self:RestoreBattlefieldMinimapState(state)
if not results[1] then
return nil, results[2]
end
return unpack(results, 2, table.getn(results))
end
-- Addon Loaded Initializer
SFrames:RegisterEvent("PLAYER_LOGIN", function()
SFrames:Initialize()
SFrames:SaveCharProfile()
end)
SFrames:RegisterEvent("PLAYER_LOGOUT", function()
SFrames:SaveCharProfile()
end)
function SFrames:GetCharKey()
local name = UnitName("player") or "Unknown"
local realm = GetRealmName and GetRealmName() or "Unknown"
return realm, name
end
function SFrames:SaveCharProfile()
if not SFramesDB then return end
if not SFramesGlobalDB then SFramesGlobalDB = {} end
if not SFramesGlobalDB.CharProfiles then SFramesGlobalDB.CharProfiles = {} end
local realm, name = self:GetCharKey()
if not SFramesGlobalDB.CharProfiles[realm] then SFramesGlobalDB.CharProfiles[realm] = {} end
local snapshot = {}
for k, v in pairs(SFramesDB) do
snapshot[k] = v
end
snapshot._savedAt = time and time() or 0
snapshot._class = select and select(2, UnitClass("player")) or "UNKNOWN"
if UnitClass then
local _, classEn = UnitClass("player")
snapshot._class = classEn or "UNKNOWN"
end
snapshot._level = UnitLevel and UnitLevel("player") or 0
-- Nanami-QT
if QuickToolboxDB then
snapshot._qtDB = QuickToolboxDB
end
if QuickToolboxSharedDB then
snapshot._qtSharedDB = QuickToolboxSharedDB
end
-- Nanami-Plates
if NanamiPlatesDB then
snapshot._platesDB = NanamiPlatesDB
end
-- Nanami-DPS
if NanamiDPS_DB then
snapshot._dpsDB = NanamiDPS_DB
end
SFramesGlobalDB.CharProfiles[realm][name] = snapshot
end
function SFrames:GetAllCharProfiles()
if not SFramesGlobalDB or not SFramesGlobalDB.CharProfiles then return {} end
local list = {}
for realm, chars in pairs(SFramesGlobalDB.CharProfiles) do
for charName, data in pairs(chars) do
table.insert(list, {
realm = realm,
name = charName,
class = data._class or "UNKNOWN",
level = data._level or 0,
savedAt = data._savedAt or 0,
data = data,
})
end
end
return list
end
function SFrames:ApplyCharProfile(profileData)
if not profileData or type(profileData) ~= "table" then return false end
for k, v in pairs(profileData) do
if k ~= "_savedAt" and k ~= "_class" and k ~= "_level" and k ~= "Positions"
and k ~= "_qtDB" and k ~= "_qtSharedDB" and k ~= "_platesDB" and k ~= "_dpsDB" then
SFramesDB[k] = v
end
end
-- Nanami-QT
if profileData._qtDB and type(profileData._qtDB) == "table" then
if not QuickToolboxDB then QuickToolboxDB = {} end
for k, v in pairs(profileData._qtDB) do
QuickToolboxDB[k] = v
end
end
if profileData._qtSharedDB and type(profileData._qtSharedDB) == "table" then
if not QuickToolboxSharedDB then QuickToolboxSharedDB = {} end
for k, v in pairs(profileData._qtSharedDB) do
QuickToolboxSharedDB[k] = v
end
end
-- Nanami-Plates
if profileData._platesDB and type(profileData._platesDB) == "table" then
if not NanamiPlatesDB then NanamiPlatesDB = {} end
for k, v in pairs(profileData._platesDB) do
NanamiPlatesDB[k] = v
end
end
-- Nanami-DPS
if profileData._dpsDB and type(profileData._dpsDB) == "table" then
if not NanamiDPS_DB then NanamiDPS_DB = {} end
for k, v in pairs(profileData._dpsDB) do
NanamiDPS_DB[k] = v
end
end
return true
end
function SFrames:DeleteCharProfile(realm, name)
if not SFramesGlobalDB or not SFramesGlobalDB.CharProfiles then return end
if SFramesGlobalDB.CharProfiles[realm] then
SFramesGlobalDB.CharProfiles[realm][name] = nil
end
end
function SFrames:SafeInit(name, initFn)
local ok, err = pcall(initFn)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: " .. name .. " init failed: " .. tostring(err) .. "|r")
end
end
function SFrames:Initialize()
if not SFramesDB then SFramesDB = {} end
if not SFramesDB.setupComplete then
if SFrames.SetupWizard and SFrames.SetupWizard.Show then
SFrames.SetupWizard:Show(function()
SFrames:DoFullInitialize()
end, "firstrun")
else
SFramesDB.setupComplete = true
self:DoFullInitialize()
end
return
end
self:DoFullInitialize()
end
function SFrames:DoFullInitialize()
self:Print("Nanami-UI 正在加载,喵呜~ =^_^=")
self:HideBlizzardFrames()
SFrames.Tooltip = CreateFrame("GameTooltip", "SFramesScanTooltip", nil, "GameTooltipTemplate")
SFrames.Tooltip:SetOwner(UIParent, "ANCHOR_NONE")
SFrames.Tooltip:SetAlpha(0)
SFrames.Tooltip:Hide()
if SFrames.AuraTracker and SFrames.AuraTracker.Initialize then
SFrames.AuraTracker:Initialize()
end
-- Phase 1: Critical modules (unit frames, action bars) — must load immediately
if SFramesDB.enableUnitFrames ~= false then
if SFramesDB.enablePlayerFrame ~= false then
if SFrames.Player and SFrames.Player.Initialize then SFrames.Player:Initialize() end
if SFrames.Pet and SFrames.Pet.Initialize then SFrames.Pet:Initialize() end
end
if SFramesDB.enableTargetFrame ~= false then
if SFrames.Target and SFrames.Target.Initialize then SFrames.Target:Initialize() end
if SFrames.ToT and SFrames.ToT.Initialize then SFrames.ToT:Initialize() end
end
if SFramesDB.enablePartyFrame ~= false then
if SFrames.Party and SFrames.Party.Initialize then SFrames.Party:Initialize() end
end
end
if SFrames.FloatingTooltip and SFrames.FloatingTooltip.Initialize then SFrames.FloatingTooltip:Initialize() end
if SFrames.ActionBars and SFrames.ActionBars.Initialize then
SFrames.ActionBars:Initialize()
end
if SFrames.ExtraBar and SFrames.ExtraBar.Initialize then
SFrames.ExtraBar:Initialize()
end
self:InitSlashCommands()
-- Phase 2: Deferred modules — spread across multiple frames to avoid memory spike
local deferred = {
{ "Raid", function() if SFramesDB.enableUnitFrames ~= false and SFrames.Raid and SFrames.Raid.Initialize then SFrames.Raid:Initialize() end end },
{ "Bags", function() if SFrames.Bags and SFrames.Bags.Core and SFrames.Bags.Core.Initialize then SFrames.Bags.Core:Initialize() end end },
{ "Focus", function() if SFrames.Focus and SFrames.Focus.Initialize then SFrames.Focus:Initialize() end end },
{ "TalentTree", function() if SFrames.TalentTree and SFrames.TalentTree.Initialize then SFrames.TalentTree:Initialize() end end },
{ "Minimap", function() if SFrames.Minimap and SFrames.Minimap.Initialize then SFrames.Minimap:Initialize() end end },
{ "MinimapBuffs",function() if SFrames.MinimapBuffs and SFrames.MinimapBuffs.Initialize then SFrames.MinimapBuffs:Initialize() end end },
{ "MinimapButton",function() if SFrames.MinimapButton and SFrames.MinimapButton.Initialize then SFrames.MinimapButton:Initialize() end end },
{ "Chat", function() if SFramesDB.enableChat ~= false and SFrames.Chat and SFrames.Chat.Initialize then SFrames.Chat:Initialize() end end },
{ "MapReveal", function() if SFrames.MapReveal and SFrames.MapReveal.Initialize then SFrames.MapReveal:Initialize() end end },
{ "WorldMap", function() if SFrames.WorldMap and SFrames.WorldMap.Initialize then SFrames.WorldMap:Initialize() end end },
{ "ZoneLevelRange", function() if SFrames.ZoneLevelRange and SFrames.ZoneLevelRange.Initialize then SFrames.ZoneLevelRange:Initialize() end end },
{ "MapIcons", function() if SFrames.MapIcons and SFrames.MapIcons.Initialize then SFrames.MapIcons:Initialize() end end },
{ "Tweaks", function() if SFrames.Tweaks and SFrames.Tweaks.Initialize then SFrames.Tweaks:Initialize() end end },
{ "AFKScreen", function() if SFrames.AFKScreen and SFrames.AFKScreen.Initialize then SFrames.AFKScreen:Initialize() end end },
{ "LootDisplay", function() if SFrames.LootDisplay and SFrames.LootDisplay.Initialize then SFrames.LootDisplay:Initialize() end end },
}
local idx = 1
local batchSize = 3
local deferFrame = CreateFrame("Frame")
deferFrame:SetScript("OnUpdate", function()
if idx > table.getn(deferred) then
this:SetScript("OnUpdate", nil)
return
end
local batchEnd = idx + batchSize - 1
if batchEnd > table.getn(deferred) then batchEnd = table.getn(deferred) end
for i = idx, batchEnd do
SFrames:SafeInit(deferred[i][1], deferred[i][2])
end
idx = batchEnd + 1
end)
end
function SFrames:GetAuraTimeLeft(unit, index, isBuff)
-- If the unit is the player (e.g. you target yourself), Vanilla API CAN give us exact times
if UnitIsUnit(unit, "player") then
local texture = isBuff and UnitBuff(unit, index) or UnitDebuff(unit, index)
if texture then
local filter = isBuff and "HELPFUL" or "HARMFUL"
for i = 0, 31 do
local buffIndex = GetPlayerBuff(i, filter)
if buffIndex and buffIndex >= 0 then
if GetPlayerBuffTexture(buffIndex) == texture then
local t = GetPlayerBuffTimeLeft(buffIndex)
if t and t > 0 then return t end
end
end
end
end
end
if SFrames.AuraTracker and SFrames.AuraTracker.GetAuraTimeLeft then
local trackerTime = SFrames.AuraTracker:GetAuraTimeLeft(unit, isBuff and "buff" or "debuff", index)
if trackerTime and trackerTime > 0 then
return trackerTime
end
end
-- Nanami-Plates SpellDB: combat log + spell DB tracking (most accurate for debuffs)
if not isBuff and NanamiPlates_SpellDB and NanamiPlates_SpellDB.UnitDebuff then
local effect, rank, tex, stacks, dtype, duration, timeleft, isOwn = NanamiPlates_SpellDB:UnitDebuff(unit, index)
if timeleft and timeleft > 0 then
return timeleft, effect
end
if effect and effect ~= "" and duration and duration > 0
and NanamiPlates_Auras and NanamiPlates_Auras.timers then
local unitKey = (UnitGUID and UnitGUID(unit)) or UnitName(unit) or ""
local cached = NanamiPlates_Auras.timers[unitKey .. "_" .. effect]
if not cached and UnitName(unit) then
cached = NanamiPlates_Auras.timers[UnitName(unit) .. "_" .. effect]
end
if cached and cached.startTime and cached.duration then
local remaining = cached.duration - (GetTime() - cached.startTime)
if remaining > 0 then
return remaining, effect
end
end
end
end
-- Fallback to ShaguTweaks libdebuff if available (Debuffs only usually)
if ShaguTweaks and ShaguTweaks.libdebuff then
if not isBuff then
local effect, rank, texture, stacks, dtype, duration, libTimeLeft = ShaguTweaks.libdebuff:UnitDebuff(unit, index)
if libTimeLeft and libTimeLeft > 0 then
return libTimeLeft
end
end
end
return 0
end
function SFrames:FormatTime(seconds)
if not seconds then return "" end
if seconds >= 3600 then
return math.floor(seconds / 3600) .. "h"
elseif seconds >= 60 then
return math.floor(seconds / 60) .. "m"
else
return math.floor(seconds) .. "s"
end
end
local POWER_RAINBOW_TEX = "Interface\\AddOns\\Nanami-UI\\img\\progress"
function SFrames:UpdateRainbowBar(bar, power, maxPower, unit)
-- 彩虹条仅适用于法力powerType 0怒气/能量等跳过
if unit and UnitPowerType(unit) ~= 0 then
if bar._rainbowActive then
if bar.rainbowTex then bar.rainbowTex:Hide() end
bar._rainbowActive = nil
end
return
end
if not (SFramesDB and SFramesDB.powerRainbow) then
if bar._rainbowActive then
if bar.rainbowTex then bar.rainbowTex:Hide() end
bar._rainbowActive = nil
end
return
end
if not bar.rainbowTex then
bar.rainbowTex = bar:CreateTexture(nil, "OVERLAY")
bar.rainbowTex:SetTexture(POWER_RAINBOW_TEX)
bar.rainbowTex:Hide()
end
if maxPower and maxPower > 0 then
local pct = power / maxPower
if pct >= 1.0 then
-- 满条:直接铺满,不依赖 GetWidth()(两锚点定尺寸的框体 GetWidth 可能返回 0
bar.rainbowTex:ClearAllPoints()
bar.rainbowTex:SetAllPoints(bar)
bar.rainbowTex:SetTexCoord(0, 1, 0, 1)
bar.rainbowTex:Show()
bar._rainbowActive = true
else
local barW = bar:GetWidth()
-- 双锚点定尺寸的框体如宠物能量条GetWidth() 可能返回 0
-- 回退到 GetRight()-GetLeft() 获取实际渲染宽度
if not barW or barW <= 0 then
local left = bar:GetLeft()
local right = bar:GetRight()
if left and right then
barW = right - left
end
end
if barW and barW > 0 then
bar.rainbowTex:ClearAllPoints()
bar.rainbowTex:SetPoint("TOPLEFT", bar, "TOPLEFT", 0, 0)
bar.rainbowTex:SetPoint("BOTTOMRIGHT", bar, "BOTTOMLEFT", barW * pct, 0)
bar.rainbowTex:SetTexCoord(0, pct, 0, 1)
bar.rainbowTex:Show()
bar._rainbowActive = true
end
end
else
bar.rainbowTex:Hide()
bar._rainbowActive = nil
end
end
function SFrames:InitSlashCommands()
DEFAULT_CHAT_FRAME:AddMessage("SF: InitSlashCommands called.")
SLASH_SFRAMES1 = "/nanami"
SLASH_SFRAMES2 = "/nui"
SlashCmdList["SFRAMES"] = function(msg)
local text = msg or ""
text = string.gsub(text, "^%s+", "")
text = string.gsub(text, "%s+$", "")
local _, _, cmd, args = string.find(text, "^(%S+)%s*(.-)$")
cmd = string.lower(cmd or "")
args = args or ""
if cmd == "unlock" or cmd == "move" then
SFrames:UnlockFrames()
elseif cmd == "lock" then
SFrames:LockFrames()
elseif cmd == "chatreset" then
if SFrames.Chat and SFrames.Chat.ResetPosition then
SFrames.Chat:ResetPosition()
end
elseif cmd == "test" then
if SFrames.Party and SFrames.Party.TestMode then SFrames.Party:TestMode() end
elseif cmd == "partyh" then
if SFrames.Party and SFrames.Party.SetLayout then
SFrames.Party:SetLayout("horizontal")
SFrames:Print("Party layout set to horizontal.")
end
elseif cmd == "partyv" then
if SFrames.Party and SFrames.Party.SetLayout then
SFrames.Party:SetLayout("vertical")
SFrames:Print("Party layout set to vertical.")
end
elseif cmd == "partylayout" or cmd == "playout" then
local mode = string.lower(args or "")
if mode == "h" or mode == "horizontal" then
if SFrames.Party and SFrames.Party.SetLayout then
SFrames.Party:SetLayout("horizontal")
SFrames:Print("Party layout set to horizontal.")
end
elseif mode == "v" or mode == "vertical" then
if SFrames.Party and SFrames.Party.SetLayout then
SFrames.Party:SetLayout("vertical")
SFrames:Print("Party layout set to vertical.")
end
else
local current = (SFramesDB and SFramesDB.partyLayout) or "vertical"
SFrames:Print("Usage: /nui partylayout horizontal|vertical (current: " .. current .. ")")
end
elseif cmd == "focus" then
if not SFrames.Focus then
SFrames:Print("Focus module unavailable.")
return
end
local ok, name, usedNative = SFrames.Focus:SetFromTarget()
if ok then
if usedNative then
SFrames:Print("Focus set: " .. tostring(name) .. " (native)")
else
SFrames:Print("Focus set: " .. tostring(name))
end
else
SFrames:Print("No valid target to set focus.")
end
elseif cmd == "clearfocus" or cmd == "cf" then
if not SFrames.Focus then
SFrames:Print("Focus module unavailable.")
return
end
SFrames.Focus:Clear()
SFrames:Print("Focus cleared.")
elseif cmd == "targetfocus" or cmd == "tf" then
if not SFrames.Focus then
SFrames:Print("Focus module unavailable.")
return
end
local ok = SFrames.Focus:Target()
if not ok then
SFrames:Print("Focus target not found.")
end
elseif cmd == "fcast" or cmd == "focuscast" then
if not SFrames.Focus then
SFrames:Print("Focus module unavailable.")
return
end
local ok, reason = SFrames.Focus:Cast(args)
if not ok then
if reason == "NO_SPELL" then
SFrames:Print("Usage: /nui fcast <spell name>")
elseif reason == "NO_FOCUS" then
SFrames:Print("No focus set.")
elseif reason == "FOCUS_NOT_FOUND" then
SFrames:Print("Focus not found in range/scene.")
else
SFrames:Print("Focus cast failed.")
end
end
elseif cmd == "focushelp" then
SFrames:Print("/nui focus - set current target as focus")
SFrames:Print("/nui clearfocus - clear focus")
SFrames:Print("/nui targetfocus - target focus")
SFrames:Print("/nui fcast <spell> - cast spell on focus")
SFrames:Print("/nui partyh / partyv - switch party layout")
SFrames:Print("/nui partylayout h|v - switch party layout")
SFrames:Print("/nui ui - open UI settings")
SFrames:Print("/nui bags - open bag settings")
SFrames:Print("/nui chat - open chat settings panel")
SFrames:Print("/nui chat help - chat command help")
SFrames:Print("/nui chatreset - reset chat frame position")
SFrames:Print("/nui afk - toggle AFK screen")
SFrames:Print("/nui pin - 地图标记 (clear/share)")
SFrames:Print("/nui nav - 切换导航地图")
SFrames:Print("/nui mapscan - 扫描所有地图更新迷雾揭示数据")
SFrames:Print("/nui mapstats - 查看地图覆盖层数据统计")
SFrames:Print("/nui mapexport - 导出扫描到的地图数据")
SFrames:Print("/nui mapclear - 清除已保存的扫描数据")
SFrames:Print("/nui layout - 布局模式(拖拽调整所有框体位置)")
SFrames:Print("/nui bind - 按键绑定模式(悬停按钮+按键)")
SFrames:Print("/nui talentdb - 天赋默认数据库管理/导出")
elseif cmd == "ui" or cmd == "uiconfig" then
if SFrames.ConfigUI and SFrames.ConfigUI.Build then SFrames.ConfigUI:Build("ui") end
elseif cmd == "chat" or cmd == "chatconfig" then
if SFrames.Chat and SFrames.Chat.HandleSlash then
SFrames.Chat:HandleSlash(args)
end
elseif cmd == "bags" or cmd == "bag" or cmd == "bagconfig" then
if SFrames.ConfigUI and SFrames.ConfigUI.Build then SFrames.ConfigUI:Build("bags") end
elseif cmd == "mapreveal" or cmd == "mr" then
if SFrames.MapReveal and SFrames.MapReveal.Toggle then
SFrames.MapReveal:Toggle()
else
SFrames:Print("MapReveal module unavailable.")
end
elseif cmd == "stats" or cmd == "stat" or cmd == "ss" then
if SFrames.StatSummary and SFrames.StatSummary.Toggle then
SFrames.StatSummary:Toggle()
else
SFrames:Print("StatSummary module unavailable.")
end
elseif cmd == "iteminfo" or cmd == "ii" then
-- 打印鼠标悬停物品或指定itemID的GetItemInfo全部返回值
local itemId = tonumber(args)
local link
if itemId and itemId > 0 then
link = "item:" .. itemId .. ":0:0:0:0:0:0:0"
else
local ttName = GameTooltip:GetName()
local left1 = ttName and _G[ttName .. "TextLeft1"]
local name = left1 and left1:GetText()
if name and name ~= "" and GetItemLinkByName then
link = GetItemLinkByName(name)
end
if not link then
SFrames:Print("用法: /nui iteminfo <itemID> 或悬停物品后输入 /nui iteminfo")
return
end
end
-- 打印所有返回值(不预设数量,逐个检查)
local results = { GetItemInfo(link) }
SFrames:Print("|cff88ccff=== GetItemInfo 结果 ===|r")
SFrames:Print(" link: " .. tostring(link))
SFrames:Print(" 返回值数量: " .. table.getn(results))
for i = 1, table.getn(results) do
SFrames:Print(" [" .. i .. "] " .. tostring(results[i]))
end
if table.getn(results) == 0 then
SFrames:Print(" |cffff4444(无返回值,物品可能不在本地缓存中)|r")
end
elseif cmd == "afk" then
if SFrames.AFKScreen and SFrames.AFKScreen.Toggle then
SFrames.AFKScreen:Toggle()
else
SFrames:Print("AFKScreen module unavailable.")
end
elseif cmd == "pin" or cmd == "wp" or cmd == "waypoint" then
if not SFrames.WorldMap then
SFrames:Print("WorldMap module unavailable.")
elseif args == "clear" or args == "remove" then
SFrames.WorldMap:ClearWaypoint()
SFrames:Print("地图标记已清除")
elseif args == "share" then
SFrames.WorldMap:ShareWaypoint()
else
SFrames:Print("/nui pin clear - 清除地图标记")
SFrames:Print("/nui pin share - 分享当前标记到聊天")
SFrames:Print("在世界地图中 Ctrl+左键 可放置标记")
end
elseif cmd == "nav" or cmd == "navigation" then
if SFrames.WorldMap and SFrames.WorldMap.ToggleNav then
SFrames.WorldMap:ToggleNav()
else
SFrames:Print("WorldMap module unavailable.")
end
elseif cmd == "mapscan" or cmd == "scanmap" then
if SFrames.MapReveal and SFrames.MapReveal.ScanAllMaps then
SFrames.MapReveal:ScanAllMaps()
else
SFrames:Print("MapReveal module unavailable.")
end
elseif cmd == "mapexport" then
if SFrames.MapReveal and SFrames.MapReveal.ExportScannedData then
SFrames.MapReveal:ExportScannedData()
else
SFrames:Print("MapReveal module unavailable.")
end
elseif cmd == "mapstats" then
if SFrames.MapReveal and SFrames.MapReveal.ShowStats then
SFrames.MapReveal:ShowStats()
else
SFrames:Print("MapReveal module unavailable.")
end
elseif cmd == "mapclear" then
if SFrames.MapReveal and SFrames.MapReveal.ClearSavedData then
SFrames.MapReveal:ClearSavedData()
else
SFrames:Print("MapReveal module unavailable.")
end
elseif cmd == "layout" or cmd == "movers" then
if SFrames.Movers and SFrames.Movers.ToggleLayoutMode then
SFrames.Movers:ToggleLayoutMode()
else
SFrames:Print("Layout mode unavailable.")
end
elseif cmd == "bind" or cmd == "keybind" then
if SFrames.ActionBars and SFrames.ActionBars.ToggleKeyBindMode then
SFrames.ActionBars:ToggleKeyBindMode()
else
SFrames:Print("ActionBars module unavailable.")
end
elseif cmd == "keybinds" or cmd == "kb" then
local KBM = SFrames.KeyBindManager
if not KBM then
SFrames:Print("KeyBindManager module unavailable.")
elseif args == "" then
if SFrames.ConfigUI and SFrames.ConfigUI.Build then
SFrames.ConfigUI:Build("keybinds")
end
elseif string.find(args, "^save ") then
local name = string.gsub(args, "^save ", "")
if name ~= "" then KBM:SaveProfile(name) end
elseif string.find(args, "^load ") then
local name = string.gsub(args, "^load ", "")
if name ~= "" then KBM:LoadProfile(name) end
elseif string.find(args, "^delete ") then
local name = string.gsub(args, "^delete ", "")
if name ~= "" then KBM:DeleteProfile(name) end
elseif args == "list" then
local list = KBM:GetProfileList()
if table.getn(list) == 0 then
SFrames:Print("没有保存的按键绑定方案")
else
SFrames:Print("按键绑定方案列表:")
for _, name in ipairs(list) do
local info = KBM:GetProfileInfo(name)
local desc = info and (info.charName .. ", " .. info.count .. "") or ""
SFrames:Print(" |cffffd100" .. name .. "|r " .. desc)
end
end
elseif args == "export" then
KBM:ShowExportDialog()
elseif args == "import" then
KBM:ShowImportDialog()
else
SFrames:Print("/nui keybinds - 打开设置面板")
SFrames:Print("/nui keybinds save <名称> - 保存当前绑定为方案")
SFrames:Print("/nui keybinds load <名称> - 加载方案")
SFrames:Print("/nui keybinds delete <名称> - 删除方案")
SFrames:Print("/nui keybinds list - 列出所有方案")
SFrames:Print("/nui keybinds export - 导出当前绑定")
SFrames:Print("/nui keybinds import - 导入绑定")
end
elseif cmd == "talentdb" or cmd == "tdb" then
if SFrames.TalentTree and SFrames.TalentTree.HandleTalentDBCommand then
SFrames.TalentTree:HandleTalentDBCommand(args)
else
SFrames:Print("TalentTree module unavailable.")
end
elseif cmd == "debugbuffs" or cmd == "db" then
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 当前所有 Buff")
for i = 0, 31 do
local buffIndex = GetPlayerBuff(i, "HELPFUL")
if buffIndex and buffIndex >= 0 then
local name = SFrames:GetBuffName(buffIndex)
local tex = GetPlayerBuffTexture(buffIndex) or "?"
local tl = GetPlayerBuffTimeLeft(buffIndex)
local timeStr = (tl and tl > 0 and tl < 99999) and string.format("%.1fs", tl) or "N/A"
local hidden = SFrames:IsBuffHidden(buffIndex) and "|cffff4444[隐藏]|r" or ""
DEFAULT_CHAT_FRAME:AddMessage(string.format(" #%d: |cffffd100%s|r %s %s", i, name or "(nil)", timeStr, hidden))
end
end
elseif cmd == "hidebuff" or cmd == "hb" then
if args == "" then
SFrames:Print("用法: /nui hidebuff <buff名称>")
else
if not SFramesDB.hiddenBuffs then SFramesDB.hiddenBuffs = {} end
SFramesDB.hiddenBuffs[args] = true
SFrames:Print("已隐藏 Buff: |cffffd100" .. args .. "|r")
end
elseif cmd == "unhidebuff" or cmd == "uhb" then
if args == "" then
SFrames:Print("用法: /nui unhidebuff <buff名称>")
else
if SFramesDB.hiddenBuffs then SFramesDB.hiddenBuffs[args] = nil end
SFrames:Print("已取消隐藏 Buff: |cffffd100" .. args .. "|r")
end
elseif cmd == "listhidden" or cmd == "lh" then
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r 已隐藏的 Buff 列表:")
if SFramesDB.hiddenBuffs then
for name, _ in pairs(SFramesDB.hiddenBuffs) do
DEFAULT_CHAT_FRAME:AddMessage(" |cffffd100" .. name .. "|r (用户自定义)")
end
end
elseif cmd == "profile" then
if SFrames.ConfigUI and SFrames.ConfigUI.Build then SFrames.ConfigUI:Build("profile") end
elseif cmd == "config" or cmd == "" then
if SFrames.ConfigUI and SFrames.ConfigUI.Build then SFrames.ConfigUI:Build("ui") end
else
local hex = SFrames.Theme and SFrames.Theme:GetAccentHex() or "ffffb3d9"
DEFAULT_CHAT_FRAME:AddMessage("|c" .. hex .. "[Nanami-UI]|r Commands: /nui, /nui ui, /nui bags, /nui chat, /nui layout, /nui unlock, /nui lock, /nui test, /nui partyh, /nui partyv, /nui focushelp, /nui mapreveal, /nui mapscan, /nui stats, /nui afk, /nui pin, /nui bind, /nui keybinds, /nui profile")
end
end
end
function SFrames:UnlockFrames()
self.isUnlocked = true
self:Print("Frames Unlocked. Drag to move.")
-- Show overlays or just let them be dragged if they are always movable
if SFramesPlayerFrame then SFramesPlayerFrame:EnableMouse(true) end
if SFramesPetFrame then SFramesPetFrame:EnableMouse(true) end
if SFramesTargetFrame then SFramesTargetFrame:EnableMouse(true) end
if SFrames.Chat and SFrames.Chat.SetUnlocked then
SFrames.Chat:SetUnlocked(true)
end
end
function SFrames:LockFrames()
self.isUnlocked = false
self:Print("Frames Locked.")
if SFrames.Chat and SFrames.Chat.SetUnlocked then
SFrames.Chat:SetUnlocked(false)
end
end
function SFrames:HideBlizzardFrames()
-- Hide Character Frame (replaced by CharacterPanel.lua)
-- Only suppress if the custom character panel is enabled
if (not SFramesDB) or (SFramesDB.charPanelEnable ~= false) then
if CharacterFrame then
CharacterFrame:UnregisterAllEvents()
CharacterFrame:Hide()
CharacterFrame.Show = function() end
end
if PaperDollFrame then PaperDollFrame:Hide() end
if ReputationFrame then ReputationFrame:Hide() end
if SkillFrame then SkillFrame:Hide() end
if HonorFrame then HonorFrame:Hide() end
end
if SFramesDB and SFramesDB.enableUnitFrames == false then
-- Keep Blizzard unit frames when Nanami frames are disabled
else
if not SFramesDB or SFramesDB.enablePlayerFrame ~= false then
if PlayerFrame then
PlayerFrame:UnregisterAllEvents()
PlayerFrame:Hide()
PlayerFrame.Show = function() end
end
if PetFrame then
PetFrame:UnregisterAllEvents()
PetFrame:Hide()
PetFrame.Show = function() end
end
end
if not SFramesDB or SFramesDB.enableTargetFrame ~= false then
if TargetFrame then
TargetFrame:UnregisterAllEvents()
TargetFrame:Hide()
TargetFrame.Show = function() end
end
-- 禁用原版目标框体右键菜单
if TargetFrameDropDown then
TargetFrameDropDown:Hide()
TargetFrameDropDown.initialize = function() end
end
if ComboFrame then
ComboFrame:UnregisterAllEvents()
ComboFrame:SetScript("OnUpdate", nil)
ComboFrame:Hide()
ComboFrame.Show = function() end
ComboFrame.fadeInfo = {}
if ComboFrame_Update then
ComboFrame_Update = function() end
end
if ComboFrame_OnUpdate then
ComboFrame_OnUpdate = function() end
end
end
end
if not SFramesDB or SFramesDB.enablePartyFrame ~= false then
for i = 1, 4 do
local pf = _G["PartyMemberFrame"..i]
if pf then
pf:UnregisterAllEvents()
pf:Hide()
pf.Show = function() end
end
end
end
end
-- Hide Native Raid Frames if SFrames raid is enabled
if (not SFramesDB) or (SFramesDB.enableRaidFrames ~= false) then
local function NeuterBlizzardRaidUI()
if RaidFrame then
RaidFrame:UnregisterAllEvents()
RaidFrame:SetScript("OnEvent", nil)
RaidFrame:SetScript("OnUpdate", nil)
end
for i = 1, NUM_RAID_GROUPS or 8 do
local rgf = _G["RaidGroupButton"..i]
if rgf then
rgf:UnregisterAllEvents()
end
end
RaidPullout_Update = function() return {} end
RaidPullout_OnEvent = function() return {} end
RaidGroupFrame_OnEvent = function() return {} end
RaidGroupFrame_Update = RaidGroupFrame_Update or function() end
if not RAID_SUBGROUP_LISTS then RAID_SUBGROUP_LISTS = {} end
for i = 1, NUM_RAID_GROUPS or 8 do
if not RAID_SUBGROUP_LISTS[i] then
RAID_SUBGROUP_LISTS[i] = {}
end
end
for i = 1, 40 do
local pf = _G["RaidPullout"..i]
if pf then
pf:UnregisterAllEvents()
pf:Hide()
pf.Show = function() end
end
end
if CompactRaidFrameManager then
CompactRaidFrameManager:UnregisterAllEvents()
CompactRaidFrameManager:Hide()
CompactRaidFrameManager.Show = function() end
end
if CompactRaidFrameContainer then
CompactRaidFrameContainer:UnregisterAllEvents()
CompactRaidFrameContainer:Hide()
CompactRaidFrameContainer.Show = function() end
end
end
NeuterBlizzardRaidUI()
local raidHook = CreateFrame("Frame")
raidHook:RegisterEvent("ADDON_LOADED")
raidHook:SetScript("OnEvent", function()
if arg1 == "Blizzard_RaidUI" then
NeuterBlizzardRaidUI()
end
end)
end
end