Feverish coding binge tonight.
Mouseover to find nearest city and display information. Camera movement via arrow keys and scroll wheel. Status bar displaying mouse location in world and window coordinates.
This commit is contained in:
parent
7fa3e83a22
commit
8eed23c87d
18
bmp.lua
18
bmp.lua
|
@ -1,5 +1,19 @@
|
|||
--Load and save the bmp formats used by DEFCON.
|
||||
local bmp = {}
|
||||
local t = {}
|
||||
local lfs = love.filesystem
|
||||
|
||||
--FFI bit-twiddling stuff.
|
||||
local ffi = require 'ffi'
|
||||
local bit = require 'bit'
|
||||
|
||||
return bmp
|
||||
function t.load( filename )
|
||||
local bytes, size = lfs.read( filename )
|
||||
|
||||
print( "LOADED", filename, size)
|
||||
end
|
||||
|
||||
function t.save( data, format )
|
||||
|
||||
end
|
||||
|
||||
return t
|
|
@ -0,0 +1,40 @@
|
|||
local tf = love.math.newTransform()
|
||||
local lg = assert( love.graphics )
|
||||
local Camera = { x = 0, y = 0, w = 360, h = 200, zoom = 1, tf = tf }
|
||||
|
||||
function Camera.GetWorldCoordinate( x, y )
|
||||
return tf: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 )
|
||||
end
|
||||
|
||||
function Camera.Translate( x, y )
|
||||
x = x or 0
|
||||
y = y or 0
|
||||
return Camera.Set( Camera.x + x, Camera.y + y, Camera.w, Camera.h)
|
||||
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
|
||||
tf:reset()
|
||||
tf:scale( w / 360, -h / 200 )
|
||||
tf:translate( 180 - x, -y - 100 )
|
||||
end
|
||||
|
||||
function Camera.Resize( w, h )
|
||||
w, h = math.min( w, h * 360 / 200 ), math.min( h, w * 200 / 360 )
|
||||
return Camera.Set( Camera.x, Camera.y, w, h )
|
||||
end
|
||||
|
||||
Camera.Resize( lg.getDimensions() )
|
||||
|
||||
return Camera
|
37
cities.lua
37
cities.lua
|
@ -1,31 +1,52 @@
|
|||
--Load and save the fixed width plaintext data used by DEFCON.
|
||||
local t = {}
|
||||
local io = io
|
||||
local math = math
|
||||
local table = table
|
||||
local tonumber = tonumber
|
||||
local lfs = love.filesystem
|
||||
local lg = love.graphics
|
||||
local locationQuery = require 'locationQuery'
|
||||
local cities
|
||||
local points = {}
|
||||
local caps = {}
|
||||
|
||||
t.selectedCity = nil
|
||||
|
||||
function t.draw()
|
||||
lg.points( points )
|
||||
end
|
||||
|
||||
function t.drawSelected( r )
|
||||
local c = t.selectedCity
|
||||
if not c then return end
|
||||
lg.circle( "fill", c.x, c.y, r )
|
||||
end
|
||||
|
||||
function t.drawCapitals()
|
||||
lg.points( caps )
|
||||
end
|
||||
|
||||
function t.selectNearestCity(x, y)
|
||||
t.selectedCity = cities:getClosestPoint(x, y)
|
||||
|
||||
if t.selectedCity then
|
||||
local city = t.selectedCity
|
||||
print( "SELECTED CITY", city.x, city.y, city.name, city.pop, city.capital)
|
||||
else
|
||||
print( "NO SELECTED CITY" )
|
||||
end
|
||||
end
|
||||
|
||||
function t.load( filename )
|
||||
|
||||
cities = {}
|
||||
local n = 0
|
||||
local n = 1
|
||||
local idxPts = 1
|
||||
local idxCaps = 1
|
||||
|
||||
for line in assert( io.lines( filename ), "Error: could not open cities.dat" ) do
|
||||
n = n + 1
|
||||
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+)" )
|
||||
x, y, pop, capital = tonumber( x ), tonumber( y ), tonumber( pop ), ( tonumber( capital ) > 0)
|
||||
local city = {
|
||||
|
@ -33,7 +54,8 @@ function t.load( filename )
|
|||
country = line:sub( 42, 82 ):gsub("%s+$",""),
|
||||
x = x, y = y, pop = pop, capital = capital
|
||||
}
|
||||
cities[n] = city
|
||||
cities[n] = city
|
||||
n = n + 1
|
||||
|
||||
points[idxPts], points[idxPts + 1] = x, y
|
||||
idxPts = idxPts + 2
|
||||
|
@ -44,9 +66,12 @@ function t.load( filename )
|
|||
end
|
||||
end
|
||||
|
||||
--Multiple inheritance.
|
||||
cities = locationQuery.New( cities )
|
||||
setmetatable( getmetatable( cities ).__index, {__index = t } )
|
||||
|
||||
print( "LOADED", filename, n )
|
||||
|
||||
return setmetatable( cities, {__index = t } )
|
||||
return cities
|
||||
end
|
||||
|
||||
function t.save( cities, filename )
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
function love.conf(t)
|
||||
t.identity = "dcEarth" -- The name of the save directory (string)
|
||||
t.appendidentity = true -- Search files in source directory before save directory (boolean)
|
||||
t.version = "11.3" -- The LÖVE version this game was made for (string)
|
||||
t.console = true -- Attach a console (boolean, Windows only)
|
||||
t.accelerometerjoystick = true -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean)
|
||||
t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean)
|
||||
t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean)
|
||||
|
||||
t.audio.mic = false -- Request and use microphone capabilities in Android (boolean)
|
||||
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.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)
|
||||
t.window.resizable = true -- Let the window be user-resizable (boolean)
|
||||
t.window.minwidth = 1 -- Minimum window width if the window is resizable (number)
|
||||
t.window.minheight = 1 -- Minimum window height if the window is resizable (number)
|
||||
t.window.fullscreen = false -- Enable fullscreen (boolean)
|
||||
t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string)
|
||||
t.window.vsync = 0 -- Vertical sync mode (number)
|
||||
t.window.msaa = 0 -- The number of samples to use with multi-sampled antialiasing (number)
|
||||
t.window.depth = nil -- The number of bits per sample in the depth buffer
|
||||
t.window.stencil = nil -- The number of bits per sample in the stencil buffer
|
||||
t.window.display = 1 -- Index of the monitor to show the window in (number)
|
||||
t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean)
|
||||
t.window.usedpiscale = true -- Enable automatic DPI scaling when highdpi is set to true as well (boolean)
|
||||
t.window.x = nil -- The x-coordinate of the window's position in the specified display (number)
|
||||
t.window.y = nil -- The y-coordinate of the window's position in the specified display (number)
|
||||
|
||||
t.modules.audio = true -- Enable the audio module (boolean)
|
||||
t.modules.data = true -- Enable the data module (boolean)
|
||||
t.modules.event = true -- Enable the event module (boolean)
|
||||
t.modules.font = true -- Enable the font module (boolean)
|
||||
t.modules.graphics = true -- Enable the graphics module (boolean)
|
||||
t.modules.image = true -- Enable the image module (boolean)
|
||||
t.modules.joystick = false -- Enable the joystick module (boolean)
|
||||
t.modules.keyboard = true -- Enable the keyboard module (boolean)
|
||||
t.modules.math = true -- Enable the math module (boolean)
|
||||
t.modules.mouse = true -- Enable the mouse module (boolean)
|
||||
t.modules.physics = false -- Enable the physics module (boolean)
|
||||
t.modules.sound = true -- Enable the sound module (boolean)
|
||||
t.modules.system = true -- Enable the system module (boolean)
|
||||
t.modules.thread = true -- Enable the thread module (boolean)
|
||||
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)
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
Cambridge United Kingdom 6.166700 35.566700 106673 0
|
||||
Cambridge United Kingdom 6.166700 35.566700 106673 0
|
||||
Gloucester United Kingdom 6.666700 36.366699 108150 0
|
||||
Hastings/Bexhill United Kingdom 5.400000 36.183300 112080 0
|
||||
Slough United Kingdom 13.250000 -8.833300 106882 0
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 8.3 KiB |
|
@ -0,0 +1,65 @@
|
|||
--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 ) )
|
||||
if debug then print( "HASH: ", s, x, y) end
|
||||
return s
|
||||
end
|
||||
|
||||
function t.getClosestPoint( points, x, y )
|
||||
local closePoints = hashes[hash( x, y )]
|
||||
if not closePoints then return end
|
||||
local distance = math.huge
|
||||
local px, py, point
|
||||
for k, v in pairs(closePoints) do
|
||||
|
||||
px, py = v.x, v.y
|
||||
local d = (x - px) * (x - px) + (y - py) * (y - py)
|
||||
if d < distance then
|
||||
distance = d
|
||||
point = v
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
print( #closePoints, distance )
|
||||
|
||||
return point
|
||||
end
|
||||
|
||||
function t.Edit( points, point, x, y )
|
||||
local h = hashes[hash( point.x, point.y )]
|
||||
if h then
|
||||
for i, p in pairs(h) do
|
||||
if p == point then
|
||||
table.remove( h, i )
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
table.insert( hashes[ hash( x, y ) ], point )
|
||||
point.x = x
|
||||
point.y = y
|
||||
|
||||
end
|
||||
|
||||
function t.Add( points )
|
||||
|
||||
end
|
||||
|
||||
function t.New( points )
|
||||
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 } )
|
||||
end
|
||||
|
||||
return t
|
61
main.lua
61
main.lua
|
@ -1,29 +1,78 @@
|
|||
local love = assert( love, "This tool requires LOVE: love2d.org" )
|
||||
local map = require 'map'
|
||||
local SAVEDIRECTORY = "out/"
|
||||
local Camera = require 'camera'
|
||||
local wasKeyPressed
|
||||
|
||||
function love.load()
|
||||
|
||||
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" )
|
||||
end
|
||||
|
||||
local canvas = love.graphics.newCanvas()
|
||||
function love.update( dt )
|
||||
love.graphics.setCanvas( canvas )
|
||||
map.draw()
|
||||
love.graphics.setCanvas()
|
||||
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 moveCamera then Camera.Translate( tx, ty ) end
|
||||
|
||||
|
||||
end
|
||||
|
||||
|
||||
function love.draw()
|
||||
love.graphics.draw( canvas )
|
||||
love.graphics.push( "all" )
|
||||
map.draw()
|
||||
love.graphics.pop()
|
||||
|
||||
--Status bar.
|
||||
local x, y = love.mouse.getPosition()
|
||||
local wx, wy = Camera.GetWorldCoordinate( 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 )
|
||||
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)
|
||||
|
||||
--Edit box.
|
||||
love.graphics.rectangle( "line", love.graphics.getWidth() / 2, h, love.graphics.getWidth() / 2, 30 )
|
||||
if map.cities.selectedCity then
|
||||
local c = map.cities.selectedCity
|
||||
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 )
|
||||
end
|
||||
end
|
||||
|
||||
function love.resize(w, h)
|
||||
Camera.Resize( w, h )
|
||||
end
|
||||
|
||||
function love.wheelmoved(x, y)
|
||||
Camera.Zoom( (y > 0) and true or false )
|
||||
end
|
||||
|
||||
function love.mousepressed( x, y, button, istouch, presses )
|
||||
local wx, wy = Camera.GetWorldCoordinate( x, y )
|
||||
print( ("MOUSE\tx %f\ty %f\twx %f\twy %f"):format(x, y, wx, wy) )
|
||||
end
|
||||
|
||||
function love.mousemoved( x, y, dx, dy, istouch )
|
||||
map.cities.selectNearestCity( Camera.GetWorldCoordinate( x, y ) )
|
||||
end
|
||||
|
||||
function love.keypressed(key)
|
||||
if key == "s" then
|
||||
if key == "l" then
|
||||
-- To open a file or folder, "file://" must be prepended to the path.
|
||||
love.system.openURL("file://"..love.filesystem.getSaveDirectory())
|
||||
end
|
||||
wasKeyPressed = true
|
||||
end
|
||||
|
|
40
map.lua
40
map.lua
|
@ -1,7 +1,9 @@
|
|||
local lg = love.graphics
|
||||
local Cities = require 'cities'
|
||||
local Lines = require 'lines'
|
||||
local Nodes = require 'nodes'
|
||||
local Bitmap = require 'bmp'
|
||||
local Camera = require 'camera'
|
||||
|
||||
local map = {
|
||||
coastlines = false,
|
||||
|
@ -26,41 +28,49 @@ function map.load()
|
|||
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.travelnodes = Nodes.load( "data/earth/travel_nodes.bmp" )
|
||||
end
|
||||
|
||||
function map.draw()
|
||||
lg.clear( 0, 0, 0, 1 )
|
||||
|
||||
do --all this stuff is drawn in world coordinates, ( -180, 180 ) x ( -100, 100 )
|
||||
lg.push( "transform" )
|
||||
lg.scale( lg.getCanvas():getDimensions() )
|
||||
lg.scale( 1 / 360, - 1 / 200 )
|
||||
lg.translate( 180, -100 )
|
||||
|
||||
|
||||
|
||||
lg.replaceTransform( Camera.tf )
|
||||
|
||||
do --points
|
||||
lg.setColor( 1, 0, 0, 0.5 )
|
||||
lg.setPointSize( 2 )
|
||||
map.cities:draw()
|
||||
lg.setPointSize( 0.5 * Camera.zoom )
|
||||
map.cities.draw()
|
||||
|
||||
lg.setColor( 1, 0, 0, 1.0 )
|
||||
lg.setPointSize( 4 )
|
||||
map.cities:drawCapitals()
|
||||
lg.setColor( 1, 1, 1.0, 0.5 )
|
||||
lg.setPointSize( 1.0 * Camera.zoom )
|
||||
map.cities.drawCapitals()
|
||||
|
||||
lg.setColor( 1, 0, 1, 0.5 )
|
||||
map.cities.drawSelected( 22.0 / Camera.zoom )
|
||||
end
|
||||
|
||||
do --line stuff
|
||||
lg.setColor(1, 1, 1, 0.2 )
|
||||
lg.setLineWidth( 0.2 )
|
||||
|
||||
lg.setLineJoin( "miter" )
|
||||
lg.setLineWidth( 0.2 / Camera.zoom )
|
||||
map.international:draw()
|
||||
|
||||
lg.setColor(1, 1, 1, 1 )
|
||||
lg.setLineWidth( 0.3 )
|
||||
lg.setColor(1, 1, 1, 0.5 )
|
||||
map.coastlines:draw()
|
||||
map.coastlinesLow:draw()
|
||||
|
||||
--International Date Line
|
||||
lg.line( -180, -100, -180, 100 )
|
||||
lg.line( 180, -100, 180, 100 )
|
||||
lg.line( -180, 90, 180, 90 )
|
||||
lg.line( -180, -90, 180, -90 )
|
||||
lg.line( -180, 100, 180, 100 )
|
||||
lg.line( -180, -100, 180, -100 )
|
||||
end
|
||||
|
||||
lg.pop( )
|
||||
end
|
||||
|
||||
end
|
||||
|
|
26
nodes.lua
26
nodes.lua
|
@ -1,7 +1,29 @@
|
|||
--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 nodes = {}
|
||||
local t = {}
|
||||
local bmp = require 'bmp'
|
||||
|
||||
function t.load( filename )
|
||||
local nodes = { points = {}, connections = {}, img = bmp.load( filename ) }
|
||||
|
||||
return setmetatable( nodes, {__index = t } )
|
||||
end
|
||||
|
||||
return nodes
|
||||
function t.isConnected( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.draw( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.save( nodes, filename )
|
||||
|
||||
end
|
||||
|
||||
return t
|
|
@ -0,0 +1,2 @@
|
|||
local jit = require 'jit'
|
||||
for k, v in pairs( jit.opt ) do print(k , v ) end
|
|
@ -0,0 +1,42 @@
|
|||
local t = {}
|
||||
local bmp = require 'bmp'
|
||||
|
||||
function t.load( filename, name )
|
||||
local territory = { img = bmp.load( filename ) }
|
||||
return setmetatable( territory, {__index = t } )
|
||||
end
|
||||
|
||||
--World space coordinate.
|
||||
function t.isValid( x, y )
|
||||
|
||||
end
|
||||
|
||||
function t.getPixel( territory, x, y )
|
||||
--ZERO INDEXED
|
||||
return territory.img[math.floor( 512 * ( x + 180 ) / 360 ) ][ math.floor( 285 * ( y + 100 ) / 200 ) ]
|
||||
end
|
||||
|
||||
--[[
|
||||
0
|
||||
20 -- once sailable.bmp is brighter than this, the area is traversable by ships
|
||||
60 -- once sailable.bmp and territory.bmp are brighter than this, ships can be placed here
|
||||
130 -- if territory.bmp is brighter than this and sailable is darker than 60, structures are placeable.
|
||||
|
||||
SO:
|
||||
SAILABLE: 0 (not), 21 (traverse not place), 61 ( traverse and place )
|
||||
TERRITORY: 131 ( place land if sailable <= 60 ), 61 ( place sea ), 0
|
||||
]]
|
||||
|
||||
function t.draw( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.drawConnections( nodes )
|
||||
|
||||
end
|
||||
|
||||
function t.save( nodes, filename )
|
||||
|
||||
end
|
||||
|
||||
return t
|
Loading…
Reference in New Issue