Compare commits

..

No commits in common. "main" and "bmploading" have entirely different histories.

53 changed files with 358 additions and 1427 deletions

View File

@ -1,7 +1,7 @@
--Manage the AI nodes used by DEFCON. --Manage the AI nodes used by DEFCON.
local bmp = require 'map.bmp' local bmp = require 'bmp'
local lg = assert( love.graphics ) local lg = assert( love.graphics )
local locationQuery = require 'map.locationQuery' local locationQuery = require 'locationQuery'
local t = setmetatable( {}, {__index = locationQuery } ) local t = setmetatable( {}, {__index = locationQuery } )
local print = print local print = print
@ -17,14 +17,6 @@ function aiNode:formatDisplayInfo()
]]):format( self.idx, self.x, self.y, tostring(self.attacking) ) ]]):format( self.idx, self.x, self.y, tostring(self.attacking) )
end end
function aiNode:add()
end
function aiNode:moveTo( x, y )
end
function t.load( filename ) function t.load( filename )
local img, imgd = bmp.load( filename ) local img, imgd = bmp.load( filename )
local nodes = { local nodes = {
@ -90,8 +82,4 @@ function t.save( nodes )
return bmp.ai( nodes.all ) return bmp.ai( nodes.all )
end end
function t.newNode( isAttacking )
end
return t return t

View File

@ -16,7 +16,6 @@ function test.load( filename )
print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() ) print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() )
local img = love.graphics.newImage( imgd ) local img = love.graphics.newImage( imgd )
img:setFilter( "nearest", "nearest" ) img:setFilter( "nearest", "nearest" )
img:setWrap( "repeat", "clampzero" )
return img, imgd return img, imgd
end end
@ -52,6 +51,7 @@ end
local function getHeader( filename ) local function getHeader( filename )
local offset = 2 + love.data.unpack( "<I4", assert(love.filesystem.read( "data", filename, 14 )), 11 ) local offset = 2 + love.data.unpack( "<I4", assert(love.filesystem.read( "data", filename, 14 )), 11 )
local header, size = assert( love.filesystem.read( filename, offset ) ) local header, size = assert( love.filesystem.read( filename, offset ) )
print( "BMP HEADER", filename, size, "\n", string.byte( header, 1, size ) )
return header return header
end end
@ -74,7 +74,7 @@ local formats = {
header = getHeader( "data/earth/sailable.bmp" ), header = getHeader( "data/earth/sailable.bmp" ),
w = 512, w = 512,
h = 285, h = 285,
--technically this information is in the header already but I don't want to write code to parse it we only use one palette ever --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 = { palette = {
[0] = 0, [0] = 0,
[8] = 1, [8] = 1,
@ -104,16 +104,6 @@ local formats = {
w = 512, w = 512,
h = 285, h = 285,
}, },
["blur"] = {
header = getHeader( "data/graphics/blur.bmp" ),
w = 512,
h = 285,
},
["screenshot"] = {
header = getHeader( "screenshot.bmp" ),
w = 256,
h = 128,
},
} }
function formats.ai:test( ) function formats.ai:test( )
@ -169,6 +159,7 @@ function formats.ai:encode( data )
local x = math.floor(self.w * (wx + 180) / 360) local x = math.floor(self.w * (wx + 180) / 360)
--idk why exactly I need to round the value here instead of truncating it --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) 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 --get index into byte array
local idx = 2 + y * self.w + x 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-editor we could have two points in the same place, possibly of the same or different types
@ -233,6 +224,7 @@ function formats.travel:encode( points )
local offset = math.floor( y * self.w + x - 2 ) local offset = math.floor( y * self.w + x - 2 )
--1 := white --1 := white
bitmap[offset] = 1 bitmap[offset] = 1
print( "encoded:", point.idx, wx, wy, x, y )
end end
--fold into 4-bit pixel array --fold into 4-bit pixel array
@ -255,63 +247,40 @@ function formats.sailable:test()
print "sailable OK" print "sailable OK"
end end
function formats.sailable:encode( data )
local w, h = self.w, self.h
local bytes = { self.header:sub( 1, -2 ) }
local i = 2
do --sailable --y coordinates are written top to bottom
for y = h - 1, 0, -1 do
local reversePalette = {} for x = 0, w - 1 do
for eight, four in pairs( formats.sailable.palette ) do bytes[i] = assert( self.palette[ math.floor( data:getPixel(x, y) * 255 )] )
reversePalette[ four ] = eight i = i + 1
end
--take the red channel in float [0, 1] format
--expand it to a byte, then quantize it to 4 bits
--according to the sailable palette
local function sailableQuantize( r )
if r == 0 then return 0 end
if r >= 1 then return 15 end
r = math.floor( r * 255 )
for four = 1, #reversePalette do
if reversePalette[ four ] > r then return four end
end end
error( "Could not quantize sailable.bmp!" )
end end
function formats.sailable:encode( data ) --fold into 4-bit pixel array
local w, h = self.w, self.h local nybbles = { bytes[1] }
local bytes = { self.header:sub( 1, -2 ) } for j = 2, #bytes / 2 do
local i = 2 local a, b = bytes[ 2 * j ], bytes[ 2 * j + 1 ]
nybbles[j] = string.char( 16 * a + b )
--y coordinates are written top to bottom
for y = h - 1, 0, -1 do
for x = 0, w - 1 do
bytes[i] = sailableQuantize( data:getPixel(x, y) )
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 end
function formats.africa:test() return table.concat( nybbles )
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
end 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 ) function formats.africa:encode( data )
local w, h = self.w, self.h local w, h = self.w, self.h
local bytes = { self.header:sub( 1, -3 ) } local bytes = { self.header:sub( 1, -3 ) }
@ -327,24 +296,11 @@ function formats.africa:encode( data )
return table.concat( bytes ) return table.concat( bytes )
end end
function formats.screenshot:encode( data )
return formats.territory.encode( self, data )
end
--this one was 8-bit grayscale in the original game
--but we're going to increase the bit depth to 24 because
--the game can render colours in blur.bmp just fine
--and we want to render tinted radar ranges into that file
function formats.blur:encode( data )
return formats.territory.encode( self, data )
end
function t.load( filename ) function t.load( filename )
local imgd = love.image.newImageData( filename ) local imgd = love.image.newImageData( filename )
print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() ) print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() )
local img = love.graphics.newImage( imgd ) local img = love.graphics.newImage( imgd )
img:setFilter( "nearest", "nearest" ) img:setFilter( "nearest", "nearest" )
img:setWrap( "repeat", "clampzero" )
return img, imgd return img, imgd
end end
@ -357,7 +313,7 @@ formats.territory = formats.africa
for fmt, tbl in pairs( formats ) do for fmt, tbl in pairs( formats ) do
t[fmt] = function( data ) return tbl:encode( data ) end t[fmt] = function( data ) return tbl:encode( data ) end
--TESTING --TESTING
--tbl:test() tbl:test()
end end
return t return t

View File

