Start laying down network protocol.

This commit is contained in:
wan-may 2023-09-11 19:44:36 -03:00
parent ab6502db61
commit a720ebf173
20 changed files with 445 additions and 39 deletions

3
.gitignore vendored
View File

@ -1,3 +1,2 @@
logs/ logs/
build/ build/
lib/lua

View File

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

View File

@ -1,6 +1,6 @@
return { return {
name = "Player Name", name = "Player Name",
pronoun = "they", pronoun = "they/them/their",
colour = {0.8, 0.4, 0.4, 0.7}, colour = {0.8, 0.4, 0.4, 0.7},
gamma = 0.5, gamma = 0.5,
keybinds = { keybinds = {
@ -12,4 +12,6 @@ return {
love = "q", love = "q",
hate = "e", hate = "e",
} }
serverIP = "192.168.2.15",
serverPort = 51312,
} }

28
src/client/connecting.lua Normal file
View File

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

View File

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

View File

@ -1,8 +1,8 @@
local lg = assert( love.graphics ) local lg = assert( love.graphics )
local scene = assert( require 'client.scene' ) local scene = assert( require 'client.scene' )
local socket = assert( require 'socket' )
local shared = assert( require 'shared' ) local shared = assert( require 'shared' )
local udp = socket.udp() local server = assert( require 'client.udp' )
local crepuscular = assert( require 'client.crepuscular' )
local game = {} local game = {}
local t = 0 local t = 0
@ -15,17 +15,19 @@ function game.draw()
lg.print( serverTick, 0, 25 ) lg.print( serverTick, 0, 25 )
end end
function game.onPacket( data, msg ) function game.onPacket( data )
if data then serverTick = data end if not data then return end
serverTick = data
return game.onPacket( server.receive() ) --Recurse to get all waiting packets.
end end
function game.update( dt ) function game.update( dt )
t = dt + t t = dt + t
game.onPacket( udp:receive() ) game.onPacket( server.receive() )
if t > 0.1 then if t > 0.1 then
t = 0 t = 0
tick = tick + 1 tick = tick + 1
udp:send( tostring(tick) ) assert( server.send( tostring(tick) ) )
end end
end end
@ -35,14 +37,11 @@ function game.newGame( )
end end
function game.disconnect( ) function game.disconnect( )
return scene.mainmenu() return scene.mainmenu( server.disconnect() )
end end
function game.onLoad( params ) 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 end
function game.keypressed( key, code, isRepeat ) function game.keypressed( key, code, isRepeat )

View File

@ -30,7 +30,19 @@ local function newScene( scenes, name, t )
rawset( scenes, name, t ) rawset( scenes, name, t )
end 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, return setmetatable( scene,
{__call = scene.loadScene, {__call = scene.loadScene,
__newindex = newScene __newindex = newScene
} ) } )

24
src/client/udp.lua Normal file
View File

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

View File

@ -4,11 +4,38 @@ local textInput = assert( require 'client.ui.textinput' )
local button = assert( require 'client.ui.button' ) local button = assert( require 'client.ui.button' )
local browser = {} 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{ local ti = textInput.new{
width = 300, width = 300,
length = 20, length = 20,
x = 15, x = 15,
y = 165 y = 175
} }
function browser.draw() function browser.draw()
@ -31,6 +58,7 @@ end
function browser.joinIPString( s ) function browser.joinIPString( s )
--Parse IP address and port from string. If it's valid, join the server. --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 if not s then return end
ti:clear() ti:clear()
local valid, ip, port local valid, ip, port
@ -39,6 +67,7 @@ function browser.joinIPString( s )
end end
function browser.joinIP( ip, port ) function browser.joinIP( ip, port )
print( "Joining server:", ip, port )
return scene.game{ serverIP = ip, serverPort = port } return scene.game{ serverIP = ip, serverPort = port }
end end

View File

