diff --git a/.gitignore b/.gitignore index bd406d6..ba060fe 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ logs/ -build/ -lib/lua \ No newline at end of file +build/ \ No newline at end of file diff --git a/src/client/assets/Montserrat-Bold.ttf b/src/client/assets/fonts/Montserrat-Bold.ttf similarity index 100% rename from src/client/assets/Montserrat-Bold.ttf rename to src/client/assets/fonts/Montserrat-Bold.ttf diff --git a/src/client/assets/glsl/crepuscular b/src/client/assets/glsl/crepuscular new file mode 100644 index 0000000..48d9a6a --- /dev/null +++ b/src/client/assets/glsl/crepuscular @@ -0,0 +1,21 @@ +#pragma language glsl3 +varying float hue; +uniform float time; + +#ifdef VERTEX +uniform mat4 proj; +uniform mat4 view; +uniform sampler2D canvas; + +vec4 position(mat4 transform_projection, vec4 vertex_position) +{ + return vertex_position; +} +#endif +#ifdef PIXEL + +vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) +{ + return color; +} +#endif \ No newline at end of file diff --git a/src/client/assets/strings.lua b/src/client/assets/strings/english.lua similarity index 100% rename from src/client/assets/strings.lua rename to src/client/assets/strings/english.lua diff --git a/src/client/config.lua b/src/client/config.lua index e7495d9..fe4145b 100644 --- a/src/client/config.lua +++ b/src/client/config.lua @@ -1,6 +1,6 @@ return { name = "Player Name", - pronoun = "they", + pronoun = "they/them/their", colour = {0.8, 0.4, 0.4, 0.7}, gamma = 0.5, keybinds = { @@ -12,4 +12,6 @@ return { love = "q", hate = "e", } + serverIP = "192.168.2.15", + serverPort = 51312, } \ No newline at end of file diff --git a/src/client/connecting.lua b/src/client/connecting.lua new file mode 100644 index 0000000..562524e --- /dev/null +++ b/src/client/connecting.lua @@ -0,0 +1,28 @@ +local scene = assert( require 'client.scene' ) +local lg = assert( love.graphics ) +local server = assert( require 'client.udp' ) +local connecting = {} + +local time, ip, port = 0 + +function connecting.draw() + lg.print( "Connecting to server:\t"..time, 0, 0, 0 ) + return false +end + +function connecting.update(dt) + time = time + dt + + if time > 5 then + return scene.loadScene( scene.game ) + end + return false +end + +function connecting:onLoad( params ) + local ip, port = assert( params, "No IP address specified!" ).ip, params.port + return server.connect( ip, port ) +end + +scene.connecting = connecting +return connecting \ No newline at end of file diff --git a/src/client/crepuscular.lua b/src/client/crepuscular.lua new file mode 100644 index 0000000..33fbb55 --- /dev/null +++ b/src/client/crepuscular.lua @@ -0,0 +1,23 @@ +local crepuscular = {} +local lg = assert( love.graphics ) +local shader = assert( lg.newShader( 'client/assets/glsl/crepuscular' )) +local scene = assert( require( 'client.scene' ) ) +local rectanglePosition = { } + +function crepuscular.draw() + +end + +function crepuscular.onLoad() + +end + +function crepuscular.resize() + +end + +function crepuscular.update() + +end + +return crepuscular \ No newline at end of file diff --git a/src/client/game.lua b/src/client/game.lua index da493ac..075d7e4 100644 --- a/src/client/game.lua +++ b/src/client/game.lua @@ -1,8 +1,8 @@ local lg = assert( love.graphics ) local scene = assert( require 'client.scene' ) -local socket = assert( require 'socket' ) local shared = assert( require 'shared' ) -local udp = socket.udp() +local server = assert( require 'client.udp' ) +local crepuscular = assert( require 'client.crepuscular' ) local game = {} local t = 0 @@ -15,17 +15,19 @@ function game.draw() lg.print( serverTick, 0, 25 ) end -function game.onPacket( data, msg ) - if data then serverTick = data end +function game.onPacket( data ) + if not data then return end + serverTick = data + return game.onPacket( server.receive() ) --Recurse to get all waiting packets. end function game.update( dt ) t = dt + t - game.onPacket( udp:receive() ) + game.onPacket( server.receive() ) if t > 0.1 then t = 0 tick = tick + 1 - udp:send( tostring(tick) ) + assert( server.send( tostring(tick) ) ) end end @@ -35,14 +37,11 @@ function game.newGame( ) end function game.disconnect( ) - return scene.mainmenu() + return scene.mainmenu( server.disconnect() ) end function game.onLoad( params ) - params = params or {} - local serverIP, serverPort = params.serverIP or "192.168.2.15", params.serverPort or 51312 - udp:settimeout( 0 ) - udp:setpeername( serverIP, serverPort ) + end function game.keypressed( key, code, isRepeat ) diff --git a/src/client/scene.lua b/src/client/scene.lua index 030d208..53959b1 100644 --- a/src/client/scene.lua +++ b/src/client/scene.lua @@ -30,7 +30,19 @@ local function newScene( scenes, name, t ) rawset( scenes, name, t ) end +function scene.overlayScene( scen, params ) + print( "Adding Scene:", scen.name ) + for k, v in pairs( callbacks ) do + local old = love[k] + local new = scen[k] + if new then + love[k] = function( ... ) return new( ... ) and old( ... ) end + end + end + scen:onLoad( params ) +end + return setmetatable( scene, {__call = scene.loadScene, - __newindex = newScene - } ) \ No newline at end of file + __newindex = newScene + } ) \ No newline at end of file diff --git a/src/client/udp.lua b/src/client/udp.lua new file mode 100644 index 0000000..929b13b --- /dev/null +++ b/src/client/udp.lua @@ -0,0 +1,24 @@ +local socket = assert( require 'socket' ) + +local udp = {} + +local cxn = assert( socket.udp() ) +cxn:settimeout( 0 ) + +function udp.receive() + return cxn:receive() +end + +function udp.connect( ip, port ) + assert( cxn:setpeername( ip, port ) ) +end + +function udp.disconnect( ) + +end + +function udp.send( s ) + return cxn:send( s ) +end + +return udp \ No newline at end of file diff --git a/src/client/ui/browser.lua b/src/client/ui/browser.lua index b89840e..d6093c3 100644 --- a/src/client/ui/browser.lua +++ b/src/client/ui/browser.lua @@ -4,11 +4,38 @@ local textInput = assert( require 'client.ui.textinput' ) local button = assert( require 'client.ui.button' ) local browser = {} +local serverList = { + selected = false, + x = 25, + y = 160, + h = 24, + { name = "test", ip = "192.168.2.150", port = 51312, players = 1, capacity = 64, map = "testMap" }, + { name = "best", ip = "142.154.3.212", port = 21345, players = 2, capacity = 64, map = "nestMap" }, + { name = "aest", ip = "123.45.67.89", port = 21253, players = 3, capacity = 32, map = "aestMap" }, + } + +function serverList.draw() + + +end + +function serverList.select() + +end + +function serverList.up() + serverList.selected = ( serverList.selected or 0 ) - 1 +end + +function serverList.down() + serverList.selected = ( serverList.selected or 0 ) + 1 +end + local ti = textInput.new{ width = 300, length = 20, x = 15, - y = 165 + y = 175 } function browser.draw() @@ -31,6 +58,7 @@ end function browser.joinIPString( s ) --Parse IP address and port from string. If it's valid, join the server. + print( "browser: Attempting to join server", s ) if not s then return end ti:clear() local valid, ip, port @@ -39,6 +67,7 @@ function browser.joinIPString( s ) end function browser.joinIP( ip, port ) + print( "Joining server:", ip, port ) return scene.game{ serverIP = ip, serverPort = port } end diff --git a/src/client/ui/mainmenu.lua b/src/client/ui/mainmenu.lua index 9b181ad..649878f 100644 --- a/src/client/ui/mainmenu.lua +++ b/src/client/ui/mainmenu.lua @@ -1,7 +1,7 @@ local lg = assert( love.graphics ) local love = assert( love ) local scene = assert( require 'client.scene' ) -local strings = assert( require 'client.assets.strings' ) +local strings = strings or assert( require 'client.assets.strings.english' ) local button = assert( require 'client.ui.button' ) local menu = assert( require 'client.ui.menu' ) @@ -14,7 +14,7 @@ return menu.new( x = 15, w = lg.getWidth(), y = 115, h = 72, text = strings.newgame_button, color = { 0.6, 0.6, 0.6, 0.9 }, - callback = function() return scene.game() end }, + callback = function() return scene.connecting() end }, button{ text = strings.join_button, @@ -47,5 +47,5 @@ return menu.new( { 0, 1, 0, 1, 0.4, 0.05, 0.05, 0.1 }, }, - lg.newFont( "client/assets/Montserrat-Bold.ttf", 48 ) + lg.newFont( "client/assets/fonts/Montserrat-Bold.ttf", 48 ) ) \ No newline at end of file diff --git a/src/client/ui/options.lua b/src/client/ui/options.lua index 33a0c17..2f83b31 100644 --- a/src/client/ui/options.lua +++ b/src/client/ui/options.lua @@ -1,7 +1,7 @@ local lg = assert( love.graphics ) local love = assert( love ) local scene = assert( require 'client.scene' ) -local strings = assert( require 'client.assets.strings' ) +local strings = strings or assert( require 'client.assets.strings.english' ) local button = assert( require 'client.ui.button' ) local menu = assert( require 'client.ui.menu' ) @@ -58,5 +58,5 @@ return menu.new( { 0, 1, 0, 1, 0, 0, 0, 0.01 }, }, - lg.newFont( "client/assets/Montserrat-Bold.ttf", 18 ) + lg.newFont( "client/assets/fonts/Montserrat-Bold.ttf", 18 ) ) \ No newline at end of file diff --git a/src/main.lua b/src/main.lua index f0fe5d2..2a00ecc 100644 --- a/src/main.lua +++ b/src/main.lua @@ -4,12 +4,13 @@ local love = assert( love ) function love.load() love.window.setIcon( assert( love.image.newImageData( "client/assets/client-icon.png" ) ) ) - love.graphics.setNewFont( "client/assets/Montserrat-Bold.ttf", 48 ) + love.graphics.setNewFont( "client/assets/fonts/Montserrat-Bold.ttf", 48 ) local scenes = assert( require 'client.scene' ) assert( require 'client.ui.options' ) assert( require 'client.ui.browser' ) assert( require 'client.game' ) assert( require 'client.ui.mainmenu' ) + assert( require 'client.connecting' ) scenes.loadScene( scenes.mainmenu ) end diff --git a/src/metaserver.lua b/src/metaserver.lua new file mode 100644 index 0000000..71040c9 --- /dev/null +++ b/src/metaserver.lua @@ -0,0 +1,20 @@ +local shared = assert( require 'shared' ) +local socket = assert( require 'socket' ) +local udp = assert( socket.udp() ) + +--Servers broadcast their information here. +--The metaserver builds a list of available servers. ( available meaning, "broadcasted in last ten heartbeats" ) +--Clients ask the metaserver for this list ( maybe with some filter? ) + +local serverInfo = {} +local serverIPs = {} +local clientIPs = {} + +local function Parse( packet, ip, port ) + +end + +print( "Starting Metaserver", socket.gettime() ) +repeat + +until socket.sleep( 2.0 - (socket.gettime() % 2.0) ) \ No newline at end of file diff --git a/src/server.lua b/src/server.lua index 33310a0..c79c455 100644 --- a/src/server.lua +++ b/src/server.lua @@ -1,18 +1,24 @@ -package.path = package.path .. "." local shared = assert( require 'shared' ) +local packet = shared.packet local socket = assert( require 'socket' ) -local udp = socket.udp() +local udp local io = assert( io ) -local server = { - tick = 0, - portNumber = 51312, - logFile = assert( io.open( "../logs/dajjal"..os.time()..".txt", "a" )), - serverName = "dajjal-server", -} + +local svInfo = { version = 13, + players = 0, + capacity = 255, + ip = shared.ip.fromString( socket.dns.toip(socket.dns.gethostname()) ), + port = 51312, + svname = "New Server", + map = "Test Map"} + +local server = { tick = 0 } + +local msIP, msPort local clients = {} -do +--[[do local _print = print function server.Print(...) _print( ... ) @@ -20,7 +26,7 @@ do server.logFile:flush() end end -local print = server.Print +local print = server.Print]] --Developer convenience function: start the local client program. @@ -28,20 +34,32 @@ function server.StartLocalClient() os.execute( "start vision.bat" ) end +function server.Advertise() + udp:sendto( packet.get( packet.serverInfo( svInfo ) ) , msIP, msPort ) +end + --Incoming packet. -function server.Parse( packet, ip, port ) - if not packet then return end +function server.Parse( msg, ip, port ) + if not msg then return end if not clients[ip] then clients[ip] = { ip = ip, port = port } end - clients[ip].tick = packet - return server.Parse( udp:receivefrom() ) -- Process any packets we missed. + + local msgs = packet.deserialise( msg ) + print( "in: ", ip, port, msg, server.tick ) + return server.Parse( udp:receivefrom() ) -- Process other packets. +end + +function server.SetIP( ipString, port ) + svInfo.ip = shared.ip.fromString( ipString ) + svInfo.port = port end function server.Start() + udp = assert( socket.udp() ) udp:settimeout(0) - udp:setsockname('*', server.portNumber) - print( "Starting Server" ) + server.SetIP( socket.dns.toip(socket.dns.gethostname()), 51312 ) + assert( udp:setsockname( tostring( svInfo.ip ), svInfo.port )) server.StartLocalClient() repeat server.Parse( udp:receivefrom() ) diff --git a/src/server/client.lua b/src/server/client.lua new file mode 100644 index 0000000..b787557 --- /dev/null +++ b/src/server/client.lua @@ -0,0 +1,19 @@ +local client = {} + +function client.connect( ip, port ) + +end + +function client.challenge( ) + +end + +function client.acceptConnection( ) + +end + +function client.disconnect( ) + +end + +return \ No newline at end of file diff --git a/src/shared.lua b/src/shared.lua index 3583780..ad1728a 100644 --- a/src/shared.lua +++ b/src/shared.lua @@ -1,5 +1,8 @@ local shared = {} +shared.ip = assert( require 'shared.ipstring' ) +shared.packet = assert( require 'shared.packet' ) + --World state. local world = {} diff --git a/src/shared/ipstring.lua b/src/shared/ipstring.lua new file mode 100644 index 0000000..f775402 --- /dev/null +++ b/src/shared/ipstring.lua @@ -0,0 +1,30 @@ +local ffi = assert( require 'ffi' ) +local ipString = {} +local string = assert( string ) +ffi.cdef[[ + typedef struct { + uint8_t a; + uint8_t b; + uint8_t c; + uint8_t d; + } ipAddress; +]] + +local ipAddress = ffi.typeof( ffi.new( "ipAddress" ) ) +ffi.metatype( ipAddress, { __tostring = function( ip ) return ip.a.."."..ip.b.."."..ip.c.."."..ip.d end } ) + +function ipString.new( t ) + local ip = ffi.new( ipAddress ) + ip.a, ip.b, ip.c, ip.d = t[1], t[2], t[3], t[4] + return ip +end + +function ipString.fromString( s ) + local t = {} + for octet in s:gmatch( '%d+' ) do + table.insert( t, tonumber( octet ) ) + end + return ipString.new( t ) +end + +return ipString \ No newline at end of file diff --git a/src/shared/packet.lua b/src/shared/packet.lua new file mode 100644 index 0000000..b58c528 --- /dev/null +++ b/src/shared/packet.lua @@ -0,0 +1,178 @@ +local ffi = assert( require 'ffi' ) +local buffer = assert( require( "string.buffer" ) ) +local ipString = assert( require 'shared.ipstring' ) +local packet = {} +local mt = { __index = { new = function( self ) return ffi.new( self.ct ) end } } + +local function newStruct( t ) + assert( not( packet[ t.name ] or packet[ t.netname ] )) + packet[ t.name ] = t + packet[ t.netname ] = t + ffi.cdef( ("typedef struct {uint8_t netname;\n%s;\n} %s;"):format( table.concat( t, ";\n" ) , t.name ) ) + t.ct = ffi.typeof( ffi.new( t.name ) ) + t.size = ffi.sizeof( t.ct ) + setmetatable( t, mt ) + + --print( "Packet:", t.name, "Members:", #t + 1, "Size:", t.size, "Alignment:", ffi.alignof( t.ct ) ) +end + +newStruct{ + name = "serverInfo", + netname = 42, + "uint8_t players", + "uint8_t capacity", + "ipAddress ip", + "uint16_t version", + "uint16_t port", + "char svname[32]", + "char map[32]", +} + +newStruct{ + name = "requestServers", + netname = 123, + "char padding[999]" +} + +newStruct{ + name = "heartbeat", + netname = 69, + "uint16_t protocol", + "uint32_t tick", + "uint32_t hash", +} + +newStruct{ + name = "insect", + netname = 81, + "uint8_t id", + "bool dead", + "int8_t hp", + "int8_t vx", + "int8_t vy", + "uint8_t folio", + "uint32_t x", + "uint32_t y", +} + +newStruct{ + name = "soleil", + netname = 27, + "uint16_t azimuth", + "uint16_t altitude", + "int8_t vazi", + "int8_t valt", +} + +newStruct{ + name = "playerChange", + netname = 72, + "uint8_t id", + "uint8_t role", + "char username[31]", +} + +newStruct{ + name = "chatMessage", + netname = 54, + "char cmsg[127]", +} + +newStruct{ + name = "command", + netname = 32, + "char command", +} + +local readBuffer = buffer.new( 1024 ) +function packet.deserialise( str ) + readBuffer:set( str ) + local data = {} + local types = {} + local n = 0 + while #readBuffer ~= 0 do + local netname = readBuffer:ref()[0] --Read a byte to determine the packet type. + local t = packet[ netname ] + if not t then + error( "Malformed packet. Unknown header:\n"..readBuffer:get() ) + end + if #readBuffer < t.size then + error( "Malformed packet. Packet too small:\n"..readBuffer:get() ) + end --Malformed packets might cause an overread. + + --Allocate new struct and copy into it. + n = n + 1 + data[n] = ffi.new( t.ct ) + types[n] = t.name + ffi.copy( data[n], readBuffer:ref(), t.size ) + readBuffer:skip( t.size ) + end + + return data, types +end + +function packet.getString( member ) + return ffi.string( member, ffi.sizeof( member ) ) +end + +local writeBuffer = buffer.new( 1024 ) +function packet.add( struct, data ) + local str = ffi.new( struct.ct, data ) + str.netname = assert( struct.netname ) + writeBuffer:putcdata( str, struct.size ) + return writeBuffer +end + +function packet.get() + return writeBuffer:get() +end + +mt.__call = packet.add + +local testing = true +--TESTS-- +if testing then + + packet.serverInfo{ + players = 0, + capacity = 255, + map = "abcdefghijklmnopqrstuvwxyz1234567890", + svname = "๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜๐Ÿ˜˜kissyfaceserver", + version = 25, + port = 51312, + ip = ipString.new{ 132, 213, 45, 21 } + } + + packet.heartbeat{ + tick = 49, + hash = 33753745832876, + protocol = 25 + } + + packet.insect{ + id = 5, + dead = true, + hp = -3, + vx = -5, + vy = 47, + x = 59183, + y = 21412 + } + + local d, t = packet.deserialise( packet.get() ) + assert( #writeBuffer == 0, "Test failed. Write buffer not empty!" ) + for i = 1, #d do + print( "", t[i], d[i] ) + if t[i] == 'serverInfo' then + print( "", "", packet.getString( d[i].map ), packet.getString( d[i].svname ) ) + end + if t[i] == 'insect' then print( d[i].vx ) end + end + + packet[42]{} + + assert( not( pcall( packet.deserialise, "grgrsgs" ) ), "Test failed. Failed to reject malformed packet." ) +end +--END TESTS-- + +return packet \ No newline at end of file