mirror of
https://github.com/brues-code/pfUI.git
synced 2026-11-01 16:36:56 +00:00
/radar toggles it and /radar <yards> sets the range; the right-click menu and the mouse wheel step through 5-40 yd. The map turns with GetPlayerFacing so ahead is up, and ClassicAPI.UnitPosition places class-colored dots (the mirrored name, since SuperWoW takes the global with x, y, z order). The range ring is two stacked TempPortraitAlphaMask discs: a ring-color disc under a slightly smaller background-color one. It turns red when a member is inside the range, and a list under the radar shows the five nearest with their raid markers inline. Raid markers come from the markN tokens, so marked mobs show too and a marked member shows the marker instead of a dot. Units behind terrain (UnitInLineOfSight, traced 4x a second) are dimmed. Units on another floor, over 5 yd up or down and steeper than 45 degrees so hills don't count, get an arrow above or below them. Hovering a blip shows its name, distance and both states; clicking a blip or a list row targets it.
374 lines
12 KiB
Lua
374 lines
12 KiB
Lua
pfUI:RegisterModule("radar", function ()
|
|
-- DBM-style range radar: the group around you, turned so that your facing
|
|
-- points up. ClassicAPI's UnitPosition reads every visible unit.
|
|
local SIZE = 150
|
|
local RADIUS = SIZE / 2
|
|
-- the range ring sits at two thirds of the radius, so the radar shows half
|
|
-- a range past it
|
|
local RING = RADIUS / 1.5
|
|
local RANGES = { 5, 8, 10, 15, 20, 25, 30, 40 }
|
|
-- about one storey; see Locate for what counts as another floor
|
|
local FLOOR = 5
|
|
-- alpha of units out of sight
|
|
local DIM = .3
|
|
-- the nearest members inside the range, listed under the radar
|
|
local LINES = 5
|
|
local LINE_HEIGHT = C.global.font_size + 4
|
|
|
|
local _, border = GetBorderSize()
|
|
local abs, sqrt = math.abs, math.sqrt
|
|
local UnitPosition = ClassicAPI.UnitPosition
|
|
|
|
local radar = CreateFrame("Frame", "pfRadar", UIParent)
|
|
radar:SetSize(SIZE, SIZE)
|
|
radar:SetPoint("CENTER", 350, 100)
|
|
radar:SetFrameStrata("MEDIUM")
|
|
radar:EnableMouse(true)
|
|
radar:EnableMouseWheel(true)
|
|
radar:Hide()
|
|
CreateBackdrop(radar)
|
|
CreateBackdropShadow(radar)
|
|
UpdateMovable(radar)
|
|
|
|
-- the range ring: a disc in the ring color under a 2px smaller disc in the
|
|
-- background color, which lets a little of the tint through
|
|
local DISC = "Interface\\CharacterFrame\\TempPortraitAlphaMask"
|
|
radar.ring = radar:CreateTexture(nil, "BACKGROUND")
|
|
radar.ring:SetTexture(DISC)
|
|
radar.ring:SetSize(RING * 2 + 2, RING * 2 + 2)
|
|
radar.ring:SetPoint("CENTER", 0, 0)
|
|
|
|
radar.zone = radar:CreateTexture(nil, "BORDER")
|
|
radar.zone:SetTexture(DISC)
|
|
radar.zone:SetSize(RING * 2 - 2, RING * 2 - 2)
|
|
radar.zone:SetPoint("CENTER", 0, 0)
|
|
local r, g, b = GetStringColor(C.appearance.border.background)
|
|
radar.zone:SetVertexColor(r, g, b, .85)
|
|
|
|
radar.player = radar:CreateTexture(nil, "OVERLAY")
|
|
radar.player:SetAtlas("MinimapArrow")
|
|
radar.player:SetSize(24, 24)
|
|
radar.player:SetPoint("CENTER", 0, 0)
|
|
|
|
radar.title = radar:CreateFontString(nil, "OVERLAY")
|
|
radar.title:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
radar.title:SetPoint("TOPLEFT", 4, -4)
|
|
|
|
radar.count = radar:CreateFontString(nil, "OVERLAY")
|
|
radar.count:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
radar.count:SetPoint("TOPRIGHT", -4, -4)
|
|
radar.count:SetTextColor(1, .2, .2)
|
|
|
|
radar.names = CreateFrame("Frame", nil, radar)
|
|
radar.names:SetPoint("TOPLEFT", radar, "BOTTOMLEFT", 0, -border * 3)
|
|
radar.names:SetPoint("TOPRIGHT", radar, "BOTTOMRIGHT", 0, -border * 3)
|
|
radar.names:Hide()
|
|
CreateBackdrop(radar.names)
|
|
CreateBackdropShadow(radar.names)
|
|
|
|
local range, scale
|
|
|
|
local function SetRange(yards)
|
|
range = yards
|
|
scale = RING / yards
|
|
C.radar.range = tostring(yards)
|
|
radar.title:SetText(T["Radar"] .. " (" .. yards .. " yd)")
|
|
end
|
|
SetRange(tonumber(C.radar.range))
|
|
|
|
local function IsRange(yards)
|
|
return yards == range
|
|
end
|
|
|
|
local function GenerateMenu(owner, rootDescription)
|
|
rootDescription:CreateTitle(T["Radar"])
|
|
for _, yards in ipairs(RANGES) do
|
|
rootDescription:CreateRadio(yards .. " yd", IsRange, SetRange, yards)
|
|
end
|
|
rootDescription:CreateDivider()
|
|
rootDescription:CreateButton(HIDE, function() radar:Hide() end)
|
|
end
|
|
|
|
radar.menuMixin = pfUIMenuStyleMixin
|
|
local function OpenMenu()
|
|
MenuUtil.CreateContextMenu(radar, GenerateMenu)
|
|
end
|
|
|
|
-- blips and list rows: left click targets, right click opens the menu
|
|
local function TargetOrMenu()
|
|
if arg1 == "RightButton" then
|
|
OpenMenu()
|
|
else
|
|
TargetUnit(this.unit)
|
|
end
|
|
end
|
|
|
|
-- unit -> true while terrain blocks the line of sight
|
|
local blocked = {}
|
|
local hovered
|
|
|
|
local function ShowTooltip(blip)
|
|
local unit = blip.unit
|
|
GameTooltip:SetOwner(blip, "ANCHOR_RIGHT")
|
|
if UnitIsPlayer(unit) then
|
|
GameTooltip:SetText(UnitName(unit), PFUI_CLASS_COLORS[UnitClassBase(unit)]:GetRGB())
|
|
else
|
|
GameTooltip:SetText(UnitName(unit), UnitSelectionColor(unit))
|
|
end
|
|
GameTooltip:AddLine(format("%.1f yd", sqrt(blip.distSq)), 1, 1, 1)
|
|
if blocked[unit] then
|
|
GameTooltip:AddLine(T["Out of line of sight"], 1, .3, .3)
|
|
end
|
|
if blip.level then
|
|
GameTooltip:AddLine(blip.level > 0 and T["Above you"] or T["Below you"], .6, .6, .6)
|
|
end
|
|
GameTooltip:Show()
|
|
end
|
|
|
|
-- a unit on the radar: hover for its tooltip, click to target it
|
|
local function CreateBlip(size, layer)
|
|
local blip = CreateFrame("Button", nil, radar)
|
|
blip:SetSize(size, size)
|
|
blip:SetFrameLevel(radar:GetFrameLevel() + layer)
|
|
blip:SetHitRectInsets(-2, -2, -2, -2)
|
|
blip:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
|
blip:Hide()
|
|
|
|
blip.icon = blip:CreateTexture(nil, "ARTWORK")
|
|
blip.icon:SetAllPoints(blip)
|
|
|
|
-- sits above or below a unit on another floor; the triangle fills the
|
|
-- middle half of img\up and img\down
|
|
blip.arrow = blip:CreateTexture(nil, "OVERLAY")
|
|
blip.arrow:SetSize(10, 5)
|
|
|
|
blip:SetScript("OnClick", TargetOrMenu)
|
|
blip:SetScript("OnEnter", function()
|
|
hovered = this
|
|
ShowTooltip(this)
|
|
end)
|
|
blip:SetScript("OnLeave", function()
|
|
hovered = nil
|
|
GameTooltip:Hide()
|
|
end)
|
|
return blip
|
|
end
|
|
|
|
local dots = {}
|
|
local function GetDot(i)
|
|
if not dots[i] then
|
|
dots[i] = CreateBlip(8, 1)
|
|
dots[i].icon:SetTexture(pfUI.media["img:circleraid"])
|
|
end
|
|
return dots[i]
|
|
end
|
|
|
|
-- above the dots
|
|
local markers = {}
|
|
for i = 1, 8 do
|
|
markers[i] = CreateBlip(14, 2)
|
|
markers[i].icon:SetAtlas("UI-RaidTargetingIcon_" .. i)
|
|
end
|
|
|
|
local rows = {}
|
|
for i = 1, LINES do
|
|
local row = CreateFrame("Button", nil, radar.names)
|
|
row:SetPoint("TOPLEFT", 4, -2 - (i - 1) * LINE_HEIGHT)
|
|
row:SetPoint("TOPRIGHT", -4, -2 - (i - 1) * LINE_HEIGHT)
|
|
row:SetHeight(LINE_HEIGHT)
|
|
row:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
|
row:SetScript("OnClick", TargetOrMenu)
|
|
|
|
row.name = row:CreateFontString(nil, "OVERLAY")
|
|
row.name:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
row.name:SetPoint("LEFT", 0, 0)
|
|
row.dist = row:CreateFontString(nil, "OVERLAY")
|
|
row.dist:SetFont(pfUI.font_default, C.global.font_size, "OUTLINE")
|
|
row.dist:SetPoint("RIGHT", 0, 0)
|
|
rows[i] = row
|
|
end
|
|
|
|
-- the LINES nearest members inside the range, nearest first
|
|
local nearUnit, nearDistSq = {}, {}
|
|
local function AddNear(listed, unit, distSq)
|
|
if listed == LINES and distSq >= nearDistSq[LINES] then return listed end
|
|
local i = listed < LINES and listed + 1 or LINES
|
|
while i > 1 and nearDistSq[i - 1] > distSq do
|
|
nearUnit[i], nearDistSq[i] = nearUnit[i - 1], nearDistSq[i - 1]
|
|
i = i - 1
|
|
end
|
|
nearUnit[i], nearDistSq[i] = unit, distSq
|
|
return listed < LINES and listed + 1 or LINES
|
|
end
|
|
|
|
-- the player's position and facing, read once per refresh
|
|
local py, px, pz, facing
|
|
|
|
-- where a unit shows on the radar in pixels, its squared distance, and 1
|
|
-- or -1 on a floor above or below you. nil past the edge. The world turns
|
|
-- clockwise by the facing, so ahead is up.
|
|
local function Locate(unit)
|
|
if not py or not facing then return end
|
|
if UnitIsUnit(unit, "player") or UnitIsDeadOrGhost(unit) then return end
|
|
|
|
local uy, ux, uz = UnitPosition(unit)
|
|
if not uy then return end
|
|
|
|
local west, north, up = uy - py, ux - px, uz - pz
|
|
local x, y = Vector2D_RotateDirection(-facing, -west, north)
|
|
x, y = x * scale, y * scale
|
|
if abs(x) >= RADIUS or abs(y) >= RADIUS then return end
|
|
|
|
-- another floor is a storey up or down and steeper than 45 degrees, which
|
|
-- no walkable slope is, so units up a hill don't count
|
|
local level
|
|
if abs(up) > FLOOR and up * up > west * west + north * north then
|
|
level = up > 0 and 1 or -1
|
|
end
|
|
return x, y, Vector3D_GetLengthSquared(west, north, up), level
|
|
end
|
|
|
|
local function Place(blip, unit, x, y, distSq, level, checkLoS)
|
|
if checkLoS then blocked[unit] = UnitInLineOfSight(unit) == false end
|
|
blip.unit, blip.distSq, blip.level = unit, distSq, level
|
|
blip:SetPoint("CENTER", x, y)
|
|
blip:SetAlpha(blocked[unit] and DIM or 1)
|
|
if level then
|
|
blip.arrow:SetTexture(pfUI.media[level > 0 and "img:up" or "img:down"])
|
|
blip.arrow:SetTexCoord(0, 1, .25, .75)
|
|
blip.arrow:ClearAllPoints()
|
|
if level > 0 then
|
|
blip.arrow:SetPoint("BOTTOM", blip, "TOP", 0, 1)
|
|
else
|
|
blip.arrow:SetPoint("TOP", blip, "BOTTOM", 0, -1)
|
|
end
|
|
end
|
|
blip.arrow:SetShown(level ~= nil)
|
|
blip:Show()
|
|
end
|
|
|
|
function radar:Refresh(checkLoS)
|
|
py, px, pz = UnitPosition("player")
|
|
facing = GetPlayerFacing()
|
|
local shown, inRange, listed = 0, 0, 0
|
|
|
|
local raid = IsInRaid()
|
|
local prefix = raid and "raid" or "party"
|
|
for i = 1, raid and GetNumRaidMembers() or GetNumPartyMembers() do
|
|
local unit = prefix .. i
|
|
local x, y, distSq, level = Locate(unit)
|
|
if x then
|
|
-- inside the range always means inside the radar
|
|
if distSq <= range * range then
|
|
inRange = inRange + 1
|
|
listed = AddNear(listed, unit, distSq)
|
|
end
|
|
|
|
-- a marked member shows the marker instead of a dot
|
|
if not GetRaidTargetIndex(unit) then
|
|
shown = shown + 1
|
|
local dot = GetDot(shown)
|
|
dot.icon:SetVertexColor(PFUI_CLASS_COLORS[UnitClassBase(unit)]:GetRGB())
|
|
Place(dot, unit, x, y, distSq, level, checkLoS)
|
|
end
|
|
end
|
|
end
|
|
|
|
for i = shown + 1, #dots do dots[i]:Hide() end
|
|
|
|
-- markN resolves to whatever unit wears marker N, mobs included
|
|
for i = 1, 8 do
|
|
local unit = "mark" .. i
|
|
local x, y, distSq, level = Locate(unit)
|
|
if x then
|
|
Place(markers[i], unit, x, y, distSq, level, checkLoS)
|
|
else
|
|
markers[i]:Hide()
|
|
end
|
|
end
|
|
|
|
for i = 1, LINES do
|
|
local row = rows[i]
|
|
if i <= listed then
|
|
local unit = nearUnit[i]
|
|
local marker = GetRaidTargetIndex(unit)
|
|
local name = UnitName(unit)
|
|
if marker then
|
|
-- the markup's own tint is white, so the text color leaves it alone
|
|
name = name .. " " .. CreateAtlasMarkup("UI-RaidTargetingIcon_" .. marker, 14, 14)
|
|
end
|
|
row.name:SetText(name)
|
|
row.name:SetTextColor(PFUI_CLASS_COLORS[UnitClassBase(unit)]:GetRGB())
|
|
row.dist:SetFormattedText("%.1f", sqrt(nearDistSq[i]))
|
|
row.unit = unit
|
|
end
|
|
row:SetShown(i <= listed)
|
|
end
|
|
self.names:SetHeight(listed * LINE_HEIGHT + 4)
|
|
self.names:SetShown(listed > 0)
|
|
|
|
if inRange > 0 then
|
|
self.ring:SetVertexColor(1, .2, .2)
|
|
self.count:SetText(inRange)
|
|
else
|
|
self.ring:SetVertexColor(.2, 1, .2)
|
|
self.count:SetText("")
|
|
end
|
|
|
|
-- keep the hovered tooltip's distance current
|
|
if hovered then
|
|
if hovered:IsShown() then
|
|
ShowTooltip(hovered)
|
|
else
|
|
hovered = nil
|
|
GameTooltip:Hide()
|
|
end
|
|
end
|
|
end
|
|
|
|
-- line of sight traces run at a slower rate than the positions
|
|
local throttle, losThrottle = 0, 1
|
|
radar:SetScript("OnUpdate", function()
|
|
throttle = throttle + arg1
|
|
losThrottle = losThrottle + arg1
|
|
if throttle < .05 then return end
|
|
throttle = 0
|
|
|
|
local checkLoS = losThrottle >= .25
|
|
if checkLoS then losThrottle = 0 end
|
|
this:Refresh(checkLoS)
|
|
end)
|
|
|
|
radar:SetScript("OnMouseUp", function()
|
|
if arg1 == "RightButton" then OpenMenu() end
|
|
end)
|
|
|
|
-- the wheel steps through the ranges, up zooms in
|
|
radar:SetScript("OnMouseWheel", function()
|
|
local yards = range
|
|
if arg1 > 0 then
|
|
for i = #RANGES, 1, -1 do
|
|
if RANGES[i] < range then yards = RANGES[i] break end
|
|
end
|
|
else
|
|
for i = 1, #RANGES do
|
|
if RANGES[i] > range then yards = RANGES[i] break end
|
|
end
|
|
end
|
|
SetRange(yards)
|
|
end)
|
|
|
|
-- /radar toggles, /radar <yards> sets the range and shows
|
|
pfUI.api.RegisterSlashCommand("PFRADAR", { "/radar", "/pfradar" }, function(msg)
|
|
local yards = tonumber(msg)
|
|
if yards and yards > 0 then
|
|
SetRange(yards)
|
|
radar:Show()
|
|
else
|
|
radar:SetShown(not radar:IsShown())
|
|
end
|
|
end)
|
|
|
|
pfUI.radar = radar
|
|
end)
|