--[[
********************************************************************************
Routes
v1 .6 .7
16 October 2014
( Originally written for Live Servers v4 .3 .0 .15050 )
( Hotfixed for v6 .0 .2 .19034 )
Author : Xaroz @ EU Emerald Dream Alliance & Xinhuan @ US Blackrock Alliance
********************************************************************************
Description :
Routes allow you to draw lines on the worldmap linking nodes together into
an efficient farming route from existing databases . The route will be shown
( by default ) on the minimap and zone map as well .
Features :
- Select node - types to build a line upon . The following are supported
* Cartographer_Fishing
* Cartographer_Mining
* Cartographer_Herbalism
* Cartographer_ExtractGas
* Cartographer_Treasure
* GatherMate
* GatherMate2
* Gatherer
* HandyNotes
- Optimize your route using the traveling salesmen problem ( TSP ) ant
colony optimization ( ACO ) algorithm
- Background ( nonblocking ) and foreground ( blocking ) optimization
- Select color / thickness / transparency / visibility for each route
- For any route created , finding a new node will try to add that as
optimal as possible
- Quick clustering algorithm to merge nearby nodes into a single traveling
point
- Quickly mark entire areas / regions as " out of bounds " or " taboo " to Routes ,
meaning your routes will ignore nodes in those areas and avoid cross them
- Fubar plugin available to quickly access your routes
- Cartographer_Waypoints and TomTom support for quickly following a route
- Works with Chinchilla ' s Expander minimap and SexyMap ' s HudMap !
- Full in - game help file and FAQ , guiding you step by step on what to do !
Download :
The latest version of Routes is always available on
- http : // www.wowace . com / projects / routes /
- http : // wow.curse . com / downloads / wow - addons / details / routes.aspx
- http : // www.wowinterface . com / downloads / info11401 - Routes.html
Localization :
You can contribute by updating / adding localizations using the system on
- http : // www.wowace . com / projects / routes / localization /
Contact :
If you find any bugs or have any suggestions , you can contact us on :
Forum : http : // forums.wowace . com / showthread.php ? t = 10369
IRC : Grum or Xinhuan on irc : // irc.freenode . org / wowace
Email : Grum ( routes AT grum DOT nl )
Xinhuan ( xinhuan AT gmail DOT com )
Paypal donations are welcome ; )
] ]
Routes = LibStub ( " AceAddon-3.0 " ) : NewAddon ( " Routes " , " AceConsole-3.0 " , " AceEvent-3.0 " , " AceHook-3.0 " )
local Routes = Routes
local L = LibStub ( " AceLocale-3.0 " ) : GetLocale ( " Routes " , false )
local G = { } -- was Graph-1.0, but we removed the dependency
Routes.G = G
Routes.Dragons = LibStub ( " HereBeDragons-2.0 " )
local WoW90 = select ( 4 , GetBuildInfo ( ) ) >= 90000
-- database defaults
local db
local defaults = {
global = {
routes = {
[ ' * ' ] = { -- zone name, stored as the MapFile string constant
[ ' * ' ] = { -- route name
route = { } , -- point, point, point
color = nil , -- defaults to db.defaults.color if nil
width = nil , -- defaults to db.defaults.width if nil
width_minimap = nil , -- defaults to db.defaults.width_minimap if nil
width_battlemap = nil , -- defaults to db.defaults.width_battlemap if nil
hidden = false , -- boolean
looped = 1 , -- looped? 1 is used (instead of true) because initial early code used 1 inside route creation code
visible = true , -- visible?
length = 0 , -- length
selection = {
[ ' ** ' ] = false -- Node we're interested in tracking
} ,
db_type = {
[ ' ** ' ] = false -- db_types used for use with auto show/hide
} ,
taboos = {
[ ' ** ' ] = false -- taboo regions in effect
} ,
taboolist = { } -- point, point, point
} ,
} ,
} ,
taboo = {
[ ' * ' ] = { -- zone name, stored as the MapFile string constant
[ ' * ' ] = { -- route name
route = { } , -- point, point, point
} ,
} ,
} ,
defaults = { -- r, g, b, a
color = { 1 , 0.75 , 0.75 , 1 } ,
hidden_color = { 1 , 1 , 1 , 0.5 } ,
width = 30 ,
width_minimap = 25 ,
width_battlemap = 15 ,
show_hidden = false ,
update_distance = 1 ,
fake_point = - 1 ,
fake_data = ' dummy ' ,
draw_minimap = true ,
draw_worldmap = true ,
draw_battlemap = true ,
draw_indoors = false ,
tsp = {
initial_pheromone = 0.1 , -- Initial pheromone trail value
alpha = 1 , -- Likelihood of ants to follow pheromone trails (larger value == more likely)
beta = 6 , -- Likelihood of ants to choose closer nodes (larger value == more likely)
local_decay = 0.2 , -- Governs local trail decay rate [0, 1]
local_update = 0.4 , -- Amount of pheromone to reinforce local trail update by
global_decay = 0.2 , -- Governs global trail decay rate [0, 1]
twoopt_passes = 3 , -- Number of times to perform 2-opt passes
two_point_five_opt = false , -- Perform optimized 2-opt pass
} ,
prof_options = {
[ ' * ' ] = " Always " ,
} ,
use_auto_showhide = false ,
waypoint_hit_distance = 50 ,
line_gaps = true ,
line_gaps_skip_cluster = true ,
cluster_dist = 60 ,
callbacks = {
[ ' * ' ] = true
}
} ,
}
}
-- Ace Options Table for our addon
local options
-- Plugins table
Routes.plugins = { }
-- Lookup table for aceoptkey-route/taboo conversion
Routes.routekeys = setmetatable ( { } , { __index = function ( t , k ) if k == " string " and tonumber ( k ) then return t [ tonumber ( k ) ] end return nil end } )
Routes.tabookeys = setmetatable ( { } , { __index = function ( t , k ) if k == " string " and tonumber ( k ) then return t [ tonumber ( k ) ] end return nil end } )
-- localize some globals
local pairs , next = pairs , next
local tinsert , tremove = tinsert , tremove
local floor = math.floor
local format = string.format
local math_abs = math.abs
local math_sin = math.sin
local math_cos = math.cos
local Minimap = Minimap
local GetPlayerFacing = GetPlayerFacing
------------------------------------------------------------------------------------------------------
-- Data for Localized Zone Names
local function GetZoneName ( uiMapID )
local name = Routes.Dragons : GetLocalizedMap ( uiMapID )
if uiMapID == 104 or uiMapID == 107 then -- Outland SMV and Nagrand
name = format ( " %s (%s) " , name , Routes.Dragons : GetLocalizedMap ( 101 ) )
elseif uiMapID == 125 then -- Northrend Dalaran
name = format ( " %s (%s) " , name , Routes.Dragons : GetLocalizedMap ( 113 ) )
end
return name
end
local function GetZoneNameSafe ( uiMapID )
local name = GetZoneName ( uiMapID )
return name or ( " Zone #%s " ) : format ( tostring ( uiMapID ) )
end
Routes.LZName = setmetatable ( { } , { __index = function ( ) return 0 end } )
local function processMapChildrenRecursive ( parent )
local children = C_Map.GetMapChildrenInfo ( parent )
if children and # children > 0 then
for i = 1 , # children do
local id = children [ i ] . mapID
if id then
if children [ i ] . mapType == Enum.UIMapType . Zone or children [ i ] . mapType == Enum.UIMapType . Continent then
local name = GetZoneName ( id )
Routes.LZName [ name ] = id
processMapChildrenRecursive ( id )
elseif children [ i ] . mapType == Enum.UIMapType . World then
processMapChildrenRecursive ( id )
end
end
end
end
end
local COSMIC_MAP_ID = 946
local WORLD_MAP_ID = 947
if WOW_PROJECT_ID == WOW_PROJECT_CLASSIC then
processMapChildrenRecursive ( WORLD_MAP_ID )
else
processMapChildrenRecursive ( COSMIC_MAP_ID )
end
------------------------------------------------------------------------------------------------------
-- Core Routes functions
--[[ Our coordinate format for Routes
Warning : These are convenience functions , most of the : getXY ( ) and : getID ( )
code are inlined in critical code paths in various functions , changing
the coord storage format requires changing the inlined code in numerous
locations in addition to these 2 functions
] ]
function Routes : getID ( x , y )
return floor ( x * 10000 + 0.5 ) * 10000 + floor ( y * 10000 + 0.5 )
end
function Routes : getXY ( id )
return floor ( id / 10000 ) / 10000 , ( id % 10000 ) / 10000
end
local MinimapShapes = {
-- quadrant booleans (same order as SetTexCoord)
-- {upper-left, lower-left, upper-right, lower-right}
-- true = rounded, false = squared
[ " ROUND " ] = { true , true , true , true } ,
[ " SQUARE " ] = { false , false , false , false } ,
[ " CORNER-TOPLEFT " ] = { true , false , false , false } ,
[ " CORNER-TOPRIGHT " ] = { false , false , true , false } ,
[ " CORNER-BOTTOMLEFT " ] = { false , true , false , false } ,
[ " CORNER-BOTTOMRIGHT " ] = { false , false , false , true } ,
[ " SIDE-LEFT " ] = { true , true , false , false } ,
[ " SIDE-RIGHT " ] = { false , false , true , true } ,
[ " SIDE-TOP " ] = { true , false , true , false } ,
[ " SIDE-BOTTOM " ] = { false , true , false , true } ,
[ " TRICORNER-TOPLEFT " ] = { true , true , true , false } ,
[ " TRICORNER-TOPRIGHT " ] = { true , false , true , true } ,
[ " TRICORNER-BOTTOMLEFT " ] = { true , true , false , true } ,
[ " TRICORNER-BOTTOMRIGHT " ] = { false , true , true , true } ,
}
local minimap_radius
local minimap_rotate
local indoors = " indoor "
local MinimapSize = { -- radius of minimap
indoor = {
[ 0 ] = 150 ,
[ 1 ] = 120 ,
[ 2 ] = 90 ,
[ 3 ] = 60 ,
[ 4 ] = 40 ,
[ 5 ] = 25 ,
} ,
outdoor = {
[ 0 ] = 233 + 1 / 3 ,
[ 1 ] = 200 ,
[ 2 ] = 166 + 2 / 3 ,
[ 3 ] = 133 + 1 / 3 ,
[ 4 ] = 100 ,
[ 5 ] = 66 + 2 / 3 ,
} ,
}
local function is_round ( dx , dy )
local map_shape = GetMinimapShape and GetMinimapShape ( ) or " ROUND "
local q = 1
if dx > 0 then q = q + 2 end -- right side
-- XXX Tripple check this
if dy > 0 then q = q + 1 end -- bottom side
return MinimapShapes [ map_shape ] [ q ]
end
local function is_inside ( sx , sy , cx , cy , radius )
local dx = sx - cx
local dy = sy - cy
if is_round ( dx , dy ) then
return dx * dx + dy * dy <= radius * radius
else
return math_abs ( dx ) <= radius and math_abs ( dy ) <= radius
end
end
local function GetFacing ( )
if GetPlayerFacing then return GetPlayerFacing ( ) end
return - MiniMapCompassRing : GetFacing ( )
end
local last_X , last_Y , last_facing = math.huge , math.huge , math.huge
-- implementation of cache - use zone in the key for an unique identifier
-- because every zone has a different X/Y location and possible yardsizes
local cache_zone , cache_zoneW , cache_zoneH
local X_cache = { }
local Y_cache = { }
local XY_cache_mt = {
__index = function ( t , key )
local zone , coord = ( ' ; ' ) : split ( key )
if cache_zone ~= zone then
cache_zoneW , cache_zoneH = Routes.Dragons : GetZoneSize ( tonumber ( zone ) )
cache_zone = zone
end
local X = cache_zoneW * floor ( coord / 10000 ) / 10000
local Y = cache_zoneH * ( coord % 10000 ) / 10000
X_cache [ key ] = X
Y_cache [ key ] = Y
-- figure out which one to return
if t == X_cache then return X else return Y end
end
}
setmetatable ( X_cache , XY_cache_mt )
setmetatable ( Y_cache , XY_cache_mt )
function Routes : DrawMinimapLines ( forceUpdate )
if not db.defaults . draw_minimap then
G : HideLines ( Minimap )
return
end
local _x , _y , currentZoneID = self.Dragons : GetPlayerZonePosition ( true )
-- invalid coordinates - clear map
if not _x or not _y then
G : HideLines ( Minimap )
return
end
-- if we are indoors, or the zone we are in is not defined in our tables ... no routes
-- double check zoom as onload doesnt get you the map zoom
indoors = GetCVar ( " minimapZoom " ) + 0 == Minimap : GetZoom ( ) and " outdoor " or " indoor "
if not db.defaults . draw_indoors and indoors == " indoor " then
G : HideLines ( Minimap )
return
end
local defaults = db.defaults
local zoneW , zoneH = self.Dragons : GetZoneSize ( currentZoneID )
if not zoneW or zoneW == 0 then return end
local cx , cy = zoneW * _x , zoneH * _y
local facing , sin , cos
if minimap_rotate then
facing = GetFacing ( )
end
if ( not forceUpdate ) and facing == last_facing and ( last_X - cx ) ^ 2 + ( last_Y - cy ) ^ 2 < defaults.update_distance ^ 2 then
-- no update!
return
end
G : HideLines ( Minimap )
last_X = cx
last_Y = cy
last_facing = facing
if minimap_rotate then
sin = math_sin ( facing )
cos = math_cos ( facing )
end
if WoW90 then
minimap_radius = C_Minimap.GetViewRadius ( )
else
minimap_radius = MinimapSize [ indoors ] [ Minimap : GetZoom ( ) ]
end
local radius = minimap_radius
local radius2 = radius * radius
local minX = cx - radius
local maxX = cx + radius
local minY = cy - radius
local maxY = cy + radius
local div_by_zero_nudge = 0.000001
local minimap_w = Minimap : GetWidth ( )
local minimap_h = Minimap : GetHeight ( )
local scale_x = minimap_w / ( radius * 2 )
local scale_y = minimap_h / ( radius * 2 )
local minimapScale = Minimap : GetScale ( )
for route_name , route_data in pairs ( db.routes [ currentZoneID ] ) do
if type ( route_data ) == " table " and type ( route_data.route ) == " table " and # route_data.route > 1 then
-- store color/width
local width = ( route_data.width_minimap or defaults.width_minimap ) / ( minimapScale )
local color = route_data.color or defaults.color
-- unless we show hidden
if ( not route_data.hidden and ( route_data.visible or not defaults.use_auto_showhide ) ) or defaults.show_hidden then
-- use this special color
if route_data.hidden then
color = defaults.hidden_color
end
-- some state data
local last_x = nil
local last_y = nil
local last_inside = nil
-- if we loop - make sure the 'last' gets filled with the right info
if route_data.looped and route_data.route [ # route_data.route ] ~= defaults.fake_point then
local key = format ( " %s;%s " , currentZoneID , route_data.route [ # route_data.route ] )
last_x , last_y = X_cache [ key ] , Y_cache [ key ]
if minimap_rotate then
local dx = last_x - cx
local dy = last_y - cy
last_x = cx + dx * cos - dy * sin
last_y = cy + dx * sin + dy * cos
end
last_inside = is_inside ( last_x , last_y , cx , cy , radius )
end
-- loop over the route
for i = 1 , # route_data.route do
local point = route_data.route [ i ]
local cur_x , cur_y , cur_inside
-- if we have a 'fake point' (gap) - clear current values
if point == defaults.fake_point then
cur_x = nil
cur_y = nil
cur_inside = false
else
local key = format ( " %s;%s " , currentZoneID , point )
cur_x , cur_y = X_cache [ key ] , Y_cache [ key ]
if minimap_rotate then
local dx = cur_x - cx
local dy = cur_y - cy
cur_x = cx + dx * cos - dy * sin
cur_y = cy + dx * sin + dy * cos
end
cur_inside = is_inside ( cur_x , cur_y , cx , cy , radius )
end
-- check if we have any nil values (we cant draw) and check boundingbox
if cur_x and cur_y and last_x and last_y and not (
( cur_x < minX and last_x < minX ) or
( cur_x > maxX and last_x > maxX ) or
( cur_y < minY and last_y < minY ) or
( cur_y > maxY and last_y > maxY )
)
then
-- default all to not drawing
local draw_sx = nil
local draw_sy = nil
local draw_ex = nil
local draw_ey = nil
-- both inside - easy! draw
if cur_inside and last_inside then
draw_sx = last_x
draw_sy = last_y
draw_ex = cur_x
draw_ey = cur_y
else
-- direction of line
local dx = last_x - cur_x
local dy = last_y - cur_y
-- calculate point on perpendicular line
local zx = cx - dy
local zy = cy + dx
-- nudge it a bit so we dont get div by 0 problems
if dx == 0 then dx = div_by_zero_nudge end
if dy == 0 then dy = div_by_zero_nudge end
-- calculate intersection point
local nd = ( ( cx - last_x ) * ( cy - zy ) - ( cx - zx ) * ( cy - last_y ) ) /
( ( cur_x - last_x ) * ( cy - zy ) - ( cx - zx ) * ( cur_y - last_y ) )
-- perpendicular point (closest to center on the line given)
local px = last_x + nd * - dx
local py = last_y + nd * - dy
-- check range of intersect point
local dpc_x = cx - px
local dpc_y = cy - py
-- distance^2 of the perpendicular point
local lenpc = dpc_x * dpc_x + dpc_y * dpc_y
-- the line can only intersect if the perpendicular point is at
-- least closer than the furthest away point (one of the corners)
if lenpc < 2 * radius2 then
-- if inside - ready to draw
if cur_inside then
draw_ex = cur_x
draw_ey = cur_y
else
-- if we're not inside we can still be in the square - if so dont do any intersection
-- calculations yet
if math_abs ( cur_x - cx ) < radius and math_abs ( cur_y - cy ) < radius then
draw_ex = cur_x
draw_ey = cur_y
else
-- need to intersect against the square
-- likely x/y to intersect with
local minimap_cur_x = cx + radius * ( dx < 0 and 1 or - 1 )
local minimap_cur_y = cy + radius * ( dy < 0 and 1 or - 1 )
-- which intersection is furthest?
local delta_cur_x = ( minimap_cur_x - cur_x ) / - dx
local delta_cur_y = ( minimap_cur_y - cur_y ) / - dy
-- dark magic - needs to be changed to positive signs whenever i can care about it
if delta_cur_x < delta_cur_y and delta_cur_x < 0 then
draw_ex = minimap_cur_x
draw_ey = cur_y + - dy * delta_cur_x
else
draw_ex = cur_x + - dx * delta_cur_y
draw_ey = minimap_cur_y
end
-- check if we didn't calculate some wonky offset - has to be inside with
-- some slack on accuracy
if math_abs ( draw_ex - cx ) > radius * 1.01 or
math_abs ( draw_ey - cy ) > radius * 1.01
then
draw_ex = nil
draw_ey = nil
end
end
-- we might have a round corner here - lets see if the quarter with the intersection is round
if draw_ex and draw_ey and is_round ( draw_ex - cx , draw_ey - cy ) then
-- if we are also within the circle-range
if lenpc < radius2 then
-- circle intersection
local dcx = cx - cur_x
local dcy = cy - cur_y
local len_dc = dcx * dcx + dcy * dcy
local len_d = dx * dx + dy * dy
local len_ddc = dx * dcx + dy * dcy
-- discriminant
local d_sqrt = ( len_ddc * len_ddc - len_d * ( len_dc - radius2 ) ) ^ 0.5
-- calculate point
draw_ex = cur_x - dx * ( - len_ddc + d_sqrt ) / len_d
draw_ey = cur_y - dy * ( - len_ddc + d_sqrt ) / len_d
-- have to be on the *same* side of the perpendicular point else it's fake
if ( draw_ex - px ) / math_abs ( draw_ex - px ) ~= ( cur_x - px ) / math_abs ( cur_x - px ) or
( draw_ey - py ) / math_abs ( draw_ey - py ) ~= ( cur_y - py ) / math_abs ( cur_y - py )
then
draw_ex = nil
draw_ey = nil
end
else
draw_ex = nil
draw_ey = nil
end
end
end
-- if inside - ready to draw
if last_inside then
draw_sx = last_x
draw_sy = last_y
else
-- if we're not inside we can still be in the square - if so dont do any intersection
-- calculations yet
if math_abs ( last_x - cx ) < radius and math_abs ( last_y - cy ) < radius then
draw_sx = last_x
draw_sy = last_y
else
-- need to intersect against the square
-- likely x/y to intersect with
local minimap_last_x = cx + radius * ( dx > 0 and 1 or - 1 )
local minimap_last_y = cy + radius * ( dy > 0 and 1 or - 1 )
-- which intersection is furthest?
local delta_last_x = ( minimap_last_x - last_x ) / dx
local delta_last_y = ( minimap_last_y - last_y ) / dy
-- dark magic - needs to be changed to positive signs whenever i can care about it
if delta_last_x < delta_last_y and delta_last_x < 0 then
draw_sx = minimap_last_x
draw_sy = last_y + dy * delta_last_x
else
draw_sx = last_x + dx * delta_last_y
draw_sy = minimap_last_y
end
-- check if we didn't calculate some wonky offset - has to be inside with
-- some slack on accuracy
if math_abs ( draw_sx - cx ) > radius * 1.01 or
math_abs ( draw_sy - cy ) > radius * 1.01
then
draw_sx = nil
draw_sy = nil
end
end
-- we might have a round corner here - lets see if the quarter with the intersection is round
if draw_sx and draw_sy and is_round ( draw_sx - cx , draw_sy - cy ) then
-- if we are also within the circle-range
if lenpc < radius2 then
-- circle intersection
local dcx = cx - cur_x
local dcy = cy - cur_y
local len_dc = dcx * dcx + dcy * dcy
local len_d = dx * dx + dy * dy
local len_ddc = dx * dcx + dy * dcy
-- discriminant
local d_sqrt = ( len_ddc * len_ddc - len_d * ( len_dc - radius2 ) ) ^ 0.5
-- calculate point
draw_sx = cur_x - dx * ( - len_ddc - d_sqrt ) / len_d
draw_sy = cur_y - dy * ( - len_ddc - d_sqrt ) / len_d
-- have to be on the *same* side of the perpendicular point else it's fake
if ( draw_sx - px ) / math_abs ( draw_sx - px ) ~= ( last_x - px ) / math_abs ( last_x - px ) or
( draw_sy - py ) / math_abs ( draw_sy - py ) ~= ( last_y - py ) / math_abs ( last_y - py )
then
draw_sx = nil
draw_sy = nil
end
else
draw_sx = nil
draw_sy = nil
end
end
end
end
end
if draw_sx and draw_sy and draw_ex and draw_ey then
-- translate to left bottom corner and apply scale
draw_sx = ( draw_sx - minX ) * scale_x
draw_sy = minimap_h - ( draw_sy - minY ) * scale_y
draw_ex = ( draw_ex - minX ) * scale_x
draw_ey = minimap_h - ( draw_ey - minY ) * scale_y
if defaults.line_gaps then
-- shorten the line by 5 pixels (scaled) on endpoints inside the Minimap
local gapConst = 5 / minimapScale
local dx = draw_sx - draw_ex
local dy = draw_sy - draw_ey
local l = ( dx * dx + dy * dy ) ^ 0.5
local x = gapConst * dx / l
local y = gapConst * dy / l
local shorten1 , shorten2
if last_inside then shorten1 = true else shorten1 = false end
if cur_inside then shorten2 = true else shorten2 = false end
if shorten2 and route_data.metadata and defaults.line_gaps_skip_cluster and # route_data.metadata [ i ] > 1 then
shorten2 = false
end
if shorten1 and route_data.metadata and defaults.line_gaps_skip_cluster and # route_data.metadata [ ( i - 1 == 0 ) and # route_data.route or i - 1 ] > 1 then
shorten1 = false
end
if shorten1 and shorten2 and l > ( gapConst * 2 ) then -- draw if line is 10 or more pixels (scaled)
G : DrawLine ( Minimap , draw_sx - x , draw_sy - y , draw_ex + x , draw_ey + y , width , color , " ARTWORK " )
elseif shorten1 and not shorten2 and l > gapConst then
G : DrawLine ( Minimap , draw_sx - x , draw_sy - y , draw_ex , draw_ey , width , color , " ARTWORK " )
elseif shorten2 and not shorten1 and l > gapConst then
G : DrawLine ( Minimap , draw_sx , draw_sy , draw_ex + x , draw_ey + y , width , color , " ARTWORK " )
elseif not shorten1 and not shorten2 then
G : DrawLine ( Minimap , draw_sx , draw_sy , draw_ex , draw_ey , width , color , " ARTWORK " )
end
else
G : DrawLine ( Minimap , draw_sx , draw_sy , draw_ex , draw_ey , width , color , " ARTWORK " )
end
end
end
-- store last point
last_x = cur_x
last_y = cur_y
last_inside = cur_inside
end
end
end
end
end
-- This frame is to throttle InsertNode() and DeleteNode() calls so that
-- redrawing the map lines are delayed by 1 frame. These 2 functions can
-- potentially be spammed by a source database importing nodes.
local throttleFrame = CreateFrame ( " Frame " )
throttleFrame : Hide ( )
throttleFrame : SetScript ( " OnUpdate " , function ( self , elapsed )
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
self : Hide ( )
end )
-- Accepts a zone name, coord and node_name
-- for inserting into relevant routes
-- Zone name must be localized, node_name can be english or localized
function Routes : InsertNode ( zone , coord , node_name )
for route_name , route_data in pairs ( db.routes [ self.LZName [ zone ] ] ) do
-- for every route check if the route is created with this node
if route_data.selection then
for k , v in pairs ( route_data.selection ) do
if k == node_name or v == node_name then
-- Add the node
local x , y = self : getXY ( coord )
local flag = false
for tabooname , used in pairs ( route_data.taboos ) do
if used and self : IsNodeInTaboo ( x , y , db.taboo [ self.LZName [ zone ] ] [ tabooname ] ) then
flag = true
end
end
if flag then
tinsert ( route_data.taboolist , coord )
else
route_data.length = self.TSP : InsertNode ( route_data.route , route_data.metadata , self.LZName [ zone ] , coord , route_data.cluster_dist or 65 ) -- 65 is the old default
throttleFrame : Show ( )
end
break
end
end
end
end
end
-- Accepts a zone name, coord and node_name
-- for deleting into relevant routes
-- Zone name must be localized, node_name can be english or localized
function Routes : DeleteNode ( zone , coord , node_name )
for route_name , route_data in pairs ( db.routes [ self.LZName [ zone ] ] ) do
-- for every route check if the route is created with this node
if route_data.selection then
local flag = false
for k , v in pairs ( route_data.selection ) do
if k == node_name or v == node_name then
-- Delete the node if it exists in this route
if route_data.metadata then
-- this is a clustered route
for i = 1 , # route_data.route do
local num_data = # route_data.metadata [ i ]
for j = 1 , num_data do
if coord == route_data.metadata [ i ] [ j ] then
-- recalcuate centroid
local x , y = self : getXY ( coord )
local cx , cy = self : getXY ( route_data.route [ i ] )
if num_data > 1 then
-- more than 1 node in this cluster
cx , cy = ( cx * num_data - x ) / ( num_data - 1 ) , ( cy * num_data - y ) / ( num_data - 1 )
tremove ( route_data.metadata [ i ] , j )
route_data.route [ i ] = self : getID ( cx , cy )
else
-- only 1 node in this cluster, just remove it
tremove ( route_data.metadata , i )
tremove ( route_data.route , i )
end
route_data.length = self.TSP : PathLength ( route_data.route , self.LZName [ zone ] )
throttleFrame : Show ( )
flag = true
break
end
end
if flag then break end
end
else
-- this is not a clustered route
for i = 1 , # route_data.route do
if coord == route_data.route [ i ] then
tremove ( route_data.route , i )
route_data.length = self.TSP : PathLength ( route_data.route , self.LZName [ zone ] )
throttleFrame : Show ( )
flag = true
break
end
end
end
if not flag then
-- node not found yet, so search the taboolist
for i = 1 , # route_data.taboolist do
if route_data.taboolist [ i ] == coord then
tremove ( route_data.taboolist , i )
flag = true
break
end
end
end
end
if flag then break end
end
end
end
end
-- This function upgrades the Routes old storage format which used mapFiles
-- to the new format using uiMapIDs in WoW 8.0
local HBDMigrate = LibStub ( " HereBeDragons-Migrate " )
function Routes : UpgradeStorageFormat2 ( )
local t = { }
for zone , zone_table in pairs ( db.routes ) do
if type ( zone ) == " string " then
-- This zone is a string, not a uiMapID, obtain the ID from HBD-Migrate
local uiMapID = HBDMigrate : GetUIMapIDFromMapFile ( zone )
-- if no mapfile was found, maybe this was a named zone from way before
if not uiMapID then
uiMapID = self.LZName [ zone ]
-- 0 is invalid from the metatable
if uiMapID == 0 then uiMapID = nil end
end
if not uiMapID then
-- invalid zone, delete the whole zone
db.routes [ zone ] = nil
else
-- We found a match, store the zone_table temporarily first
-- and delete the whole zone (because we cannot insert new
-- keys into db.routes[] while iterating over it)
t [ uiMapID ] = zone_table
db.routes [ zone ] = nil
end
end
end
for uiMapID , zone_table in pairs ( t ) do
-- Now assign the new zone uiMapID keys
db.routes [ uiMapID ] = zone_table
end
table.wipe ( t )
-- Do the same with the taboo table
for zone , zone_table in pairs ( db.taboo ) do
if type ( zone ) == " string " then
-- This zone is a string, not a uiMapID, obtain the ID from HBD-Migrate
local uiMapID = HBDMigrate : GetUIMapIDFromMapFile ( zone )
-- if no mapfile was found, maybe this was a named zone from way before
if not uiMapID then
uiMapID = self.LZName [ zone ]
-- 0 is invalid from the metatable
if uiMapID == 0 then uiMapID = nil end
end
if not uiMapID then
-- invalid zone, delete the whole zone
db.taboo [ zone ] = nil
else
-- We found a match, store the zone_table temporarily first
-- and delete the whole zone (because we cannot insert new
-- keys into db.routes[] while iterating over it)
t [ uiMapID ] = zone_table
db.taboo [ zone ] = nil
end
end
end
for uiMapID , zone_table in pairs ( t ) do
-- Now assign the new zone uiMapID keys
db.taboo [ uiMapID ] = zone_table
end
-- Reclaim memory for this function
self.UpgradeStorageFormat2 = nil
end
-- Common subtables for zone and table description
local route_zone_args_desc_table = {
type = " description " ,
name = function ( info )
local zone = tonumber ( info [ 2 ] )
local count = 0
for route_name , route_table in pairs ( db.routes [ zone ] ) do
if # route_table.route > 0 then
count = count + 1
end
end
return L [ " You have |cffffd200%d|r route(s) in |cffffd200%s|r. " ] : format ( count , GetZoneNameSafe ( zone ) )
end ,
order = 0 ,
}
local taboo_zone_args_desc_table = {
type = " description " ,
name = function ( info )
local zone = tonumber ( info [ 2 ] )
local count = 0
for taboo_name , taboo_table in pairs ( db.taboo [ zone ] ) do
if # taboo_table.route > 0 then
count = count + 1
end
end
return L [ " You have |cffffd200%d|r taboo region(s) in |cffffd200%s|r. " ] : format ( count , GetZoneNameSafe ( zone ) )
end ,
order = 0 ,
}
------------------------------------------------------------------------------------------------------
-- General event functions
function Routes : OnInitialize ( )
-- Initialize database
self.db = LibStub ( " AceDB-3.0 " ) : New ( " RoutesDB " , defaults , true )
db = self.db . global
self.options = options
-- Initialize the ace options table
LibStub ( " AceConfigRegistry-3.0 " ) : RegisterOptionsTable ( " Routes " , options )
local f = function ( ) LibStub ( " AceConfigDialog-3.0 " ) : Open ( " Routes " ) end
self : RegisterChatCommand ( L [ " routes " ] , f )
if L [ " routes " ] ~= " routes " then
self : RegisterChatCommand ( " routes " , f )
end
-- Upgrade old storage format (which was dependant on LibBabble-Zone-3.0
-- to the new format that doesn't require it
-- Also delete any invalid zones
self : UpgradeStorageFormat2 ( )
-- Generate ace options table for each route
local opts = options.args . routes_group.args
for zone , zone_table in pairs ( db.routes ) do
if next ( zone_table ) == nil then
-- cleanup the empty zone
db.routes [ zone ] = nil
else
local localizedZoneName = GetZoneNameSafe ( zone )
opts [ tostring ( zone ) ] = {
type = " group " ,
name = localizedZoneName ,
desc = L [ " Routes in %s " ] : format ( localizedZoneName ) ,
args = {
desc = route_zone_args_desc_table ,
} ,
}
self.routekeys [ zone ] = { }
for route , route_table in pairs ( zone_table ) do
local routekey = route : gsub ( " %s " , " \255 " ) -- can't have spaces in the key
self.routekeys [ zone ] [ routekey ] = route
opts [ tostring ( zone ) ] . args [ routekey ] = self : GetAceOptRouteTable ( )
route_table.editing = nil -- in case server crashes during edit.
end
end
end
-- Generate ace options table for each taboo region
local opts = options.args . taboo_group.args
for zone , zone_table in pairs ( db.taboo ) do
if next ( zone_table ) == nil then
-- cleanup the empty zone
db.taboo [ zone ] = nil
else
local localizedZoneName = GetZoneNameSafe ( zone )
opts [ tostring ( zone ) ] = {
type = " group " ,
name = localizedZoneName ,
desc = L [ " Taboos in %s " ] : format ( localizedZoneName ) ,
args = {
desc = taboo_zone_args_desc_table ,
} ,
}
self.tabookeys [ zone ] = { }
for taboo in pairs ( zone_table ) do
local tabookey = taboo : gsub ( " %s " , " \255 " ) -- can't have spaces in the key
self.tabookeys [ zone ] [ tabookey ] = taboo
opts [ tostring ( zone ) ] . args [ tabookey ] = self : GetAceOptTabooTable ( )
end
end
end
self : SetupSourcesOptTables ( )
self : RegisterEvent ( " ADDON_LOADED " )
-- Reclaim memory for this function
self.OnInitialize = nil
end
local timerFrame = CreateFrame ( " Frame " )
timerFrame : Hide ( )
timerFrame.elapsed = 0
timerFrame : SetScript ( " OnUpdate " , function ( self , elapsed )
self.elapsed = self.elapsed + elapsed
if self.elapsed > 0.025 or self.force then -- throttle to a max of 40 redraws per sec
self.elapsed = 0 -- kinda unnecessary since at default 1 yard refresh, its limited to 36 redraws/sec
Routes : DrawMinimapLines ( self.force ) -- only need 25 redraws/sec to perceive smooth motion anyway
self.force = nil
end
end )
local function SetZoomHook ( )
timerFrame.force = true
end
function Routes : MINIMAP_UPDATE_ZOOM ( )
if not WoW90 then
local zoom = Minimap : GetZoom ( )
if GetCVar ( " minimapZoom " ) == GetCVar ( " minimapInsideZoom " ) then
Minimap : SetZoom ( zoom < 2 and zoom + 1 or zoom - 1 )
end
indoors = GetCVar ( " minimapZoom " ) + 0 == Minimap : GetZoom ( ) and " outdoor " or " indoor "
Minimap : SetZoom ( zoom )
end
timerFrame.force = true
end
function Routes : CVAR_UPDATE ( event , cvar , value )
if cvar == " ROTATE_MINIMAP " then
minimap_rotate = value == " 1 "
end
end
local RoutesDataProviderMixin = CreateFromMixins ( MapCanvasDataProviderMixin )
function RoutesDataProviderMixin : OnAdded ( mapCanvas )
MapCanvasDataProviderMixin.OnAdded ( self , mapCanvas )
self : GetMap ( ) : GetPinFrameLevelsManager ( ) : InsertFrameLevelAbove ( " PIN_FRAME_LEVEL_ROUTES " , " PIN_FRAME_LEVEL_FOG_OF_WAR " )
-- a single permanent pin
local pin = self : GetMap ( ) : AcquirePin ( " RoutesPinTemplate " , self.battleField )
pin : SetPosition ( 0.5 , 0.5 ) ;
self.pin = pin ;
-- Taboo pin for the main map
if not self.battleField then
self : GetMap ( ) : GetPinFrameLevelsManager ( ) : AddFrameLevel ( " PIN_FRAME_LEVEL_ROUTES_TABOO " )
self.tabooPin = self : GetMap ( ) : AcquirePin ( " RoutesTabooPinTemplate " )
self.tabooPin : SetPosition ( 0.5 , 0.5 )
end
end
function RoutesDataProviderMixin : OnRemoved ( mapCanvas )
MapCanvasDataProviderMixin.OnRemoved ( self , mapCanvas ) ;
self : GetMap ( ) : RemoveAllPinsByTemplate ( " RoutesPinTemplate " ) ;
self : GetMap ( ) : RemoveAllPinsByTemplate ( " RoutesTabooPinTemplate " ) ;
end
function RoutesDataProviderMixin : OnMapChanged ( )
self : RefreshAllData ( )
end
function RoutesDataProviderMixin : RemoveAllData ( )
G : HideLines ( self.pin )
end
function RoutesDataProviderMixin : RefreshAllData ( )
self.pin : DrawLines ( )
end
RoutesPinMixin = CreateFromMixins ( MapCanvasPinMixin )
function RoutesPinMixin : OnLoad ( )
self : SetIgnoreGlobalPinScale ( true )
self : UseFrameLevelType ( " PIN_FRAME_LEVEL_ROUTES " )
end
function RoutesPinMixin : OnAcquired ( battleField )
if battleField then
self.width_key = " width_battlemap "
self.draw_key = " draw_battlemap "
else
self.width_key = " width "
self.draw_key = " draw_worldmap "
end
end
function RoutesPinMixin : OnCanvasSizeChanged ( )
self : SetSize ( self : GetMap ( ) : DenormalizeHorizontalSize ( 1.0 ) , self : GetMap ( ) : DenormalizeVerticalSize ( 1.0 ) ) ;
end
function RoutesPinMixin : DrawLines ( )
-- setup locals
local fh , fw = self : GetHeight ( ) , self : GetWidth ( )
local defaults = db.defaults
-- clear all the lines
G : HideLines ( self )
-- get the scale of the worldmap canvas, so the lines have the same size everywhere
local canvasScale = self : GetEffectiveScale ( ) / self : GetMap ( ) : GetEffectiveScale ( )
-- check for conditions not to draw the world map lines
local flag = defaults [ self.draw_key ] and self : GetMap ( ) : IsShown ( ) -- Draw worldmap lines?
if not flag then return end -- Nothing to draw
local uiMapID = self : GetMap ( ) : GetMapID ( )
if not uiMapID then return end
for route_name , route_data in pairs ( db.routes [ uiMapID ] ) do
if type ( route_data ) == " table " and type ( route_data.route ) == " table " and # route_data.route > 1 then
local width = ( route_data [ self.width_key ] or defaults [ self.width_key ] ) / canvasScale
local color = route_data.color or defaults.color
if ( not route_data.hidden and not route_data.editing and ( route_data.visible or not defaults.use_auto_showhide ) ) or defaults.show_hidden then
if route_data.hidden then color = defaults.hidden_color end
local last_point
local sx , sy
if route_data.looped then
last_point = route_data.route [ # route_data.route ]
sx , sy = floor ( last_point / 10000 ) / 10000 , ( last_point % 10000 ) / 10000
sy = ( 1 - sy )
end
for i = 1 , # route_data.route do
local point = route_data.route [ i ]
if point == defaults.fake_point then
point = nil
end
if last_point and point then
local ex , ey = floor ( point / 10000 ) / 10000 , ( point % 10000 ) / 10000
ey = ( 1 - ey )
G : DrawLine ( self , sx * fw , sy * fh , ex * fw , ey * fh , width , color , " OVERLAY " )
sx , sy = ex , ey
end
last_point = point
end
end
end
end
end
RoutesTabooPinMixin = CreateFromMixins ( MapCanvasPinMixin )
function RoutesTabooPinMixin : OnLoad ( )
self : SetIgnoreGlobalPinScale ( true )
self : UseFrameLevelType ( " PIN_FRAME_LEVEL_ROUTES_TABOO " )
end
function RoutesTabooPinMixin : OnCanvasSizeChanged ( )
self : SetSize ( self : GetMap ( ) : DenormalizeHorizontalSize ( 1.0 ) , self : GetMap ( ) : DenormalizeVerticalSize ( 1.0 ) ) ;
end
function Routes : DrawWorldmapLines ( )
self.DataProvider : RefreshAllData ( )
if self.BattleFieldDataProvider then
self.BattleFieldDataProvider : RefreshAllData ( )
end
end
function Routes : OnEnable ( )
-- World Map line drawing
if not self.DataProvider then
self.DataProvider = CreateFromMixins ( RoutesDataProviderMixin )
end
WorldMapFrame : AddDataProvider ( self.DataProvider )
-- Battlefield Map
if BattlefieldMapFrame then
self : ADDON_LOADED ( " ADDON_LOADED " , " Blizzard_BattlefieldMap " )
end
-- Minimap line drawing
if not WoW90 then
self : SecureHook ( Minimap , " SetZoom " , SetZoomHook )
end
if db.defaults . draw_minimap then
self : RegisterEvent ( " MINIMAP_UPDATE_ZOOM " )
self : RegisterEvent ( " CVAR_UPDATE " )
timerFrame : Show ( )
Routes.Dragons . RegisterCallback ( Routes , " PlayerZoneChanged " , function ( ) Routes : DrawMinimapLines ( true ) end )
minimap_rotate = GetCVar ( " rotateMinimap " ) == " 1 "
self : MINIMAP_UPDATE_ZOOM ( )
end
for addon , plugin_table in pairs ( Routes.plugins ) do
if db.defaults . callbacks [ addon ] and plugin_table.IsActive ( ) then
plugin_table.AddCallbacks ( )
end
end
end
function Routes : OnDisable ( )
-- Ace3 unregisters all events and hooks for us on disable
for addon , plugin_table in pairs ( Routes.plugins ) do
if db.defaults . callbacks [ addon ] and plugin_table.IsActive ( ) then
plugin_table.RemoveCallbacks ( )
end
end
timerFrame : Hide ( )
WorldMapFrame : RemoveDataProvider ( self.DataProvider )
if BattlefieldMapFrame then
BattlefieldMapFrame : RemoveDataProvider ( self.BattleFieldDataProvider )
end
end
function Routes : ADDON_LOADED ( event , addon )
if self.plugins [ addon ] then
options.args . add_group.args [ addon ] . disabled = false
options.args . add_group.args [ addon ] . guiHidden = false
if db.defaults . callbacks [ addon ] and self.plugins [ addon ] . IsActive ( ) then
self.plugins [ addon ] . AddCallbacks ( )
end
end
if addon == " Blizzard_BattlefieldMap " then
if not self.BattleFieldDataProvider then
self.BattleFieldDataProvider = CreateFromMixins ( RoutesDataProviderMixin )
self.BattleFieldDataProvider . battleField = true
end
BattlefieldMapFrame : AddDataProvider ( self.BattleFieldDataProvider )
end
end
------------------------------------------------------------------------------------------------------
-- Ace options table stuff
do
-- Helper functions for setting/clearing keybinds in our option tables
local KeybindHelper = { }
Routes.KeybindHelper = KeybindHelper
local t = { }
function KeybindHelper : MakeKeyBindingTable ( ... )
wipe ( t )
for i = 1 , select ( " # " , ... ) do
local key = select ( i , ... )
if key ~= " " then
tinsert ( t , key )
end
end
return t
end
function KeybindHelper : GetKeybind ( info )
return table.concat ( self : MakeKeyBindingTable ( GetBindingKey ( info.arg ) ) , " , " )
end
function KeybindHelper : SetKeybind ( info , key )
if key == " " then
local t = self : MakeKeyBindingTable ( GetBindingKey ( info.arg ) )
for i = 1 , # t do
SetBinding ( t [ i ] )
end
else
local oldAction = GetBindingAction ( key )
local frame = LibStub ( " AceConfigDialog-3.0 " ) . OpenFrames [ " Routes " ]
if frame then
if ( oldAction ~= " " and oldAction ~= info.arg ) then
frame : SetStatusText ( KEY_UNBOUND_ERROR : format ( GetBindingText ( oldAction , " BINDING_NAME_ " ) ) )
else
frame : SetStatusText ( KEY_BOUND )
end
end
SetBinding ( key , info.arg )
end
SaveBindings ( GetCurrentBindingSet ( ) )
end
end
options = {
type = " group " ,
name = L [ " Routes " ] ,
get = function ( k ) return db.defaults [ k.arg ] end ,
set = function ( k , v ) db.defaults [ k.arg ] = v ; Routes : DrawWorldmapLines ( ) ; Routes : DrawMinimapLines ( true ) ; end ,
args = {
options_group = {
type = " group " ,
name = L [ " Options " ] ,
desc = L [ " Options " ] ,
order = 0 ,
--args = {}, -- defined later
} ,
add_group = {
type = " group " ,
name = L [ " Add " ] ,
desc = L [ " Add " ] ,
order = 100 ,
--args = {}, -- defined later
} ,
routes_group = {
type = " group " ,
name = L [ " Routes " ] ,
desc = L [ " Routes " ] ,
order = 200 ,
args = { } , -- populated in Routes:OnInitialize()
} ,
taboo_group = {
type = " group " ,
name = L [ " Taboos " ] ,
desc = L [ " Taboos " ] ,
order = 250 ,
args = { } , -- populated in Routes:OnInitialize()
} ,
faq_group = {
type = " group " ,
name = L [ " Help File " ] ,
desc = L [ " Help File " ] ,
order = 300 ,
args = {
overview = {
type = " group " ,
name = L [ " Overview " ] ,
desc = L [ " Overview " ] ,
order = 10 ,
args = {
header = {
type = " header " ,
name = L [ " Overview " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " OVERVIEW_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
create_route = {
type = " group " ,
name = L [ " Creating a route " ] ,
desc = L [ " Creating a route " ] ,
order = 20 ,
args = {
header = {
type = " header " ,
name = L [ " Creating a route " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " CREATE_ROUTE_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
optimizing_route = {
type = " group " ,
name = L [ " Optimizing a route " ] ,
desc = L [ " Optimizing a route " ] ,
order = 30 ,
args = {
header = {
type = " header " ,
name = L [ " Optimizing a route " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " OPTIMIZING_ROUTE_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
customizing_route = {
type = " group " ,
name = L [ " Customizing route display " ] ,
desc = L [ " Customizing route display " ] ,
order = 40 ,
args = {
header = {
type = " header " ,
name = L [ " Customizing route display " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " CUSTOMIZING_ROUTE_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
create_taboos = {
type = " group " ,
name = L [ " Creating a taboo region " ] ,
desc = L [ " Creating a taboo region " ] ,
order = 50 ,
args = {
header = {
type = " header " ,
name = L [ " Creating a taboo region " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " CREATE_TABOOS_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
waypoints_integration = {
type = " group " ,
name = L [ " Waypoints Integration " ] ,
desc = L [ " Waypoints Integration " ] ,
order = 60 ,
args = {
header = {
type = " header " ,
name = L [ " Waypoints Integration " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " WAYPOINTS_INTEGRATION_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
auto_update = {
type = " group " ,
name = L [ " Automatic route updating " ] ,
desc = L [ " Automatic route updating " ] ,
order = 70 ,
args = {
header = {
type = " header " ,
name = L [ " Automatic route updating " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " AUTOMATIC_UPDATE_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
faq = {
type = " group " ,
name = L [ " FAQ " ] ,
desc = L [ " Frequently Asked Questions " ] ,
order = 100 ,
args = {
header = {
type = " header " ,
name = L [ " Frequently Asked Questions " ] ,
order = 0 ,
} ,
desc = {
type = " description " ,
name = L [ " FAQ_TEXT " ] ,
order = 1 ,
} ,
} ,
} ,
} ,
} ,
}
}
options.args . options_group.args = {
-- Mapdrawing menu entry
drawing = {
name = L [ " Map Drawing " ] , type = " group " ,
desc = L [ " Map Drawing " ] ,
order = 100 ,
args = {
linedisplay_group = {
name = L [ " Toggle drawing on each of the maps. " ] , type = " group " ,
desc = L [ " Toggle drawing on each of the maps. " ] ,
inline = true ,
order = 100 ,
args = {
worldmap_toggle = {
name = L [ " Worldmap " ] ,
desc = L [ " Worldmap drawing " ] ,
type = " toggle " ,
order = 100 ,
arg = " draw_worldmap " ,
} ,
minimap_toggle = {
name = L [ " Minimap " ] ,
desc = L [ " Minimap drawing " ] ,
type = " toggle " ,
order = 200 ,
get = function ( info ) return db.defaults . draw_minimap end ,
set = function ( info , v )
db.defaults . draw_minimap = v
if v then
Routes : RegisterEvent ( " MINIMAP_UPDATE_ZOOM " )
Routes : RegisterEvent ( " CVAR_UPDATE " )
timerFrame : Show ( )
Routes.Dragons . RegisterCallback ( Routes , " PlayerZoneChanged " , function ( ) Routes : DrawMinimapLines ( true ) end )
minimap_rotate = GetCVar ( " rotateMinimap " ) == " 1 "
Routes : MINIMAP_UPDATE_ZOOM ( ) -- This has a DrawMinimapLines(true) call in it, and sets an "indoors" variable
else
Routes : UnregisterEvent ( " MINIMAP_UPDATE_ZOOM " )
Routes : UnregisterEvent ( " CVAR_UPDATE " )
timerFrame : Hide ( )
Routes.Dragons . UnregisterCallback ( Routes , " PlayerZoneChanged " )
G : HideLines ( Minimap )
end
end ,
} ,
battlemap_toggle = {
name = L [ " Zone Map " ] ,
desc = L [ " Zone Map drawing " ] ,
type = " toggle " ,
order = 300 ,
arg = " draw_battlemap " ,
} ,
indoors_toggle = {
name = L [ " Minimap when indoors " ] ,
desc = L [ " Draw on minimap when indoors " ] ,
type = " toggle " ,
order = 400 ,
arg = " draw_indoors " ,
disabled = function ( ) return not db.defaults . draw_minimap end ,
} ,
} ,
} ,
default_group = {
name = L [ " Set the width of lines on each of the maps. " ] , type = " group " ,
desc = L [ " Normal lines " ] ,
inline = true ,
order = 200 ,
args = {
width = {
name = L [ " Worldmap " ] , type = " range " ,
desc = L [ " Width of the line in the Worldmap " ] ,
min = 10 , max = 100 , step = 1 ,
arg = " width " ,
order = 100 ,
} ,
width_minimap = {
name = L [ " Minimap " ] , type = " range " ,
desc = L [ " Width of the line in the Minimap " ] ,
min = 10 , max = 100 , step = 1 ,
arg = " width_minimap " ,
order = 110 ,
} ,
width_battlemap = {
name = L [ " Zone Map " ] , type = " range " ,
desc = L [ " Width of the line in the Zone Map " ] ,
min = 10 , max = 100 , step = 1 ,
arg = " width_battlemap " ,
order = 120 ,
} ,
} ,
} ,
color_group = {
name = L [ " Color of lines " ] , type = " group " ,
desc = L [ " Color of lines " ] ,
inline = true ,
order = 300 ,
get = function ( info ) return unpack ( db.defaults [ info.arg ] ) end ,
set = function ( info , r , g , b , a )
local c = db.defaults [ info.arg ]
c [ 1 ] = r ; c [ 2 ] = g ; c [ 3 ] = b ; c [ 4 ] = a
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end ,
args = {
color = {
name = L [ " Default route " ] , type = " color " ,
desc = L [ " Change default route color " ] ,
arg = " color " ,
hasAlpha = true ,
order = 200 ,
} ,
hidden_color = {
name = L [ " Hidden route " ] , type = " color " ,
desc = L [ " Change default hidden route color " ] ,
arg = " hidden_color " ,
hasAlpha = true ,
order = 400 ,
} ,
} ,
} ,
line_gaps_group = {
name = L [ " Line gaps " ] , type = " group " ,
desc = L [ " Line gaps " ] ,
inline = true ,
order = 400 ,
args = {
line_gaps = {
name = L [ " Draw line gaps " ] , type = " toggle " ,
desc = L [ " Shorten the lines drawn on the minimap slightly so that they do not overlap the icons and minimap tracking blips. " ] ,
arg = " line_gaps " ,
order = 400 ,
} ,
line_gaps_skip_cluster = {
name = L [ " Skip clustered node points " ] , type = " toggle " ,
desc = L [ " Do not draw gaps for clustered node points in routes. " ] ,
arg = " line_gaps_skip_cluster " ,
disabled = function ( ) return not db.defaults . line_gaps end ,
order = 400 ,
} ,
} ,
} ,
show_hidden = {
name = L [ " Show hidden routes " ] , type = " toggle " ,
desc = L [ " Show hidden routes? " ] ,
arg = " show_hidden " ,
order = 450 ,
} ,
update_distance = {
name = L [ " Update distance " ] , type = " range " ,
desc = L [ " Yards to move before triggering a minimap update " ] ,
min = 0 , max = 10 , step = 0.1 ,
arg = " update_distance " ,
order = 500 ,
} ,
} ,
} ,
}
-- Set of functions we use to edit route configs
local ConfigHandler = { }
function ConfigHandler : GetColor ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return unpack ( db.routes [ zone ] [ route ] . color or db.defaults . color )
end
function ConfigHandler : SetColor ( info , r , g , b , a )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
t.color = t.color or { }
t = t.color
t [ 1 ] = r ; t [ 2 ] = g ; t [ 3 ] = b ; t [ 4 ] = a ;
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : GetHidden ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return db.routes [ zone ] [ route ] . hidden
end
function ConfigHandler : SetHidden ( info , v )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
db.routes [ zone ] [ route ] . hidden = v
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : GetWidth ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return db.routes [ zone ] [ route ] . width or db.defaults . width
end
function ConfigHandler : SetWidth ( info , v )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
db.routes [ zone ] [ route ] . width = v
Routes : DrawWorldmapLines ( )
end
function ConfigHandler : GetWidthMinimap ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return db.routes [ zone ] [ route ] . width_minimap or db.defaults . width_minimap
end
function ConfigHandler : SetWidthMinimap ( info , v )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
db.routes [ zone ] [ route ] . width_minimap = v
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : GetWidthBattleMap ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return db.routes [ zone ] [ route ] . width_battlemap or db.defaults . width_battlemap
end
function ConfigHandler : SetWidthBattleMap ( info , v )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
db.routes [ zone ] [ route ] . width_battlemap = v
Routes : DrawWorldmapLines ( )
end
function ConfigHandler : DeleteRoute ( info )
local zone = tonumber ( info [ 2 ] )
local zoneKey = info [ 2 ]
local routekey = info [ 3 ]
local route = Routes.routekeys [ zone ] [ routekey ]
local is_running , route_table = Routes.TSP : IsTSPRunning ( )
if is_running and route_table == db.routes [ zone ] [ route ] . route then
Routes : Print ( L [ " You may not delete a route that is being optimized in the background. " ] )
return
end
db.routes [ zone ] [ route ] = nil
--local routekey = route:gsub("%s", "\255") -- can't have spaces in the key
options.args . routes_group.args [ zoneKey ] . args [ routekey ] = nil -- delete route from aceopt
Routes.routekeys [ zone ] [ routekey ] = nil
if next ( db.routes [ zone ] ) == nil then
db.routes [ zone ] = nil
options.args . routes_group.args [ zoneKey ] = nil -- delete zone from aceopt if no routes remaining
Routes.routekeys [ zone ] = nil
end
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : RecreateRoute ( info )
local zone = tonumber ( info [ 2 ] )
local routekey = info [ 3 ]
local route = Routes.routekeys [ zone ] [ routekey ]
local is_running , route_table = Routes.TSP : IsTSPRunning ( )
if is_running and route_table == db.routes [ zone ] [ route ] . route then
Routes : Print ( L [ " You may not delete a route that is being optimized in the background. " ] )
return
end
Routes : RecreateRoute ( zone , route )
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : ClusterRoute ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
t.route , t.metadata , t.length = Routes.TSP : ClusterRoute ( db.routes [ zone ] [ route ] . route , zone , db.defaults . cluster_dist )
t.cluster_dist = db.defaults . cluster_dist
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : UnClusterRoute ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
local num = 0
for i = 1 , # t.metadata do
for j = 1 , # t.metadata [ i ] do
num = num + 1
t.route [ num ] = t.metadata [ i ] [ j ]
end
end
t.metadata = nil
t.cluster_dist = nil
t.length = Routes.TSP : PathLength ( t.route , zone )
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : IsCluster ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
if t.metadata then
return true
else
return false
end
end
function ConfigHandler : IsNotCluster ( info )
return not self : IsCluster ( info )
end
function ConfigHandler : GetDefaultClusterDist ( )
return db.defaults . cluster_dist
end
function ConfigHandler : SetDefaultClusterDist ( info , v )
db.defaults . cluster_dist = v
end
function ConfigHandler : ResetLineSettings ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
t.color = nil
t.width = nil
t.width_minimap = nil
t.width_battlemap = nil
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler . GetRouteDesc ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
return L [ " This route has |cffffd200%d|r nodes and is |cffffd200%d|r yards long. " ] : format ( # t.route , t.length )
end
function ConfigHandler . GetShortClusterDesc ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
if not t.metadata then
return L [ " This route is not a clustered route. " ]
end
local numNodes = 0
for i = 1 , # t.metadata do
numNodes = numNodes + # t.metadata [ i ]
end
return L [ " This route is a clustered route, down from the original |cffffd200%d|r nodes. " ] : format ( numNodes )
end
function ConfigHandler . GetRouteClusterRadiusDesc ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
if t.metadata then
return L [ " The cluster radius of this route is |cffffd200%d|r yards. " ] : format ( t.cluster_dist or 65 ) -- 65 was an old default
end
end
do
local str = { }
function ConfigHandler . GetDataDesc ( info )
wipe ( str )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
local num = 1
str [ num ] = L [ " This route has nodes that belong to the following categories: " ]
for k in pairs ( t.db_type ) do
num = num + 1
str [ num ] = " |cffffd200 " .. L [ k ] .. " |r "
end
num = num + 1
str [ num ] = L [ " This route contains the following nodes: " ]
for k , v in pairs ( t.selection ) do
num = num + 1
if v == true then v = k end
str [ num ] = " |cffffd200 " .. v .. " |r "
end
return table.concat ( str , " \n " )
end
local data = { }
function ConfigHandler . GetClusterDesc ( info )
wipe ( str )
wipe ( data )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
if not t.metadata then
return L [ " This route is not a clustered route. " ]
end
local numNodes = 0
local maxt = 0
local zoneW , zoneH = Routes.Dragons : GetZoneSize ( zone )
for i = 1 , # t.metadata do
local numData = # t.metadata [ i ]
numNodes = numNodes + numData
local x , y = floor ( t.route [ i ] / 10000 ) / 10000 , ( t.route [ i ] % 10000 ) / 10000
for j = 1 , numData do
local x2 , y2 = floor ( t.metadata [ i ] [ j ] / 10000 ) / 10000 , ( t.metadata [ i ] [ j ] % 10000 ) / 10000 -- to round off the coordinate
local t = ( ( ( x2 - x ) * zoneW ) ^ 2 + ( ( y2 - y ) * zoneH ) ^ 2 ) ^ 0.5 - 0.0001
t = floor ( t / 10 )
data [ t ] = ( data [ t ] or 0 ) + 1
if t > maxt then maxt = t end
end
end
for i = 0 , maxt do
str [ i + 4 ] = L [ " |cffffd200 %d|r node(s) are between |cffffd200%d|r-|cffffd200%d|r yards of a cluster point " ] : format ( data [ i ] or 0 , i * 10 + 1 , i * 10 + 10 )
end
str [ 1 ] = L [ " This route is a clustered route, down from the original |cffffd200%d|r nodes. " ] : format ( numNodes )
str [ 2 ] = L [ " The cluster radius of this route is |cffffd200%d|r yards. " ] : format ( t.cluster_dist or 65 ) -- 65 was an old default
str [ 3 ] = L [ " |cffffd200 %d|r node(s) are at |cffffd2000|r yards of a cluster point " ] : format ( data [ - 1 ] or 0 )
return table.concat ( str , " \n " )
end
function ConfigHandler . GetTabooDesc ( info )
wipe ( str )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
local num = 1
str [ num ] = L [ " This route has the following taboo regions: " ]
for k , v in pairs ( t.taboos ) do
if v then
num = num + 1
str [ num ] = " |cffffd200 " .. k .. " |r "
else
t.taboos [ k ] = nil -- set the false value to nil, so we don't pairs() over it in the future
end
end
if num == 1 then
str [ num ] = L [ " This route has no taboo regions. " ]
end
num = num + 1
str [ num ] = L [ " This route contains |cffffd200%d|r nodes that have been tabooed. " ] : format ( # t.taboolist )
return table.concat ( str , " \n " )
end
end
function ConfigHandler : GetTwoPointFiveOpt ( )
return db.defaults . tsp.two_point_five_opt
end
function ConfigHandler : SetTwoPointFiveOpt ( info , v )
db.defaults . tsp.two_point_five_opt = v
end
function ConfigHandler : DoForeground ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
if # t.route > 724 then
-- Lua has 4mb limit on table size. 725x725 will result in a table of size 525625
-- 524288 (or 2^19) is the max as 8 bytes per entry will give exactly 4 Mb
Routes : Print ( L [ " TOO_MANY_NODES_ERROR " ] )
return
end
local taboos = { }
for tabooname , used in pairs ( t.taboos ) do
if used then
tinsert ( taboos , db.taboo [ zone ] [ tabooname ] )
end
end
local output , meta , length , iter , timetaken = Routes.TSP : SolveTSP ( t.route , t.metadata , taboos , zone , db.defaults . tsp )
t.route = output
t.length = length
t.metadata = meta
Routes : Print ( L [ " Path with %d nodes found with length %.2f yards after %d iterations in %.2f seconds. " ] : format ( # output , length , iter , timetaken ) )
-- redraw lines
local AutoShow = Routes : GetModule ( " AutoShow " , true )
if AutoShow and db.defaults . use_auto_showhide then
AutoShow : ApplyVisibility ( )
end
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end
function ConfigHandler : DoBackground ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local t = db.routes [ zone ] [ route ]
if # t.route > 724 then
Routes : Print ( L [ " TOO_MANY_NODES_ERROR " ] )
return
end
local taboos = { }
for tabooname , used in pairs ( t.taboos ) do
if used then
tinsert ( taboos , db.taboo [ zone ] [ tabooname ] )
end
end
local running , errormsg = Routes.TSP : SolveTSPBackground ( t.route , t.metadata , taboos , zone , db.defaults . tsp )
if ( running == 1 ) then
Routes : Print ( L [ " Now running TSP in the background... " ] )
local dispLength ;
Routes.TSP : SetStatusFunction ( function ( pass , progress , length )
local frame = LibStub ( " AceConfigDialog-3.0 " ) . OpenFrames [ " Routes " ]
if frame then
if length then
dispLength = length
end
if dispLength then
frame : SetStatusText ( L [ " Pass %d: %d%% - %d yards " ] : format ( pass , progress * 100 , dispLength ) )
else
frame : SetStatusText ( L [ " Pass %d: %d%% " ] : format ( pass , progress * 100 ) )
end
end
end )
Routes.TSP : SetFinishFunction ( function ( output , meta , length , iter , timetaken )
t.route = output
t.length = length
t.metadata = meta
local msg = L [ " Path with %d nodes found with length %.2f yards after %d iterations in %.2f seconds. " ] : format ( # output , length , iter , timetaken )
Routes : Print ( msg )
local frame = LibStub ( " AceConfigDialog-3.0 " ) . OpenFrames [ " Routes " ]
if frame then
frame : SetStatusText ( msg )
end
-- redraw lines
local AutoShow = Routes : GetModule ( " AutoShow " , true )
if AutoShow and db.defaults . use_auto_showhide then
AutoShow : ApplyVisibility ( )
end
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
end )
elseif ( running == 2 ) then
Routes : Print ( L [ " There is already a TSP running in background. Wait for it to complete first. " ] )
elseif ( running == 3 ) then
-- This should never happen, but is here as a fallback
Routes : Print ( L [ " The following error occured in the background path generation coroutine, please report to Grum or Xinhuan: " ] ) ;
Routes : Print ( errormsg ) ;
end
end
do
local t = { }
function ConfigHandler : GetTabooRegions ( info )
local zone = tonumber ( info [ 2 ] )
for k , v in pairs ( t ) do t [ k ] = nil end
for k , v in pairs ( db.taboo [ zone ] ) do
t [ k ] = k
end
return t
end
end
function ConfigHandler : GetTabooRegionStatus ( info , k )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return db.routes [ zone ] [ route ] . taboos [ k ]
end
function ConfigHandler : SetTabooRegionStatus ( info , k , v )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
if v == false then v = nil end
local route_data = db.routes [ zone ] [ route ]
local taboo_data = db.taboo [ zone ] [ k ]
if route_data.taboos [ k ] ~= v then
-- toggle it
route_data.taboos [ k ] = v
if v then
Routes : ApplyTabooToRoute ( zone , taboo_data , route_data )
else
Routes : UnTabooRoute ( zone , route_data )
end
end
end
function ConfigHandler : IsBeingManualEdited ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
return db.routes [ zone ] [ route ] . editing
end
function ConfigHandler . GetRouteName ( info )
local zone = tonumber ( info [ 2 ] )
return Routes.routekeys [ zone ] [ info [ 3 ] ]
end
function ConfigHandler : IsDisableRecreateRoute ( info )
-- One of these 2 plugins must be active to recreate routes
local disableRecreate = true
if Routes.plugins [ " GatherMate2 " ] and Routes.plugins [ " GatherMate2 " ] . IsActive ( ) then
disableRecreate = false
end
if Routes.plugins [ " Gatherer " ] and Routes.plugins [ " Gatherer " ] . IsActive ( ) then
disableRecreate = false
end
return disableRecreate or self : IsBeingManualEdited ( info )
end
do
local routeTable = {
type = " group " ,
name = ConfigHandler.GetRouteName ,
desc = ConfigHandler.GetRouteName ,
childGroups = " tab " ,
handler = ConfigHandler ,
args = {
info_group = {
type = " group " ,
name = L [ " Information " ] ,
order = 0 ,
args = {
desc1 = {
type = " description " ,
name = ConfigHandler.GetRouteDesc ,
order = 0 ,
} ,
desc2 = {
type = " description " ,
name = ConfigHandler.GetDataDesc ,
order = 10 ,
} ,
desc3 = {
type = " description " ,
name = ConfigHandler.GetClusterDesc ,
order = 20 ,
} ,
desc4 = {
type = " description " ,
name = ConfigHandler.GetTabooDesc ,
order = 30 ,
} ,
delete = {
name = L [ " Delete " ] , type = " execute " ,
desc = L [ " Permanently delete a route " ] ,
func = " DeleteRoute " ,
confirm = true ,
confirmText = L [ " Are you sure you want to delete this route? " ] ,
order = 100 ,
disabled = " IsBeingManualEdited " ,
} ,
recreate = {
name = L [ " Recreate Route " ] , type = " execute " ,
desc = L [ " Recreate this route with the same creation settings. NOTE: This only works for data from GatherMate2 and Gatherer. " ] ,
func = " RecreateRoute " ,
confirm = true ,
confirmText = L [ " Are you sure you want to recreate this route? This will delete all customized settings for this route. " ] ,
order = 110 ,
disabled = " IsDisableRecreateRoute " ,
} ,
} ,
} ,
setting_group = {
type = " group " ,
name = L [ " Line Settings " ] ,
order = 100 ,
disabled = " IsBeingManualEdited " ,
args = {
desc = {
type = " description " ,
name = L [ " These settings control the visibility and look of the drawn route. " ] ,
order = 0 ,
} ,
color = {
name = L [ " Line Color " ] , type = " color " ,
desc = L [ " Change the line color " ] ,
get = " GetColor " , set = " SetColor " ,
order = 100 ,
hasAlpha = true ,
} ,
hidden = {
name = L [ " Hide Route " ] , type = " toggle " ,
desc = L [ " Hide the route from being shown on the maps " ] ,
get = " GetHidden " , set = " SetHidden " ,
order = 200 ,
} ,
width = {
name = L [ " Width (Map) " ] , type = " range " ,
desc = L [ " Width of the line in the map " ] ,
min = 10 , max = 100 , step = 1 ,
get = " GetWidth " , set = " SetWidth " ,
order = 300 ,
} ,
width_minimap = {
name = L [ " Width (Minimap) " ] , type = " range " ,
desc = L [ " Width of the line in the Minimap " ] ,
min = 10 , max = 100 , step = 1 ,
get = " GetWidthMinimap " , set = " SetWidthMinimap " ,
order = 310 ,
} ,
width_battlemap = {
name = L [ " Width (Zone Map) " ] , type = " range " ,
desc = L [ " Width of the line in the Zone Map " ] ,
min = 10 , max = 100 , step = 1 ,
get = " GetWidthBattleMap " , set = " SetWidthBattleMap " ,
order = 320 ,
} ,
blankline = {
name = " " , type = " description " ,
order = 325 ,
} ,
reset_all = {
name = L [ " Reset " ] , type = " execute " ,
desc = L [ " Reset the line settings to defaults " ] ,
func = " ResetLineSettings " ,
order = 500 ,
} ,
} ,
} ,
optimize_group = {
type = " group " ,
order = 200 ,
name = L [ " Optimize Route " ] ,
disabled = " IsBeingManualEdited " ,
args = {
desc = {
type = " description " ,
name = ConfigHandler.GetRouteDesc ,
order = 0 ,
} ,
desc2 = {
type = " description " ,
name = ConfigHandler.GetShortClusterDesc ,
order = 1 ,
} ,
desc3 = {
type = " description " ,
name = ConfigHandler.GetRouteClusterRadiusDesc ,
hidden = " IsNotCluster " ,
disabled = " IsNotCluster " ,
order = 2 ,
} ,
cluster_header = {
type = " header " ,
name = L [ " Route Clustering " ] ,
order = 40 ,
} ,
desc_cluster = {
type = " description " ,
name = L [ " CLUSTER_DESC " ] ,
order = 50 ,
} ,
cluster_dist = {
name = L [ " Cluster Radius " ] , type = " range " ,
desc = L [ " CLUSTER_RADIUS_DESC " ] ,
min = 10 , max = 200 , step = 1 ,
get = " GetDefaultClusterDist " ,
set = " SetDefaultClusterDist " ,
hidden = " IsCluster " ,
disabled = " IsCluster " ,
order = 60 ,
} ,
cluster = {
name = L [ " Cluster " ] , type = " execute " ,
desc = L [ " Cluster this route " ] ,
func = " ClusterRoute " ,
hidden = " IsCluster " ,
disabled = " IsCluster " ,
order = 70 ,
} ,
uncluster = {
name = L [ " Uncluster " ] , type = " execute " ,
desc = L [ " Uncluster this route " ] ,
func = " UnClusterRoute " ,
hidden = " IsNotCluster " ,
disabled = " IsNotCluster " ,
order = 80 ,
} ,
optimize_header = {
type = " header " ,
name = L [ " Route Optimizing " ] ,
order = 100 ,
} ,
two_point_five_group = {
type = " group " ,
order = 150 ,
name = L [ " Extra optimization " ] ,
inline = true ,
args = {
two_point_five_opt_disc = {
name = L [ " ExtraOptDesc " ] , type = " description " ,
order = 0 ,
} ,
two_point_five_opt = {
name = L [ " Extra optimization " ] , type = " toggle " ,
desc = L [ " ExtraOptDesc " ] ,
get = " GetTwoPointFiveOpt " , set = " SetTwoPointFiveOpt " ,
disabled = false , -- to avoid inheriting from parent, so we don't have to use an arg= field
order = 100 ,
} ,
} ,
} ,
foreground_group = {
type = " group " ,
order = 200 ,
name = L [ " Foreground " ] ,
inline = true ,
args = {
foreground_disc = {
type = " description " ,
name = L [ " Foreground Disclaimer " ] ,
order = 0 ,
} ,
foreground = {
name = L [ " Foreground " ] , type = " execute " ,
desc = L [ " Foreground Disclaimer " ] ,
func = " DoForeground " ,
order = 100 ,
} ,
} ,
} ,
background_group = {
type = " group " ,
order = 300 ,
name = L [ " Background " ] ,
inline = true ,
args = {
background_disc = {
type = " description " ,
name = L [ " Background Disclaimer " ] ,
order = 0 ,
} ,
background = {
name = L [ " Background " ] , type = " execute " ,
desc = L [ " Background Disclaimer " ] ,
func = " DoBackground " ,
order = 100 ,
} ,
} ,
} ,
} ,
} ,
taboo_group = {
type = " group " ,
order = 300 ,
name = L [ " Taboos " ] ,
disabled = " IsBeingManualEdited " ,
args = {
desc = {
type = " description " ,
name = L [ " TABOO_DESC2 " ] ,
order = 0 ,
} ,
taboos = {
name = L [ " Select taboo regions to apply: " ] ,
type = " multiselect " ,
order = 100 ,
values = " GetTabooRegions " ,
get = " GetTabooRegionStatus " ,
set = " SetTabooRegionStatus " ,
} ,
} ,
} ,
--edit_group = Routes:GetAceOptRouteEditTable(),
} ,
}
function Routes : GetAceOptRouteTable ( )
routeTable.args . edit_group = Routes : GetAceOptRouteEditTable ( )
return routeTable
end
end
local source_data = { }
options.args . routes_group.args . desc = {
type = " description " ,
name = L [ " When the following data sources add or delete node data, update my routes automatically by inserting or removing the same node in the relevant routes. " ] .. L [ " Gatherer/HandyNotes currently does not support callbacks, so this is impossible for Gatherer/HandyNotes. " ] ,
order = 0 ,
}
options.args . routes_group.args . callbacks = {
type = " multiselect " ,
name = L [ " Select sources of data " ] ,
order = 100 ,
values = source_data ,
get = function ( info , k )
if Routes.plugins [ k ] . IsActive ( ) and k ~= " Gatherer " and k ~= " HandyNotes " then
return db.defaults . callbacks [ k ]
else
return nil
end
end ,
set = function ( info , k , v )
-- If plugin is not active, don't toggle anything
if not Routes.plugins [ k ] . IsActive ( ) or k == " Gatherer " or k == " HandyNotes " then return end
if v == nil then v = false end
db.defaults . callbacks [ k ] = v
if v then
Routes.plugins [ k ] . AddCallbacks ( )
else
Routes.plugins [ k ] . RemoveCallbacks ( )
end
end ,
tristate = true ,
}
-- AceOpt config table for route creation
do
-- Some upvalues used in the aceopts[] table for creating new routes
local create_name = " "
local create_zones = { }
local create_zone
local last_zone = { }
local create_choices = { }
local create_data = { }
local empty_table = { }
local source_data_choice = { }
local function deep_copy_table ( a , b )
for k , v in pairs ( b ) do
if type ( v ) == " table " then
--a[k] = {} -- no need this, AceDB defaults should handle it
deep_copy_table ( a [ k ] , v )
else
a [ k ] = v
end
end
end
local function get_source_values ( info )
if not create_zone then return empty_table end
local create_data = create_data [ info.arg ]
if last_zone [ info.arg ] == create_zone then return create_data end
-- reuse table
wipe ( create_data )
-- extract data from plugin
if Routes.plugins [ info.arg ] . IsActive ( ) then
Routes.plugins [ info.arg ] . Summarize ( create_data , create_zone )
end
-- found no data - insert dummy message
if not next ( create_data ) then
create_data [ db.defaults . fake_data .. " ;;; " ] = L [ " No data found " ]
end
last_zone [ info.arg ] = create_zone
-- Remove invalid entries due to updated data so we don't pairs over it during route creation
if create_choices [ create_zone ] then
for k in pairs ( create_choices [ create_zone ] ) do
if not create_data [ k ] then create_choices [ create_zone ] [ k ] = nil end
end
end
return create_data
end
local function get_source_value ( info , key )
--Routes:Print(("Getting choice for: %s"):format(key or "nil"));
if not create_zone then return end
if key == db.defaults . fake_data then return end
if not create_choices [ create_zone ] then create_choices [ create_zone ] = { } end
return create_choices [ create_zone ] [ key ]
end
local function set_source_value ( info , key , value )
if not create_zone then return end
if key == db.defaults . fake_data then return end
if not create_choices [ create_zone ] then create_choices [ create_zone ] = { } end
create_choices [ create_zone ] [ key ] = value
--Routes:Print(("Setting choice: %s to %s"):format(key or "nil", value and "true" or "false"));
end
function Routes : SetupSourcesOptTables ( )
-- reuse table
wipe ( source_data )
-- create a checkbox for each plugin, then setup the aceopt table
local order = 300
for addon , plugin_table in pairs ( Routes.plugins ) do
local addonkey = addon : gsub ( " %s " , " _ " )
source_data [ addonkey ] = addon
if not options.args . add_group.args [ addonkey ] then
order = order + 1
create_data [ addonkey ] = { }
options.args . add_group.args [ addonkey ] = {
name = addon .. L [ " Data " ] , type = " multiselect " ,
order = order ,
arg = addon ,
values = get_source_values ,
get = get_source_value ,
set = set_source_value ,
width = " full " ,
disabled = not plugin_table.IsActive ( ) ,
guiHidden = not plugin_table.IsActive ( ) ,
}
end
end
end
options.args . add_group.args = {
route_name = {
type = " input " ,
name = L [ " Name of Route " ] ,
desc = L [ " Name of the route to add " ] ,
validate = function ( info , name )
if name == " " or strtrim ( name ) == " " then
return L [ " No name given for new route " ]
end
return true
end ,
get = function ( ) return create_name end ,
set = function ( info , v ) create_name = strtrim ( v ) end ,
order = 100 ,
} ,
zone_choice = {
name = L [ " Select Zone " ] , type = " select " ,
desc = L [ " Zone to create route in " ] ,
order = 150 ,
values = function ( )
if not next ( create_zones ) then
for zoneName in pairs ( Routes.LZName ) do
create_zones [ zoneName ] = zoneName
end
end
return create_zones
end ,
get = function ( )
if create_zone then return create_zone end
-- Use currently viewed map on first view.
local mapID = WorldMapFrame : GetMapID ( )
if not mapID then return nil end
create_zone = GetZoneName ( mapID )
return create_zone
end ,
set = function ( info , key ) create_zone = key end ,
} ,
header_bare = {
type = " header " ,
name = L [ " Create Bare Route " ] ,
order = 200 ,
} ,
info_bare = {
type = " description " ,
name = L [ " CREATE_BARE_ROUTE_DESC " ] ,
order = 201 ,
} ,
add_route_bare = {
name = L [ " Create Bare Route " ] , type = " execute " ,
desc = L [ " CREATE_BARE_ROUTE_DESC " ] ,
order = 202 ,
func = function ( )
create_name = strtrim ( create_name )
if not create_name or create_name == " " then
Routes : Print ( L [ " No name given for new route " ] )
return
end
local new_route = { route = { 71117111 , 12357823 , 11171123 } , selection = { } , db_type = { } }
-- Perform a deep copy instead so that db defaults apply
local mapID = Routes.LZName [ create_zone ]
local mapIDKey = tostring ( mapID )
db.routes [ mapID ] [ create_name ] = nil -- overwrite old route
new_route.route = Routes.TSP : DecrossRoute ( new_route.route )
deep_copy_table ( db.routes [ mapID ] [ create_name ] , new_route )
db.routes [ mapID ] [ create_name ] . length = Routes.TSP : PathLength ( new_route.route , mapID )
-- Create the aceopts table entry for our new route
local opts = options.args . routes_group.args
if not opts [ mapIDKey ] then
opts [ mapIDKey ] = { -- use a 3 digit string which is alphabetically sorted zone names by continent
type = " group " ,
name = create_zone ,
desc = L [ " Routes in %s " ] : format ( create_zone ) ,
args = {
desc = route_zone_args_desc_table ,
} ,
}
Routes.routekeys [ mapID ] = { }
end
local routekey = create_name : gsub ( " %s " , " \255 " ) -- can't have spaces in the key
Routes.routekeys [ mapID ] [ routekey ] = create_name
opts [ mapIDKey ] . args [ routekey ] = Routes : GetAceOptRouteTable ( )
-- Draw it
local AutoShow = Routes : GetModule ( " AutoShow " , true )
if AutoShow and db.defaults . use_auto_showhide then
AutoShow : ApplyVisibility ( )
end
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
-- clear stored name
create_name = " "
create_zone = nil
end ,
disabled = function ( )
return not create_name or strtrim ( create_name ) == " "
end ,
confirm = function ( )
if # db.routes [ Routes.LZName [ create_zone ] ] [ create_name ] . route > 0 then
return true
end
return false
end ,
confirmText = L [ " A route with that name already exists. Overwrite? " ] ,
} ,
header_normal = {
type = " header " ,
name = L [ " Create Route from Data Sources " ] ,
order = 225 ,
} ,
source_choices = {
name = L [ " Select sources of data " ] , type = " multiselect " ,
order = 250 ,
values = source_data ,
get = function ( info , k )
if Routes.plugins [ k ] . IsActive ( ) then
if source_data_choice [ k ] == nil then
source_data_choice [ k ] = true
end
return source_data_choice [ k ]
else
return nil
end
end ,
set = function ( info , k , v )
-- If plugin is not active, don't toggle anything
if not Routes.plugins [ k ] . IsActive ( ) then return end
if v == nil then v = false end
source_data_choice [ k ] = v
options.args . add_group.args [ k ] . disabled = not v
options.args . add_group.args [ k ] . guiHidden = not v
end ,
tristate = true ,
} ,
add_route = {
name = L [ " Create Route " ] , type = " execute " ,
desc = L [ " Create Route " ] ,
order = 400 ,
func = function ( )
create_name = strtrim ( create_name )
if not create_name or create_name == " " then
Routes : Print ( L [ " No name given for new route " ] )
return
end
-- the real 'action', we use a temporary table in case of data corruption and only commit this to the db if successful
local new_route = { route = { } , selection = { } , db_type = { } }
-- if for every selected nodetype on this map
if type ( create_choices [ create_zone ] ) == " table " then
for data_string , wanted in pairs ( create_choices [ create_zone ] ) do
local db_src , db_type , node_type , amount = ( ' ; ' ) : split ( data_string ) ;
local addonkey = db_src : gsub ( " %s " , " _ " )
-- if we want em
if ( wanted and source_data_choice [ addonkey ] ) then
--Routes:Print(("found %s %s %s %s"):format( db_src,db_type,node_type,amount ))
if db_src ~= db.defaults . fake_data then -- ignore any fake data
-- extract data from plugin
local plugin = Routes.plugins [ db_src ]
if plugin.IsActive ( ) then
local english_node , localized_node , type = plugin.AppendNodes ( new_route.route , create_zone , db_type , node_type )
new_route.selection [ english_node ] = localized_node
new_route.db_type [ type ] = true
end
end
end
end
end
if # new_route.route == 0 then
Routes : Print ( L [ " No data selected for new route " ] )
return
end
-- Perform a deep copy instead so that db defaults apply
local mapID = Routes.LZName [ create_zone ]
local mapIDKey = tostring ( mapID )
db.routes [ mapID ] [ create_name ] = nil -- overwrite old route
new_route.route = Routes.TSP : DecrossRoute ( new_route.route )
deep_copy_table ( db.routes [ mapID ] [ create_name ] , new_route )
db.routes [ mapID ] [ create_name ] . length = Routes.TSP : PathLength ( new_route.route , mapID )
-- Create the aceopts table entry for our new route
local opts = options.args . routes_group.args
if not opts [ mapIDKey ] then
opts [ mapIDKey ] = { -- use a 3 digit string which is alphabetically sorted zone names by continent
type = " group " ,
name = create_zone ,
desc = L [ " Routes in %s " ] : format ( create_zone ) ,
args = {
desc = route_zone_args_desc_table ,
} ,
}
Routes.routekeys [ mapID ] = { }
end
local routekey = create_name : gsub ( " %s " , " \255 " ) -- can't have spaces in the key
Routes.routekeys [ mapID ] [ routekey ] = create_name
opts [ mapIDKey ] . args [ routekey ] = Routes : GetAceOptRouteTable ( )
-- Draw it
local AutoShow = Routes : GetModule ( " AutoShow " , true )
if AutoShow and db.defaults . use_auto_showhide then
AutoShow : ApplyVisibility ( )
end
Routes : DrawWorldmapLines ( )
Routes : DrawMinimapLines ( true )
-- clear stored name
create_name = " "
create_zone = nil
end ,
disabled = function ( )
return not create_name or strtrim ( create_name ) == " "
end ,
confirm = function ( )
if # db.routes [ Routes.LZName [ create_zone ] ] [ create_name ] . route > 0 then
return true
end
return false
end ,
confirmText = L [ " A route with that name already exists. Overwrite? " ] ,
} ,
}
-- Add another 'Create button'
options.args . add_group.args . add_route_copy = { }
for k , v in pairs ( options.args . add_group.args . add_route ) do
options.args . add_group.args . add_route_copy [ k ] = v
end
options.args . add_group.args . add_route_copy.order
= options.args . add_group.args . source_choices.order + 1
function Routes : RecreateRoute ( mapFile , routeName )
create_name = routeName
create_zone = GetZoneName ( mapFile )
if type ( create_choices [ create_zone ] ) == " table " then
wipe ( create_choices [ create_zone ] )
else
create_choices [ create_zone ] = { }
end
for k , v in pairs ( source_data_choice ) do
source_data_choice [ k ] = false
end
-- Load data for GatherMate2
if Routes.plugins [ " GatherMate2 " ] and Routes.plugins [ " GatherMate2 " ] . IsActive ( ) then
for englishName , translatedName in pairs ( db.routes [ mapFile ] [ create_name ] . selection ) do
local NL = LibStub ( " AceLocale-3.0 " ) : GetLocale ( " GatherMate2Nodes " , true )
translatedName = NL [ englishName ]
for db_type , db_data in pairs ( GatherMate2.gmdbs ) do
local nodeID = GatherMate2 : GetIDForNode ( db_type , translatedName )
if nodeID then
local key = ( " %s;%s;%s;%s " ) : format ( " GatherMate2 " , db_type , nodeID , 0 )
set_source_value ( nil , key , true )
source_data_choice [ " GatherMate2 " ] = true
end
end
end
end
-- Load data for Gatherer
if Routes.plugins [ " Gatherer " ] and Routes.plugins [ " Gatherer " ] . IsActive ( ) then
for englishName , translatedName in pairs ( db.routes [ mapFile ] [ create_name ] . selection ) do
local nodeID = Gatherer.Nodes . Names [ translatedName ]
local db_type = Gatherer.Nodes . Objects [ nodeID ]
if nodeID and db_type then
local key = ( " %s;%s;%s;%s " ) : format ( " Gatherer " , db_type , nodeID , 0 )
set_source_value ( nil , key , true )
source_data_choice [ " Gatherer " ] = true
end
end
end
-- Do it
options.args . add_group.args . add_route.func ( )
end
end
------------------------------------------------------------------------------------------------------
-- Taboo code
do
local intersection = { }
local pool = setmetatable ( { } , { __mode = " kv " } )
local function SortIntersection ( a , b )
return a.x < b.x
end
-- This function takes a taboo (a route basically), and draws it on screen and shades the inside
function RoutesTabooPinMixin : DrawTaboo ( route_data , width , color )
local fh , fw = self : GetHeight ( ) , self : GetWidth ( )
local canvasScale = self : GetEffectiveScale ( ) / self : GetMap ( ) : GetEffectiveScale ( )
width = width or db.defaults . width / canvasScale
color = color or db.defaults . color
-- This part just draws the taboo outline, its the same code as the one that draws routes
do
local last_point
local sx , sy
last_point = route_data.route [ # route_data.route ]
sx , sy = floor ( last_point / 10000 ) / 10000 , ( last_point % 10000 ) / 10000
sy = ( 1 - sy )
for i = 1 , # route_data.route do
local point = route_data.route [ i ]
local ex , ey = floor ( point / 10000 ) / 10000 , ( point % 10000 ) / 10000
ey = ( 1 - ey )
G : DrawLine ( self , sx * fw , sy * fh , ex * fw , ey * fh , width , color , " OVERLAY " )
sx , sy = ex , ey
last_point = point
end
end
if route_data.isroute then return end
-- The shade-lines get half-alpha and 66% width
color [ 4 ] = color [ 4 ] / 2
width = 2 / 3 * width
for z = 0 , 1 do -- loop twice, once for upper half, once for lower half
for k = 0 , 1 , 0.01 do
for i = 1 , # intersection do
pool [ tremove ( intersection ) ] = true
end
local last_point
local sx , sy
last_point = route_data.route [ # route_data.route ]
sx , sy = floor ( last_point / 10000 ) / 10000 , ( last_point % 10000 ) / 10000
for i = 1 , # route_data.route do
local point = route_data.route [ i ]
local ex , ey = floor ( point / 10000 ) / 10000 , ( point % 10000 ) / 10000
if sx + sy == z + k then -- check for endpoint 1
local vector = next ( pool ) or { }
pool [ vector ] = nil
vector.x , vector.y = sx , sy
tinsert ( intersection , vector )
elseif ex + ey == z + k then -- check for endpoint 2
--[[local vector = next(pool) or {}
pool [ vector ] = nil
vector.x , vector.y = ex , ey
tinsert ( intersection , vector ) ] ]
elseif ex + ey - sx - sy ~= 0 then -- 0 indicates a parallel line
local u , t
if z == 0 and k ~= 0 then
u = ( k - sx - sy ) / ( ex + ey - sx - sy )
t = ( sx + ( ex - sx ) * u ) / k
elseif z == 1 and k ~= 1 then
u = ( 1 - sx - sy + k ) / ( ex + ey - sx - sy )
t = ( sx + ( ex - sx ) * u - k ) / ( 1 - k )
else
u , t = - 1 , - 1 -- invalid
end
if t >= 0 and t <= 1 and u >= 0 and u <= 1 then
local vector = next ( pool ) or { }
pool [ vector ] = nil
vector.x , vector.y = sx + ( ex - sx ) * u , sy + ( ey - sy ) * u
tinsert ( intersection , vector )
end
end
sx , sy = ex , ey
last_point = point
end
table.sort ( intersection , SortIntersection )
--[[for j = #intersection, 2, -1 do -- this loop removes identical intersection points
if intersection [ j ] . x == intersection [ j - 1 ] . x then
pool [ tremove ( intersection , j ) ] = true
end
end ] ]
for j = 1 , # intersection - ( # intersection % 2 ) , 2 do -- this loop draws the pairs of intersections
G : DrawLine ( self , intersection [ j ] . x * fw , ( 1 - intersection [ j ] . y ) * fh , intersection [ j + 1 ] . x * fw , ( 1 - intersection [ j + 1 ] . y ) * fh , width , color , " OVERLAY " )
end
end
end
-- restore default alpha
color [ 4 ] = color [ 4 ] * 2
end
local taboo_edit_list = { }
function Routes : DrawTaboos ( )
local TabooPin = self.DataProvider . tabooPin
G : HideLines ( TabooPin )
for taboo_orig , taboo_copy in pairs ( taboo_edit_list ) do
TabooPin : DrawTaboo ( taboo_copy )
end
end
-- Upvalues used for our taboo node functions
local taboo_cache = { }
local TEXTURE , DATA , COORD , CURRENT , REAL , X , Y = 1 , 2 , 3 , 4 , 5 , 6 , 7
local GetOrCreateTabooNode
-- Define our functions for a node pin
local NodeHelper = { }
function NodeHelper : StartMoving ( )
self : StartMoving ( )
self : SetScript ( " OnUpdate " , NodeHelper.OnUpdate )
self.elapsed = 0
end
function NodeHelper : OnDragStop ( )
self : StopMovingOrSizing ( )
self : SetScript ( " OnUpdate " , nil )
self.elapsed = nil
self : SetParent ( Routes.DataProvider . tabooPin )
self : ClearAllPoints ( )
self : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , self [ X ] * Routes.DataProvider . tabooPin : GetWidth ( ) , - self [ Y ] * Routes.DataProvider . tabooPin : GetHeight ( ) )
end
function NodeHelper : OnUpdate ( elapsed )
self.elapsed = self.elapsed + elapsed
if self.elapsed < 0.05 then return end
-- get current location
local id = self [ COORD ]
local x , y = self : GetCenter ( )
local parent = self : GetParent ( )
local pw , ph = parent : GetWidth ( ) , parent : GetHeight ( )
x = ( x - parent : GetLeft ( ) ) / pw
y = ( parent : GetTop ( ) - y ) / ph
-- Only within our frame
if x < 0.0001 then x = 0.0001 end -- don't allow 0 values, because our intersection function doesn't like it.
if x > 0.999 then x = 0.999 end -- don't use 0.9999 because we want some slack space of 10 nodes in case user drags multiple nodes on top of each other
if y < 0.0001 then y = 0.0001 end
if y > 0.999 then y = 0.999 end -- we can't have y == 1 because of coord storage format
local new_id = Routes : getID ( x , y )
if id == new_id then return end -- position didn't change, no updates
x , y = Routes : getXY ( new_id )
-- edit the route
local current = self [ CURRENT ]
self [ DATA ] . route [ current ] = new_id
self [ COORD ] , self [ X ] , self [ Y ] = new_id , x , y
-- Relocate the before helper pin
local nodenum = current == 1 and # self [ DATA ] . route or current - 1
local node = self [ DATA ] . fakenodes [ nodenum ]
local x2 , y2 = Routes : getXY ( self [ DATA ] . route [ nodenum ] )
local new_id = Routes : getID ( ( x + x2 ) / 2 , ( y + y2 ) / 2 )
x2 , y2 = Routes : getXY ( new_id )
node [ COORD ] , node [ X ] , node [ Y ] = new_id , x2 , y2
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x2 * pw , - y2 * ph )
-- Relocate the after helper pin
nodenum = current == # self [ DATA ] . route and 1 or current + 1
node = self [ DATA ] . fakenodes [ current ]
x2 , y2 = Routes : getXY ( self [ DATA ] . route [ nodenum ] )
new_id = Routes : getID ( ( x + x2 ) / 2 , ( y + y2 ) / 2 )
x2 , y2 = Routes : getXY ( new_id )
node [ COORD ] , node [ X ] , node [ Y ] = new_id , x2 , y2
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x2 * pw , - y2 * ph )
-- redraw
Routes : DrawTaboos ( )
end
function NodeHelper : OnClick ( button , down )
if button == " LeftButton " and not self [ REAL ] then
-- Promote helper node to a real node
self [ REAL ] = true
self : SetWidth ( 16 * self.scale )
self : SetHeight ( 16 * self.scale )
self : SetAlpha ( 1 )
local current = self [ CURRENT ] + 1
for i = current , # self [ DATA ] . route do
self [ DATA ] . nodes [ i ] [ CURRENT ] = i + 1
self [ DATA ] . fakenodes [ i ] [ CURRENT ] = i + 1
end
self [ CURRENT ] = current
tinsert ( self [ DATA ] . route , current , self [ COORD ] )
tinsert ( self [ DATA ] . nodes , current , self )
tremove ( self [ DATA ] . fakenodes , current - 1 )
local x , y = Routes : getXY ( self [ COORD ] )
local w , h = Routes.DataProvider . tabooPin : GetWidth ( ) , Routes.DataProvider . tabooPin : GetHeight ( )
-- Now create the before helper pin
local nodenum = current == 1 and # self [ DATA ] . route or current - 1
local x2 , y2 = Routes : getXY ( self [ DATA ] . route [ nodenum ] )
local new_id = Routes : getID ( ( x + x2 ) / 2 , ( y + y2 ) / 2 )
local node = GetOrCreateTabooNode ( self [ DATA ] , new_id )
x2 , y2 = Routes : getXY ( new_id )
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x2 * w , - y2 * h )
node : SetWidth ( 10 * node.scale )
node : SetHeight ( 10 * node.scale )
node : SetAlpha ( 0.75 )
node [ REAL ] = false
node [ CURRENT ] = nodenum
tinsert ( self [ DATA ] . fakenodes , nodenum , node )
-- Create the after helper pin
nodenum = current == # self [ DATA ] . route and 1 or current + 1
x2 , y2 = Routes : getXY ( self [ DATA ] . route [ nodenum ] )
new_id = Routes : getID ( ( x + x2 ) / 2 , ( y + y2 ) / 2 )
node = GetOrCreateTabooNode ( self [ DATA ] , new_id )
x2 , y2 = Routes : getXY ( new_id )
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x2 * w , - y2 * h )
node : SetWidth ( 10 * node.scale )
node : SetHeight ( 10 * node.scale )
node : SetAlpha ( 0.75 )
node [ REAL ] = false
node [ CURRENT ] = current
tinsert ( self [ DATA ] . fakenodes , current , node )
elseif button == " RightButton " and self [ REAL ] and # self [ DATA ] . route > 3 then
-- Delete node if we have more than 3 nodes
local current = self [ CURRENT ]
for i = current + 1 , # self [ DATA ] . route do
self [ DATA ] . nodes [ i ] [ CURRENT ] = i - 1
self [ DATA ] . fakenodes [ i ] [ CURRENT ] = i - 1
end
tremove ( self [ DATA ] . route , current )
local a = tremove ( self [ DATA ] . nodes , current )
local b = tremove ( self [ DATA ] . fakenodes , current )
-- Relocate the before helper pin
local w , h = Routes.DataProvider . tabooPin : GetWidth ( ) , Routes.DataProvider . tabooPin : GetHeight ( )
local nodenum = current == 1 and # self [ DATA ] . route or current - 1
local nodenum2 = current > # self [ DATA ] . route and 1 or current
local node = self [ DATA ] . fakenodes [ nodenum ]
local x , y = Routes : getXY ( self [ DATA ] . route [ nodenum ] )
local x2 , y2 = Routes : getXY ( self [ DATA ] . route [ nodenum2 ] )
local new_id = Routes : getID ( ( x + x2 ) / 2 , ( y + y2 ) / 2 )
x2 , y2 = Routes : getXY ( new_id )
node [ COORD ] , node [ X ] , node [ Y ] = new_id , x2 , y2
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x2 * w , - y2 * h )
-- Recycle ourselves
a : Hide ( )
b : Hide ( )
taboo_cache [ a ] = true
taboo_cache [ b ] = true
Routes : DrawTaboos ( )
end
-- Check data
for i = 1 , # self [ DATA ] . route do
assert ( self [ DATA ] . nodes [ i ] [ CURRENT ] == i )
assert ( self [ DATA ] . fakenodes [ i ] [ CURRENT ] == i )
end
end
GetOrCreateTabooNode = function ( route_data , coord )
local node = next ( taboo_cache )
if node then
taboo_cache [ node ] = nil
else
-- Create new node
node = CreateFrame ( " Button " , nil , Routes.DataProvider . tabooPin )
node : SetFrameLevel ( Routes.DataProvider . tabooPin : GetFrameLevel ( ) + 6 ) -- we need to be above others (GatherMate nodes are @ 5)
-- set it up
local texture = node : CreateTexture ( nil , " OVERLAY " )
texture : SetTexture ( " Interface \\ WorldMap \\ WorldMapPartyIcon " )
texture : SetAllPoints ( node )
node [ TEXTURE ] = texture
node : EnableMouse ( true )
node : SetMovable ( true )
end
node.scale = 1 / ( Routes.DataProvider . tabooPin : GetEffectiveScale ( ) / Routes.DataProvider . tabooPin : GetMap ( ) : GetEffectiveScale ( ) )
node : SetWidth ( 16 * node.scale )
node : SetHeight ( 16 * node.scale )
-- store data
node [ X ] , node [ Y ] = Routes : getXY ( coord )
node [ COORD ] = coord
node [ DATA ] = route_data
node : RegisterForDrag ( " LeftButton " )
node : RegisterForClicks ( " LeftButtonDown " , " RightButtonUp " )
node : SetScript ( " OnDragStart " , NodeHelper.StartMoving )
node : SetScript ( " OnClick " , NodeHelper.OnClick )
node : SetScript ( " OnDragStop " , NodeHelper.OnDragStop )
node : Show ( )
return node
end
local function TabooDeleteNode ( menubutton , node )
local route = node [ DATA ] . route
for i = 1 , # route do
if route [ i ] == node [ COORD ] then
tremove ( route , i )
break
end
end
node : Hide ( )
taboo_cache [ node ] = true
end
local TabooHandler = { }
function TabooHandler : EditTaboo ( info )
local zone = tonumber ( info [ 2 ] )
-- make a copy of the taboo for editing
local taboo_data
if info [ 1 ] == " routes_group " then
local routeName = Routes.routekeys [ zone ] [ info [ 3 ] ]
taboo_data = db.routes [ zone ] [ routeName ]
else
local tabooName = Routes.tabookeys [ zone ] [ info [ 3 ] ]
taboo_data = db.taboo [ zone ] [ tabooName ]
end
local copy_of_taboo_data = { route = { } , nodes = { } , fakenodes = { } }
if info [ 1 ] == " routes_group " then
local is_running , route_table = Routes.TSP : IsTSPRunning ( )
if is_running and route_table == taboo_data.route then return end
if ConfigHandler : IsCluster ( info ) then return end
copy_of_taboo_data.isroute = true
taboo_data.editing = true
throttleFrame : Show ( ) -- To remove the route from the map
end
for i = 1 , # taboo_data.route do
copy_of_taboo_data.route [ i ] = taboo_data.route [ i ]
end
taboo_edit_list [ taboo_data ] = copy_of_taboo_data
-- open the WorldMapFlame on the right zone
if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then
OpenWorldMap ( zone )
else
ShowUIPanel ( WorldMapFrame )
WorldMapFrame : SetMapID ( zone )
end
local fh , fw = Routes.DataProvider . tabooPin : GetHeight ( ) , Routes.DataProvider . tabooPin : GetWidth ( )
local route = copy_of_taboo_data.route
-- Pin the real nodes
for i = 1 , # route do
local node = GetOrCreateTabooNode ( copy_of_taboo_data , route [ i ] )
local x , y = node [ X ] , node [ Y ]
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x * fw , - y * fh )
node [ CURRENT ] = i
node [ REAL ] = true
copy_of_taboo_data.nodes [ i ] = node
node : SetWidth ( 16 * node.scale )
node : SetHeight ( 16 * node.scale )
node : SetAlpha ( 1 )
end
-- Pin the helper nodes
for i = 1 , # route do
local beforeX , beforeY = Routes : getXY ( route [ i ] )
local afterX , afterY = Routes : getXY ( route [ i == # route and 1 or i + 1 ] )
local new_id = Routes : getID ( ( beforeX + afterX ) / 2 , ( beforeY + afterY ) / 2 )
local node = GetOrCreateTabooNode ( copy_of_taboo_data , new_id )
local x , y = Routes : getXY ( new_id )
node : SetPoint ( " CENTER " , Routes.DataProvider . tabooPin , " TOPLEFT " , x * fw , - y * fh )
node [ CURRENT ] = i
node [ REAL ] = false
copy_of_taboo_data.fakenodes [ i ] = node
node : SetWidth ( 10 * node.scale )
node : SetHeight ( 10 * node.scale )
node : SetAlpha ( 0.75 )
end
-- and draw taboos
Routes : DrawTaboos ( )
end
function TabooHandler : SaveEditTaboo ( info )
local zone = tonumber ( info [ 2 ] )
if info [ 1 ] == " routes_group " then
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local route_data = db.routes [ zone ] [ route ]
local copy_of_taboo = self : CancelEditTaboo ( info )
for i = 1 , # copy_of_taboo.route do
route_data.route [ i ] = copy_of_taboo.route [ i ]
end
for i = # copy_of_taboo.route + 1 , # route_data.route do
route_data.route [ i ] = nil
end
for tabooname , used in pairs ( route_data.taboos ) do
if used then
local taboo_data = db.taboo [ zone ] [ tabooname ]
Routes : ApplyTabooToRoute ( zone , taboo_data , route_data )
end
end
else
local taboo = Routes.tabookeys [ zone ] [ info [ 3 ] ]
local taboo_data = db.taboo [ zone ] [ taboo ]
local copy_of_taboo = self : CancelEditTaboo ( info )
taboo_data.route = copy_of_taboo.route
-- Update all routes with this taboo
for route_name , route_data in pairs ( db.routes [ zone ] ) do
if route_data.taboos [ taboo ] then
Routes : ApplyTabooToRoute ( zone , taboo_data , route_data )
Routes : UnTabooRoute ( zone , route_data )
else
-- Set the false value (acedb generated default) to nil, so we don't pairs() over it
route_data.taboos [ taboo ] = nil
end
end
end
throttleFrame : Show ( ) -- Redraw the changes
end
function TabooHandler : CancelEditTaboo ( info )
local zone = tonumber ( info [ 2 ] )
local taboo
if info [ 1 ] == " routes_group " then
local routeName = Routes.routekeys [ zone ] [ info [ 3 ] ]
taboo = db.routes [ zone ] [ routeName ]
else
local tabooName = Routes.tabookeys [ zone ] [ info [ 3 ] ]
taboo = db.taboo [ zone ] [ tabooName ]
end
if info [ 1 ] == " routes_group " then
taboo.editing = nil
throttleFrame : Show ( ) -- Redraw the route
end
local copy_of_taboo = taboo_edit_list [ taboo ]
taboo_edit_list [ taboo ] = nil
for i = 1 , # copy_of_taboo.route do
-- Return the pool of pins representing real nodes
local node = copy_of_taboo.nodes [ i ]
node : Hide ( )
taboo_cache [ node ] = true
copy_of_taboo.nodes [ i ] = nil
-- Return the pool of pins representing helper nodes
node = copy_of_taboo.fakenodes [ i ]
node : Hide ( )
taboo_cache [ node ] = true
copy_of_taboo.fakenodes [ i ] = nil
end
assert ( not next ( copy_of_taboo.fakenodes ) )
assert ( not next ( copy_of_taboo.nodes ) )
Routes : DrawTaboos ( )
return copy_of_taboo -- return the edited table
end
function TabooHandler : DeleteTaboo ( info )
if self : IsBeingEdited ( info ) then
Routes : Print ( L [ " You may not delete a taboo that is being edited. " ] )
return
end
local zone = tonumber ( info [ 2 ] )
local zoneKey = info [ 2 ]
local tabookey = info [ 3 ]
local taboo = Routes.tabookeys [ zone ] [ tabookey ]
db.taboo [ zone ] [ taboo ] = nil
--local tabookey = taboo:gsub("%s", "\255") -- can't have spaces in the key
options.args . taboo_group.args [ zoneKey ] . args [ tabookey ] = nil -- delete taboo from aceopt
Routes.tabookeys [ zone ] [ tabookey ] = nil
if next ( db.taboo [ zone ] ) == nil then
db.taboo [ zone ] = nil
options.args . taboo_group.args [ zoneKey ] = nil -- delete zone from aceopt if no routes remaining
Routes.tabookeys [ zone ] = nil
end
-- Now delete the taboo region from all routes in the zone that had it
for route_name , route_data in pairs ( db.routes [ zone ] ) do
if route_data.taboos [ taboo ] then
route_data.taboos [ taboo ] = nil
Routes : UnTabooRoute ( zone , route_data )
else
-- Set the false value (acedb generated default) to nil, so we don't pairs() over it
route_data.taboos [ taboo ] = nil
end
end
end
function TabooHandler : IsBeingEdited ( info )
local zone = tonumber ( info [ 2 ] )
local taboo
if info [ 1 ] == " routes_group " then
local routeName = Routes.routekeys [ zone ] [ info [ 3 ] ]
taboo = db.routes [ zone ] [ routeName ]
else
local tabooName = Routes.tabookeys [ zone ] [ info [ 3 ] ]
taboo = db.taboo [ zone ] [ tabooName ]
end
if taboo_edit_list [ taboo ] then return true end
return false
end
function TabooHandler : IsNotBeingEdited ( info )
return not self : IsBeingEdited ( info )
end
function TabooHandler . GetTabooName ( info )
local zone = tonumber ( info [ 2 ] )
return Routes.tabookeys [ zone ] [ info [ 3 ] ]
end
do
local tabooTable = {
type = " group " ,
name = TabooHandler.GetTabooName ,
desc = TabooHandler.GetTabooName ,
handler = TabooHandler ,
args = {
desc = {
type = " description " ,
order = 0 ,
name = L [ " TABOO_EDIT_DESC " ] ,
} ,
edit_taboo = {
type = " execute " ,
name = L [ " Edit taboo region " ] ,
desc = L [ " Edit this taboo region on the world map " ] ,
order = 1 ,
func = " EditTaboo " ,
disabled = " IsBeingEdited " ,
} ,
save_edit_taboo = {
type = " execute " ,
name = L [ " Save taboo edit " ] ,
desc = L [ " Stop editing this taboo region on the world map and save the edits " ] ,
order = 2 ,
func = " SaveEditTaboo " ,
disabled = " IsNotBeingEdited " ,
} ,
cancel_edit_taboo = {
type = " execute " ,
name = L [ " Cancel taboo edit " ] ,
desc = L [ " Stop editing this taboo region on the world map and abandon changes made " ] ,
order = 3 ,
func = " CancelEditTaboo " ,
disabled = " IsNotBeingEdited " ,
} ,
delete_taboo = {
type = " execute " ,
name = L [ " Delete Taboo " ] ,
desc = L [ " Delete this taboo region permanently. This will also remove it from all routes that use it. " ] ,
order = 4 ,
func = " DeleteTaboo " ,
disabled = " IsBeingEdited " ,
confirm = true ,
confirmText = L [ " Are you sure you want to delete this taboo? This action will also remove the taboo from all routes that use it. " ] ,
} ,
} ,
}
function Routes : GetAceOptTabooTable ( )
return tabooTable
end
end
local taboo_name = " "
local create_zone
local create_zones = { }
options.args . taboo_group.args = {
desc = {
name = L [ " TABOO_DESC " ] ,
type = " description " ,
order = 0 ,
} ,
taboo_name = {
type = " input " ,
name = L [ " Name of Taboo " ] ,
desc = L [ " Name of taboo region to add " ] ,
validate = function ( info , name )
if name == " " or strtrim ( name ) == " " then
return L [ " No name given for new taboo region " ]
end
return true
end ,
get = function ( ) return taboo_name end ,
set = function ( info , v ) taboo_name = strtrim ( v ) end ,
order = 100 ,
} ,
zone_choice = {
name = L [ " Select Zone " ] , type = " select " ,
desc = L [ " Zone to create taboo in " ] ,
order = 200 ,
values = function ( )
if not next ( create_zones ) then
for zoneName in pairs ( Routes.LZName ) do
create_zones [ zoneName ] = zoneName
end
end
return create_zones
end ,
get = function ( )
if create_zone then return create_zone end
-- Use currently viewed map on first view.
local mapID = WorldMapFrame : GetMapID ( )
if not mapID then return nil end
create_zone = GetZoneName ( mapID )
return create_zone
end ,
set = function ( info , key ) create_zone = key end ,
} ,
add_taboo = {
name = L [ " Create Taboo " ] , type = " execute " ,
desc = L [ " Create Taboo " ] ,
order = 300 ,
func = function ( )
taboo_name = strtrim ( taboo_name )
if not taboo_name or taboo_name == " " then
Routes : Print ( L [ " No name given for new taboo region " ] )
return
end
local mapID = Routes.LZName [ create_zone ]
local mapIDKey = tostring ( mapID )
db.taboo [ mapID ] [ taboo_name ] . route [ 1 ] = 81895171
db.taboo [ mapID ] [ taboo_name ] . route [ 2 ] = 51077512
db.taboo [ mapID ] [ taboo_name ] . route [ 3 ] = 40941800
-- Create the aceopts table entry for our new route
local opts = options.args . taboo_group.args
if not opts [ mapIDKey ] then
opts [ mapIDKey ] = {
type = " group " ,
name = create_zone ,
desc = L [ " Taboos in %s " ] : format ( create_zone ) ,
args = {
desc = taboo_zone_args_desc_table ,
} ,
}
Routes.tabookeys [ mapID ] = { }
end
local tabookey = taboo_name : gsub ( " %s " , " \255 " ) -- can't have spaces in the key
Routes.tabookeys [ mapID ] [ tabookey ] = taboo_name
opts [ mapIDKey ] . args [ tabookey ] = Routes : GetAceOptTabooTable ( )
-- clear stored name
taboo_name = " "
create_zone = nil
end ,
disabled = function ( )
return not taboo_name or strtrim ( taboo_name ) == " "
end ,
confirm = function ( )
if # db.taboo [ Routes.LZName [ create_zone ] ] [ taboo_name ] . route > 0 then
return true
end
return false
end ,
confirmText = L [ " A taboo with that name already exists. Overwrite? " ] ,
} ,
}
function TabooHandler : IsNotEditAllowed ( info )
local zone = tonumber ( info [ 2 ] )
local route = Routes.routekeys [ zone ] [ info [ 3 ] ]
local route_table = db.routes [ zone ] [ route ]
if taboo_edit_list [ route_table ] then return true end
local is_running , route_table2 = Routes.TSP : IsTSPRunning ( )
if is_running and route_table2 == route_table.route then
return true
end
if ConfigHandler : IsCluster ( info ) then
return true
end
return false
end
do
local routeEditTable = {
type = " group " ,
order = 400 ,
name = L [ " Edit Route Manually " ] ,
handler = TabooHandler ,
args = {
desc = {
type = " description " ,
order = 0 ,
name = L [ " ROUTE_EDIT_DESC " ] ,
} ,
edit_route = {
type = " execute " ,
name = L [ " Edit route " ] ,
desc = L [ " Edit this route on the world map " ] ,
order = 1 ,
func = " EditTaboo " ,
disabled = " IsNotEditAllowed " ,
} ,
save_edit_route = {
type = " execute " ,
name = L [ " Save route edit " ] ,
desc = L [ " Stop editing this route on the world map and save the edits " ] ,
order = 2 ,
func = " SaveEditTaboo " ,
disabled = " IsNotBeingEdited " ,
} ,
cancel_edit_route = {
type = " execute " ,
name = L [ " Cancel route edit " ] ,
desc = L [ " Stop editing this route on the world map and abandon changes made " ] ,
order = 3 ,
func = " CancelEditTaboo " ,
disabled = " IsNotBeingEdited " ,
} ,
} ,
}
function Routes : GetAceOptRouteEditTable ( )
return routeEditTable
end
end
--/run Routes:TestFunc()
--/run Routes:ClearTestFunc()
--[[function Routes:TestFunc(taboo)
taboo = taboo or Routes.db . global.taboo [ Routes.LZName [ " Shattrath City " ] [ 1 ] ] [ " abc " ]
local fw , fh = RoutesTabooFrame : GetWidth ( ) , RoutesTabooFrame : GetHeight ( )
for i = 0 , 1 , 0.02 do
for j = 0 , 0.99 , 0.02 do
local point = self : getID ( i , j )
local node = GetOrCreateTabooNode ( taboo , point )
local x , y = node [ X ] , node [ Y ]
node : SetPoint ( " CENTER " , RoutesTabooFrame , " TOPLEFT " , x * fw , - y * fh )
if self : IsNodeInTaboo ( x , y , taboo ) then
node [ TEXTURE ] : SetVertexColor ( 1 , 0 , 0 , 1 )
else
node [ TEXTURE ] : SetVertexColor ( 0 , 1 , 0 , 1 )
end
node [ BEFORE ] = point
node [ AFTER ] = point
node : SetAlpha ( 0.5 )
end
end
end
function Routes : ClearTestFunc ( )
for i = 0 , 1 , 0.02 do
for j = 0 , 0.99 , 0.02 do
local point = self : getID ( i , j )
local node = GetOrCreateTabooNode ( taboo , point )
node : Hide ( )
taboo_cache [ node ] = true
end
end
end ] ]
end
do
-- This function tests if the node at location (x,y) is in a taboo region
-- It does this by drawing a line from (0,0) to (x,y) and seeing how many times
-- this line intersects the taboo polygon edges. If its even, its outside. If
-- its odd its inside.
function Routes : IsNodeInTaboo ( x , y , taboo )
-- our taboo regions have x and y between 0.0001 and 0.9999
if x <= 0 or y <= 0 or x >= 1 or y >= 1 then return false end
local count = 0
local last_point = taboo.route [ # taboo.route ]
local sx , sy = floor ( last_point / 10000 ) / 10000 , ( last_point % 10000 ) / 10000
for i = 1 , # taboo.route do
local point = taboo.route [ i ]
local ex , ey = floor ( point / 10000 ) / 10000 , ( point % 10000 ) / 10000
-- check if (0,0)-(x,y) intersects with (sx,sy)-(ex,ey)
if sx >= 0 and sx <= x and sx / sy == x / y then -- check for endpoint 1
count = count + 1 -- (sx,sy) lies on the line
elseif ex >= 0 and ex <= x and ex / ey == x / y then -- check for endpoint 2
-- (ex,ey) lies on the line, do nothing
else
local d = ( x * ey - x * sy - y * ex + y * sx )
if d ~= 0 then
local u = ( sx * y - sy * x ) / d
local t = ( sx + ( ex - sx ) * u ) / x
if t >= 0 and t <= 1 and u >= 0 and u <= 1 then
count = count + 1
end
end
end
sx , sy = ex , ey
last_point = point
end
return count % 2 == 1
end
function Routes : ApplyTabooToRoute ( zone , taboo_data , route_data )
if route_data.metadata then
-- this is a clustered route
for i = # route_data.route , 1 , - 1 do
for j = # route_data.metadata [ i ] , 1 , - 1 do
local coord = route_data.metadata [ i ] [ j ]
local x , y = Routes : getXY ( coord )
if Routes : IsNodeInTaboo ( x , y , taboo_data ) then -- remove node
-- recalcuate centroid
local cx , cy = Routes : getXY ( route_data.route [ i ] )
local num_data = # route_data.metadata [ i ]
if num_data > 1 then
-- more than 1 node in this cluster
cx , cy = ( cx * num_data - x ) / ( num_data - 1 ) , ( cy * num_data - y ) / ( num_data - 1 )
tremove ( route_data.metadata [ i ] , j )
route_data.route [ i ] = Routes : getID ( cx , cy )
else
-- only 1 node in this cluster, just remove it
tremove ( route_data.metadata , i )
tremove ( route_data.route , i )
end
tinsert ( route_data.taboolist , coord )
route_data.length = Routes.TSP : PathLength ( route_data.route , zone )
throttleFrame : Show ( )
end
end
end
else
-- this is not a clustered route
for i = # route_data.route , 1 , - 1 do
local coord = route_data.route [ i ]
local x , y = Routes : getXY ( coord )
if Routes : IsNodeInTaboo ( x , y , taboo_data ) then -- remove node
tremove ( route_data.route , i )
tinsert ( route_data.taboolist , coord )
route_data.length = self.TSP : PathLength ( route_data.route , zone )
throttleFrame : Show ( )
end
end
end
end
function Routes : UnTabooRoute ( zone , route_data )
for i = # route_data.taboolist , 1 , - 1 do
local coord = route_data.taboolist [ i ]
local x , y = Routes : getXY ( coord )
local flag = false
for tabooname , used in pairs ( route_data.taboos ) do
if used and Routes : IsNodeInTaboo ( x , y , db.taboo [ zone ] [ tabooname ] ) then
flag = true
end
end
if flag == false then
route_data.length = Routes.TSP : InsertNode ( route_data.route , route_data.metadata , zone , coord , route_data.cluster_dist or 65 ) -- 65 is the old default
tremove ( route_data.taboolist , i )
throttleFrame : Show ( )
end
end
end
end
------------------------------------------------------------------------------------------------------
-- The following function is used with permission from Daniel Stephens <iriel@vigilance-committee.org>
-- with reference to TaxiFrame.lua in Blizzard's UI and Graph-1.0 Ace2 library (by Cryect) which I now
-- maintain after porting it to LibGraph-2.0 LibStub library -- Xinhuan
local TAXIROUTE_LINEFACTOR = 128 / 126 ; -- Multiplying factor for texture coordinates
local TAXIROUTE_LINEFACTOR_2 = TAXIROUTE_LINEFACTOR / 2 ; -- Half of that
-- T - Texture
-- C - Canvas Frame (for anchoring)
-- sx,sy - Coordinate of start of line
-- ex,ey - Coordinate of end of line
-- w - Width of line
-- relPoint - Relative point on canvas to interpret coords (Default BOTTOMLEFT)
function G : DrawLine ( C , sx , sy , ex , ey , w , color , layer )
local relPoint = " BOTTOMLEFT "
if not C.Routes_Lines then
C.Routes_Lines = { }
C.Routes_Lines_Used = { }
end
local T = tremove ( C.Routes_Lines ) or C : CreateTexture ( nil , " ARTWORK " )
T : SetTexture ( " Interface \\ AddOns \\ Routes \\ line " )
T : SetTexelSnappingBias ( 0 )
T : SetSnapToPixelGrid ( false )
tinsert ( C.Routes_Lines_Used , T )
T : SetDrawLayer ( layer or " ARTWORK " )
T : SetVertexColor ( color [ 1 ] , color [ 2 ] , color [ 3 ] , color [ 4 ] ) ;
-- Determine dimensions and center point of line
local dx , dy = ex - sx , ey - sy ;
-- Calculate actual length of line
local l = ( ( dx * dx ) + ( dy * dy ) ) ^ 0.5 ;
-- Quick escape if it's zero length
if l == 0 then
T : ClearAllPoints ( ) ;
T : SetTexCoord ( 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ) ;
T : SetPoint ( " BOTTOMLEFT " , C , relPoint , cx , cy ) ;
T : SetPoint ( " TOPRIGHT " , C , relPoint , cx , cy ) ;
return T ;
end
local cx , cy = ( sx + ex ) / 2 , ( sy + ey ) / 2 ;
-- Normalize direction if necessary
if ( dx < 0 ) then
dx , dy = - dx , - dy ;
end
-- Sin and Cosine of rotation, and combination (for later)
local s , c = - dy / l , dx / l ;
local sc = s * c ;
-- Calculate bounding box size and texture coordinates
local Bwid , Bhgt , BLx , BLy , TLx , TLy , TRx , TRy , BRx , BRy ;
if ( dy >= 0 ) then
Bwid = ( ( l * c ) - ( w * s ) ) * TAXIROUTE_LINEFACTOR_2 ;
Bhgt = ( ( w * c ) - ( l * s ) ) * TAXIROUTE_LINEFACTOR_2 ;
BLx , BLy , BRy = ( w / l ) * sc , s * s , ( l / w ) * sc ;
BRx , TLx , TLy , TRx = 1 - BLy , BLy , 1 - BRy , 1 - BLx ;
TRy = BRx ;
else
Bwid = ( ( l * c ) + ( w * s ) ) * TAXIROUTE_LINEFACTOR_2 ;
Bhgt = ( ( w * c ) + ( l * s ) ) * TAXIROUTE_LINEFACTOR_2 ;
BLx , BLy , BRx = s * s , - ( l / w ) * sc , 1 + ( w / l ) * sc ;
BRy , TLx , TLy , TRy = BLx , 1 - BRx , 1 - BLx , 1 - BLy ;
TRx = TLy ;
end
-- Thanks Blizzard for adding (-)10000 as a hard-cap and throwing errors!
-- The cap was added in 3.1.0 and I think it was upped in 3.1.1
-- (way less chance to get the error)
if TLx > 10000 then TLx = 10000 elseif TLx < - 10000 then TLx = - 10000 end
if TLy > 10000 then TLy = 10000 elseif TLy < - 10000 then TLy = - 10000 end
if BLx > 10000 then BLx = 10000 elseif BLx < - 10000 then BLx = - 10000 end
if BLy > 10000 then BLy = 10000 elseif BLy < - 10000 then BLy = - 10000 end
if TRx > 10000 then TRx = 10000 elseif TRx < - 10000 then TRx = - 10000 end
if TRy > 10000 then TRy = 10000 elseif TRy < - 10000 then TRy = - 10000 end
if BRx > 10000 then BRx = 10000 elseif BRx < - 10000 then BRx = - 10000 end
if BRy > 10000 then BRy = 10000 elseif BRy < - 10000 then BRy = - 10000 end
-- Set texture coordinates and anchors
T : ClearAllPoints ( ) ;
--[[ When stuff did error
local status , err = pcall ( T.SetTexCoord , T , TLx , TLy , BLx , BLy , TRx , TRy , BRx , BRy )
if not status then
error ( ( " SetTexCoord tossed an error, please report on http://wowace.com/projects/routes >> Error: %s TLx: %s TLy: %s BLx: %s BLy: %s TRx: %s TRy: %s BRx: %s BRy: %s " ) : format (
err or " nil " , TLx or " nil " , TLy or " nil " , BLx or " nil " , BLy or " nil " , TRx or " nil " , TRy or " nil " , BRx or " nil " , BRy or " nil "
) ) ;
end
--]]
T : SetTexCoord ( TLx , TLy , BLx , BLy , TRx , TRy , BRx , BRy )
T : SetPoint ( " BOTTOMLEFT " , C , relPoint , cx - Bwid , cy - Bhgt ) ;
T : SetPoint ( " TOPRIGHT " , C , relPoint , cx + Bwid , cy + Bhgt ) ;
T : Show ( )
return T
end
function G : HideLines ( C )
if C.Routes_Lines then
for i = # C.Routes_Lines_Used , 1 , - 1 do
C.Routes_Lines_Used [ i ] : Hide ( )
tinsert ( C.Routes_Lines , tremove ( C.Routes_Lines_Used ) )
end
end
end
function Routes : ReparentMinimap ( minimap )
self.G : HideLines ( Minimap )
Minimap = minimap
throttleFrame : Show ( )
end
-- vim: ts=4 noexpandtab