1943 lines
70 KiB
Lua
1943 lines
70 KiB
Lua
--------------------------------------------------------------------------------
|
|
-- Nanami-UI: WorldMap Module (WorldMap.lua)
|
|
-- Full Nanami-UI themed world map: windowed, dark pink-purple skin,
|
|
-- hides all Blizzard decorations, custom title bar + close button,
|
|
-- brute-force tooltip theming while the map is open.
|
|
--------------------------------------------------------------------------------
|
|
|
|
SFrames = SFrames or {}
|
|
SFrames.WorldMap = {}
|
|
local WM = SFrames.WorldMap
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Nanami theme constants (matches SocialUI / ConfigUI / QuestUI)
|
|
--------------------------------------------------------------------------------
|
|
local PANEL_BACKDROP = {
|
|
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 },
|
|
}
|
|
local _A = SFrames.ActiveTheme
|
|
|
|
local TOOLTIP_BACKDROP = {
|
|
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 },
|
|
}
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- Config
|
|
--------------------------------------------------------------------------------
|
|
function WM:GetConfig()
|
|
SFramesDB = SFramesDB or {}
|
|
if type(SFramesDB.WorldMap) ~= "table" then
|
|
SFramesDB.WorldMap = { enabled = true, scale = 0.85 }
|
|
end
|
|
if SFramesDB.WorldMap.enabled == nil then
|
|
SFramesDB.WorldMap.enabled = true
|
|
end
|
|
return SFramesDB.WorldMap
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- HookScript helper
|
|
--------------------------------------------------------------------------------
|
|
local function HookScript(frame, script, fn)
|
|
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
|
|
fn(a1,a2,a3,a4,a5,a6,a7,a8,a9)
|
|
end)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 1. Hide Blizzard Decorations
|
|
-- Called at init AND on every WorldMapFrame:OnShow to counter Blizzard resets
|
|
--------------------------------------------------------------------------------
|
|
local function NukeFrame(f)
|
|
if not f then return end
|
|
f:Hide()
|
|
f:SetAlpha(0)
|
|
f:EnableMouse(false)
|
|
f:ClearAllPoints()
|
|
f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -9999, 9999)
|
|
f:SetWidth(1)
|
|
f:SetHeight(1)
|
|
if not f._nanamiNuked then
|
|
f._nanamiNuked = true
|
|
f.Show = function(self) self:Hide() end
|
|
if f.SetScript then
|
|
f:SetScript("OnShow", function() this:Hide() end)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function HideAllRegions(frame)
|
|
if not frame or not frame.GetRegions then return end
|
|
local regions = { frame:GetRegions() }
|
|
for _, r in ipairs(regions) do
|
|
if r and r.Hide then r:Hide() end
|
|
end
|
|
end
|
|
|
|
local blizzHooked = false
|
|
|
|
local function NukeBorderChildren(border)
|
|
if not border or not border.GetChildren then return end
|
|
local children = { border:GetChildren() }
|
|
for _, child in ipairs(children) do
|
|
local name = child.GetName and child:GetName() or ""
|
|
local isDropDown = name ~= "" and string.find(name, "DropDown")
|
|
if not isDropDown then
|
|
NukeFrame(child)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function HideBlizzardDecorations()
|
|
if BlackoutWorld then BlackoutWorld:Hide() end
|
|
|
|
local _G = getfenv(0)
|
|
|
|
local framesToNuke = {
|
|
"WorldMapFrameCloseButton",
|
|
"WorldMapFrameSizeUpButton",
|
|
"WorldMapFrameSizeDownButton",
|
|
}
|
|
for _, name in ipairs(framesToNuke) do
|
|
NukeFrame(_G[name])
|
|
end
|
|
|
|
HideAllRegions(WorldMapFrame)
|
|
|
|
local borderNames = {
|
|
"WorldMapFrameMiniBorderLeft",
|
|
"WorldMapFrameMiniBorderRight",
|
|
}
|
|
for _, name in ipairs(borderNames) do
|
|
local border = _G[name]
|
|
if border then
|
|
HideAllRegions(border)
|
|
NukeBorderChildren(border)
|
|
border:EnableMouse(false)
|
|
|
|
if not blizzHooked then
|
|
local oldShow = border.Show
|
|
if oldShow then
|
|
border.Show = function(self)
|
|
oldShow(self)
|
|
HideAllRegions(self)
|
|
NukeBorderChildren(self)
|
|
self:EnableMouse(false)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if WorldMapFrameTitle then
|
|
WorldMapFrameTitle:Hide()
|
|
end
|
|
|
|
blizzHooked = true
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 2. Create Nanami Skin
|
|
--------------------------------------------------------------------------------
|
|
local skinFrame, titleBar, titleText, closeBtn, coordText, mouseCoordText, coordOverlay
|
|
|
|
local function CreateNanamiSkin()
|
|
if skinFrame then return end
|
|
|
|
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
|
|
|
skinFrame = CreateFrame("Frame", "NanamiWorldMapSkin", WorldMapFrame)
|
|
skinFrame:SetFrameLevel(WorldMapFrame:GetFrameLevel())
|
|
skinFrame:SetAllPoints(WorldMapFrame)
|
|
skinFrame:SetBackdrop(PANEL_BACKDROP)
|
|
skinFrame:SetBackdropColor(unpack(_A.panelBg))
|
|
skinFrame:SetBackdropBorderColor(unpack(_A.panelBorder))
|
|
|
|
titleBar = CreateFrame("Frame", nil, skinFrame)
|
|
titleBar:SetHeight(26)
|
|
titleBar:SetPoint("TOPLEFT", skinFrame, "TOPLEFT", 4, -4)
|
|
titleBar:SetPoint("TOPRIGHT", skinFrame, "TOPRIGHT", -4, -4)
|
|
titleBar:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
titleBar:SetBackdropColor(unpack(_A.headerBg))
|
|
titleBar:SetBackdropBorderColor(unpack(_A.panelBorder))
|
|
|
|
local titleIco = SFrames:CreateIcon(titleBar, "worldmap", 14)
|
|
titleIco:SetDrawLayer("OVERLAY")
|
|
titleIco:SetPoint("LEFT", titleBar, "LEFT", 10, 0)
|
|
titleIco:SetVertexColor(unpack(_A.title))
|
|
|
|
titleText = titleBar:CreateFontString(nil, "OVERLAY")
|
|
titleText:SetFont(font, 13, "OUTLINE")
|
|
titleText:SetPoint("LEFT", titleIco, "RIGHT", 5, 0)
|
|
titleText:SetTextColor(unpack(_A.title))
|
|
titleText:SetText("世界地图")
|
|
|
|
local hintText = titleBar:CreateFontString(nil, "OVERLAY")
|
|
hintText:SetFont(font, 10, "")
|
|
hintText:SetPoint("RIGHT", titleBar, "RIGHT", -40, 0)
|
|
hintText:SetTextColor(unpack(_A.dimText))
|
|
hintText:SetText("Ctrl+滚轮缩放 | Shift+滚轮透明度 | Ctrl+左键标记")
|
|
|
|
closeBtn = CreateFrame("Button", nil, titleBar)
|
|
closeBtn:SetWidth(22)
|
|
closeBtn:SetHeight(22)
|
|
closeBtn:SetPoint("RIGHT", titleBar, "RIGHT", -2, 0)
|
|
closeBtn:SetBackdrop({
|
|
bgFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeSize = 1,
|
|
insets = { left = 1, right = 1, top = 1, bottom = 1 },
|
|
})
|
|
closeBtn:SetBackdropColor(unpack(_A.buttonDownBg))
|
|
closeBtn:SetBackdropBorderColor(unpack(_A.btnBorder))
|
|
|
|
local closeIcon = closeBtn:CreateTexture(nil, "OVERLAY")
|
|
closeIcon:SetTexture("Interface\\AddOns\\Nanami-UI\\img\\icon")
|
|
closeIcon:SetTexCoord(0.25, 0.375, 0, 0.125)
|
|
closeIcon:SetPoint("CENTER", 0, 0)
|
|
closeIcon:SetWidth(14)
|
|
closeIcon:SetHeight(14)
|
|
|
|
closeBtn:SetScript("OnClick", function()
|
|
WorldMapFrame:Hide()
|
|
end)
|
|
closeBtn:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(unpack(_A.btnHoverBg))
|
|
this:SetBackdropBorderColor(unpack(_A.btnHoverBd))
|
|
end)
|
|
closeBtn:SetScript("OnLeave", function()
|
|
this:SetBackdropColor(unpack(_A.buttonDownBg))
|
|
this:SetBackdropBorderColor(unpack(_A.btnBorder))
|
|
end)
|
|
|
|
-- High-level overlay for coordinates so they render above map content
|
|
coordOverlay = CreateFrame("Frame", "NanamiWorldMapCoordOverlay", WorldMapFrame)
|
|
coordOverlay:SetAllPoints(skinFrame)
|
|
coordOverlay:SetFrameStrata("TOOLTIP")
|
|
coordOverlay:SetFrameLevel(250)
|
|
coordOverlay:EnableMouse(false)
|
|
|
|
coordText = coordOverlay:CreateFontString(nil, "OVERLAY")
|
|
coordText:SetFont(font, 12, "OUTLINE")
|
|
coordText:SetPoint("BOTTOMLEFT", skinFrame, "BOTTOMLEFT", 12, 6)
|
|
coordText:SetTextColor(unpack(_A.title))
|
|
coordText:SetText("")
|
|
|
|
mouseCoordText = coordOverlay:CreateFontString(nil, "OVERLAY")
|
|
mouseCoordText:SetFont(font, 12, "OUTLINE")
|
|
mouseCoordText:SetPoint("BOTTOMRIGHT", skinFrame, "BOTTOMRIGHT", -12, 6)
|
|
mouseCoordText:SetTextColor(unpack(_A.dimText))
|
|
mouseCoordText:SetText("")
|
|
end
|
|
|
|
local function UpdateTitle()
|
|
if not titleText then return end
|
|
local zoneName = GetZoneText and GetZoneText() or ""
|
|
local subZone = GetSubZoneText and GetSubZoneText() or ""
|
|
local display = zoneName
|
|
if subZone ~= "" and subZone ~= zoneName then
|
|
display = zoneName .. " - " .. subZone
|
|
end
|
|
if display == "" then display = "世界地图" end
|
|
titleText:SetText(display)
|
|
end
|
|
|
|
local function UpdateCoords()
|
|
if not coordText then return end
|
|
local x, y = GetPlayerMapPosition("player")
|
|
if x and y and (x > 0 or y > 0) then
|
|
coordText:SetText(string.format("玩家: %.1f, %.1f", x * 100, y * 100))
|
|
else
|
|
coordText:SetText("")
|
|
end
|
|
end
|
|
|
|
local function UpdateMouseCoords()
|
|
if not mouseCoordText or not WorldMapButton then return end
|
|
if not WorldMapFrame or not WorldMapFrame:IsVisible() then
|
|
mouseCoordText:SetText("")
|
|
return
|
|
end
|
|
|
|
local cx, cy = GetCursorPosition()
|
|
if not cx or not cy then
|
|
mouseCoordText:SetText("")
|
|
return
|
|
end
|
|
|
|
local scale = WorldMapButton:GetEffectiveScale()
|
|
local left = WorldMapButton:GetLeft()
|
|
local top = WorldMapButton:GetTop()
|
|
local w = WorldMapButton:GetWidth()
|
|
local h = WorldMapButton:GetHeight()
|
|
|
|
if not left or not top or not w or not h or w == 0 or h == 0 then
|
|
mouseCoordText:SetText("")
|
|
return
|
|
end
|
|
|
|
local mx = (cx / scale - left) / w
|
|
local my = (top - cy / scale) / h
|
|
|
|
if mx >= 0 and mx <= 1 and my >= 0 and my <= 1 then
|
|
mouseCoordText:SetText(string.format("鼠标: %.1f, %.1f", mx * 100, my * 100))
|
|
else
|
|
mouseCoordText:SetText("")
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 3. Tooltip theming
|
|
-- SetBackdrop on scaled child frames is unreliable in WoW 1.12.
|
|
-- Approach: use a SEPARATE frame parented to UIParent (outside the
|
|
-- WorldMapFrame scale chain) positioned exactly behind whichever tooltip
|
|
-- is currently visible. This frame has its own backdrop at TOOLTIP strata.
|
|
-- Also hook WorldMapTooltip:Show to try SetBackdrop as secondary attempt.
|
|
--------------------------------------------------------------------------------
|
|
local ttBG
|
|
|
|
local TT_PAD = 4
|
|
|
|
local function CreateTooltipBG()
|
|
if ttBG then return end
|
|
ttBG = CreateFrame("Frame", "NanamiMapTTBG", UIParent)
|
|
ttBG:SetFrameStrata("FULLSCREEN_DIALOG")
|
|
ttBG:SetFrameLevel(255)
|
|
ttBG:SetBackdrop({
|
|
bgFile = "Interface\\Tooltips\\UI-Tooltip-Background",
|
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
|
tile = true, tileSize = 16, edgeSize = 16,
|
|
insets = { left = 4, right = 4, top = 4, bottom = 4 },
|
|
})
|
|
ttBG:SetBackdropColor(unpack(_A.panelBg))
|
|
ttBG:SetBackdropBorderColor(unpack(_A.panelBorder))
|
|
ttBG:Hide()
|
|
end
|
|
|
|
local function PositionTTBG(tip)
|
|
if not ttBG then return end
|
|
if not tip or not tip:IsVisible() then
|
|
ttBG:Hide()
|
|
return
|
|
end
|
|
|
|
local l = tip:GetLeft()
|
|
local r = tip:GetRight()
|
|
local t = tip:GetTop()
|
|
local b = tip:GetBottom()
|
|
if not l or not r or not t or not b then
|
|
ttBG:Hide()
|
|
return
|
|
end
|
|
|
|
local tipScale = tip:GetEffectiveScale()
|
|
local bgScale = ttBG:GetEffectiveScale()
|
|
local s = tipScale / bgScale
|
|
|
|
ttBG:ClearAllPoints()
|
|
ttBG:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", l * s - TT_PAD, t * s + TT_PAD)
|
|
ttBG:SetPoint("BOTTOMRIGHT", UIParent, "BOTTOMLEFT", r * s + TT_PAD, b * s - TT_PAD)
|
|
ttBG:Show()
|
|
end
|
|
|
|
local function HideTTBG()
|
|
if ttBG then ttBG:Hide() end
|
|
end
|
|
|
|
local function RaiseTooltipStrata(tip)
|
|
if not tip then return end
|
|
tip:SetFrameStrata("TOOLTIP")
|
|
end
|
|
|
|
local function HookTooltipShow(tip)
|
|
if not tip or tip._nanamiShowHooked then return end
|
|
tip._nanamiShowHooked = true
|
|
local origShow = tip.Show
|
|
tip.Show = function(self)
|
|
origShow(self)
|
|
if WorldMapFrame and WorldMapFrame:IsVisible() then
|
|
self:SetFrameStrata("TOOLTIP")
|
|
self:SetBackdrop(TOOLTIP_BACKDROP)
|
|
self:SetBackdropColor(unpack(_A.panelBg))
|
|
self:SetBackdropBorderColor(unpack(_A.panelBorder))
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 4. Per-frame updater
|
|
--------------------------------------------------------------------------------
|
|
local elapsed = 0
|
|
local function OnFrameUpdate()
|
|
if not WorldMapFrame or not WorldMapFrame:IsShown() then
|
|
HideTTBG()
|
|
return
|
|
end
|
|
|
|
local activeTip = nil
|
|
if WorldMapTooltip and WorldMapTooltip:IsVisible() then
|
|
activeTip = WorldMapTooltip
|
|
elseif GameTooltip and GameTooltip:IsVisible() then
|
|
activeTip = GameTooltip
|
|
end
|
|
|
|
if activeTip then
|
|
PositionTTBG(activeTip)
|
|
RaiseTooltipStrata(activeTip)
|
|
activeTip:SetBackdrop(TOOLTIP_BACKDROP)
|
|
activeTip:SetBackdropColor(unpack(_A.panelBg))
|
|
activeTip:SetBackdropBorderColor(unpack(_A.panelBorder))
|
|
else
|
|
HideTTBG()
|
|
end
|
|
|
|
UpdateMouseCoords()
|
|
|
|
elapsed = elapsed + (arg1 or 0)
|
|
if elapsed > 0.1 then
|
|
elapsed = 0
|
|
UpdateTitle()
|
|
UpdateCoords()
|
|
HideBlizzardDecorations()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 5. Layout
|
|
--------------------------------------------------------------------------------
|
|
local function ApplyLayout(cfg)
|
|
local s = cfg.scale or 0.85
|
|
WorldMapFrame:SetMovable(true)
|
|
WorldMapFrame:EnableMouse(true)
|
|
WorldMapFrame:SetScale(s)
|
|
WorldMapFrame:ClearAllPoints()
|
|
WorldMapFrame:SetPoint("CENTER", UIParent, "CENTER", 0, 30)
|
|
WorldMapFrame:SetWidth(WorldMapButton:GetWidth() + 20)
|
|
WorldMapFrame:SetHeight(WorldMapButton:GetHeight() + 60)
|
|
if BlackoutWorld then
|
|
BlackoutWorld:Hide()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 6. Window mode setup
|
|
--------------------------------------------------------------------------------
|
|
function WM:SetupWindowMode()
|
|
if Cartographer or METAMAP_TITLE then return end
|
|
|
|
local cfg = self:GetConfig()
|
|
|
|
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)
|
|
local c = WM:GetConfig()
|
|
WorldMapFrame:SetScale(c.scale or 0.85)
|
|
WorldMapFrame:SetAlpha(1)
|
|
WorldMapFrame:SetFrameStrata("FULLSCREEN_DIALOG")
|
|
if BlackoutWorld then BlackoutWorld:Hide() end
|
|
HideBlizzardDecorations()
|
|
end)
|
|
|
|
HookScript(WorldMapFrame, "OnHide", function()
|
|
HideTTBG()
|
|
end)
|
|
|
|
HookScript(WorldMapFrame, "OnMouseWheel", function()
|
|
if IsShiftKeyDown() then
|
|
local a = WorldMapFrame:GetAlpha() + arg1 / 10
|
|
if a < 0.2 then a = 0.2 end
|
|
if a > 1 then a = 1 end
|
|
WorldMapFrame:SetAlpha(a)
|
|
elseif IsControlKeyDown() then
|
|
local oldScale = WorldMapFrame:GetScale()
|
|
local newScale = oldScale + arg1 / 10
|
|
if newScale < 0.4 then newScale = 0.4 end
|
|
if newScale > 1.5 then newScale = 1.5 end
|
|
|
|
local eff = WorldMapFrame:GetEffectiveScale()
|
|
local fw = WorldMapFrame:GetWidth()
|
|
local fh = WorldMapFrame:GetHeight()
|
|
local fl = WorldMapFrame:GetLeft()
|
|
local ft = WorldMapFrame:GetTop()
|
|
|
|
if fl and ft and fw and fh and eff and eff > 0 then
|
|
local scrCX = (fl + fw / 2) * eff
|
|
local scrCY = (ft - fh / 2) * eff
|
|
WorldMapFrame:SetScale(newScale)
|
|
local newEff = newScale * eff / oldScale
|
|
WorldMapFrame:ClearAllPoints()
|
|
WorldMapFrame:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT",
|
|
scrCX / newEff - fw / 2,
|
|
scrCY / newEff + fh / 2)
|
|
else
|
|
WorldMapFrame:SetScale(newScale)
|
|
end
|
|
|
|
local c = WM:GetConfig()
|
|
c.scale = newScale
|
|
end
|
|
end)
|
|
|
|
HookScript(WorldMapFrame, "OnMouseDown", function()
|
|
WorldMapFrame:StartMoving()
|
|
end)
|
|
|
|
HookScript(WorldMapFrame, "OnMouseUp", function()
|
|
WorldMapFrame:StopMovingOrSizing()
|
|
end)
|
|
|
|
ApplyLayout(cfg)
|
|
|
|
local _G2 = getfenv(0)
|
|
local origMaximize = _G2.WorldMapFrame_Maximize
|
|
local origMinimize = _G2.WorldMapFrame_Minimize
|
|
_G2.WorldMapFrame_Maximize = function()
|
|
if origMaximize then origMaximize() end
|
|
ApplyLayout(WM:GetConfig())
|
|
HideBlizzardDecorations()
|
|
end
|
|
_G2.WorldMapFrame_Minimize = function()
|
|
if origMinimize then origMinimize() end
|
|
ApplyLayout(WM:GetConfig())
|
|
HideBlizzardDecorations()
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 7. Skin World Map Native Controls (Dropdowns, Buttons)
|
|
--------------------------------------------------------------------------------
|
|
|
|
local function SkinDropDown(dropdownName)
|
|
local _G = getfenv(0)
|
|
local dd = _G[dropdownName]
|
|
if not dd or dd._nanamiSkinned then return end
|
|
dd._nanamiSkinned = true
|
|
|
|
for _, suffix in ipairs({ "Left", "Middle", "Right" }) do
|
|
local tex = _G[dropdownName .. suffix]
|
|
if tex then tex:SetAlpha(0) end
|
|
end
|
|
|
|
local bg = CreateFrame("Frame", nil, dd)
|
|
bg:SetPoint("TOPLEFT", dd, "TOPLEFT", 16, -2)
|
|
bg:SetPoint("BOTTOMRIGHT", dd, "BOTTOMRIGHT", -16, 6)
|
|
bg:SetFrameLevel(dd:GetFrameLevel())
|
|
bg:SetBackdrop(PANEL_BACKDROP)
|
|
bg:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
bg:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
dd._nanamiBG = bg
|
|
|
|
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
|
|
|
local text = _G[dropdownName .. "Text"]
|
|
if text then
|
|
text:SetFont(font, 11, "OUTLINE")
|
|
text:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
|
|
end
|
|
|
|
local btn = _G[dropdownName .. "Button"]
|
|
if btn then
|
|
local nt = btn:GetNormalTexture()
|
|
if nt then nt:SetVertexColor(_A.title[1], _A.title[2], _A.title[3]) end
|
|
local pt = btn:GetPushedTexture()
|
|
if pt then pt:SetVertexColor(_A.title[1], _A.title[2], _A.title[3]) end
|
|
local dt = btn:GetDisabledTexture()
|
|
if dt then dt:SetVertexColor(_A.dimText[1], _A.dimText[2], _A.dimText[3]) end
|
|
local ht = btn:GetHighlightTexture()
|
|
if ht then ht:SetVertexColor(_A.title[1], _A.title[2], _A.title[3], 0.3) end
|
|
end
|
|
end
|
|
|
|
local function SkinDropDownLists()
|
|
local _G = getfenv(0)
|
|
for i = 1, 3 do
|
|
local listName = "DropDownList" .. i
|
|
local backdrop = _G[listName .. "Backdrop"]
|
|
if backdrop then
|
|
backdrop:SetBackdrop(PANEL_BACKDROP)
|
|
backdrop:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], _A.panelBg[4])
|
|
backdrop:SetBackdropBorderColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], _A.panelBorder[4])
|
|
end
|
|
local menuBackdrop = _G[listName .. "MenuBackdrop"]
|
|
if menuBackdrop then
|
|
menuBackdrop:SetBackdrop(PANEL_BACKDROP)
|
|
menuBackdrop:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], _A.panelBg[4])
|
|
menuBackdrop:SetBackdropBorderColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], _A.panelBorder[4])
|
|
end
|
|
for j = 1, 30 do
|
|
local hlTex = _G[listName .. "Button" .. j .. "Highlight"]
|
|
if hlTex and hlTex.SetVertexColor then
|
|
hlTex:SetVertexColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], 0.35)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
local function SkinNativeButton(btnName, labelOverride)
|
|
local _G = getfenv(0)
|
|
local btn = _G[btnName]
|
|
if not btn or btn._nanamiSkinned then return end
|
|
btn._nanamiSkinned = true
|
|
|
|
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
|
|
|
local regions = { btn:GetRegions() }
|
|
for _, r in ipairs(regions) do
|
|
if r and r.GetObjectType 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
|
|
|
|
local nt = btn.GetNormalTexture and btn:GetNormalTexture()
|
|
if nt then nt:SetAlpha(0) end
|
|
local pt = btn.GetPushedTexture and btn:GetPushedTexture()
|
|
if pt then pt:SetAlpha(0) end
|
|
local ht = btn.GetHighlightTexture and btn:GetHighlightTexture()
|
|
if ht then ht:SetAlpha(0) end
|
|
|
|
btn:SetBackdrop(PANEL_BACKDROP)
|
|
btn:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
btn:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
|
|
if labelOverride then
|
|
local fs = btn:GetFontString() or btn:CreateFontString(nil, "OVERLAY")
|
|
fs:SetFont(font, 11, "OUTLINE")
|
|
fs:SetText(labelOverride)
|
|
fs:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
|
|
fs:SetPoint("CENTER", 0, 0)
|
|
elseif btn.GetFontString and btn:GetFontString() then
|
|
local fs = btn:GetFontString()
|
|
fs:SetFont(font, 11, "OUTLINE")
|
|
fs:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
|
|
end
|
|
|
|
local prevEnter = btn:GetScript("OnEnter")
|
|
local prevLeave = btn:GetScript("OnLeave")
|
|
btn:SetScript("OnEnter", function()
|
|
if prevEnter then prevEnter() end
|
|
this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
|
|
end)
|
|
btn:SetScript("OnLeave", function()
|
|
if prevLeave then prevLeave() end
|
|
this:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
this:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
end)
|
|
end
|
|
|
|
local ddListHooked = false
|
|
local function SkinWorldMapControls()
|
|
SkinDropDown("WorldMapContinentDropDown")
|
|
SkinDropDown("WorldMapZoneDropDown")
|
|
SkinDropDown("WorldMapZoneMinimapDropDown")
|
|
|
|
SkinNativeButton("WorldMapZoomOutButton", "缩小")
|
|
|
|
SkinDropDownLists()
|
|
|
|
if not ddListHooked then
|
|
ddListHooked = true
|
|
local _G = getfenv(0)
|
|
for i = 1, 3 do
|
|
local list = _G["DropDownList" .. i]
|
|
if list then
|
|
local oldShow = list.Show
|
|
if oldShow then
|
|
list.Show = function(self)
|
|
oldShow(self)
|
|
SkinDropDownLists()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 8. Waypoint / Map Pin System
|
|
--------------------------------------------------------------------------------
|
|
local waypoint = { active = false, continent = 0, zone = 0, x = 0, y = 0, name = "" }
|
|
local pinFrame, pinTexture, pinLabel, pinShareBtn, pinClearBtn
|
|
|
|
local function GetMapCursorPos()
|
|
if not WorldMapButton then return nil, nil end
|
|
local cx, cy = GetCursorPosition()
|
|
if not cx or not cy then return nil, nil end
|
|
local scale = WorldMapButton:GetEffectiveScale()
|
|
local left = WorldMapButton:GetLeft()
|
|
local top = WorldMapButton:GetTop()
|
|
local w = WorldMapButton:GetWidth()
|
|
local h = WorldMapButton:GetHeight()
|
|
if not left or not top or not w or not h or w == 0 or h == 0 then return nil, nil end
|
|
local mx = (cx / scale - left) / w
|
|
local my = (top - cy / scale) / h
|
|
if mx >= 0 and mx <= 1 and my >= 0 and my <= 1 then
|
|
return mx, my
|
|
end
|
|
return nil, nil
|
|
end
|
|
|
|
local function GetCurrentMapName()
|
|
local _G = getfenv(0)
|
|
local zoneText = _G["WorldMapFrameAreaLabel"]
|
|
if zoneText and zoneText.GetText then
|
|
local t = zoneText:GetText()
|
|
if t and t ~= "" then return t end
|
|
end
|
|
local c = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local z = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
if z > 0 and GetMapZones then
|
|
local zones = { GetMapZones(c) }
|
|
if zones[z] then return zones[z] end
|
|
end
|
|
if c > 0 and GetMapContinents then
|
|
local continents = { GetMapContinents() }
|
|
if continents[c] then return continents[c] end
|
|
end
|
|
return "未知区域"
|
|
end
|
|
|
|
local function CreateWaypointPin()
|
|
if pinFrame then return end
|
|
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
|
|
|
pinFrame = CreateFrame("Frame", "NanamiWorldMapPin", WorldMapButton)
|
|
pinFrame:SetWidth(24)
|
|
pinFrame:SetHeight(38)
|
|
pinFrame:SetFrameStrata("FULLSCREEN_DIALOG")
|
|
pinFrame:SetFrameLevel(200)
|
|
|
|
local stem = pinFrame:CreateTexture(nil, "ARTWORK")
|
|
stem:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
stem:SetWidth(2)
|
|
stem:SetHeight(14)
|
|
stem:SetPoint("BOTTOM", pinFrame, "BOTTOM", 0, 0)
|
|
stem:SetVertexColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], 0.85)
|
|
|
|
local glow = pinFrame:CreateTexture(nil, "BACKGROUND")
|
|
glow:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
glow:SetWidth(20)
|
|
glow:SetHeight(20)
|
|
glow:SetPoint("BOTTOM", stem, "TOP", 0, -6)
|
|
glow:SetVertexColor(_A.accent[1], _A.accent[2], _A.accent[3], 0.30)
|
|
|
|
pinTexture = pinFrame:CreateTexture(nil, "ARTWORK")
|
|
pinTexture:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
pinTexture:SetWidth(12)
|
|
pinTexture:SetHeight(12)
|
|
pinTexture:SetPoint("BOTTOM", stem, "TOP", 0, -1)
|
|
pinTexture:SetVertexColor(_A.accent[1], _A.accent[2], _A.accent[3], 1)
|
|
|
|
local dot = pinFrame:CreateTexture(nil, "OVERLAY")
|
|
dot:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
dot:SetWidth(4)
|
|
dot:SetHeight(4)
|
|
dot:SetPoint("CENTER", pinTexture, "CENTER", 0, 0)
|
|
dot:SetVertexColor(_A.title[1], _A.title[2], _A.title[3], 1)
|
|
|
|
pinLabel = pinFrame:CreateFontString(nil, "OVERLAY")
|
|
pinLabel:SetFont(font, 11, "OUTLINE")
|
|
pinLabel:SetPoint("TOP", pinFrame, "BOTTOM", 0, -2)
|
|
pinLabel:SetTextColor(_A.title[1], _A.title[2], _A.title[3])
|
|
pinLabel:SetText("")
|
|
|
|
local btnW, btnH = 52, 18
|
|
|
|
pinShareBtn = CreateFrame("Button", nil, pinFrame)
|
|
pinShareBtn:SetWidth(btnW)
|
|
pinShareBtn:SetHeight(btnH)
|
|
pinShareBtn:SetPoint("TOPLEFT", pinFrame, "BOTTOMLEFT", -10, -8)
|
|
pinShareBtn:SetBackdrop(PANEL_BACKDROP)
|
|
pinShareBtn:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
pinShareBtn:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
local shareFS = pinShareBtn:CreateFontString(nil, "OVERLAY")
|
|
shareFS:SetFont(font, 10, "OUTLINE")
|
|
shareFS:SetPoint("CENTER", 0, 0)
|
|
shareFS:SetText("分享")
|
|
shareFS:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
|
|
pinShareBtn:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
|
|
end)
|
|
pinShareBtn:SetScript("OnLeave", function()
|
|
this:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
end)
|
|
pinShareBtn:SetScript("OnClick", function()
|
|
WM:ShareWaypoint()
|
|
end)
|
|
|
|
pinClearBtn = CreateFrame("Button", nil, pinFrame)
|
|
pinClearBtn:SetWidth(btnW)
|
|
pinClearBtn:SetHeight(btnH)
|
|
pinClearBtn:SetPoint("LEFT", pinShareBtn, "RIGHT", 4, 0)
|
|
pinClearBtn:SetBackdrop(PANEL_BACKDROP)
|
|
pinClearBtn:SetBackdropColor(_A.buttonDownBg[1], _A.buttonDownBg[2], _A.buttonDownBg[3], _A.buttonDownBg[4])
|
|
pinClearBtn:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
local clearFS = pinClearBtn:CreateFontString(nil, "OVERLAY")
|
|
clearFS:SetFont(font, 10, "OUTLINE")
|
|
clearFS:SetPoint("CENTER", 0, 0)
|
|
clearFS:SetText("清除")
|
|
clearFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
|
|
pinClearBtn:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
|
|
end)
|
|
pinClearBtn:SetScript("OnLeave", function()
|
|
this:SetBackdropColor(_A.buttonDownBg[1], _A.buttonDownBg[2], _A.buttonDownBg[3], _A.buttonDownBg[4])
|
|
end)
|
|
pinClearBtn:SetScript("OnClick", function()
|
|
WM:ClearWaypoint()
|
|
end)
|
|
|
|
pinFrame:Hide()
|
|
end
|
|
|
|
local function UpdatePinPosition()
|
|
if not pinFrame or not waypoint.active then
|
|
if pinFrame then pinFrame:Hide() end
|
|
return
|
|
end
|
|
if not WorldMapButton or not WorldMapFrame:IsVisible() then
|
|
pinFrame:Hide()
|
|
return
|
|
end
|
|
local c = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local z = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
if c ~= waypoint.continent or z ~= waypoint.zone then
|
|
pinFrame:Hide()
|
|
return
|
|
end
|
|
local w = WorldMapButton:GetWidth()
|
|
local h = WorldMapButton:GetHeight()
|
|
if not w or not h or w == 0 or h == 0 then
|
|
pinFrame:Hide()
|
|
return
|
|
end
|
|
pinFrame:ClearAllPoints()
|
|
pinFrame:SetPoint("BOTTOM", WorldMapButton, "TOPLEFT", w * waypoint.x, -h * waypoint.y)
|
|
pinFrame:Show()
|
|
end
|
|
|
|
function WM:SetWaypoint(continent, zone, x, y, name)
|
|
waypoint.active = true
|
|
waypoint.continent = continent
|
|
waypoint.zone = zone
|
|
waypoint.x = x
|
|
waypoint.y = y
|
|
waypoint.name = name or "标记点"
|
|
|
|
CreateWaypointPin()
|
|
local xPct = string.format("%.1f", x * 100)
|
|
local yPct = string.format("%.1f", y * 100)
|
|
pinLabel:SetText(waypoint.name .. " (" .. xPct .. ", " .. yPct .. ")")
|
|
UpdatePinPosition()
|
|
|
|
if SFrames and SFrames.Print then
|
|
SFrames:Print("地图标记已放置: " .. waypoint.name .. " (" .. xPct .. ", " .. yPct .. ")")
|
|
end
|
|
end
|
|
|
|
function WM:ClearWaypoint()
|
|
waypoint.active = false
|
|
waypoint.continent = 0
|
|
waypoint.zone = 0
|
|
waypoint.x = 0
|
|
waypoint.y = 0
|
|
waypoint.name = ""
|
|
if pinFrame then pinFrame:Hide() end
|
|
end
|
|
|
|
function WM:ShareWaypoint()
|
|
if not waypoint.active then return end
|
|
local xPct = string.format("%.1f", waypoint.x * 100)
|
|
local yPct = string.format("%.1f", waypoint.y * 100)
|
|
local shareText = "<npin:" .. waypoint.continent .. ":" .. waypoint.zone .. ":" .. xPct .. ":" .. yPct .. ":" .. waypoint.name .. ">"
|
|
if ChatFrameEditBox then
|
|
if not ChatFrameEditBox:IsVisible() then
|
|
ChatFrameEditBox:Show()
|
|
end
|
|
ChatFrameEditBox:SetFocus()
|
|
ChatFrameEditBox:SetText(shareText .. " " .. waypoint.name .. " (" .. xPct .. ", " .. yPct .. ")")
|
|
end
|
|
end
|
|
|
|
function WM:HandleWaypointLink(data)
|
|
local _, _, cs, zs, xs, ys = string.find(data, "^(%d+):(%d+):([%d%.]+):([%d%.]+)")
|
|
if not cs then return end
|
|
local c = tonumber(cs) or 0
|
|
local z = tonumber(zs) or 0
|
|
local x = (tonumber(xs) or 0) / 100
|
|
local y = (tonumber(ys) or 0) / 100
|
|
|
|
local name = "标记点"
|
|
if z > 0 and GetMapZones then
|
|
local zones = { GetMapZones(c) }
|
|
if zones[z] then name = zones[z] end
|
|
elseif c > 0 and GetMapContinents then
|
|
local continents = { GetMapContinents() }
|
|
if continents[c] then name = continents[c] end
|
|
end
|
|
|
|
if not WorldMapFrame:IsVisible() then
|
|
WorldMapFrame:Show()
|
|
end
|
|
|
|
self:SetWaypoint(c, z, x, y, name)
|
|
|
|
local pending = { continent = c, zone = z, x = x, y = y, name = name }
|
|
local timer = CreateFrame("Frame")
|
|
local waited = 0
|
|
timer:SetScript("OnUpdate", function()
|
|
waited = waited + (arg1 or 0)
|
|
if waited >= 0.05 then
|
|
timer:SetScript("OnUpdate", nil)
|
|
if SetMapZoom then
|
|
SetMapZoom(pending.continent, pending.zone)
|
|
end
|
|
WM:SetWaypoint(pending.continent, pending.zone, pending.x, pending.y, pending.name)
|
|
end
|
|
end)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 8b. Darkmoon Faire Map Markers (暗月马戏团世界地图标记)
|
|
--------------------------------------------------------------------------------
|
|
local DMF_ICON = "Interface\\Icons\\INV_Misc_Orb_02"
|
|
local DMF_SIZE = 24
|
|
local DMF_SIZE_CONT = 16
|
|
local DMF_LOCATIONS = {
|
|
{ zone = "ElwynnForest", cont = 2,
|
|
zx = 0.41, zy = 0.69, -- zone-level coordinates
|
|
cx = 0.453, cy = 0.721, -- continent-level coordinates (Eastern Kingdoms)
|
|
name = "闪金镇", fullName = "闪金镇 (艾尔文森林)",
|
|
},
|
|
{ zone = "Mulgore", cont = 1,
|
|
zx = 0.36, zy = 0.38, -- zone-level coordinates
|
|
cx = 0.463, cy = 0.595, -- continent-level coordinates (Kalimdor)
|
|
name = "莫高雷", fullName = "莫高雷",
|
|
},
|
|
}
|
|
local DMF_REF_LOC = 1 -- ref Monday (2026-03-09) week → Goldshire
|
|
local dmfPins = {}
|
|
local dmfPulse = 0
|
|
local dmfRefJDN = nil
|
|
|
|
local function DiscoverDmfZoneIndices()
|
|
local savedC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local savedZ = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
for i = 1, 2 do
|
|
local loc = DMF_LOCATIONS[i]
|
|
if not loc.zoneIdx then
|
|
local target = string.lower(loc.zone)
|
|
local zones = { GetMapZones(loc.cont) }
|
|
for idx = 1, table.getn(zones) do
|
|
SetMapZoom(loc.cont, idx)
|
|
local mf = GetMapInfo and GetMapInfo() or ""
|
|
if mf ~= "" then
|
|
if mf == loc.zone then
|
|
loc.zoneIdx = idx
|
|
break
|
|
elseif string.find(string.lower(mf), target) then
|
|
loc.zoneIdx = idx
|
|
loc.zone = mf
|
|
break
|
|
elseif string.find(target, string.lower(mf)) then
|
|
loc.zoneIdx = idx
|
|
loc.zone = mf
|
|
break
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
if savedZ > 0 then
|
|
SetMapZoom(savedC, savedZ)
|
|
elseif savedC > 0 then
|
|
SetMapZoom(savedC, 0)
|
|
else
|
|
if SetMapToCurrentZone then SetMapToCurrentZone() end
|
|
end
|
|
end
|
|
|
|
local function DmfJDN(y, m, d)
|
|
local a = math.floor((14 - m) / 12)
|
|
local y2 = y + 4800 - a
|
|
local m2 = m + 12 * a - 3
|
|
return d + math.floor((153 * m2 + 2) / 5) + 365 * y2
|
|
+ math.floor(y2 / 4) - math.floor(y2 / 100) + math.floor(y2 / 400) - 32045
|
|
end
|
|
|
|
local function GetCETJDN()
|
|
local ts = time()
|
|
local daysSinceEpoch = math.floor(ts / 86400)
|
|
local utcJDN = 2440588 + daysSinceEpoch -- Jan 1 1970 = JDN 2440588
|
|
local utcHour = math.mod(math.floor(ts / 3600), 24)
|
|
|
|
local ok, utc = pcall(date, "!*t")
|
|
local utcYear
|
|
if ok and utc and utc.year then
|
|
utcYear = utc.year
|
|
else
|
|
local lt = date("*t")
|
|
utcYear = lt.year
|
|
end
|
|
|
|
local mar31dow = math.mod(DmfJDN(utcYear, 3, 31), 7)
|
|
local lastSunMar = 31 - math.mod(mar31dow + 1, 7)
|
|
local oct31dow = math.mod(DmfJDN(utcYear, 10, 31), 7)
|
|
local lastSunOct = 31 - math.mod(oct31dow + 1, 7)
|
|
|
|
local utcMonth = math.floor((daysSinceEpoch - 10957) / 30.44) + 1
|
|
if ok and utc and utc.month then utcMonth = utc.month end
|
|
local utcDay = 0
|
|
if ok and utc then utcDay = utc.day or 0 end
|
|
|
|
local afterSpring = (utcMonth > 3)
|
|
or (utcMonth == 3 and utcDay > lastSunMar)
|
|
or (utcMonth == 3 and utcDay == lastSunMar and utcHour >= 1)
|
|
local beforeAutumn = (utcMonth < 10)
|
|
or (utcMonth == 10 and utcDay < lastSunOct)
|
|
or (utcMonth == 10 and utcDay == lastSunOct and utcHour < 1)
|
|
|
|
local offset = (afterSpring and beforeAutumn) and 2 or 1 -- CEST=2, CET=1
|
|
local cetJDN = utcJDN
|
|
if utcHour + offset >= 24 then cetJDN = cetJDN + 1 end
|
|
return cetJDN
|
|
end
|
|
|
|
local function GetDmfSchedule()
|
|
local todayJDN = GetCETJDN()
|
|
local dow = math.mod(todayJDN, 7) -- 0=Mon 1=Tue 2=Wed 3=Thu 4=Fri 5=Sat 6=Sun
|
|
local isWed = (dow == 2)
|
|
|
|
-- Week runs Sun(6)-Sat(5); ref Sunday March 8 = Goldshire
|
|
if not dmfRefJDN then dmfRefJDN = DmfJDN(2026, 3, 8) end
|
|
local daysSinceSun = math.mod(dow + 1, 7) -- Sun=0 Mon=1 Tue=2 Wed=3 Thu=4 Fri=5 Sat=6
|
|
local thisSunJDN = todayJDN - daysSinceSun
|
|
local weeksDiff = math.floor((thisSunJDN - dmfRefJDN) / 7)
|
|
if weeksDiff < 0 then weeksDiff = -weeksDiff end
|
|
local ai = math.mod(weeksDiff, 2) == 0 and DMF_REF_LOC or (3 - DMF_REF_LOC)
|
|
|
|
local daysLeft = 6 - daysSinceSun -- days until Saturday (0 on Sat itself)
|
|
local daysUntilNext = daysLeft + 1 -- days until next Sunday
|
|
|
|
return ai, isWed, daysLeft, daysUntilNext
|
|
end
|
|
|
|
local function CreateDmfPin(i)
|
|
if dmfPins[i] then return end
|
|
if not WorldMapButton then return end
|
|
local f = CreateFrame("Button", "NanamiDMFPin" .. i, WorldMapButton)
|
|
f:SetWidth(DMF_SIZE); f:SetHeight(DMF_SIZE)
|
|
f:SetFrameStrata("FULLSCREEN_DIALOG")
|
|
f:SetFrameLevel(200)
|
|
|
|
local ico = f:CreateTexture(nil, "OVERLAY")
|
|
ico:SetTexture(DMF_ICON)
|
|
ico:SetWidth(DMF_SIZE); ico:SetHeight(DMF_SIZE)
|
|
ico:SetPoint("CENTER", f, "CENTER", 0, 0)
|
|
f.icon = ico
|
|
|
|
local gl = f:CreateTexture(nil, "OVERLAY")
|
|
gl:SetTexture("Interface\\Buttons\\UI-ActionButton-Border")
|
|
gl:SetBlendMode("ADD")
|
|
gl:SetWidth(DMF_SIZE * 2.2); gl:SetHeight(DMF_SIZE * 2.2)
|
|
gl:SetPoint("CENTER", f, "CENTER", 0, 0)
|
|
local _glc = SFrames.ActiveTheme and SFrames.ActiveTheme.accentLight or { 1, 0.84, 0 }
|
|
gl:SetVertexColor(_glc[1], _glc[2], _glc[3], 0.35)
|
|
f.glow = gl
|
|
|
|
f.locIdx = i
|
|
f:SetScript("OnEnter", function()
|
|
local loc = DMF_LOCATIONS[this.locIdx]
|
|
local other = DMF_LOCATIONS[3 - this.locIdx]
|
|
local ai, iw, dl, ds = GetDmfSchedule()
|
|
local here = (ai == this.locIdx)
|
|
GameTooltip:SetOwner(this, "ANCHOR_RIGHT")
|
|
GameTooltip:AddLine("暗月马戏团", 1, 0.84, 0)
|
|
GameTooltip:AddLine(loc.fullName, 0.7, 0.7, 0.7)
|
|
GameTooltip:AddLine(" ")
|
|
if here then
|
|
if iw then
|
|
GameTooltip:AddLine("今天是马戏团休息日 (周三)", 1, 0.3, 0.3)
|
|
GameTooltip:AddLine("马戏团在此处,明天恢复营业", 0.4, 1, 0.4)
|
|
GameTooltip:AddLine(" ")
|
|
GameTooltip:AddLine("本轮还剩 " .. dl .. " 天 (周六结束)", 0.8, 0.8, 0.8)
|
|
else
|
|
GameTooltip:AddLine("马戏团正在此处营业中!", 0.4, 1, 0.4)
|
|
GameTooltip:AddLine(" ")
|
|
if dl > 1 then
|
|
GameTooltip:AddLine("剩余 " .. dl .. " 天 (周六结束)", 0.8, 0.8, 0.8)
|
|
elseif dl == 1 then
|
|
GameTooltip:AddLine("明天是本轮最后一天", 1, 0.84, 0)
|
|
else
|
|
GameTooltip:AddLine("今天是本轮最后一天!", 1, 0.84, 0)
|
|
GameTooltip:AddLine("之后将转移至 " .. other.fullName, 0.6, 0.8, 1)
|
|
end
|
|
end
|
|
else
|
|
GameTooltip:AddLine("马戏团目前不在此处", 0.6, 0.6, 0.6)
|
|
GameTooltip:AddLine(" ")
|
|
GameTooltip:AddLine("当前位置: " .. other.fullName, 0.6, 0.8, 1)
|
|
if iw then
|
|
GameTooltip:AddLine("今天对方休息," .. ds .. " 天后来此", 1, 0.84, 0)
|
|
else
|
|
GameTooltip:AddLine(ds .. " 天后到来", 1, 0.84, 0)
|
|
end
|
|
end
|
|
GameTooltip:AddLine(" ")
|
|
GameTooltip:AddLine("点击打开Buff指引", 0.5, 0.5, 0.5)
|
|
GameTooltip:Show()
|
|
end)
|
|
f:SetScript("OnLeave", function() GameTooltip:Hide() end)
|
|
f:SetScript("OnClick", function()
|
|
if SFrames.DarkmoonGuide and SFrames.DarkmoonGuide.Toggle then
|
|
SFrames.DarkmoonGuide:Toggle()
|
|
end
|
|
end)
|
|
f:RegisterForClicks("LeftButtonUp")
|
|
f:Hide()
|
|
dmfPins[i] = f
|
|
end
|
|
|
|
local function UpdateDarkmoonPins()
|
|
if not WorldMapButton or not WorldMapFrame:IsVisible() then
|
|
for i = 1, 2 do if dmfPins[i] then dmfPins[i]:Hide() end end
|
|
return
|
|
end
|
|
|
|
local mapFile = GetMapInfo and GetMapInfo() or ""
|
|
local curC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local curZ = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
local ai, iw = GetDmfSchedule()
|
|
local w = WorldMapButton:GetWidth()
|
|
local h = WorldMapButton:GetHeight()
|
|
if not w or not h or w == 0 or h == 0 then return end
|
|
|
|
for i = 1, 2 do
|
|
CreateDmfPin(i)
|
|
local pin = dmfPins[i]
|
|
local loc = DMF_LOCATIONS[i]
|
|
local px, py
|
|
local show = false
|
|
local isContinent = false
|
|
|
|
if curZ > 0 and curC == loc.cont and loc.zoneIdx and curZ == loc.zoneIdx then
|
|
show = true
|
|
px, py = loc.zx, loc.zy
|
|
elseif mapFile ~= "" and mapFile == loc.zone then
|
|
show = true
|
|
px, py = loc.zx, loc.zy
|
|
elseif curZ == 0 and curC == loc.cont then
|
|
show = true
|
|
isContinent = true
|
|
px, py = loc.cx, loc.cy
|
|
end
|
|
|
|
if show then
|
|
local sz = isContinent and DMF_SIZE_CONT or DMF_SIZE
|
|
pin:SetWidth(sz); pin:SetHeight(sz)
|
|
pin.icon:SetWidth(sz); pin.icon:SetHeight(sz)
|
|
pin.glow:SetWidth(sz * 2.2); pin.glow:SetHeight(sz * 2.2)
|
|
pin:ClearAllPoints()
|
|
pin:SetPoint("CENTER", WorldMapButton, "TOPLEFT", w * px, -h * py)
|
|
if ai == i then
|
|
if iw then
|
|
pin.icon:SetVertexColor(0.75, 0.75, 0.75)
|
|
pin:SetAlpha(0.85)
|
|
pin.glow:Hide()
|
|
else
|
|
pin.icon:SetVertexColor(1, 1, 1)
|
|
pin:SetAlpha(1)
|
|
pin.glow:Show()
|
|
end
|
|
else
|
|
pin.icon:SetVertexColor(0.45, 0.45, 0.45)
|
|
pin:SetAlpha(0.7)
|
|
pin.glow:Hide()
|
|
end
|
|
pin:Show()
|
|
else
|
|
pin:Hide()
|
|
end
|
|
end
|
|
|
|
dmfPulse = dmfPulse + 0.03
|
|
if dmfPulse > 6.2832 then dmfPulse = dmfPulse - 6.2832 end
|
|
for i = 1, 2 do
|
|
local p = dmfPins[i]
|
|
if p and p:IsShown() and p.glow and p.glow:IsShown() then
|
|
p.glow:SetAlpha(0.25 + 0.20 * math.sin(dmfPulse * 2))
|
|
end
|
|
end
|
|
end
|
|
|
|
SLASH_DMFMAP1 = "/dmfmap"
|
|
SlashCmdList["DMFMAP"] = function(msg)
|
|
local cf = DEFAULT_CHAT_FRAME
|
|
if msg == "scan" then
|
|
local savedC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local savedZ = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
for c = 1, 2 do
|
|
local cname = c == 1 and "Kalimdor" or "Eastern Kingdoms"
|
|
cf:AddMessage("|cffffcc66[DMF Scan] " .. cname .. " (cont=" .. c .. "):|r")
|
|
local zones = { GetMapZones(c) }
|
|
for idx = 1, table.getn(zones) do
|
|
SetMapZoom(c, idx)
|
|
local mf = GetMapInfo and GetMapInfo() or "(nil)"
|
|
local zname = zones[idx] or "?"
|
|
if string.find(string.lower(mf), "elwynn") or string.find(string.lower(zname), "elwynn")
|
|
or string.find(string.lower(mf), "mulgore") or string.find(string.lower(zname), "mulgore") then
|
|
cf:AddMessage(" |cff00ff00>>|r idx=" .. idx .. " file=" .. tostring(mf) .. " name=" .. tostring(zname))
|
|
else
|
|
cf:AddMessage(" idx=" .. idx .. " file=" .. tostring(mf) .. " name=" .. tostring(zname))
|
|
end
|
|
end
|
|
end
|
|
if savedZ > 0 then SetMapZoom(savedC, savedZ)
|
|
elseif savedC > 0 then SetMapZoom(savedC, 0)
|
|
else if SetMapToCurrentZone then SetMapToCurrentZone() end end
|
|
return
|
|
end
|
|
local ai, iw, dl, ds = GetDmfSchedule()
|
|
local n = DMF_LOCATIONS[ai] and DMF_LOCATIONS[ai].name or "?"
|
|
local mf = GetMapInfo and GetMapInfo() or "(nil)"
|
|
local curC = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local curZ = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
local cetJDN = GetCETJDN()
|
|
local dow = math.mod(cetJDN, 7)
|
|
local dayNames = { [0]="Mon", [1]="Tue", [2]="Wed", [3]="Thu", [4]="Fri", [5]="Sat", [6]="Sun" }
|
|
local localT = date("%H:%M")
|
|
local ts = time()
|
|
local utcH = math.mod(math.floor(ts / 3600), 24)
|
|
local utcM = math.mod(math.floor(ts / 60), 60)
|
|
cf:AddMessage("|cffffcc66[暗月马戏团]|r 本周: " .. n
|
|
.. " | CET " .. (dayNames[dow] or "?") .. (iw and "(休息)" or "")
|
|
.. " | 剩余" .. dl .. "天 | " .. ds .. "天后轮换")
|
|
cf:AddMessage(" 本地=" .. localT .. " UTC=" .. string.format("%02d:%02d", utcH, utcM)
|
|
.. " | map=" .. tostring(mf)
|
|
.. " | cont=" .. curC .. " zone=" .. curZ)
|
|
for i = 1, 2 do
|
|
local p = dmfPins[i]
|
|
local loc = DMF_LOCATIONS[i]
|
|
cf:AddMessage(" " .. loc.name .. ": "
|
|
.. (p and ("shown=" .. tostring(p:IsShown())) or "未创建")
|
|
.. " | zoneIdx=" .. tostring(loc.zoneIdx or "未发现")
|
|
.. " | file=" .. loc.zone .. " cont=" .. loc.cont)
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 9. Chat Link Integration: transform <npin:...> and handle nmwp: clicks
|
|
--------------------------------------------------------------------------------
|
|
local function TransformMapLinks(text)
|
|
if not text or type(text) ~= "string" then return text end
|
|
if not string.find(text, "<npin:", 1, true) then return text end
|
|
local result = string.gsub(text, "<npin:(%d+):(%d+):([%d%.]+):([%d%.]+):([^>]*)>",
|
|
function(c, z, x, y, name)
|
|
local display = name
|
|
if not display or display == "" then display = "地图标记" end
|
|
local _wh = (SFrames.Theme and SFrames.Theme:GetAccentHex()) or "ffFFB3D9"
|
|
return "|c" .. _wh .. "|Hnmwp:" .. c .. ":" .. z .. ":" .. x .. ":" .. y .. "|h[" .. display .. " (" .. x .. ", " .. y .. ")]|h|r"
|
|
end)
|
|
return result
|
|
end
|
|
|
|
local function HookChatFrameForMapLinks(cf)
|
|
if not cf or not cf.AddMessage or cf._nanamiMapLinkHooked then return end
|
|
cf._nanamiMapLinkHooked = true
|
|
local orig = cf.AddMessage
|
|
cf.AddMessage = function(self, text, r, g, b, alpha, holdTime)
|
|
if text and type(text) == "string" then
|
|
text = TransformMapLinks(text)
|
|
end
|
|
orig(self, text, r, g, b, alpha, holdTime)
|
|
end
|
|
end
|
|
|
|
local function HookAllChatFramesForMapLinks()
|
|
local _G = getfenv(0)
|
|
for i = 1, 10 do
|
|
local cf = _G["ChatFrame" .. i]
|
|
if cf then HookChatFrameForMapLinks(cf) end
|
|
end
|
|
end
|
|
|
|
local function HookSetItemRefForWaypoints()
|
|
local _G = getfenv(0)
|
|
local origSIR = _G.SetItemRef
|
|
_G.SetItemRef = function(link, text, button)
|
|
if link and string.sub(link, 1, 5) == "nmwp:" then
|
|
local data = string.sub(link, 6)
|
|
WM:HandleWaypointLink(data)
|
|
return
|
|
end
|
|
if origSIR then
|
|
origSIR(link, text, button)
|
|
end
|
|
end
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 10. WorldMapButton Click Hook for Placing Waypoints
|
|
--------------------------------------------------------------------------------
|
|
local function HookWorldMapButtonClick()
|
|
if not WorldMapButton then return end
|
|
local prevOnClick = WorldMapButton:GetScript("OnClick")
|
|
WorldMapButton:SetScript("OnClick", function()
|
|
if arg1 == "LeftButton" and IsControlKeyDown() then
|
|
local mx, my = GetMapCursorPos()
|
|
if mx and my then
|
|
local c = GetCurrentMapContinent and GetCurrentMapContinent() or 0
|
|
local z = GetCurrentMapZone and GetCurrentMapZone() or 0
|
|
local name = GetCurrentMapName()
|
|
WM:SetWaypoint(c, z, mx, my, name)
|
|
end
|
|
return
|
|
end
|
|
if prevOnClick then prevOnClick() end
|
|
end)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 11. Navigation Map (迷你全区域地图 - 类似正式服 Shift+M)
|
|
-- Shows the entire zone map scaled down. All state in table N.
|
|
--------------------------------------------------------------------------------
|
|
local N = {
|
|
MW = 1002, MH = 668,
|
|
CW = { 256, 256, 256, 234 },
|
|
RH = { 256, 256, 156 },
|
|
DEF_W = 350, MIN_W = 200, MAX_W = 600,
|
|
curMap = "", pulse = 0,
|
|
overlays = {}, overlayCount = 0,
|
|
}
|
|
local NC = {
|
|
PBG = _A.panelBg, PBD = _A.panelBorder, TC = _A.title, DT = _A.dimText,
|
|
}
|
|
|
|
local function GetNavConfig()
|
|
local cfg = WM:GetConfig()
|
|
if type(cfg.nav) ~= "table" then
|
|
cfg.nav = { enabled = false, width = N.DEF_W, alpha = 0.70, locked = false }
|
|
end
|
|
if not cfg.nav.width then cfg.nav.width = N.DEF_W end
|
|
if cfg.nav.width < N.MIN_W then cfg.nav.width = N.MIN_W end
|
|
if cfg.nav.width > N.MAX_W then cfg.nav.width = N.MAX_W end
|
|
return cfg.nav
|
|
end
|
|
|
|
local function SaveNavPos()
|
|
if not N.frame then return end
|
|
local nc = GetNavConfig()
|
|
local pt, _, rp, x, y = N.frame:GetPoint()
|
|
nc.aF = pt; nc.aT = rp; nc.pX = x; nc.pY = y
|
|
end
|
|
|
|
local function NavNextPow2(n)
|
|
local p = 16
|
|
while p < n do p = p * 2 end
|
|
return p
|
|
end
|
|
|
|
|
|
local function NavApplySize(w)
|
|
if not N.frame or not N.tiles then return end
|
|
local h = w * N.MH / N.MW
|
|
local s = w / N.MW
|
|
N.frame:SetWidth(w); N.frame:SetHeight(h)
|
|
|
|
local idx = 0
|
|
for row = 1, 3 do
|
|
for col = 1, 4 do
|
|
idx = idx + 1
|
|
local t = N.tiles[idx]
|
|
t:SetWidth(N.CW[col] * s); t:SetHeight(N.RH[row] * s)
|
|
local xO, yO = 0, 0
|
|
for c = 1, col - 1 do xO = xO + N.CW[c] end
|
|
for r = 1, row - 1 do yO = yO + N.RH[r] end
|
|
t:ClearAllPoints()
|
|
t:SetPoint("TOPLEFT", N.frame, "TOPLEFT", xO * s, -yO * s)
|
|
end
|
|
end
|
|
|
|
for i = 1, N.overlayCount do
|
|
if N.overlays[i] and N.ovData and N.ovData[i] then
|
|
local d = N.ovData[i]
|
|
local tex = N.overlays[i]
|
|
tex:SetWidth(d.pw * s); tex:SetHeight(d.ph * s)
|
|
tex:ClearAllPoints()
|
|
tex:SetPoint("TOPLEFT", N.frame, "TOPLEFT", d.ox * s, -d.oy * s)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function UpdateNavOverlays(mapFile)
|
|
if not N.frame or not mapFile or mapFile == "" then return end
|
|
|
|
local db = MapOverlayData or LibMapOverlayData or zMapOverlayData or mapOverlayData
|
|
if not db then
|
|
for i = 1, N.overlayCount do if N.overlays[i] then N.overlays[i]:Hide() end end
|
|
return
|
|
end
|
|
|
|
local zoneData = db[mapFile]
|
|
if not zoneData then
|
|
for i = 1, N.overlayCount do if N.overlays[i] then N.overlays[i]:Hide() end end
|
|
return
|
|
end
|
|
|
|
local nc = GetNavConfig()
|
|
local s = (nc.width or N.DEF_W) / N.MW
|
|
local prefix = "Interface\\WorldMap\\" .. mapFile .. "\\"
|
|
local texIdx = 0
|
|
if not N.ovData then N.ovData = {} end
|
|
|
|
for idx = 1, table.getn(zoneData) do
|
|
local entry = zoneData[idx]
|
|
local _, _, oName, sW, sH, sX, sY = string.find(entry, "^(%u+):(%d+):(%d+):(%d+):(%d+)$")
|
|
if not oName then
|
|
_, _, oName, sW, sH, sX, sY = string.find(entry, "^([^:]+):(%d+):(%d+):(%d+):(%d+)$")
|
|
end
|
|
if oName then
|
|
local tw = tonumber(sW)
|
|
local th = tonumber(sH)
|
|
local ox = tonumber(sX)
|
|
local oy = tonumber(sY)
|
|
local tName = prefix .. oName
|
|
local numH = math.ceil(tw / 256)
|
|
local numV = math.ceil(th / 256)
|
|
|
|
for row = 1, numV do
|
|
local pxH, fileH
|
|
if row < numV then
|
|
pxH = 256; fileH = 256
|
|
else
|
|
pxH = math.mod(th, 256)
|
|
if pxH == 0 then pxH = 256 end
|
|
fileH = NavNextPow2(pxH)
|
|
end
|
|
for col = 1, numH do
|
|
texIdx = texIdx + 1
|
|
local pxW, fileW
|
|
if col < numH then
|
|
pxW = 256; fileW = 256
|
|
else
|
|
pxW = math.mod(tw, 256)
|
|
if pxW == 0 then pxW = 256 end
|
|
fileW = NavNextPow2(pxW)
|
|
end
|
|
|
|
if not N.overlays[texIdx] then
|
|
N.overlays[texIdx] = N.frame:CreateTexture(nil, "OVERLAY")
|
|
end
|
|
local tex = N.overlays[texIdx]
|
|
local realOx = ox + 256 * (col - 1)
|
|
local realOy = oy + 256 * (row - 1)
|
|
|
|
N.ovData[texIdx] = { pw = pxW, ph = pxH, ox = realOx, oy = realOy }
|
|
|
|
tex:SetWidth(pxW * s); tex:SetHeight(pxH * s)
|
|
tex:SetTexCoord(0, pxW / fileW, 0, pxH / fileH)
|
|
tex:ClearAllPoints()
|
|
tex:SetPoint("TOPLEFT", N.frame, "TOPLEFT", realOx * s, -realOy * s)
|
|
|
|
local tileIndex = ((row - 1) * numH) + col
|
|
tex:SetTexture(tName .. tileIndex)
|
|
tex:SetVertexColor(1, 1, 1, 1)
|
|
tex:Show()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = texIdx + 1, N.overlayCount do
|
|
if N.overlays[i] then N.overlays[i]:Hide() end
|
|
end
|
|
if texIdx > N.overlayCount then N.overlayCount = texIdx end
|
|
end
|
|
|
|
local function CreateNavMap()
|
|
if N.frame then return end
|
|
local nc = GetNavConfig()
|
|
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
|
local w = nc.width or N.DEF_W
|
|
local h = w * N.MH / N.MW
|
|
local s = w / N.MW
|
|
|
|
local f = CreateFrame("Frame", "NanamiNavMap", UIParent)
|
|
N.frame = f
|
|
f:SetWidth(w); f:SetHeight(h)
|
|
f:SetAlpha(nc.alpha or 0.70)
|
|
f:SetMovable(true); f:EnableMouse(true); f:EnableMouseWheel(1)
|
|
f:SetClampedToScreen(true)
|
|
f:SetFrameStrata("LOW"); f:SetFrameLevel(10)
|
|
f:Hide()
|
|
|
|
f:ClearAllPoints()
|
|
if nc.aF and nc.pX and nc.pY then
|
|
f:SetPoint(nc.aF, UIParent, nc.aT or nc.aF, nc.pX, nc.pY)
|
|
else
|
|
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
|
end
|
|
|
|
N.tiles = {}
|
|
local idx = 0
|
|
for row = 1, 3 do
|
|
for col = 1, 4 do
|
|
idx = idx + 1
|
|
local oW = N.CW[col]
|
|
local oH = N.RH[row]
|
|
local tile = f:CreateTexture("NanamiNavTile" .. idx, "ARTWORK")
|
|
tile:SetWidth(oW * s); tile:SetHeight(oH * s)
|
|
local xO, yO = 0, 0
|
|
for c = 1, col - 1 do xO = xO + N.CW[c] end
|
|
for r = 1, row - 1 do yO = yO + N.RH[r] end
|
|
tile:SetPoint("TOPLEFT", f, "TOPLEFT", xO * s, -yO * s)
|
|
local tcR = (oW >= 256) and 1 or (oW / 256)
|
|
local tcB = (oH >= 256) and 1 or (oH / 256)
|
|
tile:SetTexCoord(0, tcR, 0, tcB)
|
|
N.tiles[idx] = tile
|
|
end
|
|
end
|
|
|
|
-- thin border
|
|
local borderFrame = CreateFrame("Frame", nil, f)
|
|
borderFrame:SetAllPoints(f)
|
|
borderFrame:SetFrameLevel(f:GetFrameLevel() + 5)
|
|
borderFrame:SetBackdrop({
|
|
edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1,
|
|
insets = { left = 0, right = 0, top = 0, bottom = 0 },
|
|
})
|
|
borderFrame:SetBackdropBorderColor(0, 0, 0, 0.50)
|
|
|
|
-- gradient fade overlay (soft vignette at edges)
|
|
local fade = CreateFrame("Frame", nil, f)
|
|
fade:SetAllPoints(f)
|
|
fade:SetFrameLevel(f:GetFrameLevel() + 6)
|
|
local FD = 30
|
|
|
|
local fadeL = fade:CreateTexture(nil, "OVERLAY")
|
|
fadeL:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
fadeL:SetWidth(FD)
|
|
fadeL:SetPoint("TOPLEFT", fade, "TOPLEFT", 0, 0)
|
|
fadeL:SetPoint("BOTTOMLEFT", fade, "BOTTOMLEFT", 0, 0)
|
|
fadeL:SetGradientAlpha("HORIZONTAL", 0,0,0,0.25, 0,0,0,0)
|
|
|
|
local fadeR = fade:CreateTexture(nil, "OVERLAY")
|
|
fadeR:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
fadeR:SetWidth(FD)
|
|
fadeR:SetPoint("TOPRIGHT", fade, "TOPRIGHT", 0, 0)
|
|
fadeR:SetPoint("BOTTOMRIGHT", fade, "BOTTOMRIGHT", 0, 0)
|
|
fadeR:SetGradientAlpha("HORIZONTAL", 0,0,0,0, 0,0,0,0.25)
|
|
|
|
local fadeT = fade:CreateTexture(nil, "OVERLAY")
|
|
fadeT:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
fadeT:SetHeight(FD)
|
|
fadeT:SetPoint("TOPLEFT", fade, "TOPLEFT", 0, 0)
|
|
fadeT:SetPoint("TOPRIGHT", fade, "TOPRIGHT", 0, 0)
|
|
fadeT:SetGradientAlpha("VERTICAL", 0,0,0,0, 0,0,0,0.25)
|
|
|
|
local fadeB = fade:CreateTexture(nil, "OVERLAY")
|
|
fadeB:SetTexture("Interface\\Buttons\\WHITE8X8")
|
|
fadeB:SetHeight(FD)
|
|
fadeB:SetPoint("BOTTOMLEFT", fade, "BOTTOMLEFT", 0, 0)
|
|
fadeB:SetPoint("BOTTOMRIGHT", fade, "BOTTOMRIGHT", 0, 0)
|
|
fadeB:SetGradientAlpha("VERTICAL", 0,0,0,0.25, 0,0,0,0)
|
|
|
|
-- player indicator (arrow + ripple)
|
|
local dotFrame = CreateFrame("Frame", nil, f)
|
|
N.dotFrame = dotFrame
|
|
dotFrame:SetWidth(1); dotFrame:SetHeight(1)
|
|
dotFrame:SetPoint("TOPLEFT", f, "TOPLEFT", 0, 0)
|
|
dotFrame:SetFrameLevel(f:GetFrameLevel() + 7)
|
|
|
|
local CIRCLE = "Interface\\Minimap\\UI-Minimap-Background"
|
|
N.ripples = {}
|
|
for i = 1, 3 do
|
|
local rip = dotFrame:CreateTexture(nil, "ARTWORK")
|
|
rip:SetTexture(CIRCLE)
|
|
rip:SetWidth(8); rip:SetHeight(8)
|
|
rip:SetPoint("CENTER", dotFrame, "CENTER", 0, 0)
|
|
rip:SetVertexColor(0.0, 0.85, 1.0, 0.35)
|
|
rip:SetBlendMode("ADD")
|
|
N.ripples[i] = rip
|
|
end
|
|
|
|
local CLASS_ICON = "Interface\\AddOns\\Nanami-UI\\img\\UI-Classes-Circles"
|
|
N.dotTex = dotFrame:CreateTexture(nil, "OVERLAY")
|
|
N.dotTex:SetTexture(CLASS_ICON)
|
|
N.dotTex:SetWidth(14); N.dotTex:SetHeight(14)
|
|
N.dotTex:SetPoint("CENTER", dotFrame, "CENTER", 0, 0)
|
|
local _, pClass = UnitClass("player")
|
|
if pClass and SFrames.CLASS_ICON_TCOORDS and SFrames.CLASS_ICON_TCOORDS[pClass] then
|
|
local tc = SFrames.CLASS_ICON_TCOORDS[pClass]
|
|
N.dotTex:SetTexCoord(tc[1], tc[2], tc[3], tc[4])
|
|
end
|
|
|
|
N.dirTip = dotFrame:CreateTexture(nil, "OVERLAY")
|
|
N.dirTip:SetTexture(CIRCLE)
|
|
N.dirTip:SetWidth(4); N.dirTip:SetHeight(4)
|
|
N.dirTip:SetPoint("CENTER", dotFrame, "CENTER", 0, 10)
|
|
N.dirTip:SetVertexColor(1, 1, 1, 0.90)
|
|
|
|
N.dirTail = dotFrame:CreateTexture(nil, "OVERLAY")
|
|
N.dirTail:SetTexture(CIRCLE)
|
|
N.dirTail:SetWidth(2); N.dirTail:SetHeight(2)
|
|
N.dirTail:SetPoint("CENTER", dotFrame, "CENTER", 0, -7)
|
|
N.dirTail:SetVertexColor(0.6, 0.6, 0.6, 0.50)
|
|
|
|
-- info bar (zone + coords below map)
|
|
local infoFrame = CreateFrame("Frame", nil, f)
|
|
infoFrame:SetWidth(w); infoFrame:SetHeight(24)
|
|
infoFrame:SetPoint("TOP", f, "BOTTOM", 0, 0)
|
|
infoFrame:SetFrameLevel(f:GetFrameLevel() + 6)
|
|
|
|
N.zoneFS = infoFrame:CreateFontString(nil, "OVERLAY")
|
|
N.zoneFS:SetFont(font, 9, "OUTLINE")
|
|
N.zoneFS:SetPoint("LEFT", infoFrame, "LEFT", 4, 0)
|
|
N.zoneFS:SetTextColor(NC.TC[1], NC.TC[2], NC.TC[3])
|
|
|
|
N.coordFS = infoFrame:CreateFontString(nil, "OVERLAY")
|
|
N.coordFS:SetFont(font, 9, "OUTLINE")
|
|
N.coordFS:SetPoint("RIGHT", infoFrame, "RIGHT", -4, 0)
|
|
N.coordFS:SetTextColor(0.85, 0.85, 0.85)
|
|
|
|
-- close button
|
|
local cbtn = CreateFrame("Button", nil, f)
|
|
cbtn:SetWidth(14); cbtn:SetHeight(14)
|
|
cbtn:SetPoint("TOPRIGHT", f, "TOPRIGHT", -3, -3)
|
|
cbtn:SetFrameLevel(f:GetFrameLevel() + 8)
|
|
local cbFS = cbtn:CreateFontString(nil, "OVERLAY")
|
|
cbFS:SetFont(font, 11, "OUTLINE"); cbFS:SetPoint("CENTER", 0, 0)
|
|
cbFS:SetText("x"); cbFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
|
|
cbtn:SetScript("OnClick", function() WM:ToggleNav() end)
|
|
cbtn:SetScript("OnEnter", function() cbFS:SetTextColor(1, 0.3, 0.3) end)
|
|
cbtn:SetScript("OnLeave", function() cbFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3]) end)
|
|
|
|
-- hover toolbar (shown on mouse enter)
|
|
local toolbar = CreateFrame("Frame", nil, f)
|
|
toolbar:SetWidth(w); toolbar:SetHeight(22)
|
|
toolbar:SetPoint("BOTTOM", f, "TOP", 0, 2)
|
|
toolbar:SetFrameLevel(f:GetFrameLevel() + 9)
|
|
toolbar:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
|
|
toolbar:SetBackdropColor(_A.headerBg[1], _A.headerBg[2], _A.headerBg[3], 0.85)
|
|
toolbar:SetBackdropBorderColor(0, 0, 0, 0.50)
|
|
toolbar:EnableMouse(true)
|
|
toolbar:Hide()
|
|
N.toolbar = toolbar
|
|
|
|
local tipFS = toolbar:CreateFontString(nil, "OVERLAY")
|
|
tipFS:SetFont(font, 9, "OUTLINE")
|
|
tipFS:SetPoint("CENTER", toolbar, "CENTER", 0, 0)
|
|
tipFS:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3])
|
|
tipFS:SetText("拖动移位 | 滚轮缩放 | Ctrl+滚轮透明度")
|
|
|
|
local lockBtn = CreateFrame("Button", nil, toolbar)
|
|
lockBtn:SetWidth(28); lockBtn:SetHeight(16)
|
|
lockBtn:SetPoint("RIGHT", toolbar, "RIGHT", -4, 0)
|
|
lockBtn:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
|
|
lockBtn:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], 0.90)
|
|
lockBtn:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
|
|
local lockFS = lockBtn:CreateFontString(nil, "OVERLAY")
|
|
lockFS:SetFont(font, 8, "OUTLINE"); lockFS:SetPoint("CENTER", 0, 0)
|
|
N.lockFS = lockFS
|
|
|
|
local function UpdateLockText()
|
|
local lk = GetNavConfig().locked
|
|
if lk then
|
|
lockFS:SetText("解锁"); lockFS:SetTextColor(1.0, 0.5, 0.5)
|
|
else
|
|
lockFS:SetText("锁定"); lockFS:SetTextColor(0.5, 1.0, 0.5)
|
|
end
|
|
end
|
|
UpdateLockText()
|
|
|
|
lockBtn:SetScript("OnClick", function()
|
|
local cfg = GetNavConfig()
|
|
cfg.locked = not cfg.locked
|
|
UpdateLockText()
|
|
end)
|
|
lockBtn:SetScript("OnEnter", function()
|
|
this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
|
|
end)
|
|
lockBtn:SetScript("OnLeave", function()
|
|
this:SetBackdropBorderColor(0.4, 0.4, 0.4, 0.6)
|
|
end)
|
|
|
|
N.hideTimer = 0
|
|
f:SetScript("OnEnter", function()
|
|
N.hideTimer = 0
|
|
if N.toolbar then N.toolbar:Show() end
|
|
if cbtn then cbtn:Show() end
|
|
end)
|
|
f:SetScript("OnLeave", function()
|
|
N.hideTimer = 1.5
|
|
end)
|
|
toolbar:SetScript("OnEnter", function()
|
|
N.hideTimer = 0
|
|
end)
|
|
toolbar:SetScript("OnLeave", function()
|
|
N.hideTimer = 1.5
|
|
end)
|
|
cbtn:Hide()
|
|
|
|
-- dragging
|
|
f:SetScript("OnMouseDown", function()
|
|
if arg1 == "LeftButton" and not GetNavConfig().locked then this:StartMoving() end
|
|
end)
|
|
f:SetScript("OnMouseUp", function() this:StopMovingOrSizing(); SaveNavPos() end)
|
|
|
|
-- scroll: resize from center / alpha
|
|
f:SetScript("OnMouseWheel", function()
|
|
local cfg = GetNavConfig()
|
|
if IsControlKeyDown() then
|
|
local a = (cfg.alpha or this:GetAlpha()) + arg1 * 0.10
|
|
if a < 0.15 then a = 0.15 end; if a > 1 then a = 1 end
|
|
cfg.alpha = a; this:SetAlpha(a)
|
|
else
|
|
local oldW = this:GetWidth()
|
|
local oldH = this:GetHeight()
|
|
local nw = cfg.width + arg1 * 25
|
|
if nw < N.MIN_W then nw = N.MIN_W end
|
|
if nw > N.MAX_W then nw = N.MAX_W end
|
|
local nh = nw * N.MH / N.MW
|
|
local dw = (nw - oldW) / 2
|
|
local dh = (nh - oldH) / 2
|
|
|
|
local pt, rel, rp, px, py = this:GetPoint()
|
|
if pt and px and py then
|
|
this:ClearAllPoints()
|
|
this:SetPoint(pt, rel, rp, px - dw, py + dh)
|
|
end
|
|
|
|
cfg.width = nw
|
|
NavApplySize(nw)
|
|
if N.toolbar then N.toolbar:SetWidth(nw) end
|
|
SaveNavPos()
|
|
end
|
|
end)
|
|
end
|
|
|
|
local function UpdateNavMap()
|
|
if not N.frame or not N.tiles or not N.frame:IsVisible() then return end
|
|
if WorldMapFrame and WorldMapFrame:IsVisible() then return end
|
|
|
|
if SetMapToCurrentZone then SetMapToCurrentZone() end
|
|
|
|
local mapFile = GetMapInfo and GetMapInfo() or ""
|
|
if mapFile ~= "" and mapFile ~= N.curMap then
|
|
N.curMap = mapFile
|
|
for i = 1, 12 do
|
|
N.tiles[i]:SetTexture("Interface\\WorldMap\\" .. mapFile .. "\\" .. mapFile .. i)
|
|
end
|
|
UpdateNavOverlays(mapFile)
|
|
end
|
|
|
|
if mapFile ~= "" then
|
|
local px, py = GetPlayerMapPosition("player")
|
|
if px and py and (px > 0 or py > 0) then
|
|
local fw = N.frame:GetWidth()
|
|
local fh = N.frame:GetHeight()
|
|
if N.dotFrame then
|
|
N.dotFrame:ClearAllPoints()
|
|
N.dotFrame:SetPoint("TOPLEFT", N.frame, "TOPLEFT", px * fw, -py * fh)
|
|
end
|
|
|
|
if GetPlayerFacing and N.dirTip then
|
|
local facing = GetPlayerFacing()
|
|
if facing then
|
|
local r = 10
|
|
local tipX = -math.sin(facing) * r
|
|
local tipY = math.cos(facing) * r
|
|
N.dirTip:ClearAllPoints()
|
|
N.dirTip:SetPoint("CENTER", N.dotFrame, "CENTER", tipX, tipY)
|
|
if N.dirTail then
|
|
local tr = 7
|
|
N.dirTail:ClearAllPoints()
|
|
N.dirTail:SetPoint("CENTER", N.dotFrame, "CENTER", -tipX / r * tr, -tipY / r * tr)
|
|
end
|
|
end
|
|
end
|
|
|
|
local coordStr = string.format("%.1f, %.1f", px * 100, py * 100)
|
|
if N.coordFS then N.coordFS:SetText(coordStr) end
|
|
end
|
|
end
|
|
|
|
local zn = GetZoneText and GetZoneText() or ""
|
|
if zn ~= "" and N.zoneFS then N.zoneFS:SetText(zn) end
|
|
end
|
|
|
|
local function UpdateNavPulse(dt)
|
|
if not N.frame or not N.ripples or not N.frame:IsVisible() then return end
|
|
N.pulse = (N.pulse or 0) + 0.05
|
|
if N.pulse > 6.2832 then N.pulse = N.pulse - 6.2832 end
|
|
|
|
local PHASE_OFF = 2.0944
|
|
for i = 1, 3 do
|
|
local rip = N.ripples[i]
|
|
if rip then
|
|
local t = math.mod(N.pulse + (i - 1) * PHASE_OFF, 6.2832) / 6.2832
|
|
local sz = 6 + 28 * t
|
|
local a = 0.40 * (1 - t)
|
|
rip:SetWidth(sz); rip:SetHeight(sz)
|
|
rip:SetVertexColor(0.0, 0.85, 1.0, a)
|
|
end
|
|
end
|
|
|
|
if N.hideTimer and N.hideTimer > 0 then
|
|
N.hideTimer = N.hideTimer - (dt or 0.04)
|
|
if N.hideTimer <= 0 then
|
|
N.hideTimer = 0
|
|
if N.toolbar then N.toolbar:Hide() end
|
|
end
|
|
end
|
|
end
|
|
|
|
function WM:ToggleNav()
|
|
local nc = GetNavConfig()
|
|
nc.enabled = not nc.enabled
|
|
CreateNavMap()
|
|
if nc.enabled then
|
|
N.frame:Show(); N.curMap = ""
|
|
UpdateNavMap()
|
|
if SFrames and SFrames.Print then
|
|
SFrames:Print("导航地图: |cff00ff00已开启|r (滚轮缩放 | Ctrl+滚轮透明度 | 拖动)")
|
|
end
|
|
else
|
|
N.frame:Hide()
|
|
if SFrames and SFrames.Print then
|
|
SFrames:Print("导航地图: |cffff0000已关闭|r")
|
|
end
|
|
end
|
|
end
|
|
|
|
function WM:ShowNav()
|
|
local nc = GetNavConfig()
|
|
CreateNavMap()
|
|
if not nc.enabled then nc.enabled = true end
|
|
if WorldMapFrame and WorldMapFrame:IsVisible() then
|
|
WorldMapFrame:Hide()
|
|
end
|
|
N.frame:Show(); N.curMap = ""
|
|
UpdateNavMap()
|
|
end
|
|
|
|
local function CreateNavToggleBtn()
|
|
if not skinFrame or not coordOverlay then return end
|
|
if N.toggleBtn then return end
|
|
local font = (SFrames and SFrames.GetFont and SFrames:GetFont()) or "Fonts\\ARIALN.TTF"
|
|
local b = CreateFrame("Button", nil, coordOverlay)
|
|
N.toggleBtn = b
|
|
b:SetWidth(70); b:SetHeight(20)
|
|
b:SetPoint("BOTTOMRIGHT", skinFrame, "BOTTOMRIGHT", -12, 22)
|
|
b:SetFrameLevel(coordOverlay:GetFrameLevel() + 1)
|
|
b:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8",
|
|
edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 } })
|
|
b:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
b:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
local fs = b:CreateFontString(nil, "OVERLAY")
|
|
fs:SetFont(font, 10, "OUTLINE"); fs:SetPoint("CENTER", 0, 0)
|
|
fs:SetText("缩略地图"); fs:SetTextColor(_A.btnText[1], _A.btnText[2], _A.btnText[3])
|
|
b:SetScript("OnClick", function() WM:ShowNav() end)
|
|
b:SetScript("OnEnter", function()
|
|
this:SetBackdropColor(_A.btnHoverBg[1], _A.btnHoverBg[2], _A.btnHoverBg[3], _A.btnHoverBg[4])
|
|
this:SetBackdropBorderColor(_A.btnHoverBd[1], _A.btnHoverBd[2], _A.btnHoverBd[3], _A.btnHoverBd[4])
|
|
end)
|
|
b:SetScript("OnLeave", function()
|
|
this:SetBackdropColor(_A.btnBg[1], _A.btnBg[2], _A.btnBg[3], _A.btnBg[4])
|
|
this:SetBackdropBorderColor(_A.btnBorder[1], _A.btnBorder[2], _A.btnBorder[3], _A.btnBorder[4])
|
|
end)
|
|
end
|
|
|
|
local function InitNavMap()
|
|
CreateNavMap()
|
|
CreateNavToggleBtn()
|
|
|
|
local nc = GetNavConfig()
|
|
if nc.enabled then
|
|
N.frame:Show(); N.curMap = ""
|
|
end
|
|
|
|
local mapEl, animEl = 0, 0
|
|
local u = CreateFrame("Frame", nil, UIParent)
|
|
u:SetScript("OnUpdate", function()
|
|
local dt = arg1 or 0
|
|
mapEl = mapEl + dt
|
|
animEl = animEl + dt
|
|
if mapEl >= 0.10 then mapEl = 0; UpdateNavMap() end
|
|
if animEl >= 0.04 then
|
|
local elapsed = animEl; animEl = 0; UpdateNavPulse(elapsed)
|
|
end
|
|
end)
|
|
end
|
|
|
|
--------------------------------------------------------------------------------
|
|
-- 12. Initialize
|
|
--------------------------------------------------------------------------------
|
|
function WM:Initialize()
|
|
local cfg = self:GetConfig()
|
|
if not cfg.enabled then return end
|
|
|
|
HideBlizzardDecorations()
|
|
CreateNanamiSkin()
|
|
CreateTooltipBG()
|
|
self:SetupWindowMode()
|
|
|
|
SkinWorldMapControls()
|
|
CreateWaypointPin()
|
|
HookWorldMapButtonClick()
|
|
|
|
HookTooltipShow(WorldMapTooltip)
|
|
HookTooltipShow(GameTooltip)
|
|
|
|
HookAllChatFramesForMapLinks()
|
|
HookSetItemRefForWaypoints()
|
|
|
|
local origUpdater = OnFrameUpdate
|
|
local function NewOnFrameUpdate()
|
|
origUpdater()
|
|
UpdatePinPosition()
|
|
UpdateDarkmoonPins()
|
|
end
|
|
|
|
local updater = CreateFrame("Frame", nil, UIParent)
|
|
updater:SetScript("OnUpdate", NewOnFrameUpdate)
|
|
|
|
InitNavMap()
|
|
DiscoverDmfZoneIndices()
|
|
|
|
self.initialized = true
|
|
local _hex = (SFrames.Theme and SFrames.Theme:GetAccentHex()) or "ffffb3d9"
|
|
DEFAULT_CHAT_FRAME:AddMessage("|c" .. _hex .. "[Nanami-UI]|r 世界地图模块已加载 (Nanami主题 | Ctrl+左键放置标记 | /nui nav 导航地图)")
|
|
end
|