-------------------------------------------------------------------------------- -- 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 for _, n in ipairs({"WorldMapFrameSizeUpButton", "WorldMapFrameSizeDownButton"}) do local f = _G[n] if f then f:Hide() f:SetAlpha(0) f:EnableMouse(false) if f.ClearAllPoints then f:ClearAllPoints() f:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -9999, 9999) end if f.GetRegions then local regs = { f:GetRegions() } for _, r in ipairs(regs) do if r and r.Hide then r:Hide() end end end end end if WorldMapFrame.GetChildren then local wmCh = { WorldMapFrame:GetChildren() } for _, c in ipairs(wmCh) do if c and not c._nanamiNuked and not c._nanamiSkinned then local cn = c.GetName and c:GetName() or "" if cn ~= "" and ( string.find(cn, "SizeUp") or string.find(cn, "SizeDown") or string.find(cn, "Resize") or string.find(cn, "Maximize") or string.find(cn, "Minimize") ) then NukeFrame(c) elseif cn == "" then local w = c.GetWidth and c:GetWidth() or 999 local h = c.GetHeight and c:GetHeight() or 999 if w > 0 and w < 30 and h > 0 and h < 30 then NukeFrame(c) end end end end 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 local RepositionWorldMapControls -------------------------------------------------------------------------------- -- 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() RepositionWorldMapControls() 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() + 12) WorldMapFrame:SetHeight(WorldMapButton:GetHeight() + 40) WorldMapDetailFrame:ClearAllPoints() WorldMapDetailFrame:SetPoint("TOPLEFT", WorldMapFrame, "TOPLEFT", 6, -32) WorldMapButton:ClearAllPoints() WorldMapButton:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", 0, 0) WorldMapButton:SetPoint("BOTTOMRIGHT", WorldMapDetailFrame, "BOTTOMRIGHT", 0, 0) if BlackoutWorld then BlackoutWorld:Hide() end end local blizzLabelsKilled = false local function KillBlizzardDropDownLabels() if blizzLabelsKilled then return end local _G = getfenv(0) local targets = { _G["WorldMapContinentDropDown"], _G["WorldMapZoneDropDown"], _G["WorldMapZoneMinimapDropDown"], _G["WorldMapFrameMiniBorderLeft"], _G["WorldMapFrameMiniBorderRight"], WorldMapFrame, } local found = 0 for _, parent in ipairs(targets) do if parent and parent.GetRegions then local regions = { parent:GetRegions() } for _, r in ipairs(regions) do if r and not r._nanamiCustom and r.GetObjectType and r:GetObjectType() == "FontString" then local t = r.GetText and r:GetText() or "" if t == "大陆" or t == "地区" or t == "Continent" or t == "Zone" then r:SetText("") r:SetAlpha(0) r:Hide() r.Show = function() end r.SetText = function() end r.SetAlpha = function() end found = found + 1 end end end end end for _, n in ipairs({"WorldMapContinentDropDownLabel", "WorldMapZoneDropDownLabel", "WorldMapZoneMinimapDropDownLabel"}) do local lbl = _G[n] if lbl then lbl:SetAlpha(0) lbl:Hide() lbl.Show = function() end lbl.SetText = function() end found = found + 1 end end if found > 0 then blizzLabelsKilled = true end end RepositionWorldMapControls = function() if not skinFrame or not titleBar then return end local _G = getfenv(0) if not blizzLabelsKilled then KillBlizzardDropDownLabels() end if WorldMapDetailFrame then WorldMapDetailFrame:ClearAllPoints() WorldMapDetailFrame:SetPoint("TOPLEFT", WorldMapFrame, "TOPLEFT", 6, -32) end if WorldMapButton and WorldMapDetailFrame then WorldMapButton:ClearAllPoints() WorldMapButton:SetPoint("TOPLEFT", WorldMapDetailFrame, "TOPLEFT", 0, 0) WorldMapButton:SetPoint("BOTTOMRIGHT", WorldMapDetailFrame, "BOTTOMRIGHT", 0, 0) end local cDD = _G["WorldMapContinentDropDown"] local zDD = _G["WorldMapZoneDropDown"] local zBtn = _G["WorldMapZoomOutButton"] if cDD then cDD:ClearAllPoints() cDD:SetPoint("TOPLEFT", skinFrame, "TOPLEFT", 200, -3) end if zDD then zDD:ClearAllPoints() zDD:SetPoint("LEFT", cDD, "RIGHT", 0, 0) end if zBtn then zBtn:ClearAllPoints() zBtn:SetPoint("LEFT", zDD, "RIGHT", -14, 2) zBtn:SetWidth(48) zBtn:SetHeight(20) 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() RepositionWorldMapControls() 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() RepositionWorldMapControls() end _G2.WorldMapFrame_Minimize = function() if origMinimize then origMinimize() end ApplyLayout(WM:GetConfig()) HideBlizzardDecorations() RepositionWorldMapControls() 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({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 }, }) 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 origLabel = _G[dropdownName .. "Label"] local labelText = "" if origLabel then labelText = origLabel:GetText() or "" origLabel:Hide() origLabel:SetAlpha(0) origLabel:SetText("") origLabel:ClearAllPoints() origLabel:SetPoint("TOPLEFT", UIParent, "TOPLEFT", -9999, 9999) origLabel.Show = function() end origLabel.SetText = function() end origLabel.SetPoint = function() end origLabel.ClearAllPoints = function() end end if labelText == "" then if string.find(dropdownName, "Continent") then labelText = "大陆" elseif string.find(dropdownName, "ZoneMinimap") then labelText = "" elseif string.find(dropdownName, "Zone") then labelText = "地区" end end if labelText ~= "" then local newLabel = bg:CreateFontString(nil, "OVERLAY") newLabel._nanamiCustom = true newLabel:SetFont(font, 11, "OUTLINE") newLabel:SetTextColor(_A.dimText[1], _A.dimText[2], _A.dimText[3]) newLabel:SetPoint("RIGHT", bg, "LEFT", -4, 0) newLabel:SetText(labelText) dd._nanamiLabel = newLabel 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({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 1, insets = { left = 1, right = 1, top = 1, bottom = 1 }, }) 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() RepositionWorldMapControls() 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 = "" 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 and handle nmwp: clicks -------------------------------------------------------------------------------- local function TransformMapLinks(text) if not text or type(text) ~= "string" then return text end if not string.find(text, "]*)>", 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 end