@ -2,16 +2,14 @@ local lg = love.graphics
local t = { local t = {
name = "", name = "",
tooltip = "", tooltip = "button",
icon = false, icon = false,
lit = false,
x = 8, x = 8,
y = 250, y = 250,
w = 13 * 28 - 4, w = 176,
h = 24, h = 24,
group = false, group = false,
visible = false, visible = true,
align = "center",
callback = function( self ) return print( "clicked button: ", self.name, self.x, self.y, self.w, self.h, self.visible ) end 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 t.selected, t.next, t.prev = t, t, t
@ -31,22 +29,18 @@ function t.new( b )
return b return b
end end
function t.highlight( b )
lg.rectangle( "fill", b.x, b.y, b.w, b.h )
end
local drawPassOngoing = false local drawPassOngoing = false
function t.draw( b ) function t.draw( b )
if b == t then if b == t then
drawPassOngoing = not( drawPassOngoing ) drawPassOngoing = not( drawPassOngoing )
if not drawPassOngoing then return end if not drawPassOngoing then return end
elseif b.visible then elseif b.visible then
lg.rectangle( "line", b.x, b.y, b.w, b.h ) lg.rectangle( "line", b.x, b.y, b.w, b.h, 6 )
lg.printf( b.name, lg.printf( b.name,
b.x + (b.icon and b.h or 0), b.x + (b.icon and b.h or 0),
b.y + 0.5 * ( b.h - lg.getFont():getHeight() ), b.y + 0.5 * ( b.h - lg.getFont():getHeight() ),
b.w - (b.icon and b.h or 0), b.w - (b.icon and b.h or 0),
b.align ) "center" )
if b.icon then if b.icon then
local h = b.icon:getHeight() local h = b.icon:getHeight()
lg.draw( b.icon, lg.draw( b.icon,
@ -54,8 +48,8 @@ function t.draw( b )
0, 0,
b.h / h ) b.h / h )
end end
if b.lit or t.selected == b then if t.selected == b then
b:highlight() lg.rectangle( "fill", b.x, b.y, b.w, b.h, 6 )
end end
end end
return t.draw( b.next ) return t.draw( b.next )
@ -80,9 +74,7 @@ end
function t.selectNextInGroup() function t.selectNextInGroup()
--make sure our group is visible, otherwise the loop doesn't end --make sure our group is visible, otherwise the loop doesn't end
local group = t.selected local group = t.selected and t.selected.visible and t.selected.group
group = group and t.selected.visible
group = group and t.selected.group
if not group then return t.selectNext() end if not group then return t.selectNext() end
repeat t.selectNext() until group == t.selected.group repeat t.selectNext() until group == t.selected.group
end end
@ -94,27 +86,15 @@ function t.selectPrevInGroup()
repeat t.selectPrev() until group == t.selected.group repeat t.selectPrev() until group == t.selected.group
end end
--show/hide all buttons in a group function t.displayGroup( group, show )
--passing hide=true will hide the group, hide=false or nil will show the group
--solo=true will hide all buttons outside the group
function t.displayGroup( group, hide, solo )
local b = t local b = t
repeat repeat
b = b.next b = b.next
local inGroup = (group == b.group) b.visible = ( b.group == group )
if solo or inGroup then
b.visible = not(hide) and inGroup
end
until b == t until b == t
t.visible = true t.visible = true
end end
function t.mousepressed( x, y )
if t.selected and t.selected:contains( x, y ) then
return t.selected:callback()
end
end
function t.deselect( b ) function t.deselect( b )
t.selected = t t.selected = t
end end

View File

@ -3,7 +3,7 @@ local tfTerritory = love.math.newTransform()
local tfNodes = love.math.newTransform() local tfNodes = love.math.newTransform()
local lg = assert( love.graphics ) local lg = assert( love.graphics )
local Camera = { local Camera = {
x = 0, y = 70, x = -90, y = 45,
w = 360, h = 200, w = 360, h = 200,
zoom = 1, tf = tf, zoom = 1, tf = tf,
tfTerritory = tfTerritory, tfNodes = tfNodes } tfTerritory = tfTerritory, tfNodes = tfNodes }
@ -37,9 +37,7 @@ end
function Camera.Translate( x, y ) function Camera.Translate( x, y )
x = x or 0 x = x or 0
y = y or 0 y = y or 0
return Camera.Set( return Camera.Set( math.max(-180, math.min(360, Camera.x + x)), math.min(100, Camera.y + y), Camera.w, Camera.h)
math.max(-360, math.min(360, Camera.x + x)),
math.max(-140, math.min(140, Camera.y + y)), Camera.w, Camera.h)
end end
--In world coordinates: top left corner at x, y, extent of 1/w, 1/h. --In world coordinates: top left corner at x, y, extent of 1/w, 1/h.
@ -50,17 +48,14 @@ function Camera.Set( x, y, w, h )
tf:scale( w / 360, -h / 200 ) tf:scale( w / 360, -h / 200 )
tf:translate( 180 - x, -y - 100 ) tf:translate( 180 - x, -y - 100 )
tfTerritory:reset() tfTerritory:reset()
tfTerritory:scale( w / 512, h / 285 ) tfTerritory:scale( w / 512, h / 285 )
tfTerritory:translate( -x * 512 / 360, y * 512 / 360 ) tfTerritory:translate( -x * 512 / 360, y * 512 / 360 )
tfNodes:reset() tfNodes:reset()
tfNodes:scale( w / 360, -h / 200 ) tfNodes:scale( w / 360, -h / 200 )
tfNodes:translate( 180 - x , -y - 100 ) tfNodes:translate( 180 - x , -y - 100 )
--tfNodes:translate( -x * 800 / 360, y * 400 / 200 ) --tfNodes:translate( -x * 800 / 360, y * 400 / 200 )
end end
function Camera.Resize( w, h ) function Camera.Resize( w, h )

View File

@ -6,14 +6,13 @@ local table = table
local tonumber = tonumber local tonumber = tonumber
local lfs = love.filesystem local lfs = love.filesystem
local lg = love.graphics local lg = love.graphics
local locationQuery = require 'map.locationQuery' local locationQuery = require 'locationQuery'
local cities local cities
local points = {} local points = {}
local caps = {} local caps = {}
t.selected = nil t.selected = nil
t.selectionLocked = false t.selectionLocked = false
local invisible = 10000 --sentinel value outside the draw rectangle
function t.lockSelection() function t.lockSelection()
t.selectionLocked = true t.selectionLocked = true
@ -54,65 +53,6 @@ function city:formatDisplayInfo()
CAPITAL: %s]]):format( self.name, self.country, self.x, self.y, self.pop, tostring(self.capital) ) CAPITAL: %s]]):format( self.name, self.country, self.x, self.y, self.pop, tostring(self.capital) )
end end
function city:delete()
print( "deleting city:", self.name )
self.deleted = true
if self.capital then
caps[ self.caps ] = invisible
caps[ self.caps + 1] = invisible
end
points[ self.points ] = invisible
points[ self.points + 1] = invisible
end
function city:add()
local n = #cities + 1
cities[ n ] = self
self.n = n
local idxPoints = #points + 1
self.points = idxPoints
points[ idxPoints ], points[ idxPoints + 1 ] = self.x, self.y
end
function city:moveTo(x, y)
self.x, self.y = x, y
if self.points then
points[ self.points ] = x
points[ self.points + 1 ] = y
end
if self.capital then
caps[ self.caps ] = x
caps[ self.caps + 1 ] = y
end
end
function city:toggleCapital()
if self.capital then
self.capital = false
caps[ self.caps ] = invisible
caps[ self.caps + 1 ] = invisible
else
self.capital = true
local idx = #caps + 1
caps[ idx ] = self.x
caps[ idx + 1 ] = self.y
self.caps = idx
end
end
function t.newCity( tbl )
return setmetatable({
name = "",
country = "",
x = 0,
y = 0,
pop = 0,
capital = false,
}, citymt )
end
function t.load( filename ) function t.load( filename )
print( "=== LOADING CITIES. ===" ) print( "=== LOADING CITIES. ===" )
@ -130,11 +70,10 @@ function t.load( filename )
if capital then --check against empty or malformed line if capital then --check against empty or malformed line
x, y, pop, capital = tonumber( x ), tonumber( y ), tonumber( pop ), ( tonumber( capital ) > 0) x, y, pop, capital = tonumber( x ), tonumber( y ), tonumber( pop ), ( tonumber( capital ) > 0)
local city = setmetatable({ local city = setmetatable({
name = line:sub( 1, 39 ):gsub("%s+$",""), name = line:sub( 1, 39 ):gsub("%s+$",""),
country = line:sub( 42, 82 ):gsub("%s+$",""), country = line:sub( 42, 82 ):gsub("%s+$",""),
x = x, y = y, pop = pop, capital = capital, x = x, y = y, pop = pop, capital = capital
n = n, points = idxPts, caps = capital and idxCaps }, citymt )
}, citymt )
cities[n] = city cities[n] = city
n = n + 1 n = n + 1
@ -160,13 +99,9 @@ end
function t.save( cities ) function t.save( cities )
local str = {} local str = {}
local i = 1 for n, city in ipairs( cities ) do
for _, city in ipairs( cities ) do str[n] = ("%-41s%-41s%-14f%-14f%-19d %d"):format(
if not city.deleted then city.name, city.country, city.x, city.y, city.pop, city.capital and 1 or 0 )
str[i] = ("%-41s%-41s%-14f%-14f%-19d %d"):format(
city.name, city.country, city.x, city.y, city.pop, city.capital and 1 or 0 )
i = i + 1
end
end end
return assert(table.concat( str, "\n" )) return assert(table.concat( str, "\n" ))
end end

