Quantcast
--[[
##############################################################################
_____/\\\\\\\\\\\____/\\\________/\\\__/\\\________/\\\__/\\\\\\\\\\\_       #
 ___/\\\/////////\\\_\/\\\_______\/\\\_\/\\\_______\/\\\_\/////\\\///__      #
  __\//\\\______\///__\//\\\______/\\\__\/\\\_______\/\\\_____\/\\\_____     #
   ___\////\\\__________\//\\\____/\\\___\/\\\_______\/\\\_____\/\\\_____    #
    ______\////\\\________\//\\\__/\\\____\/\\\_______\/\\\_____\/\\\_____   #
     _________\////\\\______\//\\\/\\\_____\/\\\_______\/\\\_____\/\\\_____  #
      __/\\\______\//\\\______\//\\\\\______\//\\\______/\\\______\/\\\_____ #
       _\///\\\\\\\\\\\/________\//\\\________\///\\\\\\\\\/____/\\\\\\\\\\\_#
        ___\///////////___________\///___________\/////////_____\///////////_#
##############################################################################
S U P E R - V I L L A I N - U I   By: Munglunch                              #
##############################################################################
##########################################################
LOCALIZED LUA FUNCTIONS
##########################################################
]]--
--[[ GLOBALS ]]--
local _G = _G;
local unpack    = _G.unpack;
local select    = _G.select;
local pairs     = _G.pairs;
local type      = _G.type;
local tostring  = _G.tostring;
local tonumber  = _G.tonumber;
local tinsert   = _G.tinsert;
local tremove   = _G.tremove;
local string    = _G.string;
local math      = _G.math;
local bit       = _G.bit;
local table     = _G.table;
--[[ STRING METHODS ]]--
local format, find, lower, match = string.format, string.find, string.lower, string.match;
--[[ MATH METHODS ]]--
local abs, ceil, floor, round = math.abs, math.ceil, math.floor, math.round;  -- Basic
local fmod, modf, sqrt = math.fmod, math.modf, math.sqrt;   -- Algebra
local atan2, cos, deg, rad, sin = math.atan2, math.cos, math.deg, math.rad, math.sin;  -- Trigonometry
local min, huge, random = math.min, math.huge, math.random;  -- Uncommon
local sqrt2, max = math.sqrt(2), math.max;
--[[ TABLE METHODS ]]--
local tcopy, twipe, tsort, tconcat, tdump = table.copy, table.wipe, table.sort, table.concat, table.dump;
--[[ BINARY METHODS ]]--
local band = bit.band;
--[[
##########################################################
GET ADDON DATA
##########################################################
]]--
local SVUIAddOnName, PLUGIN = ...;
local SV, L, Registry = unpack(SVUI);

PLUGIN = SV:Prototype(SVUIAddOnName)

_G["TrackingVillain"] = PLUGIN;
--[[
##########################################################
LOCALS AND BINDING
##########################################################
]]--
BINDING_HEADER_SVUITRACK = "Supervillain UI: Tracking Device";

local CONFIGS = SV.Configs
local NewHook = hooksecurefunc;
local playerGUID = UnitGUID('player')
local classColor = RAID_CLASS_COLORS
local radian90 = (3.141592653589793  /  2) * -1;
local GetDistance, GetTarget, GetFromPlayer;

local SCHEMA = PLUGIN.___schema;
local VERSION = PLUGIN.___version;
--[[
##########################################################
BUILD
##########################################################
]]--
function SVUI_TrackingDoodad_OnLoad()
    local frame = _G["SVUI_TrackingDoodad"]
    frame.Border:SetGradient(unpack(SV.Media.gradient.dark))
    frame.Arrow:SetVertexColor(0.1, 0.8, 0.8)
    frame.Range:SetFont(SV.Media.font.roboto, 14, "OUTLINE")
    frame.Range:SetTextColor(1, 1, 1, 0.75)
    SV.Animate:Orbit(frame.Radar, 8, true)
    frame:RegisterForDrag("LeftButton");
    frame:Hide()
