You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

986 lines
33 KiB

---------------
-- Globals --
---------------
DBM.RangeCheck = {}
--------------
-- Locals --
--------------
local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)
local isClassic = WOW_PROJECT_ID == (WOW_PROJECT_CLASSIC or 2)
local function UnitPhaseReasonHack(uId)
if isRetail then
return not UnitPhaseReason(uId)
end
return UnitInPhase(uId)
end
local L = DBM_CORE_L
local rangeCheck = DBM.RangeCheck
local mainFrame = CreateFrame("Frame")
local textFrame, radarFrame, updateIcon, updateRangeFrame, initializeDropdown
local RAID_CLASS_COLORS = _G["CUSTOM_CLASS_COLORS"] or RAID_CLASS_COLORS -- For Phanx' Class Colors
-- Function for automatically converting inputed ranges from old mods to be ones that have valid item/api checks
local function setCompatibleRestrictedRange(range)
if range <= 4 and isRetail then
return 4
elseif range <= 6 and not isClassic then
return 6
elseif range <= 8 then
return 8
elseif range <= 10 then
return 10
elseif range <= 11 then
return 11
elseif range <= 13 then
return 13
elseif range <= 18 then
return 18
elseif range <= 23 then
return 23
elseif range <= 28 then
return 28
elseif range <= 30 and isRetail then
return 30
elseif range <= 33 then
return 33
elseif range <= 43 and not isClassic then
return 43
elseif range <= 48 and not isClassic then
return 48
elseif range <= 53 and isRetail then
return 53
elseif range <= 60 and not isClassic then
return 60
elseif range <= 80 and isRetail then
return 80
end
end
local itsBCAgain
do
local CheckInteractDistance, IsItemInRange, UnitInRange = CheckInteractDistance, IsItemInRange, UnitInRange
-- All ranges are tested and compared against UnitDistanceSquared.
-- Example: Worgsaw has a tooltip of 6 but doesn't factor in hitboxes/etc. It doesn't return false until UnitDistanceSquared of 8.
local itemRanges = {
[8] = 8149, -- Voodoo Charm
[13] = isClassic and 17626 or 32321, -- Sparrowhawk Net / Frostwolf Muzzle
[18] = 6450, -- Silk Bandage
[23] = 21519, -- Mistletoe
[28] = 13289,--Egan's Blaster
[33] = 1180, -- Scroll of Stamina
}
if isRetail then
itemRanges[4] = 90175 -- Gin-Ji Knife Set (MoP)
itemRanges[53] = 116139 -- Haunting Memento (WoD)
itemRanges[80] = 35278 -- Reinforced Net (WotLK)
end
if not isClassic then -- Exists in BCC
itemRanges[6] = 16114 -- Foremans Blackjack (TBC)
itemRanges[43] = 34471 -- Vial of the Sunwell (UnitInRange api alternate if item checks break)
itemRanges[48] = 32698 -- Wrangling Rope
itemRanges[60] = 32825 -- Soul Cannon
end
local apiRanges = {
[10] = 3, -- CheckInteractDistance (Duel)
[11] = 2, -- CheckInteractDistance (Trade)
}
if isRetail then
apiRanges[30] = 1 -- CheckInteractDistance (Inspect), Classic: Inspect range is 10
end
function itsBCAgain(uId, checkrange)
if checkrange then -- Specified range, this check only cares whether unit is within specific range
if not isRetail and checkrange == 43 then -- Only classic/BCC uses UnitInRange so only classic has this check, TBC+ can use Vial of the Sunwell
return UnitInRange(uId) and checkrange or 1000
elseif itemRanges[checkrange] then -- Only query item range for requested active range check
return IsItemInRange(itemRanges[checkrange], uId) and checkrange or 1000
elseif apiRanges[checkrange] then -- Only query item range for requested active range if no item found for it
return CheckInteractDistance(uId, apiRanges[checkrange]) and checkrange or 1000
else
return 1000 -- Just so it has a numeric value, even if it's unknown to protect from nil errors
end
else -- No range passed, this is being used by a getDistanceBetween function that needs to calculate precise distances of members of raid (well as precise as possible with a crappy api)
if isRetail and IsItemInRange(90175, uId) then return 4
elseif not isClassic and IsItemInRange(16114, uId) then return 6
elseif IsItemInRange(8149, uId) then return 8
elseif CheckInteractDistance(uId, 3) then return 10
elseif CheckInteractDistance(uId, 2) then return 11
elseif IsItemInRange(isClassic and 17626 or 32321 , uId) then return 13
elseif IsItemInRange(6450, uId) then return 18
elseif IsItemInRange(21519, uId) then return 23
elseif IsItemInRange(13289, uId) then return 28
elseif isRetail and CheckInteractDistance(uId, 1) then return 30
elseif IsItemInRange(1180, uId) then return 33
elseif not isClassic and UnitInRange(uId) then return 43
elseif not isClassic and IsItemInRange(32698, uId) then return 48
elseif isRetail and IsItemInRange(116139, uId) then return 53
elseif not isClassic and IsItemInRange(32825, uId) then return 60
elseif isRetail and IsItemInRange(35278, uId) then return 80
else return 1000 end -- Just so it has a numeric value, even if it's unknown to protect from nil errors
end
end
end
---------------------
-- Dropdown Menu --
---------------------
do
local sound0 = "none"
local sound1 = "Interface\\AddOns\\DBM-Core\\Sounds\\blip_8.ogg"
local sound2 = "Interface\\AddOns\\DBM-Core\\Sounds\\alarmclockbeeps.ogg"
local function setSound(self, option, sound)
DBM.Options[option] = sound
if sound ~= "none" then
DBM:PlaySoundFile(sound)
end
end
local function setRange(self, range)
rangeCheck:Hide(true)
rangeCheck:Show(range, mainFrame.filter, true, mainFrame.redCircleNumPlayers or 1)
end
local function setThreshold(self, threshold)
rangeCheck:Hide(true)
rangeCheck:Show(mainFrame.range, mainFrame.filter, true, threshold)
end
local function setFrames(self, option)
DBM.Options.RangeFrameFrames = option
rangeCheck:Hide(true)
rangeCheck:Show(mainFrame.range, mainFrame.filter, true, mainFrame.redCircleNumPlayers or 1)
end
local function toggleLocked()
DBM.Options.RangeFrameLocked = not DBM.Options.RangeFrameLocked
end
function initializeDropdown(_, level, menu)
local info
if level == 1 then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "range"
UIDropDownMenu_AddButton(info, 1)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETTHRESHOLD
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "threshold"
UIDropDownMenu_AddButton(info, 1)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SOUNDS
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "sounds"
UIDropDownMenu_AddButton(info, 1)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_OPTION_FRAMES
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "frames"
UIDropDownMenu_AddButton(info, 1)
info = UIDropDownMenu_CreateInfo()
info.text = LOCK_FRAME
if DBM.Options.RangeFrameLocked then
info.checked = true
end
info.func = toggleLocked
UIDropDownMenu_AddButton(info, 1)
info = UIDropDownMenu_CreateInfo()
info.text = HIDE
info.notCheckable = true
info.func = function() rangeCheck:Hide(true) end
info.arg1 = rangeCheck
UIDropDownMenu_AddButton(info, 1)
elseif level == 2 then
if menu == "range" then
if isRetail then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(4)
info.func = setRange
info.arg1 = 4
info.checked = (mainFrame.range == 4)
UIDropDownMenu_AddButton(info, 2)
end
if not isClassic then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(6)
info.func = setRange
info.arg1 = 6
info.checked = (mainFrame.range == 6)
UIDropDownMenu_AddButton(info, 2)
end
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(8)
info.func = setRange
info.arg1 = 8
info.checked = (mainFrame.range == 8)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(10)
info.func = setRange
info.arg1 = 10
info.checked = (mainFrame.range == 10)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(13)
info.func = setRange
info.arg1 = 13
info.checked = (mainFrame.range == 13)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(18)
info.func = setRange
info.arg1 = 18
info.checked = (mainFrame.range == 18)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(23)
info.func = setRange
info.arg1 = 23
info.checked = (mainFrame.range == 23)
UIDropDownMenu_AddButton(info, 2)
if isRetail then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(30)
info.func = setRange
info.arg1 = 30
info.checked = (mainFrame.range == 30)
UIDropDownMenu_AddButton(info, 2)
end
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(33)
info.func = setRange
info.arg1 = 33
info.checked = (mainFrame.range == 33)
UIDropDownMenu_AddButton(info, 2)
if not isClassic then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SETRANGE_TO:format(43)
info.func = setRange
info.arg1 = 43
info.checked = (mainFrame.range == 43)
UIDropDownMenu_AddButton(info, 2)
end
elseif menu == "threshold" then
info = UIDropDownMenu_CreateInfo()
info.text = 1
info.func = setThreshold
info.arg1 = 1
info.checked = (mainFrame.redCircleNumPlayers == 1)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = 2
info.func = setThreshold
info.arg1 = 2
info.checked = (mainFrame.redCircleNumPlayers == 2)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = 3
info.func = setThreshold
info.arg1 = 3
info.checked = (mainFrame.redCircleNumPlayers == 3)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = 4
info.func = setThreshold
info.arg1 = 4
info.checked = (mainFrame.redCircleNumPlayers == 4)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = 5
info.func = setThreshold
info.arg1 = 5
info.checked = (mainFrame.redCircleNumPlayers == 5)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = 6
info.func = setThreshold
info.arg1 = 6
info.checked = (mainFrame.redCircleNumPlayers == 6)
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = 8
info.func = setThreshold
info.arg1 = 8
info.checked = (mainFrame.redCircleNumPlayers == 8)
UIDropDownMenu_AddButton(info, 2)
elseif menu == "sounds" then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SOUND_OPTION_1
info.notCheckable = true
info.hasArrow = true
info.menuList = "RangeFrameSound1"
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SOUND_OPTION_2
info.notCheckable = true
info.hasArrow = true
info.menuList = "RangeFrameSound2"
UIDropDownMenu_AddButton(info, 2)
elseif menu == "frames" then
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_OPTION_TEXT
info.func = setFrames
info.arg1 = "text"
info.checked = (DBM.Options.RangeFrameFrames == "text")
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_OPTION_RADAR
info.func = setFrames
info.arg1 = "radar"
info.checked = (DBM.Options.RangeFrameFrames == "radar")
UIDropDownMenu_AddButton(info, 2)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_OPTION_BOTH
info.func = setFrames
info.arg1 = "both"
info.checked = (DBM.Options.RangeFrameFrames == "both")
UIDropDownMenu_AddButton(info, 2)
end
elseif level == 3 then
local option = menu
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SOUND_0
info.func = setSound
info.arg1 = option
info.arg2 = sound0
info.checked = (DBM.Options[option] == sound0)
UIDropDownMenu_AddButton(info, 3)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SOUND_1
info.func = setSound
info.arg1 = option
info.arg2 = sound1
info.checked = (DBM.Options[option] == sound1)
UIDropDownMenu_AddButton(info, 3)
info = UIDropDownMenu_CreateInfo()
info.text = L.RANGECHECK_SOUND_2
info.func = setSound
info.arg1 = option
info.arg2 = sound2
info.checked = (DBM.Options[option] == sound2)
UIDropDownMenu_AddButton(info, 3)
end
end
end
-----------------
-- Play Sounds --
-----------------
local updateSound
local soundUpdate = 0
do
local UnitAffectingCombat = UnitAffectingCombat
function updateSound(num)
if not UnitAffectingCombat("player") or (GetTime() - soundUpdate) < 5 then
return
end
soundUpdate = GetTime()
if num == 1 then
if DBM.Options.RangeFrameSound1 ~= "none" then
DBM:PlaySoundFile(DBM.Options.RangeFrameSound1)
end
elseif num > 1 then
if DBM.Options.RangeFrameSound2 ~= "none" then
DBM:PlaySoundFile(DBM.Options.RangeFrameSound2)
end
end
end
end
------------------------
-- Create the frame --
------------------------
local function createTextFrame()
textFrame = CreateFrame("Frame", "DBMRangeCheck", UIParent, "BackdropTemplate")
textFrame:SetFrameStrata("DIALOG")
textFrame.backdropInfo = {
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",--131071
tile = true,
tileSize = 16
}
textFrame:ApplyBackdrop()
textFrame:SetPoint(DBM.Options.RangeFramePoint, UIParent, DBM.Options.RangeFramePoint, DBM.Options.RangeFrameX, DBM.Options.RangeFrameY)
textFrame:SetSize(128, 12)
textFrame:SetClampedToScreen(true)
textFrame:EnableMouse(true)
textFrame:SetToplevel(true)
textFrame:SetMovable(true)
textFrame:RegisterForDrag("LeftButton")
textFrame:SetScript("OnDragStart", function(self)
if not DBM.Options.RangeFrameLocked then
self:StartMoving()
end
end)
textFrame:SetScript("OnDragStop", function(self)
self:StopMovingOrSizing()
local point, _, _, x, y = self:GetPoint(1)
DBM.Options.RangeFrameX = x
DBM.Options.RangeFrameY = y
DBM.Options.RangeFramePoint = point
end)
textFrame:SetScript("OnMouseDown", function(_, button)
if button == "RightButton" then
local dropdownFrame = CreateFrame("Frame", "DBMRangeCheckDropdown", UIParent)
UIDropDownMenu_Initialize(dropdownFrame, initializeDropdown)
ToggleDropDownMenu(1, nil, dropdownFrame, "cursor", 5, -10)
end
end)
local text = textFrame:CreateFontString(nil, "OVERLAY", "GameTooltipText")
text:SetSize(128, 15)
text:SetPoint("BOTTOMLEFT", textFrame, "TOPLEFT")
text:SetTextColor(1, 1, 1, 1)
text:Show()
text.OldSetText = text.SetText
text.SetText = function(self, text)
self:OldSetText(text)
self:SetWidth(0) -- Set the text width to 0, so the system can auto-calculate the size
end
textFrame.text = text
local inRangeText = textFrame:CreateFontString(nil, "OVERLAY", "GameTooltipText")
inRangeText:SetSize(128, 15)
inRangeText:SetPoint("TOPLEFT", textFrame, "BOTTOMLEFT")
inRangeText:SetTextColor(1, 1, 1, 1)
inRangeText:Hide()
textFrame.inRangeText = inRangeText
textFrame.lines = {}
for i = 1, 5 do
local line = textFrame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
line:SetSize(128, 12)
line:SetJustifyH("LEFT")
if i == 1 then
line:SetPoint("TOPLEFT", textFrame, "TOPLEFT", 6, -6)
else
line:SetPoint("TOPLEFT", textFrame.lines[i - 1], "LEFT", 0, -6)
end
textFrame.lines[i] = line
end
textFrame:Hide()
end
local function createRadarFrame()
radarFrame = CreateFrame("Frame", "DBMRangeCheckRadar", UIParent)
radarFrame:SetFrameStrata("DIALOG")
radarFrame:SetPoint(DBM.Options.RangeFrameRadarPoint, UIParent, DBM.Options.RangeFrameRadarPoint, DBM.Options.RangeFrameRadarX, DBM.Options.RangeFrameRadarY)
radarFrame:SetSize(128, 128)
radarFrame:SetClampedToScreen(true)
radarFrame:EnableMouse(true)
radarFrame:SetToplevel(true)
radarFrame:SetMovable(true)
radarFrame:RegisterForDrag("LeftButton")
radarFrame:SetScript("OnDragStart", function(self)
if not DBM.Options.RangeFrameLocked then
self:StartMoving()
end
end)
radarFrame:SetScript("OnDragStop", function(self)
self:StopMovingOrSizing()
local point, _, _, x, y = self:GetPoint(1)
DBM.Options.RangeFrameRadarX = x
DBM.Options.RangeFrameRadarY = y
DBM.Options.RangeFrameRadarPoint = point
end)
radarFrame:SetScript("OnMouseDown", function(_, button)
if button == "RightButton" then
local dropdownFrame = CreateFrame("Frame", "DBMRangeCheckDropdown", UIParent)
UIDropDownMenu_Initialize(dropdownFrame, initializeDropdown)
ToggleDropDownMenu(1, nil, dropdownFrame, "cursor", 5, -10)
end
end)
local bg = radarFrame:CreateTexture(nil, "BACKGROUND")
bg:SetAllPoints(radarFrame)
bg:SetBlendMode("BLEND")
bg:SetColorTexture(0, 0, 0, 0.3)
radarFrame.background = bg
local circle = radarFrame:CreateTexture(nil, "ARTWORK")
circle:SetSize(85, 85)
circle:SetPoint("CENTER")
circle:SetTexture("Interface\\AddOns\\DBM-Core\\textures\\radar_circle.blp")
circle:SetVertexColor(0, 1, 0)
circle:SetBlendMode("ADD")
radarFrame.circle = circle
local player = radarFrame:CreateTexture(nil, "OVERLAY")
player:SetSize(32, 32)
player:SetTexture(136431) -- "Interface\\Minimap\\MinimapArrow.blp"
player:SetBlendMode("ADD")
player:SetPoint("CENTER")
local text = radarFrame:CreateFontString(nil, "OVERLAY", "GameTooltipText")
text:SetSize(128, 15)
text:SetPoint("BOTTOMLEFT", radarFrame, "TOPLEFT")
text:SetTextColor(1, 1, 1, 1)
text:Show()
radarFrame.text = text
local inRangeText = radarFrame:CreateFontString(nil, "OVERLAY", "GameTooltipText")
inRangeText:SetSize(128, 15)
inRangeText:SetPoint("TOPLEFT", radarFrame, "BOTTOMLEFT")
inRangeText:SetTextColor(1, 1, 1, 1)
inRangeText:Hide()
radarFrame.inRangeText = inRangeText
radarFrame.dots = {}
for i = 1, 40 do
local dot = radarFrame:CreateTexture(nil, "OVERLAY")
dot:SetSize(24, 24)
dot:SetTexture(249183) -- "Interface\\Minimap\\PartyRaidBlips"
dot:Hide()
radarFrame.dots[i] = dot
end
radarFrame:Hide()
end
----------------
-- OnUpdate --
----------------
do
local UnitExists, UnitIsUnit, UnitIsDeadOrGhost, UnitIsConnected, GetPlayerFacing, UnitClass, IsInRaid, GetNumGroupMembers, GetRaidTargetIndex, GetBestMapForUnit = UnitExists, UnitIsUnit, UnitIsDeadOrGhost, UnitIsConnected, GetPlayerFacing, UnitClass, IsInRaid, GetNumGroupMembers, GetRaidTargetIndex, C_Map.GetBestMapForUnit
local max, min, sin, cos, pi2 = math.max, math.min, math.sin, math.cos, math.pi * 2
local circleColor, rotation, pixelsperyard, activeDots, prevRange, prevThreshold, prevNumClosePlayer, prevclosestRange, prevColor, prevType = 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
local unitList = {}
local BLIP_TEX_COORDS = {
["WARRIOR"] = { 0, 0.125, 0, 0.25 },
["PALADIN"] = { 0.125, 0.25, 0, 0.25 },
["HUNTER"] = { 0.25, 0.375, 0, 0.25 },
["ROGUE"] = { 0.375, 0.5, 0, 0.25 },
["PRIEST"] = { 0.5, 0.625, 0, 0.25 },
["DEATHKNIGHT"] = { 0.625, 0.75, 0, 0.25 },
["SHAMAN"] = { 0.75, 0.875, 0, 0.25 },
["MAGE"] = { 0.875, 1, 0, 0.25 },
["WARLOCK"] = { 0, 0.125, 0.25, 0.5 },
["DRUID"] = { 0.25, 0.375, 0.25, 0.5 },
["MONK"] = { 0.125, 0.25, 0.25, 0.5 },
["DEMONHUNTER"] = { 0.375, 0.5, 0.25, 0.5 },
["EVOKER"] = { 0, 0.125, 0, 0.25 }, -- Uses the same as WARRIOR, because that's what Blizzard is doing currently
}
local function setDot(id, sinTheta, cosTheta)
local dot = radarFrame.dots[id]
if dot.range < (mainFrame.range * 1.5) then -- If person is closer than 1.5 * range, show the dot. Else hide it
dot:ClearAllPoints()
dot:SetPoint("CENTER", radarFrame, "CENTER", ((dot.x * cosTheta) - (-dot.y * sinTheta)) * pixelsperyard, ((dot.x * sinTheta) + (-dot.y * cosTheta)) * pixelsperyard)
dot:Show()
elseif dot:IsShown() then
dot:Hide()
end
end
function updateIcon()
local numPlayers = GetNumGroupMembers()
activeDots = max(numPlayers, activeDots)
for i = 1, activeDots do
local dot = radarFrame.dots[i]
if i <= numPlayers then
unitList[i] = IsInRaid() and "raid" .. i or "party" .. i
local _, class = UnitClass(unitList[i])
local icon = GetRaidTargetIndex(unitList[i])
dot.class = class
if icon and icon < 9 then
dot.icon = icon
dot:SetTexture(13700 .. icon) -- "Interface\\TargetingFrame\\UI-RaidTargetingIcon_" .. icon
dot:SetTexCoord(0, 1, 0, 1)
dot:SetSize(16, 16)
dot:SetDrawLayer("OVERLAY", 1)
else
dot.icon = nil
class = class or "PRIEST"
dot:SetTexture(249183) -- "Interface\\Minimap\\PartyRaidBlips"
dot:SetTexCoord(BLIP_TEX_COORDS[class][1], BLIP_TEX_COORDS[class][2], BLIP_TEX_COORDS[class][3], BLIP_TEX_COORDS[class][4])
dot:SetSize(24, 24)
dot:SetDrawLayer("OVERLAY", 0)
end
elseif dot:IsShown() then
dot:Hide()
end
end
end
function updateRangeFrame()
if mainFrame.hideTime > 0 and GetTime() > mainFrame.hideTime then
rangeCheck:Hide()
return
end
local activeRange = mainFrame.range
local restricted = mainFrame.restrictions
local tEnabled = textFrame:IsShown()
local rEnabled = radarFrame:IsShown()
local reverse = mainFrame.reverse
local warnThreshold = mainFrame.redCircleNumPlayers
if tEnabled then
for i = 1, 5 do
textFrame.lines[i]:SetText("")
textFrame.lines[i]:Hide()
end
if reverse then
if warnThreshold > 1 then
textFrame.text:SetText(L.RANGECHECK_RHEADERT:format(activeRange, warnThreshold))
else
textFrame.text:SetText(L.RANGECHECK_RHEADER:format(activeRange))
end
else
if warnThreshold > 1 then
textFrame.text:SetText(L.RANGECHECK_HEADERT:format(activeRange, warnThreshold))
else
textFrame.text:SetText(L.RANGECHECK_HEADER:format(activeRange))
end
end
end
if rEnabled and (prevRange ~= activeRange or prevThreshold ~= mainFrame.redCircleNumPlayers) then
prevRange = activeRange
pixelsperyard = min(radarFrame:GetWidth(), radarFrame:GetHeight()) / (activeRange * 3)
radarFrame.circle:SetSize(activeRange * pixelsperyard * 2, activeRange * pixelsperyard * 2)
if reverse then
radarFrame.text:SetText(L.RANGERADAR_RHEADER:format(activeRange, mainFrame.redCircleNumPlayers))
else
radarFrame.text:SetText(L.RANGERADAR_HEADER:format(activeRange, mainFrame.redCircleNumPlayers))
end
end
local playerMapId = GetBestMapForUnit("player") or 0
if not restricted then
rotation = pi2 - (GetPlayerFacing() or 0)
end
local sinTheta = sin(rotation)
local cosTheta = cos(rotation)
local closePlayer = 0
local closestRange
local closetName
local filter = mainFrame.filter
local type = reverse and 2 or filter and 1 or 0
local onlySummary = mainFrame.onlySummary
for i = 1, GetNumGroupMembers() do
local uId = unitList[i]
local dot = radarFrame.dots[i]
local mapId = GetBestMapForUnit(uId) or 0
if UnitExists(uId) and playerMapId == mapId and not UnitIsUnit(uId, "player") and not UnitIsDeadOrGhost(uId) and UnitIsConnected(uId) and UnitPhaseReasonHack(uId) and (not filter or filter(uId)) then
local range = restricted and itsBCAgain(uId, activeRange) or UnitDistanceSquared(uId) ^ 0.5
local inRange = false
if range < activeRange + 0.5 then
closePlayer = closePlayer + 1
inRange = true
if rEnabled then -- Only used by radar
if not closestRange then
closestRange = range
elseif range < closestRange then
closestRange = range
end
end
if not closetName then
closetName = DBM:GetUnitFullName(uId)
closetName = DBM:GetShortServerName(closetName)
end
end
if tEnabled and inRange and not onlySummary and closePlayer < 6 then -- Display up to 5 players in text range frame.
local playerName = DBM:GetUnitFullName(uId)
playerName = DBM:GetShortServerName(playerName)
local color = RAID_CLASS_COLORS[dot.class] or NORMAL_FONT_COLOR
textFrame.lines[closePlayer]:SetText(dot.icon and ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:0|t %s"):format(dot.icon, playerName) or playerName)
textFrame.lines[closePlayer]:SetTextColor(color.r, color.g, color.b)
textFrame.lines[closePlayer]:Show()
textFrame:SetHeight((closePlayer * 12) + 12)
end
if rEnabled then
local playerX, playerY = UnitPosition("player")
local x, y = UnitPosition(uId)
if not x and not y then
rangeCheck:Hide(true)
return
end
dot.y = -(x - playerX)
dot.x = -(y - playerY)
dot.range = range
setDot(i, sinTheta, cosTheta)
end
elseif rEnabled and dot:IsShown() then
dot:Hide()
end
end
if tEnabled then
-- Green Text (Regular range frame and not near too many players, or reverse range frame and we ARE near enough)
textFrame.inRangeText:SetText(L.RANGECHECK_IN_RANGE_TEXT:format(closePlayer, activeRange))
textFrame.inRangeText:Show()
if (reverse and closePlayer >= warnThreshold) or (not reverse and closePlayer < warnThreshold) then
textFrame.inRangeText:SetTextColor(0, 1, 0)
-- Red Text (Regular range frame and we are near too many players, or reverse range frame and we aren't near enough)
else
updateSound(closePlayer)
textFrame.inRangeText:SetTextColor(1, 0, 0)
end
textFrame:Show()
end
if rEnabled then
if prevNumClosePlayer ~= closePlayer or prevclosestRange ~= closestRange or prevType ~= type then
if closePlayer >= warnThreshold then -- Only show the text if the circle is red
circleColor = reverse and 1 or 2
if closePlayer == 1 then
radarFrame.inRangeText:SetText(L.RANGERADAR_IN_RANGE_TEXTONE:format(closetName, closestRange))
else
radarFrame.inRangeText:SetText(L.RANGERADAR_IN_RANGE_TEXT:format(closePlayer, closestRange))
end
radarFrame.inRangeText:Show()
else
circleColor = reverse and 2 or 1
radarFrame.inRangeText:Hide()
end
prevNumClosePlayer = closePlayer
prevclosestRange = closestRange
prevType = type
end
if UnitIsDeadOrGhost("player") then
circleColor = 3
end
if prevColor ~= circleColor then
if circleColor == 1 then
radarFrame.circle:SetVertexColor(0, 1, 0)
elseif circleColor == 2 then
radarFrame.circle:SetVertexColor(1, 0, 0)
else
radarFrame.circle:SetVertexColor(1, 1, 1)
end
prevColor = circleColor
end
if circleColor == 2 then -- Red
updateSound(closePlayer)
end
end
end
end
local updater = mainFrame:CreateAnimationGroup()
updater:SetLooping("REPEAT")
local anim = updater:CreateAnimation()
anim:SetDuration(0.05)
mainFrame:SetSize(0, 0)
mainFrame:SetScript("OnEvent", function(self, event)
if event == "GROUP_ROSTER_UPDATE" or event == "RAID_TARGET_UPDATE" then
updateIcon()
end
end)
-----------------------
-- Check functions --
-----------------------
local getDistanceBetween, getDistanceBetweenAll
do
local UnitPosition, UnitExists, UnitIsUnit, UnitIsDeadOrGhost, UnitIsConnected = UnitPosition, UnitExists, UnitIsUnit, UnitIsDeadOrGhost, UnitIsConnected
function getDistanceBetweenAll(checkrange)
local range
for uId in DBM:GetGroupMembers() do
if UnitExists(uId) and not UnitIsUnit(uId, "player") and not UnitIsDeadOrGhost(uId) and UnitIsConnected(uId) and UnitPhaseReasonHack(uId) then
range = DBM:HasMapRestrictions() and itsBCAgain(uId, checkrange) or UnitDistanceSquared(uId) * 0.5
if checkrange < (range + 0.5) then
return true
end
end
end
return false
end
function getDistanceBetween(uId, x, y)
local restrictionsActive = DBM:HasMapRestrictions()
if not x then -- If only one arg then 2nd arg is always assumed to be player
return restrictionsActive and itsBCAgain(uId) or UnitDistanceSquared(uId) ^ 0.5
end
if type(x) == "string" and UnitExists(x) then -- arguments: uId, uId2
-- First attempt to avoid UnitPosition if any of args is player UnitDistanceSquared should work
if UnitIsUnit("player", uId) then
return restrictionsActive and itsBCAgain(x) or UnitDistanceSquared(x) ^ 0.5
elseif UnitIsUnit("player", x) then
return restrictionsActive and itsBCAgain(uId) or UnitDistanceSquared(uId) ^ 0.5
else -- Neither unit is player, no way to avoid UnitPosition
if restrictionsActive then -- Cannot compare two units that don't involve player with restrictions, just fail quietly
return 1000
end
local uId2 = x
x, y = UnitPosition(uId2)
if not x then
print("getDistanceBetween failed for: " .. uId .. " (" .. tostring(UnitExists(uId)) .. ") and " .. uId2 .. " (" .. tostring(UnitExists(uId2)) .. ")")
return
end
end
end
if restrictionsActive then -- Cannot check distance between player and a location (not another unit, again, fail quietly)
return 1000
end
local startX, startY = UnitPosition(uId)
local dX = startX - x
local dY = startY - y
return (dX * dX + dY * dY) ^ 0.5
end
end
---------------
-- Methods --
---------------
local restoreRange, restoreFilter, restoreThreshold, restoreReverse
function rangeCheck:Show(range, filter, forceshow, redCircleNumPlayers, reverse, hideTime, onlySummary)
if (DBM:GetNumRealGroupMembers() < 2 or DBM.Options.DontShowRangeFrame or DBM.Options.SpamSpecInformationalOnly) and not forceshow then
return
end
if type(range) == "function" then -- The first argument is optional
return self:Show(nil, range)
end
range = range or 10
redCircleNumPlayers = redCircleNumPlayers or 1
if not textFrame then
createTextFrame()
end
if not radarFrame then
createRadarFrame()
end
local restrictionsActive = DBM:HasMapRestrictions()
if (DBM.Options.RangeFrameFrames == "text" or DBM.Options.RangeFrameFrames == "both" or restrictionsActive) and not textFrame:IsShown() then
if restrictionsActive then
range = setCompatibleRestrictedRange(range)
end
textFrame:Show()
end
-- TODO, add check for restricted area here so we can prevent radar frame loading.
if not restrictionsActive and (DBM.Options.RangeFrameFrames == "radar" or DBM.Options.RangeFrameFrames == "both") and not radarFrame:IsShown() then
radarFrame:Show()
end
mainFrame.range = range
mainFrame.filter = filter
mainFrame.redCircleNumPlayers = redCircleNumPlayers
mainFrame.reverse = reverse
mainFrame.hideTime = hideTime and (GetTime() + hideTime) or 0
mainFrame.restrictions = restrictionsActive
mainFrame.onlySummary = onlySummary
if not mainFrame.eventRegistered then
mainFrame.eventRegistered = true
updateIcon()
mainFrame:RegisterEvent("GROUP_ROSTER_UPDATE")
mainFrame:RegisterEvent("RAID_TARGET_UPDATE")
end
updater:SetScript("OnLoop", updateRangeFrame)
updater:Play()
if forceshow and not DBM.Options.DontRestoreRange then -- Force means user activated range frame, store user value for restore function
restoreRange, restoreFilter, restoreThreshold, restoreReverse = mainFrame.range, mainFrame.filter, mainFrame.redCircleNumPlayers, mainFrame.reverse
end
end
function rangeCheck:Hide(force)
if restoreRange and not force then -- Restore range frame to way it was when boss mod is done with it
rangeCheck:Show(restoreRange, restoreFilter, true, restoreThreshold, restoreReverse)
else
restoreRange, restoreFilter, restoreThreshold, restoreReverse = nil, nil, nil, nil
updater:Stop()
if mainFrame.eventRegistered then
mainFrame.eventRegistered = nil
mainFrame:UnregisterAllEvents()
end
if textFrame then
textFrame:Hide()
end
if radarFrame then
radarFrame:Hide()
end
end
end
function rangeCheck:IsShown()
return textFrame and textFrame:IsShown() or radarFrame and radarFrame:IsShown()
end
function rangeCheck:IsRadarShown()
return radarFrame and radarFrame:IsShown()
end
function rangeCheck:UpdateRestrictions(force)
mainFrame.restrictions = force or DBM:HasMapRestrictions()
end
function rangeCheck:SetHideTime(hideTime)
mainFrame.hideTime = hideTime and (GetTime() + hideTime) or 0
end
function rangeCheck:GetDistance(...)
return getDistanceBetween(...)
end
function rangeCheck:GetDistanceAll(checkrange)
if DBM:HasMapRestrictions() then
checkrange = setCompatibleRestrictedRange(checkrange)
end
return getDistanceBetweenAll(checkrange)
end
do
local function UpdateRangeFrame(r, reverse)
if rangeCheck:IsShown() then
rangeCheck:Hide(true)
else
if DBM:HasMapRestrictions() then
DBM:AddMsg(L.NO_RANGE)
end
rangeCheck:Show((r and r < 201) and r or 10 , nil, true, nil, reverse)
end
end
SLASH_DBMRANGE1 = "/range"
SLASH_DBMRANGE2 = "/distance"
SLASH_DBMRRANGE1 = "/rrange"
SLASH_DBMRRANGE2 = "/rdistance"
SlashCmdList["DBMRANGE"] = function(msg)
UpdateRangeFrame(tonumber(msg), false)
end
SlashCmdList["DBMRRANGE"] = function(msg)
UpdateRangeFrame(tonumber(msg), true)
end
end