-------------------------------------------------------------------------------- -- Nanami-UI: Zone Level Range Module (ZoneLevelRange.lua) -- Displays zone level ranges, territory status, instances and raids -- on the world map continent view using Nanami-themed tooltip. -- Self-contained data; supersedes LevelRange-Turtle's display. -------------------------------------------------------------------------------- SFrames = SFrames or {} SFrames.ZoneLevelRange = {} local ZLR = SFrames.ZoneLevelRange -------------------------------------------------------------------------------- -- Locale helper -------------------------------------------------------------------------------- local isZH = (GetLocale() == "zhCN") local function N(en, zh) return isZH and zh or en end -------------------------------------------------------------------------------- -- Faction constants -------------------------------------------------------------------------------- local F_A = "Alliance" local F_H = "Horde" local F_C = "Contested" -------------------------------------------------------------------------------- -- Zone Level Ranges: [localizedName] = { min, max, factionType } -------------------------------------------------------------------------------- local ZONE_RANGES = { -- Eastern Kingdoms [N("Elwynn Forest", "艾尔文森林")] = { 1, 10, F_A }, [N("Dun Morogh", "丹莫罗")] = { 1, 10, F_A }, [N("Tirisfal Glades", "提瑞斯法林地")] = { 1, 10, F_H }, [N("Loch Modan", "洛克莫丹")] = { 10, 20, F_A }, [N("Silverpine Forest", "银松森林")] = { 10, 20, F_H }, [N("Westfall", "西部荒野")] = { 10, 20, F_A }, [N("Redridge Mountains", "赤脊山")] = { 15, 25, F_C }, [N("Duskwood", "暮色森林")] = { 18, 30, F_C }, [N("Hillsbrad Foothills", "希尔斯布莱德丘陵")] = { 20, 30, F_C }, [N("Wetlands", "湿地")] = { 20, 30, F_C }, [N("Alterac Mountains", "奥特兰克山脉")] = { 30, 40, F_C }, [N("Arathi Highlands", "阿拉希高地")] = { 30, 40, F_C }, [N("Stranglethorn Vale", "荆棘谷")] = { 30, 45, F_C }, [N("Badlands", "荒芜之地")] = { 35, 45, F_C }, [N("Swamp of Sorrows", "悲伤沼泽")] = { 35, 45, F_C }, [N("The Hinterlands", "辛特兰")] = { 40, 50, F_C }, [N("Searing Gorge", "灼热峡谷")] = { 43, 50, F_C }, [N("Blasted Lands", "诅咒之地")] = { 45, 55, F_C }, [N("Burning Steppes", "燃烧平原")] = { 50, 58, F_C }, [N("Western Plaguelands", "西瘟疫之地")] = { 51, 58, F_C }, [N("Eastern Plaguelands", "东瘟疫之地")] = { 53, 60, F_C }, [N("Deadwind Pass", "逆风小径")] = { 55, 60, F_C }, -- Kalimdor [N("Durotar", "杜隆塔尔")] = { 1, 10, F_H }, [N("Mulgore", "莫高雷")] = { 1, 10, F_H }, [N("Teldrassil", "泰达希尔")] = { 1, 10, F_A }, [N("Darkshore", "黑海岸")] = { 10, 20, F_A }, [N("The Barrens", "贫瘠之地")] = { 10, 25, F_H }, [N("Stonetalon Mountains", "石爪山脉")] = { 15, 27, F_C }, [N("Ashenvale", "灰谷")] = { 18, 30, F_C }, [N("Thousand Needles", "千针石林")] = { 25, 35, F_C }, [N("Desolace", "凄凉之地")] = { 30, 40, F_C }, [N("Dustwallow Marsh", "尘泥沼泽")] = { 35, 45, F_C }, [N("Feralas", "菲拉斯")] = { 40, 50, F_C }, [N("Tanaris", "塔纳利斯")] = { 40, 50, F_C }, [N("Azshara", "艾萨拉")] = { 45, 55, F_C }, [N("Felwood", "费伍德森林")] = { 48, 55, F_C }, [N("Un'Goro Crater", "安戈洛环形山")] = { 48, 55, F_C }, [N("Silithus", "希利苏斯")] = { 55, 60, F_C }, [N("Winterspring", "冬泉谷")] = { 55, 60, F_C }, [N("Moonglade", "月光林地")] = { 1, 60, F_C }, -- Turtle WoW [N("Thalassian Highlands", "阿尔萨拉斯")] = { 1, 10, F_A }, [N("Blackstone Island", "黑石岛")] = { 1, 10, F_H }, [N("Gilneas", "吉尔尼斯")] = { 39, 46, F_C }, [N("Gillijim's Isle", "吉利吉姆之岛")] = { 48, 53, F_C }, [N("Lapidis Isle", "拉匹迪斯之岛")] = { 48, 53, F_C }, [N("Tel'Abim", "泰拉比姆")] = { 54, 60, F_C }, [N("Scarlet Enclave", "东瘟疫之地:血色领地")] = { 55, 60, F_C }, [N("Hyjal", "海加尔山")] = { 58, 60, F_C }, [N("Grim Reaches", "冷酷海岸")] = { 33, 38, F_C }, [N("Northwind", "北风领")] = { 28, 34, F_C }, [N("Balor", "巴洛")] = { 29, 34, F_C }, [N("Moonsong Coast", "月语海岸")] = { 53, 58, F_C }, } -------------------------------------------------------------------------------- -- Instances: [zoneName] = { { instanceName, levelString }, ... } -------------------------------------------------------------------------------- local ZONE_INSTANCES = { [N("Westfall", "西部荒野")] = { { N("Deadmines", "死亡矿井"), "17-26" } }, [N("The Barrens", "贫瘠之地")] = { { N("Wailing Caverns", "哀嚎洞穴"), "17-24" }, { N("Razorfen Kraul", "剃刀高地"), "25-30" }, { N("Razorfen Downs", "剃刀沼泽"), "33-45" } }, [N("Silverpine Forest", "银松森林")] = { { N("Shadowfang Keep", "影牙城堡"), "22-30" } }, [N("Dun Morogh", "丹莫罗")] = { { N("Gnomeregan", "诺莫瑞根"), "29-38" } }, [N("Tirisfal Glades", "提瑞斯法林地")] = { { N("The Scarlet Monastery","血色修道院"), "34-45" } }, [N("Badlands", "荒芜之地")] = { { N("Uldaman", "奥达曼"), "35-47" } }, [N("Desolace", "凄凉之地")] = { { N("Maraudon", "玛拉顿"), "46-55" } }, [N("Swamp of Sorrows", "悲伤沼泽")] = { { N("The Sunken Temple", "沉没的神庙"), "45-55" } }, [N("Searing Gorge", "灼热峡谷")] = { { N("Blackrock Depths", "黑石深渊"), "52-60" }, { N("Blackrock Spire", "黑石塔"), "58-60" } }, [N("Eastern Plaguelands", "东瘟疫之地")] = { { N("Stratholme", "斯坦索姆"), "58-60" } }, [N("Feralas", "菲拉斯")] = { { N("Dire Maul", "厄运之槌"), "55-60" } }, [N("Western Plaguelands", "西瘟疫之地")] = { { N("Scholomance", "通灵学院"), "57-60" } }, [N("Durotar", "杜隆塔尔")] = { { N("Ragefire Chasm", "怒焰裂谷"), "13-18" } }, [N("Ashenvale", "灰谷")] = { { N("Blackfathom Deeps", "黑暗深渊"), "24-32" }, { N("The Crescent Grove", "新月林地"), "32-38" } }, [N("Gilneas", "吉尔尼斯")] = { { N("Gilneas City", "吉尔尼斯城"), "43-49" } }, [N("Burning Steppes", "燃烧平原")] = { { N("Hateforge Quarry", "仇恨熔炉采石场"), "52-60" }, { N("Blackrock Depths", "黑石深渊"), "52-60" }, { N("Blackrock Spire", "黑石塔"), "58-60" } }, [N("Deadwind Pass", "逆风小径")] = { { N("Karazhan Crypt", "卡拉赞地穴"), "58-60" } }, [N("Elwynn Forest", "艾尔文森林")] = { { N("The Stockades", "监狱"), "24-32" }, { N("Stormwind Vault", "暴风城地窖"), "60+" } }, [N("Tanaris", "塔纳利斯")] = { { N("Zul'Farrak", "祖尔法拉克"), "44-54" }, { N("Caverns of Time: The Black Morass", "时光之穴:黑色沼泽"), "60+" } }, [N("Balor", "巴洛")] = { { N("Stormwrought Ruins", "风暴废墟"), "35-41" } }, [N("Wetlands", "湿地")] = { { N("Dragonmaw Retreat", "龙喉要塞"), "27-33" } }, [N("Moonsong Coast", "月语海岸")] = { { N("Timbermaw Hold", "木喉要塞"), "60+" } }, } -------------------------------------------------------------------------------- -- Raids -------------------------------------------------------------------------------- local ZONE_RAIDS = { [N("Eastern Plaguelands", "东瘟疫之地")] = { { N("Naxxramas", "纳克萨玛斯"), "60+" } }, [N("Dustwallow Marsh", "尘泥沼泽")] = { { N("Onyxia's Lair", "奥妮克希亚的巢穴"), "60+" } }, [N("Silithus", "希利苏斯")] = { { N("Ruins of Ahn'Qiraj", "安其拉废墟"), "60+" }, { N("Temple of Ahn'Qiraj", "安其拉神庙"), "60+" } }, [N("Stranglethorn Vale", "荆棘谷")] = { { N("Zul'Gurub", "祖尔格拉布"), "60+" } }, [N("Hyjal", "海加尔山")] = { { N("Emerald Sanctum", "翡翠圣所"), "60+" } }, [N("Deadwind Pass", "逆风小径")] = { { N("Lower Karazhan Halls", "下层卡拉赞大厅"), "60+" } }, } -------------------------------------------------------------------------------- -- Fishing Level Requirements -------------------------------------------------------------------------------- local ZONE_FISHING = { [N("Elwynn Forest", "艾尔文森林")] = 25, [N("Dun Morogh", "丹莫罗")] = 25, [N("Tirisfal Glades", "提瑞斯法林地")] = 25, [N("Loch Modan", "洛克莫丹")] = 75, [N("Silverpine Forest", "银松森林")] = 75, [N("Westfall", "西部荒野")] = 75, [N("Redridge Mountains", "赤脊山")] = 150, [N("Duskwood", "暮色森林")] = 150, [N("Hillsbrad Foothills", "希尔斯布莱德丘陵")] = 150, [N("Wetlands", "湿地")] = 150, [N("Alterac Mountains", "奥特兰克山脉")] = 225, [N("Arathi Highlands", "阿拉希高地")] = 225, [N("Stranglethorn Vale", "荆棘谷")] = 225, [N("Swamp of Sorrows", "悲伤沼泽")] = 225, [N("The Hinterlands", "辛特兰")] = 300, [N("Western Plaguelands", "西瘟疫之地")] = 300, [N("Durotar", "杜隆塔尔")] = 25, [N("Mulgore", "莫高雷")] = 25, [N("Teldrassil", "泰达希尔")] = 25, [N("Darkshore", "黑海岸")] = 75, [N("The Barrens", "贫瘠之地")] = 75, [N("Stonetalon Mountains","石爪山脉")] = 150, [N("Ashenvale", "灰谷")] = 150, [N("Thousand Needles", "千针石林")] = 225, [N("Desolace", "凄凉之地")] = 225, [N("Dustwallow Marsh", "尘泥沼泽")] = 225, [N("Feralas", "菲拉斯")] = 300, [N("Tanaris", "塔纳利斯")] = 300, [N("Azshara", "艾萨拉")] = 300, [N("Felwood", "费伍德森林")] = 300, [N("Un'Goro Crater", "安戈洛环形山")] = 300, [N("Moonglade", "月光林地")] = 300, } -------------------------------------------------------------------------------- -- Sub-zone -> Parent Zone Mapping -------------------------------------------------------------------------------- local ZONE_SUBZONES = { [N("Orgrimmar", "奥格瑞玛")] = N("Durotar", "杜隆塔尔"), [N("Thunder Bluff", "雷霆崖")] = N("Mulgore", "莫高雷"), [N("Undercity", "幽暗城")] = N("Tirisfal Glades", "提瑞斯法林地"), [N("Ironforge", "铁炉堡")] = N("Dun Morogh", "丹莫罗"), [N("Stormwind City", "暴风城")] = N("Elwynn Forest", "艾尔文森林"), [N("Darnassus", "达纳苏斯")] = N("Teldrassil", "泰达希尔"), [N("Alah'Thalas", "萨拉斯高地")] = N("Thalassian Highlands", "阿尔萨拉斯"), } -------------------------------------------------------------------------------- -- Display strings & colors -------------------------------------------------------------------------------- local S_LEVELS = N("Level %d-%d", "等级 %d-%d") local S_FISHING = N("Fishing Level %d", "钓鱼等级 %d") local S_INSTANCES = N("Instances:", "地下城:") local S_RAIDS = N("Raids:", "团队副本:") local S_FRIENDLY = N("Friendly Territory", "友好领土") local S_HOSTILE = N("Hostile Territory", "敌对领土") local S_CONTESTED = N("Contested Territory", "争夺中的领土") local COLORS = { friendly = { 0.20, 0.90, 0.20 }, hostile = { 0.90, 0.20, 0.20 }, contested = { 0.80, 0.60, 0.40 }, levels = { 0.80, 0.60, 0.00 }, header = { 1.00, 0.84, 0.00 }, instance = { 0.81, 0.81, 0.81 }, fishing = { 0.50, 0.70, 0.90 }, } -------------------------------------------------------------------------------- -- Tooltip frame & display logic -------------------------------------------------------------------------------- local zlrTT local zlrCurrentZone, zlrCurrentArea local zlrOldUpdate local function CreateZLRTooltip() if zlrTT then return end zlrTT = CreateFrame("GameTooltip", "NanamiZoneLevelTT", UIParent, "GameTooltipTemplate") zlrTT:SetFrameStrata("TOOLTIP") zlrTT:SetFrameLevel(255) end local function StyleTooltip() if not zlrTT then return end local _A = SFrames.ActiveTheme if not _A then return end zlrTT:SetBackdrop({ bgFile = "Interface\\Tooltips\\UI-Tooltip-Background", edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border", tile = true, tileSize = 16, edgeSize = 14, insets = { left = 3, right = 3, top = 3, bottom = 3 }, }) zlrTT:SetBackdropColor(_A.panelBg[1], _A.panelBg[2], _A.panelBg[3], _A.panelBg[4] or 0.95) zlrTT:SetBackdropBorderColor(_A.panelBorder[1], _A.panelBorder[2], _A.panelBorder[3], _A.panelBorder[4] or 1) end local function UpdateZLRTooltip(zoneName) if not zlrTT then return end if not zoneName or zoneName == "" then zlrTT:Hide() return end local data = ZONE_RANGES[zoneName] if not data then zlrTT:Hide() return end zlrTT:SetOwner(UIParent, "ANCHOR_NONE") zlrTT:ClearAllPoints() if WorldMapDetailFrame then zlrTT:SetPoint("BOTTOMLEFT", WorldMapDetailFrame, "BOTTOMLEFT", 2, 2) end local minLv, maxLv, faction = data[1], data[2], data[3] zlrTT:SetText(zoneName, 1, 1, 1) local lvStr = string.format(S_LEVELS, minLv, maxLv) zlrTT:AddLine(lvStr, COLORS.levels[1], COLORS.levels[2], COLORS.levels[3]) local _, playerFaction = UnitFactionGroup("player") local territoryText, territoryColor if faction == F_C then territoryText = S_CONTESTED territoryColor = COLORS.contested elseif playerFaction == faction then territoryText = S_FRIENDLY territoryColor = COLORS.friendly else territoryText = S_HOSTILE territoryColor = COLORS.hostile end zlrTT:AddLine(territoryText, territoryColor[1], territoryColor[2], territoryColor[3]) local instances = ZONE_INSTANCES[zoneName] if instances then zlrTT:AddLine(" ") zlrTT:AddLine(S_INSTANCES, COLORS.header[1], COLORS.header[2], COLORS.header[3]) for _, inst in ipairs(instances) do local iName, iLevels = inst[1], inst[2] zlrTT:AddDoubleLine( iName, "(" .. iLevels .. ")", COLORS.instance[1], COLORS.instance[2], COLORS.instance[3], COLORS.instance[1], COLORS.instance[2], COLORS.instance[3] ) end end local raids = ZONE_RAIDS[zoneName] if raids then zlrTT:AddLine(" ") zlrTT:AddLine(S_RAIDS, COLORS.header[1], COLORS.header[2], COLORS.header[3]) for _, raid in ipairs(raids) do local rName, rLevels = raid[1], raid[2] zlrTT:AddDoubleLine( rName, "(" .. rLevels .. ")", COLORS.instance[1], COLORS.instance[2], COLORS.instance[3], COLORS.instance[1], COLORS.instance[2], COLORS.instance[3] ) end end local fishLv = ZONE_FISHING[zoneName] if fishLv then zlrTT:AddLine(" ") zlrTT:AddLine(string.format(S_FISHING, fishLv), COLORS.fishing[1], COLORS.fishing[2], COLORS.fishing[3]) end StyleTooltip() zlrTT:Show() end -------------------------------------------------------------------------------- -- Hook WorldMapButton_OnUpdate -------------------------------------------------------------------------------- local function HookWorldMapUpdate() zlrOldUpdate = WorldMapButton_OnUpdate WorldMapButton_OnUpdate = function(a1) if zlrOldUpdate then zlrOldUpdate(a1) end local areaNameRaw = WorldMapFrame and WorldMapFrame.areaName or "" local _, _, trimmed = string.find(areaNameRaw, "^%s*(.-)%s*$") if not trimmed then trimmed = areaNameRaw end local zoneNum = GetCurrentMapZone and GetCurrentMapZone() or -1 if ZONE_SUBZONES[trimmed] then trimmed = ZONE_SUBZONES[trimmed] end if zoneNum == zlrCurrentZone and areaNameRaw == zlrCurrentArea then return end zlrCurrentZone = zoneNum zlrCurrentArea = areaNameRaw if zoneNum == 0 then UpdateZLRTooltip(trimmed) else UpdateZLRTooltip(nil) end end end -------------------------------------------------------------------------------- -- Suppress LevelRange-Turtle's original tooltip -------------------------------------------------------------------------------- local function SuppressLevelRangeTooltip() local _G = getfenv(0) if _G["LevelRangeTooltip"] then _G["LevelRangeTooltip"]:Hide() _G["LevelRangeTooltip"].Show = function(self) self:Hide() end end end -------------------------------------------------------------------------------- -- Hook WorldMapFrame hide to dismiss tooltip -------------------------------------------------------------------------------- local function HookMapHide() if not WorldMapFrame then return end local prevOnHide = WorldMapFrame:GetScript("OnHide") WorldMapFrame:SetScript("OnHide", function(a1,a2,a3,a4,a5) if prevOnHide then prevOnHide(a1,a2,a3,a4,a5) end if zlrTT then zlrTT:Hide() end zlrCurrentZone = nil zlrCurrentArea = nil end) end -------------------------------------------------------------------------------- -- Initialize -------------------------------------------------------------------------------- function ZLR:Initialize() local wmCfg = SFramesDB and SFramesDB.WorldMap if wmCfg and wmCfg.enabled == false then return end CreateZLRTooltip() HookWorldMapUpdate() HookMapHide() SuppressLevelRangeTooltip() self.initialized = true end