diff --git a/data/graphics/blur.bmp b/data/graphics/blur.bmp index 0c1628b..cc815ad 100644 Binary files a/data/graphics/blur.bmp and b/data/graphics/blur.bmp differ diff --git a/main.lua b/main.lua index dbf65ef..c8a34d0 100644 --- a/main.lua +++ b/main.lua @@ -20,6 +20,10 @@ function love.directorydropped( path ) return map.load( path ) end +function love.filedropped( path ) + +end + function love.update( dt ) local tx, ty = 0, 0 local moveCamera = false diff --git a/map/blur.lua b/map/blur.lua new file mode 100644 index 0000000..08d2247 --- /dev/null +++ b/map/blur.lua @@ -0,0 +1,113 @@ +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 \ No newline at end of file diff --git a/map/bmp.lua b/map/bmp.lua index 0db1ba7..4b712d2 100644 --- a/map/bmp.lua +++ b/map/bmp.lua @@ -16,6 +16,7 @@ function test.load( filename ) print( "LOADING BITMAP: ", filename, imgd:getSize(), imgd:getFormat(), imgd:getDimensions() ) local img = love.graphics.newImage( imgd ) img:setFilter( "nearest", "nearest" ) + img:setWrap( "repeat", "clampzero" ) return img, imgd end @@ -103,6 +104,16 @@ local formats = { w = 512, 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( ) @@ -316,11 +327,24 @@ function formats.africa:encode( data ) return table.concat( bytes ) 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 ) 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" ) + img:setWrap( "repeat", "clampzero" ) return img, imgd end diff --git a/map/map.lua b/map/map.lua index 81c8d6c..f398cdf 100644 --- a/map/map.lua +++ b/map/map.lua @@ -1,5 +1,6 @@ local love = assert( love ) local io = io +local coroutine = coroutine local mkdir = assert( require 'lib.mkdir' ) local lg = love.graphics local AI = require 'map.ai' @@ -8,6 +9,7 @@ local Lines = require 'map.lines' local Nodes = require 'map.travelNodes' local Camera = require 'ui.camera' local Territory = require 'map.territory' +local Blur = require 'map.blur' --flat list of editable layers for convenience local layers = { @@ -24,6 +26,7 @@ local layers = { sailable = false, ainodes = false, cities = false, + blur = false, } local map = { @@ -50,9 +53,28 @@ local map = { travelnodes = false, sailable = 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 ) map.background = lg.newImage( "/data/graphics/blur.bmp" ) map.cities = Cities.load( "/data/earth/cities.dat" ) @@ -62,6 +84,7 @@ function map.load( path ) 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" ) + map.blur = Blur.load( "/data/graphics/blur.bmp", map ) for k, v in pairs(map.territory) do map.territory[k] = Territory.load( "/data/earth/"..k..".bmp", k ) end @@ -85,31 +108,31 @@ function map.draw() lg.setBlendMode( "add" ) lg.setColor( 1, 1, 1, 0.2 ) - lg.draw( map.background ) + --lg.draw( map.background ) 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 if v.visible then + v:draw() - lg.setLineWidth( 1 / Camera.zoom ) + --[[lg.setLineWidth( 1 / Camera.zoom ) v:drawBorder( "land" ) lg.setLineWidth( 3 / Camera.zoom ) - v:drawBorder( "sea" ) + v:drawBorder( "sea" )]] end end if map.sailable.visible then + sh:send( "lowBorder", 20 ) + sh:send( "highBorder", 60 ) + lg.setShader( require 'shaders.sailable' ) 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 + lg.setShader() lg.setBlendMode( "alpha" ) - lg.setColor( 1, 1, 1, 1 ) end @@ -182,33 +205,42 @@ function map.draw() end -local function write( filename, string ) - print( "Writing", string:len(), "bytes to", filename ) - 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() - --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 - assert( mkdir.exists( map.path ), map.path ) - local path = map.path..folder - if not mkdir.exists( path ) then mkdir.mkdir( path ) end +do + local function write( filename, string ) + 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 - 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 - files[ map.path..tostring( layer.filename ) ] = assert( layer:save() ) - end - for filename, str in pairs( files ) do - write( filename, str ) + 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 function map.hover(x, y) diff --git a/map/saveoptions.lua b/map/saveoptions.lua new file mode 100644 index 0000000..3c5c2ce --- /dev/null +++ b/map/saveoptions.lua @@ -0,0 +1,19 @@ +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 \ No newline at end of file diff --git a/map/territory.lua b/map/territory.lua index 58786e6..c58523d 100644 --- a/map/territory.lua +++ b/map/territory.lua @@ -144,7 +144,6 @@ end 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 diff --git a/map/travelNodes.lua b/map/travelNodes.lua index 63d0aaa..9b6d541 100644 --- a/map/travelNodes.lua +++ b/map/travelNodes.lua @@ -57,7 +57,7 @@ end function t.load( filename, sailable ) - isSailable = sailable + isSailable = sailable or isSailable local img, imgd = bmp.load( filename ) local nodes = { filename = filename, visible = true, nodes = {}, points = {}, connections = {}, img = img } t.nodes = nodes diff --git a/mod.txt b/mod.txt new file mode 100644 index 0000000..7acc703 --- /dev/null +++ b/mod.txt @@ -0,0 +1,5 @@ +Name New DEFCON Mod +Version 0.1 +Author DeFacto +Website https://wan-may.art/dev/ +Comment DEFCON map made with dcEarth \ No newline at end of file diff --git a/screenshot.bmp b/screenshot.bmp new file mode 100644 index 0000000..40d96de Binary files /dev/null and b/screenshot.bmp differ diff --git a/shaders/dilate.lua b/shaders/dilate.lua new file mode 100644 index 0000000..f751b96 --- /dev/null +++ b/shaders/dilate.lua @@ -0,0 +1,46 @@ +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 ); + } +]] \ No newline at end of file diff --git a/shaders/sailable.lua b/shaders/sailable.lua new file mode 100644 index 0000000..13654ad --- /dev/null +++ b/shaders/sailable.lua @@ -0,0 +1,36 @@ +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 ); + } +]] \ No newline at end of file diff --git a/shaders/territory.lua b/shaders/territory.lua new file mode 100644 index 0000000..ed5fccb --- /dev/null +++ b/shaders/territory.lua @@ -0,0 +1,54 @@ +--[[ + 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 ); + } +]] \ No newline at end of file diff --git a/ui/loadmodal.lua b/ui/loadmodal.lua index be75b8b..aae1dc1 100644 --- a/ui/loadmodal.lua +++ b/ui/loadmodal.lua @@ -57,5 +57,9 @@ function t.directorydropped( path ) return love.filesystem.mount( path, "" ) end +function t.filedropped( path ) + return map.reloadLayer( path ) +end + return modal.new( t ) \ No newline at end of file diff --git a/ui/menu/cities.lua b/ui/menu/cities.lua index d8023cb..f14676d 100644 --- a/ui/menu/cities.lua +++ b/ui/menu/cities.lua @@ -138,17 +138,6 @@ function editModal.draw() return button:draw() end -function editModal.resize( w, h ) - local i = 0 - local high = h / 6 - for key, b in pairs( editButtons ) do - b.x = w / 2 - button.w / 2 - b.y = ( high + 4 ) * i - b.h = high - i = i + 1 - end -end - function moveModal.update( dt ) local x, y = love.mouse.getPosition() if y > t.menuHeight and love.mouse.isDown( 1 ) then diff --git a/ui/modal.lua b/ui/modal.lua index 18ea5b9..c5e485e 100644 --- a/ui/modal.lua +++ b/ui/modal.lua @@ -6,6 +6,7 @@ t.__index = t local i = 0 function t.start( self ) print( "starting modal:", i + 1) + love.graphics.push( "all" ) love.graphics.setScissor( self.x or 0, self.y or 0, @@ -55,7 +56,7 @@ function t.stop( self ) i = i - 1 t.previous = t[i - 1] - love.graphics.setScissor(0, 0, love.graphics.getDimensions()) + love.graphics.pop( "all" ) end function t.exitAll() diff --git a/ui/savemodal.lua b/ui/savemodal.lua index 780676d..751c9dd 100644 --- a/ui/savemodal.lua +++ b/ui/savemodal.lua @@ -1,8 +1,10 @@ +--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 t = {} +local save = require( "ui.saveprogress" ) +local t = { w = love.graphics.getWidth(), h = 200 } local saveLocation = false local floppy = love.graphics.newImage( "icons/save.png" ) @@ -10,40 +12,40 @@ floppy:setFilter( "nearest", "nearest" ) local saveButton = button.new{ group = t, name = "save", - callback = function() map.save(); return t:stop() end, + align = "left", + callback = function() save:start() end, visible = false, icon = floppy, - x = love.graphics.getWidth() / 2 - 300, - y = love.graphics.getHeight() / 2 - 150, - w = 600, - h = 100, + 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", + name = " cancel", + align = "left", 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 + 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 + 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, 0, 0, 0.4 ) + love.graphics.setColor( 1, 1, 1, 0.9 ) button:draw() end @@ -51,7 +53,7 @@ end function t.directorydropped( path ) saveLocation = path map.path = path - saveButton.name = "save to "..map.path + saveButton.name = " save to "..map.path return love.filesystem.mount( path, "" ) end diff --git a/ui/saveprogress.lua b/ui/saveprogress.lua new file mode 100644 index 0000000..3f6f60d --- /dev/null +++ b/ui/saveprogress.lua @@ -0,0 +1,36 @@ +--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 ) \ No newline at end of file