Files
Nanami-UI/ZoneLevelRange.lua
2026-03-24 15:56:28 +08:00

386 lines
20 KiB
Lua

--------------------------------------------------------------------------------
-- 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