You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
3649 lines
117 KiB
3649 lines
117 KiB
--[[
|
|
********************************************************************************
|
|
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
|
|
|