Compare commits
12 Commits
688e954c2b
...
b8e00cf340
Author | SHA1 | Date |
---|---|---|
wan-may | b8e00cf340 | |
wan-may | 03f1a96112 | |
wan-may | 9635e0ea2f | |
wan-may | e39e772589 | |
wan-may | 03e45194cf | |
wan-may | 8b766723f1 | |
wan-may | a2eeafcdb3 | |
wan-may | 0fbbbe4409 | |
wan-may | 7c2c3867cf | |
wan-may | 18ed5c1ce1 | |
wan-may | 7f457daa94 | |
wan-may | ce15bfdcf7 |
|
@ -0,0 +1,3 @@
|
|||
build/
|
||||
test/
|
||||
backup/
|
39
ai.lua
|
@ -1,13 +1,28 @@
|
|||
--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 = {
|
||||
filename = filename,
|
||||
visible = true,
|
||||
all = {},
|
||||
att = {},
|
||||
ptsAtt = {},
|
||||
def = {},
|
||||
|
@ -16,15 +31,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 +62,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 )
|
||||
|
@ -53,8 +78,8 @@ function t.draw( nodes )
|
|||
lg.points( nodes.ptsDef )
|
||||
end
|
||||
|
||||
function t.save( nodes, filename )
|
||||
|
||||
function t.save( nodes )
|
||||
return bmp.ai( nodes.all )
|
||||
end
|
||||
|
||||
return t
|
309
bmp.lua
|
@ -1,11 +1,300 @@
|
|||
--Load and save the bmp formats used by DEFCON.
|
||||
local t = {}
|
||||
local assert = assert
|
||||
local print = print
|
||||
local error = error
|
||||
local table = table
|
||||
local math = math
|
||||
local love = assert( love )
|
||||
local lfs = love.filesystem
|
||||
|
||||
--FFI bit-twiddling stuff.
|
||||
local ffi = require 'ffi'
|
||||
local bit = require 'bit'
|
||||
|
||||
|
||||
local test = {}
|
||||
|
||||
function test.load( filename )
|
||||
local imgd = love.image.newImageData( filename )
|
||||
print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() )
|
||||
local img = love.graphics.newImage( imgd )
|
||||
img:setFilter( "nearest", "nearest" )
|
||||
return img, imgd
|
||||
end
|
||||
|
||||
function test.bitmapToWorld( x, y )
|
||||
local w, a = 360.0, 600.0 / 800.0
|
||||
local h = 360.0 * a
|
||||
x = w * ( x - 800 ) / 800 - w / 2 + 360
|
||||
y = h * ( y - 600 ) / 600 + 180
|
||||
return x, y
|
||||
end
|
||||
|
||||
function test.compareData( a, b )
|
||||
if a == b then return true end
|
||||
print( "lengths:", a:len(), b:len() )
|
||||
local errors = 0
|
||||
for i = 1, math.min( a:len(), b:len() ) do
|
||||
local a, b = a:sub( i, i ), b:sub( i, i )
|
||||
if a ~= b then
|
||||
errors = errors + 1
|
||||
print( "mismatch:", errors, i, string.byte( a ), string.byte( b ) )
|
||||
end
|
||||
if errors > 1000 then break end
|
||||
end
|
||||
error( "test failed. output bitmap does not match!" )
|
||||
end
|
||||
|
||||
function test.worldToBitmap( x, y )
|
||||
x = 800 * ( x + 180 ) / 360
|
||||
y = 600 + 800 * ( y - 180 ) / 360
|
||||
return x, y
|
||||
end
|
||||
|
||||
local function getHeader( filename )
|
||||
local offset = 2 + love.data.unpack( "<I4", assert(love.filesystem.read( "data", filename, 14 )), 11 )
|
||||
local header, size = assert( love.filesystem.read( filename, offset ) )
|
||||
print( "BMP HEADER", filename, size, "\n", string.byte( header, 1, size ) )
|
||||
return header
|
||||
end
|
||||
|
||||
--BGR24 format. Takes the input of Love2D's data:getPixel( x, y )
|
||||
--on paper we would only care about one channel because all of these images are grayscale,
|
||||
--but a few of the pixels in the game's default territories are not exactly gray,
|
||||
--so we do it like this to make sure our output is byte-faithful when saving an unmodified vanilla map
|
||||
local function bgrChar( r, g, b )
|
||||
return string.char( math.floor( b * 255 ), math.floor( g * 255 ), math.floor( r * 255 ) )
|
||||
end
|
||||
|
||||
|
||||
local formats = {
|
||||
["africa"] = {
|
||||
header = getHeader( "data/earth/africa.bmp" ),
|
||||
w = 512,
|
||||
h = 285,
|
||||
},
|
||||
["sailable"] = {
|
||||
header = getHeader( "data/earth/sailable.bmp" ),
|
||||
w = 512,
|
||||
h = 285,
|
||||
--technically this information is in the header already but I don't want to write code to parse it since we only use this one header
|
||||
palette = {
|
||||
[0] = 0,
|
||||
[8] = 1,
|
||||
[20] = 2,
|
||||
[33] = 3,
|
||||
[49] = 4,
|
||||
[90] = 5,
|
||||
[139] = 6,
|
||||
[169] = 7,
|
||||
[189] = 8,
|
||||
[206] = 9,
|
||||
[214] = 10,
|
||||
[222] = 11,
|
||||
[231] = 12,
|
||||
[239] = 13,
|
||||
[247] = 14,
|
||||
[255] = 15
|
||||
},
|
||||
},
|
||||
["travel"] = {
|
||||
header = getHeader( "data/earth/travel_nodes.bmp" ),
|
||||
w = 800,
|
||||
h = 400,
|
||||
},
|
||||
["ai"] = {
|
||||
header = getHeader( "data/earth/ai_markers.bmp" ),
|
||||
w = 512,
|
||||
h = 285,
|
||||
},
|
||||
}
|
||||
|
||||
function formats.ai:test( )
|
||||
print "testing ai nodes"
|
||||
local filename = "data/earth/ai_markers.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local idx = 1
|
||||
local nodes = {}
|
||||
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 attacking = (r > 0.5)
|
||||
local node = {x = long, y = lat, attacking = attacking, idx = idx}
|
||||
print( "ai marker", idx, x, y, long, lat )
|
||||
nodes[ idx ] = node
|
||||
idx = idx + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local encodedString = self:encode( nodes )
|
||||
love.filesystem.write( "ai_markers.bmp", encodedString )
|
||||
|
||||
local eimg, eimgd = test.load( "ai_markers.bmp" ) --the one we just saved
|
||||
for x = 0, 511 do
|
||||
for y = 0, 284 do
|
||||
local r, g = imgd:getPixel( x, 284 - y )
|
||||
local er, eg = eimgd:getPixel( x, 284 - y )
|
||||
if math.max( r, g, er, eg ) > 0.5 then
|
||||
print( "node pixel: ", x, 284 - y, r, g, er, eg )
|
||||
assert( ( r > 0.5 and er > 0.5 ) or ( g > 0.5 and eg > 0.5 ), "ai marker mismatch!" )
|
||||
end
|
||||
end
|
||||
end
|
||||
print( "ai markers OK" )
|
||||
end
|
||||
|
||||
function formats.ai:encode( data )
|
||||
|
||||
--set up bitmap as an array of pixels
|
||||
local w, h = self.w, self.h
|
||||
local size = 2 + self.w * self.h
|
||||
local bitmap = { self.header:sub( 1, -3 ) }
|
||||
for j = 2, size do bitmap[j] = 0 end
|
||||
|
||||
|
||||
for i, point in ipairs( data ) do
|
||||
local wx, wy = point.x, point.y
|
||||
--get bitmap coordinates
|
||||
local x = math.floor(self.w * (wx + 180) / 360)
|
||||
--idk why exactly I need to round the value here instead of truncating it
|
||||
local y = math.floor(0.5 + self.h * (wy + 100) / 200)
|
||||
print( "export ai marker", i, x, y, wx, wy )
|
||||
--get index into byte array
|
||||
local idx = 2 + y * self.w + x
|
||||
--in-editor we could have two points in the same place, possibly of the same or different types
|
||||
--in case we miss something upstream, don't corrupt these duplicate points,
|
||||
--just saturate the attack/defense value
|
||||
if bitmap[idx] == 0 then bitmap[idx] = { attack = point.attacking, place = not(point.attacking)}
|
||||
else
|
||||
bitmap[idx].attack = bitmap[idx].attack or point.attacking
|
||||
bitmap[idx].place = bitmap[idx].place or not(point.attacking)
|
||||
end
|
||||
end
|
||||
|
||||
--now map pixels to 3-byte strings
|
||||
for j = 2, size do
|
||||
if bitmap[j] == 0
|
||||
then bitmap[j] = "\0\0\0"
|
||||
else bitmap[j] = string.char( 0, bitmap[j].place and 0xff or 0, bitmap[j].attack and 0xff or 0 ) --bgr24
|
||||
end
|
||||
end
|
||||
|
||||
return table.concat( bitmap )
|
||||
end
|
||||
|
||||
function formats.travel:test()
|
||||
print "testing travel nodes"
|
||||
local filename = "data/earth/travel_nodes.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local nodes = {}
|
||||
local n = 1
|
||||
for x = 0, 799 do
|
||||
for y = 0, 399 do
|
||||
if imgd:getPixel( x, 399 - y ) > 0 then
|
||||
local long, lat = test.bitmapToWorld( x, y )
|
||||
nodes[n] = {x = long, y = lat, idx = n}
|
||||
print( "read:", n, long, lat, x, y )
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
print( "loaded ", n, "nodes" )
|
||||
local encodedString = self:encode( nodes )
|
||||
test.compareData( love.filesystem.read( filename ), encodedString )
|
||||
love.filesystem.write( "travel_nodes.bmp", encodedString )
|
||||
print( "travel nodes OK" )
|
||||
end
|
||||
|
||||
--Sparse bitmap with a few white pixels.
|
||||
function formats.travel:encode( points )
|
||||
--set up bitmap as an array of 8-bit pixels
|
||||
local w, h = self.w, self.h
|
||||
local size = 2 + w * h
|
||||
local bitmap = { self.header }
|
||||
for j = 2, size do bitmap[j] = 0 end
|
||||
|
||||
--write white pixels
|
||||
for i, point in ipairs( points ) do
|
||||
local wx, wy = point.x, point.y
|
||||
-- get bitmap coordinates:
|
||||
local x = 800 * ( wx + 180 ) / 360
|
||||
local y = 600 + 800 * ( wy - 180 ) / 360
|
||||
local offset = math.floor( y * self.w + x - 2 )
|
||||
--1 := white
|
||||
bitmap[offset] = 1
|
||||
print( "encoded:", point.idx, wx, wy, x, y )
|
||||
end
|
||||
|
||||
--fold into 4-bit pixel array
|
||||
local nybbles = { bitmap[1] }
|
||||
for j = 2, size / 2 do
|
||||
local a, b = bitmap[ 2 * j - 2 ], bitmap[ 2 * j - 1 ]
|
||||
nybbles[j] = string.char( 16 * a + b )
|
||||
end
|
||||
|
||||
return table.concat( nybbles )
|
||||
end
|
||||
|
||||
function formats.sailable:test()
|
||||
print "testing sailable"
|
||||
local filename = "data/earth/sailable.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local encoded = self:encode( imgd )
|
||||
love.filesystem.write( "sailable_out.bmp", encoded )
|
||||
test.compareData( love.filesystem.read( filename ), encoded )
|
||||
print "sailable OK"
|
||||
end
|
||||
|
||||
function formats.sailable:encode( data )
|
||||
local w, h = self.w, self.h
|
||||
local bytes = { self.header:sub( 1, -2 ) }
|
||||
local i = 2
|
||||
|
||||
--y coordinates are written top to bottom
|
||||
for y = h - 1, 0, -1 do
|
||||
for x = 0, w - 1 do
|
||||
bytes[i] = assert( self.palette[ math.floor( data:getPixel(x, y) * 255 )] )
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
|
||||
--fold into 4-bit pixel array
|
||||
local nybbles = { bytes[1] }
|
||||
for j = 2, #bytes / 2 do
|
||||
local a, b = bytes[ 2 * j ], bytes[ 2 * j + 1 ]
|
||||
nybbles[j] = string.char( 16 * a + b )
|
||||
end
|
||||
|
||||
return table.concat( nybbles )
|
||||
end
|
||||
|
||||
function formats.africa:test()
|
||||
print "testing africa"
|
||||
local filename = "data/earth/africa.bmp"
|
||||
local img, imgd = test.load( filename )
|
||||
local encoded = self:encode( imgd )
|
||||
love.filesystem.write( "africa_out.bmp", encoded )
|
||||
test.compareData( love.filesystem.read( filename ), encoded )
|
||||
print "africa OK"
|
||||
end
|
||||
|
||||
|
||||
function formats.africa:encode( data )
|
||||
local w, h = self.w, self.h
|
||||
local bytes = { self.header:sub( 1, -3 ) }
|
||||
local i = 2
|
||||
|
||||
--y coordinates are written top to bottom
|
||||
for y = h - 1, 0, -1 do
|
||||
for x = 0, w - 1 do
|
||||
bytes[i] = bgrChar( data:getPixel(x, y) )
|
||||
i = i + 1
|
||||
end
|
||||
end
|
||||
return table.concat( bytes )
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
local imgd = love.image.newImageData( filename )
|
||||
|
@ -15,8 +304,16 @@ function t.load( filename )
|
|||
return img, imgd
|
||||
end
|
||||
|
||||
function t.save( data, filename )
|
||||
local w, h = data:getDimensions()
|
||||
function t.save( data, format )
|
||||
return assert(formats[format]):encode( data )
|
||||
end
|
||||
|
||||
--convenience
|
||||
formats.territory = formats.africa
|
||||
for fmt, tbl in pairs( formats ) do
|
||||
t[fmt] = function( data ) return tbl:encode( data ) end
|
||||
--TESTING
|
||||
tbl:test()
|
||||
end
|
||||
|
||||
return t
|
110
button.lua
|
@ -1,32 +1,106 @@
|
|||
local t = {}
|
||||
local lg = love.graphics
|
||||
|
||||
function t.onHover( button )
|
||||
local t = {
|
||||
name = "",
|
||||
tooltip = "button",
|
||||
icon = false,
|
||||
x = 8,
|
||||
y = 250,
|
||||
w = 176,
|
||||
h = 24,
|
||||
group = false,
|
||||
visible = true,
|
||||
callback = function( self ) return print( "clicked button: ", self.name, self.x, self.y, self.w, self.h, self.visible ) end
|
||||
}
|
||||
t.selected, t.next, t.prev = t, t, t
|
||||
|
||||
function t.contains( button, x, y )
|
||||
return x < button.x + button.w and x > button.x
|
||||
and y < button.y + button.h and y > button.y
|
||||
end
|
||||
|
||||
function t.onClick( button )
|
||||
|
||||
function t.new( b )
|
||||
b = setmetatable( b or {}, t )
|
||||
b.next = t
|
||||
t.prev.next = b
|
||||
b.prev = t.prev
|
||||
t.prev = b
|
||||
return b
|
||||
end
|
||||
|
||||
function t.newButton( name, tooltip, icon, x, y, w, h, callback )
|
||||
return setmetatable( {
|
||||
name = name,
|
||||
tooltip = tooltip,
|
||||
icon = icon,
|
||||
x = x,
|
||||
y = y,
|
||||
w = w,
|
||||
h = h,
|
||||
callback = callback },
|
||||
t )
|
||||
local drawPassOngoing = false
|
||||
function t.draw( b )
|
||||
if b == t then
|
||||
drawPassOngoing = not( drawPassOngoing )
|
||||
if not drawPassOngoing then return end
|
||||
elseif b.visible then
|
||||
lg.rectangle( "line", b.x, b.y, b.w, b.h, 6 )
|
||||
lg.printf( b.name,
|
||||
b.x + (b.icon and b.h or 0),
|
||||
b.y + 0.5 * ( b.h - lg.getFont():getHeight() ),
|
||||
b.w - (b.icon and b.h or 0),
|
||||
"center" )
|
||||
if b.icon then
|
||||
local h = b.icon:getHeight()
|
||||
lg.draw( b.icon,
|
||||
b.x, b.y,
|
||||
0,
|
||||
b.h / h )
|
||||
end
|
||||
if t.selected == b then
|
||||
lg.rectangle( "fill", b.x, b.y, b.w, b.h, 6 )
|
||||
end
|
||||
end
|
||||
return t.draw( b.next )
|
||||
end
|
||||
|
||||
function t.draw( button )
|
||||
|
||||
function t.select( b )
|
||||
t.selected = b
|
||||
end
|
||||
|
||||
t.__index = t
|
||||
t.__call = t.newButton
|
||||
function t.selectNext()
|
||||
repeat t.selected = t.selected.next until (t.selected == t) or t.selected.visible
|
||||
end
|
||||
|
||||
function t.selectPrev()
|
||||
repeat t.selected = t.selected.prev until (t.selected == t) or t.selected.visible
|
||||
end
|
||||
|
||||
function t.selectIn( x, y )
|
||||
t.selected = t
|
||||
repeat t.selected = t.selected.next until (t.selected == t) or (t.selected.visible and t.selected:contains( x, y ))
|
||||
end
|
||||
|
||||
function t.selectNextInGroup()
|
||||
--make sure our group is visible, otherwise the loop doesn't end
|
||||
local group = t.selected and t.selected.visible and t.selected.group
|
||||
if not group then return t.selectNext() end
|
||||
repeat t.selectNext() until group == t.selected.group
|
||||
end
|
||||
|
||||
function t.selectPrevInGroup()
|
||||
--make sure our group is visible, otherwise the loop doesn't end
|
||||
local group = t.selected and t.selected.visible and t.selected.group
|
||||
if not group then return t.selectPrev() end
|
||||
repeat t.selectPrev() until group == t.selected.group
|
||||
end
|
||||
|
||||
function t.displayGroup( group, show )
|
||||
local b = t
|
||||
repeat
|
||||
b = b.next
|
||||
b.visible = ( b.group == group )
|
||||
until b == t
|
||||
t.visible = true
|
||||
end
|
||||
|
||||
function t.deselect( b )
|
||||
t.selected = t
|
||||
end
|
||||
|
||||
setmetatable( t, t )
|
||||
t.__index = t
|
||||
t.__call = t.new
|
||||
|
||||
return t
|
29
camera.lua
|
@ -3,7 +3,7 @@ local tfTerritory = love.math.newTransform()
|
|||
local tfNodes = love.math.newTransform()
|
||||
local lg = assert( love.graphics )
|
||||
local Camera = {
|
||||
x = 0, y = 0,
|
||||
x = -90, y = 45,
|
||||
w = 360, h = 200,
|
||||
zoom = 1, tf = tf,
|
||||
tfTerritory = tfTerritory, tfNodes = tfNodes }
|
||||
|
@ -20,12 +20,18 @@ 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 )
|
||||
if Camera.zoom < 25.0 and delta > 0 or --zooming in
|
||||
Camera.zoom > 0.5 and delta < 0 then --zooming out
|
||||
|
||||
delta = delta * Camera.zoom
|
||||
local cx, cy = Camera.x, Camera.y
|
||||
return Camera.Set(
|
||||
cx,
|
||||
cy,
|
||||
Camera.w + delta,
|
||||
Camera.h * (Camera.w + delta) / Camera.w )
|
||||
end
|
||||
end
|
||||
|
||||
function Camera.Translate( x, y )
|
||||
|
@ -34,11 +40,10 @@ function Camera.Translate( x, y )
|
|||
return Camera.Set( math.max(-180, math.min(360, Camera.x + x)), math.min(100, Camera.y + y), Camera.w, Camera.h)
|
||||
end
|
||||
|
||||
--In world coordinates: top left corner at x, y, extent of w, h.
|
||||
--In world coordinates: top left corner at x, y, extent of 1/w, 1/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 )
|
||||
|
@ -48,8 +53,8 @@ function Camera.Set( x, y, w, h )
|
|||
tfTerritory:translate( -x * 512 / 360, y * 512 / 360 )
|
||||
|
||||
tfNodes:reset()
|
||||
tfNodes:scale( w / 360, h / 200 )
|
||||
tfNodes:translate( 180 - x , y + 100 )
|
||||
tfNodes:scale( w / 360, -h / 200 )
|
||||
tfNodes:translate( 180 - x , -y - 100 )
|
||||
--tfNodes:translate( -x * 800 / 360, y * 400 / 200 )
|
||||
end
|
||||
|
||||
|
|
48
cities.lua
|
@ -23,40 +23,57 @@ 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 )
|
||||
|
||||
cities = { visible = true, active = false }
|
||||
print( "=== LOADING CITIES. ===" )
|
||||
|
||||
cities = { visible = true, active = false, filename = filename }
|
||||
local n = 1
|
||||
local idxPts = 1
|
||||
local idxCaps = 1
|
||||
points = {}
|
||||
caps = {}
|
||||
|
||||
for line in assert( lfs.lines( filename ), "Error: could not open cities.dat" ) do
|
||||
|
||||
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
|
||||
|
||||
|
@ -67,23 +84,26 @@ function t.load( filename )
|
|||
caps[idxCaps], caps[idxCaps + 1] = x, y
|
||||
idxCaps = idxCaps + 2
|
||||
end
|
||||
else
|
||||
print( "CITIES: malformed line:", line )
|
||||
end
|
||||
end
|
||||
|
||||
--Multiple inheritance.
|
||||
cities = locationQuery.New( cities )
|
||||
setmetatable( getmetatable( cities ).__index, {__index = t } )
|
||||
|
||||
print( "LOADED", filename, n )
|
||||
print( "=== CITIES LOADED:", filename, n, "===" )
|
||||
return cities
|
||||
end
|
||||
|
||||
function t.save( cities, filename )
|
||||
function t.save( cities )
|
||||
local str = {}
|
||||
for n, city in ipairs( cities ) do
|
||||
str[n] = ("%-40s%-40s%f %f %d %d"):format( city.name, city.country, city.x, city.y, city.pop, city.capital and 1 or 0 )
|
||||
str[n] = ("%-41s%-41s%-14f%-14f%-19d %d"):format(
|
||||
city.name, city.country, city.x, city.y, city.pop, city.capital and 1 or 0 )
|
||||
end
|
||||
assert( lfs.write( filename, table.concat( str, "\n" ) ) )
|
||||
print( "Saved", filename )
|
||||
return assert(table.concat( str, "\n" ))
|
||||
end
|
||||
|
||||
return t
|
6
conf.lua
|
@ -11,9 +11,9 @@ 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.height = 640 -- The window height (number)
|
||||
t.window.borderless = false -- Remove all border visuals from the window (boolean)
|
||||
t.window.resizable = true -- Let the window be user-resizable (boolean)
|
||||
t.window.minwidth = 512 -- Minimum window width if the window is resizable (number)
|
||||
|
@ -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: 144 KiB |
Before Width: | Height: | Size: 426 KiB |
Before Width: | Height: | Size: 428 KiB |
Before Width: | Height: | Size: 428 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 8.3 KiB After Width: | Height: | Size: 8.3 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 3.1 KiB |
80
lines.lua
|
@ -3,32 +3,96 @@ local t = {}
|
|||
local lfs = love.filesystem
|
||||
local lg = love.graphics
|
||||
|
||||
local polygon = { x = 180, X = -180, y = 100, Y = -100 } --default empty bounding box
|
||||
local polymt = { __index = polygon }
|
||||
|
||||
function polygon:formatDisplayInfo()
|
||||
return ([[
|
||||
x: %f
|
||||
y: %f
|
||||
X: %f
|
||||
Y: %f
|
||||
N: %d]]):format( self.x, self.y, self.X, self.Y, #self )
|
||||
end
|
||||
|
||||
function polygon:drawDirection()
|
||||
local a,b,c,d = self[1], self[2], self[3], self[4]
|
||||
|
||||
local bx, by = (c + a) / 2, (b + d) / 2
|
||||
local dx, dy = c - a, d - b
|
||||
local r = math.max( math.sqrt( dy * dy + dx * dx ), 0.0001 )
|
||||
dx, dy = dx / r, dy / r
|
||||
lg.polygon( "fill",
|
||||
a + dx, b + dy,
|
||||
a - 0.4 * dy, b + 0.4 * dx,
|
||||
a + 0.4 * dy, b - 0.4 * dx )
|
||||
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
local polys = { visible = true }
|
||||
local polys = { visible = true, filename = filename }
|
||||
local poly = {}
|
||||
local n = 0
|
||||
local n = 1
|
||||
local k = 1
|
||||
for line in assert( lfs.lines( filename ) ) do
|
||||
if line == "b" then
|
||||
if line:find "b" then
|
||||
k = 1
|
||||
n = n + 1
|
||||
poly = {}
|
||||
if #poly > 2 then n = n + 1 end
|
||||
poly = setmetatable({}, polymt) --axis-aligned bounding box
|
||||
polys[n] = poly
|
||||
else
|
||||
local _, _, x, y = line:find( "(%g+)%s+(%g+)" )
|
||||
x, y = assert( tonumber( x ) ), assert( tonumber( y ) )
|
||||
x, y = tonumber( x ), tonumber( y )
|
||||
if x and y then
|
||||
poly[k], poly[ k + 1 ] = x, y
|
||||
k = k + 2
|
||||
if x < poly.x then poly.x = x end
|
||||
if x > poly.X then poly.X = x end
|
||||
if y < poly.y then poly.y = y end
|
||||
if y > poly.Y then poly.Y = y end
|
||||
else
|
||||
print( "LINES: malformed line:", filename, line )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
if not polys[n] or (#(polys[n]) < 3) then
|
||||
polys[n] = nil
|
||||
n = n - 1
|
||||
end
|
||||
|
||||
print( "LOADED", filename, n )
|
||||
return setmetatable( polys, {__index = t } )
|
||||
end
|
||||
|
||||
function t.save( lines, filename )
|
||||
|
||||
function t.selectNearest( lines, wx, wy )
|
||||
local d = math.huge
|
||||
local nearest
|
||||
for i, poly in ipairs( lines ) do
|
||||
if poly.x - 5 < wx and
|
||||
poly.X + 5 > wx and
|
||||
poly.y - 5 < wy and
|
||||
poly.Y + 5 > wy then
|
||||
for k = 1, #poly, 2 do
|
||||
local x, y = poly[k], poly[k + 1]
|
||||
local r = ( x - wx ) * ( x - wx ) + ( y - wy ) * ( y - wy )
|
||||
if r < d then
|
||||
d = r
|
||||
nearest = poly
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return nearest
|
||||
end
|
||||
|
||||
function t.save( lines )
|
||||
local str = { "b" }
|
||||
for i, poly in ipairs( lines ) do
|
||||
str[i + 1] = table.concat( poly, " " )
|
||||
end
|
||||
str = table.concat( str, "\nb\n" ):gsub("(%S+) (%S+) ", "%1 %2\n")
|
||||
return str
|
||||
end
|
||||
|
||||
function t.newPolygon( x, y )
|
||||
|
|
|
@ -1,17 +1,25 @@
|
|||
--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 )
|
||||
local s = "h"..("%2d%2d"):format(math.floor( 0.5 + (180 + x) / 10 ), math.floor( 0.5 + (100 + y) / 10 ) )
|
||||
local s = "h"..("%2d%2d"):format(math.floor( 0.5 + (180 + x) / 20 ), math.floor( 0.5 + (100 + y) / 20 ) )
|
||||
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,13 +59,21 @@ 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
|
||||
return setmetatable( points, {__index = t } )
|
||||
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
|
||||
|
||||
return t
|
200
main.lua
|
@ -1,115 +1,72 @@
|
|||
local love = assert( love, "This tool requires LOVE: love2d.org" )
|
||||
--assert( require('mobdebug') ).start() --remote debugger
|
||||
local map = require 'map'
|
||||
local button = require 'button'
|
||||
local SAVEDIRECTORY = "out/"
|
||||
require 'mainmenu'
|
||||
local Camera = require 'camera'
|
||||
local wasKeyPressed
|
||||
|
||||
function love.load()
|
||||
love.filesystem.setIdentity( "dcearth", false )
|
||||
love.keyboard.setKeyRepeat( true )
|
||||
love.graphics.setNewFont( 14 )
|
||||
end
|
||||
|
||||
local lfs = assert( love.filesystem )
|
||||
lfs.setIdentity( "dcearth", false )
|
||||
assert( lfs.createDirectory( SAVEDIRECTORY.."data/earth" ))
|
||||
assert( lfs.createDirectory( SAVEDIRECTORY.."data/graphics" ))
|
||||
map.load()
|
||||
|
||||
|
||||
love.graphics.setNewFont( 12, "mono" )
|
||||
function love.directorydropped( path )
|
||||
if map.path then
|
||||
assert( love.filesystem.unmount( map.path ) )
|
||||
map.loaded = false
|
||||
end
|
||||
love.filesystem.mount( path, "" )
|
||||
return map.load( path )
|
||||
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 + dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "a" ) then moveCamera = true; tx = tx - dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "s" ) then moveCamera = true; ty = ty - dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "d" ) then moveCamera = true; tx = tx + dt * 150 / Camera.zoom end
|
||||
if love.keyboard.isScancodeDown( "q" ) then Camera.Zoom( dt * 400 ) end
|
||||
if love.keyboard.isScancodeDown( "e" ) then Camera.Zoom( -dt* 400 ) end
|
||||
if moveCamera then Camera.Translate( tx, ty ) end
|
||||
|
||||
|
||||
end
|
||||
|
||||
local toolButtons = {
|
||||
"Brush",
|
||||
"Move Nodes",
|
||||
"Add Nodes",
|
||||
"Edit Node",
|
||||
"Draw Polygon",
|
||||
"Erase Polygon"
|
||||
}
|
||||
|
||||
local layerButtons = {
|
||||
"Africa",
|
||||
"Europe",
|
||||
"North America",
|
||||
"South America",
|
||||
"Asia",
|
||||
"Russia",
|
||||
"Travel Nodes",
|
||||
"AI Markers",
|
||||
"Cities",
|
||||
"Coastlines",
|
||||
"Coastlines Low",
|
||||
"Sailable"
|
||||
}
|
||||
|
||||
|
||||
function love.draw()
|
||||
if not map.loaded then
|
||||
local w, h = love.graphics.getDimensions()
|
||||
return love.graphics.printf( "Drag and drop folder to begin.", w / 2 - 200, h / 2 - 128, 400, "center")
|
||||
end
|
||||
|
||||
love.graphics.push( "all" )
|
||||
map.draw()
|
||||
love.graphics.pop()
|
||||
|
||||
--Layer buttons.
|
||||
do
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
local h = love.graphics.getHeight() - 60
|
||||
for x = 0, 300, 30 do
|
||||
love.graphics.rectangle( "line", x, h, 30, 30 )
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
--Status bar.
|
||||
local x, y = love.mouse.getPosition()
|
||||
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.rectangle( "fill", 0, h, love.graphics.getWidth() / 2, 30 )
|
||||
local h = love.graphics.getHeight() - 60
|
||||
love.graphics.setColor( 0, 0, 0, 0.9 )
|
||||
love.graphics.rectangle( "fill", 0, 0, 250, love.graphics.getHeight() )
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
love.graphics.rectangle( "line", 0, h, love.graphics.getWidth() / 2, 30 )
|
||||
love.graphics.print(("SCREEN\t%d\t%d\nWORLD \t%5.2f\t%5.2f"):format(x, y, wx, wy), 0, h)
|
||||
love.graphics.print(("BITMAP\t%5.2f\t%5.2f"):format(bx, by), 200, h )
|
||||
love.graphics.print(([[
|
||||
SCREEN %-12d %-12d
|
||||
WORLD %-12.2f%-12.2f
|
||||
BITMAP %-12d %-12d
|
||||
%s]]):format(x, y, wx, wy, bx, by, map.editLayer and map.editLayer.filename or ""), 0, 0)
|
||||
|
||||
--Edit box.
|
||||
love.graphics.rectangle( "line", love.graphics.getWidth() / 2, h, love.graphics.getWidth() / 2, 30 )
|
||||
if map.cities.selected then
|
||||
local c = map.cities.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( ("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.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.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) )
|
||||
end
|
||||
if map.selected then love.graphics.print( map.selected:formatDisplayInfo(), 0, 80 ) end
|
||||
if map.selectionLocked then end
|
||||
|
||||
|
||||
love.graphics.rectangle( "line", 0, 0 , 250, 218 )
|
||||
love.graphics.rectangle( "line", 0, 218, 250, love.graphics.getHeight() )
|
||||
|
||||
love.graphics.setColor( 1, 1, 1, 0.6 )
|
||||
button:draw()
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
|
@ -117,54 +74,47 @@ function love.resize(w, h)
|
|||
end
|
||||
|
||||
function love.wheelmoved(x, y)
|
||||
Camera.Zoom( (y > 0) and true or false )
|
||||
Camera.Zoom( (y > 0) and 3 or -3 )
|
||||
end
|
||||
|
||||
function love.mousepressed( x, y, button, istouch, presses )
|
||||
function love.mousepressed( x, y, mouseButton, istouch, presses )
|
||||
local wx, wy = Camera.GetWorldCoordinate( x, y )
|
||||
if button == 1 then
|
||||
map.cities.lockSelection()
|
||||
else
|
||||
map.cities.unlockSelection()
|
||||
end
|
||||
|
||||
if button.selected and button.selected:contains( x, y ) then
|
||||
print( ("MOUSE\tx %f\ty %f\twx %f\twy %f"):format(x, y, wx, wy) )
|
||||
button.callback( button.selected )
|
||||
return button.selected:callback()
|
||||
end
|
||||
end
|
||||
|
||||
function love.mousemoved( x, y, dx, dy, istouch )
|
||||
map.cities.selectNearestCity( Camera.GetWorldCoordinate( x, y ) )
|
||||
end
|
||||
if not map.loaded then return end
|
||||
--mouse over menu
|
||||
button.selectIn( x, y )
|
||||
|
||||
local function ToggleVisibility( layer )
|
||||
if not layer then return end
|
||||
local ml
|
||||
if map[layer] then ml = map[layer] end
|
||||
if map.territory[layer] then ml = map.territory[layer] end
|
||||
assert( ml )
|
||||
ml.visible = not( ml.visible )
|
||||
print( layer, ml.visible )
|
||||
end
|
||||
|
||||
local layerVisibilityKeybinds = {
|
||||
["1"] = "africa",
|
||||
["2"] = "europe",
|
||||
["3"] = "northamerica",
|
||||
["4"] = "southamerica",
|
||||
["5"] = "southasia",
|
||||
["6"] = "russia",
|
||||
["7"] = "sailable",
|
||||
["8"] = "coastlines",
|
||||
["9"] = "coastlinesLow",
|
||||
["0"] = "international",
|
||||
["-"] = "cities"
|
||||
}
|
||||
|
||||
function love.keypressed(key)
|
||||
ToggleVisibility( layerVisibilityKeybinds[key] )
|
||||
|
||||
if key == "l" then
|
||||
-- To open a file or folder, "file://" must be prepended to the path.
|
||||
love.system.openURL("file://"..love.filesystem.getSaveDirectory())
|
||||
--mouse on map
|
||||
if map.selectionLocked then return end
|
||||
if map.editLayer and map.editLayer.selectNearest then
|
||||
map.selected = map.editLayer:selectNearest( Camera.GetWorldCoordinate( x, y ) )
|
||||
end
|
||||
|
||||
wasKeyPressed = true
|
||||
end
|
||||
|
||||
function love.keypressed(key, code, isRepeat)
|
||||
|
||||
if code == "left" then return button.selectPrev() end
|
||||
if code == "right" then return button.selectNext() end
|
||||
if code == "down" then return button.selectNextInGroup() end
|
||||
if code == "up" then return button.selectPrevInGroup() end
|
||||
if code == "return" then return button.selected:callback() end
|
||||
|
||||
if key == "c" then
|
||||
map.selectionLocked = not( map.selectionLocked )
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
do
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,142 @@
|
|||
|
||||
local love = assert( love )
|
||||
local button = require 'button'
|
||||
local savemodal = require 'savemodal'
|
||||
local map = require 'map'
|
||||
|
||||
button.new{ name = "SAVE", y = 222, callback = savemodal.start, icon = love.graphics.newImage( "icons/save.png" )}
|
||||
button.new{ name = "UNDO", y = 250, callback = map.undo, icon = love.graphics.newImage( "icons/undo.bmp" ) }
|
||||
|
||||
local tools
|
||||
local layerButtons = {}
|
||||
local function back( self )
|
||||
for k, button in pairs( tools ) do button.visible = false end
|
||||
for k, button in pairs( layerButtons ) do button.visible = true end
|
||||
self.visible = false
|
||||
map.editLayer = false
|
||||
end
|
||||
|
||||
local backButton = button.new{
|
||||
name = "UP",
|
||||
visible = false,
|
||||
y = 250 + button.h + 4,
|
||||
icon = love.graphics.newImage( "icons/up.bmp" ),
|
||||
callback = back,
|
||||
}
|
||||
|
||||
local function toolCallback( self )
|
||||
local f = (map.layers[self.layer])[self.name]
|
||||
if f then return f(self) end
|
||||
end
|
||||
|
||||
tools = {
|
||||
button.new{ name = "SELECT"},
|
||||
button.new{ name = "ERASE",},
|
||||
button.new{ name = "MOVE", },
|
||||
button.new{ name = "ADD", },
|
||||
button.new{ name = "EDIT", },
|
||||
button.new{ name = "DRAW", },
|
||||
}
|
||||
for i, v in ipairs( tools ) do
|
||||
v.callback = toolCallback
|
||||
v.y = 250 + (v.h + 4) * ( i + 1 )
|
||||
v.visible = false
|
||||
end
|
||||
|
||||
local layers = {
|
||||
{ name = "AF", layer = "africa" },
|
||||
{ name = "EU", layer = "europe" },
|
||||
{ name = "NA", layer = "northamerica" },
|
||||
{ name = "SA", layer = "southamerica" },
|
||||
{ name = "AS", layer = "southasia" },
|
||||
{ name = "RU", layer = "russia" },
|
||||
{ name = "PATH", layer = "travelnodes" },
|
||||
{ name = "AI", layer = "ainodes" },
|
||||
{ name = "CITY", layer = "cities" },
|
||||
{ name = "COAST", layer = "coastlines" },
|
||||
{ name = "LOW", layer = "coastlinesLow"},
|
||||
{ name = "INT", layer = "international"},
|
||||
{ name = "SAIL", layer = "sailable" },
|
||||
}
|
||||
|
||||
local showButtons = {}
|
||||
local visibilityIcon = love.graphics.newImage( "icons/eye.bmp" )
|
||||
local function updateVisibilityIcons()
|
||||
for i = 1, #showButtons do
|
||||
showButtons[i].icon = map.layers[ showButtons[i].layer ].visible and visibilityIcon
|
||||
end
|
||||
end
|
||||
|
||||
local function toggleVisibleLayer( self )
|
||||
if not (self and self.layer) then return end
|
||||
local ml = map.layers[ self.layer ]
|
||||
ml.visible = not( ml.visible )
|
||||
return updateVisibilityIcons()
|
||||
end
|
||||
|
||||
local soloIcon = false--love.graphics.newImage( "icons/eye.bmp" )
|
||||
local function soloVisibleLayer( self )
|
||||
for k, layer in pairs( map.layers ) do
|
||||
print( "invisible layer, map:", k, layer)
|
||||
layer.visible = false
|
||||
end
|
||||
map.layers[ self.layer ].visible = true
|
||||
return updateVisibilityIcons()
|
||||
end
|
||||
|
||||
local function editLayer( self )
|
||||
map.editLayer = map.layers[ self.layer ]
|
||||
map.editLayer.visible = true
|
||||
for k, button in pairs( layerButtons ) do button.visible = false end
|
||||
for k, button in pairs( tools ) do
|
||||
button.visible = true
|
||||
button.layer = self.layer
|
||||
end
|
||||
backButton.visible = true
|
||||
print( "EDITING LAYER", self.layer )
|
||||
return updateVisibilityIcons()
|
||||
end
|
||||
|
||||
local function copy( i, target )
|
||||
for k, v in pairs( layers[i] ) do
|
||||
target[k] = target[k] or v
|
||||
end
|
||||
return target
|
||||
end
|
||||
|
||||
|
||||
local y = 250
|
||||
local soloButtons = {}
|
||||
local editButtons = {}
|
||||
for i = 1, #layers do
|
||||
editButtons[i] = button.new( copy( i, {
|
||||
x = 8,
|
||||
y = y + (button.h + 4) * i,
|
||||
w = 112,
|
||||
callback = editLayer,
|
||||
group = "edit",
|
||||
}))
|
||||
layerButtons[ 3 * i - 2 ] = editButtons[i]
|
||||
|
||||
showButtons[i] = button.new( copy( i, {
|
||||
x = 128,
|
||||
y = y + (button.h + 4) * i,
|
||||
w = 24,
|
||||
name = "",
|
||||
callback = toggleVisibleLayer,
|
||||
icon = visibilityIcon,
|
||||
group = "show",
|
||||
}))
|
||||
layerButtons[ 3 * i - 1 ] = showButtons[i]
|
||||
|
||||
soloButtons[i] = button.new( copy( i, {
|
||||
x = 160,
|
||||
y = y + (button.h + 4) * i,
|
||||
w = 24,
|
||||
name = "S",
|
||||
callback = soloVisibleLayer,
|
||||
icon = soloIcon,
|
||||
group = "solo",
|
||||
}))
|
||||
layerButtons[ 3 * i ] = soloButtons[i]
|
||||
end
|
133
map.lua
|
@ -2,15 +2,35 @@ 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'
|
||||
|
||||
local map = {
|
||||
--flat list of editable layers for convenience
|
||||
local layers = {
|
||||
coastlines = false,
|
||||
coastlinesLow = false,
|
||||
international = false,
|
||||
africa = false,
|
||||
europe = false,
|
||||
northamerica = false,
|
||||
russia = false,
|
||||
southamerica = false,
|
||||
southasia = false,
|
||||
travelnodes = false,
|
||||
sailable = false,
|
||||
ainodes = false,
|
||||
cities = false,
|
||||
}
|
||||
|
||||
local map = {
|
||||
layers = layers,
|
||||
path = false,
|
||||
loaded = false,
|
||||
selected = false,
|
||||
selectionLocked = false,
|
||||
editLayer = false,
|
||||
|
||||
territory = {
|
||||
africa = false,
|
||||
europe = false,
|
||||
|
@ -19,33 +39,50 @@ local map = {
|
|||
southamerica = false,
|
||||
southasia = false
|
||||
},
|
||||
|
||||
background = false,
|
||||
coastlines = false,
|
||||
coastlinesLow = false,
|
||||
international = false,
|
||||
travelnodes = false,
|
||||
sailable = false,
|
||||
aimarkers = false,
|
||||
ainodes = false,
|
||||
cities = false
|
||||
}
|
||||
|
||||
function map.load()
|
||||
map.cities = Cities.load( "data/earth/cities.dat" )
|
||||
map.coastlines = Lines.load( "data/earth/coastlines.dat" )
|
||||
map.coastlinesLow = Lines.load( "data/earth/coastlines-low.dat" )
|
||||
map.international = Lines.load( "data/earth/international.dat" )
|
||||
map.sailable = Territory.load( "data/earth/sailable.bmp", "sailable" )
|
||||
map.travelnodes = Nodes.load( "data/earth/travel_nodes.bmp", map.sailable.isSailable ) --travel node adjacency matrix depends on sailable bitmap
|
||||
map.ainodes = AI.load( "data/earth/ai_markers.bmp" )
|
||||
function map.load( path )
|
||||
map.background = lg.newImage( "/data/graphics/blur.bmp" )
|
||||
map.cities = Cities.load( "/data/earth/cities.dat" )
|
||||
map.coastlines = Lines.load( "/data/earth/coastlines.dat" )
|
||||
map.coastlinesLow = Lines.load( "/data/earth/coastlines-low.dat" )
|
||||
map.international = Lines.load( "/data/earth/international.dat" )
|
||||
map.sailable = Territory.load( "/data/earth/sailable.bmp", "sailable" )
|
||||
map.travelnodes = Nodes.load( "/data/earth/travel_nodes.bmp", map.sailable.isSailable ) --travel node adjacency matrix depends on sailable bitmap
|
||||
map.ainodes = AI.load( "/data/earth/ai_markers.bmp" )
|
||||
for k, v in pairs(map.territory) do
|
||||
map.territory[k] = Territory.load( "data/earth/"..k..".bmp", k )
|
||||
map.territory[k] = Territory.load( "/data/earth/"..k..".bmp", k )
|
||||
end
|
||||
map.loaded = true
|
||||
map.path = path
|
||||
|
||||
--update references
|
||||
for k, v in pairs( layers ) do
|
||||
layers[k] = map[k] or map.territory[k]
|
||||
end
|
||||
end
|
||||
|
||||
function map.draw()
|
||||
lg.clear( 0, 0, 0, 1 )
|
||||
if not map.loaded then return end
|
||||
|
||||
do --territory
|
||||
lg.setColor( 1,1,1,1)
|
||||
lg.setLineJoin( "none" )
|
||||
lg.replaceTransform( Camera.tfTerritory )
|
||||
lg.setBlendMode( "add" )
|
||||
|
||||
lg.setColor( 1, 1, 1, 0.2 )
|
||||
lg.draw( map.background )
|
||||
lg.setColor( 1, 1, 1, 0.5 )
|
||||
for k, v in pairs(map.territory) do
|
||||
if v.visible then
|
||||
v:draw()
|
||||
|
@ -55,13 +92,9 @@ function map.draw()
|
|||
v:drawBorder( "sea" )
|
||||
end
|
||||
end
|
||||
if map.sailable.visible then map.sailable:draw() end
|
||||
lg.setBlendMode( "alpha" )
|
||||
if map.sailable.visible then
|
||||
map.sailable:draw()
|
||||
|
||||
lg.setColor( 1, 1, 1, 1 )
|
||||
end
|
||||
|
||||
do --borders
|
||||
lg.setLineJoin( "none" )
|
||||
lg.setLineWidth( 1 / Camera.zoom )
|
||||
|
||||
|
@ -71,25 +104,45 @@ function map.draw()
|
|||
lg.setLineWidth( 3 / Camera.zoom )
|
||||
map.sailable:drawBorder( "placeable" )
|
||||
end
|
||||
lg.setBlendMode( "alpha" )
|
||||
|
||||
lg.setColor( 1, 1, 1, 1 )
|
||||
end
|
||||
|
||||
do --all this stuff is drawn in world coordinates, ( -180, 180 ) x ( -100, 100 )
|
||||
|
||||
lg.replaceTransform( Camera.tf )
|
||||
|
||||
do --points
|
||||
if map.selected then
|
||||
if map.selected[1] then --lines
|
||||
local p = map.selected
|
||||
lg.setColor( 0.4, 0.5, 0.8, 0.5 )
|
||||
lg.setLineWidth( 0.2 / Camera.zoom )
|
||||
lg.rectangle( "fill", p.x, p.y, p.X - p.x, p.Y - p.y )
|
||||
lg.setColor( 1.0, 0, 0, 0.5 )
|
||||
lg.line( p )
|
||||
p:drawDirection()
|
||||
else --points
|
||||
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
|
||||
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
|
||||
lg.setPointSize( 5.0 )
|
||||
map.ainodes:draw()
|
||||
end
|
||||
|
||||
|
@ -115,23 +168,29 @@ function map.draw()
|
|||
|
||||
do --travel nodes
|
||||
lg.replaceTransform( Camera.tfNodes )
|
||||
if map.travelnodes.visible then
|
||||
map.travelnodes:draw()
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
local function write( filename, string )
|
||||
--
|
||||
print( "Writing", string:len(), "bytes to", filename )
|
||||
os.rename( filename, filename..".bak" ) --just in case :^)
|
||||
local file = assert( io.open( filename, "wb+" ) )
|
||||
assert( file:write( string ) )
|
||||
assert( file:flush() ) --your toilet is set to stun, not kill
|
||||
file:close()
|
||||
end
|
||||
|
||||
function map.save()
|
||||
map.cities.save()
|
||||
map.coastlines.save()
|
||||
map.coastlinesLow.save()
|
||||
map.international.save()
|
||||
map.sailable.save()
|
||||
map.travelnodes.save()
|
||||
map.ainodes.save()
|
||||
for k, v in pairs(map.territory) do
|
||||
map.territory[k].save()
|
||||
for k, layer in pairs( layers ) do
|
||||
write( map.path..tostring( layer.filename ), assert( layer:save() ) )
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -139,4 +198,8 @@ function map.hover(x, y)
|
|||
|
||||
end
|
||||
|
||||
function map.undo()
|
||||
print( "=== UNDO ===" )
|
||||
end
|
||||
|
||||
return map
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
local love = assert( love )
|
||||
local button = require( "button" )
|
||||
local t = {}
|
||||
t.__index = t
|
||||
|
||||
local i = 0
|
||||
function t.start( self )
|
||||
i = i + 1
|
||||
t[i] = t[i] or {}
|
||||
|
||||
--store callbacks
|
||||
for name in pairs( self ) do
|
||||
if love[name] then
|
||||
t[i][name] = love[name]
|
||||
love[name] = self[name]
|
||||
end
|
||||
end
|
||||
|
||||
--store menus
|
||||
local b = button.next
|
||||
repeat
|
||||
t[i][b] = b.visible
|
||||
b = b.next
|
||||
until b == button
|
||||
end
|
||||
|
||||
function t.stop( self )
|
||||
--restore callbacks
|
||||
for name in pairs( self ) do
|
||||
if love[name] then
|
||||
love[name] = t[i][name]
|
||||
end
|
||||
end
|
||||
|
||||
--restore menus
|
||||
local b = button
|
||||
button.selected = button
|
||||
repeat
|
||||
b = b.next
|
||||
b.visible = t[i][b]
|
||||
until b == button
|
||||
|
||||
t[i] = nil
|
||||
i = i - 1
|
||||
|
||||
end
|
||||
|
||||
function t.new( modal )
|
||||
return setmetatable( modal or {}, t )
|
||||
end
|
||||
|
||||
return t
|
101
nodes.lua
|
@ -1,101 +0,0 @@
|
|||
--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 lg = assert( love.graphics )
|
||||
|
||||
local isSailable
|
||||
|
||||
local function isConnected( startNode, endNode )
|
||||
local ix, iy, fx, fy = startNode.x, startNode.y, endNode.x, endNode.y
|
||||
if fx < -180 then fx = fx + 180 end
|
||||
if fx > 180 then fx = fx - 180 end
|
||||
|
||||
local dx, dy = fx - ix, fy - iy
|
||||
local mag = math.sqrt( dx * dx + dy * dy )
|
||||
local n = 2 * math.floor( mag )
|
||||
dx, dy = 0.5 * dx / mag, 0.5 * dy / mag
|
||||
|
||||
for i = 1, n do
|
||||
ix, iy = ix + dx, iy + dy
|
||||
if not( isSailable( ix, iy ) ) then return nil end
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
return closestNode
|
||||
end
|
||||
|
||||
function t.load( filename, sailable )
|
||||
|
||||
isSailable = sailable
|
||||
local img, imgd = bmp.load( filename )
|
||||
local nodes = { visible = true, nodes = {}, points = {}, connections = {}, img = img }
|
||||
|
||||
print( "=== Loading Nodes: ===" )
|
||||
local n = 1
|
||||
for x = 0, 799 do
|
||||
for y = 0, 399 do
|
||||
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.points[ 2 * n - 1 ] = long
|
||||
nodes.points[ 2 * n ] = lat
|
||||
print( n, long, lat )
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i, srcNode in ipairs( nodes.nodes ) do
|
||||
local adjacent = {}
|
||||
|
||||
for j, destNode in ipairs( nodes.nodes ) do
|
||||
adjacent[j] = isConnected( srcNode, destNode )
|
||||
end
|
||||
|
||||
nodes.connections[i] = adjacent
|
||||
end
|
||||
|
||||
print( "=== Nodes Loaded ===" )
|
||||
return setmetatable( nodes, {__index = t } )
|
||||
end
|
||||
|
||||
--Determine if graph has more than one connected component.
|
||||
function t.isConnected( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.draw( nodes )
|
||||
lg.setPointSize( 10 )
|
||||
lg.setColor( 1, 1, 1, 0.5 )
|
||||
lg.points( nodes.points )
|
||||
|
||||
for i, connection in pairs( nodes.connections ) do
|
||||
for j in pairs( connection ) do
|
||||
local ix, iy, fx, fy = nodes.nodes[i].x, nodes.nodes[i].y, nodes.nodes[j].x, nodes.nodes[j].y
|
||||
lg.line( ix, iy, fx, fy )
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.save( nodes, filename )
|
||||
|
||||
end
|
||||
|
||||
return t
|
|
@ -0,0 +1 @@
|
|||
Map editor for DEFCON, the strategy game, written in LOVE2D, the engine.
|
|
@ -0,0 +1,58 @@
|
|||
local love = assert( love )
|
||||
local modal = require( "modal" )
|
||||
local button = require( "button" )
|
||||
local map = require( "map" )
|
||||
local t = {}
|
||||
|
||||
local saveLocation = map.path
|
||||
local floppy = love.graphics.newImage( "icons/save.png" )
|
||||
floppy:setFilter( "nearest", "nearest" )
|
||||
local saveButton = button.new{
|
||||
group = "saveModal",
|
||||
name = "save",
|
||||
callback = function() map.save(); return t:stop() end,
|
||||
visible = false,
|
||||
icon = floppy,
|
||||
x = love.graphics.getWidth() / 2 - 300,
|
||||
y = love.graphics.getHeight() / 2 - 150,
|
||||
w = 600,
|
||||
h = 100,
|
||||
}
|
||||
|
||||
local xIcon = love.graphics.newImage( "icons/x.png" )
|
||||
xIcon:setFilter( "nearest", "nearest" )
|
||||
local cancelButton = button.new{
|
||||
group = "saveModal",
|
||||
name = "cancel",
|
||||
visible = false,
|
||||
icon = xIcon,
|
||||
callback = function() return t:stop() end,
|
||||
x = love.graphics.getWidth() / 2 - 300,
|
||||
y = love.graphics.getHeight() / 2,
|
||||
w = 600,
|
||||
h = 100
|
||||
}
|
||||
|
||||
function t.start()
|
||||
modal.start( t )
|
||||
button.selected = saveButton
|
||||
saveButton.name = "save to "..map.path
|
||||
button.displayGroup( "saveModal", true )
|
||||
end
|
||||
|
||||
function t.draw()
|
||||
love.graphics.clear( 0,0,0,1 )
|
||||
love.graphics.setColor( 1, 0, 0, 0.4 )
|
||||
button:draw()
|
||||
|
||||
end
|
||||
|
||||
function t.directorydropped( path )
|
||||
saveLocation = path
|
||||
map.path = path
|
||||
saveButton.name = "save to "..map.path
|
||||
return love.filesystem.mount( path, "" )
|
||||
end
|
||||
|
||||
|
||||
return modal.new( t )
|
|
@ -1,2 +0,0 @@
|
|||
local jit = require 'jit'
|
||||
for k, v in pairs( jit.opt ) do print(k , v ) end
|
|
@ -18,6 +18,7 @@ function t.load( filename, name )
|
|||
local territory = {
|
||||
visible = true,
|
||||
name = name,
|
||||
filename = filename,
|
||||
colour = colours[name],
|
||||
border = {},
|
||||
img = img,
|
||||
|
@ -56,10 +57,6 @@ function t.getPixel( territory, x, y )
|
|||
return territory.imgd:getPixel( imgx, imgy )
|
||||
end
|
||||
|
||||
function t.toggleVisibility( territory )
|
||||
|
||||
end
|
||||
|
||||
--[[
|
||||
0
|
||||
20 -- once sailable.bmp is brighter than this, the area is traversable by ships
|
||||
|
@ -109,8 +106,10 @@ 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
|
||||
w, h = math.min( 512, w ), math.min( 285, h )
|
||||
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
|
||||
|
@ -143,12 +142,10 @@ function t.computeBorder( territory, threshold, key )
|
|||
end
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.save( nodes, filename )
|
||||
|
||||
function t.save( territory )
|
||||
local fmt = (territory.name == "sailable") and "sailable" or "territory"
|
||||
print( "saving bitmap: ", territory.name, fmt )
|
||||
return bmp[fmt]( territory.imgd )
|
||||
end
|
||||
|
||||
return t
|
|
@ -0,0 +1,64 @@
|
|||
local love = assert( love )
|
||||
local utf8 = require("utf8")
|
||||
local modal = require( "modal" )
|
||||
local t = modal.new{ }
|
||||
|
||||
function t.setCurrentModal( fields )
|
||||
t.currentModal = assert( fields )
|
||||
t.currentIdx = 1
|
||||
return t:start()
|
||||
end
|
||||
|
||||
function t.draw()
|
||||
if not t.currentModal then return end
|
||||
love.graphics.setColor( 0, 0, 0, 0.3 )
|
||||
love.graphics.rectangle("fill", 0, 0, love.graphics.getDimensions())
|
||||
|
||||
|
||||
-- other fields
|
||||
for i, field in ipairs( t.currentModal ) do
|
||||
love.graphics.setColor( 1, 1, 1, 0.2 )
|
||||
local mode = (i == t.currentIdx) and "fill" or "line"
|
||||
love.graphics.rectangle( mode, 200, i * 28, 200, 14, 4 )
|
||||
love.graphics.setColor( 1, 1, 1, 1 )
|
||||
love.graphics.print( field.name, 8, i * 28 )
|
||||
love.graphics.print( field.value, 216, i * 28 )
|
||||
end
|
||||
end
|
||||
|
||||
function t.textinput( char )
|
||||
if t.currentModal then
|
||||
local field = t.currentModal.currentFieldIdx
|
||||
t.currentModal.currentField.value = t.currentModal.currentField.value .. char
|
||||
end
|
||||
end
|
||||
|
||||
function t.keypressed(key, code, isRepeat)
|
||||
if code == "down" then
|
||||
t.currentIdx = t.currentIdx + 1
|
||||
if t.currentIdx > #(t.currentModal) then
|
||||
t.currentIdx = 1
|
||||
end
|
||||
elseif code == "up" then
|
||||
t.currentIdx = t.currentIdx - 1
|
||||
if t.currentIdx < 1 then
|
||||
t.currentIdx = #(t.currentModal)
|
||||
end
|
||||
end
|
||||
|
||||
if key == "backspace" then
|
||||
-- get the byte offset to the last UTF-8 character in the string.
|
||||
local byteoffset = utf8.offset(text, -1)
|
||||
|
||||
if byteoffset then
|
||||
-- remove the last UTF-8 character.
|
||||
-- string.sub operates on bytes rather than UTF-8 characters, so we couldn't do string.sub(text, 1, -2).
|
||||
text = string.sub(text, 1, byteoffset - 1)
|
||||
end
|
||||
end
|
||||
if key == "escape" then
|
||||
return t:stop()
|
||||
end
|
||||
end
|
||||
|
||||
return t
|
|
@ -0,0 +1,159 @@
|
|||
--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 bmp = require 'bmp'
|
||||
local locationQuery = require 'locationQuery'
|
||||
local lg = assert( love.graphics )
|
||||
|
||||
|
||||
local t = setmetatable({}, {__index = locationQuery})
|
||||
local isSailable
|
||||
|
||||
local function hasEdge( startNode, endNode )
|
||||
local ix, iy, fx, fy = startNode.x, startNode.y, endNode.x, endNode.y
|
||||
if fx < -180 then fx = fx + 180 end
|
||||
if fx > 180 then fx = fx - 180 end
|
||||
|
||||
local dx, dy = fx - ix, fy - iy
|
||||
local mag = math.sqrt( dx * dx + dy * dy )
|
||||
local n = math.floor( mag )--/ 0.5 )
|
||||
dx, dy = dx / mag, dy / mag
|
||||
--dx, dy = 0.5 * dx, 0.5 * dy
|
||||
|
||||
for i = 0, n - 1 do
|
||||
ix, iy = ix + dx, iy + dy
|
||||
if not( isSailable( ix, -iy ) ) then return nil end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
function t.selectNearest( nodes, x, y )
|
||||
return (nodes.nodes):getClosestPoint( x, y )
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
local function worldToBitmap( x, y )
|
||||
|
||||
end
|
||||
|
||||
local function bitmapToWorld( x, y )
|
||||
local w, a = 360.0, 600.0 / 800.0
|
||||
local h = 360.0 * a
|
||||
x = w * ( x - 800 ) / 800 - w / 2 + 360
|
||||
y = h * ( y - 600 ) / 600 + 180
|
||||
return x, y
|
||||
--(360 * ( 600 / 800 )) * ( y - 600 ) / 600 + 180
|
||||
end
|
||||
|
||||
function t.load( filename, sailable )
|
||||
|
||||
isSailable = sailable
|
||||
local img, imgd = bmp.load( filename )
|
||||
local nodes = { filename = filename, visible = true, nodes = {}, points = {}, connections = {}, img = img }
|
||||
t.nodes = nodes
|
||||
local n = 1
|
||||
for x = 0, 799 do
|
||||
for y = 0, 399 do
|
||||
if imgd:getPixel( x, 399 - y ) > 0 then
|
||||
local long, lat = bitmapToWorld( x, y )
|
||||
nodes.nodes[n] = setmetatable({x = long, y = lat, idx = n}, mtTravelNode )
|
||||
nodes.points[ 2 * n - 1 ] = long
|
||||
nodes.points[ 2 * n ] = lat
|
||||
n = n + 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
for i, srcNode in ipairs( nodes.nodes ) do
|
||||
local adjacent = {}
|
||||
|
||||
for j, destNode in ipairs( nodes.nodes ) do
|
||||
adjacent[j] = hasEdge( srcNode, destNode )
|
||||
end
|
||||
|
||||
nodes.connections[i] = adjacent
|
||||
end
|
||||
|
||||
for i, node in ipairs( nodes.nodes ) do
|
||||
for j, destNode in ipairs( nodes.nodes ) do
|
||||
nodes.connections[i][j] = nodes.connections[i][j] or nodes.connections[j][i]
|
||||
end
|
||||
end
|
||||
|
||||
nodes.connected = t.isConnected( nodes )
|
||||
|
||||
nodes.nodes = locationQuery.New( nodes.nodes )
|
||||
setmetatable( nodes, {__index = t} )
|
||||
return nodes
|
||||
end
|
||||
|
||||
local function updateAdjacency( connections, i )
|
||||
for j in pairs( connections[i] ) do
|
||||
local edge = hasEdge( i, j )
|
||||
connections[j][i] = edge
|
||||
connections[i][j] = edge
|
||||
end
|
||||
end
|
||||
|
||||
local function dfs( idx, adj, unvisited )
|
||||
if not unvisited[idx] then return end
|
||||
print( "visiting node", idx )
|
||||
unvisited[ idx ] = nil
|
||||
for i in pairs( adj[idx] ) do dfs( i, adj, unvisited ) end
|
||||
end
|
||||
|
||||
--Determine if graph has more than one connected component.
|
||||
function t.isConnected( nodes )
|
||||
local adj = nodes.connections
|
||||
local unvisited = {}
|
||||
for i in ipairs( nodes.nodes ) do
|
||||
unvisited[i] = true
|
||||
end
|
||||
print( "DEPTH FIRST SEARCH:", "total nodes", #unvisited)
|
||||
|
||||
dfs( 1, adj, unvisited )
|
||||
for k in pairs( unvisited ) do
|
||||
print( "DEPTH FIRST SEARCH:", "unvisited", k)
|
||||
end
|
||||
return not(next(unvisited)) --empty if a graph is connected
|
||||
end
|
||||
|
||||
function t.draw( nodes )
|
||||
lg.setPointSize( 8 )
|
||||
lg.setColor( 1, 1, 1, 0.7 )
|
||||
lg.points( nodes.points )
|
||||
return t.drawConnections( nodes )
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
if nodes.connected then
|
||||
lg.setColor( 1, 1, 1, 0.4 )
|
||||
else
|
||||
lg.setColor( 1, 0, 0, 0.7 )
|
||||
end
|
||||
|
||||
for i, connection in pairs( nodes.connections ) do
|
||||
for j in pairs( connection ) do
|
||||
local ix, iy, fx, fy = nodes.nodes[i].x, nodes.nodes[i].y, nodes.nodes[j].x, nodes.nodes[j].y
|
||||
lg.line( ix, iy, fx, fy )
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
function t.save( nodes )
|
||||
return bmp.travel( nodes.nodes )
|
||||
end
|
||||
|
||||
return t
|