starting over
This commit is contained in:
parent
077b075267
commit
ddaf61c52c
|
|
@ -1,21 +0,0 @@
|
||||||
#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
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
|
|
@ -1,68 +0,0 @@
|
||||||
local config
|
|
||||||
local lfs = assert( love ).filesystem
|
|
||||||
local cfg = lfs.newFile "cfg.lua"
|
|
||||||
|
|
||||||
local mt = { __tostring = function( t )
|
|
||||||
local arr = { "tblOption{\n ",}
|
|
||||||
for k, v in pairs( t ) do
|
|
||||||
arr[#arr + 1] = k
|
|
||||||
arr[#arr + 1] = " = "
|
|
||||||
arr[#arr + 1] = tostring( v )
|
|
||||||
arr[#arr + 1] = ",\n "
|
|
||||||
end
|
|
||||||
arr[#arr + 1] = "}"
|
|
||||||
return table.concat( arr )
|
|
||||||
end }
|
|
||||||
local function tblOption(t)
|
|
||||||
return setmetatable( t, mt )
|
|
||||||
end
|
|
||||||
|
|
||||||
local defaultConfig = {
|
|
||||||
plName = "Player Name",
|
|
||||||
plPronoun = tblOption{
|
|
||||||
subject = "they",
|
|
||||||
object = "them",
|
|
||||||
possessive = "their",
|
|
||||||
},
|
|
||||||
plColour = tblOption{ 0.5, 0.6, 0.7, 0.5 },
|
|
||||||
gamma = 0.5,
|
|
||||||
keybinds = tblOption{
|
|
||||||
Forward = "w",
|
|
||||||
Back = "s",
|
|
||||||
Left = "a",
|
|
||||||
Right = "d",
|
|
||||||
Chat = "t",
|
|
||||||
Love = "q",
|
|
||||||
Hate = "e",
|
|
||||||
},
|
|
||||||
serverIP = "192.168.2.15",
|
|
||||||
serverPort = 51312,
|
|
||||||
logFile = "log.txt",
|
|
||||||
}
|
|
||||||
|
|
||||||
local function readConfigFile()
|
|
||||||
local ok, cfgTbl, err = pcall( lfs.load, 'cfg.lua' )
|
|
||||||
if ok and cfgTbl then
|
|
||||||
local ok, err = pcall( cfgTbl )
|
|
||||||
if err then print( "Failed to load settings file: ", err ) end
|
|
||||||
return
|
|
||||||
end
|
|
||||||
return defaultConfig
|
|
||||||
end
|
|
||||||
|
|
||||||
local function dumpConfigFile()
|
|
||||||
print( "Saving Settings." )
|
|
||||||
assert( cfg:open 'w' )
|
|
||||||
cfg:write( "return {\n " )
|
|
||||||
for k, v in pairs( config ) do
|
|
||||||
cfg:write( k )
|
|
||||||
cfg:write( " = " )
|
|
||||||
cfg:write( tostring( v ) )
|
|
||||||
cfg:write( ",\n" )
|
|
||||||
end
|
|
||||||
cfg:write( "}" )
|
|
||||||
end
|
|
||||||
|
|
||||||
config = readConfigFile()
|
|
||||||
|
|
||||||
return config
|
|
||||||
|
|
@ -1,90 +0,0 @@
|
||||||
local scene = assert( require 'scene' )
|
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local server = assert( require 'udp' )
|
|
||||||
local button = assert( require 'ui.button' )
|
|
||||||
local strings = assert( require 'strings' )
|
|
||||||
local connecting = {}
|
|
||||||
|
|
||||||
local time, ip, port, attempts, svInfo = 0, 0, 0, 1
|
|
||||||
|
|
||||||
local cancelButton = button{
|
|
||||||
x = lg.getWidth() / 4,
|
|
||||||
y = lg.getHeight() / 2,
|
|
||||||
w = lg.getWidth() / 2,
|
|
||||||
h = 100,
|
|
||||||
text = lg.newText( lg.getFont(), strings.cancel_button ),
|
|
||||||
}
|
|
||||||
|
|
||||||
function connecting.mousemoved( x, y )
|
|
||||||
cancelButton.selected = cancelButton:contains( x, y )
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.mousepressed( x, y )
|
|
||||||
if cancelButton:contains( x, y ) then return scene.browser() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.keypressed( x, y )
|
|
||||||
return scene.browser()
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.draw()
|
|
||||||
lg.setColor( 1,1,1,1 )
|
|
||||||
lg.printf( ("ADDRESS: %s"):format( ip ), lg.getWidth() / 4, 15, lg.getWidth() - 30, "left" )
|
|
||||||
lg.printf( ("ATTEMPTS: %d"):format( attempts ), lg.getWidth() / 4, 20 + lg.getFont():getHeight(), lg.getWidth() - 30, "left" )
|
|
||||||
cancelButton:draw()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.svChimo( msg )
|
|
||||||
return server.answerChallenge( msg.nonce )
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.connected( msg )
|
|
||||||
server.setToken( msg.token )
|
|
||||||
return scene.game()
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.keypressed(key, code, isrepeat)
|
|
||||||
if code == "escape" then return scene.browser() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.read( msg )
|
|
||||||
if not msg then return false end
|
|
||||||
local msgs, types = server.deserialise( msg )
|
|
||||||
for i = 1, #msgs do
|
|
||||||
( connecting[ types[i] ] or print )( msgs[i], ip, port )
|
|
||||||
end
|
|
||||||
return connecting.read( server.receive() )
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.update(dt)
|
|
||||||
time = time + dt
|
|
||||||
|
|
||||||
if time > 2 then
|
|
||||||
time = 0
|
|
||||||
attempts = attempts + 1
|
|
||||||
server.connect( ip, port )
|
|
||||||
--return scene.loadScene( scene.game )
|
|
||||||
end
|
|
||||||
|
|
||||||
return connecting.read( server.receive() )
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting:onLoad( params )
|
|
||||||
lg.setCanvas()
|
|
||||||
time = 0
|
|
||||||
attempts = 1
|
|
||||||
params = params or { ip = "8.8.8.8", port = 8 }
|
|
||||||
ip, port = params.ip, params.port
|
|
||||||
if server.isValid( ip, port ) then
|
|
||||||
server.setInfo( params.svInfo )
|
|
||||||
return server.connect( ip, port )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function connecting.exit()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
scene.connecting = connecting
|
|
||||||
return connecting
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
local crepuscular = {}
|
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local shader = assert( lg.newShader( 'assets/glsl/crepuscular' ))
|
|
||||||
local scene = assert( require( 'scene' ) )
|
|
||||||
local rectanglePosition = { }
|
|
||||||
|
|
||||||
function crepuscular.draw()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function crepuscular.onLoad()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function crepuscular.resize()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function crepuscular.update()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return crepuscular
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local scene = assert( require 'scene' )
|
|
||||||
local shared = assert( require 'shared.shared' )
|
|
||||||
local server = assert( require 'udp' )
|
|
||||||
local packet = shared.packet
|
|
||||||
local crepuscular = assert( require 'crepuscular' )
|
|
||||||
|
|
||||||
local game = {}
|
|
||||||
local handlers = setmetatable( {}, {__index = function() return print end } )
|
|
||||||
local t = 0
|
|
||||||
local tick = 0
|
|
||||||
local serverTick = 0
|
|
||||||
|
|
||||||
function handlers.connected( data )
|
|
||||||
serverTick = math.max( data.tick, serverTick )
|
|
||||||
tick = math.max( serverTick, tick )
|
|
||||||
end
|
|
||||||
|
|
||||||
function handlers.insect( data )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function handlers.soleil( data )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function handlers.playerChange( data )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function handlers.svInfo( data )
|
|
||||||
if server.svInfo.map ~= data.map then end
|
|
||||||
return server.setInfo( data )
|
|
||||||
end
|
|
||||||
|
|
||||||
function handlers.chatMessage( msg )
|
|
||||||
print( msg.cmsg )
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.draw()
|
|
||||||
lg.setColor( 1, 1, 1, 1 )
|
|
||||||
lg.print( "Client Tick: "..tick, 0, 0 )
|
|
||||||
lg.print( "Server Tick: "..serverTick, 0, 25 )
|
|
||||||
lg.print( "Current Map: "..packet.getString(server.svInfo.map), 0, 50 )
|
|
||||||
lg.print( "Server Name: "..packet.getString(server.svInfo.svname), 0, 75 )
|
|
||||||
lg.print( "# Connected: "..server.svInfo.players, 0, 100 )
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.onPacket( msg )
|
|
||||||
if not msg or (#msg < 1) then return end
|
|
||||||
local msgs, types = packet.deserialise( msg )
|
|
||||||
if not msgs then
|
|
||||||
print( "malformed packet:", types )
|
|
||||||
return game.onPacket( server.receive() )
|
|
||||||
end
|
|
||||||
for i = 1, #msgs do
|
|
||||||
--Handler returns something if msg should be discarded.
|
|
||||||
if handlers[ types[i] ]( msgs[i] ) then break end
|
|
||||||
end
|
|
||||||
return game.onPacket( server.receive() )
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.update( dt )
|
|
||||||
t = dt + t
|
|
||||||
game.onPacket( server.receive() )
|
|
||||||
if t > 0.1 then
|
|
||||||
t = 0
|
|
||||||
tick = tick + 1
|
|
||||||
server.newPacket( tick )
|
|
||||||
assert( server.send() )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.newGame( )
|
|
||||||
game.curWorld = shared.NewWorld() --Last world state received from server.
|
|
||||||
game.preWorld = shared.NewWorld() --Current world state predicted by this client.
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.disconnect( )
|
|
||||||
return scene.mainmenu( server.disconnect( tick ) )
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.onLoad( params )
|
|
||||||
end
|
|
||||||
|
|
||||||
function game.keypressed( key, code, isRepeat )
|
|
||||||
if code == "escape" then return game.disconnect() end
|
|
||||||
end
|
|
||||||
|
|
||||||
scene.game = game
|
|
||||||
return game
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
package.cpath = "..\\?.dll;..\\clib\\?.dll;"..package.cpath
|
|
||||||
package.path = "..\\?.lua;..\\lualib\\?.lua;"..package.path
|
|
||||||
local shared = assert( require 'shared.shared' )
|
local shared = assert( require 'shared.shared' )
|
||||||
local love = assert( love )
|
local love = assert( love )
|
||||||
|
|
||||||
|
|
||||||
function love.load()
|
function love.load()
|
||||||
|
package.path = "./client/?.lua"..package.path
|
||||||
print( "Client Started." )
|
print( "Client Started." )
|
||||||
assert( require 'config' )
|
assert( require 'config' )
|
||||||
|
|
||||||
--Crash if running luajit 2.1 and coconut missing
|
--Crash if running luajit 2.1 and coconut missing
|
||||||
--[[loadstring( love.data.decode( "string", "base64","G0xKAgrbAQAACgALABY2AAAAOQABADkAAgAnAgMAJwMEADYEAAA5BAEEOQQFBCcGBgA2BwAAOQcHBzkHCAcnCQkAQgcCAEEEAQBBAAICBgAKAFgAAoArAAEAWAEBgCsAAgBMAAIALWQ4MmY3M2RkNjQ1MDcxNDZiNTkwNTMwYjg0NDcwMWZlMmJmYjdjZTkeY2xpZW50L2Fzc2V0cy9jb2NvbnV0LnBuZxFuZXdJbWFnZURhdGEKaW1hZ2UJc2hhMQloYXNoCGhleAtzdHJpbmcLZW5jb2RlCWRhdGEJbG92ZV0BAAUABQAONgAAADMCAQBCAAICDgAAAFgBB4A2AAIANAIAADUDAwA2BAAAPQQEA0IAAwJCAAECMgAAgEwAAgALX19jYWxsAQAAEXNldG1ldGF0YWJsZQAKcGNhbGwA" ))()]]
|
--[[loadstring( love.data.decode( "string", "base64","G0xKAgrbAQAACgALABY2AAAAOQABADkAAgAnAgMAJwMEADYEAAA5BAEEOQQFBCcGBgA2BwAAOQcHBzkHCAcnCQkAQgcCAEEEAQBBAAICBgAKAFgAAoArAAEAWAEBgCsAAgBMAAIALWQ4MmY3M2RkNjQ1MDcxNDZiNTkwNTMwYjg0NDcwMWZlMmJmYjdjZTkeY2xpZW50L2Fzc2V0cy9jb2NvbnV0LnBuZxFuZXdJbWFnZURhdGEKaW1hZ2UJc2hhMQloYXNoCGhleAtzdHJpbmcLZW5jb2RlCWRhdGEJbG92ZV0BAAUABQAONgAAADMCAQBCAAICDgAAAFgBB4A2AAIANAIAADUDAwA2BAAAPQQEA0IAAwJCAAECMgAAgEwAAgALX19jYWxsAQAAEXNldG1ldGF0YWJsZQAKcGNhbGwA" ))()]]
|
||||||
|
|
||||||
love.window.setIcon( assert( love.image.newImageData( "assets/client-icon.png" ) ) )
|
love.window.setIcon( assert( love.image.newImageData( "client/assets/client-icon.png" ) ) )
|
||||||
love.graphics.setNewFont( "assets/fonts/Montserrat-Bold.ttf", 48 )
|
love.graphics.setNewFont( "client/assets/fonts/Montserrat-Bold.ttf", 48 )
|
||||||
local scenes = assert( require 'scene' )
|
local scenes = assert( require 'scene' )
|
||||||
|
|
||||||
assert( require 'ui.options' )
|
assert( require 'ui.options' )
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
local love = assert( love )
|
|
||||||
local lg = love.graphics
|
|
||||||
local time = 0
|
|
||||||
local text = ""
|
|
||||||
|
|
||||||
--Set up the canvas.
|
|
||||||
assert( lg.getCanvasFormats( false ).stencil8, "Error: 8-bit stencil buffers not supported on this system." )
|
|
||||||
local stencil = love.graphics.newCanvas(
|
|
||||||
lg.getWidth(),
|
|
||||||
lg.getHeight(),
|
|
||||||
{
|
|
||||||
type = "2d",
|
|
||||||
format = "stencil8",
|
|
||||||
readable = false,
|
|
||||||
msaa = 0,
|
|
||||||
dpiscale = 1,
|
|
||||||
mipmaps = "none",
|
|
||||||
})
|
|
||||||
|
|
||||||
local canvas = love.graphics.newCanvas(
|
|
||||||
lg.getWidth(),
|
|
||||||
lg.getHeight(),
|
|
||||||
{
|
|
||||||
type = "2d",
|
|
||||||
format = "rgba8",
|
|
||||||
readable = true,
|
|
||||||
msaa = 0,
|
|
||||||
dpiscale = 1,
|
|
||||||
mipmaps = "none",
|
|
||||||
})
|
|
||||||
|
|
||||||
function love.update(dt)
|
|
||||||
time = time + dt
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.draw()
|
|
||||||
lg.setCanvas{{canvas}, depthstencil = {stencil}}
|
|
||||||
lg.clear()
|
|
||||||
lg.setColor( 1,1,1,0.3 )
|
|
||||||
lg.rectangle( "fill", time * 15.0 , 0, 400, 400 )
|
|
||||||
lg.setCanvas()
|
|
||||||
lg.draw( canvas )
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.textinput(t)
|
|
||||||
text = text..t
|
|
||||||
end
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
local scene = {}
|
|
||||||
local love = assert( love )
|
|
||||||
|
|
||||||
local callbacks = {
|
|
||||||
draw = true,
|
|
||||||
mousepressed = true,
|
|
||||||
mousemoved = true,
|
|
||||||
keypressed = true,
|
|
||||||
update = true,
|
|
||||||
resize = true,
|
|
||||||
wheelmoved = true,
|
|
||||||
}
|
|
||||||
|
|
||||||
local mt = {}
|
|
||||||
|
|
||||||
function scene.loadScene( scen, params )
|
|
||||||
print( "Loading Scene:", scen.name )
|
|
||||||
for k, v in pairs( callbacks ) do
|
|
||||||
if not scen[k] then print( "Warning: scene missing callback.", scen.name, k ) end
|
|
||||||
love[k] = scen[k] or love[k]
|
|
||||||
end
|
|
||||||
scen:onLoad( params )
|
|
||||||
end
|
|
||||||
|
|
||||||
local function newScene( scenes, name, t )
|
|
||||||
t.name = t.name or name
|
|
||||||
print( "Adding Scene:", t.name )
|
|
||||||
setmetatable( t, { __call = function() return scene.loadScene( t ) end } )
|
|
||||||
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
|
|
||||||
} )
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
--TODO: check config option for current language, dynamically change it?
|
|
||||||
local utf8 = assert( require 'utf8' )
|
|
||||||
return require 'client.assets.strings.english'
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
local packet = assert( require 'shared.packet' )
|
|
||||||
local ipString = assert( require 'shared.ipstring' )
|
|
||||||
local utf8 = assert( require 'utf8' )
|
|
||||||
local r = math.random
|
|
||||||
local rand = function() return r( 1, 255 ) end
|
|
||||||
local rs = function()
|
|
||||||
local t = {}
|
|
||||||
for i = 1, r( 0, 64 ) do t[i] = rand() end
|
|
||||||
return string.char( unpack( t ) )
|
|
||||||
end
|
|
||||||
local rutf8 = function()
|
|
||||||
local t = {}
|
|
||||||
for i = 1, r( 1, 16 ) do t[i] = r( 0x40, 0x70 ) end
|
|
||||||
|
|
||||||
local s = utf8.char( unpack( t ) )
|
|
||||||
return s
|
|
||||||
end
|
|
||||||
local randserver = function()
|
|
||||||
local str = rutf8
|
|
||||||
return packet.serverInfo{
|
|
||||||
players = rand(),
|
|
||||||
capacity = rand(),
|
|
||||||
ip = ipString.new{ rand(), rand(), rand(), rand() },
|
|
||||||
port = r( 1, 65535 ),
|
|
||||||
version = 25,
|
|
||||||
svname = str(),
|
|
||||||
map = str(),
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
return {
|
|
||||||
|
|
||||||
--Simulate getting server info from the metaserver.
|
|
||||||
getTestServers = function()
|
|
||||||
packet.get()
|
|
||||||
for i = 1, r( 1, 125 ) do randserver() end
|
|
||||||
return packet.deserialise( packet.get() )
|
|
||||||
end
|
|
||||||
}
|
|
||||||
|
|
@ -1,82 +0,0 @@
|
||||||
local socket = assert( require 'socket' )
|
|
||||||
local ms = assert( require 'shared.metaserver' )
|
|
||||||
local config = assert( require 'config' )
|
|
||||||
|
|
||||||
local udp = { svInfo = {}, token = false, }
|
|
||||||
local packet = assert( require 'shared.packet' )
|
|
||||||
local hash = assert( require 'shared.hash' )
|
|
||||||
|
|
||||||
local cxn = assert( socket.udp() )
|
|
||||||
local mscxn = assert( socket.udp() )
|
|
||||||
cxn:settimeout( 0 )
|
|
||||||
mscxn:settimeout( 0 )
|
|
||||||
assert(mscxn:setpeername( ms.ip, ms.port ))
|
|
||||||
|
|
||||||
function udp.receive()
|
|
||||||
return cxn:receive()
|
|
||||||
end
|
|
||||||
|
|
||||||
udp.deserialise = packet.deserialise
|
|
||||||
|
|
||||||
function udp.receiveMeta()
|
|
||||||
return mscxn:receive()
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.requestServerList()
|
|
||||||
print( "Requesting server list." )
|
|
||||||
packet.get()
|
|
||||||
packet.metaServer()
|
|
||||||
packet.clientInfo()
|
|
||||||
return mscxn:send( packet.get() )
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.newPacket( tick )
|
|
||||||
if udp.token then packet.connected{ token = udp.token, tick = tick or 0 } end
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.setToken( token )
|
|
||||||
print( "Setting server token:", token )
|
|
||||||
udp.token = token
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.setInfo( svInfo )
|
|
||||||
udp.svInfo = svInfo
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.answerChallenge( svNonce )
|
|
||||||
local clNonce = hash.rand()
|
|
||||||
packet.get()
|
|
||||||
packet.clChimo{ nonce = clNonce, hash = hash.hash( clNonce, svNonce ) }
|
|
||||||
print( "Received authentication nonce. Reply:", clNonce, svNonce )
|
|
||||||
return udp.send()
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.isValid( ip, port )
|
|
||||||
local s, e = socket.udp()
|
|
||||||
if s then s, e = s:setpeername( ip, port ) end
|
|
||||||
if s then return true
|
|
||||||
else return nil, e end --temporary socket, gc it, we just want the error
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.connect( ip, port )
|
|
||||||
assert( cxn:setpeername( ip, port ) )
|
|
||||||
print( "Connection request to:", ip, port )
|
|
||||||
return udp.send( packet.clientInfo{ username = config.plName })
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.disconnect( tick )
|
|
||||||
for i = 1, 10 do
|
|
||||||
udp.newPacket( tick )
|
|
||||||
packet.disconnect()
|
|
||||||
udp.send()
|
|
||||||
end
|
|
||||||
udp.setToken()
|
|
||||||
cxn = assert( socket.udp() )
|
|
||||||
cxn:settimeout( 0 )
|
|
||||||
end
|
|
||||||
|
|
||||||
function udp.send()
|
|
||||||
return cxn:send( packet.get() )
|
|
||||||
end
|
|
||||||
|
|
||||||
return udp
|
|
||||||
|
|
@ -1,247 +0,0 @@
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local scene = assert( require 'scene' )
|
|
||||||
local textInput = assert( require 'ui.textinput' )
|
|
||||||
local button = assert( require 'ui.button' )
|
|
||||||
local packet = assert( require 'shared.packet' )
|
|
||||||
local menu = assert( require 'ui.menu' )
|
|
||||||
local strings = assert( require 'strings' )
|
|
||||||
local fonts = assert( require 'ui.fonts' )
|
|
||||||
local metaserver = assert ( require 'udp' )
|
|
||||||
local utf8 = assert( require 'utf8' )
|
|
||||||
|
|
||||||
local browser = { latest = 0, }
|
|
||||||
|
|
||||||
local test = assert( require 'test.browser' )
|
|
||||||
|
|
||||||
local font = fonts.font
|
|
||||||
local cw = fonts.font:getWidth( "w" )
|
|
||||||
|
|
||||||
local function joinServerCallback( button )
|
|
||||||
if button.ip and button.port then
|
|
||||||
return browser.joinIP( button.ip, button.port, button.serverInfo )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function tryAdd( text, d, x )
|
|
||||||
local s = packet.getString( d )
|
|
||||||
return pcall( text.add, text, s, x ) or text:add( strings.utf8_error, x )
|
|
||||||
end
|
|
||||||
|
|
||||||
local function serverInfoToText( server )
|
|
||||||
local cw = fonts.font:getWidth( "w" )
|
|
||||||
local text = lg.newText( fonts.font )
|
|
||||||
tryAdd( text, server.svname, 0 )
|
|
||||||
tryAdd( text, server.map, cw * 16 )
|
|
||||||
text:add( tostring( server.ip ), cw * 32 )
|
|
||||||
text:add( server.port, cw * ( 32 + 12 ) )
|
|
||||||
text:add( server.players, cw * ( 32 + 12 + 6 ) )
|
|
||||||
text:add( server.capacity, cw * ( 32 + 12 + 9 ) )
|
|
||||||
return text
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
local serverList = menu.new{
|
|
||||||
name = "serverList",
|
|
||||||
buttons = {},
|
|
||||||
fg = lg.newMesh{
|
|
||||||
{ 0.5, 0, 0.5, 0, 0, 0, 0, 0 },
|
|
||||||
{ 1, 0, 1, 0, 1, 1, 1, 0.5 },
|
|
||||||
{ 1, 1, 1, 1, 1, 1, 1, 0.5 },
|
|
||||||
{ 0.5, 1, 0.5, 1, 0, 0, 0, 0 },
|
|
||||||
},
|
|
||||||
bg = lg.newMesh{
|
|
||||||
{ 0, 0, 0, 0, 0.4, 0.05, 0.0, 0.9 },
|
|
||||||
{ 1, 0, 1, 0, 0.8, 0.3, 0.1, 0.8 },
|
|
||||||
{ 1, 1, 1, 1, 0.7, 0.4, 0.1, 0.8 },
|
|
||||||
{ 0, 1, 0, 1, 0.4, 0.05, 0.05, 0.9 },
|
|
||||||
},
|
|
||||||
|
|
||||||
font = fonts.font,
|
|
||||||
subScene = true }
|
|
||||||
serverList.selected = false
|
|
||||||
serverList.x = 25
|
|
||||||
serverList.y = 0
|
|
||||||
serverList.h = 36
|
|
||||||
serverList.ips = {}
|
|
||||||
local serverButtons = serverList.buttons
|
|
||||||
local color = { 1, 0.6, 0.6, 0.1 }
|
|
||||||
local ti = textInput.new{
|
|
||||||
width = lg.getWidth(),
|
|
||||||
length = 20,
|
|
||||||
x = cw,
|
|
||||||
y = 35,
|
|
||||||
h = 55,
|
|
||||||
str = strings.ip_button,
|
|
||||||
font = fonts.midFont,
|
|
||||||
}
|
|
||||||
function ti:callback() return self:enterText( browser.joinIPString ) end
|
|
||||||
local headerButtons = {
|
|
||||||
|
|
||||||
button{
|
|
||||||
callback = function() serverList.clear() return serverList.requestServers() end,
|
|
||||||
text = lg.newText( fonts.midFont, strings.refresh_button ) ,
|
|
||||||
color = color,
|
|
||||||
x = cw * 32,
|
|
||||||
y = 75,
|
|
||||||
w = 1400,
|
|
||||||
h = 36
|
|
||||||
},
|
|
||||||
|
|
||||||
button{
|
|
||||||
callback = function() return scene.mainmenu() end,
|
|
||||||
text = lg.newText( fonts.midFont, strings.mainmenu_button ) ,
|
|
||||||
color = color,
|
|
||||||
x = cw,
|
|
||||||
y = 75,
|
|
||||||
w = 1400,
|
|
||||||
},
|
|
||||||
|
|
||||||
ti,
|
|
||||||
|
|
||||||
button{ x = cw * 53, color = color, y = 135, text = lg.newText( font, strings.svinfo_capacity ) },
|
|
||||||
button{ x = cw * 50, color = color, y = 135, text = lg.newText( font, strings.svinfo_players ) },
|
|
||||||
button{ x = cw * 44, color = color, y = 135, text = lg.newText( font, strings.svinfo_port ) },
|
|
||||||
button{ x = cw * 32, color = color, y = 135, text = lg.newText( font, strings.svinfo_ip ) },
|
|
||||||
button{ x = cw * 16, color = color, y = 135, text = lg.newText( font, strings.svinfo_map ) },
|
|
||||||
button{ x = cw , color = color, y = 135, text = lg.newText( font, strings.svinfo_name ) },
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
for j, headerButton in ipairs( headerButtons ) do
|
|
||||||
serverButtons[j] = headerButton
|
|
||||||
end
|
|
||||||
|
|
||||||
function serverList.requestServers()
|
|
||||||
return metaserver.requestServerList()
|
|
||||||
end
|
|
||||||
|
|
||||||
function serverList.clear( )
|
|
||||||
for i = #headerButtons + 1, #serverButtons do serverButtons[i] = nil end
|
|
||||||
for ip in pairs( serverList.ips ) do serverList.ips[ip] = nil end
|
|
||||||
end
|
|
||||||
|
|
||||||
function serverList.add( serverInfo )
|
|
||||||
local y = 27 * ( #serverButtons - #headerButtons ) + 180
|
|
||||||
local ip = tostring( serverInfo.ip )
|
|
||||||
serverButtons[ #serverButtons + 1] = button{
|
|
||||||
space = 0,
|
|
||||||
x = cw,
|
|
||||||
w = lg.getWidth(),
|
|
||||||
y = y,
|
|
||||||
h = 24,
|
|
||||||
color = { 0.3 + 0.1 * (#serverButtons % 2), 0.3 + 0.1 * (#serverButtons % 2), 0.8, 0.3 },
|
|
||||||
callback = joinServerCallback,
|
|
||||||
serverInfo = serverInfo,
|
|
||||||
ip = ip,
|
|
||||||
port = serverInfo.port,
|
|
||||||
text = serverInfoToText( serverInfo ),
|
|
||||||
active = ( y < lg.getHeight() )}
|
|
||||||
serverList.ips[ ip ] = true
|
|
||||||
return serverList:paint()
|
|
||||||
end
|
|
||||||
|
|
||||||
local metaServerHandlers = setmetatable(
|
|
||||||
{
|
|
||||||
default = function() end,
|
|
||||||
serverInfo = serverList.add,
|
|
||||||
},
|
|
||||||
{__index = function( t ) return t.default end })
|
|
||||||
|
|
||||||
function serverList.scroll( up )
|
|
||||||
local minY = 170
|
|
||||||
local maxY = lg.getHeight() + 40
|
|
||||||
if #serverButtons > #headerButtons then
|
|
||||||
if up and serverButtons[ #headerButtons + 1 ].y > minY then return end
|
|
||||||
if ( not up ) and serverButtons[ #serverButtons ].y < maxY then return end
|
|
||||||
|
|
||||||
up = ( 27 / 3 ) * ( up and 1 or -1 )
|
|
||||||
for i = #headerButtons + 1, #serverButtons do
|
|
||||||
local sb = serverButtons[i]
|
|
||||||
sb.y = sb.y + up
|
|
||||||
sb.active = ( sb.y > minY ) and ( sb.y < maxY )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return serverList:paint()
|
|
||||||
end
|
|
||||||
|
|
||||||
browser.selected = false
|
|
||||||
|
|
||||||
function browser.draw()
|
|
||||||
lg.setColor( 1, 1, 1, 1 )
|
|
||||||
serverList.draw()
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.update( dt )
|
|
||||||
local p = metaserver.receiveMeta()
|
|
||||||
if not p then return end
|
|
||||||
print( "Receiving server list:", p )
|
|
||||||
local msgs, types = packet.deserialise( p )
|
|
||||||
for i = 1, #msgs do metaServerHandlers[types[i]]( msgs[i] ) end
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.onLoad( )
|
|
||||||
serverList:onLoad()
|
|
||||||
lg.setColor( 1, 1, 1, 1 )
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.mousemoved( x, y, dx, dy, istouch )
|
|
||||||
return serverList.mousemoved( x, y, dx, dy, istouch )
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.wheelmoved( x, y )
|
|
||||||
if y == 0 then return end
|
|
||||||
return serverList.scroll( ( y > 0 ) )
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.resize( x, y )
|
|
||||||
serverList.resize( x, y )
|
|
||||||
|
|
||||||
for i, button in ipairs( serverButtons ) do
|
|
||||||
button.w = x
|
|
||||||
end
|
|
||||||
return serverList:paint()
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.mousepressed(x, y, button, istouch, pressed)
|
|
||||||
return serverList.mousepressed( x, y, button, istouch, pressed )
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.joinIPString( s )
|
|
||||||
--Parse IP address and port from string. If it's valid, join the server.
|
|
||||||
--TODO: there should be two fields, one for IP, one for port.
|
|
||||||
--Parsing the entered address for the port is possible but more error-prone.
|
|
||||||
print( "browser: entered IP and port", s )
|
|
||||||
if not s then return end
|
|
||||||
ti:clear()
|
|
||||||
local ip, port = s:match '(%d+%.%d+%.%d+%.%d+)', s:match ':(%d+)'
|
|
||||||
print( "browser:", "ip:", ip, port )
|
|
||||||
|
|
||||||
if ip and port then return browser.joinIP( ip, port ) end
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.joinIP( ip, port, svInfo )
|
|
||||||
print( "Joining server:", ip, port )
|
|
||||||
return scene.loadScene( scene.connecting, { ip = ip, port = port, svInfo = svInfo } )
|
|
||||||
end
|
|
||||||
|
|
||||||
function browser.keypressed( key, code, isRepeat )
|
|
||||||
|
|
||||||
local y = serverList.getSelectedButton()
|
|
||||||
|
|
||||||
if code == "escape" then return scene.mainmenu() end
|
|
||||||
|
|
||||||
if y and code == "down" and y.y > lg.getHeight() - 100 then
|
|
||||||
for i = 1, 3 do serverList.scroll( false ) end
|
|
||||||
end
|
|
||||||
|
|
||||||
if y and code == "up" and y.y > 180 and y.y < 250 then
|
|
||||||
for i = 1, 3 do serverList.scroll( true ) end
|
|
||||||
end
|
|
||||||
|
|
||||||
return serverList.keypressed( key, code, isRepeat )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
scene.browser = browser
|
|
||||||
return browser
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
|
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local button = {
|
|
||||||
h = 60,
|
|
||||||
y = 0,
|
|
||||||
x = 0,
|
|
||||||
w = 100,
|
|
||||||
space = 15,
|
|
||||||
}
|
|
||||||
local mt = { __index = button }
|
|
||||||
|
|
||||||
function button:new( t )
|
|
||||||
t = t or {}
|
|
||||||
|
|
||||||
if t.y then button.y = t.y end
|
|
||||||
if t.h then button.h = t.h end
|
|
||||||
if t.w then button.w = t.w end
|
|
||||||
if t.x then button.x = t.x end
|
|
||||||
if t.space then button.space = t.space end
|
|
||||||
|
|
||||||
t.x = t.x or button.x
|
|
||||||
t.y = t.y or button.y
|
|
||||||
t.w = t.w or button.w
|
|
||||||
t.h = t.h or button.h
|
|
||||||
t.text = t.text or lg.newText( lg.getFont(), "button" )
|
|
||||||
t.color = t.color or { 0.5, 0.5, 0.5, 0.5 }
|
|
||||||
t.callback = t.callback or function() print( "Clicked button:", t.text ) end
|
|
||||||
t.selected = t.selected or false
|
|
||||||
if t.active == nil then t.active = true end
|
|
||||||
|
|
||||||
button.y = button.y + t.h + button.space
|
|
||||||
|
|
||||||
return setmetatable( t, mt )
|
|
||||||
end
|
|
||||||
|
|
||||||
function button:contains( x, y )
|
|
||||||
local mx, my, Mx, My = self.x, self.y, self.x + self.w, self.y + self.h
|
|
||||||
return self.active and (x < Mx and x > mx and y > my and y < My)
|
|
||||||
end
|
|
||||||
|
|
||||||
function button:draw( )
|
|
||||||
if not self.active then return end
|
|
||||||
lg.setColor( self.color )
|
|
||||||
lg.rectangle( "fill", self.x, self.y, self.w, self.h, 10)
|
|
||||||
|
|
||||||
|
|
||||||
if self.selected then
|
|
||||||
lg.setColor( 1, 1, 1, 0.8 )
|
|
||||||
lg.rectangle( "fill", self.x + 3, self.y + 3, self.w - 6, self.h - 6, 10 )
|
|
||||||
end
|
|
||||||
|
|
||||||
lg.setColor( 0, 0, 0, 0.8 )
|
|
||||||
lg.draw( self.text, self.x + 15, self.y + self.h / 2 - self.text:getHeight() / 2 )
|
|
||||||
lg.setColor( 0, 0, 0, 0.2 )
|
|
||||||
lg.draw( self.text, self.x + 12, self.y + self.h / 2 - self.text:getHeight() / 2 )-- + self.h / 2 )
|
|
||||||
end
|
|
||||||
|
|
||||||
return setmetatable( button, { __call = button.new } )
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
--References to font objects.
|
|
||||||
local lgnf = love.graphics.newFont
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
|
||||||
font = lgnf( "assets/fonts/Montserrat-Bold.ttf", 14 ),
|
|
||||||
midFont = lgnf( "assets/fonts/Montserrat-Bold.ttf", 24 ),
|
|
||||||
headerFont = lgnf( "assets/fonts/Montserrat-Bold.ttf", 48 )
|
|
||||||
}
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local love = assert( love )
|
|
||||||
local scene = assert( require 'scene' )
|
|
||||||
local strings = strings or assert( require 'strings' )
|
|
||||||
local button = assert( require 'ui.button' )
|
|
||||||
local menu = assert( require 'ui.menu' )
|
|
||||||
local font = assert( require 'ui.fonts').headerFont
|
|
||||||
|
|
||||||
return menu.new{
|
|
||||||
name = "mainmenu",
|
|
||||||
|
|
||||||
buttons = {
|
|
||||||
|
|
||||||
button{
|
|
||||||
x = 15, w = lg.getWidth(), y = 115, h = 72, space = 15,
|
|
||||||
text = lg.newText( font, strings.newgame_button ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.9 },
|
|
||||||
callback = function() return scene.connecting{ip = "127.0.0.0", port = 8} end },
|
|
||||||
|
|
||||||
button{
|
|
||||||
text = lg.newText( font, strings.join_button ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.9 },
|
|
||||||
callback = function() return scene.browser() end },
|
|
||||||
|
|
||||||
button{
|
|
||||||
text = lg.newText( font, strings.option_button ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.9 },
|
|
||||||
callback = function() return scene.options() end },
|
|
||||||
|
|
||||||
button{
|
|
||||||
text = lg.newText( font, strings.quit_button ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.9 },
|
|
||||||
callback = love.event.quit },
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
fg = lg.newMesh{
|
|
||||||
{ 0, 0, 0, 0, 0.4, 0.1, 0.05, 0.0 },
|
|
||||||
{ 1, 0, 1, 0, 0.8, 0.3, 0.1, 0.8 },
|
|
||||||
{ 1, 1, 1, 1, 0.7, 0.4, 0.1, 0.8 },
|
|
||||||
{ 0, 1, 0, 1, 0.4, 0.1, 0.03, 0.0 },
|
|
||||||
},
|
|
||||||
|
|
||||||
bg = lg.newMesh{
|
|
||||||
{ 0, 0, 0, 0, 1, 1, 1, 0.01 },
|
|
||||||
{ 1, 0, 1, 0, 1, 1, 1, 0.1 },
|
|
||||||
{ 1, 1, 1, 1, 0, 0, 0, 0.1 },
|
|
||||||
{ 0, 1, 0, 1, 0, 0, 0, 0.01 },
|
|
||||||
},
|
|
||||||
|
|
||||||
font = font,
|
|
||||||
|
|
||||||
wheelmoved = function( x, y ) return ( y ~= 0 ) and love.keypressed( nil, (y > 0) and "up" or "down" ) end
|
|
||||||
}
|
|
||||||
|
|
@ -1,153 +0,0 @@
|
||||||
local love = assert( love )
|
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local scene = assert( require 'scene' )
|
|
||||||
local menu = {}
|
|
||||||
|
|
||||||
--Static variables.
|
|
||||||
local selectedButtonIdx
|
|
||||||
local canvas
|
|
||||||
local wWidth, wHeight
|
|
||||||
local currentMenu
|
|
||||||
|
|
||||||
function menu.getSelectedButton()
|
|
||||||
return selectedButtonIdx and currentMenu.buttons[selectedButtonIdx]
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.selectButton( idx )
|
|
||||||
local but = currentMenu.buttons[selectedButtonIdx]
|
|
||||||
if but then but.selected = false end
|
|
||||||
selectedButtonIdx = idx
|
|
||||||
but = currentMenu.buttons[idx]
|
|
||||||
if but then but.selected = true end
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.clear()
|
|
||||||
return lg.clear( canvas )
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.new( t )
|
|
||||||
if t.subScene then setmetatable( t, t )
|
|
||||||
else
|
|
||||||
scene[t.name] = t
|
|
||||||
end
|
|
||||||
getmetatable( t ).__index = menu
|
|
||||||
return t
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu:onLoad()
|
|
||||||
print( 'Loading Menu:', self.name )
|
|
||||||
currentMenu = self
|
|
||||||
if self.font then lg.setFont( self.font ) end
|
|
||||||
return menu.resize( lg.getDimensions() )
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.update( dt ) end
|
|
||||||
|
|
||||||
function menu.resize( w, h )
|
|
||||||
wWidth, wHeight = w, h
|
|
||||||
canvas = lg.newCanvas()
|
|
||||||
return currentMenu:paint()
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.draw()
|
|
||||||
lg.setCanvas()
|
|
||||||
lg.setColor( 1,1,1,1 )
|
|
||||||
lg.draw( canvas )
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.mousemoved( x, y, dx, dy, istouch )
|
|
||||||
if not currentMenu then return end
|
|
||||||
local buttons = currentMenu.buttons
|
|
||||||
|
|
||||||
local selectedButton = buttons[selectedButtonIdx or 0]
|
|
||||||
for id, menuButton in ipairs( buttons ) do
|
|
||||||
if menuButton:contains( x, y ) then
|
|
||||||
if selectedButton then selectedButton.selected = false end
|
|
||||||
menuButton.selected = true
|
|
||||||
selectedButtonIdx = id
|
|
||||||
if menuButton ~= selectedButton then
|
|
||||||
return currentMenu:paint()
|
|
||||||
else return end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
--deselect button
|
|
||||||
if selectedButton then
|
|
||||||
selectedButtonIdx = nil
|
|
||||||
selectedButton.selected = false
|
|
||||||
return currentMenu:paint()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function menu.mousepressed( x, y, button, istouch, presses )
|
|
||||||
|
|
||||||
if not selectedButtonIdx and currentMenu then return end
|
|
||||||
local uiButton = currentMenu.buttons[selectedButtonIdx]
|
|
||||||
if uiButton:contains( x, y ) then
|
|
||||||
selectedButtonIdx = nil
|
|
||||||
uiButton.selected = false
|
|
||||||
return uiButton:callback()
|
|
||||||
end
|
|
||||||
return currentMenu:paint()
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu.keypressed( key, code, isrepeat )
|
|
||||||
|
|
||||||
assert( currentMenu )
|
|
||||||
|
|
||||||
if code == "escape" then
|
|
||||||
return love.event.quit()
|
|
||||||
end
|
|
||||||
|
|
||||||
local buttons = currentMenu.buttons
|
|
||||||
|
|
||||||
if code == "return" and selectedButtonIdx then
|
|
||||||
local button = buttons[selectedButtonIdx]
|
|
||||||
selectedButtonIdx = nil
|
|
||||||
button.selected = false
|
|
||||||
currentMenu:paint()
|
|
||||||
return button:callback()
|
|
||||||
end
|
|
||||||
|
|
||||||
if #buttons > 0 and (code == "down" or code == "tab" or code == "up") then
|
|
||||||
repeat
|
|
||||||
local sbi = (selectedButtonIdx or 1)
|
|
||||||
if buttons[sbi] then buttons[sbi].selected = false end
|
|
||||||
|
|
||||||
--Increment / decrement
|
|
||||||
|
|
||||||
sbi = sbi + ((code == "up") and -1 or 1)
|
|
||||||
if #buttons < 1 then selectedButtonIdx = false; return end
|
|
||||||
if sbi > #buttons then sbi = 1 end
|
|
||||||
if sbi < 1 then sbi = #buttons end
|
|
||||||
|
|
||||||
selectedButtonIdx = sbi
|
|
||||||
buttons[selectedButtonIdx].selected = true
|
|
||||||
until buttons[selectedButtonIdx].active --Skip deactivated buttons.
|
|
||||||
print( "Selected button: ", selectedButtonIdx )
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return currentMenu:paint()
|
|
||||||
end
|
|
||||||
|
|
||||||
function menu:paint()
|
|
||||||
lg.setCanvas( canvas )
|
|
||||||
|
|
||||||
--bg
|
|
||||||
lg.setColor( 1, 1, 1, 1 )
|
|
||||||
if self.bg then lg.draw( self.bg, 0, 0, 0, wWidth, wHeight ) end
|
|
||||||
|
|
||||||
--buttons
|
|
||||||
for i = #self.buttons, 1, -1 do self.buttons[i]:draw( ) end
|
|
||||||
|
|
||||||
--gradient
|
|
||||||
lg.setColor( 1, 1, 1, 1 )
|
|
||||||
|
|
||||||
if self.fg then lg.draw( self.fg, 0, 0, 0, wWidth, wHeight ) end
|
|
||||||
lg.setCanvas()
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
return menu
|
|
||||||
|
|
@ -1,84 +0,0 @@
|
||||||
local lg = assert( love.graphics )
|
|
||||||
local love = assert( love )
|
|
||||||
local scene = assert( require 'scene' )
|
|
||||||
local strings = strings or assert( require 'strings' )
|
|
||||||
local button = assert( require 'ui.button' )
|
|
||||||
local menu = assert( require 'ui.menu' )
|
|
||||||
local config = assert( require 'config' )
|
|
||||||
local font = assert( require 'ui.fonts' ).midFont
|
|
||||||
|
|
||||||
local function editSelectedOption( button )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
local optionsMenu = menu.new{
|
|
||||||
name = "options",
|
|
||||||
|
|
||||||
buttons = {
|
|
||||||
|
|
||||||
button{
|
|
||||||
x = 0.5 * lg.getWidth(), w = 0.45 * lg.getWidth(), h = lg.getHeight(), y = 55,
|
|
||||||
text = lg.newText( font, "" ),
|
|
||||||
color = { 0.4, 0.1, 0.1, 0.1 }
|
|
||||||
},
|
|
||||||
|
|
||||||
button{
|
|
||||||
x = 15, y = 115, w = 800, h = 30, space = 8,
|
|
||||||
text = lg.newText( font, strings.mainmenu_button ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.8 },
|
|
||||||
callback = function() return scene.mainmenu() end },
|
|
||||||
|
|
||||||
button{
|
|
||||||
option = 'plName',
|
|
||||||
text = lg.newText( font, strings.option_name ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.8 },
|
|
||||||
callback = editSelectedOption },
|
|
||||||
|
|
||||||
button{
|
|
||||||
option = 'plPronoun',
|
|
||||||
text = lg.newText( font, strings.option_pron ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.8 },
|
|
||||||
callback = editSelectedOption },
|
|
||||||
|
|
||||||
button{
|
|
||||||
option = 'plColour',
|
|
||||||
text = lg.newText( font, strings.option_tint ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.8 },
|
|
||||||
callback = editSelectedOption },
|
|
||||||
|
|
||||||
|
|
||||||
button{
|
|
||||||
option = 'keybinds',
|
|
||||||
text = lg.newText( font, strings.option_keybinds ),
|
|
||||||
color = { 0.6, 0.6, 0.6, 0.8 },
|
|
||||||
callback = editSelectedOption,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
fg = lg.newMesh{
|
|
||||||
{ 0, 0, 0, 0, 0.4, 0.1, 0.05, 0.0 },
|
|
||||||
{ 1, 0, 1, 0, 0.8, 0.3, 0.1, 0.8 },
|
|
||||||
{ 1, 1, 1, 1, 0.7, 0.4, 0.1, 0.8 },
|
|
||||||
{ 0, 1, 0, 1, 0.4, 0.1, 0.03, 0.0 },
|
|
||||||
},
|
|
||||||
|
|
||||||
bg = lg.newMesh{
|
|
||||||
{ 0, 0, 0, 0, 1, 1, 1, 0.01 },
|
|
||||||
{ 1, 0, 1, 0, 1, 1, 1, 0.1 },
|
|
||||||
{ 1, 1, 1, 1, 0, 0, 0, 0.1 },
|
|
||||||
{ 0, 1, 0, 1, 0, 0, 0, 0.01 },
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
do
|
|
||||||
local op = optionsMenu.paint
|
|
||||||
function optionsMenu:paint()
|
|
||||||
local selected = self.getSelectedButton()
|
|
||||||
local optionName = selected and selected.option
|
|
||||||
self.buttons[1].text:set( optionName and tostring( config[optionName] ) or "")
|
|
||||||
return op( self )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return optionsMenu
|
|
||||||
|
|
@ -1,119 +0,0 @@
|
||||||
local love = assert( love )
|
|
||||||
local lk = assert( love.keyboard )
|
|
||||||
local lg = assert( love.graphics )
|
|
||||||
|
|
||||||
local utf8 = assert( require 'utf8' )
|
|
||||||
local string = assert( string )
|
|
||||||
local button = assert( require 'client.ui.button' )
|
|
||||||
|
|
||||||
local _lt
|
|
||||||
local _lkp
|
|
||||||
local _lmm
|
|
||||||
local _lmp
|
|
||||||
local _callback
|
|
||||||
|
|
||||||
|
|
||||||
local textInput = { }
|
|
||||||
local __mt = { __index = textInput }
|
|
||||||
|
|
||||||
local font = lg.getFont()
|
|
||||||
|
|
||||||
-- There is only one active text input widget at a time.
|
|
||||||
-- It takes exclusive control of key input.
|
|
||||||
local activeWidget
|
|
||||||
|
|
||||||
textInput.width = 200
|
|
||||||
textInput.length = 20
|
|
||||||
textInput.x = 0
|
|
||||||
textInput.y = 0
|
|
||||||
|
|
||||||
function textInput.new( t )
|
|
||||||
t = t or {}
|
|
||||||
t.str = t.str or ""
|
|
||||||
t.text = lg.newText( t.font or font, t.str )
|
|
||||||
return setmetatable( t, __mt )
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput.keypressed(key, code, isRepeat)
|
|
||||||
if activeWidget and textInput[code] then textInput[code]() end
|
|
||||||
if _lkp then return _lkp() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput:clear()
|
|
||||||
self.str = ""
|
|
||||||
self.text:set( self.str )
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput:contains(x, y)
|
|
||||||
local mx, my, Mx, My = self.x, self.y, self.x + self.width, self.y + font:getHeight()
|
|
||||||
return (x < Mx and x > mx and y > my and y < My)
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput:draw()
|
|
||||||
local w = self.text:getWidth()
|
|
||||||
local h = (self.font or font):getHeight()
|
|
||||||
lg.setColor( 1, 1, 1, 0.5 )
|
|
||||||
if w > 1 then lg.rectangle( "fill", self.x, self.y, math.max( w, 30 ), h, 15, 15 ) end
|
|
||||||
if self.selected then lg.rectangle( "fill", self.x, self.y, self.width, h, 15, 15 ) end
|
|
||||||
lg.rectangle( "line", self.x - 3, self.y - 3, self.width + 6, h + 6, 15, 15 )
|
|
||||||
lg.setColor( 0, 0, 0, 1 )
|
|
||||||
lg.draw( self.text, self.x or 0, self.y or 0)
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput:getText()
|
|
||||||
return self.str
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput.textInput( s )
|
|
||||||
activeWidget.str = activeWidget.str..s
|
|
||||||
activeWidget.text:set( activeWidget.str )
|
|
||||||
if _lkp then return _lkp() end
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput.backspace( )
|
|
||||||
local str = activeWidget.str
|
|
||||||
local byteoffset = utf8.offset(str, -1)
|
|
||||||
if byteoffset then str = string.sub(str, 1, byteoffset - 1)
|
|
||||||
else return end
|
|
||||||
activeWidget.str = str
|
|
||||||
activeWidget.text:set( activeWidget.str )
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput:enterText( callback )
|
|
||||||
_lt = love.textinput
|
|
||||||
_lkp = love.keypressed
|
|
||||||
_lmm = love.mousemoved
|
|
||||||
_lmp = love.mousepressed
|
|
||||||
_callback = assert( callback )
|
|
||||||
love.textinput = textInput.textInput
|
|
||||||
love.keypressed = textInput.keypressed
|
|
||||||
love.mousepressed = nil
|
|
||||||
love.mousemoved = nil
|
|
||||||
self.oldStr = self.str
|
|
||||||
self.str = ""
|
|
||||||
self.text:set( self.str )
|
|
||||||
activeWidget = self
|
|
||||||
end
|
|
||||||
|
|
||||||
local function disable()
|
|
||||||
activeWidget = nil
|
|
||||||
love.textinput = _lt
|
|
||||||
love.keypressed = _lkp
|
|
||||||
love.mousemoved = _lmm
|
|
||||||
love.mousepressed = _lmp
|
|
||||||
end
|
|
||||||
|
|
||||||
function textInput.escape()
|
|
||||||
activeWidget.str = activeWidget.oldStr or ""
|
|
||||||
activeWidget.text:set( activeWidget.str )
|
|
||||||
disable()
|
|
||||||
return _callback()
|
|
||||||
end
|
|
||||||
|
|
||||||
textInput["return"] = function() --unusual decl because return is a keyword
|
|
||||||
local str = activeWidget.str
|
|
||||||
disable()
|
|
||||||
return _callback( str )
|
|
||||||
end
|
|
||||||
|
|
||||||
return textInput
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
if love then require( "client.main" ) else require( "server.main" ) end
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
local clients = {}
|
|
||||||
local client = {}
|
|
||||||
client.__index = client
|
|
||||||
|
|
||||||
function client.new()
|
|
||||||
local c = setmetatable( {}, client )
|
|
||||||
clients[ c.id ] = c
|
|
||||||
return c
|
|
||||||
end
|
|
||||||
|
|
||||||
function client:disconnect()
|
|
||||||
clients[self.id] = nil
|
|
||||||
end
|
|
||||||
|
|
||||||
function client:assignRole( role )
|
|
||||||
self.role = role
|
|
||||||
end
|
|
||||||
|
|
||||||
function client.connect( ip, port )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
return client
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
function love.conf(t)
|
|
||||||
t.identity = "vision-server" -- The name of the save directory (string)
|
|
||||||
t.appendidentity = false -- Search files in source directory before save directory (boolean)
|
|
||||||
t.version = "11.4" -- The LÖVE version this game was made for (string)
|
|
||||||
t.console = true -- Attach a console (boolean, Windows only)
|
|
||||||
t.accelerometerjoystick = true -- Enable the accelerometer on iOS and Android by exposing it as a Joystick (boolean)
|
|
||||||
t.externalstorage = false -- True to save files (and read from the save directory) in external storage on Android (boolean)
|
|
||||||
t.gammacorrect = false -- Enable gamma-correct rendering, when supported by the system (boolean)
|
|
||||||
|
|
||||||
t.audio.mic = false -- Request and use microphone capabilities in Android (boolean)
|
|
||||||
t.audio.mixwithsystem = true -- Keep background music playing when opening LOVE (boolean, iOS and Android only)
|
|
||||||
|
|
||||||
t.window.title = "vision-server" -- The window title (string)
|
|
||||||
t.window.icon = false -- Filepath to an image to use as the window's icon (string)
|
|
||||||
t.window.width = 800 -- The window width (number)
|
|
||||||
t.window.height = 600 -- The window height (number)
|
|
||||||
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.minwidth = 400 -- Minimum window width if the window is resizable (number)
|
|
||||||
t.window.minheight = 400 -- Minimum window height if the window is resizable (number)
|
|
||||||
t.window.fullscreen = false -- Enable fullscreen (boolean)
|
|
||||||
t.window.fullscreentype = "desktop" -- Choose between "desktop" fullscreen or "exclusive" fullscreen mode (string)
|
|
||||||
t.window.vsync = 1 -- Vertical sync mode (number)
|
|
||||||
t.window.msaa = 3 -- The number of samples to use with multi-sampled antialiasing (number)
|
|
||||||
t.window.depth = nil -- The number of bits per sample in the depth buffer
|
|
||||||
t.window.stencil = nil -- The number of bits per sample in the stencil buffer
|
|
||||||
t.window.display = 1 -- Index of the monitor to show the window in (number)
|
|
||||||
t.window.highdpi = false -- Enable high-dpi mode for the window on a Retina display (boolean)
|
|
||||||
t.window.usedpiscale = true -- Enable automatic DPI scaling when highdpi is set to true as well (boolean)
|
|
||||||
t.window.x = nil -- The x-coordinate of the window's position in the specified display (number)
|
|
||||||
t.window.y = nil -- The y-coordinate of the window's position in the specified display (number)
|
|
||||||
|
|
||||||
t.modules.audio = true -- Enable the audio module (boolean)
|
|
||||||
t.modules.data = true -- Enable the data module (boolean)
|
|
||||||
t.modules.event = true -- Enable the event module (boolean)
|
|
||||||
t.modules.font = true -- Enable the font module (boolean)
|
|
||||||
t.modules.graphics = true -- Enable the graphics module (boolean)
|
|
||||||
t.modules.image = true -- Enable the image module (boolean)
|
|
||||||
t.modules.joystick = false -- Enable the joystick module (boolean)
|
|
||||||
t.modules.keyboard = true -- Enable the keyboard module (boolean)
|
|
||||||
t.modules.math = true -- Enable the math module (boolean)
|
|
||||||
t.modules.mouse = true -- Enable the mouse module (boolean)
|
|
||||||
t.modules.physics = true -- Enable the physics module (boolean)
|
|
||||||
t.modules.sound = true -- Enable the sound module (boolean)
|
|
||||||
t.modules.system = true -- Enable the system module (boolean)
|
|
||||||
t.modules.thread = true -- Enable the thread module (boolean)
|
|
||||||
t.modules.timer = true -- Enable the timer module (boolean), Disabling it will result 0 delta time in love.update
|
|
||||||
t.modules.touch = false -- Enable the touch module (boolean)
|
|
||||||
t.modules.video = false -- Enable the video module (boolean)
|
|
||||||
t.modules.window = false -- Enable the window module (boolean)
|
|
||||||
end
|
|
||||||
|
|
@ -1,212 +0,0 @@
|
||||||
package.cpath = "..\\?.dll;..\\clib\\?.dll;"..package.cpath
|
|
||||||
package.path = "..\\?.lua;..\\lualib\\?.lua;"..package.path
|
|
||||||
local shared = assert( require 'shared.shared' )
|
|
||||||
local packet = shared.packet
|
|
||||||
local socket = assert( require 'socket' )
|
|
||||||
local mscxn = assert( socket.udp() )
|
|
||||||
mscxn:settimeout( 0 )
|
|
||||||
assert( mscxn:setpeername( shared.metaserver.ip, shared.metaserver.port ), "Could not connect to metaserver!" )
|
|
||||||
local udp
|
|
||||||
local io = assert( io )
|
|
||||||
|
|
||||||
local CLIENTTIMEOUT = 10
|
|
||||||
|
|
||||||
local svInfo = packet.serverInfo{ 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 clients = {}
|
|
||||||
local connecting = {}
|
|
||||||
local server = { tick = 0, time = 0, currentClient = false }
|
|
||||||
|
|
||||||
local handlers = setmetatable({
|
|
||||||
|
|
||||||
--This is the start of a packet from a connected client.
|
|
||||||
connected = function( msg, ip, port )
|
|
||||||
local client = clients[msg.token]
|
|
||||||
if not client or
|
|
||||||
(client.ip ~= ip) or
|
|
||||||
(client.port ~= port)
|
|
||||||
then
|
|
||||||
print( "Invalid token from IP:", msg.token, ip, port )
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
if client.tick > msg.tick then
|
|
||||||
print( "Old packet received:", msg.tick, ip )
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
client.tick = msg.tick
|
|
||||||
client.time = server.time
|
|
||||||
server.currentClient = client
|
|
||||||
end,
|
|
||||||
|
|
||||||
--A client wants to disconnect.
|
|
||||||
disconnect = function( msg, ip, port )
|
|
||||||
--This is the authenticated client whose packets are being processed.
|
|
||||||
local client = server.currentClient
|
|
||||||
if client and client.ip == ip and client.port == port then
|
|
||||||
print( "Client disconnecting:", client.id)
|
|
||||||
clients[ client.id ] = nil
|
|
||||||
server.currentClient = false
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
|
|
||||||
--Client responds to handshake challenge.
|
|
||||||
clChimo = function( clChimo, ip, port )
|
|
||||||
|
|
||||||
print( "Received handshake response." )
|
|
||||||
|
|
||||||
--No active challenge, don't send anything.
|
|
||||||
local key = ip..":"..port
|
|
||||||
if not connecting[ key ] then
|
|
||||||
print( "Old connection attempt from:", key )
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--Compute session token.
|
|
||||||
local remoteHash = clChimo.hash
|
|
||||||
local clNonce = clChimo.nonce
|
|
||||||
local svNonce = connecting[key].nonce
|
|
||||||
local token = shared.hash.hash( clNonce, svNonce )
|
|
||||||
if token ~= remoteHash then
|
|
||||||
print( "Hashes differ:", shared.hash.hex( clNonce ), shared.hash.hex( svNonce ) )
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
|
|
||||||
--Hash collision.
|
|
||||||
while clients[token] do token = token + 1 end
|
|
||||||
|
|
||||||
--Successful handshake.
|
|
||||||
print( "Client connected:", token, port, ip )
|
|
||||||
clients[ token ] = connecting[ key ]
|
|
||||||
clients[ token ].id = token
|
|
||||||
clients[ token ].time = socket.gettime()
|
|
||||||
packet.connected{ token = token, tick = server.tick }
|
|
||||||
return udp:sendto( packet.get(), ip, port )
|
|
||||||
end,
|
|
||||||
|
|
||||||
--Metaserver replying with real IP address.
|
|
||||||
advertised = function( ack, ip, port )
|
|
||||||
if ip ~= shared.metaserver.ip then return print( "Advertisement acked from rogue address:", ip, port ) end
|
|
||||||
if udp:getsockname() then
|
|
||||||
--assert( udp:getsockname() == tostring( ack.ip ), print( tostring( ack.ip ), udp:getsockname()) or "IP mismatch!" )
|
|
||||||
return print( "Advertised. Address already set." )
|
|
||||||
end
|
|
||||||
print( "Advertised. Setting address:", ack.ip, ack.port )
|
|
||||||
server.SetIP( tostring( ack.ip ), ack.port )
|
|
||||||
end,
|
|
||||||
|
|
||||||
--New client seeks to connect. Challenge immediately.
|
|
||||||
clientInfo = function( clientInfo, ip, port )
|
|
||||||
local key = ip..":"..port
|
|
||||||
connecting[key] = connecting[key] or { ip = ip, port = port, tick = 0 }
|
|
||||||
local client = connecting[key]
|
|
||||||
local nonce = shared.hash.rand()
|
|
||||||
client.nonce = nonce
|
|
||||||
print( "Received connection request from:", ip, port )
|
|
||||||
print( "Sending authentication nonce:", nonce )
|
|
||||||
packet.svChimo{ nonce = nonce }
|
|
||||||
return udp:sendto( packet.get(), ip, port )
|
|
||||||
end,
|
|
||||||
|
|
||||||
|
|
||||||
}, {__index = function() return print end })
|
|
||||||
|
|
||||||
function server.Advertise()
|
|
||||||
print( "Advertise." )
|
|
||||||
packet.get()
|
|
||||||
packet.metaServer()
|
|
||||||
packet.serverInfo( svInfo )
|
|
||||||
mscxn:send( packet.get() )
|
|
||||||
end
|
|
||||||
|
|
||||||
function server.serverInfo( serverInfo )
|
|
||||||
print( packet.getString( serverInfo.svname ) )
|
|
||||||
end
|
|
||||||
|
|
||||||
function server.metaServer( ms )
|
|
||||||
server.advertising = true
|
|
||||||
end
|
|
||||||
|
|
||||||
--Incoming packet.
|
|
||||||
function server.Parse( msg, ip, port )
|
|
||||||
if (not msg) or (#msg < 1) then return end
|
|
||||||
local msgs, types = packet.deserialise( msg )
|
|
||||||
if msgs then
|
|
||||||
for i = 1, #msgs do
|
|
||||||
if handlers[ types[i] ]( msgs[i], ip, port ) then break end
|
|
||||||
end
|
|
||||||
server.currentClient = false
|
|
||||||
else print( types )
|
|
||||||
end
|
|
||||||
return server.Parse( udp:receivefrom() ) -- Process other packets.
|
|
||||||
end
|
|
||||||
|
|
||||||
function server.SetIP( ipString, port )
|
|
||||||
svInfo.ip = shared.ip.fromString( ipString )
|
|
||||||
svInfo.port = port
|
|
||||||
local ok, err = udp:setsockname( ipString, port )
|
|
||||||
if ok then
|
|
||||||
print( "Server IP:", udp:getsockname() )
|
|
||||||
return true
|
|
||||||
--Find another port.
|
|
||||||
elseif port < 65536 then
|
|
||||||
print( "Could not use port:", ipString, port, err )
|
|
||||||
return server.SetIP( ipString, port + 1 )
|
|
||||||
else
|
|
||||||
print( "Could not use IP:", ipString, err )
|
|
||||||
return error( "Connection failed." )
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
function server.Start()
|
|
||||||
udp = assert( socket.udp() )
|
|
||||||
udp:settimeout(0)
|
|
||||||
end
|
|
||||||
|
|
||||||
function server.Advance()
|
|
||||||
server.tick = server.tick + 1
|
|
||||||
for id, client in pairs( clients ) do
|
|
||||||
packet.get()
|
|
||||||
packet.connected{ token = id, tick = server.tick }
|
|
||||||
udp:sendto( packet.get(), client.ip, client.port )
|
|
||||||
|
|
||||||
if server.time - client.time > CLIENTTIMEOUT then
|
|
||||||
print( "dropping client:", id )
|
|
||||||
clients[id] = nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function server.NewGame()
|
|
||||||
server.tick = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
function server.Quit()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
server.Start()
|
|
||||||
function love.update( dt )
|
|
||||||
server.time = server.time + dt
|
|
||||||
server.Parse( udp:receivefrom() )
|
|
||||||
if server.time > 0.1 then
|
|
||||||
if (server.tick % 50) == 0 then
|
|
||||||
server.Advertise()
|
|
||||||
server.Parse( mscxn:receive(), shared.metaserver.ip, shared.metaserver.port )
|
|
||||||
end
|
|
||||||
server.time = 0
|
|
||||||
server.Advance()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function love.draw() end
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
local dns = assert( require 'socket' ).dns
|
|
||||||
--Get your own local IP address via DNS.
|
|
||||||
return dns.toip( dns.gethostname() )
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
local math = math
|
|
||||||
local bit = assert( require 'bit' )
|
|
||||||
local max = 65536
|
|
||||||
math.randomseed( 4 )
|
|
||||||
--hash of a pair of 32-bit numbers
|
|
||||||
return {
|
|
||||||
hash = bit.bxor,
|
|
||||||
hex = bit.tohex,
|
|
||||||
rand = function()
|
|
||||||
return math.random( 1, max )
|
|
||||||
end, }
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
--CData structure that can hold the longest string representation of an IPv6 address
|
|
||||||
--we do this because LuaSocket expects a Lua string, and because we can't guarantee v4-only addresses
|
|
||||||
local ffi = assert( require 'ffi' )
|
|
||||||
local ipString = {}
|
|
||||||
local string = assert( string )
|
|
||||||
ffi.cdef[[
|
|
||||||
typedef struct {
|
|
||||||
char ip[40];
|
|
||||||
} ipAddress;
|
|
||||||
]]
|
|
||||||
|
|
||||||
local ipAddress = ffi.typeof( ffi.new( "ipAddress" ) )
|
|
||||||
ffi.metatype( ipAddress, { __tostring = function( ip ) return ffi.string( ip.ip, ffi.sizeof( ip.ip ) ) end } )
|
|
||||||
|
|
||||||
function ipString.new( t )
|
|
||||||
local ip = ffi.new( ipAddress )
|
|
||||||
ip.ip = t
|
|
||||||
return ip
|
|
||||||
end
|
|
||||||
|
|
||||||
ipString.fromString = ipString.new
|
|
||||||
|
|
||||||
return ipString
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
local ms = { ip = '192.168.2.15', port = 42069 }
|
|
||||||
print( "Metaserver public address:", ms.ip, ms.port )
|
|
||||||
return ms
|
|
||||||
|
|
@ -1,216 +0,0 @@
|
||||||
local ffi = assert( require 'ffi' )
|
|
||||||
local buffer = assert( require( "string.buffer" ) )
|
|
||||||
local ipString = assert( require 'shared.ipstring' )
|
|
||||||
local roles = assert( require 'shared.roles' )
|
|
||||||
local packet = {}
|
|
||||||
local mt = { __index = { new = function( self ) return ffi.new( self.ct ) end } }
|
|
||||||
|
|
||||||
local headerByte = 0x41 --Ensure printable characters at start of packets.
|
|
||||||
|
|
||||||
local function newStruct( t )
|
|
||||||
assert( not( packet[ t.name ] ))
|
|
||||||
packet[ t.name ] = t
|
|
||||||
t.netname = headerByte
|
|
||||||
packet[ headerByte ] = t
|
|
||||||
headerByte = headerByte + 1
|
|
||||||
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 )
|
|
||||||
assert( t.size < 500, t.name )
|
|
||||||
setmetatable( t, mt )
|
|
||||||
|
|
||||||
print( "Packet:", t.name, "Members:", #t + 1, "Size:", t.size, "Alignment:", ffi.alignof( t.ct ) )
|
|
||||||
end
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "serverInfo",
|
|
||||||
"uint8_t players",
|
|
||||||
"uint8_t capacity",
|
|
||||||
"ipAddress ip",
|
|
||||||
"uint16_t version",
|
|
||||||
"uint16_t port",
|
|
||||||
"char svname[32]",
|
|
||||||
"char map[32]",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "clientInfo",
|
|
||||||
"char username[31]",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "svChimo",
|
|
||||||
"uint32_t nonce",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "clChimo",
|
|
||||||
"uint32_t nonce",
|
|
||||||
"uint32_t hash",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "connected",
|
|
||||||
"uint32_t token",
|
|
||||||
"uint32_t tick",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "metaServer",
|
|
||||||
"char padding[300]" --Just a bunch of padding to mitigate amplification.
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "insect",
|
|
||||||
"uint8_t id",
|
|
||||||
"bool dead",
|
|
||||||
"int16_t z",
|
|
||||||
"int8_t hp",
|
|
||||||
"int8_t vx",
|
|
||||||
"int8_t vy",
|
|
||||||
"int8_t vz",
|
|
||||||
"int32_t x",
|
|
||||||
"int32_t y",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "soleil",
|
|
||||||
"uint16_t azimuth",
|
|
||||||
"uint16_t altitude",
|
|
||||||
"int8_t vazi",
|
|
||||||
"int8_t valt",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "playerInfo",
|
|
||||||
"uint8_t id",
|
|
||||||
"role_t role",
|
|
||||||
"char username[31]",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "chatMessage",
|
|
||||||
"uint8_t id",
|
|
||||||
"char cmsg[127]",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "command",
|
|
||||||
"char command",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "advertised",
|
|
||||||
"uint32_t time",
|
|
||||||
"ipAddress ip",
|
|
||||||
"uint16_t port",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "disconnect",
|
|
||||||
"char reason",
|
|
||||||
}
|
|
||||||
|
|
||||||
newStruct{
|
|
||||||
name = "debugLatency",
|
|
||||||
"uint32_t lastTick",
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
return nil, "Malformed packet. Unknown header:\n"..readBuffer:get()
|
|
||||||
end
|
|
||||||
if #readBuffer < t.size then
|
|
||||||
return nil, "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
|
|
||||||
|
|
||||||
--Slow!
|
|
||||||
function packet.luaString( member )
|
|
||||||
local s = packet.getString( member )
|
|
||||||
return s:sub( 1, ( s:find( "\0" ) or s:len() ) - 1 )
|
|
||||||
end
|
|
||||||
|
|
||||||
local writeBuffer = buffer.new( 1024 )
|
|
||||||
function packet.add( struct, data )
|
|
||||||
local str = ffi.new( struct.ct, data or 0 )
|
|
||||||
str.netname = assert( struct.netname )
|
|
||||||
writeBuffer:putcdata( str, struct.size )
|
|
||||||
return str
|
|
||||||
end
|
|
||||||
|
|
||||||
function packet.get()
|
|
||||||
return writeBuffer:get()
|
|
||||||
end
|
|
||||||
|
|
||||||
mt.__call = packet.add
|
|
||||||
|
|
||||||
local testing = testing
|
|
||||||
--TESTS--
|
|
||||||
if testing then
|
|
||||||
|
|
||||||
packet.serverInfo{
|
|
||||||
players = 0,
|
|
||||||
capacity = 255,
|
|
||||||
map = "abcdefghijklmnopqrstuvwxyz1234567890",
|
|
||||||
svname = "😘😘😘😘😘😘😘kissyfaceserver",
|
|
||||||
version = 25,
|
|
||||||
port = 51312,
|
|
||||||
ip = ipString.new '132.145.25.62'
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
local _print = print
|
|
||||||
local lfs = assert( love.filesystem )
|
|
||||||
local log = assert( lfs.newFile( ("log_%04d.txt"):format( os.time() % 1000 )))
|
|
||||||
assert( log:open 'a', "Could not open log file!" )
|
|
||||||
return function( ... )
|
|
||||||
|
|
||||||
log:write( os.date("!%M%S") )
|
|
||||||
for i, v in ipairs{...} do
|
|
||||||
log:write("\t")
|
|
||||||
log:write(tostring(v))
|
|
||||||
end
|
|
||||||
log:write("\n")
|
|
||||||
|
|
||||||
return _print( ... )
|
|
||||||
end
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
local ffi = require 'ffi'
|
|
||||||
ffi.cdef[[
|
|
||||||
typedef enum {
|
|
||||||
roleSpectator = 0,
|
|
||||||
roleSoleil = 1,
|
|
||||||
roleInsect = 2,
|
|
||||||
} role_t;
|
|
||||||
]]
|
|
||||||
return ffi.typeof( ffi.new 'role_t' )
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
print( "Loading Shared." )
|
|
||||||
local shared = {}
|
|
||||||
|
|
||||||
--[[do
|
|
||||||
local rq = require
|
|
||||||
require = function( ... )
|
|
||||||
print( "Require:", ... )
|
|
||||||
return rq( ... )
|
|
||||||
end
|
|
||||||
end]]
|
|
||||||
shared.hash = assert( require 'shared.hash' )
|
|
||||||
shared.ip = assert( require 'shared.ipstring' )
|
|
||||||
shared.packet = assert( require 'shared.packet' )
|
|
||||||
shared.print = assert( require 'shared.print' )
|
|
||||||
shared.metaserver = assert( require 'shared.metaserver' )
|
|
||||||
shared.myip = assert( require 'shared.getip' )
|
|
||||||
|
|
||||||
--Turn on logging?
|
|
||||||
print = shared.print
|
|
||||||
print( "My IP: ", shared.myip )
|
|
||||||
|
|
||||||
--World state.
|
|
||||||
local world = {}
|
|
||||||
function world:Advance()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function world:Reset()
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function world:Load( map )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function world:AddPlayer( playerID, x, y, stage )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function world:RemovePlayer( playerID )
|
|
||||||
|
|
||||||
end
|
|
||||||
|
|
||||||
function shared.NewWorld()
|
|
||||||
return setmetatable( {}, {__index = world } )
|
|
||||||
end
|
|
||||||
|
|
||||||
return shared
|
|
||||||
Loading…
Reference in New Issue