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.

994 lines
34 KiB

---@class DBM
local DBM = DBM
---@class DBMRangeCheck
local rangeCheck = {}
DBM.RangeCheck = rangeCheck
4 years ago
--------------
-- Locals --
--------------
local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)
local isWrath = WOW_PROJECT_ID == (WOW_PROJECT_WRATH_CLASSIC or 11)
4 years ago
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
4 years ago
local function UnitPhaseReasonHack(uId)
if isRetail then
return not UnitPhaseReason(uId)
end
return UnitInPhase(uId)
end
local L = DBM_CORE_L
---@class DBMRangeCheckFrame: Frame
4 years ago
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 <= 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 <= 33 then
return 33
elseif range <= 43 then
4 years ago
return 43
elseif range <= 48 and not isClassic then
return 48
elseif range <= 60 and not isClassic then
return 60
elseif range <= 80 and not isClassic then
4 years ago
return 80
elseif range <= 100 and not isClassic then
return 100
else--Mod passed a range that exceeds max range known apis can cover, we really don't have a way to measure this anymore so we return highest range we can measure based on game client
return isClassic and 43 or 100
4 years ago
end
end
-----------------------
-- Check functions --
-----------------------
local getDistanceBetween, getDistanceBetweenAll
local itsBCAgain--Needs to be called outside of below scope, itsDFBaby does not
4 years ago
do
local UnitPosition, UnitExists, UnitIsUnit, UnitIsDeadOrGhost, UnitIsConnected = UnitPosition, UnitExists, UnitIsUnit, UnitIsDeadOrGhost, UnitIsConnected
local CheckInteractDistance, IsItemInRange, UnitInRange = CheckInteractDistance, C_Item and C_Item.IsItemInRange or IsItemInRange, UnitInRange
4 years ago
-- 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] = 17626, -- Sparrowhawk Net
4 years ago
[18] = 6450, -- Silk Bandage
[23] = 21519, -- Mistletoe
[28] = 13289,--Egan's Blaster
[33] = 1180, -- Scroll of Stamina
}
if not isClassic then -- Exists in Wrath/BCC but not vanilla/era
4 years ago
itemRanges[6] = 16114 -- Foremans Blackjack (TBC)
4 years ago
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)
4 years ago
end
local apiRanges = {
[10] = 3, -- CheckInteractDistance (Duel)
[11] = 2, -- CheckInteractDistance (Trade)
}
local function itsDFBaby(uId)
local inRange, checkedRange = UnitInRange(uId)
if inRange and checkedRange then--Range checked and api was successful
return 43
else
return 1000
end
4 years ago
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
3 years ago
return 1000 -- Just so it has a numeric value, even if it's unknown to protect from nil errors
4 years ago
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
4 years ago
elseif not isClassic and IsItemInRange(16114, uId) then return 6
4 years ago
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 IsItemInRange(1180, uId) then return 33
elseif UnitInRange and UnitInRange(uId) then return 43
4 years ago
elseif not isClassic and IsItemInRange(32698, uId) then return 48
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
4 years ago
else return 1000 end -- Just so it has a numeric value, even if it's unknown to protect from nil errors
end
end
--Retail is limited to just returning true or false for being within 43 (40+hitbox) of target while in instances (outdoors retail can still use UnitDistanceSquared)
function getDistanceBetweenAll(checkrange)
local restrictionsActive = not isWrath and DBM:HasMapRestrictions()
checkrange = restrictionsActive and 43 or checkrange
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
local range = DBM:HasMapRestrictions() and (not isWrath and itsDFBaby(uId) or 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 (not isWrath and itsDFBaby(uId) or 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 (not isWrath and itsDFBaby(x) or itsBCAgain(x)) or UnitDistanceSquared(x) ^ 0.5
elseif UnitIsUnit("player", x) then
return restrictionsActive and (not isWrath and itsDFBaby(uId) or 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
4 years ago
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 = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "range"
UIDropDownMenu_AddButton(info, 1)
info = {}
4 years ago
info.text = L.RANGECHECK_SETTHRESHOLD
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "threshold"
UIDropDownMenu_AddButton(info, 1)
info = {}
4 years ago
info.text = L.RANGECHECK_SOUNDS
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "sounds"
UIDropDownMenu_AddButton(info, 1)
info = {}
4 years ago
info.text = L.RANGECHECK_OPTION_FRAMES
info.notCheckable = true
info.hasArrow = true
info.keepShownOnClick = true
info.menuList = "frames"
UIDropDownMenu_AddButton(info, 1)
info = {}
4 years ago
info.text = LOCK_FRAME
if DBM.Options.RangeFrameLocked then
info.checked = true
end
info.func = toggleLocked
UIDropDownMenu_AddButton(info, 1)
info = {}
4 years ago
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
4 years ago
if not isClassic then
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(6)
info.func = setRange
info.arg1 = 6
info.checked = (mainFrame.range == 6)
UIDropDownMenu_AddButton(info, 2)
end
4 years ago
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(8)
info.func = setRange
info.arg1 = 8
info.checked = (mainFrame.range == 8)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(10)
info.func = setRange
info.arg1 = 10
info.checked = (mainFrame.range == 10)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(13)
info.func = setRange
info.arg1 = 13
info.checked = (mainFrame.range == 13)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(18)
info.func = setRange
info.arg1 = 18
info.checked = (mainFrame.range == 18)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(23)
info.func = setRange
info.arg1 = 23
info.checked = (mainFrame.range == 23)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(33)
info.func = setRange
info.arg1 = 33
info.checked = (mainFrame.range == 33)
UIDropDownMenu_AddButton(info, 2)
4 years ago
if not isClassic then
info = {}
4 years ago
info.text = L.RANGECHECK_SETRANGE_TO:format(43)
info.func = setRange
info.arg1 = 43
info.checked = (mainFrame.range == 43)
UIDropDownMenu_AddButton(info, 2)
end
4 years ago
elseif menu == "threshold" then
info = {}
4 years ago
info.text = 1
info.func = setThreshold
info.arg1 = 1
info.checked = (mainFrame.redCircleNumPlayers == 1)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = 2
info.func = setThreshold
info.arg1 = 2
info.checked = (mainFrame.redCircleNumPlayers == 2)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = 3
info.func = setThreshold
info.arg1 = 3
info.checked = (mainFrame.redCircleNumPlayers == 3)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = 4
info.func = setThreshold
info.arg1 = 4
info.checked = (mainFrame.redCircleNumPlayers == 4)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = 5
info.func = setThreshold
info.arg1 = 5
info.checked = (mainFrame.redCircleNumPlayers == 5)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = 6
info.func = setThreshold
info.arg1 = 6
info.checked = (mainFrame.redCircleNumPlayers == 6)
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = 8
info.func = setThreshold
info.arg1 = 8
info.checked = (mainFrame.redCircleNumPlayers == 8)
UIDropDownMenu_AddButton(info, 2)
elseif menu == "sounds" then
info = {}
4 years ago
info.text = L.RANGECHECK_SOUND_OPTION_1
info.notCheckable = true
info.hasArrow = true
info.menuList = "RangeFrameSound1"
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
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 = {}
4 years ago
info.text = L.RANGECHECK_OPTION_TEXT
info.func = setFrames
info.arg1 = "text"
info.checked = (DBM.Options.RangeFrameFrames == "text")
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
info.text = L.RANGECHECK_OPTION_RADAR
info.func = setFrames
info.arg1 = "radar"
info.checked = (DBM.Options.RangeFrameFrames == "radar")
UIDropDownMenu_AddButton(info, 2)
info = {}
4 years ago
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 = {}
4 years ago
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 = {}
4 years ago
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 = {}
4 years ago
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()
---@class DBMRangeCheckFrame: Frame, BackdropTemplate
4 years ago
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)
---@diagnostic disable-next-line: param-type-mismatch
4 years ago
UIDropDownMenu_Initialize(dropdownFrame, initializeDropdown)
---@diagnostic disable-next-line: param-type-mismatch
4 years ago
ToggleDropDownMenu(1, nil, dropdownFrame, "cursor", 5, -10)
end
end)
---@class DBMRangeCheckTitleFrame: FontString
4 years ago
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
3 years ago
textFrame:Hide()
4 years ago
end
local function createRadarFrame()
---@class DBMRangeCheckRadarFrame: Frame
4 years ago
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)
---@diagnostic disable-next-line: param-type-mismatch
4 years ago
UIDropDownMenu_Initialize(dropdownFrame, initializeDropdown)
---@diagnostic disable-next-line: param-type-mismatch
4 years ago
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 = {
3 years ago
["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
4 years ago
}
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() or 0
4 years ago
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)
---------------
-- 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
4 years ago
return
end
local restrictionsActive = DBM:HasMapRestrictions()
if not isWrath and restrictionsActive then--Don't popup on retail or classic era at all if in an instance (it now only works in wrath)
return
end
4 years ago
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
if restrictionsActive then
range = setCompatibleRestrictedRange(range)
end
4 years ago
if (DBM.Options.RangeFrameFrames == "text" or DBM.Options.RangeFrameFrames == "both" or restrictionsActive) and not textFrame:IsShown() then
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 and (isWrath or not (mainFrame.restrictions or DBM:HasMapRestrictions())) then -- Restore range frame to way it was when boss mod is done with it
4 years ago
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()
if mainFrame.restrictions and not isWrath then
rangeCheck:Hide(true)
end
4 years ago
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 UpdateLocalRangeFrame(r, reverse)
4 years ago
if rangeCheck:IsShown() then
4 years ago
rangeCheck:Hide(true)
4 years ago
else
if DBM:HasMapRestrictions() then
DBM:AddMsg(L.TEXT_ONLY_RANGE)
4 years ago
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)
if not isWrath and DBM:HasMapRestrictions() then
DBM:AddMsg(L.NO_RANGE)
else
UpdateLocalRangeFrame(tonumber(msg))
end
4 years ago
end
SlashCmdList["DBMRRANGE"] = function(msg)
if not isWrath and DBM:HasMapRestrictions() then
DBM:AddMsg(L.NO_RANGE)
else
UpdateLocalRangeFrame(tonumber(msg), true)
end
4 years ago
end
end