fixed the camera so its motion is no longer tied to framerate
limited minimum and maximum zoom on camera changed point scaling so that cities get smaller when zooming in changed selection cursor to hollow circle changed capital cities to orange squares included data/earth/ directory so that files are loaded from there when they're not available in the mod folder when cities are hidden, hovering over travel nodes will display the travel node location (broken for now) when cities and travel nodes are hidden, hovering over ai markers will display their details made the information boxes more opaque changed border-calculating code so that it works with higher resolutions
|
@ -0,0 +1 @@
|
|||
build/
|
34
ai.lua
|
@ -1,13 +1,27 @@
|
|||
--Manage the AI nodes used by DEFCON.
|
||||
local t = {}
|
||||
local bmp = require 'bmp'
|
||||
local lg = assert( love.graphics )
|
||||
local locationQuery = require 'locationQuery'
|
||||
|
||||
local t = setmetatable( {}, {__index = locationQuery } )
|
||||
local print = print
|
||||
|
||||
local aiNode = {}
|
||||
local mtAiNode = { __index = aiNode }
|
||||
|
||||
function aiNode:formatDisplayInfo()
|
||||
return ([[AI NODE: %d
|
||||
LONGITUDE: %3.2f
|
||||
LATITUDE: %3.2f
|
||||
ATTACKING: %s
|
||||
]]):format( self.idx, self.x, self.y, tostring(self.attacking) )
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
local img, imgd = bmp.load( filename )
|
||||
local nodes = {
|
||||
visible = true,
|
||||
all = {},
|
||||
att = {},
|
||||
ptsAtt = {},
|
||||
def = {},
|
||||
|
@ -16,15 +30,19 @@ function t.load( filename )
|
|||
imgd = imgd }
|
||||
|
||||
print( "=== Loading AI Markers: ===" )
|
||||
local idx = 1
|
||||
for x = 0, 511 do
|
||||
for y = 0, 284 do
|
||||
local r, g = imgd:getPixel( x, 284 - y )
|
||||
if r > 0.5 or g > 0.5 then
|
||||
local long = x * (360 / imgd:getWidth()) - 180
|
||||
local lat = y * (200 / img:getHeight()) - 100
|
||||
local set = (r > 0.5) and nodes.att or nodes.def
|
||||
set[#set + 1] = {x = long, y = lat}
|
||||
print( #set, long, lat )
|
||||
local attacking = (r > 0.5)
|
||||
local set = attacking and nodes.att or nodes.def
|
||||
local node = setmetatable( {x = long, y = lat, attacking = attacking, idx = idx}, mtAiNode )
|
||||
nodes.all[ idx ] = node
|
||||
set[#set + 1] = node
|
||||
idx = idx + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -43,7 +61,13 @@ function t.load( filename )
|
|||
end
|
||||
end
|
||||
|
||||
return setmetatable( nodes, {__index = t } )
|
||||
nodes.all = locationQuery.New( nodes.all )
|
||||
setmetatable( nodes, {__index = t } )
|
||||
return nodes
|
||||
end
|
||||
|
||||
function t.selectNearest( nodes, x, y )
|
||||
return (nodes.all):getClosestPoint( x, y )
|
||||
end
|
||||
|
||||
function t.draw( nodes )
|
||||
|
|
15
camera.lua
|
@ -20,12 +20,12 @@ function Camera.GetNodeCoordinate( x, y )
|
|||
return tfNodes:inverseTransformPoint( x, y )
|
||||
end
|
||||
|
||||
function Camera.Zoom( out )
|
||||
local scale = out and 1.1 or 0.9
|
||||
tf:scale( scale, scale )
|
||||
local x = Camera.x
|
||||
local y = Camera.y
|
||||
return Camera.Set( x, y, Camera.w * scale, Camera.h * scale )
|
||||
function Camera.Zoom( delta )
|
||||
local scale = 1.0 + delta
|
||||
if Camera.zoom < 25.0 and delta > 0 or --zooming in
|
||||
Camera.zoom > 0.5 and delta < 0 then --zooming out
|
||||
return Camera.Set( Camera.x , Camera.y, Camera.w * scale, Camera.h * scale )
|
||||
end
|
||||
end
|
||||
|
||||
function Camera.Translate( x, y )
|
||||
|
@ -36,9 +36,8 @@ end
|
|||
|
||||
--In world coordinates: top left corner at x, y, extent of w, h.
|
||||
function Camera.Set( x, y, w, h )
|
||||
print( ("CAMERA: %3.2f %3.2f %3.2f %3.2f"):format(x, y, w, h) )
|
||||
Camera.x, Camera.y, Camera.w, Camera.h = x, y, w, h
|
||||
Camera.zoom = w / 360
|
||||
Camera.zoom = w / 800
|
||||
tf:reset()
|
||||
tf:scale( w / 360, -h / 200 )
|
||||
tf:translate( 180 - x, -y - 100 )
|
||||
|
|
32
cities.lua
|
@ -23,26 +23,40 @@ function t.unlockSelection()
|
|||
end
|
||||
|
||||
function t.draw()
|
||||
if cities.visible then lg.points( points ) end
|
||||
return lg.points( points )
|
||||
end
|
||||
|
||||
function t.drawSelected( r )
|
||||
if not cities.visible then return end
|
||||
function t.drawSelected( )
|
||||
local c = t.selected
|
||||
if not c then return end
|
||||
lg.circle( "fill", c.x, c.y, r )
|
||||
lg.circle( "line", c.x, c.y, 1.0 )
|
||||
end
|
||||
|
||||
function t.drawCapitals()
|
||||
if cities.visible then lg.points( caps ) end
|
||||
end
|
||||
|
||||
function t.selectNearestCity(x, y)
|
||||
if not t.selectionLocked then t.selected = cities:getClosestPoint(x, y) end
|
||||
function t.selectNearest( cities, x, y )
|
||||
return cities:getClosestPoint(x, y) --defer to locationQuery
|
||||
end
|
||||
|
||||
local city = {}
|
||||
local citymt = {__index = city}
|
||||
function city:formatDisplayInfo()
|
||||
return (
|
||||
[[
|
||||
NAME: %s
|
||||
COUNTRY: %s
|
||||
LONGITUDE: %3.2f
|
||||
LATITUDE: %3.2f
|
||||
POP: %d
|
||||
CAPITAL: %s]]):format( self.name, self.country, self.x, self.y, self.pop, tostring(self.capital) )
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
|
||||
print( "=== LOADING CITIES. ===" )
|
||||
|
||||
cities = { visible = true, active = false }
|
||||
local n = 1
|
||||
local idxPts = 1
|
||||
|
@ -53,11 +67,11 @@ function t.load( filename )
|
|||
local _, _, x, y, pop, capital = line:sub( 83 ):find( "(%g+)%s+(%g+)%s+(%g+)%s+(%g+)" )
|
||||
if capital then --check against empty or malformed line
|
||||
x, y, pop, capital = tonumber( x ), tonumber( y ), tonumber( pop ), ( tonumber( capital ) > 0)
|
||||
local city = {
|
||||
local city = setmetatable({
|
||||
name = line:sub( 1, 39 ):gsub("%s+$",""),
|
||||
country = line:sub( 42, 82 ):gsub("%s+$",""),
|
||||
x = x, y = y, pop = pop, capital = capital
|
||||
}
|
||||
}, citymt )
|
||||
cities[n] = city
|
||||
n = n + 1
|
||||
|
||||
|
@ -77,7 +91,7 @@ function t.load( filename )
|
|||
cities = locationQuery.New( cities )
|
||||
setmetatable( getmetatable( cities ).__index, {__index = t } )
|
||||
|
||||
print( "LOADED", filename, n )
|
||||
print( "=== CITIES LOADED:", filename, n, "===" )
|
||||
return cities
|
||||
end
|
||||
|
||||
|
|
4
conf.lua
|
@ -11,7 +11,7 @@ function love.conf(t)
|
|||
t.audio.mixwithsystem = true -- Keep background music playing when opening LOVE (boolean, iOS and Android only)
|
||||
|
||||
t.window.title = "dcEarth" -- The window title (string)
|
||||
t.window.icon = "favicon.png" -- Filepath to an image to use as the window's icon (string)
|
||||
t.window.icon = "icons/favicon.png" -- Filepath to an image to use as the window's icon (string)
|
||||
t.window.width = 800 -- The window width (number)
|
||||
t.window.height = 600 -- The window height (number)
|
||||
t.window.borderless = false -- Remove all border visuals from the window (boolean)
|
||||
|
@ -44,7 +44,7 @@ function love.conf(t)
|
|||
t.modules.sound = false -- Enable the sound module (boolean)
|
||||
t.modules.system = true -- Enable the system module (boolean)
|
||||
t.modules.thread = false -- Enable the thread module (boolean)
|
||||
t.modules.timer = false -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
|
||||
t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
|
||||
t.modules.touch = false -- Enable the touch module (boolean)
|
||||
t.modules.video = false -- Enable the video module (boolean)
|
||||
t.modules.window = true -- Enable the window module (boolean)
|
||||
|
|
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 71 KiB |
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 156 KiB |
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
|
@ -1,7 +1,6 @@
|
|||
--Shitty acceleration structure: get closest point in set.
|
||||
--Assumed to be in world coordinates: ( -180, 180 ) x ( -100, 100 )
|
||||
local t = {}
|
||||
local hashes = {}
|
||||
local math = math
|
||||
|
||||
local hash = function( x, y, debug )
|
||||
|
@ -9,9 +8,18 @@ local hash = function( x, y, debug )
|
|||
return s
|
||||
end
|
||||
|
||||
function t.getHashes( points )
|
||||
return assert( points.hashes )
|
||||
end
|
||||
|
||||
function t.getClosestPoint( points, x, y )
|
||||
local hashes = t.getHashes( points )
|
||||
local closePoints = hashes[hash( x, y )]
|
||||
if not closePoints then return end
|
||||
|
||||
if not closePoints then
|
||||
return
|
||||
end
|
||||
|
||||
local distance = math.huge
|
||||
local px, py, point
|
||||
for k, v in pairs(closePoints) do
|
||||
|
@ -29,6 +37,7 @@ function t.getClosestPoint( points, x, y )
|
|||
end
|
||||
|
||||
function t.Edit( points, point, x, y )
|
||||
local hashes = t.getHashes( points )
|
||||
local h = hashes[hash( point.x, point.y )]
|
||||
if h then
|
||||
for i, p in pairs(h) do
|
||||
|
@ -50,12 +59,20 @@ function t.Add( points, x, y )
|
|||
end
|
||||
|
||||
function t.New( points )
|
||||
local hashes = {}
|
||||
for i = 1, #points do
|
||||
local x, y = points[i].x, points[i].y
|
||||
local h = hash( x, y )
|
||||
hashes[h] = hashes[h] or {}
|
||||
hashes[h][#hashes[h] + 1] = points[i]
|
||||
end
|
||||
points.hashes = hashes
|
||||
|
||||
do
|
||||
local count = 0
|
||||
for k, v in pairs(hashes) do count = count + 1 end
|
||||
print( "LOCATION QUERY. Points:", count )
|
||||
end
|
||||
return setmetatable( points, {__index = t} )
|
||||
end
|
||||
|
||||
|
|
47
main.lua
|
@ -25,12 +25,12 @@ end
|
|||
function love.update( dt )
|
||||
local tx, ty = 0, 0
|
||||
local moveCamera = false
|
||||
if love.keyboard.isScancodeDown( "w" ) then moveCamera = true; ty = ty + 1 end
|
||||
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - 1 end
|
||||
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - 1 end
|
||||
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + 1 end
|
||||
if love.keyboard.isScancodeDown( "q" ) then Camera.Zoom( true ) end
|
||||
if love.keyboard.isScancodeDown( "e" ) then Camera.Zoom( false ) end
|
||||
if love.keyboard.isScancodeDown( "w" ) then moveCamera = true; ty = ty + 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + 30 * dt end
|
||||
if love.keyboard.isScancodeDown( "q" ) then Camera.Zoom( dt ) end
|
||||
if love.keyboard.isScancodeDown( "e" ) then Camera.Zoom( -dt ) end
|
||||
if moveCamera then Camera.Translate( tx, ty ) end
|
||||
|
||||
|
||||
|
@ -85,7 +85,7 @@ function love.draw()
|
|||
local wx, wy = Camera.GetWorldCoordinate( x, y )
|
||||
local bx, by = Camera.GetBitmapCoordinate( x, y )
|
||||
local h = love.graphics.getHeight() - 30
|
||||
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
|
||||
love.graphics.setColor( 0.2, 0.1, 0.1, 1.0 )
|
||||
love.graphics.rectangle( "fill", 0, h, love.graphics.getWidth() / 2, 30 )
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
love.graphics.rectangle( "line", 0, h, love.graphics.getWidth() / 2, 30 )
|
||||
|
@ -94,30 +94,13 @@ function love.draw()
|
|||
|
||||
--Edit box.
|
||||
love.graphics.rectangle( "line", love.graphics.getWidth() / 2, h, love.graphics.getWidth() / 2, 30 )
|
||||
if map.loaded and map.cities.selected then
|
||||
local c = map.cities.selected
|
||||
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
|
||||
if map.selected then
|
||||
love.graphics.setColor( 0.2, 0.1, 0.1, 1.0 )
|
||||
love.graphics.rectangle( "fill", 0, 0, 150 ,100 )
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
love.graphics.rectangle( "line", 0, 0, 150 ,100 )
|
||||
love.graphics.setColor( 1.2, 1.1, 1.1, 1.5 )
|
||||
love.graphics.print( ("NAME: %s\nX: %3.2f\nY: %3.2f\nPOP: %d\nCAPITAL: %s\nCOUNTRY: %s"):format(c.name, c.x, c.y, c.pop, tostring(c.capital), c.country), 0, 0 )
|
||||
elseif map.loaded and map.travelnodes.selected then
|
||||
local c = map.travelnodes.selected
|
||||
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
|
||||
love.graphics.rectangle( "fill", 0, 0, 150 ,100 )
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
love.graphics.rectangle( "line", 0, 0, 150 ,100 )
|
||||
love.graphics.setColor( 1.2, 1.1, 1.1, 1.5 )
|
||||
love.graphics.print( ("Node: %d\nX: %3.2f\nY: %3.2f\n"):format(c.number, c.x, c.y) )
|
||||
elseif map.loaded and map.ainodes.selectedNode then
|
||||
local c = map.ainodes.selected
|
||||
love.graphics.setColor( 0.2, 0.1, 0.1, 0.5 )
|
||||
love.graphics.rectangle( "fill", 0, 0, 150 ,100 )
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
love.graphics.rectangle( "line", 0, 0, 150 ,100 )
|
||||
love.graphics.setColor( 1.2, 1.1, 1.1, 1.5 )
|
||||
love.graphics.print( ("Node: %d\nX: %3.2f\nY: %3.2f\noffensive: %s"):format(c.number, c.x, c.y, c.attack) )
|
||||
love.graphics.print( map.selected:formatDisplayInfo(), 0, 0 )
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -126,7 +109,7 @@ function love.resize(w, h)
|
|||
end
|
||||
|
||||
function love.wheelmoved(x, y)
|
||||
Camera.Zoom( (y > 0) and true or false )
|
||||
Camera.Zoom( (y > 0) and 0.5 or -0.5 )
|
||||
end
|
||||
|
||||
function love.mousepressed( x, y, button, istouch, presses )
|
||||
|
@ -142,7 +125,13 @@ function love.mousepressed( x, y, button, istouch, presses )
|
|||
end
|
||||
|
||||
function love.mousemoved( x, y, dx, dy, istouch )
|
||||
if map.loaded and map.cities.visible then map.cities.selectNearestCity( Camera.GetWorldCoordinate( x, y ) ) end
|
||||
local wx, wy = Camera.GetWorldCoordinate( x, y )
|
||||
if map.loaded then
|
||||
if map.cities.visible then map.selected = map.cities:selectNearest( wx, wy )
|
||||
elseif map.travelnodes.visible then map.selected = map.travelnodes:selectNearest( wx, wy )
|
||||
elseif map.ainodes.visible then map.selected = map.ainodes:selectNearest( wx, wy )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function ToggleVisibility( layer )
|
||||
|
|
25
map.lua
|
@ -2,8 +2,7 @@ local lg = love.graphics
|
|||
local AI = require 'ai'
|
||||
local Cities = require 'cities'
|
||||
local Lines = require 'lines'
|
||||
local Nodes = require 'nodes'
|
||||
local Bitmap = require 'bmp'
|
||||
local Nodes = require 'travelNodes'
|
||||
local Camera = require 'camera'
|
||||
local Territory = require 'territory'
|
||||
|
||||
|
@ -79,20 +78,27 @@ function map.draw()
|
|||
|
||||
lg.replaceTransform( Camera.tf )
|
||||
|
||||
do --points
|
||||
if map.selected then
|
||||
lg.setColor( 1.0, 0.5, 0.5, 0.9 )
|
||||
lg.setLineJoin( "miter" )
|
||||
lg.setLineWidth( 1.0 / Camera.zoom )
|
||||
lg.circle( "line", map.selected.x, map.selected.y, 0.2 + 1.0 / Camera.zoom )
|
||||
end
|
||||
|
||||
if map.cities.visible then --points
|
||||
|
||||
lg.setColor( 1, 0, 0, 0.5 )
|
||||
lg.setPointSize( 0.5 * Camera.zoom )
|
||||
lg.setPointSize( 5.0 )
|
||||
map.cities.draw()
|
||||
|
||||
lg.setColor( 1, 1, 1.0, 0.5 )
|
||||
lg.setPointSize( 1.0 * Camera.zoom )
|
||||
lg.setColor( 1, 1, 0.0, 0.5 )
|
||||
map.cities.drawCapitals()
|
||||
|
||||
lg.setColor( 1, 1, 1, 0.5 )
|
||||
map.cities.drawSelected( 15.0 / Camera.zoom )
|
||||
end
|
||||
|
||||
if map.ainodes.visible then map.ainodes:draw() end
|
||||
if map.ainodes.visible then
|
||||
lg.setPointSize( 5.0 )
|
||||
map.ainodes:draw()
|
||||
end
|
||||
|
||||
do --line stuff
|
||||
|
@ -122,6 +128,7 @@ function map.draw()
|
|||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
@ -109,8 +109,9 @@ function t.computeBorder( territory, threshold, key )
|
|||
local border = territory[key]
|
||||
local n = 1
|
||||
|
||||
for x = 0, 511 do
|
||||
for y = 0, 284 do
|
||||
local w, h = territory.imgd:getWidth() - 1, territory.imgd:getHeight() - 1
|
||||
for x = 0, w do
|
||||
for y = 0, h do
|
||||
--Bottom left, bottom right, and top right of pixel in image coordinates:
|
||||
local blx, bly = x, y + 1
|
||||
local brx, bry = x + 1, y + 1
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
--Manage the pathfinding nodes used by DEFCON.
|
||||
--This is important for a mapping tool because the DEFCON client will not load a map unless
|
||||
--the pathfinding nodes form a connected graph.
|
||||
local t = {}
|
||||
|
||||
local bmp = require 'bmp'
|
||||
local locationQuery = require 'locationQuery'
|
||||
local lg = assert( love.graphics )
|
||||
|
||||
|
||||
local t = setmetatable({}, {__index = locationQuery})
|
||||
local isSailable
|
||||
|
||||
local function isConnected( startNode, endNode )
|
||||
|
@ -25,15 +28,17 @@ local function isConnected( startNode, endNode )
|
|||
return true
|
||||
end
|
||||
|
||||
function t.getClosest( nodes, x, y )
|
||||
local d = math.huge
|
||||
local closestNode
|
||||
for _, node in pairs( nodes.nodes ) do
|
||||
local nx, ny = node.x, node.y
|
||||
local nd = (nx - x) * (nx - x) + (ny - y) * (ny - y)
|
||||
if nd < d then d = nd; closestNode = node end
|
||||
function t.selectNearest( nodes, x, y )
|
||||
return (nodes.nodes):getClosestPoint( x, y )
|
||||
end
|
||||
return closestNode
|
||||
|
||||
local travelNode = {}
|
||||
local mtTravelNode = { __index = travelNode }
|
||||
|
||||
function travelNode:formatDisplayInfo()
|
||||
return ([[ TRAVEL NODE: %d
|
||||
LONGITUDE: %3.2f
|
||||
LATITUDE: %3.2f]]):format( self.idx, self.x, self.y )
|
||||
end
|
||||
|
||||
function t.load( filename, sailable )
|
||||
|
@ -49,10 +54,10 @@ function t.load( filename, sailable )
|
|||
if imgd:getPixel( x, 399 - y ) > 0 then
|
||||
local long = 360 * ( x - 800 ) / 800 - 360 / 2 + 360
|
||||
local lat = 360 * ( 600 / 800 ) * ( 600 - y ) / 600 - 180
|
||||
nodes.nodes[n] = {x = long, y = lat}
|
||||
nodes.nodes[n] = setmetatable({x = long, y = lat, idx = n}, mtTravelNode )
|
||||
print( nodes.nodes[n]:formatDisplayInfo() )
|
||||
nodes.points[ 2 * n - 1 ] = long
|
||||
nodes.points[ 2 * n ] = lat
|
||||
print( n, long, lat )
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
|
@ -69,7 +74,9 @@ function t.load( filename, sailable )
|
|||
end
|
||||
|
||||
print( "=== Nodes Loaded ===" )
|
||||
return setmetatable( nodes, {__index = t } )
|
||||
nodes.nodes = locationQuery.New( nodes.nodes )
|
||||
setmetatable( nodes, {__index = t} )
|
||||
return nodes
|
||||
end
|
||||
|
||||
--Determine if graph has more than one connected component.
|
||||
|
@ -78,9 +85,13 @@ function t.isConnected( nodes )
|
|||
end
|
||||
|
||||
function t.draw( nodes )
|
||||
lg.setPointSize( 10 )
|
||||
lg.setPointSize( 8 )
|
||||
lg.setColor( 1, 1, 1, 0.5 )
|
||||
lg.points( nodes.points )
|
||||
return t.drawConnections( nodes )
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
for i, connection in pairs( nodes.connections ) do
|
||||
for j in pairs( connection ) do
|
||||
|
@ -88,9 +99,6 @@ function t.draw( nodes )
|
|||
lg.line( ix, iy, fx, fy )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
end
|
||||
|