-------------------------------------------------------------------------------- -- Nanami-UI: MapIcons - Class-colored party/raid icons on maps -- Shows class icon circles on World Map, Battlefield Minimap, and Minimap -- Uses UI-Classes-Circles.tga for class-specific circular portraits -- Zone size data: prefers pfQuest DB, falls back to built-in table -------------------------------------------------------------------------------- SFrames.MapIcons = SFrames.MapIcons or {} local MI = SFrames.MapIcons local CLASS_ICON_PATH = "Interface\\AddOns\\Nanami-UI\\img\\UI-Classes-Circles" local CLASS_ICON_TCOORDS = SFrames.CLASS_ICON_TCOORDS local CLASS_COLORS = SFrames.Config.colors.class -------------------------------------------------------------------------------- -- Minimap zoom yard ranges: [indoor/outdoor][zoomLevel] = diameter in yards -- indoor=0, outdoor=1 -------------------------------------------------------------------------------- local MM_ZOOM = { [0] = { [0]=300, [1]=240, [2]=180, [3]=120, [4]=80, [5]=50 }, [1] = { [0]=466.67, [1]=400, [2]=333.33, [3]=266.67, [4]=200, [5]=133.33 }, } -------------------------------------------------------------------------------- -- Built-in zone sizes (width, height in yards) keyed by GetMapInfo() file name -- Fallback when pfQuest is not installed -------------------------------------------------------------------------------- local ZONE_SIZES = { ["Ashenvale"] = { 5766.67, 3843.75 }, ["Aszhara"] = { 5533.33, 3689.58 }, ["Darkshore"] = { 6550.00, 4366.66 }, ["Desolace"] = { 4600.00, 3066.67 }, ["Durotar"] = { 4925.00, 3283.34 }, ["Dustwallow"] = { 5250.00, 3500.00 }, ["Felwood"] = { 5750.00, 3833.33 }, ["Feralas"] = { 6950.00, 4633.33 }, ["Moonglade"] = { 2308.33, 1539.59 }, ["Mulgore"] = { 5250.00, 3500.00 }, ["Silithus"] = { 3483.33, 2322.92 }, ["StonetalonMountains"] = { 4883.33, 3256.25 }, ["Tanaris"] = { 6900.00, 4600.00 }, ["Teldrassil"] = { 4925.00, 3283.34 }, ["Barrens"] = { 10133.34, 6756.25 }, ["ThousandNeedles"] = { 4400.00, 2933.33 }, ["UngoroCrater"] = { 3677.08, 2452.08 }, ["Winterspring"] = { 7100.00, 4733.33 }, ["Alterac"] = { 2800.00, 1866.67 }, ["ArathiHighlands"] = { 3600.00, 2400.00 }, ["Badlands"] = { 2487.50, 1658.34 }, ["BlastedLands"] = { 3350.00, 2233.30 }, ["BurningSteppes"] = { 2929.16, 1952.08 }, ["DeadwindPass"] = { 2500.00, 1666.63 }, ["DunMorogh"] = { 4925.00, 3283.34 }, ["Duskwood"] = { 2700.00, 1800.03 }, ["EasternPlaguelands"] = { 4031.25, 2687.50 }, ["ElwynnForest"] = { 3470.84, 2314.62 }, ["Hilsbrad"] = { 3200.00, 2133.33 }, ["Hinterlands"] = { 3850.00, 2566.67 }, ["LochModan"] = { 2758.33, 1839.58 }, ["RedridgeMountains"] = { 2170.84, 1447.90 }, ["SearingGorge"] = { 1837.50, 1225.00 }, ["SilverpineForest"] = { 4200.00, 2800.00 }, ["Stranglethorn"] = { 6381.25, 4254.10 }, ["SwampOfSorrows"] = { 2293.75, 1529.17 }, ["Tirisfal"] = { 4518.75, 3012.50 }, ["WesternPlaguelands"] = { 4300.00, 2866.67 }, ["Westfall"] = { 3500.00, 2333.30 }, ["Wetlands"] = { 4300.00, 2866.67 }, } -------------------------------------------------------------------------------- -- Indoor detection (CVar trick from pfQuest) -- Returns 0 = indoor, 1 = outdoor -------------------------------------------------------------------------------- local cachedIndoor = 1 local indoorCheckTime = 0 local function DetectIndoor() if pfMap and pfMap.minimap_indoor then return pfMap.minimap_indoor() end local ok1, zoomVal = pcall(GetCVar, "minimapZoom") local ok2, insideVal = pcall(GetCVar, "minimapInsideZoom") if not ok1 or not ok2 then return 1 end local tempzoom = 0 local state = 1 if zoomVal == insideVal then local cur = Minimap:GetZoom() if cur >= 3 then Minimap:SetZoom(cur - 1) tempzoom = 1 else Minimap:SetZoom(cur + 1) tempzoom = -1 end end local ok3, zoomVal2 = pcall(GetCVar, "minimapZoom") local ok4, insideVal2 = pcall(GetCVar, "minimapInsideZoom") if ok3 and ok4 and zoomVal2 ~= insideVal2 then state = 0 end if tempzoom ~= 0 then Minimap:SetZoom(Minimap:GetZoom() + tempzoom) end return state end -------------------------------------------------------------------------------- -- Get current zone dimensions in yards -------------------------------------------------------------------------------- local function GetZoneYards() if pfMap and pfMap.GetMapIDByName and pfDB and pfDB["minimap"] then local name = GetRealZoneText and GetRealZoneText() or "" if name ~= "" then local id = pfMap:GetMapIDByName(name) if id and pfDB["minimap"][id] then return pfDB["minimap"][id][1], pfDB["minimap"][id][2] end end end if GetMapInfo then local ok, info = pcall(GetMapInfo) if ok and info and ZONE_SIZES[info] then return ZONE_SIZES[info][1], ZONE_SIZES[info][2] end end return nil, nil end -------------------------------------------------------------------------------- -- 1. World Map + Battlefield Minimap: class icon overlays -------------------------------------------------------------------------------- local mapButtons local function InitMapButtons() if mapButtons then return end mapButtons = {} for i = 1, 4 do mapButtons["WorldMapParty" .. i] = "party" .. i mapButtons["BattlefieldMinimapParty" .. i] = "party" .. i end for i = 1, 40 do mapButtons["WorldMapRaid" .. i] = "raid" .. i mapButtons["BattlefieldMinimapRaid" .. i] = "raid" .. i end end local mapTickTime = 0 local function UpdateMapClassIcons() mapTickTime = mapTickTime + (arg1 or 0) if mapTickTime < 0.15 then return end mapTickTime = 0 if not mapButtons then InitMapButtons() end local _G = getfenv(0) for name, unit in pairs(mapButtons) do local frame = _G[name] if frame and frame:IsVisible() and UnitExists(unit) then local defIcon = _G[name .. "Icon"] if defIcon then defIcon:SetTexture() end if not frame.nanamiClassTex then frame.nanamiClassTex = frame:CreateTexture(nil, "OVERLAY") frame.nanamiClassTex:SetTexture(CLASS_ICON_PATH) frame.nanamiClassTex:SetPoint("TOPLEFT", frame, "TOPLEFT", 2, -2) frame.nanamiClassTex:SetPoint("BOTTOMRIGHT", frame, "BOTTOMRIGHT", -2, 2) end local _, class = UnitClass(unit) if class and CLASS_ICON_TCOORDS and CLASS_ICON_TCOORDS[class] then local tc = CLASS_ICON_TCOORDS[class] frame.nanamiClassTex:SetTexCoord(tc[1], tc[2], tc[3], tc[4]) frame.nanamiClassTex:Show() else frame.nanamiClassTex:Hide() end end end end -------------------------------------------------------------------------------- -- 2. Minimap: class-colored party dot overlays -------------------------------------------------------------------------------- local MAX_PARTY = 4 local mmDots = {} local function CreateMinimapDot(index) local dot = CreateFrame("Frame", "NanamiMMDot" .. index, Minimap) dot:SetWidth(10) dot:SetHeight(10) dot:SetFrameStrata("MEDIUM") dot:SetFrameLevel(Minimap:GetFrameLevel() + 5) dot.icon = dot:CreateTexture(nil, "ARTWORK") dot.icon:SetTexture("Interface\\Minimap\\UI-Minimap-Background") dot.icon:SetAllPoints() dot.icon:SetVertexColor(1, 0.82, 0, 1) dot:Hide() return dot end local mmTickTime = 0 local function UpdateMinimapDots() mmTickTime = mmTickTime + (arg1 or 0) if mmTickTime < 0.25 then return end mmTickTime = 0 if not Minimap or not Minimap:IsVisible() then for i = 1, MAX_PARTY do if mmDots[i] then mmDots[i]:Hide() end end return end local numParty = GetNumPartyMembers and GetNumPartyMembers() or 0 if numParty == 0 then for i = 1, MAX_PARTY do if mmDots[i] then mmDots[i]:Hide() end end return end if WorldMapFrame and WorldMapFrame:IsVisible() then return end if SetMapToCurrentZone then pcall(SetMapToCurrentZone) end local px, py = GetPlayerMapPosition("player") if not px or not py or (px == 0 and py == 0) then for i = 1, MAX_PARTY do if mmDots[i] then mmDots[i]:Hide() end end return end local zw, zh = GetZoneYards() if not zw or not zh or zw == 0 or zh == 0 then for i = 1, MAX_PARTY do if mmDots[i] then mmDots[i]:Hide() end end return end local now = GetTime() if now - indoorCheckTime > 3 then indoorCheckTime = now cachedIndoor = DetectIndoor() end local zoom = Minimap:GetZoom() local mmYards = MM_ZOOM[cachedIndoor] and MM_ZOOM[cachedIndoor][zoom] or MM_ZOOM[1][zoom] or 466.67 local mmHalfYards = mmYards / 2 local mmHalfPx = Minimap:GetWidth() / 2 local facing = 0 local doRotate = false local okCvar, rotateVal = pcall(GetCVar, "rotateMinimap") if okCvar and rotateVal == "1" and GetPlayerFacing then local ok2, f = pcall(GetPlayerFacing) if ok2 and f then facing = f doRotate = true end end for i = 1, MAX_PARTY do local unit = "party" .. i if i <= numParty and UnitExists(unit) and UnitIsConnected(unit) then local mx, my = GetPlayerMapPosition(unit) if mx and my and (mx ~= 0 or my ~= 0) then local dx = (mx - px) * zw local dy = (py - my) * zh if doRotate then local s = math.sin(facing) local c = math.cos(facing) dx, dy = dx * c + dy * s, -dx * s + dy * c end local dist = math.sqrt(dx * dx + dy * dy) if dist < mmHalfYards * 0.92 then local scale = mmHalfPx / mmHalfYards if not mmDots[i] then mmDots[i] = CreateMinimapDot(i) end local dot = mmDots[i] local _, class = UnitClass(unit) local cc = class and CLASS_COLORS and CLASS_COLORS[class] if cc then dot.icon:SetVertexColor(cc.r, cc.g, cc.b, 1) else dot.icon:SetVertexColor(1, 0.82, 0, 1) end dot:ClearAllPoints() dot:SetPoint("CENTER", Minimap, "CENTER", dx * scale, dy * scale) dot:Show() else if mmDots[i] then mmDots[i]:Hide() end end else if mmDots[i] then mmDots[i]:Hide() end end else if mmDots[i] then mmDots[i]:Hide() end end end end -------------------------------------------------------------------------------- -- Initialize -------------------------------------------------------------------------------- function MI:Initialize() InitMapButtons() local updater = CreateFrame("Frame", "NanamiMapIconsUpdater", UIParent) updater._elapsed = 0 updater:SetScript("OnUpdate", function() this._elapsed = (this._elapsed or 0) + arg1 if this._elapsed < 0.2 then return end this._elapsed = 0 UpdateMapClassIcons() UpdateMinimapDots() end) end