Pedro Gimeno 956ebed1f2 Add --dedicated command line argument to run a dedicated server | 3 gadi atpakaļ | |
---|---|---|
.. | ||
examples | 9 gadi atpakaļ | |
serverlist | 9 gadi atpakaļ | |
License.txt | 9 gadi atpakaļ | |
README.md | 9 gadi atpakaļ | |
advertise.lua | 3 gadi atpakaļ | |
client.lua | 9 gadi atpakaļ | |
commands.lua | 9 gadi atpakaļ | |
game.lua | 9 gadi atpakaļ | |
main.lua | 9 gadi atpakaļ | |
menu.lua | 9 gadi atpakaļ | |
network.lua | 9 gadi atpakaļ | |
server.lua | 9 gadi atpakaļ | |
user.lua | 9 gadi atpakaļ | |
utility.lua | 9 gadi atpakaļ |
A networking library for the awesome Löve engine.
This library aims to take care of the enourmous overhead of connecting, authorizing, passwords, synchronizing game states, usernames, userlist etc. involved when creating a network game. There are other networking libraries available, such as LUBE, Noobhub, or you could even use plain Luasocket (which this library uses internally). However, Affair is much more high-level. Because it has many functions which are frequently used in multiplayer games, using Affair should speed up multiplayer game development a lot.
The lib comes with an example (main.lua). Run a server using:
love . --server
Connect a user by calling:
love . --client ADDRESS
ADDRESS is the IP address. Defaults to 'localhost'.
Default port is 3410.
You can also create a server and connect a client to it at the same time:
love . --server --client localhost
Another included example is the dedicated server, which runs in plain Lua (Luasocket must be installed. If you have Löve installed, then this is usually the case.) Run:
lua examples/dedicated.lua
Then connect a client to it by running the client example above.
A server is started using the network:startServer function:
server, err = network:startServer( numberOfPlayers, port, pingUpdate, portUDP )
lua
-- creates a server which allows a maximum of 8 connections.
server, err = network:startServer( 8 )
if server then
...
else
print("Could not start server: " .. err )
end
Once you have created a server object, you can define the server's callbacks. If, for example, "authorize" and "serverReceive" are functions with the correct parameters, then you can define the callbacks like this:
server, err = network:startServer( ... )
if server then
server.callbacks.received = serverReceive
server.callbacks.authorize = authorize
end
server.callbacks.received( command, msg, user ): Called whenever the server receives a message from a user (which is not an engine-internal message). So whenever you call client:send( command, msg ) on a client, this event will fire on the server.
server.callbacks.userFullyConnected( user ): Called when a user has connected AND has been synchronized. "user" is the newly connected user, which has a player name and id set already. Ideally, you should never interact with a user before this callback has fired. Important: before this callback has fired, any broadcasts will not be forwarded to this user.
server.callbacks.synchronize( user ): This callback is called during the connection process of a new user. If there are vital objects/information which the client needs before joining the game (for example, the current map or the other clients' player entities) then it should be sent to the client here. Note: At this point, the new client knows about all other clients, so it's okay to send client-specific data - like the player entities - which might require knowledge about the other players. Note: At this point, the new client also knows the current status of all of the other users' customData (userValues) which have previously been set. Note: If you use server:send(...) in this function to send values to the new user, make sure to give the third parameter to the function (the "user" value). Otherwise, server:send broadcasts this info to all synchronized clients - and the others usually already have the data. Note: Do not user server:setUserCallback here (it will throw an error), because the user must be fully synchronized before setUserValue works. If you need to set custom user data, use server:setUserCallback in the userFullyConnected
server.callbacks.authorize( user, authMsg ): Called when a new user is trying to connect. Use this event to let the engine know whether or not a new user may connect at the moment. This event should return either true or false followed by an error message. If this event is not specified, it Example usage: The authorize event could return true while the server is in a lobby, but as soon as the actual game is started, it returns: false, "Game already started!". The client will then be disconnected and userFullyConnected and synchronize (above) will never be called for this client. Note: You don't need to worry about the maximum number of players here - if the server is already full, then the engine will not authorize the player and won't even call this event. authMsg is the string which the client used when calling network:startClient. This way, you can check if the client is using the same game version as you, or entered the correct password.
server.callbacks.customDataChanged( user, value, key, previousValue ): Called whenever a client changes their customUserData. The userdata is already synched with other clients, but if you want to do something when user data changes (example: start game when sets his "ready" value to true), then this is the place. The previousValue is the value which the user had set before - it could be nil, if this value is set for the first time.
server.callbacks.disconnectedUser( user ): Called when a user has disconnected. Note: after this call, the "user" table will be invalid. Don't attempt to use it again - but you're allowed to access it to print the user name of the client who left and similar:
function disconnected( user )
print( user.playername .. " has has left. (ID: " .. user.id .. ")" )
end
A client is started (and connected to an already running server) by calling network:startClient.
client, err = network:startClient( address, playername, port, authMsg )
Once you have created a client object, you can define the client's callbacks. If, for example, "connect" and "clientReceive" are functions with the correct parameters, then you can define the callbacks like this:
client, err = network:startClient( ... )
if client then
client.callbacks.connected = connect
client.callbacks.received = clientReceive
end
client.callbacks.authorized( auth, reason ): This is called when the server responds to the authorization request by the client (which the client will always to automatically when connecting). The 'auth' paramter will be true or false depending on whether the client has been authorized. The "reason" parameter will hold a message in case the client has not been authorized, telling it, why.
client.callbacks.connected(): Called on the client when the connection process has finished (similar to the server.callbacks.userFullyConnected callback called on the server) and the client is synchronized. At this point, the client is 'equal' to all other clients who have previously connected and has their user values, names and IDs.
client.callbacks.received( command, msg ): Called when the client gets a message from the server (i.e. when server:send( command, msg ) has been called on the server.
client.callbacks.disconnected(): Called when the client has been disconnected for some reason.
client.callbacks.newUser( user ): Called on all clients when a new user has been synchronized. Note: This is not called on the client who is joining (i.e. the one who has just been synchronized). Note: You do not need to keep a list of all users. Use client:getUsers() to get an up-to-date list of all currently connected users.
Maximum size of a message is 4 GB (to be more precise: 256^4 bytes). You should always stay well below, of course - but hey, I won't stop you.
For the online server list to work, you will need to install some sort of web-server (a simple Apache server will do, you need php support) and put the files from the AffairMainServer into some folder on that main server (The only requirement is that the folder is visible to anyone). On your game's server, call the following function (after having created a server using network:startServer):
server:advertise( data, id, url, portUDP )
Careful! The data and id strings may not contain the following characters: " & $ | and any whitespace (space, tab, newline). Usually, you're best off by using a comma-seperated list.
The function will start sending updates about your server every minute or so, to tell the web server that your server is still alive, and will stop sending updates when your server goes offline.
You can change the server's data, by calling the function again with only the data parameter - for example when you want to change the map or the number of players.
If you want to stop advertising the server, call (for example because a round has started and you want no more players to join): server:unAdvertise()
Example:
function startServer()
-- Attempt to create a server:
server, err = network:startServer( NUMBER_OF_PLAYERS, PORT, PING_UPDATE_TIME )
if server then
-- These callbacks are called when a new user connects or a user disconnected:
server.callbacks.userFullyConnected = connected
server.callbacks.disconnectedUser = disconnected
-- Start advertising this server, so others can join:
server:advertise( "Players:0", "ExampleServer", MAIN_SERVER_ADDRESS )
else
print("Error starting server:", err)
love.event.quit()
end
end
-- Update number of players on the serverlist:
function connected()
local players = network:getUsers()
-- Only update the data field in the advertisement, leave the id and URL the same:
server:advertise( "Players:" .. #players )
end
function disconnected()
local players = network:getUsers()
-- Only update the data field in the advertisement, leave the id and URL the same:
server:advertise( "Players:" .. #players - 1 )
end
When calling the server:advertise function above, the server will also advertise itself in your Local Area Network. This uses a UDP port to accept messages from clients. The port can be set as the fourth argument in network:startServer (see above).
On the client, you can start looking for LAN servers using:
network:requestServerListLAN( id, portUDP )
You may call this function again to refresh the LAN server list (i.e. send out a new request). In this case, you can call it without parameters - the previous ones will be used.