-- This file uses models and textures taken from TomTom. The 3D arrow model was created by Guillotine (curse.guillotine@gmail.com) and 2D minimap textures by Cladhaire. ---@class DBM local DBM = DBM local L = DBM_CORE_L -------------- -- Locals -- -------------- ---@class DBMArrow local arrowFrame = {} local frame, runAwayArrow, targetType, targetPlayer, targetX, targetY, targetMapId, hideTime, hideDistance DBM.Arrow = arrowFrame -------------------------------------------------------- -- Cache frequently used global variables in locals -- -------------------------------------------------------- local pi, pi2 = math.pi, math.pi * 2 local floor, sin, cos, atan2, sqrt, min = math.floor, math.sin, math.cos, math.atan2, math.sqrt, math.min local UnitPosition, GetTime = UnitPosition, GetTime -------------------- -- Create Frame -- -------------------- ---@class DBMArrowFrame: Button frame = CreateFrame("Button", "DBMArrow", UIParent) frame:Hide() frame:SetFrameStrata("HIGH") frame:SetSize(56, 42) frame:SetMovable(true) frame:EnableMouse(false) frame:RegisterForDrag("LeftButton", "RightButton") frame:SetScript("OnDragStart", function(self) self:StartMoving() end) frame:SetScript("OnDragStop", function(self) self:StopMovingOrSizing() local point, _, _, x, y = self:GetPoint(1) DBM.Options.ArrowPoint = point DBM.Options.ArrowPosX = x DBM.Options.ArrowPosY = y end) ---@class DBMArrowTextFrame: Frame local textframe = CreateFrame("Frame", nil, frame) frame.distance = textframe:CreateFontString(nil, "OVERLAY", "GameFontNormalSmall") frame.title = textframe:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall") frame.title:SetPoint("TOP", frame, "BOTTOM") frame.distance:SetPoint("TOP", frame.title, "BOTTOM") textframe:Hide() local arrow = frame:CreateTexture(nil, "OVERLAY") arrow:SetTexture("Interface\\AddOns\\DBM-Core\\textures\\arrows\\Arrow.blp") arrow:SetAllPoints(frame) --------------------- -- Map Utilities -- --------------------- local calculateDistance do function calculateDistance(x1, y1, x2, y2) local dX = x1 - x2 local dY = y1 - y2 return sqrt(dX * dX + dY * dY) end end -- GetPlayerFacing seems to return values between -pi and pi instead of 0 - 2pi sometimes since 3.3.3 local GetPlayerFacing = function() local result = GetPlayerFacing() or 0 if result < 0 then result = result + pi2 end return result end ------------------------ -- Update the arrow -- ------------------------ local updateArrow do local currentCell local formatText = "%dy" function updateArrow(direction, distance) local cell = floor(direction / pi2 * 108 + 0.5) % 108 if cell ~= currentCell then currentCell = cell local column = cell % 9 local row = floor(cell / 9) local xStart = (column * 56) / 512 local yStart = (row * 42) / 512 local xEnd = ((column + 1) * 56) / 512 local yEnd = ((row + 1) * 42) / 512 arrow:SetTexCoord(xStart, xEnd, yStart, yEnd) end if distance then if runAwayArrow then local perc = distance / hideDistance arrow:SetVertexColor(1 - perc, perc, 0) if distance >= hideDistance then frame:Hide() end else local perc = min(distance, 100) / 100 arrow:SetVertexColor(1, 1 - perc, 0) if distance <= hideDistance then frame:Hide() else frame.distance:SetText(formatText:format(distance)) end end else if runAwayArrow then arrow:SetVertexColor(1, 0.3, 0) else arrow:SetVertexColor(1, 1, 0) end end end end ------------------------ -- OnUpdate Handler -- ------------------------ do local rotateState = 0 frame:SetScript("OnUpdate", function(self, elapsed) if hideTime and GetTime() > hideTime then frame:Hide() end arrow:Show() -- the static arrow type is special because it doesn't depend on the player's orientation or position if targetType == "static" then return updateArrow(targetX) -- targetX contains the static angle to show end local x, y, _, mapId = UnitPosition("player") --New bug in 8.2.5, unit position returns nil for position in areas there aren't restrictions for first few frames in that new area --this just has the arrow skip some onupdates during that if not x or not y then if IsInInstance() then--Somehow x and y returned on entering an instance, before restrictions kicked in? frame:Hide()--Hide, if in an instance, disable arrow entirely end return--Not in instance, but x and y nil, just skip updates until x and y start returning end if targetType == "player" then targetX, targetY, _, targetMapId = UnitPosition(targetPlayer) if not targetX or not targetY or mapId ~= targetMapId then self:Hide() -- hide the arrow if the target doesn't exist. TODO: just hide the texture and add a timeout end elseif targetType == "rotate" then rotateState = rotateState + elapsed targetX = x + cos(rotateState) targetY = y + sin(rotateState) end if not targetX or not targetY then return end local angle = atan2(targetY - y, x - targetX) if angle <= 0 then -- -pi < angle < pi but we need/want a value between 0 and 2 pi if runAwayArrow then angle = -angle -- 0 < angle < pi else angle = pi - angle -- pi < angle < 2pi end else if runAwayArrow then angle = pi2 - angle -- pi < angle < 2pi else angle = pi - angle -- 0 < angle < pi end end updateArrow(angle - GetPlayerFacing(), calculateDistance(x, y, targetX, targetY)) end) end ---------------------- -- Public Methods -- ---------------------- --/run DBM.Arrow:ShowRunTo(50, 50, 1, nil, true, true, "Waypoint", custom local mapID) local function show(runAway, x, y, distance, time, legacy, _, title, customAreaID) if DBM:HasMapRestrictions() then return end local player if type(x) == "string" then player, hideDistance, hideTime = x, y, hideDistance end frame:Show() textframe:Show() if title then frame.title:Show() frame.title:SetText(title) else frame.title:Hide() end if runAway then frame.distance:Hide() else frame.distance:Show() end runAwayArrow = runAway hideDistance = distance or runAway and 100 or 3 if time then hideTime = time + GetTime() else hideTime = nil end if player then targetType = "player" targetPlayer = player else targetType = "fixed" if legacy and x >= 0 and x <= 100 and y >= 0 and y <= 100 then local _, temptable = C_Map.GetWorldPosFromMapPos(tonumber(customAreaID) or C_Map.GetBestMapForUnit("player") or 0, CreateVector2D(x / 100, y / 100)) x, y = temptable.x, temptable.y end targetX, targetY = x, y end end function arrowFrame:ShowRunTo(...) return show(false, ...) end function arrowFrame:ShowRunAway(...) return show(true, ...) end -- shows a static arrow function arrowFrame:ShowStatic(angle, time) --Static arrows do not need restrictions, and are still permitted even in 7.1 runAwayArrow = false hideDistance = 0 targetType = "static" targetX = angle * pi2 / 360 if time then hideTime = time + GetTime() else hideTime = nil end frame:Show() textframe:Hide()--just in case they call static while a non static was already showing end function arrowFrame:IsShown() return frame and frame:IsShown() end function arrowFrame:Hide() textframe:Hide() frame:Hide() end local function endMove() frame:EnableMouse(false) arrowFrame:Hide() end function arrowFrame:Move() targetType = "rotate" runAwayArrow = false hideDistance = 0 frame:EnableMouse(true) frame:Show() DBT:CreateBar(25, L.ARROW_MOVABLE, 237538) DBM:Unschedule(endMove) DBM:Schedule(25, endMove) end function arrowFrame:LoadPosition() frame:SetPoint(DBM.Options.ArrowPoint, DBM.Options.ArrowPosX, DBM.Options.ArrowPosY) end do SLASH_DEADLYBOSSMODSDWAY1 = "/dway"--/way not used because DBM would load before TomTom and can't check SlashCmdList["DEADLYBOSSMODSDWAY"] = function(msg) if DBM:HasMapRestrictions() then DBM:AddMsg(L.NO_ARROW) return end msg = msg:sub(1):trim() local x, y = strsplit(" ", msg) -- Try splitting by space local xNum, yNum = tonumber(x or ""), tonumber(y or "") if not xNum or not yNum then x, y = strsplit(",", msg) -- And then by comma xNum, yNum = tonumber(x or ""), tonumber(y or "") end if xNum and yNum then DBM.Arrow:ShowRunTo(xNum, yNum, 1, nil, true) return end if DBM.Arrow:IsShown() then DBM.Arrow:Hide() else DBM:AddMsg(L.ARROW_WAY_USAGE) end end end