@ -1,7 +1,7 @@
local lg = assert( love.graphics ) local lg = assert( love.graphics )
local love = assert( love ) local love = assert( love )
local scene = assert( require 'client.scene' ) 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 button = assert( require 'client.ui.button' )
local menu = assert( require 'client.ui.menu' ) local menu = assert( require 'client.ui.menu' )
@ -14,7 +14,7 @@ return menu.new(
x = 15, w = lg.getWidth(), y = 115, h = 72, x = 15, w = lg.getWidth(), y = 115, h = 72,
text = strings.newgame_button, text = strings.newgame_button,
color = { 0.6, 0.6, 0.6, 0.9 }, color = { 0.6, 0.6, 0.6, 0.9 },
callback = function() return scene.game() end }, callback = function() return scene.connecting() end },
button{ button{
text = strings.join_button, text = strings.join_button,
@ -47,5 +47,5 @@ return menu.new(
{ 0, 1, 0, 1, 0.4, 0.05, 0.05, 0.1 }, { 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 )
) )

View File

@ -1,7 +1,7 @@
local lg = assert( love.graphics ) local lg = assert( love.graphics )
local love = assert( love ) local love = assert( love )
local scene = assert( require 'client.scene' ) 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 button = assert( require 'client.ui.button' )
local menu = assert( require 'client.ui.menu' ) local menu = assert( require 'client.ui.menu' )
@ -58,5 +58,5 @@ return menu.new(
{ 0, 1, 0, 1, 0, 0, 0, 0.01 }, { 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 )
) )

View File

@ -4,12 +4,13 @@ local love = assert( love )
function love.load() function love.load()
love.window.setIcon( assert( love.image.newImageData( "client/assets/client-icon.png" ) ) ) 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' ) local scenes = assert( require 'client.scene' )
assert( require 'client.ui.options' ) assert( require 'client.ui.options' )
assert( require 'client.ui.browser' ) assert( require 'client.ui.browser' )
assert( require 'client.game' ) assert( require 'client.game' )
assert( require 'client.ui.mainmenu' ) assert( require 'client.ui.mainmenu' )
assert( require 'client.connecting' )
scenes.loadScene( scenes.mainmenu ) scenes.loadScene( scenes.mainmenu )
end end

20
src/metaserver.lua Normal file
View File

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

View File

@ -1,18 +1,24 @@
package.path = package.path .. "."
local shared = assert( require 'shared' ) local shared = assert( require 'shared' )
local packet = shared.packet
local socket = assert( require 'socket' ) local socket = assert( require 'socket' )
local udp = socket.udp() local udp
local io = assert( io ) local io = assert( io )
local server = {
tick = 0, local svInfo = { version = 13,
portNumber = 51312, players = 0,
logFile = assert( io.open( "../logs/dajjal"..os.time()..".txt", "a" )), capacity = 255,
serverName = "dajjal-server", 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 = {} local clients = {}
do --[[do
local _print = print local _print = print
function server.Print(...) function server.Print(...)
_print( ... ) _print( ... )
@ -20,7 +26,7 @@ do
server.logFile:flush() server.logFile:flush()
end end
end end
local print = server.Print local print = server.Print]]
--Developer convenience function: start the local client program. --Developer convenience function: start the local client program.
@ -28,20 +34,32 @@ function server.StartLocalClient()
os.execute( "start vision.bat" ) os.execute( "start vision.bat" )
end end
function server.Advertise()
udp:sendto( packet.get( packet.serverInfo( svInfo ) ) , msIP, msPort )
end
--Incoming packet. --Incoming packet.
function server.Parse( packet, ip, port ) function server.Parse( msg, ip, port )
if not packet then return end if not msg then return end
if not clients[ip] then if not clients[ip] then
clients[ip] = { ip = ip, port = port } clients[ip] = { ip = ip, port = port }
end 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 end
function server.Start() function server.Start()
udp = assert( socket.udp() )
udp:settimeout(0) udp:settimeout(0)
udp:setsockname('*', server.portNumber) server.SetIP( socket.dns.toip(socket.dns.gethostname()), 51312 )
print( "Starting Server" ) assert( udp:setsockname( tostring( svInfo.ip ), svInfo.port ))
server.StartLocalClient() server.StartLocalClient()
repeat repeat
server.Parse( udp:receivefrom() ) server.Parse( udp:receivefrom() )

19
src/server/client.lua Normal file
View File

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

View File

@ -1,5 +1,8 @@
local shared = {} local shared = {}
shared.ip = assert( require 'shared.ipstring' )
shared.packet = assert( require 'shared.packet' )
--World state. --World state.
local world = {} local world = {}

30
src/shared/ipstring.lua Normal file
View File

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

178
src/shared/packet.lua Normal file
View File

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