View File

@ -12,7 +12,7 @@ function love.conf(t)
t.window.title = "dcEarth" -- The window title (string) t.window.title = "dcEarth" -- The window title (string)
t.window.icon = "icons/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 = 1024 -- The window width (number) t.window.width = 800 -- The window width (number)
t.window.height = 640 -- 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.borderless = false -- Remove all border visuals from the window (boolean)
t.window.resizable = true -- Let the window be user-resizable (boolean) t.window.resizable = true -- Let the window be user-resizable (boolean)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 391 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 357 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -1,103 +0,0 @@
-- mkdir only
-- A portable filesystem API using LuaJIT's FFI
-- Retrieved 2024-07-13 from https://gist.githubusercontent.com/Techcable/503f35ceea9554fb81cf3a5c1aa550da/raw/33a29f59207335b743824fbb657e4721a12ce280/fs.lua
local ffi = require("ffi")
local table = require("table")
require("string")
-- Cache needed functions and locals
local C, errno, string = ffi.C, ffi.errno, ffi.string
local concat, insert = table.concat, table.insert
-- "Standard" C99 functions
ffi.cdef[[
char *strerror(int errnum);
]]
local exists, mkdir, PATH_SEPARATOR
if ffi.os == "Windows" then
ffi.cdef[[
bool CreateDirectoryA(const char *path, void *lpSecurityAttributes);
]]
function mkdir(path, _)
assert(type(path) == "string", "path isn't a string")
if not C.CreateDirectoryA(path, nil) then
local message = string(C.strerror(errno()))
error("Unable to create directory " .. path .. ": " .. message)
end
end
PATH_SEPARATOR = "\\"
elseif ffi.os == "Linux" or ffi.os == "OSX" then
ffi.cdef[[
int mkdir(const char *path, int mode);
]]
function mkdir(path, mode)
assert(type(path) == "string", "path isn't a string")
local mode = tonumber(mode or "755", 8)
if C.mkdir(path, mode) ~= 0 then
local message = string(C.strerror(errno()))
error("Unable to create directory " .. path .. ": " .. message)
end
end
PATH_SEPARATOR = "/"
else
error("Unsupported operating system: " .. ffi.os)
end
local function join(...)
local parts = {}
for i = 1, select("#", ...) do
local part = select(i, ...)
insert(parts, part)
end
return concat(parts, PATH_SEPARATOR)
end
local function splitPath(path)
assert(type(path) == "string", "path isn't a string!")
local parts = {}
local lastIndex = 0
for i = 1, path:len() do
if path:sub(i, i) == PATH_SEPARATOR then
insert(parts, path:sub(lastIndex, i - 1))
lastIndex = i + 1
end
end
insert(parts, path:sub(lastIndex))
return parts
end
local function mkdirs(path)
local parts = splitPath(path)
local currentPath = parts[1]
for i=2, #parts do
if not exists(currentPath) then
mkdir(currentPath)
end
-- Note: This isn't suboptimal, since we really do need the intermediate results
currentPath = currentPath .. PATH_SEPARATOR .. parts[i]
end
if not exists(path) then
mkdir(path)
end
end
--- Check if a file or directory exists in this path
function exists(file)
local ok, err, code = os.rename(file, file)
if not ok then
if code == 13 then
-- Permission denied, but it exists
return true
end
end
return ok, err
end
return {
exists = exists,
join = join,
mkdir = mkdir,
mkdirs = mkdirs,
splitPath = splitPath,
PATH_SEPERATOR = PATH_SEPARATOR
}

View File

@ -12,7 +12,7 @@ function polygon:formatDisplayInfo()
y: %f y: %f
X: %f X: %f
Y: %f Y: %f
Length: %d]]):format( self.x, self.y, self.X, self.Y, #self / 4 ) N: %d]]):format( self.x, self.y, self.X, self.Y, #self )
end end
function polygon:drawDirection() function polygon:drawDirection()
@ -73,9 +73,8 @@ function t.selectNearest( lines, wx, wy )
poly.X + 5 > wx and poly.X + 5 > wx and
poly.y - 5 < wy and poly.y - 5 < wy and
poly.Y + 5 > wy then poly.Y + 5 > wy then
for k = 1, #poly - 3, 4 do for k = 1, #poly, 2 do
--find the midpoint of each line segment local x, y = poly[k], poly[k + 1]
local x, y = 0.5 * (poly[k] + poly[k + 2]), 0.5 * (poly[k + 1] + poly[k + 3])
local r = ( x - wx ) * ( x - wx ) + ( y - wy ) * ( y - wy ) local r = ( x - wx ) * ( x - wx ) + ( y - wy ) * ( y - wy )
if r < d then if r < d then
d = r d = r
@ -88,14 +87,11 @@ function t.selectNearest( lines, wx, wy )
end end
function t.save( lines ) function t.save( lines )
local str = { "b" } --initial B local str = { "b" }
for i, poly in ipairs( lines ) do for i, poly in ipairs( lines ) do
str[i + 1] = table.concat( poly, " " ) str[i + 1] = table.concat( poly, " " )
end end
--concatenate into one big string, one line per polygon str = table.concat( str, "\nb\n" ):gsub("(%S+) (%S+) ", "%1 %2\n")
--then put each pair of numbers on their own line (without concatenating)
--we use CRLF line breaks here because that's what's in the original game files
str = table.concat( str, "\13\nb\13\n" ):gsub("(%S+) (%S+) ", "%1 %2\13\n")
return str return str
end end

View File

@ -1,9 +1,9 @@
local love = assert( love, "This tool requires LOVE: love2d.org" ) local love = assert( love, "This tool requires LOVE: love2d.org" )
--assert( require('mobdebug') ).start() --remote debugger --assert( require('mobdebug') ).start() --remote debugger
local map = require 'map.map' local map = require 'map'
local button = require 'ui.button' local button = require 'button'
local mainmenu = require 'ui.menu.mainmenu' require 'mainmenu'
local Camera = require 'ui.camera' local Camera = require 'camera'
function love.load() function love.load()
love.filesystem.setIdentity( "dcearth", false ) love.filesystem.setIdentity( "dcearth", false )
@ -20,10 +20,6 @@ function love.directorydropped( path )
return map.load( path ) return map.load( path )
end end
function love.filedropped( path )
end
function love.update( dt ) function love.update( dt )
local tx, ty = 0, 0 local tx, ty = 0, 0
local moveCamera = false local moveCamera = false
@ -48,7 +44,29 @@ function love.draw()
map.draw() map.draw()
love.graphics.pop() love.graphics.pop()
mainmenu.draw() --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() - 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.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)
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 end
function love.resize(w, h) function love.resize(w, h)
@ -62,29 +80,31 @@ end
function love.mousepressed( x, y, mouseButton, istouch, presses ) function love.mousepressed( x, y, mouseButton, istouch, presses )
local wx, wy = Camera.GetWorldCoordinate( x, y ) local wx, wy = Camera.GetWorldCoordinate( x, y )
return button.mousepressed( x, y ) 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 end
function love.mousemoved( x, y, dx, dy, istouch ) function love.mousemoved( x, y, dx, dy, istouch )
if not map.loaded then return end if not map.loaded then return end
--mouse over menu --mouse over menu
if y < mainmenu.menuHeight then button.selectIn( x, y )
button.selectIn( x, y )
--mouse on map --mouse on map
else if map.selectionLocked then return end
if map.selectionLocked then return end if map.editLayer and map.editLayer.selectNearest then
if map.editLayer and map.editLayer.selectNearest then map.selected = map.editLayer:selectNearest( Camera.GetWorldCoordinate( x, y ) )
map.selected = map.editLayer:selectNearest( Camera.GetWorldCoordinate( x, y ) )
end
end end
end end
function love.keypressed(key, code, isRepeat) function love.keypressed(key, code, isRepeat)
if code == "up" then return button.selectPrev() end if code == "left" then return button.selectPrev() end
if code == "down" then return button.selectNext() end if code == "right" then return button.selectNext() end
if code == "right" then return button.selectNextInGroup() end if code == "down" then return button.selectNextInGroup() end
if code == "left" then return button.selectPrevInGroup() end if code == "up" then return button.selectPrevInGroup() end
if code == "return" then return button.selected:callback() end if code == "return" then return button.selected:callback() end
if key == "c" then if key == "c" then
@ -92,6 +112,9 @@ function love.keypressed(key, code, isRepeat)
end end
end end
function love.textinput()
do
end end

