940 lines
36 KiB
C++
940 lines
36 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2013-2015 SuperTuxKart-Team
|
|
//
|
|
// This program is free software; you can redistribute it and/or
|
|
// modify it under the terms of the GNU General Public License
|
|
// as published by the Free Software Foundation; either version 3
|
|
// of the License, or (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program; if not, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "network/protocols/server_lobby.hpp"
|
|
|
|
#include "config/player_manager.hpp"
|
|
#include "config/user_config.hpp"
|
|
#include "modes/world.hpp"
|
|
#include "network/event.hpp"
|
|
#include "network/network_config.hpp"
|
|
#include "network/network_player_profile.hpp"
|
|
#include "network/protocols/get_public_address.hpp"
|
|
#include "network/protocols/connect_to_peer.hpp"
|
|
#include "network/protocols/latency_protocol.hpp"
|
|
#include "network/protocol_manager.hpp"
|
|
#include "network/race_event_manager.hpp"
|
|
#include "network/stk_host.hpp"
|
|
#include "network/stk_peer.hpp"
|
|
#include "online/online_profile.hpp"
|
|
#include "online/request_manager.hpp"
|
|
#include "states_screens/networking_lobby.hpp"
|
|
#include "states_screens/race_result_gui.hpp"
|
|
#include "states_screens/waiting_for_others.hpp"
|
|
#include "utils/log.hpp"
|
|
#include "utils/random_generator.hpp"
|
|
#include "utils/time.hpp"
|
|
|
|
|
|
/** This is the central game setup protocol running in the server. It is
|
|
* mostly a finite state machine. Note that all nodes in ellipses and light
|
|
* grey background are actual states; nodes in boxes and white background
|
|
* are functions triggered from a state or triggering potentially a state
|
|
* change.
|
|
\dot
|
|
digraph interaction {
|
|
node [shape=box]; "Server Constructor"; "playerTrackVote"; "connectionRequested";
|
|
"signalRaceStartToClients"; "startedRaceOnClient"; "loadWorld";
|
|
node [shape=ellipse,style=filled,color=lightgrey];
|
|
|
|
"Server Constructor" -> "INIT_WAN" [label="If WAN game"]
|
|
"Server Constructor" -> "ACCEPTING_CLIENTS" [label="If LAN game"]
|
|
"INIT_WAN" -> "GETTING_PUBLIC_ADDRESS" [label="GetPublicAddress protocol callback"]
|
|
"GETTING_PUBLIC_ADDRESS" -> "ACCEPTING_CLIENTS" [label="Register server"]
|
|
"ACCEPTING_CLIENTS" -> "connectionRequested" [label="Client connection request"]
|
|
"connectionRequested" -> "ACCEPTING_CLIENTS"
|
|
"ACCEPTING_CLIENTS" -> "SELECTING" [label="Start race from authorised client"]
|
|
"SELECTING" -> "SELECTING" [label="Client selects kart, #laps, ..."]
|
|
"SELECTING" -> "playerTrackVote" [label="Client selected track"]
|
|
"playerTrackVote" -> "SELECTING" [label="Not all clients have selected"]
|
|
"playerTrackVote" -> "LOAD_WORLD" [label="All clients have selected; signal load_world to clients"]
|
|
"LOAD_WORLD" -> "loadWorld"
|
|
"loadWorld" -> "WAIT_FOR_WORLD_LOADED"
|
|
"WAIT_FOR_WORLD_LOADED" -> "WAIT_FOR_WORLD_LOADED" [label="Client or server loaded world"]
|
|
"WAIT_FOR_WORLD_LOADED" -> "signalRaceStartToClients" [label="All clients and server ready"]
|
|
"signalRaceStartToClients" -> "WAIT_FOR_RACE_STARTED"
|
|
"WAIT_FOR_RACE_STARTED" -> "startedRaceOnClient" [label="Client has started race"]
|
|
"startedRaceOnClient" -> "WAIT_FOR_RACE_STARTED" [label="Not all clients have started"]
|
|
"startedRaceOnClient" -> "DELAY_SERVER" [label="All clients have started"]
|
|
"DELAY_SERVER" -> "DELAY_SERVER" [label="Not done waiting"]
|
|
"DELAY_SERVER" -> "RACING" [label="Server starts race now"]
|
|
}
|
|
\enddot
|
|
|
|
|
|
* It starts with detecting the public ip address and port of this
|
|
* host (GetPublicAddress).
|
|
*/
|
|
ServerLobby::ServerLobby() : LobbyProtocol(NULL)
|
|
{
|
|
setHandleDisconnections(true);
|
|
} // ServerLobby
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Destructor.
|
|
*/
|
|
ServerLobby::~ServerLobby()
|
|
{
|
|
} // ~ServerLobby
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
void ServerLobby::setup()
|
|
{
|
|
m_game_setup = STKHost::get()->setupNewGame();
|
|
m_game_setup->setNumLocalPlayers(0); // no local players on a server
|
|
m_next_player_id.setAtomic(0);
|
|
|
|
// In case of LAN we don't need our public address or register with the
|
|
// STK server, so we can directly go to the accepting clients state.
|
|
m_state = NetworkConfig::get()->isLAN() ? ACCEPTING_CLIENTS
|
|
: INIT_WAN;
|
|
m_selection_enabled = false;
|
|
m_current_protocol = NULL;
|
|
Log::info("ServerLobby", "Starting the protocol.");
|
|
|
|
// Initialise the data structures to detect if all clients and
|
|
// the server are ready:
|
|
m_player_states.clear();
|
|
m_client_ready_count.setAtomic(0);
|
|
m_server_has_loaded_world = false;
|
|
const std::vector<NetworkPlayerProfile*> &players =
|
|
m_game_setup->getPlayers();
|
|
for (unsigned int i = 0; i < players.size(); i++)
|
|
{
|
|
m_player_states[players[i]->getGlobalPlayerId()] = false;
|
|
}
|
|
|
|
} // setup
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
bool ServerLobby::notifyEventAsynchronous(Event* event)
|
|
{
|
|
assert(m_game_setup); // assert that the setup exists
|
|
if (event->getType() == EVENT_TYPE_MESSAGE)
|
|
{
|
|
NetworkString &data = event->data();
|
|
assert(data.size()); // message not empty
|
|
uint8_t message_type;
|
|
message_type = data.getUInt8();
|
|
Log::info("ServerLobby", "Message received with type %d.",
|
|
message_type);
|
|
switch(message_type)
|
|
{
|
|
case LE_CONNECTION_REQUESTED: connectionRequested(event); break;
|
|
case LE_REQUEST_BEGIN: startSelection(event); break;
|
|
case LE_KART_SELECTION: kartSelectionRequested(event); break;
|
|
case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break;
|
|
case LE_STARTED_RACE: startedRaceOnClient(event); break;
|
|
case LE_VOTE_MAJOR: playerMajorVote(event); break;
|
|
case LE_VOTE_RACE_COUNT: playerRaceCountVote(event); break;
|
|
case LE_VOTE_MINOR: playerMinorVote(event); break;
|
|
case LE_VOTE_TRACK: playerTrackVote(event); break;
|
|
case LE_VOTE_REVERSE: playerReversedVote(event); break;
|
|
case LE_VOTE_LAPS: playerLapsVote(event); break;
|
|
case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break;
|
|
} // switch
|
|
|
|
} // if (event->getType() == EVENT_TYPE_MESSAGE)
|
|
else if (event->getType() == EVENT_TYPE_DISCONNECTED)
|
|
{
|
|
clientDisconnected(event);
|
|
} // if (event->getType() == EVENT_TYPE_DISCONNECTED)
|
|
return true;
|
|
} // notifyEventAsynchronous
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Simple finite state machine. First get the public ip address. Once this
|
|
* is known, register the server and its address with the stk server so that
|
|
* client can find it.
|
|
*/
|
|
void ServerLobby::update(float dt)
|
|
{
|
|
switch (m_state)
|
|
{
|
|
case INIT_WAN:
|
|
// Start the protocol to find the public ip address.
|
|
m_current_protocol = new GetPublicAddress(this);
|
|
m_current_protocol->requestStart();
|
|
m_state = GETTING_PUBLIC_ADDRESS;
|
|
// The callback from GetPublicAddress will wake this protocol up
|
|
requestPause();
|
|
break;
|
|
case GETTING_PUBLIC_ADDRESS:
|
|
{
|
|
Log::debug("ServerLobby", "Public address known.");
|
|
// Free GetPublicAddress protocol
|
|
delete m_current_protocol;
|
|
|
|
// Register this server with the STK server. This will block
|
|
// this thread, but there is no need for the protocol manager
|
|
// to react to any requests before the server is registered.
|
|
registerServer();
|
|
Log::info("ServerLobby", "Server registered.");
|
|
m_state = ACCEPTING_CLIENTS;
|
|
}
|
|
break;
|
|
case ACCEPTING_CLIENTS:
|
|
{
|
|
// Only poll the STK server if this is a WAN server.
|
|
if(NetworkConfig::get()->isWAN())
|
|
checkIncomingConnectionRequests();
|
|
break;
|
|
}
|
|
case SELECTING:
|
|
// The function playerTrackVote will trigger the next state
|
|
// once all track votes have been received.
|
|
break;
|
|
case LOAD_WORLD:
|
|
Log::info("ServerLobbyRoom", "Starting the race loading.");
|
|
// This will create the world instance, i.e. load track and karts
|
|
loadWorld();
|
|
m_state = WAIT_FOR_WORLD_LOADED;
|
|
break;
|
|
case WAIT_FOR_WORLD_LOADED:
|
|
// Note that m_server_has_loaded_world is called by the main thread
|
|
// (same a the thread updating this protocol)
|
|
m_client_ready_count.lock();
|
|
if (m_server_has_loaded_world &&
|
|
m_client_ready_count.getData() == m_game_setup->getPlayerCount())
|
|
{
|
|
signalRaceStartToClients();
|
|
m_server_delay = 0.02f;
|
|
m_client_ready_count.getData() = 0;
|
|
}
|
|
m_client_ready_count.unlock();
|
|
// Initialise counter again, to wait for all clients to indicate that
|
|
// they have started the race/
|
|
break;
|
|
case WAIT_FOR_RACE_STARTED:
|
|
// The function finishedLoadingWorldClient() will trigger the
|
|
// next state.
|
|
break;
|
|
case DELAY_SERVER:
|
|
m_server_delay -= dt;
|
|
if (m_server_delay < 0)
|
|
{
|
|
m_state = RACING;
|
|
World::getWorld()->setReadyToRace();
|
|
}
|
|
break;
|
|
case RACING:
|
|
if (World::getWorld() &&
|
|
RaceEventManager::getInstance<RaceEventManager>()->isRunning())
|
|
{
|
|
checkRaceFinished();
|
|
}
|
|
break;
|
|
case RESULT_DISPLAY:
|
|
if(StkTime::getRealTime() > m_timeout)
|
|
{
|
|
// Send a notification to all clients to exit
|
|
// the race result screen
|
|
NetworkString *exit_result_screen = getNetworkString(1);
|
|
exit_result_screen->setSynchronous(true);
|
|
exit_result_screen->addUInt8(LE_EXIT_RESULT);
|
|
sendMessageToPeersChangingToken(exit_result_screen,
|
|
/*reliable*/true);
|
|
delete exit_result_screen;
|
|
m_state = ACCEPTING_CLIENTS;
|
|
RaceResultGUI::getInstance()->backToLobby();
|
|
// notify the network world that it is stopped
|
|
RaceEventManager::getInstance()->stop();
|
|
// stop race protocols
|
|
findAndTerminateProtocol(PROTOCOL_CONTROLLER_EVENTS);
|
|
findAndTerminateProtocol(PROTOCOL_KART_UPDATE);
|
|
findAndTerminateProtocol(PROTOCOL_GAME_EVENTS);
|
|
}
|
|
break;
|
|
case DONE:
|
|
m_state = EXITING;
|
|
requestTerminate();
|
|
break;
|
|
case EXITING:
|
|
break;
|
|
}
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Callback when the GetPublicAddress terminates. It will unpause this
|
|
* protocol, which triggers the next state of the finite state machine.
|
|
*/
|
|
void ServerLobby::callback(Protocol *protocol)
|
|
{
|
|
requestUnpause();
|
|
} // callback
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Register this server (i.e. its public address) with the STK server
|
|
* so that clients can find it. It blocks till a response from the
|
|
* stk server is received (this function is executed from the
|
|
* ProtocolManager thread). The information about this client is added
|
|
* to the table 'server'.
|
|
*/
|
|
void ServerLobby::registerServer()
|
|
{
|
|
Online::XMLRequest *request = new Online::XMLRequest();
|
|
const TransportAddress& addr = NetworkConfig::get()->getMyAddress();
|
|
PlayerManager::setUserDetails(request, "create", Online::API::SERVER_PATH);
|
|
request->addParameter("address", addr.getIP() );
|
|
request->addParameter("port", addr.getPort() );
|
|
request->addParameter("private_port",
|
|
NetworkConfig::get()->getServerPort() );
|
|
request->addParameter("name", NetworkConfig::get()->getServerName() );
|
|
request->addParameter("max_players",
|
|
UserConfigParams::m_server_max_players );
|
|
Log::info("RegisterServer", "Showing addr %s", addr.toString().c_str());
|
|
|
|
request->executeNow();
|
|
|
|
const XMLNode * result = request->getXMLData();
|
|
std::string rec_success;
|
|
|
|
if (result->get("success", &rec_success) && rec_success == "yes")
|
|
{
|
|
Log::info("RegisterServer", "Server is now online.");
|
|
STKHost::get()->setRegistered(true);
|
|
}
|
|
else
|
|
{
|
|
irr::core::stringc error(request->getInfo().c_str());
|
|
Log::error("RegisterServer", "%s", error.c_str());
|
|
STKHost::get()->setErrorMessage(_("Failed to register server: %s", error.c_str()));
|
|
}
|
|
|
|
} // registerServer
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** This function is called when all clients have loaded the world and
|
|
* are therefore ready to start the race. It signals to all clients
|
|
* to start the race and then switches state to DELAY_SERVER.
|
|
*/
|
|
void ServerLobby::signalRaceStartToClients()
|
|
{
|
|
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
|
|
NetworkString *ns = getNetworkString(1);
|
|
ns->addUInt8(LE_START_RACE);
|
|
sendMessageToPeersChangingToken(ns, /*reliable*/true);
|
|
delete ns;
|
|
m_state = WAIT_FOR_RACE_STARTED;
|
|
} // startGame
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Instructs all clients to start the kart selection. If event is not NULL,
|
|
* the command comes from a client (which needs to be authorised).
|
|
*/
|
|
void ServerLobby::startSelection(const Event *event)
|
|
{
|
|
|
|
if (m_state != ACCEPTING_CLIENTS)
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Received startSelection while being in state %d", m_state);
|
|
return;
|
|
}
|
|
if(event && !event->getPeer()->isAuthorised())
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Client %lx is not authorised to start selection.",
|
|
event->getPeer());
|
|
return;
|
|
}
|
|
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
|
|
NetworkString *ns = getNetworkString(1);
|
|
// start selection
|
|
ns->addUInt8(LE_START_SELECTION);
|
|
sendMessageToPeersChangingToken(ns, /*reliable*/true);
|
|
delete ns;
|
|
|
|
m_selection_enabled = true;
|
|
|
|
m_state = SELECTING;
|
|
WaitingForOthersScreen::getInstance()->push();
|
|
|
|
Protocol *p = new LatencyProtocol();
|
|
p->requestStart();
|
|
Log::info("LobbyProtocol", "LatencyProtocol started.");
|
|
|
|
} // startSelection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Query the STK server for connection requests. For each connection request
|
|
* start a ConnectToPeer protocol.
|
|
*/
|
|
void ServerLobby::checkIncomingConnectionRequests()
|
|
{
|
|
// First poll every 5 seconds. Return if no polling needs to be done.
|
|
const float POLL_INTERVAL = 5.0f;
|
|
static double last_poll_time = 0;
|
|
if (StkTime::getRealTime() < last_poll_time + POLL_INTERVAL)
|
|
return;
|
|
|
|
// Now poll the stk server
|
|
last_poll_time = StkTime::getRealTime();
|
|
Online::XMLRequest* request = new Online::XMLRequest();
|
|
PlayerManager::setUserDetails(request, "poll-connection-requests",
|
|
Online::API::SERVER_PATH);
|
|
|
|
const TransportAddress &addr = NetworkConfig::get()->getMyAddress();
|
|
request->addParameter("address", addr.getIP() );
|
|
request->addParameter("port", addr.getPort());
|
|
|
|
request->executeNow();
|
|
assert(request->isDone());
|
|
|
|
const XMLNode *result = request->getXMLData();
|
|
std::string success;
|
|
|
|
if (!result->get("success", &success) || success != "yes")
|
|
{
|
|
Log::error("ServerLobby", "Cannot retrieve the list.");
|
|
return;
|
|
}
|
|
|
|
// Now start a ConnectToPeer protocol for each connection request
|
|
const XMLNode * users_xml = result->getNode("users");
|
|
uint32_t id = 0;
|
|
for (unsigned int i = 0; i < users_xml->getNumNodes(); i++)
|
|
{
|
|
users_xml->getNode(i)->get("id", &id);
|
|
Log::debug("ServerLobby",
|
|
"User with id %d wants to connect.", id);
|
|
Protocol *p = new ConnectToPeer(id);
|
|
p->requestStart();
|
|
}
|
|
delete request;
|
|
} // checkIncomingConnectionRequests
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Checks if the race is finished, and if so informs the clients and switches
|
|
* to state RESULT_DISPLAY, during which the race result gui is shown and all
|
|
* clients can click on 'continue'.
|
|
*/
|
|
void ServerLobby::checkRaceFinished()
|
|
{
|
|
assert(RaceEventManager::getInstance()->isRunning());
|
|
assert(World::getWorld());
|
|
if(!RaceEventManager::getInstance()->isRaceOver()) return;
|
|
|
|
m_player_ready_counter = 0;
|
|
// Set the delay before the server forces all clients to exit the race
|
|
// result screen and go back to the lobby
|
|
m_timeout = (float)(StkTime::getRealTime()+15.0f);
|
|
m_state = RESULT_DISPLAY;
|
|
|
|
// calculate karts ranks :
|
|
int num_karts = race_manager->getNumberOfKarts();
|
|
std::vector<int> karts_results;
|
|
std::vector<float> karts_times;
|
|
for (int j = 0; j < num_karts; j++)
|
|
{
|
|
float kart_time = race_manager->getKartRaceTime(j);
|
|
for (unsigned int i = 0; i < karts_times.size(); i++)
|
|
{
|
|
if (kart_time < karts_times[i])
|
|
{
|
|
karts_times.insert(karts_times.begin() + i, kart_time);
|
|
karts_results.insert(karts_results.begin() + i, j);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const std::vector<STKPeer*> &peers = STKHost::get()->getPeers();
|
|
|
|
NetworkString *total = getNetworkString(1 + karts_results.size());
|
|
total->setSynchronous(true);
|
|
total->addUInt8(LE_RACE_FINISHED);
|
|
for (unsigned int i = 0; i < karts_results.size(); i++)
|
|
{
|
|
total->addUInt8(karts_results[i]); // kart pos = i+1
|
|
Log::info("ServerLobby", "Kart %d finished #%d",
|
|
karts_results[i], i + 1);
|
|
}
|
|
sendMessageToPeersChangingToken(total, /*reliable*/ true);
|
|
delete total;
|
|
Log::info("ServerLobby", "End of game message sent");
|
|
|
|
} // checkRaceFinished
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when a client disconnects.
|
|
* \param event The disconnect event.
|
|
*/
|
|
void ServerLobby::clientDisconnected(Event* event)
|
|
{
|
|
std::vector<NetworkPlayerProfile*> players_on_host =
|
|
event->getPeer()->getAllPlayerProfiles();
|
|
|
|
NetworkString *msg = getNetworkString(2);
|
|
msg->addUInt8(LE_PLAYER_DISCONNECTED);
|
|
|
|
for(unsigned int i=0; i<players_on_host.size(); i++)
|
|
{
|
|
msg->addUInt8(players_on_host[i]->getGlobalPlayerId());
|
|
Log::info("ServerLobby", "Player disconnected : id %d",
|
|
players_on_host[i]->getGlobalPlayerId());
|
|
m_game_setup->removePlayer(players_on_host[i]);
|
|
}
|
|
|
|
sendMessageToPeersChangingToken(msg, /*reliable*/true);
|
|
// Remove the profile from the peer (to avoid double free)
|
|
STKHost::get()->removePeer(event->getPeer());
|
|
delete msg;
|
|
|
|
} // clientDisconnected
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! \brief Called when a player asks for a connection.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1
|
|
* ---------------------
|
|
* Size | 1 |1| |
|
|
* Data | 4 |n| player name |
|
|
* ---------------------
|
|
*/
|
|
void ServerLobby::connectionRequested(Event* event)
|
|
{
|
|
STKPeer* peer = event->getPeer();
|
|
const NetworkString &data = event->data();
|
|
|
|
// can we add the player ?
|
|
if (m_game_setup->getPlayerCount() >= NetworkConfig::get()->getMaxPlayers() ||
|
|
m_state!=ACCEPTING_CLIENTS )
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
// Len, error code: 2 = busy, 0 = too many players
|
|
message->addUInt8(LE_CONNECTION_REFUSED)
|
|
.addUInt8(m_state!=ACCEPTING_CLIENTS ? 2 : 0);
|
|
|
|
// send only to the peer that made the request
|
|
peer->sendPacket(message);
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused");
|
|
return;
|
|
}
|
|
|
|
// Connection accepted.
|
|
// ====================
|
|
std::string name_u8;
|
|
int len = data.decodeString(&name_u8);
|
|
core::stringw name = StringUtils::utf8ToWide(name_u8);
|
|
std::string password;
|
|
data.decodeString(&password);
|
|
bool is_authorised = (password==NetworkConfig::get()->getPassword());
|
|
|
|
// Get the unique global ID for this player.
|
|
m_next_player_id.lock();
|
|
m_next_player_id.getData()++;
|
|
int new_player_id = m_next_player_id.getData();
|
|
m_next_player_id.unlock();
|
|
if(m_game_setup->getLocalMasterID()==0)
|
|
m_game_setup->setLocalMaster(new_player_id);
|
|
|
|
// The host id has already been incremented when the peer
|
|
// was added, so it is the right id now.
|
|
int new_host_id = STKHost::get()->getNextHostId();
|
|
|
|
// Notify everybody that there is a new player
|
|
// -------------------------------------------
|
|
NetworkString *message = getNetworkString(3+1+name_u8.size());
|
|
// size of id -- id -- size of local id -- local id;
|
|
message->addUInt8(LE_NEW_PLAYER_CONNECTED).addUInt8(new_player_id)
|
|
.addUInt8(new_host_id).encodeString(name_u8);
|
|
STKHost::get()->sendPacketExcept(peer, message);
|
|
delete message;
|
|
|
|
// Now answer to the peer that just connected
|
|
// ------------------------------------------
|
|
RandomGenerator token_generator;
|
|
// use 4 random numbers because rand_max is probably 2^15-1.
|
|
uint32_t token = (uint32_t)((token_generator.get(RAND_MAX) & 0xff) << 24 |
|
|
(token_generator.get(RAND_MAX) & 0xff) << 16 |
|
|
(token_generator.get(RAND_MAX) & 0xff) << 8 |
|
|
(token_generator.get(RAND_MAX) & 0xff));
|
|
|
|
peer->setClientServerToken(token);
|
|
peer->setAuthorised(is_authorised);
|
|
peer->setHostId(new_host_id);
|
|
|
|
const std::vector<NetworkPlayerProfile*> &players = m_game_setup->getPlayers();
|
|
// send a message to the one that asked to connect
|
|
// Estimate 10 as average name length
|
|
NetworkString *message_ack = getNetworkString(4 + players.size() * (2+10));
|
|
// connection success -- size of token -- token
|
|
message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt8(new_player_id)
|
|
.addUInt8(new_host_id).addUInt8(is_authorised);
|
|
// Add all players so that this user knows (this new player is only added
|
|
// to the list of players later, so the new player's info is not included)
|
|
for (unsigned int i = 0; i < players.size(); i++)
|
|
{
|
|
message_ack->addUInt8(players[i]->getGlobalPlayerId())
|
|
.addUInt8(players[i]->getHostId())
|
|
.encodeString(players[i]->getName());
|
|
}
|
|
peer->sendPacket(message_ack);
|
|
delete message_ack;
|
|
|
|
NetworkPlayerProfile* profile =
|
|
new NetworkPlayerProfile(name, new_player_id, new_host_id);
|
|
m_game_setup->addPlayer(profile);
|
|
NetworkingLobby::getInstance()->addPlayer(profile);
|
|
|
|
Log::verbose("ServerLobby", "New player.");
|
|
|
|
} // connectionRequested
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! \brief Called when a player asks to select a kart.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1 2
|
|
* ----------------------------------------------
|
|
* Size | 1 | 1 | N |
|
|
* Data |player id | N (kart name size) | kart name |
|
|
* ----------------------------------------------
|
|
*/
|
|
void ServerLobby::kartSelectionRequested(Event* event)
|
|
{
|
|
if(m_state!=SELECTING)
|
|
{
|
|
Log::warn("Server", "Received kart selection while in state %d.",
|
|
m_state);
|
|
return;
|
|
}
|
|
|
|
if (!checkDataSize(event, 1)) return;
|
|
|
|
const NetworkString &data = event->data();
|
|
STKPeer* peer = event->getPeer();
|
|
|
|
uint8_t player_id = data.getUInt8();
|
|
std::string kart_name;
|
|
data.decodeString(&kart_name);
|
|
// check if selection is possible
|
|
if (!m_selection_enabled)
|
|
{
|
|
NetworkString *answer = getNetworkString(2);
|
|
// selection still not started
|
|
answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(2);
|
|
peer->sendPacket(answer);
|
|
delete answer;
|
|
return;
|
|
}
|
|
// check if somebody picked that kart
|
|
if (!m_game_setup->isKartAvailable(kart_name))
|
|
{
|
|
NetworkString *answer = getNetworkString(2);
|
|
// kart is already taken
|
|
answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(0);
|
|
peer->sendPacket(answer);
|
|
delete answer;
|
|
return;
|
|
}
|
|
// check if this kart is authorized
|
|
if (!m_game_setup->isKartAllowed(kart_name))
|
|
{
|
|
NetworkString *answer = getNetworkString(2);
|
|
// kart is not authorized
|
|
answer->addUInt8(LE_KART_SELECTION_REFUSED).addUInt8(1);
|
|
peer->sendPacket(answer);
|
|
delete answer;
|
|
return;
|
|
}
|
|
|
|
// send a kart update to everyone
|
|
NetworkString *answer = getNetworkString(3+kart_name.size());
|
|
// This message must be handled synchronously on the client.
|
|
answer->setSynchronous(true);
|
|
// kart update (3), 1, race id
|
|
answer->addUInt8(LE_KART_SELECTION_UPDATE).addUInt8(player_id)
|
|
.encodeString(kart_name);
|
|
sendMessageToPeersChangingToken(answer);
|
|
delete answer;
|
|
m_game_setup->setPlayerKart(player_id, kart_name);
|
|
} // kartSelectionRequested
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! \brief Called when a player votes for a major race mode.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1
|
|
* -------------------------------
|
|
* Size | 1 | 4 |
|
|
* Data | player-id | major mode vote |
|
|
* -------------------------------
|
|
*/
|
|
void ServerLobby::playerMajorVote(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 5)) return;
|
|
|
|
NetworkString &data = event->data();
|
|
uint8_t player_id = data.getUInt8();
|
|
uint32_t major = data.getUInt32();
|
|
m_game_setup->getRaceConfig()->setPlayerMajorVote(player_id, major);
|
|
// Send the vote to everybody (including the sender)
|
|
NetworkString *other = getNetworkString(6);
|
|
other->addUInt8(LE_VOTE_MAJOR).addUInt8(player_id).addUInt32(major);
|
|
sendMessageToPeersChangingToken(other);
|
|
delete other;
|
|
} // playerMajorVote
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** \brief Called when a player votes for the number of races in a GP.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1
|
|
* ---------------------------
|
|
* Size | 1 | 4 |
|
|
* Data | player-id | races count |
|
|
* ---------------------------
|
|
*/
|
|
void ServerLobby::playerRaceCountVote(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 1)) return;
|
|
NetworkString &data = event->data();
|
|
uint8_t player_id = data.getUInt8();
|
|
uint8_t race_count = data.getUInt8();
|
|
m_game_setup->getRaceConfig()->setPlayerRaceCountVote(player_id, race_count);
|
|
// Send the vote to everybody (including the sender)
|
|
NetworkString *other = getNetworkString(3);
|
|
other->addUInt8(LE_VOTE_RACE_COUNT).addUInt8(player_id)
|
|
.addUInt8(race_count);
|
|
sendMessageToPeersChangingToken(other);
|
|
delete other;
|
|
} // playerRaceCountVote
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! \brief Called when a player votes for a minor race mode.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1
|
|
* -------------------------------
|
|
* Size | 1 | 4 |
|
|
* Data | player-id | minor mode vote |
|
|
* -------------------------------
|
|
*/
|
|
void ServerLobby::playerMinorVote(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 1)) return;
|
|
NetworkString &data = event->data();
|
|
uint8_t player_id = data.getUInt8();
|
|
uint32_t minor = data.getUInt32();
|
|
m_game_setup->getRaceConfig()->setPlayerMinorVote(player_id, minor);
|
|
|
|
// Send the vote to everybody (including the sender)
|
|
NetworkString *other = getNetworkString(3);
|
|
other->addUInt8(LE_VOTE_MINOR).addUInt8(player_id).addUInt8(minor);
|
|
sendMessageToPeersChangingToken(other);
|
|
delete other;
|
|
} // playerMinorVote
|
|
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/*! \brief Called when a player votes for a track.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1 2 3
|
|
* --------------------------------------------------
|
|
* Size | 1 | 1 | 1 | N |
|
|
* Data | player id | track number (gp) | N | track name |
|
|
* --------------------------------------------------
|
|
*/
|
|
void ServerLobby::playerTrackVote(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 3)) return;
|
|
NetworkString &data = event->data();
|
|
uint8_t player_id = data.getUInt8();
|
|
// As which track this track should be used, e.g. 1st track: Sandtrack
|
|
// 2nd track Mathclass, ...
|
|
uint8_t track_number = data.getUInt8();
|
|
std::string track_name;
|
|
int N = data.decodeString(&track_name);
|
|
m_game_setup->getRaceConfig()->setPlayerTrackVote(player_id, track_name,
|
|
track_number);
|
|
// Send the vote to everybody (including the sender)
|
|
NetworkString *other = getNetworkString(3+1+data.size());
|
|
other->addUInt8(LE_VOTE_TRACK).addUInt8(player_id).addUInt8(track_number)
|
|
.encodeString(track_name);
|
|
sendMessageToPeersChangingToken(other);
|
|
delete other;
|
|
|
|
// Check if we received all information
|
|
if (m_game_setup->getRaceConfig()->getNumTrackVotes() ==
|
|
m_game_setup->getPlayerCount())
|
|
{
|
|
// Inform clients to start loading the world
|
|
NetworkString *ns = getNetworkString(1);
|
|
ns->setSynchronous(true);
|
|
ns->addUInt8(LE_LOAD_WORLD);
|
|
sendMessageToPeersChangingToken(ns, /*reliable*/true);
|
|
delete ns;
|
|
m_state = LOAD_WORLD; // Server can now load world
|
|
}
|
|
} // playerTrackVote
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! \brief Called when a player votes for the reverse mode of a race
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1 2
|
|
* --------------------------------------------
|
|
* Size | 1 | 1 | 1 |
|
|
* Data | player id | reversed | track number (gp) |
|
|
* --------------------------------------------
|
|
*/
|
|
void ServerLobby::playerReversedVote(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 3)) return;
|
|
|
|
NetworkString &data = event->data();
|
|
uint8_t player_id = data.getUInt8();
|
|
uint8_t reverse = data.getUInt8();
|
|
uint8_t nb_track = data.getUInt8();
|
|
m_game_setup->getRaceConfig()->setPlayerReversedVote(player_id,
|
|
reverse!=0, nb_track);
|
|
// Send the vote to everybody (including the sender)
|
|
NetworkString *other = getNetworkString(4);
|
|
other->addUInt8(LE_VOTE_REVERSE).addUInt8(player_id).addUInt8(reverse)
|
|
.addUInt8(nb_track);
|
|
sendMessageToPeersChangingToken(other);
|
|
delete other;
|
|
} // playerReversedVote
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! \brief Called when a player votes for a major race mode.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1 2
|
|
* ----------------------------------------
|
|
* Size | 1 | 1 | 1 |
|
|
* Data | player id | laps | track number (gp) |
|
|
* ----------------------------------------
|
|
*/
|
|
void ServerLobby::playerLapsVote(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 2)) return;
|
|
NetworkString &data = event->data();
|
|
uint8_t player_id = data.getUInt8();
|
|
uint8_t lap_count = data.getUInt8();
|
|
uint8_t track_nb = data.getUInt8();
|
|
m_game_setup->getRaceConfig()->setPlayerLapsVote(player_id, lap_count,
|
|
track_nb);
|
|
NetworkString *other = getNetworkString(4);
|
|
other->addUInt8(LE_VOTE_LAPS).addUInt8(player_id).addUInt8(lap_count)
|
|
.addUInt8(track_nb);
|
|
sendMessageToPeersChangingToken(other);
|
|
delete other;
|
|
} // playerLapsVote
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Called from the RaceManager of the server when the world is loaded. Marks
|
|
* the server to be ready to start the race.
|
|
*/
|
|
void ServerLobby::finishedLoadingWorld()
|
|
{
|
|
m_server_has_loaded_world = true;
|
|
} // finishedLoadingWorld;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when a client notifies the server that it has loaded the world.
|
|
* When all clients and the server are ready, the race can be started.
|
|
*/
|
|
void ServerLobby::finishedLoadingWorldClient(Event *event)
|
|
{
|
|
if (!checkDataSize(event, 1)) return;
|
|
|
|
const NetworkString &data = event->data();
|
|
uint8_t player_count = data.getUInt8();
|
|
m_client_ready_count.lock();
|
|
for (unsigned int i = 0; i < player_count; i++)
|
|
{
|
|
uint8_t player_id = data.getUInt8();
|
|
if (m_player_states[player_id])
|
|
{
|
|
Log::error("ServerLobbyProtocol",
|
|
"Player %d send more than one ready message.",
|
|
player_id);
|
|
m_client_ready_count.unlock();
|
|
return;
|
|
}
|
|
m_player_states[player_id] = true;
|
|
m_client_ready_count.getData()++;
|
|
Log::info("ServerLobbyeProtocol", "Player %d is ready (%d/%d).",
|
|
player_id, m_client_ready_count.getData(),
|
|
m_game_setup->getPlayerCount());
|
|
}
|
|
m_client_ready_count.unlock();
|
|
|
|
} // finishedLoadingWorldClient
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when a notification from a client is received that the client has
|
|
* started the race. Once all clients have informed the server that they
|
|
* have started the race, the server can start. This makes sure that the
|
|
* server's local time is behind all clients by (at least) their latency,
|
|
* which in turn means that when the server simulates local time T all
|
|
* messages from clients at their local time T should have arrived at
|
|
* the server, which creates smoother play experience.
|
|
*/
|
|
void ServerLobby::startedRaceOnClient(Event *event)
|
|
{
|
|
m_client_ready_count.lock();
|
|
Log::verbose("ServerLobby", "Host %d has started race.",
|
|
event->getPeer()->getHostId());
|
|
m_client_ready_count.getData()++;
|
|
if (m_client_ready_count.getData() == m_game_setup->getPlayerCount())
|
|
{
|
|
m_state = DELAY_SERVER;
|
|
terminateLatencyProtocol();
|
|
}
|
|
m_client_ready_count.unlock();
|
|
} // startedRaceOnClient
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when a client clicks on 'ok' on the race result screen.
|
|
* If all players have clicked on 'ok', go back to the lobby.
|
|
*/
|
|
void ServerLobby::playerFinishedResult(Event *event)
|
|
{
|
|
m_player_ready_counter++;
|
|
if(m_player_ready_counter == STKHost::get()->getPeerCount())
|
|
{
|
|
// We can't trigger the world/race exit here, since this is called
|
|
// from the protocol manager thread. So instead we force the timeout
|
|
// to get triggered (which is done from the main thread):
|
|
m_timeout = 0;
|
|
}
|
|
} // playerFinishedResult
|
|
|
|
//-----------------------------------------------------------------------------
|