end

do
    local WORLDMAPAREA_DEFAULT_DUNGEON_FLOOR_IS_TERRAIN = 0x00000004
    local WORLDMAPAREA_VIRTUAL_CONTINENT = 0x00000008
    local DUNGEONMAP_MICRO_DUNGEON = 0x00000001
    local _failsafe, _cache, _dungeons, _transform = {}, {}, {}, {};

    local _mapdata = {
        [0] = {
            height = 22266.74312,
            system = -1,
            width = 33400.121,
            xOffset = 0,
            yOffset = 0,
            [1] = {
                xOffset = -10311.71318,
                yOffset = -19819.33898,
                scale = 0.56089997291565,
            },
            [0] = {
                xOffset = -48226.86993,
                yOffset = -16433.90283,
                scale = 0.56300002336502,
            },
            [571] = {
                xOffset = -29750.89905,
                yOffset = -11454.50802,
                scale = 0.5949000120163,
            },
            [870] = {
                xOffset = -27693.71178,
                yOffset = -29720.0585,
                scale = 0.65140002965927,
            },
        },
    }

    local _failsafeFunc = function(tbl, key)
        if(type(key) == "number") then
            return _failsafe;
        else
            return rawget(_failsafe, key);
        end
    end

    setmetatable(_failsafe, { xOffset = 0, height = 1, yOffset = 0, width = 1, __index = _failsafeFunc });
    setmetatable(_mapdata, _failsafe);

    for _, ID in ipairs(GetWorldMapTransforms()) do
        local terrain, newterrain, _, _, transformMinY, transformMaxY, transformMinX, transformMaxX, offsetY, offsetX = GetWorldMapTransformInfo(ID)
        if ( offsetX ~= 0 or offsetY ~= 0 ) then
            _transform[ID] = {
                terrain = terrain,
                newterrain = newterrain,
                BRy = -transformMinY,
                TLy = -transformMaxY,
                BRx = -transformMinX,
                TLx = -transformMaxX,
                offsetY = offsetY,
                offsetX = offsetX,
            }
        end
    end

    local function _getmapdata(t)
        local chunk = {}
        local mapName = GetMapInfo();
        local id = GetCurrentMapAreaID();
        local numFloors = GetNumDungeonMapLevels();
        chunk.mapName = mapName;
        chunk.cont = (GetCurrentMapContinent()) or -100;
        chunk.zone = (GetCurrentMapZone()) or -100;
        chunk.numFloors = numFloors;
        local _, TLx, TLy, BRx, BRy = GetCurrentMapZone();
        if(TLx and TLy and BRx and BRy and (TLx~=0 or TLy~=0 or BRx~=0 or BRy~=0)) then
            chunk[0] = {};
            chunk[0].TLx = TLx;
            chunk[0].TLy = TLy;
            chunk[0].BRx = BRx;
            chunk[0].BRy = BRy;
        end
        if(not chunk[0] and numFloors == 0 and (GetCurrentMapDungeonLevel()) == 1) then
            numFloors = 1;
            chunk.hiddenFloor = true;
        end
        if(numFloors > 0) then
            for f = 1, numFloors do
                SetDungeonMapLevel(f);
                local _, TLx, TLy, BRx, BRy = GetCurrentMapDungeonLevel();
                if(TLx and TLy and BRx and BRy) then
                    chunk[f] = {};
                    chunk[f].TLx = TLx;
                    chunk[f].TLy = TLy;
                    chunk[f].BRx = BRx;
                    chunk[f].BRy = BRy;
                end
            end
        end

        t[id] = chunk;
    end

    do
        local continents = { GetMapContinents() };
        for C in pairs(continents) do
            local zones = { GetMapZones(C) };
            continents[C] = zones;
            local pass, error = pcall(SetMapZoom, C, 0)
            if(pass) then
                zones[0] = GetCurrentMapAreaID();
                _getmapdata(_cache);
                for Z in ipairs(zones) do
                    SetMapZoom(C, Z);
                    zones[Z] = GetCurrentMapAreaID();
                    _getmapdata(_cache);
                end
            end
        end

        for _, id in ipairs(GetAreaMaps()) do
            if not (_cache[id]) then
                if(SetMapByID(id)) then
                    _getmapdata(_cache);
                end
            end
        end
    end

    for id, map in pairs(_cache) do
        local terrain, _, _, _, _, _, _, _, _, flags = GetAreaMapInfo(id)
        local origin = terrain;
        local chunk = _mapdata[id];
        if not (chunk) then chunk = {}; end
        if(map.numFloors > 0 or map.hiddenFloor) then
            for f, coords in pairs(map) do
                if(type(f) == "number" and f > 0) then
                    if not (chunk[f]) then
                        chunk[f] = {};
                    end
                    local flr = chunk[f]
                    local TLx, TLy, BRx, BRy = -coords.BRx, -coords.BRy, -coords.TLx, -coords.TLy
                    if not (flr.width) then
                        flr.width = BRx - TLx
                    end
                    if not (flr.height) then
                        flr.height = BRy - TLy
                    end
                    if not (flr.xOffset) then
                        flr.xOffset = TLx
                    end
                    if not (flr.yOffset) then
                        flr.yOffset = TLy
                    end
                end
            end
            for f = 1, map.numFloors do
                if not (chunk[f]) then
                    if(f == 1 and map[0] and map[0].TLx and map[0].TLy and map[0].BRx and map[0].BRy and
                      band(flags, WORLDMAPAREA_DEFAULT_DUNGEON_FLOOR_IS_TERRAIN) == WORLDMAPAREA_DEFAULT_DUNGEON_FLOOR_IS_TERRAIN) then
                        chunk[f] = {};
                        local flr = chunk[f]
                        local coords = map[0]
                        local TLx, TLy, BRx, BRy = -coords.TLx, -coords.TLy, -coords.BRx, -coords.BRy
                        flr.width = BRx - TLx
                        flr.height = BRy - TLy
                        flr.xOffset = TLx
                        flr.yOffset = TLy
                    end
                end
            end
            if(map.hiddenFloor) then
                chunk.width = chunk[1].width
                chunk.height = chunk[1].height
                chunk.xOffset = chunk[1].xOffset
                chunk.yOffset = chunk[1].yOffset
            end
        else
            local coords = map[0]
            if(coords ~= nil) then
                local TLx, TLy, BRx, BRy = -coords.TLx, -coords.TLy, -coords.BRx, -coords.BRy
                for _, trans in pairs(_transform) do
                    if(trans.terrain == terrain) then
                        if((trans.TLx < TLx and BRx < trans.BRx) and (trans.TLy < TLy and BRy < trans.BRy)) then
                            TLx = TLx - trans.offsetX;
                            BRx = BRx - trans.offsetX;
                            BRy = BRy - trans.offsetY;
                            TLy = TLy - trans.offsetY;
                            terrain = trans.newterrain;
                            break;
                        end
                    end
                end
                if not (TLx==0 and TLy==0 and BRx==0 and BRy==0) then
                    if not (TLx < BRx) then
                        printError("Bad x-axis Orientation (Zone): ", id, TLx, BRx);
                    end
                    if not (TLy < BRy) then
                        printError("Bad y-axis Orientation (Zone): ", id, TLy, BRy);
                    end
                end
                if not (chunk.width) then
                    chunk.width = BRx - TLx
                end
                if not (chunk.height) then
                    chunk.height = BRy - TLy
                end
                if not (chunk.xOffset) then
                    chunk.xOffset = TLx
                end
                if not (chunk.yOffset) then
                    chunk.yOffset = TLy
                end
            end
        end
        if not (next(chunk, nil)) then
            chunk = { xOffset = 0, height = 1, yOffset = 0, width = 1 };
        end
        if not (chunk.origin) then
            chunk.origin = origin;
        end
        _mapdata[id] = chunk;
        if(chunk and chunk ~= _failsafe) then
            if not (chunk.system) then
                chunk.system = terrain;
            end
            if(map.cont > 0 and map.zone > 0) then
                _dungeons[terrain] = {}
            end
            setmetatable(chunk, _failsafe);
        end
    end

    local function _getpos(map, mapFloor, x, y)
        if (mapFloor ~= 0) then
            map = rawget(map, mapFloor) or _dungeons[map.origin][mapFloor];
        end
        x = x * map.width + map.xOffset;
        y = y * map.height + map.yOffset;
        return x, y;
    end

    function GetDistance(map1, floor1, x1, y1, map2, floor2, x2, y2)
        if not (map1 and map2) then return end
        floor1 = floor1 or min(#_mapdata[map1], 1);
        floor2 = floor2 or min(#_mapdata[map2], 1);
        local dist, xDelta, yDelta, angle;
        if(map1 == map2 and floor1 == floor2) then
            local chunk = _mapdata[map1];
            local tmp = chunk
            if(floor1 ~= 0) then
                tmp = rawget(chunk, floor1)
            end
            local w,h = 1,1
            if(not tmp) then
                if(_dungeons[chunk.origin] and _dungeons[chunk.origin][floor1]) then
                    chunk = _dungeons[chunk.origin][floor1]
                    w = chunk.width
                    h = chunk.height
                else
                    w = 1
                    h = 1
                end
            else
                w = chunk.width
                h = chunk.height
            end
            xDelta = (x2 - x1) * (w or 1);
            yDelta = (y2 - y1) * (h or 1);
        else
            local map1 = _mapdata[map1];
            local map2 = _mapdata[map2];
            if(map1.system == map2.system) then
                x1, y1 = _getpos(map1, floor1, x1, y1);
                x2, y2 = _getpos(map2, floor2, x2, y2);
                xDelta = (x2 - x1);
                yDelta = (y2 - y1);
            else
                local s1 = map1.system;
                local s2 = map2.system;
                if((map1==0 or _mapdata[0][s1]) and (map2 == 0 or _mapdata[0][s2])) then
                    x1, y1 = _getpos(map1, floor1, x1, y1);
                    x2, y2 = _getpos(map2, floor2, x2, y2);
                    if(map1 ~= 0) then
                        local cont1 = _mapdata[0][s1];
                        x1 = (x1 - cont1.xOffset) * cont1.scale;
                        y1 = (y1 - cont1.yOffset) * cont1.scale;
                    end
                    if(map2 ~= 0) then
                        local cont2 = _mapdata[0][s2];
                        x2 = (x2 - cont2.xOffset) * cont2.scale;
                        y2 = (y2 - cont2.yOffset) * cont2.scale;
                    end
                    xDelta = x2 - x1;
                    yDelta = y2 - y1;
                end
            end
        end

        if(xDelta and yDelta) then
            local playerAngle = GetPlayerFacing()
            dist = sqrt(xDelta * xDelta + yDelta * yDelta);
            angle = (radian90 - playerAngle) - atan2(yDelta, xDelta)
        end

        return dist, angle;
    end
end

do
    local function _findunit(unit, doNotCheckMap)
        local x, y = GetPlayerMapPosition(unit);
        if(x <= 0 and y <= 0) then
            if(doNotCheckMap) then return; end
            local lastMapID, lastFloor = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel();
            SetMapToCurrentZone();
            x, y = GetPlayerMapPosition(unit);
            if(x <= 0 and y <= 0) then
                    if(ZoomOut()) then
                    elseif(GetCurrentMapZone() ~= WORLDMAP_WORLD_ID) then
                        SetMapZoom(GetCurrentMapContinent());
                    else
                        SetMapZoom(WORLDMAP_WORLD_ID);
                    end
                x, y = GetPlayerMapPosition(unit);
                if(x <= 0 and y <= 0) then
                    return;
                end
            end
            local thisMapID, thisFloor = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel();
            if(thisMapID ~= lastMapID or thisFloor ~= lastFloor) then
                SetMapByID(lastMapID);
                SetDungeonMapLevel(lastFloor);
            end
            return thisMapID, thisFloor, x, y;
        end
        return GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(), x, y;
    end

    local function _findplayer()
        local x, y = GetPlayerMapPosition("player");
        if(x <= 0 and y <= 0) then
            if(WorldMap and WorldMap:IsShown()) then return end
            SetMapToCurrentZone();
            x, y = GetPlayerMapPosition("player");
            if(x <= 0 and y <= 0) then
                    if(ZoomOut()) then
                    elseif(GetCurrentMapZone() ~= WORLDMAP_WORLD_ID) then
                        SetMapZoom(GetCurrentMapContinent());
                    else
                        SetMapZoom(WORLDMAP_WORLD_ID);
                    end
                x, y = GetPlayerMapPosition("player");
                if(x <= 0 and y <= 0) then
                    return;
                end
            end
        end
        return GetCurrentMapAreaID(), GetCurrentMapDungeonLevel(), x, y;
    end

    function GetTarget(unit, doNotCheckMap)
        local plot1, plot2, plot3, plot4;
        if unit == "player" or UnitIsUnit("player", unit) then
            plot1, plot2, plot3, plot4 = _findplayer()
        else
            plot1, plot2, plot3, plot4 = _findunit(unit, doNotCheckMap or WorldMapFrame:IsVisible())
        end
        if not (plot1 and plot4) then
            return false
        else
            return true, plot1, plot2, plot3, plot4
        end
    end

    function GetFromPlayer(unit, noMapLocation)
        if(WorldMap and WorldMap:IsShown()) then return end
        local plot3, plot4 = GetPlayerMapPosition("player");
        if(plot3 <= 0 and plot4 <= 0) then
            SetMapToCurrentZone();
            plot3, plot4 = GetPlayerMapPosition("player");
            if(plot3 <= 0 and plot4 <= 0) then
                    if(ZoomOut()) then
                    elseif(GetCurrentMapZone() ~= WORLDMAP_WORLD_ID) then
                        SetMapZoom(GetCurrentMapContinent());
                    else
                        SetMapZoom(WORLDMAP_WORLD_ID);
                    end
                plot3, plot4 = GetPlayerMapPosition("player");
                if(plot3 <= 0 and plot4 <= 0) then
                    return;
                end
            end
        end

        local plot1 = GetCurrentMapAreaID()
        local plot2 = GetCurrentMapDungeonLevel()

        local plot5, plot6;
        local plot7, plot8 = GetPlayerMapPosition(unit);

        if(noMapLocation and (plot7 <= 0 and plot8 <= 0)) then
            local lastMapID, lastFloor = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel();
            SetMapToCurrentZone();
            plot7, plot8 = GetPlayerMapPosition(unit);
            if(plot7 <= 0 and plot8 <= 0) then
                    if(ZoomOut()) then
                    elseif(GetCurrentMapZone() ~= WORLDMAP_WORLD_ID) then
                        SetMapZoom(GetCurrentMapContinent());
                    else
                        SetMapZoom(WORLDMAP_WORLD_ID);
                    end
                plot7, plot8 = GetPlayerMapPosition(unit);
                if(plot7 <= 0 and plot8 <= 0) then
                    return;
                end
            end
            plot5, plot6 = GetCurrentMapAreaID(), GetCurrentMapDungeonLevel();
            if(plot5 ~= lastMapID or plot6 ~= lastFloor) then
                SetMapByID(lastMapID);
                SetDungeonMapLevel(lastFloor);
            end
            local distance, angle = GetDistance(plot1, plot2, plot3, plot4, plot5, plot6, plot7, plot8)
            return distance, angle
        end

        local distance, angle = GetDistance(plot1, plot2, plot3, plot4, plot1, plot2, plot7, plot8)
        return distance, angle
    end
end

function Triangulate(unit, noMapLocation)
    local distance, angle = GetFromPlayer(unit, noMapLocation)
    return distance, angle
end
--[[
##########################################################
MAIN MOVABLE TRACKER
##########################################################
]]--
function PLUGIN:PLAYER_TARGET_CHANGED()
    if not SVUI_TrackingDoodad then return end
    if((UnitInParty("target") or UnitInRaid("target")) and not UnitIsUnit("target", "player")) then
        SVUI_TrackingDoodad.Trackable = true
        SVUI_TrackingDoodad:Show()
    else
        SVUI_TrackingDoodad.Trackable = false
    end
end

local Rotate_Arrow = function(self, angle)
    local radius, ULx, ULy, LLx, LLy, URx, URy, LRx, LRy

    radius = angle - 0.785398163
    URx = 0.5 + cos(radius) / sqrt2
    URy =  0.5 + sin(radius) / sqrt2
    -- (-1)
    radius = angle + 0.785398163
    LRx = 0.5 + cos(radius) / sqrt2
    LRy =  0.5 + sin(radius) / sqrt2
    -- 1
    radius = angle + 2.35619449
    LLx = 0.5 + cos(radius) / sqrt2
    LLy =  0.5 + sin(radius) / sqrt2
    -- 3
    radius = angle + 3.92699082
    ULx = 0.5 + cos(radius) / sqrt2
    ULy =  0.5 + sin(radius) / sqrt2
    -- 5

    self.Arrow:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy);
end

local TargetFrame_OnChange = function()
    if not SVUI_TrackingDoodad then return end
    if((UnitInParty("target") or UnitInRaid("target")) and not UnitIsUnit("target", "player")) then
        SVUI_TrackingDoodad.Trackable = true
        SVUI_TrackingDoodad:Show()
    else
        SVUI_TrackingDoodad.Trackable = false
    end
end

local Tracker_OnUpdate = function(self, elapsed)
    if self.elapsed and self.elapsed > (self.throttle or 0.02) then
        if(self.Trackable) then
            local distance, angle = Triangulate("target", true)
            local _ARROW = self.Arrow
            local _SPINNER = self.Radar
            local _TEXT = self.Range
            local _BG = self.BG
            if not angle then
                self.throttle = 4
                _ARROW:SetAlpha(0)
                _SPINNER:SetVertexColor(0.8,0.1,0.1,0.15)
                _BG:SetVertexColor(1,0,0,0.15)
            else
                self.throttle = 0.02
                local out = floor(tonumber(distance))
                self:Spin(angle)
                if(out > 100) then
                    _ARROW:SetVertexColor(1,0.1,0.1,0.4)
                    _SPINNER:SetVertexColor(0.8,0.1,0.1,0.25)
                    _BG:SetVertexColor(0.8,0.4,0.1,0.25)
                elseif(out > 40) then
                    _ARROW:SetVertexColor(1,0.8,0.1,0.6)
                    _SPINNER:SetVertexColor(0.8,0.8,0.1,0.5)
                    _BG:SetVertexColor(0.4,0.8,0.1,0.5)
                elseif(out > 5) then
                    _ARROW:SetVertexColor(0.1,1,0.8,0.9)
                    _SPINNER:SetVertexColor(0.1,0.8,0.8,0.75)
                    _BG:SetVertexColor(0.1,0.8,0.1,0.75)
                end
                _ARROW:SetAlpha(1)
                _TEXT:SetText(out)
            end
        else
            self:Hide()
        end
        self.elapsed = 0
    else
        self.elapsed = (self.elapsed or 0) + elapsed
    end
end
--[[
##########################################################
GROUP UNIT TRACKERS
##########################################################
]]--
local GPS_Triangulate = function(self, unit)
    local available = (self.OnlyProximity == false and self.onMouseOver == false)
    local distance, angle = Triangulate(unit, available)
    return distance, angle
end

local RefreshGPS = function(self, frame, template)
    if(frame.GPS) then
        local config = PLUGIN.db
        if(config.groups) then
            frame.GPS.OnlyProximity = config.proximity
            local actualSz = min(frame.GPS.DefaultSize, (frame:GetHeight() - 2))
            if(not frame:IsElementEnabled("GPS")) then
                frame:EnableElement("GPS")
            end
        else
            if(frame:IsElementEnabled("GPS")) then
                frame:DisableElement("GPS")
            end
        end
    end
end

function PLUGIN:CreateGPS(frame)
    if not frame then return end
    local size = 32

    local gps = CreateFrame("Frame", nil, frame.InfoPanel)
    gps:SetFrameLevel(99)
    gps:Size(size, size)
    gps.DefaultSize = size
    gps:Point("RIGHT", frame, "RIGHT", 0, 0)

    gps.Arrow = gps:CreateTexture(nil, "OVERLAY", nil, 7)
    gps.Arrow:SetTexture([[Interface\AddOns\SVUI_TrackingDevice\artwork\GPS-ARROW]])
    gps.Arrow:Size(size, size)
    gps.Arrow:SetPoint("CENTER", gps, "CENTER", 0, 0)
    gps.Arrow:SetVertexColor(0.1, 0.8, 0.8)
    gps.Arrow:SetBlendMode("ADD")

    gps.onMouseOver = true
    gps.OnlyProximity = false

    gps.Spin = Rotate_Arrow

    frame.GPS = gps

    --frame.GPS:Hide()
end
--[[
##########################################################
CORE
##########################################################
]]--
function PLUGIN:ReLoad()
    if(not SV.db[SCHEMA].enable) then return end

    local frameSize = self.db.size or 70
    local arrowSize = frameSize * 0.5
    local fontSize = self.db.fontSize or 14
    local frame = _G["SVUI_TrackingDoodad"]

    frame:SetSize(frameSize, frameSize)
    frame.Arrow:SetSize(arrowSize, arrowSize)
    frame.Range:SetFont(SV.Media.font.roboto, fontSize, "OUTLINE")
end

function PLUGIN:Load()
    if(not SV.db[SCHEMA].enable) then return end

    local _TRACKER = SVUI_TrackingDoodad
    local _TARGET = SVUI_Target

    if(_TRACKER) then
        self.db = SV.db[SCHEMA]

        _TRACKER.Spin = Rotate_Arrow
        _TRACKER:SetParent(SVUI_Target)
        _TRACKER:SetScript("OnUpdate", Tracker_OnUpdate)

        self:RegisterEvent("PLAYER_TARGET_CHANGED")
    end

    if(_TARGET) then
        local frame = _G["SVUI_TrackingDoodad"]
        frame:SetPoint("LEFT", _TARGET, "RIGHT", 2, 0)
        _TARGET:HookScript("OnShow", TargetFrame_OnChange)
    end

    NewHook(SV.SVUnit, "RefreshUnitLayout", RefreshGPS)

    local options = {
        order = 3,
        name = L["GPS"],
        desc = L["Use group frame GPS elements"],
        type = "toggle",
        get = function() return SV.db.SVTracker.groups end,
        set = function(key,value) PLUGIN:ChangeDBVar(value, key[#key]); PLUGIN:UpdateLogWindow() end
    }

    self:AddOption("groups", options)

    options = {
        order = 4,
        name = L["GPS Proximity"],
        desc = L["Only point to closest low health unit"],
        type = "toggle",
        get = function() return SV.db.SVTracker.proximity end,
        set = function(key,value) PLUGIN:ChangeDBVar(value, key[#key]); PLUGIN:UpdateLogWindow() end
    }

    self:AddOption("proximity", options)

    options = {
        order = 5,
        name = L["Font Size"],
        desc = L["Set the font size of the range text"],
        type = "range",
        min = 6,
        max = 22,
        step = 1,
        get = function() return SV.db.SVTracker.fontSize end,
        set = function(key,value) PLUGIN:ChangeDBVar(value, key[#key]); PLUGIN:UpdateLogWindow() end
    }

    self:AddOption("fontSize", options)
end

CONFIGS[SCHEMA] = {
    ["enable"] = true,
    ["size"] = 75,
    ["fontSize"] = 12,
    ["groups"] = true,
    ["proximity"] = false,
}

Registry:NewPlugin(PLUGIN)