142
mainmenu.lua Normal file
View File

@ -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

View File

@ -1,15 +1,10 @@
local love = assert( love )
local io = io
local coroutine = coroutine
local mkdir = assert( require 'lib.mkdir' )
local lg = love.graphics local lg = love.graphics
local AI = require 'map.ai' local AI = require 'ai'
local Cities = require 'map.cities' local Cities = require 'cities'
local Lines = require 'map.lines' local Lines = require 'lines'
local Nodes = require 'map.travelNodes' local Nodes = require 'travelNodes'
local Camera = require 'ui.camera' local Camera = require 'camera'
local Territory = require 'map.territory' local Territory = require 'territory'
local Blur = require 'map.blur'
--flat list of editable layers for convenience --flat list of editable layers for convenience
local layers = { local layers = {
@ -26,7 +21,6 @@ local layers = {
sailable = false, sailable = false,
ainodes = false, ainodes = false,
cities = false, cities = false,
blur = false,
} }
local map = { local map = {
@ -53,28 +47,9 @@ local map = {
travelnodes = false, travelnodes = false,
sailable = false, sailable = false,
ainodes = false, ainodes = false,
cities = false, cities = false
blur = false,
} }
function map.reloadLayer( path )
--Shouldn't call this before the map loads, but just in case
if not map.loaded then return end
for name, layer in pairs( layers ) do
if layer.filename and
(layer.filename:gsub( ".+[\\/]", "") == path.filename:gsub( ".+[\\/]", "" )) then
local newLayer = layer:load( path )
newLayer.filename = layer.filename --we don't store the full path in there
map[ name ] = newLayer
layers[ name ] = newLayer
return layer.filename
end
end
return
end
function map.load( path ) function map.load( path )
map.background = lg.newImage( "/data/graphics/blur.bmp" ) map.background = lg.newImage( "/data/graphics/blur.bmp" )
map.cities = Cities.load( "/data/earth/cities.dat" ) map.cities = Cities.load( "/data/earth/cities.dat" )
@ -84,7 +59,6 @@ function map.load( path )
map.sailable = Territory.load( "/data/earth/sailable.bmp", "sailable" ) 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.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" ) map.ainodes = AI.load( "/data/earth/ai_markers.bmp" )
map.blur = Blur.load( "/data/graphics/blur.bmp", map )
for k, v in pairs(map.territory) do 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 end
@ -98,7 +72,6 @@ function map.load( path )
end end
function map.draw() function map.draw()
love.graphics.setScissor( 0, 200, love.graphics.getWidth(), love.graphics.getHeight() - 200 )
lg.clear( 0, 0, 0, 1 ) lg.clear( 0, 0, 0, 1 )
if not map.loaded then return end if not map.loaded then return end
@ -108,31 +81,31 @@ function map.draw()
lg.setBlendMode( "add" ) lg.setBlendMode( "add" )
lg.setColor( 1, 1, 1, 0.2 ) lg.setColor( 1, 1, 1, 0.2 )
--lg.draw( map.background ) lg.draw( map.background )
lg.setColor( 1, 1, 1, 0.5 ) lg.setColor( 1, 1, 1, 0.5 )
local sh = require 'shaders.sailable'
lg.setShader( sh )
sh:send( "lowBorder", 60 )
sh:send( "highBorder", 130 )
for k, v in pairs(map.territory) do for k, v in pairs(map.territory) do
if v.visible then if v.visible then
v:draw() v:draw()
--[[lg.setLineWidth( 1 / Camera.zoom ) lg.setLineWidth( 1 / Camera.zoom )
v:drawBorder( "land" ) v:drawBorder( "land" )
lg.setLineWidth( 3 / Camera.zoom ) lg.setLineWidth( 3 / Camera.zoom )
v:drawBorder( "sea" )]] v:drawBorder( "sea" )
end end
end end
if map.sailable.visible then if map.sailable.visible then
sh:send( "lowBorder", 20 )
sh:send( "highBorder", 60 )
lg.setShader( require 'shaders.sailable' )
map.sailable:draw() map.sailable:draw()
lg.setLineJoin( "none" )
lg.setLineWidth( 1 / Camera.zoom )
lg.setColor( 1, 1, 1, 0.5)
map.sailable:drawBorder( "sailable" )
lg.setLineWidth( 3 / Camera.zoom )
map.sailable:drawBorder( "placeable" )
end end
lg.setShader()
lg.setBlendMode( "alpha" ) lg.setBlendMode( "alpha" )
lg.setColor( 1, 1, 1, 1 ) lg.setColor( 1, 1, 1, 1 )
end end
@ -205,42 +178,20 @@ function map.draw()
end end
do local function write( filename, string )
local function write( filename, string ) --
local file = assert( io.open( filename, "wb" ) ) print( "Writing", string:len(), "bytes to", filename )
assert( file:write( string ) ) os.rename( filename, filename..".bak" ) --just in case :^)
assert( file:flush() ) --your toilet is set to stun, not kill local file = assert( io.open( filename, "wb+" ) )
file:close() assert( file:write( string ) )
assert( file:flush() ) --your toilet is set to stun, not kill
file:close()
end
function map.save()
for k, layer in pairs( layers ) do
write( map.path..tostring( layer.filename ), assert( layer:save() ) )
end end
local function save()
--should be cross platform-ish.
--race condition, unfortunately.
--maybe we should do this on part on load, then keep a lockfile open in each of these folders
for _, folder in ipairs{ "/data/", "/data/earth/", "/data/graphics/" } do
coroutine.yield( "Creating folder ".. folder )
assert( mkdir.exists( map.path ), map.path )
local path = map.path..folder
if not mkdir.exists( path ) then mkdir.mkdir( path ) end
end
local files = {}
--Write everything to strings first, in case there are errors we don't want to half-write the map
for k, layer in pairs( layers ) do
coroutine.yield( "Exporting layer ".. k )
files[ map.path..tostring( layer.filename ) ] = assert( layer:save() )
end
for filename, str in pairs( files ) do
coroutine.yield( "Writing ".. filename )
write( filename, str )
end
coroutine.yield() --yield nothing to indicate we're done
return save() --save again
end
map.save = coroutine.wrap( save )
end end
function map.hover(x, y) function map.hover(x, y)
@ -251,17 +202,4 @@ function map.undo()
print( "=== UNDO ===" ) print( "=== UNDO ===" )
end end
function map.setEditLayer( layerName )
if not layerName then
map.editLayer = nil
for name, layer in pairs( layers ) do layer.visible = true end
else
for name, layer in pairs( layers ) do
layer.visible = false
end
map.editLayer = layers[ layerName ]
if map.editLayer then map.editLayer.visible = true end
end
end
return map return map

View File

@ -1,113 +0,0 @@
local love = assert( love )
local lg = love.graphics
local lt = love.timer
local coroutine = assert( coroutine )
local bmp = require 'map.bmp'
local scale = 1
local w, h = scale * 512, scale * 285
local dilateShader = require 'shaders.dilate'
local finalCanvas = lg.newCanvas( w, h )
local canvas = lg.newCanvas( w, h )
local identityTransform = love.math.newTransform()
local tiles = {}
do
local sideLength = 4
local i = 1
for x = 1, sideLength do
for y = 1, sideLength do
local width = math.floor( w / sideLength )
local height = math.floor( h / sideLength )
tiles[i] = function() return
(x - 1) * width, (y - 1) * height,
width, height
end
i = i + 1
end
end
end
--the coroutine library doesn't reset all the global graphics state for us
--so this yield function does
local function y( s, currentTile )
lg.pop( "all" )
coroutine.yield( s )
lg.push( "all" )
lg.setCanvas( canvas )
lg.setShader( dilateShader )
lg.setScissor( currentTile() )
lg.setBlendMode( "add" )
end
local function renderRadar( map, filename )
local statusString = "%s: rendering %s radius\n %s\n %d/%d"
--we need this first push to the stack to keep y() simple
lg.push( "all" )
y( filename, tiles[1] )
--then we clear the whole canvas to opaque black, pause, and select the first tile
lg.setScissor()
lg.clear( 0, 0, 0, 1 )
dilateShader:send( "sailable", map.sailable.img )
for tile = 1, #tiles do
--dilate placeable land area by 30 degrees (radar radius)
dilateShader:send( "radius", math.floor( 512 * 30 / 360 ) )
for name, terr in pairs( map.territory ) do
y( statusString:format( filename, "radar", name, tile, #tiles ), tiles[tile] )
lg.setColor(
0.9 + terr.colour[1],
0.9 + terr.colour[2],
0.9 + terr.colour[3],
1 / 6 )
lg.draw( terr.img, 0, 0, 0, scale, scale )
end
--dilate placeable land area by 45 degrees (sub launch radius)
dilateShader:send( "radius", math.floor( 512 * 45 / 360 ) )
for name, terr in pairs( map.territory ) do
y( statusString:format( filename, "sub", name, tile, #tiles ), tiles[tile] )
lg.setColor(
0.9 + terr.colour[1],
0.9 + terr.colour[2],
0.9 + terr.colour[3],
1 / 12 )
lg.draw( terr.img, 0, 0, 0, scale, scale )
end
end
lg.setCanvas( finalCanvas )
lg.setScissor()
lg.setShader()
lg.clear( 0, 0, 0, 1 )
lg.setColor( 1, 1, 1, 1 )
lg.draw( canvas )
lg.pop( "all" )
coroutine.yield( ("%s: encoding image data"):format( filename ))
return bmp.blur( finalCanvas:newImageData() )
end
local t = {}
function t:save( )
return renderRadar( self.map, self.filename )
end
function t.load( filename, map )
t.filename = filename
t.map = map
return t
end
function t.draw()
lg.setScissor()
return lg.draw( canvas )
end
return t

View File

@ -1,19 +0,0 @@
local t = {}
t.options = {
Name = "New DEFCON Mod",
Version = "0.1",
Author = "DeFacto",
Website = "https://wan-may.art/dev/",
Comment = "DEFCON map made with dcEarth",
radarExport = true,
screenGenerator = true,
highDetail = true,
}
--parse and load metadata from mod.txt
function t.load( filename )
end
return t

View File

@ -1,5 +0,0 @@
Name New DEFCON Mod
Version 0.1
Author DeFacto
Website https://wan-may.art/dev/
Comment DEFCON map made with dcEarth

View File

@ -1,22 +1,12 @@
local love = assert( love ) local love = assert( love )
local button = require( "ui.button" ) local button = require( "button" )
local t = {} local t = {}
t.__index = t t.__index = t
local i = 0 local i = 0
function t.start( self ) function t.start( self )
print( "starting modal:", i + 1)
love.graphics.push( "all" )
love.graphics.setScissor(
self.x or 0,
self.y or 0,
self.w or love.graphics.getWidth(),
self.h or love.graphics.getHeight())
i = i + 1 i = i + 1
t[i] = { modal = self } t[i] = t[i] or {}
t.previous = t[i]
t.current = self
--store callbacks --store callbacks
for name in pairs( self ) do for name in pairs( self ) do
@ -38,31 +28,21 @@ function t.stop( self )
--restore callbacks --restore callbacks
for name in pairs( self ) do for name in pairs( self ) do
if love[name] then if love[name] then
love[name] = t[i][name] or love[name] love[name] = t[i][name]
end end
end end
--restore menus --restore menus
local b = button local b = button
button.deselect() button.selected = button
repeat repeat
b = b.next b = b.next
b.visible = t[i][b] or false b.visible = t[i][b]
until b == button until b == button
t.current = t[i].modal
t[i] = nil t[i] = nil
i = i - 1 i = i - 1
t.previous = t[i - 1]
love.graphics.pop( "all" )
end
function t.exitAll()
if i < 1 then return end
i = 1
return t.stop( love )
end end
function t.new( modal ) function t.new( modal )

View File

@ -1,5 +1 @@
Map editor for DEFCON, the strategy game, written in LOVE2D, the engine. Map editor for DEFCON, the strategy game, written in LOVE2D, the engine.
Currently does not do anything besides read the files and write them back out.
Need to make a more structured UI, rewriting menu.lua and button.lua for this purpose.

58
savemodal.lua Normal file
View File

@ -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 )

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

View File

@ -1,46 +0,0 @@
return love.graphics.newShader[[
#pragma language glsl3
uniform int radius;
uniform Image sailable;
bool own( float x )
{
if( x * 255.0 > 130.0 ){
return true;
}
else {
return false;
}
}
bool land( float x )
{
if( x * 255.0 <= 60.0 ){
return true;
}
else {
return false;
}
}
bool placeable( Image tex, vec2 uv )
{
return own(Texel(tex, uv).r) && land(Texel(sailable, uv).r);
}
vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords )
{
for(int i = -radius; i <= radius; ++i) {
for(int j = -radius; j <= radius; ++j) {
vec2 offset = vec2( ivec2( i, j ) ) / vec2( textureSize(tex, 0) );
int r = ((i * i) + (j * j));
if ( (r < (radius * radius) )
&& placeable( tex, texture_coords + offset )){
return color;
}
}
}
return vec4( 0.0, 0.0, 0.0, 0.0 );
}
]]

View File

@ -1,36 +0,0 @@
return love.graphics.newShader[[
#pragma language glsl3
uniform float highBorder;
uniform float lowBorder;
#define p(x) ((x) * 255.0 > highBorder)
#define t(x) ((x) * 255.0 > lowBorder)
vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords )
{
vec2 s = vec2( 1.0, 1.0 ) / vec2( textureSize(tex, 0) );
float c = Texel( tex, texture_coords ).r;
vec4 g = vec4(
Texel( tex, texture_coords + s * vec2(1.0, 0.0)).r,
Texel( tex, texture_coords + s * vec2(0.0, 1.0)).r,
Texel( tex, texture_coords + s * vec2(-1.0, 0.0)).r,
Texel( tex, texture_coords + s * vec2(0.0, -1.0)).r
);
bvec4 place = bvec4( p(g.r), p(g.g), p(g.b), p(g.a) );
bvec4 traverse = bvec4( t(g.r), t(g.g), t(g.b), t(g.a) );
float a;
if ( p(c) ){
if( all( place ) ) { a = 1.0; }
else { a = 2.0; }
}
else if( t(c) ){
if( all( traverse ) ) { a = 0.5; }
else { a = 0.75; }
}
else{
a = 0.0;
}
return vec4( color.rgb, a * color.a );
}
]]

View File

@ -1,54 +0,0 @@
--[[
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
]]
return love.graphics.newShader[[
#pragma language glsl3
uniform int radius;
uniform Image sailable;
bool seaPlace( float x, float y )
{
return ( x * 255.0 > 60.0 ) && ( y * 255.0 > 60.0 );
}
bool own( float x )
{
if( x * 255.0 > 130.0 ){
return true;
}
else {
return false;
}
}
bool land( float x )
{
if( x * 255.0 <= 60.0 ){
return true;
}
else {
return false;
}
}
bool placeable( Image tex, vec2 uv )
{
return own(Texel(tex, uv).r) && land(Texel(sailable, uv).r);
}
vec4 effect( vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords )
{
if (placeable( tex, texture_coords + offset )){
return color;
}
return vec4( 0.0, 0.0, 0.0, 0.0 );
}
]]

View File

@ -1,5 +1,5 @@
local t = {} local t = {}
local bmp = require 'map.bmp' local bmp = require 'bmp'
local lg = assert( love.graphics ) local lg = assert( love.graphics )
local colours = { local colours = {
@ -144,6 +144,7 @@ end
function t.save( territory ) function t.save( territory )
local fmt = (territory.name == "sailable") and "sailable" or "territory" local fmt = (territory.name == "sailable") and "sailable" or "territory"
print( "saving bitmap: ", territory.name, fmt )
return bmp[fmt]( territory.imgd ) return bmp[fmt]( territory.imgd )
end end

View File

@ -1,6 +1,6 @@
local love = assert( love ) local love = assert( love )
local utf8 = require("utf8") local utf8 = require("utf8")
local modal = require( "ui.modal" ) local modal = require( "modal" )
local t = modal.new{ } local t = modal.new{ }
function t.setCurrentModal( fields ) function t.setCurrentModal( fields )
@ -47,17 +47,16 @@ function t.keypressed(key, code, isRepeat)
end end
if key == "backspace" then if key == "backspace" then
local text = t.currentModal.currentField
-- get the byte offset to the last UTF-8 character in the string. -- get the byte offset to the last UTF-8 character in the string.
local byteoffset = utf8.offset(text, -1) local byteoffset = utf8.offset(text, -1)
if byteoffset then if byteoffset then
-- remove the last UTF-8 character. -- 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). -- string.sub operates on bytes rather than UTF-8 characters, so we couldn't do string.sub(text, 1, -2).
t.currentModal.currentField = text:sub( 1, byteoffset - 1) text = string.sub(text, 1, byteoffset - 1)
end end
end end
if code == "escape" then if key == "escape" then
return t:stop() return t:stop()
end end
end end

View File

@ -2,8 +2,8 @@
--This is important for a mapping tool because the DEFCON client will not load a map unless --This is important for a mapping tool because the DEFCON client will not load a map unless
--the pathfinding nodes form a connected graph. --the pathfinding nodes form a connected graph.
local bmp = require 'map.bmp' local bmp = require 'bmp'
local locationQuery = require 'map.locationQuery' local locationQuery = require 'locationQuery'
local lg = assert( love.graphics ) local lg = assert( love.graphics )
@ -57,7 +57,7 @@ end
function t.load( filename, sailable ) function t.load( filename, sailable )
isSailable = sailable or isSailable isSailable = sailable
local img, imgd = bmp.load( filename ) local img, imgd = bmp.load( filename )
local nodes = { filename = filename, visible = true, nodes = {}, points = {}, connections = {}, img = img } local nodes = { filename = filename, visible = true, nodes = {}, points = {}, connections = {}, img = img }
t.nodes = nodes t.nodes = nodes
@ -107,6 +107,7 @@ end
local function dfs( idx, adj, unvisited ) local function dfs( idx, adj, unvisited )
if not unvisited[idx] then return end if not unvisited[idx] then return end
print( "visiting node", idx )
unvisited[ idx ] = nil unvisited[ idx ] = nil
for i in pairs( adj[idx] ) do dfs( i, adj, unvisited ) end for i in pairs( adj[idx] ) do dfs( i, adj, unvisited ) end
end end

View File

@ -1,65 +0,0 @@
local love = assert( love )
local modal = require( "ui.modal" )
local button = require( "ui.button" )
local map = require( "map.map" )
local t = {}
local loadLocation = false
local folder = love.graphics.newImage( "icons/load.png" )
folder:setFilter( "nearest", "nearest" )
local loadButton = button.new{
group = t,
name = "load",
callback = function()
if not loadLocation then return end
map.load( loadLocation )
return t:stop() end,
visible = false,
icon = folder,
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 = t,
name = "cancel load",
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 )
loadLocation = loadLocation or map.path
button.selected = loadButton
loadButton.name = "load from "..loadLocation
button.displayGroup( t, false, true )
end
function t.draw()
love.graphics.clear( 0,0,0,1 )
love.graphics.setColor( 0, 0, 1, 0.4 )
button:draw()
end
function t.directorydropped( path )
loadLocation = path
map.path = path
loadButton.name = "load from "..map.path
return love.filesystem.mount( path, "" )
end
function t.filedropped( path )
return map.reloadLayer( path )
end
return modal.new( t )

View File

@ -1,45 +0,0 @@
local t = {}
local lg = assert( love ).graphics
local modal = require 'ui.modal'
local button = require 'ui.button'
local camera = require 'ui.camera'
local map = require 'map.map'
local node
local moveModal = modal.new{}
local selectModal = modal.new{}
button.new{ name = "ATTACK NODE",
group = t,
icon = lg.newImage("icons/node-attack.png"),
x = 615,
y = 0,
callback = function()
node = map.ainodes.newNode( true )
return moveModal:start()
end
}
button.new{ name = "PLACEMENT NODE",
group = t,
icon = lg.newImage("icons/node-place.png"),
x = 615,
y = 1 * (4 + button.h),
callback = function()
node = map.ainodes.newNode( true )
return moveModal:start()
end
}
button.new{ name = "MOVE NODE",
group = t,
y = 2 * (4 + button.h),
x = 615,
}
button.new{ name = "DELETE NODE",
group = t,
y = 3 * (4 + button.h),
x = 615,
}
return t

View File

@ -1,279 +0,0 @@
local love = assert( love )
local lg = assert( love ).graphics
local utf8 = require 'utf8'
local button = require 'ui.button'
local textinput = require 'ui.textinput'
local modal = require 'ui.modal'
local map = require 'map.map'
local camera = require 'ui.camera'
local t = {}
local city
local function keypressed( key, code, isRepeat )
if code == 'escape' then return modal.current:stop() end
if modal.previous then return modal.previous.keypressed( key, code, isRepeat ) end
end
--select modal: as normal, but clicking a city will proceed to the previously selected mode,
--and clicking escape will clear the previously selected mode
local selectModal = modal.new{}
local textModal = modal.new{}
local numberModal = modal.new{}
local editModal = modal.new{ mousepressed = button.mousepressed, keypressed = keypressed, }
local moveModal = modal.new{ mousepressed = button.mousepressed, keypressed = keypressed, mousemoved = button.selectIn }
local deleteModal = modal.new{ keypressed = keypressed }
local currentMode
button.new{ name = "NEW CITY",
group = t,
icon = lg.newImage("icons/city-new.png"),
x = 615,
y = 0,
callback = function()
city = map.cities.newCity()
city:add()
return editModal:start()
end
}
moveModal.button = button.new{ name = "MOVE CITY",
group = t,
icon = lg.newImage("icons/city-move.png"),
x = 615,
y = button.h + 4,
callback = function( self )
self.lit = true
selectModal.mode = moveModal
return selectModal:start()
end,
}
editModal.button = button.new{ name = "EDIT CITY",
group = t,
x = 615,
y = (button.h + 4) * 2,
callback = function( self )
self.lit = true
selectModal.mode = editModal
return selectModal:start()
end,
}
deleteModal.button = button.new{ name = "DELETE CITY",
group = t,
icon = lg.newImage("icons/city-delete.png"),
x = 615,
y = (button.h + 4) * 3,
callback = function( self )
self.lit = true
return deleteModal:start()
end,
}
--editButtons
local function editText( self )
print( "editing: ", self.field, city.name )
self.lit = true
return textModal:start( self.field )
end
local function editNumber( self )
print( "editing: ", self.field, city.name )
self.lit = true
return numberModal:start( self.field )
end
local editButtons = {
save = button.new{
icon = lg.newImage( "icons/check.png" ),
callback = function() return editModal:stop() end,},
name = button.new{ callback = editText },
country = button.new{ callback = editText },
x = button.new{ callback = editNumber },
y = button.new{ callback = editNumber },
capital = button.new{ callback = function( self )
if city then
self.name = tostring( not( city.capital ) )
return city:toggleCapital()
end
end },
}
do
local i = 0
for _, key in ipairs{ "save", "name", "country", "x", "y", "capital" } do
local b = assert( editButtons[ key ] )
b.align = "right"
b.field = key
b.name = tostring( key ) --bools must be cast to string before getting passed to printf
b.group = editModal
b.x = lg.getWidth() / 2 - button.w / 2
b.y = (button.h + 4) * i
i = i + 1
end
end
function editModal:start()
modal.start( self )
button.displayGroup( self, false, true )
for k, b in pairs( editButtons ) do
b.name = tostring( city[k] or b.name )
end
if city.capital == false then editButtons.capital.name = "false" end
end
function editModal:stop()
return modal.stop( self )
end
function editModal.draw()
lg.setColor( 1, 1, 1, 0.5 )
return button:draw()
end
function moveModal.update( dt )
local x, y = love.mouse.getPosition()
if y > t.menuHeight and love.mouse.isDown( 1 ) then
local wx, wy = camera.GetWorldCoordinate( x, y )
city:moveTo( wx, wy )
end
end
function moveModal.mousemoved( x, y, dx, dy, istouch )
return button.selectIn( x, y )
end
function moveModal.mousepressed( x, y, mouseButton, istouch, presses )
if y < t.menuHeight then
moveModal:stop()
return button.mousepressed( x, y, mouseButton, istouch, presses )
end
if map.selected and mouseButton == 2 then
city = map.selected
end
end
function deleteModal.mousepressed( x, y, mouseButton, istouch, presses )
if map.selected then
map.selected:delete()
end
if y < t.menuHeight then
deleteModal.button.lit = false
deleteModal:stop()
return button.mousepressed( x, y, mouseButton, istouch, presses )
end
end
function numberModal:start( field )
self.field = field
return modal.start( self )
end
function numberModal.keypressed( key, code, isrepeat )
if code == "backspace" then
local text = tostring( city[numberModal.field] )
-- get the byte offset to the last UTF-8 character in the string.
local byteoffset = utf8.offset(text, -1)
print( "textmodal: backspace", byteoffset )
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).
local newstr = text:sub( 1, byteoffset - 1)
if newstr == "" then newstr = 0 end
city[numberModal.field] = tonumber( newstr ) or city[numberModal.field]
editButtons[numberModal.field].name = city[numberModal.field]
end
end
if code == "escape" or code == "return" then
return numberModal:stop()
end
end
function numberModal.textinput( char )
local str = tostring( city[ numberModal.field ] )
local plus = str..char
print( "text input: ", char )
if tonumber( plus ) then
city[ numberModal.field ] = plus
editButtons[ numberModal.field ].name = plus
end
end
function textModal.textinput( char )
print( "text input: ", char )
city[textModal.field] = city[textModal.field] .. char
editButtons[textModal.field].name = city[textModal.field]
end
function textModal.mousepressed()
end
function textModal:stop()
self.field = nil
return modal.stop( self )
end
function textModal:start( field )
self.field = field
return modal.start( self )
end
function textModal.keypressed( key, code, isRepeat )
if code == "backspace" then
local text = city[textModal.field]
-- get the byte offset to the last UTF-8 character in the string.
local byteoffset = utf8.offset(text, -1)
print( "textmodal: backspace", byteoffset )
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).
city[textModal.field] = text:sub( 1, byteoffset - 1)
editButtons[textModal.field].name = city[textModal.field]
end
end
if code == "escape" or code == "return" then
return textModal:stop()
end
end
function selectModal.keypressed( key, code, isRepeat )
if code == 'escape' then return selectModal:stop() end
if modal.previous then return modal.previous.keypressed( key, code, isRepeat ) end
end
function selectModal:stop()
local mode = selectModal.mode
if mode then
mode.button.lit = false
end
return modal.stop( self )
end
function selectModal.mousepressed( x, y, mouseButton, istouch, presses )
if y < t.menuHeight then
selectModal:stop()
return button.mousepressed( x, y, mouseButton, istouch, presses )
end
if map.selected then
city = map.selected
return selectModal.mode:start()
end
end
function t.setMenuHeight( h )
t.menuHeight = h
end
return t

View File

@ -1,3 +0,0 @@
local t = {}
return t

View File

@ -1,186 +0,0 @@
local love = assert( love )
local button = require 'ui.button'
local modal = require 'ui.modal'
local camera = require 'ui.camera'
local map = require 'map.map'
local t = { menuHeight = 200 }
local loadImg = love.graphics.newImage
local layers = {
{ name = "AF", layer = "africa" , menu = require 'ui.menu.territory' },
{ name = "EU", layer = "europe" , menu = require 'ui.menu.territory' },
{ name = "NA", layer = "northamerica" , menu = require 'ui.menu.territory' },
{ name = "SA", layer = "southamerica" , menu = require 'ui.menu.territory' },
{ name = "AS", layer = "southasia" , menu = require 'ui.menu.territory' },
{ name = "RU", layer = "russia" , menu = require 'ui.menu.territory' },
{ name = "PATH", layer = "travelnodes" , menu = require 'ui.menu.travelnodes', icon = loadImg( "icons/layer-travelnodes.png" )},
{ name = "AI", layer = "ainodes" , menu = require 'ui.menu.ainodes', icon = loadImg( "icons/layer-ainodes.png" )},
{ name = "CITY", layer = "cities" , menu = require 'ui.menu.cities', icon = loadImg( "icons/layer-cities.png" )},
{ name = "COAST", layer = "coastlines" , menu = require 'ui.menu.lines', icon = loadImg( "icons/layer-coastlines.png" )},
{ name = "LOW", layer = "coastlinesLow", menu = require 'ui.menu.lines', icon = loadImg( "icons/layer-coastlines-low.png" )},
{ name = "INT", layer = "international", menu = require 'ui.menu.lines', icon = loadImg( "icons/layer-international.png" )},
{ name = "SAIL", layer = "sailable" , menu = require 'ui.menu.territory', icon = loadImg( "icons/layer-sailable.png" )},
}
button.new{
name = "LOAD", x = 250, y = 0,
group = t,
callback = require( 'ui.loadmodal' ).start,
icon = love.graphics.newImage( "icons/load.png" )}
button.new{
name = "SAVE", x = 250, y = 28,
group = t,
callback = require( 'ui.savemodal' ).start,
icon = love.graphics.newImage( "icons/save.png" )}
button.new{
name = "UNDO", x = 250, y = 2 * 28,
group = t,
callback = map.undo,
icon = love.graphics.newImage( "icons/undo.bmp" ) }
local editButtons = {}
local layerButtons = {}
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 activeLayerButton
local function back( self )
print( "back button clicked" )
if activeLayerButton then
activeLayerButton.lit = false
end
activeLayerButton = nil
map.setEditLayer()
modal.exitAll()
button.displayGroup( t, false, true )
for i, b in ipairs( editButtons ) do
b.visible = true
end
for i, b in ipairs( showButtons ) do
b.visible = true
end
self.visible = false
return updateVisibilityIcons()
end
local backButton = button.new{
name = "UP",
visible = false,
y = 5 * 28,
x = 250,
group = false,
icon = love.graphics.newImage( "icons/up.bmp" ),
callback = back,
}
local function editLayer( self )
back( backButton )
self.lit = true
map.setEditLayer( self.layer )
activeLayerButton = self
if self.menu then
button.displayGroup( self.menu )
end
backButton.visible = true
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 x = 250
for i = 1, #layers do
layers[i].menu.menuHeight = t.menuHeight
editButtons[i] = button.new( copy( i, {
y = 3 * 28,
x = x + (button.h + 4) * ( i - 1 ),
w = 24,
callback = editLayer,
group = editButtons,
tooltip = "edit "..layers[i].layer
}))
layerButtons[ 2 * i - 2 ] = editButtons[i]
showButtons[i] = button.new( copy( i, {
y = 4 * 28,
x = x + (button.h + 4) * ( i - 1 ),
w = 24,
name = "",
callback = toggleVisibleLayer,
icon = visibilityIcon,
group = showButtons,
tooltip = "show "..layers[i].layer
}))
layerButtons[ 2 * i - 1 ] = showButtons[i]
end
function t.draw()
--Status bar.
love.graphics.setScissor( 0, 0, 250, t.menuHeight )
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() - 60
love.graphics.setColor( 0, 0, 0, 1 )
love.graphics.rectangle( "fill", 0, 0, 250, love.graphics.getHeight() )
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.print(([[
SCREEN %-12d %-12d
WORLD %-12.2f%-12.2f
BITMAP %-12d %-12d
%s
%s]]):format(
x, y,
wx, wy,
bx, by,
button.selected and button.selected.tooltip or "",
map.editLayer and map.editLayer.filename or ""), 0, 0)
if map.selected then love.graphics.print( map.selected:formatDisplayInfo(), 0, 80 ) end
if map.selectionLocked then end
love.graphics.setScissor( 250, 0, love.graphics.getWidth() - 250, t.menuHeight)
love.graphics.rectangle( "line", 0, 0 , 250, t.menuHeight )
love.graphics.rectangle( "line", 250, 0, love.graphics.getWidth() - 250, t.menuHeight )
love.graphics.rectangle( "line", 250, 0, button.w, t.menuHeight )
love.graphics.setColor( 1, 1, 1, 0.8 )
button:draw()
love.graphics.setColor( 1, 0, 0, 0.4 )
end
do --button visibility
button.displayGroup( t, false, true )
button.displayGroup( editButtons, false, false )
button.displayGroup( layerButtons, false, false )
button.displayGroup( showButtons, false, false )
end
return t

View File

@ -1,3 +0,0 @@
local t = {}
return t

View File

@ -1,3 +0,0 @@
local t = {}
return t

View File

@ -1,61 +0,0 @@
--Modal for setting save options.
local love = assert( love )
local modal = require( "ui.modal" )
local button = require( "ui.button" )
local map = require( "map.map" )
local save = require( "ui.saveprogress" )
local t = { w = love.graphics.getWidth(), h = 200 }
local saveLocation = false
local floppy = love.graphics.newImage( "icons/save.png" )
floppy:setFilter( "nearest", "nearest" )
local saveButton = button.new{
group = t,
name = "save",
align = "left",
callback = function() save:start() end,
visible = false,
icon = floppy,
w = 400 - button.x,
h = 64,
y = 0,
}
local xIcon = love.graphics.newImage( "icons/x.png" )
xIcon:setFilter( "nearest", "nearest" )
local cancelButton = button.new{
group = t,
name = " cancel",
align = "left",
visible = false,
icon = xIcon,
callback = function() return t:stop() end,
y = 68,
w = 400 - button.x,
h = 64,
}
function t.start()
modal.start( t )
saveLocation = saveLocation or map.path
button.selected = saveButton
saveButton.name = " save to "..saveLocation
button.displayGroup( t, false, true )
end
function t.draw()
love.graphics.clear( 0,0,0,1 )
love.graphics.setColor( 1, 1, 1, 0.9 )
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 )

View File

@ -1,36 +0,0 @@
--What to display when saving is in progress.
local love = assert( love )
local modal = require( "ui.modal" )
local button = require( "ui.button" )
local map = require( "map.map" )
local timer = love.timer
local time = 0
local t = { w = 400, h = 200 }
local progressMessage = ""
function t.start()
time = timer.getTime()
progressMessage = ""
return modal.start( t )
end
function t.update( dt )
local msg = map.save()
if not msg then return t:stop() end
progressMessage = msg
end
function t.draw()
love.graphics.push( "all" )
love.graphics.setCanvas()
love.graphics.setShader()
love.graphics.setScissor( 0, 0, t.w, t.h )
love.graphics.clear()
love.graphics.setColor( 1, 1, 1, 1 )
love.graphics.print( timer.getTime() - time )
love.graphics.printf( progressMessage, 0, love.graphics.getFont():getHeight(), t.w, "left")
love.graphics.pop( "all" )
end
return modal.new( t )

View File