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

1423 lines
51 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.

--------------------------------------------------------------------------------
-- Nanami-UI: Tweaks -- Ported from ShaguTweaks
-- 1. Auto Stance - auto switch warrior/druid stance on spell cast
-- 2. SuperWoW - GUID-based cast/channel data for SuperWoW client
-- 3. Turtle Compat - hide TW's overlapping target HP text, etc.
-- 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. Hunter Aspect Guard - cancel Cheetah/Pack when taking damage in combat (avoid OOC false positives)
-- 8. Mouseover Cast - cast on mouseover unit without changing target
--------------------------------------------------------------------------------
SFrames.Tweaks = SFrames.Tweaks or {}
local Tweaks = SFrames.Tweaks
SFrames.castdb = SFrames.castdb or {}
SFrames.guidToName = SFrames.guidToName or {}
SFrames.castByName = SFrames.castByName or {} -- [unitName] = { spell, start, casttime, icon, channel }
local function GetTweaksCfg()
if not SFramesDB or type(SFramesDB.Tweaks) ~= "table" then
return { autoStance = true, superWoW = true, turtleCompat = true,
cooldownNumbers = true, darkUI = false, worldMapWindow = false,
hunterAspectGuard = true, mouseoverCast = false }
end
return SFramesDB.Tweaks
end
local function strsplit(delimiter, str)
local result = {}
local pattern = "([^" .. delimiter .. "]+)"
for match in string.gfind(str, pattern) do
table.insert(result, match)
end
return unpack(result)
end
-- hooksecurefunc polyfill for WoW 1.12 (vanilla)
local _hooks = {}
local _hooksecurefunc = hooksecurefunc
if not _hooksecurefunc then
_hooksecurefunc = function(tbl, name, func)
if type(tbl) == "string" then
func = name
name = tbl
tbl = getfenv(0)
end
if not tbl or not tbl[name] then return end
local key = tostring(func)
_hooks[key] = {}
_hooks[key].old = tbl[name]
_hooks[key].new = func
tbl[name] = function(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
local r1,r2,r3,r4,r5 = _hooks[key].old(a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
pcall(_hooks[key].new, a1,a2,a3,a4,a5,a6,a7,a8,a9,a10)
return r1,r2,r3,r4,r5
end
end
end
--------------------------------------------------------------------------------
-- Auto Stance
-- When a spell fails because you're in the wrong stance/form, automatically
-- switch to the required one. Works for warriors, druids, etc.
--------------------------------------------------------------------------------
local function InitAutoStance()
local frame = CreateFrame("Frame", "NanamiAutoStance")
local scanString = string.gsub(SPELL_FAILED_ONLY_SHAPESHIFT, "%%s", "(.+)")
frame:RegisterEvent("UI_ERROR_MESSAGE")
frame:SetScript("OnEvent", function()
for stances in string.gfind(arg1, scanString) do
for _, stance in pairs({ strsplit(",", stances) }) do
CastSpellByName(string.gsub(stance, "^%s*(.-)%s*$", "%1"))
end
end
end)
end
--------------------------------------------------------------------------------
-- SuperWoW Compatibility
-- Provides GUID-based cast/channel data when SuperWoW client mod is active.
-- Data stored in SFrames.castdb[guid] for consumption by castbar features.
--------------------------------------------------------------------------------
local function InitSuperWoW()
if not SpellInfo and not UnitGUID and not SUPERWOW_VERSION then return end
local castdb = SFrames.castdb
local frame = CreateFrame("Frame", "NanamiSuperWoW")
frame:RegisterEvent("UNIT_CASTEVENT")
frame:SetScript("OnEvent", function()
if arg3 == "START" or arg3 == "CAST" or arg3 == "CHANNEL" then
local guid = arg1
local spell, icon, _
if SpellInfo and SpellInfo(arg4) then
spell, _, icon = SpellInfo(arg4)
end
spell = spell or UNKNOWN
icon = icon or "Interface\\Icons\\INV_Misc_QuestionMark"
if not castdb[guid] then castdb[guid] = {} end
castdb[guid].cast = spell
castdb[guid].rank = nil
castdb[guid].start = GetTime()
castdb[guid].casttime = arg5
castdb[guid].icon = icon
castdb[guid].channel = (arg3 == "CHANNEL") or false
-- Build GUID -> name mapping and castByName
-- Try UnitName(guid) directly (SuperWoW allows GUID as unitID)
local castName
local ok_n, gname = pcall(UnitName, guid)
if ok_n and gname and gname ~= "" and gname ~= UNKNOWN then
castName = gname
SFrames.guidToName[guid] = gname
end
-- Fallback: scan known unitIDs with UnitGUID
if not castName and UnitGUID then
local scanUnits = {"target","targettarget","party1","party2","party3","party4",
"party1target","party2target","party3target","party4target"}
local rn = GetNumRaidMembers and GetNumRaidMembers() or 0
for ri = 1, rn do
table.insert(scanUnits, "raid"..ri)
table.insert(scanUnits, "raid"..ri.."target")
end
for _, u in ipairs(scanUnits) do
local ok1, exists = pcall(UnitExists, u)
if ok1 and exists then
local ok2, ug = pcall(UnitGUID, u)
if ok2 and ug and ug == guid then
local ok3, uname = pcall(UnitName, u)
if ok3 and uname then
castName = uname
SFrames.guidToName[guid] = uname
end
break
end
end
end
end
-- Write castByName for name-based consumers (Focus castbar)
if castName then
SFrames.castByName[castName] = {
cast = spell,
start = GetTime(),
casttime = arg5,
icon = icon,
channel = (arg3 == "CHANNEL") or false,
}
end
SFrames.superwow_active = true
elseif arg3 == "FAIL" then
local guid = arg1
if castdb[guid] then
castdb[guid].cast = nil
castdb[guid].rank = nil
castdb[guid].start = nil
castdb[guid].casttime = nil
castdb[guid].icon = nil
castdb[guid].channel = nil
end
-- Clear castByName
local failName = SFrames.guidToName[guid]
if not failName then
local ok_n, gname = pcall(UnitName, guid)
if ok_n and gname and gname ~= "" and gname ~= UNKNOWN then failName = gname end
end
if failName and SFrames.castByName[failName] then
SFrames.castByName[failName] = nil
end
end
end)
end
--------------------------------------------------------------------------------
-- Turtle WoW Compatibility
-- Hides TW's built-in target HP text that overlaps with Nanami-UI frames,
-- and applies other TW-specific fixes.
--------------------------------------------------------------------------------
local function ApplyWorldMapWindowLayout()
WorldMapFrame:SetMovable(true)
WorldMapFrame:EnableMouse(true)
WorldMapFrame:SetScale(.85)
WorldMapFrame:ClearAllPoints()
WorldMapFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 30)
WorldMapFrame:SetWidth(WorldMapButton:GetWidth() + 15)
WorldMapFrame:SetHeight(WorldMapButton:GetHeight() + 55)
if WorldMapFrameTitle then
WorldMapFrameTitle:SetPoint("TOP", WorldMapFrame, 0, 17)
end
BlackoutWorld:Hide()
end
local function InitTurtleCompat()
if not TargetHPText or not TargetHPPercText then return end
TargetHPText:Hide()
TargetHPText.Show = function() return end
TargetHPPercText:Hide()
TargetHPPercText.Show = function() return end
if WorldMapFrame_Maximize then
local origMaximize = WorldMapFrame_Maximize
WorldMapFrame_Maximize = function()
origMaximize()
local cfg = GetTweaksCfg()
if cfg.worldMapWindow ~= false then
ApplyWorldMapWindowLayout()
elseif WorldMapFrameTitle then
WorldMapFrameTitle:SetPoint("TOP", WorldMapFrame, 0, 17)
end
end
end
end
--------------------------------------------------------------------------------
-- WorldMap Window
-- Turn the fullscreen world map into a movable, scalable window.
-- Ctrl+Scroll to zoom, Shift+Scroll to change transparency, drag to move.
--------------------------------------------------------------------------------
local function HookScript(frame, script, func)
local prev = frame:GetScript(script)
frame:SetScript(script, function(a1,a2,a3,a4,a5,a6,a7,a8,a9)
if prev then prev(a1,a2,a3,a4,a5,a6,a7,a8,a9) end
func(a1,a2,a3,a4,a5,a6,a7,a8,a9)
end)
end
local function InitWorldMapWindow()
if Cartographer or METAMAP_TITLE then return end
table.insert(UISpecialFrames, "WorldMapFrame")
local _G = getfenv(0)
_G.ToggleWorldMap = function()
if WorldMapFrame:IsShown() then
WorldMapFrame:Hide()
else
WorldMapFrame:Show()
end
end
UIPanelWindows["WorldMapFrame"] = { area = "center" }
HookScript(WorldMapFrame, "OnShow", function()
this:EnableKeyboard(false)
this:EnableMouseWheel(1)
WorldMapFrame:SetScale(.85)
WorldMapFrame:SetAlpha(1)
WorldMapFrame:SetFrameStrata("FULLSCREEN_DIALOG")
end)
HookScript(WorldMapFrame, "OnMouseWheel", function()
if IsShiftKeyDown() then
local newAlpha = WorldMapFrame:GetAlpha() + arg1 / 10
if newAlpha < 0.2 then newAlpha = 0.2 end
if newAlpha > 1 then newAlpha = 1 end
WorldMapFrame:SetAlpha(newAlpha)
elseif IsControlKeyDown() then
local newScale = WorldMapFrame:GetScale() + arg1 / 10
if newScale < 0.4 then newScale = 0.4 end
if newScale > 1.5 then newScale = 1.5 end
WorldMapFrame:SetScale(newScale)
end
end)
HookScript(WorldMapFrame, "OnMouseDown", function()
WorldMapFrame:StartMoving()
end)
HookScript(WorldMapFrame, "OnMouseUp", function()
WorldMapFrame:StopMovingOrSizing()
end)
ApplyWorldMapWindowLayout()
-- WorldMapTooltip: raw textures on a child frame (SetBackdrop is unreliable)
if WorldMapTooltip and not WorldMapTooltip._nanamiBG then
WorldMapTooltip._nanamiBG = true
local wmtBgFrame = CreateFrame("Frame", nil, WorldMapTooltip)
wmtBgFrame:SetAllPoints(WorldMapTooltip)
wmtBgFrame:SetFrameLevel(math.max(0, WorldMapTooltip:GetFrameLevel()))
local bg = wmtBgFrame:CreateTexture(nil, "BACKGROUND")
bg:SetTexture("Interface\\Buttons\\WHITE8X8")
bg:SetVertexColor(0.05, 0.05, 0.05, 1)
bg:SetAllPoints(wmtBgFrame)
local function MakeEdge(p1, r1, p2, r2, w, h)
local t = wmtBgFrame:CreateTexture(nil, "BORDER")
t:SetTexture("Interface\\Buttons\\WHITE8X8")
t:SetVertexColor(0.25, 0.25, 0.25, 1)
t:SetPoint(p1, WorldMapTooltip, r1)
t:SetPoint(p2, WorldMapTooltip, r2)
if w then t:SetWidth(w) end
if h then t:SetHeight(h) end
end
MakeEdge("TOPLEFT","TOPLEFT","TOPRIGHT","TOPRIGHT", nil, 1)
MakeEdge("BOTTOMLEFT","BOTTOMLEFT","BOTTOMRIGHT","BOTTOMRIGHT", nil, 1)
MakeEdge("TOPLEFT","TOPLEFT","BOTTOMLEFT","BOTTOMLEFT", 1, nil)
MakeEdge("TOPRIGHT","TOPRIGHT","BOTTOMRIGHT","BOTTOMRIGHT", 1, nil)
end
end
--------------------------------------------------------------------------------
-- Cooldown Numbers
-- Display remaining duration as text on every cooldown frame (>= 2 sec).
--------------------------------------------------------------------------------
local function TimeConvert(remaining)
local color = "|cffffffff"
if remaining < 5 then
color = "|cffff5555"
elseif remaining < 10 then
color = "|cffffff55"
end
if remaining < 60 then
return color .. math.ceil(remaining)
elseif remaining < 3600 then
return color .. math.ceil(remaining / 60) .. "m"
elseif remaining < 86400 then
return color .. math.ceil(remaining / 3600) .. "h"
else
return color .. math.ceil(remaining / 86400) .. "d"
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
if not this.tick then this.tick = GetTime() + 0.1 end
if this.tick > GetTime() then return end
this.tick = GetTime() + 0.1
this:SetAlpha(parent:GetAlpha())
if this.start < GetTime() then
local remaining = this.duration - (GetTime() - this.start)
if remaining > 0 then
this.text:SetText(TimeConvert(remaining))
else
this:Hide()
end
else
local time = time()
local startupTime = time - GetTime()
local cdTime = (2 ^ 32) / 1000 - this.start
local cdStartTime = startupTime - cdTime
local cdEndTime = cdStartTime + this.duration
local remaining = cdEndTime - time
if remaining >= 0 then
this.text:SetText(TimeConvert(remaining))
else
this:Hide()
end
end
end
local function IsActionBarButtonName(name)
if not name then return false end
return string.find(name, "^ActionButton%d+$")
or string.find(name, "^BonusActionButton%d+$")
or string.find(name, "^MultiBarBottomLeftButton%d+$")
or string.find(name, "^MultiBarBottomRightButton%d+$")
or string.find(name, "^MultiBarLeftButton%d+$")
or string.find(name, "^MultiBarRightButton%d+$")
or string.find(name, "^PetActionButton%d+$")
or string.find(name, "^ShapeshiftButton%d+$")
end
local function UpdateActionBarCooldownMask(cooldown)
if not cooldown then return end
if cooldown.cooldownmask then
cooldown.cooldownmask:SetAllPoints(cooldown)
cooldown.cooldownmask:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldownmask:SetFrameLevel(cooldown:GetFrameLevel() + 1)
end
if cooldown.cooldowntext then
cooldown.cooldowntext:SetAllPoints(cooldown)
cooldown.cooldowntext:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldowntext:SetFrameLevel(cooldown:GetFrameLevel() + 2)
end
end
local function CreateCoolDown(cooldown, start, duration)
if not cooldown then return end
local parent = cooldown:GetParent()
if not parent then return end
if cooldown.readable then return end
local parentname = parent and parent.GetName and parent:GetName()
parentname = parentname or "UnknownCooldownFrame"
cooldown.cooldowntext = CreateFrame("Frame", parentname .. "NanamiCDText", cooldown)
cooldown.cooldowntext:SetAllPoints(cooldown)
cooldown.cooldowntext:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldowntext:SetFrameLevel(cooldown:GetFrameLevel() + 2)
cooldown.cooldowntext.text = cooldown.cooldowntext:CreateFontString(
parentname .. "NanamiCDFont", "OVERLAY")
local isActionBar = IsActionBarButtonName(parentname)
local size = parent:GetHeight() or 0
size = size > 0 and size * 0.64 or 12
size = size > 14 and 14 or size
if isActionBar then
local bigSize = size * 1.22
if bigSize < 13 then bigSize = 13 end
if bigSize > 18 then bigSize = 18 end
cooldown.cooldowntext.text:SetFont(STANDARD_TEXT_FONT, bigSize, "THICKOUTLINE")
else
cooldown.cooldowntext.text:SetFont(STANDARD_TEXT_FONT, size, "OUTLINE")
end
cooldown.cooldowntext.text:SetDrawLayer("OVERLAY", 7)
if isActionBar then
cooldown.cooldownmask = CreateFrame("Frame", parentname .. "NanamiCDMask", cooldown)
cooldown.cooldownmask:SetAllPoints(cooldown)
cooldown.cooldownmask:SetFrameStrata(cooldown:GetFrameStrata())
cooldown.cooldownmask:SetFrameLevel(cooldown:GetFrameLevel() + 1)
local mask = cooldown.cooldownmask:CreateTexture(nil, "BACKGROUND")
mask:SetTexture("Interface\\Buttons\\WHITE8X8")
mask:SetAllPoints(cooldown.cooldownmask)
mask:SetDrawLayer("BACKGROUND", 0)
mask:SetVertexColor(0, 0, 0, 0.45)
cooldown.cooldownmask.mask = mask
cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
else
cooldown.cooldowntext.text:SetPoint("CENTER", cooldown.cooldowntext, "CENTER", 0, 0)
end
RegisterCooldownFrame(cooldown.cooldowntext)
end
local function SetCooldown(frame, start, duration, enable)
if not frame then return end
if frame.noCooldownCount then return end
if not duration or duration < 2 then
if frame.cooldowntext then
frame.cooldowntext:Hide()
end
if frame.cooldownmask then
frame.cooldownmask:Hide()
end
return
end
if not frame.cooldowntext then
CreateCoolDown(frame, start, duration)
end
if frame.cooldowntext then
UpdateActionBarCooldownMask(frame)
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
end
end
local function InitCooldownNumbers()
_hooksecurefunc("CooldownFrame_SetTimer", SetCooldown)
end
--------------------------------------------------------------------------------
-- Quest Watch Timed Quest Countdown
-- Append remaining time for timed quests in the watched quest list (outside
-- quest detail UI), so players can see countdown directly on the main HUD.
--------------------------------------------------------------------------------
local function FormatQuestCountdown(seconds)
local s = tonumber(seconds) or 0
if s <= 0 then return "0s" end
local h = math.floor(s / 3600)
local m = math.floor(math.mod(s, 3600) / 60)
local sec = math.floor(math.mod(s, 60))
if h > 0 then
return string.format("%dh %02dm %02ds", h, m, sec)
elseif m > 0 then
return string.format("%dm %02ds", m, sec)
end
return string.format("%ds", sec)
end
local function InitQuestWatchCountdown()
if not QuestWatch_Update or not GetNumQuestWatches or not GetQuestLogTitle
or not GetQuestIndexForWatch or not GetQuestLogTimeLeft then
return
end
local function StripNanamiCountdown(text)
if not text then return text end
return string.gsub(text, " |cffffcc00%[剩余: [^%]]+%]|r$", "")
end
local applying = false
local function ApplyQuestWatchCountdown()
if applying then return end
applying = true
local timedByTitle = {}
local watchCount = tonumber(GetNumQuestWatches()) or 0
for i = 1, watchCount do
local questIndex = GetQuestIndexForWatch(i)
if questIndex and questIndex > 0 then
local title, level, tag, isHeader = GetQuestLogTitle(questIndex)
if title and not isHeader then
local timeLeft = GetQuestLogTimeLeft(questIndex)
if timeLeft and timeLeft > 0 then
timedByTitle[title] = FormatQuestCountdown(timeLeft)
end
end
end
end
local lineCount = tonumber(QUESTWATCHLINES) or 0
for i = 1, lineCount do
local fs = _G["QuestWatchLine" .. i]
if fs and fs.GetText and fs.SetText then
local text = fs:GetText()
if text and text ~= "" then
local clean = StripNanamiCountdown(text)
local timerText = timedByTitle[clean]
if timerText then
fs:SetText(clean .. " |cffffcc00[剩余: " .. timerText .. "]|r")
elseif clean ~= text then
fs:SetText(clean)
end
end
end
end
applying = false
end
_hooksecurefunc("QuestWatch_Update", ApplyQuestWatchCountdown)
local ticker = CreateFrame("Frame", "NanamiQuestWatchCountdownTicker")
ticker._e = 0
ticker:SetScript("OnUpdate", function()
this._e = (this._e or 0) + (arg1 or 0)
if this._e < 1.0 then return end
this._e = 0
if (tonumber(GetNumQuestWatches()) or 0) > 0 then
pcall(ApplyQuestWatchCountdown)
end
end)
end
--------------------------------------------------------------------------------
-- Dark UI
-- Turns the entire interface into darker colors by applying vertex color
-- tinting to all UI textures recursively.
--------------------------------------------------------------------------------
local function HookAddonOrVariable(addon, func)
local lurker = CreateFrame("Frame", nil)
lurker.func = func
lurker:RegisterEvent("ADDON_LOADED")
lurker:RegisterEvent("VARIABLES_LOADED")
lurker:RegisterEvent("PLAYER_ENTERING_WORLD")
lurker:SetScript("OnEvent", function()
if IsAddOnLoaded(addon) or getfenv(0)[addon] then
this:func()
this:UnregisterAllEvents()
end
end)
end
local borderBackdrop = {
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 8, edgeSize = 16,
insets = { left = 0, right = 0, top = 0, bottom = 0 }
}
local function AddBorder(frame, inset, color)
if not frame then return end
if frame.NanamiBorder then return frame.NanamiBorder end
local top, right, bottom, left
if type(inset) == "table" then
top, right, bottom, left = unpack(inset)
left, bottom = -left, -bottom
end
frame.NanamiBorder = CreateFrame("Frame", nil, frame)
frame.NanamiBorder:SetPoint("TOPLEFT", frame, "TOPLEFT",
(left or -inset), (top or inset))
frame.NanamiBorder:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT",
(right or inset), (bottom or -inset))
frame.NanamiBorder:SetBackdrop(borderBackdrop)
if color then
frame.NanamiBorder:SetBackdropBorderColor(color.r, color.g, color.b, 1)
end
return frame.NanamiBorder
end
local darkColor = { r = .3, g = .3, b = .3, a = .9 }
local darkBlacklist = {
["Solid Texture"] = true, ["WHITE8X8"] = true, ["StatusBar"] = true,
["BarFill"] = true, ["Portrait"] = true, ["Button"] = true,
["Icon"] = true, ["AddOns"] = true, ["StationeryTest"] = true,
["TargetDead"] = true, ["^KeyRing"] = true, ["GossipIcon"] = true,
["WorldMap\\(.+)\\"] = true, ["PetHappiness"] = true,
["Elite"] = true, ["Rare"] = true, ["ColorPickerWheel"] = true,
["ComboPoint"] = true, ["Skull"] = true,
["battlenetworking0"] = true, ["damage"] = true,
["tank"] = true, ["healer"] = true,
}
local darkRegionSkips = {
["ColorPickerFrame"] = { [15] = true }
}
local darkBackgrounds = {
["^SpellBookFrame$"] = { 325, 355, 17, -74 },
["^ItemTextFrame$"] = { 300, 355, 24, -74 },
}
local darkBorders = {
["ShapeshiftButton"] = 3, ["BuffButton"] = 3,
["TargetFrameBuff"] = 3, ["TempEnchant"] = 3,
["SpellButton"] = 3, ["SpellBookSkillLineTab"] = 3,
["ActionButton%d+$"] = 3, ["MultiBar(.+)Button%d+$"] = 3,
["KeyRingButton"] = 2,
["ActionBarUpButton"] = -3, ["ActionBarDownButton"] = -3,
["Character(.+)Slot$"] = 3, ["Inspect(.+)Slot$"] = 3,
["ContainerFrame(.+)Item"] = 3, ["MainMenuBarBackpackButton$"] = 3,
["CharacterBag(.+)Slot$"] = 3, ["ChatFrame(.+)Button"] = -2,
["PetFrameHappiness"] = 2, ["MicroButton"] = { -21, 0, 0, 0 },
}
local darkAddonFrames = {
["Blizzard_TalentUI"] = { "TalentFrame" },
["Blizzard_AuctionUI"] = { "AuctionFrame", "AuctionDressUpFrame" },
["Blizzard_CraftUI"] = { "CraftFrame" },
["Blizzard_InspectUI"] = { "InspectPaperDollFrame", "InspectHonorFrame", "InspectFrameTab1", "InspectFrameTab2" },
["Blizzard_MacroUI"] = { "MacroFrame", "MacroPopupFrame" },
["Blizzard_RaidUI"] = { "ReadyCheckFrame" },
["Blizzard_TradeSkillUI"] = { "TradeSkillFrame" },
-- ClassTrainerFrame replaced by TrainerUI.lua
}
local function IsDarkBlacklisted(texture)
local name = texture:GetName()
local tex = texture:GetTexture()
if not tex then return true end
if name then
for entry in pairs(darkBlacklist) do
if string.find(name, entry, 1) then return true end
end
end
for entry in pairs(darkBlacklist) do
if string.find(tex, entry, 1) then return true end
end
return nil
end
local function AddSpecialBackground(frame, w, h, x, y)
frame.NanamiMaterial = frame.NanamiMaterial or frame:CreateTexture(nil, "OVERLAY")
frame.NanamiMaterial:SetTexture("Interface\\Stationery\\StationeryTest1")
frame.NanamiMaterial:SetWidth(w)
frame.NanamiMaterial:SetHeight(h)
frame.NanamiMaterial:SetPoint("TOPLEFT", frame, x, y)
frame.NanamiMaterial:SetVertexColor(.8, .8, .8)
end
local darkFrameSkips = {
["^SFramesChat"] = true,
["^SFramesPlayer"] = true,
["^SFramesTarget"] = true,
["^SFramesParty"] = true,
["^SFramesRaid"] = true,
["^SFramesMBuff"] = true,
["^SFramesMDebuff"] = true,
["^GameTooltip"] = true,
}
local function DarkenFrame(frame, r, g, b, a)
if not r and not g and not b then
r, g, b, a = darkColor.r, darkColor.g, darkColor.b, darkColor.a
end
local fname = frame and frame.GetName and frame:GetName()
if fname then
for pattern in pairs(darkFrameSkips) do
if string.find(fname, pattern) then return end
end
end
if frame and frame.GetChildren then
for _, child in pairs({ frame:GetChildren() }) do
DarkenFrame(child, r, g, b, a)
end
end
if frame and frame.GetRegions then
local name = frame.GetName and frame:GetName()
if frame.SetBackdropBorderColor then
frame:SetBackdropBorderColor(darkColor.r, darkColor.g, darkColor.b, darkColor.a)
end
for pattern, inset in pairs(darkBackgrounds) do
if name and string.find(name, pattern) then
AddSpecialBackground(frame, inset[1], inset[2], inset[3], inset[4])
end
end
for pattern, inset in pairs(darkBorders) do
if name and string.find(name, pattern) then
AddBorder(frame, inset, darkColor)
end
end
for id, region in pairs({ frame:GetRegions() }) do
if region.SetVertexColor and region:GetObjectType() == "Texture" then
if name and id and darkRegionSkips[name] and darkRegionSkips[name][id] then
-- skip
elseif region.GetBlendMode and region:GetBlendMode() == "ADD" then
-- skip blend textures
elseif IsDarkBlacklisted(region) then
-- skip blacklisted
else
region:SetVertexColor(r, g, b, a)
end
end
end
end
end
--------------------------------------------------------------------------------
-- StaticPopup Theme Skin
--------------------------------------------------------------------------------
local function InitPopupSkin()
local popupSkinned = {}
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARKai_T.ttf"
local _A = SFrames.ActiveTheme
local P = {
bg = _A.panelBg or { 0.12, 0.06, 0.10, 0.95 },
border = _A.panelBorder or { 0.55, 0.30, 0.42, 0.9 },
btnBg = _A.btnBg or { 0.18, 0.10, 0.15, 0.94 },
btnBd = _A.btnBorder or { 0.50, 0.30, 0.40, 0.80 },
text = _A.nameText or { 0.90, 0.88, 0.94 },
}
local function HidePopupTex(tex)
if not tex then return end
if tex.SetTexture then tex:SetTexture(nil) end
if tex.SetAlpha then tex:SetAlpha(0) end
if tex.Hide then tex:Hide() end
end
local function SkinButton(btn)
if not btn then return end
local regions = { btn:GetRegions() }
for _, r in ipairs(regions) do
if r and r.SetTexture and r:GetObjectType() == "Texture" then
local tex = r:GetTexture()
if tex and type(tex) == "string" and (string.find(tex, "UI%-Panel") or string.find(tex, "UI%-DialogBox")) then
r:Hide()
end
end
end
HidePopupTex(btn.GetNormalTexture and btn:GetNormalTexture())
HidePopupTex(btn.GetPushedTexture and btn:GetPushedTexture())
HidePopupTex(btn.GetHighlightTexture and btn:GetHighlightTexture())
HidePopupTex(btn.GetDisabledTexture and btn:GetDisabledTexture())
local btnName = btn:GetName() or ""
for _, sfx in ipairs({"Left", "Right", "Middle"}) do
local t = _G[btnName .. sfx]
if t then t:SetAlpha(0); t:Hide() end
end
btn:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, tileSize = 0, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
btn:SetBackdropColor(P.btnBg[1], P.btnBg[2], P.btnBg[3], P.btnBg[4])
btn:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
local fs = btn:GetFontString()
if fs then
fs:SetFont(font, 12, "OUTLINE")
fs:SetTextColor(P.text[1], P.text[2], P.text[3])
end
if not btn.nanamiPopupStyled then
btn.nanamiPopupStyled = true
local origEnter = btn:GetScript("OnEnter")
local origLeave = btn:GetScript("OnLeave")
btn:SetScript("OnEnter", function()
if origEnter then origEnter() end
this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
if _A.btnHoverBorder then
this:SetBackdropBorderColor(_A.btnHoverBorder[1], _A.btnHoverBorder[2], _A.btnHoverBorder[3], _A.btnHoverBorder[4])
elseif _A.btnHoverBd then
this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
end
local t = this:GetFontString()
if t and _A.btnActiveText then t:SetTextColor(_A.btnActiveText[1], _A.btnActiveText[2], _A.btnActiveText[3]) end
end)
btn:SetScript("OnLeave", function()
if origLeave then origLeave() end
this:SetBackdropColor(P.btnBg[1], P.btnBg[2], P.btnBg[3], P.btnBg[4])
this:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
local t = this:GetFontString()
if t then t:SetTextColor(P.text[1], P.text[2], P.text[3]) end
end)
btn:SetScript("OnMouseDown", function()
if _A.btnDownBg then
this:SetBackdropColor(_A.btnDownBg[1], _A.btnDownBg[2], _A.btnDownBg[3], _A.btnDownBg[4])
end
end)
btn:SetScript("OnMouseUp", function()
this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
end)
end
end
local function SkinPopupFrame(frame)
if not frame then return end
local frameName = frame:GetName()
if not frameName then return end
if not popupSkinned[frameName] then
popupSkinned[frameName] = true
local regions = { frame:GetRegions() }
for _, r in ipairs(regions) do
if r and r:GetObjectType() == "Texture" then
local dl = r:GetDrawLayer()
if dl == "BACKGROUND" or dl == "BORDER" or dl == "ARTWORK" then
local tex = r:GetTexture() or ""
if type(tex) == "string" and (string.find(tex, "UI%-DialogBox") or string.find(tex, "UI%-Panel")) then
r:Hide()
end
end
end
end
frame:SetBackdrop({
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
tile = true, tileSize = 16, edgeSize = 14,
insets = { left = 3, right = 3, top = 3, bottom = 3 },
})
frame:SetBackdropColor(P.bg[1], P.bg[2], P.bg[3], P.bg[4])
frame:SetBackdropBorderColor(P.border[1], P.border[2], P.border[3], P.border[4])
local textFS = _G[frameName .. "Text"]
if textFS and textFS.SetFont then
textFS:SetFont(font, 13, "OUTLINE")
textFS:SetTextColor(P.text[1], P.text[2], P.text[3])
end
local editBox = _G[frameName .. "EditBox"]
if editBox then
editBox:SetBackdrop({
bgFile = "Interface\\Buttons\\WHITE8X8",
edgeFile = "Interface\\Buttons\\WHITE8X8",
tile = false, edgeSize = 1,
insets = { left = 1, right = 1, top = 1, bottom = 1 },
})
editBox:SetBackdropColor(0.05, 0.03, 0.05, 0.9)
editBox:SetBackdropBorderColor(P.btnBd[1], P.btnBd[2], P.btnBd[3], P.btnBd[4])
editBox:SetFont(font, 12, "OUTLINE")
editBox:SetTextColor(P.text[1], P.text[2], P.text[3])
end
local moneyFrame = _G[frameName .. "MoneyFrame"]
if moneyFrame then
local mRegions = { moneyFrame:GetRegions() }
for _, r in ipairs(mRegions) do
if r and r.SetFont then r:SetFont(font, 12, "OUTLINE") end
end
end
end
for _, suffix in ipairs({"Button1", "Button2", "Button3"}) do
local btn = _G[frameName .. suffix]
if btn then SkinButton(btn) end
end
end
for i = 1, 4 do
local f = _G["StaticPopup" .. i]
if f then pcall(SkinPopupFrame, f) end
end
if StaticPopup_Show then
local origShow = StaticPopup_Show
StaticPopup_Show = function(a1, a2, a3, a4)
local dialog = origShow(a1, a2, a3, a4)
if dialog then
pcall(SkinPopupFrame, dialog)
end
return dialog
end
end
end
local function InitDarkUI()
local hookBuffButton_Update = BuffButton_Update
BuffButton_Update = function(buttonName, index, filter)
hookBuffButton_Update(buttonName, index, filter)
local name = buttonName and index and buttonName .. index or this:GetName()
local original = getfenv(0)[name .. "Border"]
if original and this.NanamiBorder then
local r, g, b = original:GetVertexColor()
this.NanamiBorder:SetBackdropBorderColor(r, g, b, 1)
original:SetAlpha(0)
end
end
TOOLTIP_DEFAULT_COLOR.r = darkColor.r
TOOLTIP_DEFAULT_COLOR.g = darkColor.g
TOOLTIP_DEFAULT_COLOR.b = darkColor.b
TOOLTIP_DEFAULT_BACKGROUND_COLOR.r = darkColor.r
TOOLTIP_DEFAULT_BACKGROUND_COLOR.g = darkColor.g
TOOLTIP_DEFAULT_BACKGROUND_COLOR.b = darkColor.b
DarkenFrame(UIParent)
DarkenFrame(WorldMapFrame)
DarkenFrame(DropDownList1)
DarkenFrame(DropDownList2)
DarkenFrame(DropDownList3)
local bars = { "Action", "BonusAction", "MultiBarBottomLeft",
"MultiBarBottomRight", "MultiBarLeft", "MultiBarRight", "Shapeshift" }
for _, prefix in pairs(bars) do
for i = 1, NUM_ACTIONBAR_BUTTONS do
local button = getfenv(0)[prefix .. "Button" .. i]
local texture = getfenv(0)[prefix .. "Button" .. i .. "NormalTexture"]
if button and texture then
texture:SetWidth(60)
texture:SetHeight(60)
texture:SetPoint("CENTER", 0, 0)
AddBorder(button, 3)
end
end
end
for _, button in pairs({ MinimapZoomOut, MinimapZoomIn }) do
for _, func in pairs({ "GetNormalTexture", "GetDisabledTexture", "GetPushedTexture" }) do
if button[func] then
local tex = button[func](button)
if tex then
tex:SetVertexColor(darkColor.r + .2, darkColor.g + .2, darkColor.b + .2, 1)
end
end
end
end
for addon, data in pairs(darkAddonFrames) do
local skip = false
if SFramesDB and SFramesDB.enableTradeSkill ~= false then
if addon == "Blizzard_TradeSkillUI" or addon == "Blizzard_CraftUI" then
skip = true
end
end
if not skip then
for _, frameName in pairs(data) do
local fn = frameName
HookAddonOrVariable(fn, function()
DarkenFrame(getfenv(0)[fn])
end)
end
end
end
HookAddonOrVariable("Blizzard_TimeManager", function()
DarkenFrame(TimeManagerClockButton)
end)
HookAddonOrVariable("GameTooltipStatusBarBackdrop", function()
DarkenFrame(getfenv(0)["GameTooltipStatusBarBackdrop"])
end)
end
--------------------------------------------------------------------------------
-- Hunter Aspect Guard
-- 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")
if playerClass ~= "HUNTER" then return end
local CHEETAH_TEX = "ability_mount_jungletiger"
local PACK_TEX = "ability_mount_packhorse"
local function CancelDangerousAspect()
for i = 0, 31 do
local buffIdx = GetPlayerBuff(i, "HELPFUL")
if buffIdx and buffIdx >= 0 then
local tex = GetPlayerBuffTexture(buffIdx)
if tex then
local lower = string.lower(tex)
if string.find(lower, CHEETAH_TEX) or string.find(lower, PACK_TEX) then
CancelPlayerBuff(buffIdx)
SFrames:Print("受到伤害,已自动取消守护")
return true
end
end
end
end
return false
end
local lastHP = UnitHealth("player") or 0
local lastCancel = 0
local elapsed = 0
local frame = CreateFrame("Frame", "NanamiHunterAspectGuard")
frame:SetScript("OnUpdate", function()
elapsed = elapsed + (arg1 or 0)
if elapsed < 0.1 then return end
elapsed = 0
local hp = UnitHealth("player")
if hp <= 0 then
lastHP = 0
return
end
if lastHP > 0 and hp < lastHP and UnitAffectingCombat("player") then
if GetTime() - lastCancel >= 1.0 then
if CancelDangerousAspect() then
lastCancel = GetTime()
end
end
end
lastHP = hp
end)
end
--------------------------------------------------------------------------------
-- Combat Background Notify
-- Flash the Windows taskbar icon when entering combat (UnitXP SP3 required).
--------------------------------------------------------------------------------
local function InitCombatNotify()
if not (type(UnitXP) == "function" and pcall(UnitXP, "nop", "nop")) then return end
local f = CreateFrame("Frame", "NanamiCombatNotify")
f:RegisterEvent("PLAYER_REGEN_DISABLED")
f:SetScript("OnEvent", function()
pcall(UnitXP, "notify", "taskbarIcon")
end)
end
--------------------------------------------------------------------------------
-- Mouseover Cast
-- When enabled, action bar presses and CastSpellByName calls will target the
-- unit under the mouse cursor without changing current target.
--
-- Strategy:
-- Temporarily try the mouseover unit first while preserving the original
-- target. If the spell cannot resolve on mouseover, stop the pending target
-- mode, restore the original target, and retry there.
--------------------------------------------------------------------------------
local mouseoverCastEnabled = false
local origUseAction = nil
local origCastSpellByName = nil
local inMouseoverAction = false -- re-entrancy guard
local function GetMouseoverUnit()
local focus = GetMouseFocus and GetMouseFocus()
if focus then
if focus.unit and UnitExists(focus.unit) then
return focus.unit
end
local parent = focus:GetParent()
if parent and parent.unit and UnitExists(parent.unit) then
return parent.unit
end
end
if UnitExists("mouseover") then
return "mouseover"
end
return nil
end
local function CaptureTargetState()
local hadTarget = UnitExists("target")
return {
hadTarget = hadTarget,
name = hadTarget and UnitName("target") or nil,
}
end
local function RestoreTargetState(state)
if not state then return end
if state.hadTarget and state.name then
TargetLastTarget()
if not UnitExists("target") or UnitName("target") ~= state.name then
TargetByName(state.name, true)
end
else
ClearTarget()
end
end
local function ResolvePendingSpellTarget(unit)
if not (SpellIsTargeting and SpellIsTargeting()) then
return true
end
if unit then
if SpellCanTargetUnit then
if SpellCanTargetUnit(unit) then
SpellTargetUnit(unit)
end
else
SpellTargetUnit(unit)
end
end
if SpellIsTargeting and SpellIsTargeting() then
return false
end
return true
end
local function TryActionOnUnit(unit, action, cursor, onSelf)
if not unit then return false end
if not (UnitIsUnit and UnitExists("target") and UnitIsUnit(unit, "target")) then
TargetUnit(unit)
end
origUseAction(action, cursor, onSelf)
return ResolvePendingSpellTarget(unit)
end
local function TryCastSpellOnUnit(unit, spell)
if not unit then return false end
if not (UnitIsUnit and UnitExists("target") and UnitIsUnit(unit, "target")) then
TargetUnit(unit)
end
origCastSpellByName(spell)
return ResolvePendingSpellTarget(unit)
end
local function MouseoverUseAction(action, cursor, onSelf)
-- Don't interfere: picking up action, or re-entrant call
if cursor == 1 or inMouseoverAction then
return origUseAction(action, cursor, onSelf)
end
local moUnit = GetMouseoverUnit()
if not moUnit then
return origUseAction(action, cursor, onSelf)
end
local prevTarget = CaptureTargetState()
inMouseoverAction = true
local castOnMouseover = TryActionOnUnit(moUnit, action, cursor, onSelf)
if not castOnMouseover and SpellIsTargeting and SpellIsTargeting() then
SpellStopTargeting()
end
RestoreTargetState(prevTarget)
if not castOnMouseover and prevTarget.hadTarget then
origUseAction(action, cursor, onSelf)
if SpellIsTargeting and SpellIsTargeting() then
if not ResolvePendingSpellTarget("target") then
SpellStopTargeting()
end
end
end
inMouseoverAction = false
end
local function MouseoverCastSpellByName(spell, arg2)
-- Already has explicit target arg, pass through
if arg2 then
return origCastSpellByName(spell, arg2)
end
-- Re-entrancy guard (e.g. called from within MouseoverUseAction's chain)
if inMouseoverAction then
return origCastSpellByName(spell)
end
local moUnit = GetMouseoverUnit()
if not moUnit then
return origCastSpellByName(spell)
end
local prevTarget = CaptureTargetState()
inMouseoverAction = true
local castOnMouseover = TryCastSpellOnUnit(moUnit, spell)
if not castOnMouseover and SpellIsTargeting and SpellIsTargeting() then
SpellStopTargeting()
end
RestoreTargetState(prevTarget)
if not castOnMouseover and prevTarget.hadTarget then
origCastSpellByName(spell)
if SpellIsTargeting and SpellIsTargeting() then
if not ResolvePendingSpellTarget("target") then
SpellStopTargeting()
end
end
end
inMouseoverAction = false
end
local function InitMouseoverCast()
if UseAction then
origUseAction = UseAction
UseAction = MouseoverUseAction
end
if CastSpellByName then
origCastSpellByName = CastSpellByName
CastSpellByName = MouseoverCastSpellByName
end
mouseoverCastEnabled = true
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami] 鼠标指向施法已启用" ..
(SUPERWOW_VERSION and "SuperWoW 模式)" or "(目标切换模式)") .. "|r")
end
function Tweaks:SetMouseoverCast(enabled)
if enabled and not mouseoverCastEnabled then
InitMouseoverCast()
elseif not enabled and mouseoverCastEnabled then
if origUseAction then UseAction = origUseAction end
if origCastSpellByName then CastSpellByName = origCastSpellByName end
mouseoverCastEnabled = false
DEFAULT_CHAT_FRAME:AddMessage("|cff88ccff[Nanami] 鼠标指向施法已关闭|r")
end
end
--------------------------------------------------------------------------------
-- Module API
--------------------------------------------------------------------------------
function Tweaks:Initialize()
local cfg = GetTweaksCfg()
if cfg.autoStance ~= false then
local ok, err = pcall(InitAutoStance)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: AutoStance init failed: " .. tostring(err) .. "|r")
end
end
if cfg.superWoW ~= false then
local ok, err = pcall(InitSuperWoW)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: SuperWoW init failed: " .. tostring(err) .. "|r")
end
end
if cfg.turtleCompat ~= false then
local ok, err = pcall(InitTurtleCompat)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: TurtleCompat init failed: " .. tostring(err) .. "|r")
end
end
if SFrames.WorldMap and SFrames.WorldMap.initialized then
-- New WorldMap module has taken over; skip legacy code
elseif cfg.worldMapWindow ~= false then
local ok, err = pcall(InitWorldMapWindow)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: WorldMapWindow init failed: " .. tostring(err) .. "|r")
end
end
if cfg.cooldownNumbers ~= false then
local ok, err = pcall(InitCooldownNumbers)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: CooldownNumbers init failed: " .. tostring(err) .. "|r")
end
end
do
local ok, err = pcall(InitQuestWatchCountdown)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: QuestWatchCountdown init failed: " .. tostring(err) .. "|r")
end
end
if cfg.hunterAspectGuard ~= false then
local ok, err = pcall(InitHunterAspectGuard)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: HunterAspectGuard init failed: " .. tostring(err) .. "|r")
end
end
if cfg.combatNotify ~= false then
local ok, err = pcall(InitCombatNotify)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: CombatNotify init failed: " .. tostring(err) .. "|r")
end
end
if cfg.mouseoverCast then
local ok, err = pcall(InitMouseoverCast)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: MouseoverCast init failed: " .. tostring(err) .. "|r")
end
end
if cfg.darkUI then
local ok, err = pcall(InitDarkUI)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: DarkUI init failed: " .. tostring(err) .. "|r")
end
end
do
local ok, err = pcall(InitPopupSkin)
if not ok then
DEFAULT_CHAT_FRAME:AddMessage("|cffff4444Nanami-UI: PopupSkin init failed: " .. tostring(err) .. "|r")
end
end
end