Start laying down network protocol.
This commit is contained in:
parent
ab6502db61
commit
a720ebf173
|
@ -1,3 +1,2 @@
|
|||
logs/
|
||||
build/
|
||||
lib/lua
|
||||
build/
|
|
@ -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
|
|
@ -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,
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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 )
|
||||
|
|
|
@ -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
|
||||
} )
|
||||
__newindex = newScene
|
||||
} )
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 )
|
||||
)
|
|
@ -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 )
|
||||
)
|
|
@ -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
|
||||
|
|
|
@ -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) )
|
|
@ -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() )
|
||||
|
|
|
@ -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
|
|
@ -1,5 +1,8 @@
|
|||
local shared = {}
|
||||
|
||||
shared.ip = assert( require 'shared.ipstring' )
|
||||
shared.packet = assert( require 'shared.packet' )
|
||||
|
||||
|
||||
--World state.
|
||||
local world = {}
|
||||
|
|
|
@ -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
|
|
@ -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
|
Loading…
Reference in New Issue