---------------
-- 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 DDM = _G [ " LibStub " ] : GetLibrary ( " LibDropDownMenu " )
local UIDropDownMenu_AddButton , UIDropDownMenu_Initialize , ToggleDropDownMenu = DDM.UIDropDownMenu_AddButton , DDM.UIDropDownMenu_Initialize , DDM.ToggleDropDownMenu
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 not isClassic then
return 80
elseif range <= 100 and not isClassic then
return 100
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)
end
if not isClassic then -- Exists in Wrath/BCC but not vanilla/era
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
itemRanges [ 80 ] = 35278 -- Reinforced Net (WotLK)
itemRanges [ 100 ] = 41058 -- Hyldnir Harpoon (WotLK)
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 not isClassic and IsItemInRange ( 35278 , uId ) then return 80
elseif not isClassic and IsItemInRange ( 41058 , uId ) then return 100
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 = { }
info.text = L.RANGECHECK_SETRANGE
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = " range "
UIDropDownMenu_AddButton ( info , 1 )
info = { }
info.text = L.RANGECHECK_SETTHRESHOLD
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = " threshold "
UIDropDownMenu_AddButton ( info , 1 )
info = { }
info.text = L.RANGECHECK_SOUNDS
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = " sounds "
UIDropDownMenu_AddButton ( info , 1 )
info = { }
info.text = L.RANGECHECK_OPTION_FRAMES
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = " frames "
UIDropDownMenu_AddButton ( info , 1 )
info = { }
info.text = LOCK_FRAME
if DBM.Options . RangeFrameLocked then
info.checked = true
end
info.func = toggleLocked
UIDropDownMenu_AddButton ( info , 1 )
info = { }
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 = { }
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 = { }
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 = { }
info.text = L.RANGECHECK_SETRANGE_TO : format ( 8 )
info.func = setRange
info.arg1 = 8
info.checked = ( mainFrame.range == 8 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = L.RANGECHECK_SETRANGE_TO : format ( 10 )
info.func = setRange
info.arg1 = 10
info.checked = ( mainFrame.range == 10 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = L.RANGECHECK_SETRANGE_TO : format ( 13 )
info.func = setRange
info.arg1 = 13
info.checked = ( mainFrame.range == 13 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = L.RANGECHECK_SETRANGE_TO : format ( 18 )
info.func = setRange
info.arg1 = 18
info.checked = ( mainFrame.range == 18 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
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 = { }
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 = { }
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 = { }
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 = { }
info.text = 1
info.func = setThreshold
info.arg1 = 1
info.checked = ( mainFrame.redCircleNumPlayers == 1 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = 2
info.func = setThreshold
info.arg1 = 2
info.checked = ( mainFrame.redCircleNumPlayers == 2 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = 3
info.func = setThreshold
info.arg1 = 3
info.checked = ( mainFrame.redCircleNumPlayers == 3 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = 4
info.func = setThreshold
info.arg1 = 4
info.checked = ( mainFrame.redCircleNumPlayers == 4 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = 5
info.func = setThreshold
info.arg1 = 5
info.checked = ( mainFrame.redCircleNumPlayers == 5 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = 6
info.func = setThreshold
info.arg1 = 6
info.checked = ( mainFrame.redCircleNumPlayers == 6 )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = 8
info.func = setThreshold
info.arg1 = 8
info.checked = ( mainFrame.redCircleNumPlayers == 8 )
UIDropDownMenu_AddButton ( info , 2 )
elseif menu == " sounds " then
info = { }
info.text = L.RANGECHECK_SOUND_OPTION_1
info.notCheckable = true
info.hasArrow = true
info.menuList = " RangeFrameSound1 "
UIDropDownMenu_AddButton ( info , 2 )
info = { }
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 = { }
info.text = L.RANGECHECK_OPTION_TEXT
info.func = setFrames
info.arg1 = " text "
info.checked = ( DBM.Options . RangeFrameFrames == " text " )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
info.text = L.RANGECHECK_OPTION_RADAR
info.func = setFrames
info.arg1 = " radar "
info.checked = ( DBM.Options . RangeFrameFrames == " radar " )
UIDropDownMenu_AddButton ( info , 2 )
info = { }
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 = { }
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 = { }
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 = { }
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