3708 lines
131 KiB
C++
3708 lines
131 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/user_config.hpp"
|
|
#include "items/network_item_manager.hpp"
|
|
#include "items/powerup_manager.hpp"
|
|
#include "karts/abstract_kart.hpp"
|
|
#include "karts/controller/player_controller.hpp"
|
|
#include "karts/kart_properties.hpp"
|
|
#include "karts/kart_properties_manager.hpp"
|
|
#include "modes/capture_the_flag.hpp"
|
|
#include "modes/linear_world.hpp"
|
|
#include "network/crypto.hpp"
|
|
#include "network/event.hpp"
|
|
#include "network/game_setup.hpp"
|
|
#include "network/network_config.hpp"
|
|
#include "network/network_player_profile.hpp"
|
|
#include "network/peer_vote.hpp"
|
|
#include "network/protocol_manager.hpp"
|
|
#include "network/protocols/connect_to_peer.hpp"
|
|
#include "network/protocols/game_protocol.hpp"
|
|
#include "network/protocols/game_events_protocol.hpp"
|
|
#include "network/race_event_manager.hpp"
|
|
#include "network/server_config.hpp"
|
|
#include "network/stk_host.hpp"
|
|
#include "network/stk_peer.hpp"
|
|
#include "online/online_profile.hpp"
|
|
#include "online/request_manager.hpp"
|
|
#include "race/race_manager.hpp"
|
|
#include "states_screens/online/networking_lobby.hpp"
|
|
#include "states_screens/race_result_gui.hpp"
|
|
#include "tracks/check_manager.hpp"
|
|
#include "tracks/track.hpp"
|
|
#include "tracks/track_manager.hpp"
|
|
#include "utils/log.hpp"
|
|
#include "utils/random_generator.hpp"
|
|
#include "utils/time.hpp"
|
|
|
|
#include <algorithm>
|
|
#include <fstream>
|
|
#include <iterator>
|
|
|
|
/** 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" -> "WAITING_FOR_START_GAME" [label="If LAN game"]
|
|
"INIT_WAN" -> "GETTING_PUBLIC_ADDRESS" [label="GetPublicAddress protocol callback"]
|
|
"GETTING_PUBLIC_ADDRESS" -> "WAITING_FOR_START_GAME" [label="Register server"]
|
|
"WAITING_FOR_START_GAME" -> "connectionRequested" [label="Client connection request"]
|
|
"connectionRequested" -> "WAITING_FOR_START_GAME"
|
|
"WAITING_FOR_START_GAME" -> "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)
|
|
{
|
|
std::vector<int> all_k =
|
|
kart_properties_manager->getKartsInGroup("standard");
|
|
std::vector<int> all_t =
|
|
track_manager->getTracksInGroup("standard");
|
|
std::vector<int> all_arenas =
|
|
track_manager->getArenasInGroup("standard", false);
|
|
std::vector<int> all_soccers =
|
|
track_manager->getArenasInGroup("standard", true);
|
|
all_t.insert(all_t.end(), all_arenas.begin(), all_arenas.end());
|
|
all_t.insert(all_t.end(), all_soccers.begin(), all_soccers.end());
|
|
|
|
for (int kart : all_k)
|
|
{
|
|
const KartProperties* kp = kart_properties_manager->getKartById(kart);
|
|
if (!kp->isAddon())
|
|
m_official_kts.first.insert(kp->getIdent());
|
|
}
|
|
for (int track : all_t)
|
|
{
|
|
Track* t = track_manager->getTrack(track);
|
|
if (!t->isAddon())
|
|
m_official_kts.second.insert(t->getIdent());
|
|
}
|
|
|
|
m_rs_state.store(RS_NONE);
|
|
m_last_success_poll_time.store(StkTime::getRealTimeMs() + 30000);
|
|
m_server_owner_id.store(-1);
|
|
m_registered_for_once_only = false;
|
|
m_has_created_server_id_file = false;
|
|
setHandleDisconnections(true);
|
|
m_state = SET_PUBLIC_ADDRESS;
|
|
m_save_server_config = true;
|
|
updateBanList();
|
|
if (ServerConfig::m_ranked)
|
|
{
|
|
Log::info("ServerLobby", "This server will submit ranking scores to "
|
|
"the STK addons server. Don't bother hosting one without the "
|
|
"corresponding permissions, as they would be rejected.");
|
|
}
|
|
m_result_ns = getNetworkString();
|
|
m_result_ns->setSynchronous(true);
|
|
m_items_complete_state = new BareNetworkString();
|
|
m_server_id_online.store(0);
|
|
m_difficulty.store(ServerConfig::m_server_difficulty);
|
|
m_game_mode.store(ServerConfig::m_server_mode);
|
|
m_default_vote = new PeerVote();
|
|
} // ServerLobby
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Destructor.
|
|
*/
|
|
ServerLobby::~ServerLobby()
|
|
{
|
|
if (NetworkConfig::get()->isNetworking() &&
|
|
NetworkConfig::get()->isWAN())
|
|
{
|
|
unregisterServer(true/*now*/);
|
|
}
|
|
delete m_result_ns;
|
|
delete m_items_complete_state;
|
|
if (m_save_server_config)
|
|
ServerConfig::writeServerConfigToDisk();
|
|
delete m_default_vote;
|
|
} // ~ServerLobby
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called whenever server is reset or game mode is changed.
|
|
*/
|
|
void ServerLobby::updateTracksForMode()
|
|
{
|
|
auto all_t = track_manager->getAllTrackIdentifiers();
|
|
if (all_t.size() >= 65536)
|
|
all_t.resize(65535);
|
|
m_available_kts.second = { all_t.begin(), all_t.end() };
|
|
RaceManager::MinorRaceModeType m =
|
|
ServerConfig::getLocalGameMode(m_game_mode.load()).first;
|
|
switch (m)
|
|
{
|
|
case RaceManager::MINOR_MODE_NORMAL_RACE:
|
|
case RaceManager::MINOR_MODE_TIME_TRIAL:
|
|
case RaceManager::MINOR_MODE_FOLLOW_LEADER:
|
|
{
|
|
auto it = m_available_kts.second.begin();
|
|
while (it != m_available_kts.second.end())
|
|
{
|
|
Track* t = track_manager->getTrack(*it);
|
|
if (t->isArena() || t->isSoccer() || t->isInternal())
|
|
{
|
|
it = m_available_kts.second.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
break;
|
|
}
|
|
case RaceManager::MINOR_MODE_FREE_FOR_ALL:
|
|
case RaceManager::MINOR_MODE_CAPTURE_THE_FLAG:
|
|
{
|
|
auto it = m_available_kts.second.begin();
|
|
while (it != m_available_kts.second.end())
|
|
{
|
|
Track* t = track_manager->getTrack(*it);
|
|
if (race_manager->getMinorMode() ==
|
|
RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
|
|
{
|
|
if (!t->isCTF() || t->isInternal())
|
|
{
|
|
it = m_available_kts.second.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
else
|
|
{
|
|
if (!t->isArena() || t->isInternal())
|
|
{
|
|
it = m_available_kts.second.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case RaceManager::MINOR_MODE_SOCCER:
|
|
{
|
|
auto it = m_available_kts.second.begin();
|
|
while (it != m_available_kts.second.end())
|
|
{
|
|
Track* t = track_manager->getTrack(*it);
|
|
if (!t->isSoccer() || t->isInternal())
|
|
{
|
|
it = m_available_kts.second.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
} // updateTracksForMode
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::setup()
|
|
{
|
|
LobbyProtocol::setup();
|
|
m_battle_hit_capture_limit = 0;
|
|
m_battle_time_limit = 0.0f;
|
|
m_item_seed = 0;
|
|
m_winner_peer_id = 0;
|
|
m_client_starting_time = 0;
|
|
auto players = STKHost::get()->getPlayersForNewGame();
|
|
if (m_game_setup->isGrandPrix() && !m_game_setup->isGrandPrixStarted())
|
|
{
|
|
for (auto player : players)
|
|
player->resetGrandPrixData();
|
|
}
|
|
if (!m_game_setup->isGrandPrix() || !m_game_setup->isGrandPrixStarted())
|
|
{
|
|
for (auto player : players)
|
|
player->setKartName("");
|
|
}
|
|
|
|
StateManager::get()->resetActivePlayers();
|
|
// We use maximum 16bit unsigned limit
|
|
auto all_k = kart_properties_manager->getAllAvailableKarts();
|
|
if (all_k.size() >= 65536)
|
|
all_k.resize(65535);
|
|
if (ServerConfig::m_live_players)
|
|
m_available_kts.first = m_official_kts.first;
|
|
else
|
|
m_available_kts.first = { all_k.begin(), all_k.end() };
|
|
updateTracksForMode();
|
|
|
|
m_server_has_loaded_world.store(false);
|
|
|
|
// Initialise the data structures to detect if all clients and
|
|
// the server are ready:
|
|
resetPeersReady();
|
|
m_timeout.store(std::numeric_limits<int64_t>::max());
|
|
m_server_started_at = m_server_delay = 0;
|
|
Log::info("ServerLobby", "Resetting the server to its initial state.");
|
|
} // setup
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ServerLobby::notifyEvent(Event* event)
|
|
{
|
|
assert(m_game_setup); // assert that the setup exists
|
|
if (event->getType() != EVENT_TYPE_MESSAGE)
|
|
return false;
|
|
|
|
NetworkString &data = event->data();
|
|
assert(data.size()); // message not empty
|
|
uint8_t message_type;
|
|
message_type = data.getUInt8();
|
|
Log::info("ServerLobby", "Synchronous message of type %d received.",
|
|
message_type);
|
|
switch (message_type)
|
|
{
|
|
case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break;
|
|
case LE_LIVE_JOIN: liveJoinRequest(event); break;
|
|
case LE_CLIENT_LOADED_WORLD: finishedLoadingLiveJoinClient(event); break;
|
|
case LE_KART_INFO: handleKartInfo(event); break;
|
|
case LE_CLIENT_BACK_LOBBY: clientInGameWantsToBackLobby(event); break;
|
|
default: Log::error("ServerLobby", "Unknown message of type %d - ignored.",
|
|
message_type);
|
|
break;
|
|
} // switch message_type
|
|
return true;
|
|
} // notifyEvent
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::handleChat(Event* event)
|
|
{
|
|
if (!checkDataSize(event, 1) || !ServerConfig::m_chat) return;
|
|
|
|
// Update so that the peer is not kicked
|
|
event->getPeer()->updateLastActivity();
|
|
const bool sender_in_game = event->getPeer()->isWaitingForGame();
|
|
core::stringw message;
|
|
event->data().decodeString16(&message);
|
|
if (message.size() > 0)
|
|
{
|
|
NetworkString* chat = getNetworkString();
|
|
chat->setSynchronous(true);
|
|
chat->addUInt8(LE_CHAT).encodeString16(message);
|
|
const bool game_started = m_state.load() != WAITING_FOR_START_GAME;
|
|
STKHost::get()->sendPacketToAllPeersWith(
|
|
[game_started, sender_in_game](STKPeer* p)
|
|
{
|
|
if (!p->isValidated())
|
|
return false;
|
|
if (game_started)
|
|
{
|
|
if (p->isWaitingForGame() && !sender_in_game)
|
|
return false;
|
|
if (!p->isWaitingForGame() && sender_in_game)
|
|
return false;
|
|
}
|
|
return true;
|
|
}, chat);
|
|
delete chat;
|
|
}
|
|
} // handleChat
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::changeTeam(Event* event)
|
|
{
|
|
if (!ServerConfig::m_team_choosing ||
|
|
!race_manager->teamEnabled())
|
|
return;
|
|
if (!checkDataSize(event, 1)) return;
|
|
NetworkString& data = event->data();
|
|
uint8_t local_id = data.getUInt8();
|
|
auto& player = event->getPeer()->getPlayerProfiles().at(local_id);
|
|
auto red_blue = STKHost::get()->getAllPlayersTeamInfo();
|
|
// At most 7 players on each team (for live join)
|
|
if (player->getTeam() == KART_TEAM_BLUE)
|
|
{
|
|
if (red_blue.first >= 7)
|
|
return;
|
|
player->setTeam(KART_TEAM_RED);
|
|
}
|
|
else
|
|
{
|
|
if (red_blue.second >= 7)
|
|
return;
|
|
player->setTeam(KART_TEAM_BLUE);
|
|
}
|
|
updatePlayerList();
|
|
} // changeTeam
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::kickHost(Event* event)
|
|
{
|
|
if (m_server_owner.lock() != event->getPeerSP())
|
|
return;
|
|
if (!checkDataSize(event, 4)) return;
|
|
NetworkString& data = event->data();
|
|
uint32_t host_id = data.getUInt32();
|
|
std::shared_ptr<STKPeer> peer = STKHost::get()->findPeerByHostId(host_id);
|
|
if (peer)
|
|
peer->kick();
|
|
} // kickHost
|
|
|
|
//-----------------------------------------------------------------------------
|
|
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 of type %d received.",
|
|
message_type);
|
|
switch(message_type)
|
|
{
|
|
case LE_CONNECTION_REQUESTED: connectionRequested(event); break;
|
|
case LE_KART_SELECTION: kartSelectionRequested(event); break;
|
|
case LE_CLIENT_LOADED_WORLD: finishedLoadingWorldClient(event); break;
|
|
case LE_VOTE: handlePlayerVote(event); break;
|
|
case LE_KICK_HOST: kickHost(event); break;
|
|
case LE_CHANGE_TEAM: changeTeam(event); break;
|
|
case LE_REQUEST_BEGIN: startSelection(event); break;
|
|
case LE_CHAT: handleChat(event); break;
|
|
case LE_CONFIG_SERVER: handleServerConfiguration(event); break;
|
|
case LE_CHANGE_HANDICAP: changeHandicap(event); break;
|
|
case LE_CLIENT_BACK_LOBBY:
|
|
clientSelectingAssetsWantsToBackLobby(event); break;
|
|
default: 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
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Create the server id file to let the graphics server client connect. */
|
|
void ServerLobby::createServerIdFile()
|
|
{
|
|
std::string sid = NetworkConfig::get()->getServerIdFile();
|
|
if (!sid.empty() && !m_has_created_server_id_file)
|
|
{
|
|
std::fstream fs;
|
|
sid += StringUtils::toString(m_server_id_online.load()) + "_" +
|
|
StringUtils::toString(STKHost::get()->getPrivatePort());
|
|
fs.open(sid, std::ios::out);
|
|
fs.close();
|
|
m_has_created_server_id_file = true;
|
|
}
|
|
} // createServerIdFile
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Find out the public IP server or poll STK server asynchronously. */
|
|
void ServerLobby::asynchronousUpdate()
|
|
{
|
|
if (m_rs_state.load() == RS_ASYNC_RESET)
|
|
{
|
|
resetVotingTime();
|
|
resetServer();
|
|
m_rs_state.store(RS_NONE);
|
|
}
|
|
|
|
// Check if server owner has left
|
|
updateServerOwner();
|
|
|
|
if (ServerConfig::m_ranked && m_state.load() == WAITING_FOR_START_GAME)
|
|
clearDisconnectedRankedPlayer();
|
|
|
|
if (allowJoinedPlayersWaiting() || (m_game_setup->isGrandPrix() &&
|
|
m_state.load() == WAITING_FOR_START_GAME))
|
|
{
|
|
// Only poll the STK server if this is a WAN server.
|
|
if (NetworkConfig::get()->isWAN())
|
|
checkIncomingConnectionRequests();
|
|
handlePendingConnection();
|
|
}
|
|
|
|
if (NetworkConfig::get()->isWAN() &&
|
|
allowJoinedPlayersWaiting() && m_server_recovering.expired() &&
|
|
StkTime::getRealTimeMs() > m_last_success_poll_time.load() + 30000)
|
|
{
|
|
Log::warn("ServerLobby", "Trying auto server recovery.");
|
|
registerServer(false/*now*/);
|
|
}
|
|
|
|
switch (m_state.load())
|
|
{
|
|
case SET_PUBLIC_ADDRESS:
|
|
{
|
|
// 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.
|
|
if (NetworkConfig::get()->isLAN())
|
|
{
|
|
m_state = WAITING_FOR_START_GAME;
|
|
STKHost::get()->startListening();
|
|
createServerIdFile();
|
|
return;
|
|
}
|
|
STKHost::get()->setPublicAddress();
|
|
if (STKHost::get()->getPublicAddress().isUnset())
|
|
{
|
|
m_state = ERROR_LEAVE;
|
|
}
|
|
else
|
|
{
|
|
m_server_address = STKHost::get()->getPublicAddress();
|
|
STKHost::get()->startListening();
|
|
m_state = REGISTER_SELF_ADDRESS;
|
|
}
|
|
break;
|
|
}
|
|
case REGISTER_SELF_ADDRESS:
|
|
{
|
|
if (m_game_setup->isGrandPrixStarted() || m_registered_for_once_only)
|
|
{
|
|
m_state = WAITING_FOR_START_GAME;
|
|
break;
|
|
}
|
|
// Register this server with the STK server. This will block
|
|
// this thread, because there is no need for the protocol manager
|
|
// to react to any requests before the server is registered.
|
|
if (registerServer(true/*now*/))
|
|
{
|
|
if (allowJoinedPlayersWaiting())
|
|
m_registered_for_once_only = true;
|
|
m_state = WAITING_FOR_START_GAME;
|
|
createServerIdFile();
|
|
}
|
|
else
|
|
{
|
|
m_state = ERROR_LEAVE;
|
|
}
|
|
break;
|
|
}
|
|
case WAITING_FOR_START_GAME:
|
|
{
|
|
if (ServerConfig::m_owner_less)
|
|
{
|
|
unsigned players = 0;
|
|
STKHost::get()->updatePlayers(&players);
|
|
if (((int)players >= ServerConfig::m_min_start_game_players ||
|
|
m_game_setup->isGrandPrixStarted()) &&
|
|
m_timeout.load() == std::numeric_limits<int64_t>::max())
|
|
{
|
|
m_timeout.store((int64_t)StkTime::getRealTimeMs() +
|
|
(int64_t)
|
|
(ServerConfig::m_start_game_counter * 1000.0f));
|
|
}
|
|
else if ((int)players < ServerConfig::m_min_start_game_players &&
|
|
!m_game_setup->isGrandPrixStarted())
|
|
{
|
|
resetPeersReady();
|
|
if (m_timeout.load() != std::numeric_limits<int64_t>::max())
|
|
updatePlayerList();
|
|
m_timeout.store(std::numeric_limits<int64_t>::max());
|
|
}
|
|
if (m_timeout.load() < (int64_t)StkTime::getRealTimeMs() ||
|
|
(checkPeersReady() &&
|
|
(int)players >= ServerConfig::m_min_start_game_players))
|
|
{
|
|
resetPeersReady();
|
|
startSelection();
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case ERROR_LEAVE:
|
|
{
|
|
requestTerminate();
|
|
m_state = EXITING;
|
|
STKHost::get()->requestShutdown();
|
|
break;
|
|
}
|
|
case WAIT_FOR_WORLD_LOADED:
|
|
{
|
|
// For WAIT_FOR_WORLD_LOADED and SELECTING make sure there are enough
|
|
// players to start next game, otherwise exiting and let main thread
|
|
// reset
|
|
if (m_end_voting_period.load() == 0)
|
|
return;
|
|
|
|
unsigned player_in_game = 0;
|
|
STKHost::get()->updatePlayers(&player_in_game);
|
|
// Reset lobby will be done in main thread
|
|
if ((player_in_game == 1 && ServerConfig::m_ranked) ||
|
|
player_in_game == 0)
|
|
{
|
|
resetVotingTime();
|
|
return;
|
|
}
|
|
|
|
// m_server_has_loaded_world is set by main thread with atomic write
|
|
if (m_server_has_loaded_world.load() == false)
|
|
return;
|
|
if (!checkPeersReady())
|
|
return;
|
|
// Reset for next state usage
|
|
resetPeersReady();
|
|
configPeersStartTime();
|
|
break;
|
|
}
|
|
case SELECTING:
|
|
{
|
|
if (m_end_voting_period.load() == 0)
|
|
return;
|
|
unsigned player_in_game = 0;
|
|
STKHost::get()->updatePlayers(&player_in_game);
|
|
if ((player_in_game == 1 && ServerConfig::m_ranked) ||
|
|
player_in_game == 0)
|
|
{
|
|
resetVotingTime();
|
|
return;
|
|
}
|
|
|
|
PeerVote winner_vote;
|
|
m_winner_peer_id = std::numeric_limits<uint32_t>::max();
|
|
bool go_on_race = false;
|
|
if (ServerConfig::m_track_voting)
|
|
go_on_race = handleAllVotes(&winner_vote, &m_winner_peer_id);
|
|
else if (m_game_setup->isGrandPrixStarted() || isVotingOver())
|
|
{
|
|
winner_vote = *m_default_vote;
|
|
go_on_race = true;
|
|
}
|
|
if (go_on_race)
|
|
{
|
|
*m_default_vote = winner_vote;
|
|
m_item_seed = (uint32_t)StkTime::getTimeSinceEpoch();
|
|
ItemManager::updateRandomSeed(m_item_seed);
|
|
m_game_setup->setRace(winner_vote);
|
|
auto players = STKHost::get()->getPlayersForNewGame();
|
|
m_game_setup->sortPlayersForGrandPrix(players);
|
|
m_game_setup->sortPlayersForGame(players);
|
|
for (unsigned i = 0; i < players.size(); i++)
|
|
{
|
|
std::shared_ptr<NetworkPlayerProfile>& player = players[i];
|
|
std::shared_ptr<STKPeer> peer = player->getPeer();
|
|
if (peer)
|
|
peer->clearAvailableKartIDs();
|
|
}
|
|
for (unsigned i = 0; i < players.size(); i++)
|
|
{
|
|
std::shared_ptr<NetworkPlayerProfile>& player = players[i];
|
|
std::shared_ptr<STKPeer> peer = player->getPeer();
|
|
if (peer)
|
|
peer->addAvailableKartID(i);
|
|
}
|
|
getHitCaptureLimit();
|
|
|
|
// Add placeholder players for live join
|
|
addLiveJoinPlaceholder(players);
|
|
// If player chose random / hasn't chose any kart
|
|
for (unsigned i = 0; i < players.size(); i++)
|
|
{
|
|
if (players[i]->getKartName().empty())
|
|
{
|
|
RandomGenerator rg;
|
|
std::set<std::string>::iterator it =
|
|
m_available_kts.first.begin();
|
|
std::advance(it,
|
|
rg.get((int)m_available_kts.first.size()));
|
|
players[i]->setKartName(*it);
|
|
}
|
|
}
|
|
|
|
NetworkString* load_world_message = getLoadWorldMessage(players,
|
|
false/*live_join*/);
|
|
m_game_setup->setHitCaptureTime(m_battle_hit_capture_limit,
|
|
m_battle_time_limit);
|
|
uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks(
|
|
ServerConfig::m_flag_return_timeout);
|
|
race_manager->setFlagReturnTicks(flag_return_time);
|
|
uint16_t flag_deactivated_time = (uint16_t)stk_config->time2Ticks(
|
|
ServerConfig::m_flag_deactivated_time);
|
|
race_manager->setFlagDeactivatedTicks(flag_deactivated_time);
|
|
configRemoteKart(players, 0);
|
|
|
|
// Reset for next state usage
|
|
resetPeersReady();
|
|
m_state = LOAD_WORLD;
|
|
sendMessageToPeers(load_world_message);
|
|
delete load_world_message;
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
} // asynchronousUpdate
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::encodePlayers(BareNetworkString* bns,
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> >& players) const
|
|
{
|
|
bns->addUInt8((uint8_t)players.size());
|
|
for (unsigned i = 0; i < players.size(); i++)
|
|
{
|
|
std::shared_ptr<NetworkPlayerProfile>& player = players[i];
|
|
bns->encodeString(player->getName())
|
|
.addUInt32(player->getHostId())
|
|
.addFloat(player->getDefaultKartColor())
|
|
.addUInt32(player->getOnlineId())
|
|
.addUInt8(player->getPerPlayerDifficulty())
|
|
.addUInt8(player->getLocalPlayerId())
|
|
.addUInt8(
|
|
race_manager->teamEnabled() ? player->getTeam() : KART_TEAM_NONE)
|
|
.encodeString(player->getCountryId());
|
|
bns->encodeString(player->getKartName());
|
|
}
|
|
} // encodePlayers
|
|
|
|
//-----------------------------------------------------------------------------
|
|
NetworkString* ServerLobby::getLoadWorldMessage(
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> >& players,
|
|
bool live_join) const
|
|
{
|
|
NetworkString* load_world_message = getNetworkString();
|
|
load_world_message->setSynchronous(true);
|
|
load_world_message->addUInt8(LE_LOAD_WORLD);
|
|
load_world_message->addUInt32(m_winner_peer_id);
|
|
m_default_vote->encode(load_world_message);
|
|
load_world_message->addUInt8(live_join ? 1 : 0);
|
|
encodePlayers(load_world_message, players);
|
|
load_world_message->addUInt32(m_item_seed);
|
|
if (race_manager->isBattleMode())
|
|
{
|
|
load_world_message->addUInt32(m_battle_hit_capture_limit)
|
|
.addFloat(m_battle_time_limit);
|
|
uint16_t flag_return_time = (uint16_t)stk_config->time2Ticks(
|
|
ServerConfig::m_flag_return_timeout);
|
|
load_world_message->addUInt16(flag_return_time);
|
|
uint16_t flag_deactivated_time = (uint16_t)stk_config->time2Ticks(
|
|
ServerConfig::m_flag_deactivated_time);
|
|
load_world_message->addUInt16(flag_deactivated_time);
|
|
}
|
|
return load_world_message;
|
|
} // getLoadWorldMessage
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns true if server can be live joined or spectating
|
|
*/
|
|
bool ServerLobby::canLiveJoinNow() const
|
|
{
|
|
bool live_join = ServerConfig::m_live_players && worldIsActive();
|
|
if (!live_join)
|
|
return false;
|
|
if (race_manager->modeHasLaps())
|
|
{
|
|
// No spectate when fastest kart is nearly finish, because if there
|
|
// is endcontroller the spectating remote may not be knowing this
|
|
// on time
|
|
LinearWorld* w = dynamic_cast<LinearWorld*>(World::getWorld());
|
|
if (!w)
|
|
return false;
|
|
AbstractKart* fastest_kart = NULL;
|
|
for (unsigned i = 0; i < w->getNumKarts(); i++)
|
|
{
|
|
fastest_kart = w->getKartAtPosition(i + 1);
|
|
if (fastest_kart && !fastest_kart->isEliminated())
|
|
break;
|
|
}
|
|
if (!fastest_kart)
|
|
return false;
|
|
float progress = w->getOverallDistance(
|
|
fastest_kart->getWorldKartId()) /
|
|
(Track::getCurrentTrack()->getTrackLength() *
|
|
(float)race_manager->getNumLaps());
|
|
if (progress > 0.9f)
|
|
return false;
|
|
}
|
|
return live_join;
|
|
} // canLiveJoinNow
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns true if world is active for clients to live join, spectate or
|
|
* going back to lobby live
|
|
*/
|
|
bool ServerLobby::worldIsActive() const
|
|
{
|
|
return World::getWorld() && RaceEventManager::getInstance()->isRunning() &&
|
|
!RaceEventManager::getInstance()->isRaceOver() &&
|
|
World::getWorld()->getPhase() == WorldStatus::RACE_PHASE;
|
|
} // worldIsActive
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** \ref STKPeer peer will be reset back to the lobby with reason
|
|
* \ref BackLobbyReason blr
|
|
*/
|
|
void ServerLobby::rejectLiveJoin(STKPeer* peer, BackLobbyReason blr)
|
|
{
|
|
NetworkString* reset = getNetworkString(2);
|
|
reset->setSynchronous(true);
|
|
reset->addUInt8(LE_BACK_LOBBY).addUInt8(blr);
|
|
peer->sendPacket(reset, /*reliable*/true);
|
|
delete reset;
|
|
updatePlayerList();
|
|
NetworkString* server_info = getNetworkString();
|
|
server_info->setSynchronous(true);
|
|
server_info->addUInt8(LE_SERVER_INFO);
|
|
m_game_setup->addServerInfo(server_info);
|
|
peer->sendPacket(server_info, /*reliable*/true);
|
|
delete server_info;
|
|
} // rejectLiveJoin
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** This message is like kartSelectionRequested, but it will send the peer
|
|
* load world message if he can join the current started game.
|
|
*/
|
|
void ServerLobby::liveJoinRequest(Event* event)
|
|
{
|
|
STKPeer* peer = event->getPeer();
|
|
const NetworkString& data = event->data();
|
|
|
|
if (!canLiveJoinNow())
|
|
{
|
|
rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN);
|
|
return;
|
|
}
|
|
bool spectator = data.getUInt8() == 1;
|
|
if (race_manager->modeHasLaps() && !spectator)
|
|
{
|
|
// No live join for linear race
|
|
rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN);
|
|
return;
|
|
}
|
|
|
|
peer->clearAvailableKartIDs();
|
|
if (!spectator)
|
|
{
|
|
setPlayerKarts(data, peer);
|
|
|
|
std::vector<int> used_id;
|
|
for (unsigned i = 0; i < peer->getPlayerProfiles().size(); i++)
|
|
{
|
|
int id = getReservedId(peer->getPlayerProfiles()[i], i);
|
|
if (id == -1)
|
|
break;
|
|
used_id.push_back(id);
|
|
}
|
|
if (used_id.size() != peer->getPlayerProfiles().size())
|
|
{
|
|
for (unsigned i = 0; i < peer->getPlayerProfiles().size(); i++)
|
|
peer->getPlayerProfiles()[i]->setKartName("");
|
|
for (unsigned i = 0; i < used_id.size(); i++)
|
|
{
|
|
RemoteKartInfo& rki = race_manager->getKartInfo(used_id[i]);
|
|
rki.makeReserved();
|
|
}
|
|
Log::info("ServerLobby", "Too many players (%d) try to live join",
|
|
(int)peer->getPlayerProfiles().size());
|
|
rejectLiveJoin(peer, BLR_NO_PLACE_FOR_LIVE_JOIN);
|
|
return;
|
|
}
|
|
|
|
for (int id : used_id)
|
|
{
|
|
Log::info("ServerLobby", "%s live joining with reserved kart id %d.",
|
|
peer->getAddress().toString().c_str(), id);
|
|
peer->addAvailableKartID(id);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log::info("ServerLobby", "%s spectating now.",
|
|
peer->getAddress().toString().c_str());
|
|
}
|
|
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> > players =
|
|
getLivePlayers();
|
|
NetworkString* load_world_message = getLoadWorldMessage(players,
|
|
true/*live_join*/);
|
|
peer->sendPacket(load_world_message, true/*reliable*/);
|
|
delete load_world_message;
|
|
peer->updateLastActivity();
|
|
} // liveJoinRequest
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Get a list of current ingame players for live join or spectate.
|
|
*/
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> >
|
|
ServerLobby::getLivePlayers() const
|
|
{
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> > players;
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
const RemoteKartInfo& rki = race_manager->getKartInfo(i);
|
|
std::shared_ptr<NetworkPlayerProfile> player =
|
|
rki.getNetworkPlayerProfile().lock();
|
|
if (!player)
|
|
{
|
|
if (race_manager->modeHasLaps())
|
|
{
|
|
player = std::make_shared<NetworkPlayerProfile>(
|
|
nullptr, rki.getPlayerName(),
|
|
std::numeric_limits<uint32_t>::max(),
|
|
rki.getDefaultKartColor(),
|
|
rki.getOnlineId(), rki.getDifficulty(),
|
|
rki.getLocalPlayerId(), KART_TEAM_NONE,
|
|
rki.getCountryId());
|
|
player->setKartName(rki.getKartName());
|
|
}
|
|
else
|
|
{
|
|
player = NetworkPlayerProfile::getReservedProfile(
|
|
race_manager->getMinorMode() ==
|
|
RaceManager::MINOR_MODE_FREE_FOR_ALL ?
|
|
KART_TEAM_NONE : rki.getKartTeam());
|
|
}
|
|
}
|
|
players.push_back(player);
|
|
}
|
|
return players;
|
|
} // getLivePlayers
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Decide where to put the live join player depends on his team and game mode.
|
|
*/
|
|
int ServerLobby::getReservedId(std::shared_ptr<NetworkPlayerProfile>& p,
|
|
unsigned local_id) const
|
|
{
|
|
const bool is_ffa =
|
|
race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL;
|
|
int red_count = 0;
|
|
int blue_count = 0;
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
RemoteKartInfo& rki = race_manager->getKartInfo(i);
|
|
if (rki.isReserved())
|
|
continue;
|
|
bool disconnected = rki.disconnected();
|
|
if (race_manager->getKartInfo(i).getKartTeam() == KART_TEAM_RED &&
|
|
!disconnected)
|
|
red_count++;
|
|
else if (race_manager->getKartInfo(i).getKartTeam() ==
|
|
KART_TEAM_BLUE && !disconnected)
|
|
blue_count++;
|
|
}
|
|
KartTeam target_team = red_count > blue_count ? KART_TEAM_BLUE :
|
|
KART_TEAM_RED;
|
|
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
RemoteKartInfo& rki = race_manager->getKartInfo(i);
|
|
std::shared_ptr<NetworkPlayerProfile> player =
|
|
rki.getNetworkPlayerProfile().lock();
|
|
if (!player)
|
|
{
|
|
if (is_ffa)
|
|
{
|
|
rki.copyFrom(p, local_id);
|
|
return i;
|
|
}
|
|
if (ServerConfig::m_team_choosing)
|
|
{
|
|
if ((p->getTeam() == KART_TEAM_RED &&
|
|
rki.getKartTeam() == KART_TEAM_RED) ||
|
|
(p->getTeam() == KART_TEAM_BLUE &&
|
|
rki.getKartTeam() == KART_TEAM_BLUE))
|
|
{
|
|
rki.copyFrom(p, local_id);
|
|
return i;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (rki.getKartTeam() == target_team)
|
|
{
|
|
p->setTeam(target_team);
|
|
rki.copyFrom(p, local_id);
|
|
return i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return -1;
|
|
} // getReservedId
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Finally put the kart in the world and inform client the current world
|
|
* status, (including current confirmed item state, kart scores...)
|
|
*/
|
|
void ServerLobby::finishedLoadingLiveJoinClient(Event* event)
|
|
{
|
|
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
|
if (!canLiveJoinNow())
|
|
{
|
|
rejectLiveJoin(peer.get(), BLR_NO_GAME_FOR_LIVE_JOIN);
|
|
return;
|
|
}
|
|
bool live_joined_in_time = true;
|
|
for (const int id : peer->getAvailableKartIDs())
|
|
{
|
|
const RemoteKartInfo& rki = race_manager->getKartInfo(id);
|
|
if (rki.isReserved())
|
|
{
|
|
live_joined_in_time = false;
|
|
break;
|
|
}
|
|
}
|
|
if (!live_joined_in_time)
|
|
{
|
|
Log::warn("ServerLobby", "%s can't live-join in time.",
|
|
peer->getAddress().toString().c_str());
|
|
rejectLiveJoin(peer.get(), BLR_NO_GAME_FOR_LIVE_JOIN);
|
|
return;
|
|
}
|
|
World* w = World::getWorld();
|
|
assert(w);
|
|
|
|
// Give 3 seconds for all peers to get new kart info
|
|
m_last_live_join_util_ticks = w->getTicksSinceStart() +
|
|
stk_config->time2Ticks(3.0f);
|
|
uint64_t live_join_start_time = STKHost::get()->getNetworkTimer();
|
|
live_join_start_time -= m_server_delay;
|
|
live_join_start_time += 3000;
|
|
|
|
bool spectator = false;
|
|
for (const int id : peer->getAvailableKartIDs())
|
|
{
|
|
World::getWorld()->addReservedKart(id);
|
|
const RemoteKartInfo& rki = race_manager->getKartInfo(id);
|
|
addLiveJoiningKart(id, rki, m_last_live_join_util_ticks);
|
|
Log::info("ServerLobby", "%s succeeded live-joining with kart id %d.",
|
|
peer->getAddress().toString().c_str(), id);
|
|
}
|
|
if (peer->getAvailableKartIDs().empty())
|
|
{
|
|
Log::info("ServerLobby", "%s spectating succeeded.",
|
|
peer->getAddress().toString().c_str());
|
|
spectator = true;
|
|
}
|
|
|
|
const uint8_t cc = (uint8_t)CheckManager::get()->getCheckStructureCount();
|
|
NetworkString* ns = getNetworkString(10);
|
|
ns->setSynchronous(true);
|
|
ns->addUInt8(LE_LIVE_JOIN_ACK).addUInt64(m_client_starting_time)
|
|
.addUInt8(cc).addUInt64(live_join_start_time)
|
|
.addUInt32(m_last_live_join_util_ticks);
|
|
|
|
NetworkItemManager* nim =
|
|
dynamic_cast<NetworkItemManager*>(ItemManager::get());
|
|
assert(nim);
|
|
nim->saveCompleteState(ns);
|
|
nim->addLiveJoinPeer(peer);
|
|
|
|
w->saveCompleteState(ns);
|
|
if (race_manager->supportsLiveJoining())
|
|
{
|
|
// Only needed in non-racing mode as no need players can added after
|
|
// starting of race
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> > players =
|
|
getLivePlayers();
|
|
encodePlayers(ns, players);
|
|
}
|
|
|
|
m_peers_ready[peer] = false;
|
|
peer->setWaitingForGame(false);
|
|
peer->setSpectator(spectator);
|
|
|
|
peer->sendPacket(ns, true/*reliable*/);
|
|
delete ns;
|
|
updatePlayerList();
|
|
peer->updateLastActivity();
|
|
} // finishedLoadingLiveJoinClient
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Simple finite state machine. Once this
|
|
* is known, register the server and its address with the stk server so that
|
|
* client can find it.
|
|
*/
|
|
void ServerLobby::update(int ticks)
|
|
{
|
|
World* w = World::getWorld();
|
|
bool world_started = m_state.load() >= WAIT_FOR_WORLD_LOADED &&
|
|
m_state.load() <= RACING && m_server_has_loaded_world.load();
|
|
bool all_players_in_world_disconnected = (w != NULL && world_started);
|
|
int sec = ServerConfig::m_kick_idle_player_seconds;
|
|
if (world_started)
|
|
{
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
RemoteKartInfo& rki = race_manager->getKartInfo(i);
|
|
std::shared_ptr<NetworkPlayerProfile> player =
|
|
rki.getNetworkPlayerProfile().lock();
|
|
if (player)
|
|
{
|
|
if (w)
|
|
all_players_in_world_disconnected = false;
|
|
}
|
|
else
|
|
continue;
|
|
auto peer = player->getPeer();
|
|
if (!peer)
|
|
continue;
|
|
|
|
if (peer->idleForSeconds() > 60 && w &&
|
|
w->getKart(i)->isEliminated())
|
|
{
|
|
// Remove loading world too long (60 seconds) live join peer
|
|
Log::info("ServerLobby", "%s hasn't live-joined within"
|
|
" 60 seconds, remove it.",
|
|
peer->getAddress().toString().c_str());
|
|
rki.makeReserved();
|
|
continue;
|
|
}
|
|
if (sec > 0 && peer->idleForSeconds() > sec &&
|
|
!peer->isDisconnected() && NetworkConfig::get()->isWAN())
|
|
{
|
|
if (w && w->getKart(i)->hasFinishedRace())
|
|
continue;
|
|
Log::info("ServerLobby", "%s %s has been idle for more than"
|
|
" %d seconds, kick.",
|
|
peer->getAddress().toString().c_str(),
|
|
StringUtils::wideToUtf8(rki.getPlayerName()).c_str(), sec);
|
|
peer->kick();
|
|
}
|
|
}
|
|
}
|
|
if (w)
|
|
setGameStartedProgress(w->getGameStartedProgress());
|
|
else
|
|
resetGameStartedProgress();
|
|
|
|
if (w && w->getPhase() == World::RACE_PHASE)
|
|
{
|
|
storePlayingTrack(track_manager->getTrackIndexByIdent(
|
|
race_manager->getTrackName()));
|
|
}
|
|
else
|
|
storePlayingTrack(-1);
|
|
|
|
// Reset server to initial state if no more connected players
|
|
if (m_rs_state.load() == RS_WAITING)
|
|
{
|
|
if ((RaceEventManager::getInstance() &&
|
|
!RaceEventManager::getInstance()->protocolStopped()) ||
|
|
!GameProtocol::emptyInstance())
|
|
return;
|
|
|
|
RaceResultGUI::getInstance()->backToLobby();
|
|
m_rs_state.store(RS_ASYNC_RESET);
|
|
}
|
|
|
|
STKHost::get()->updatePlayers();
|
|
if (m_rs_state.load() == RS_NONE &&
|
|
(m_state.load() > WAITING_FOR_START_GAME ||
|
|
m_game_setup->isGrandPrixStarted()) &&
|
|
(STKHost::get()->getPlayersInGame() == 0 ||
|
|
all_players_in_world_disconnected))
|
|
{
|
|
if (RaceEventManager::getInstance() &&
|
|
RaceEventManager::getInstance()->isRunning())
|
|
{
|
|
// Send a notification to all players who may have start live join
|
|
// or spectate to go back to lobby
|
|
NetworkString* back_to_lobby = getNetworkString(2);
|
|
back_to_lobby->setSynchronous(true);
|
|
back_to_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE);
|
|
sendMessageToPeersInServer(back_to_lobby, /*reliable*/true);
|
|
delete back_to_lobby;
|
|
|
|
RaceEventManager::getInstance()->stop();
|
|
RaceEventManager::getInstance()->getProtocol()->requestTerminate();
|
|
GameProtocol::lock()->requestTerminate();
|
|
}
|
|
resetVotingTime();
|
|
m_game_setup->stopGrandPrix();
|
|
m_rs_state.store(RS_WAITING);
|
|
return;
|
|
}
|
|
|
|
if (m_rs_state.load() != RS_NONE)
|
|
return;
|
|
|
|
// Reset for ranked server if in kart / track selection has only 1 player
|
|
if (ServerConfig::m_ranked &&
|
|
m_state.load() == SELECTING &&
|
|
STKHost::get()->getPlayersInGame() == 1)
|
|
{
|
|
NetworkString* back_lobby = getNetworkString(2);
|
|
back_lobby->setSynchronous(true);
|
|
back_lobby->addUInt8(LE_BACK_LOBBY)
|
|
.addUInt8(BLR_ONE_PLAYER_IN_RANKED_MATCH);
|
|
sendMessageToPeers(back_lobby, /*reliable*/true);
|
|
delete back_lobby;
|
|
resetVotingTime();
|
|
m_game_setup->stopGrandPrix();
|
|
m_rs_state.store(RS_ASYNC_RESET);
|
|
}
|
|
|
|
handlePlayerDisconnection();
|
|
|
|
switch (m_state.load())
|
|
{
|
|
case SET_PUBLIC_ADDRESS:
|
|
case REGISTER_SELF_ADDRESS:
|
|
case WAITING_FOR_START_GAME:
|
|
case WAIT_FOR_WORLD_LOADED:
|
|
case WAIT_FOR_RACE_STARTED:
|
|
{
|
|
// Waiting for asynchronousUpdate
|
|
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 RACING:
|
|
if (World::getWorld() &&
|
|
RaceEventManager::getInstance<RaceEventManager>()->isRunning())
|
|
{
|
|
checkRaceFinished();
|
|
}
|
|
break;
|
|
case WAIT_FOR_RACE_STOPPED:
|
|
if (!RaceEventManager::getInstance()->protocolStopped() ||
|
|
!GameProtocol::emptyInstance())
|
|
return;
|
|
|
|
// This will go back to lobby in server (and exit the current race)
|
|
RaceResultGUI::getInstance()->backToLobby();
|
|
// Reset for next state usage
|
|
resetPeersReady();
|
|
// Set the delay before the server forces all clients to exit the race
|
|
// result screen and go back to the lobby
|
|
m_timeout.store((int64_t)StkTime::getRealTimeMs() + 15000);
|
|
m_state = RESULT_DISPLAY;
|
|
sendMessageToPeers(m_result_ns, /*reliable*/ true);
|
|
Log::info("ServerLobby", "End of game message sent");
|
|
break;
|
|
case RESULT_DISPLAY:
|
|
if (checkPeersReady() ||
|
|
(int64_t)StkTime::getRealTimeMs() > m_timeout.load())
|
|
{
|
|
// Send a notification to all clients to exit
|
|
// the race result screen
|
|
NetworkString* back_to_lobby = getNetworkString(2);
|
|
back_to_lobby->setSynchronous(true);
|
|
back_to_lobby->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE);
|
|
sendMessageToPeersInServer(back_to_lobby, /*reliable*/true);
|
|
delete back_to_lobby;
|
|
m_rs_state.store(RS_ASYNC_RESET);
|
|
}
|
|
break;
|
|
case ERROR_LEAVE:
|
|
case EXITING:
|
|
break;
|
|
}
|
|
} // update
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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'.
|
|
*/
|
|
bool ServerLobby::registerServer(bool now)
|
|
{
|
|
while (now && !m_server_unregistered.expired())
|
|
StkTime::sleep(1);
|
|
|
|
// ========================================================================
|
|
class RegisterServerRequest : public Online::XMLRequest
|
|
{
|
|
private:
|
|
std::weak_ptr<ServerLobby> m_server_lobby;
|
|
protected:
|
|
virtual void afterOperation()
|
|
{
|
|
Online::XMLRequest::afterOperation();
|
|
const XMLNode* result = getXMLData();
|
|
std::string rec_success;
|
|
auto sl = m_server_lobby.lock();
|
|
if (!sl)
|
|
return;
|
|
|
|
if (result->get("success", &rec_success) &&
|
|
rec_success == "yes")
|
|
{
|
|
const XMLNode* server = result->getNode("server");
|
|
assert(server);
|
|
const XMLNode* server_info = server->getNode("server-info");
|
|
assert(server_info);
|
|
unsigned server_id_online = 0;
|
|
server_info->get("id", &server_id_online);
|
|
assert(server_id_online != 0);
|
|
bool is_official = false;
|
|
server_info->get("official", &is_official);
|
|
if (!is_official && ServerConfig::m_ranked)
|
|
{
|
|
Log::fatal("ServerLobby", "You don't have permission to "
|
|
"host a ranked server.");
|
|
}
|
|
Log::info("ServerLobby",
|
|
"Server %d is now online.", server_id_online);
|
|
sl->m_server_id_online.store(server_id_online);
|
|
sl->m_last_success_poll_time.store(StkTime::getRealTimeMs());
|
|
return;
|
|
}
|
|
Log::error("ServerLobby", "%s",
|
|
StringUtils::wideToUtf8(getInfo()).c_str());
|
|
// For auto server recovery wait 3 seconds for next try
|
|
// This sleep only the request manager thread
|
|
if (manageMemory())
|
|
StkTime::sleep(3000);
|
|
}
|
|
public:
|
|
RegisterServerRequest(bool now, std::shared_ptr<ServerLobby> sl)
|
|
: XMLRequest(!now/*manage memory*/), m_server_lobby(sl) {}
|
|
}; // RegisterServerRequest
|
|
|
|
RegisterServerRequest *request = new RegisterServerRequest(now,
|
|
std::dynamic_pointer_cast<ServerLobby>(shared_from_this()));
|
|
NetworkConfig::get()->setServerDetails(request, "create");
|
|
request->addParameter("address", m_server_address.getIP() );
|
|
request->addParameter("port", m_server_address.getPort() );
|
|
request->addParameter("private_port",
|
|
STKHost::get()->getPrivatePort() );
|
|
request->addParameter("name", m_game_setup->getServerNameUtf8());
|
|
request->addParameter("max_players", ServerConfig::m_server_max_players);
|
|
int difficulty = m_difficulty.load();
|
|
request->addParameter("difficulty", difficulty);
|
|
int game_mode = m_game_mode.load();
|
|
request->addParameter("game_mode", game_mode);
|
|
const std::string& pw = ServerConfig::m_private_server_password;
|
|
request->addParameter("password", (unsigned)(!pw.empty()));
|
|
request->addParameter("version", (unsigned)ServerConfig::m_server_version);
|
|
|
|
Log::info("ServerLobby", "Public server address %s",
|
|
m_server_address.toString().c_str());
|
|
|
|
if (now)
|
|
{
|
|
request->executeNow();
|
|
delete request;
|
|
if (m_server_id_online.load() == 0)
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
request->queue();
|
|
m_server_recovering = request->observeExistence();
|
|
}
|
|
return true;
|
|
} // registerServer
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Unregister this server (i.e. its public address) with the STK server,
|
|
* currently when karts enter kart selection screen it will be done or quit
|
|
* stk.
|
|
*/
|
|
void ServerLobby::unregisterServer(bool now)
|
|
{
|
|
Online::XMLRequest* request =
|
|
new Online::XMLRequest(!now/*manage memory*/);
|
|
m_server_unregistered = request->observeExistence();
|
|
NetworkConfig::get()->setServerDetails(request, "stop");
|
|
|
|
request->addParameter("address", m_server_address.getIP());
|
|
request->addParameter("port", m_server_address.getPort());
|
|
Log::info("ServerLobby", "Unregister server address %s",
|
|
m_server_address.toString().c_str());
|
|
// No need to check for result as server will be auto-cleared anyway
|
|
// when no polling is done
|
|
if (now)
|
|
{
|
|
request->executeNow();
|
|
delete request;
|
|
}
|
|
else
|
|
request->queue();
|
|
|
|
} // unregisterServer
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Instructs all clients to start the kart selection. If event is NULL,
|
|
* the command comes from the owner less server.
|
|
*/
|
|
void ServerLobby::startSelection(const Event *event)
|
|
{
|
|
if (event != NULL)
|
|
{
|
|
if (m_state != WAITING_FOR_START_GAME)
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Received startSelection while being in state %d.",
|
|
m_state.load());
|
|
return;
|
|
}
|
|
if (ServerConfig::m_owner_less)
|
|
{
|
|
m_peers_ready.at(event->getPeerSP()) =
|
|
!m_peers_ready.at(event->getPeerSP());
|
|
updatePlayerList();
|
|
return;
|
|
}
|
|
if (event->getPeerSP() != m_server_owner.lock())
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Client %d is not authorised to start selection.",
|
|
event->getPeer()->getHostId());
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
if (!ServerConfig::m_owner_less && ServerConfig::m_team_choosing &&
|
|
race_manager->teamEnabled())
|
|
{
|
|
auto red_blue = STKHost::get()->getAllPlayersTeamInfo();
|
|
if ((red_blue.first == 0 || red_blue.second == 0) &&
|
|
red_blue.first + red_blue.second != 1)
|
|
{
|
|
Log::warn("ServerLobby", "Bad team choosing.");
|
|
if (event)
|
|
{
|
|
NetworkString* bt = getNetworkString();
|
|
bt->setSynchronous(true);
|
|
bt->addUInt8(LE_BAD_TEAM);
|
|
event->getPeer()->sendPacket(bt, true/*reliable*/);
|
|
delete bt;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Remove karts / tracks from server that are not supported on all clients
|
|
std::set<std::string> karts_erase, tracks_erase;
|
|
auto peers = STKHost::get()->getPeers();
|
|
for (auto peer : peers)
|
|
{
|
|
if (!peer->isValidated() || peer->isWaitingForGame())
|
|
continue;
|
|
peer->eraseServerKarts(m_available_kts.first, karts_erase);
|
|
peer->eraseServerTracks(m_available_kts.second, tracks_erase);
|
|
}
|
|
for (const std::string& kart_erase : karts_erase)
|
|
{
|
|
m_available_kts.first.erase(kart_erase);
|
|
}
|
|
for (const std::string& track_erase : tracks_erase)
|
|
{
|
|
m_available_kts.second.erase(track_erase);
|
|
}
|
|
|
|
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL)
|
|
{
|
|
unsigned max_player = 0;
|
|
STKHost::get()->updatePlayers(&max_player);
|
|
auto it = m_available_kts.second.begin();
|
|
while (it != m_available_kts.second.end())
|
|
{
|
|
Track* t = track_manager->getTrack(*it);
|
|
if (t->getMaxArenaPlayers() < max_player)
|
|
{
|
|
it = m_available_kts.second.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
}
|
|
// Default vote use only official tracks to prevent network AI cannot
|
|
// finish some bad wip / addons tracks
|
|
std::set<std::string> official_tracks = m_official_kts.second;
|
|
std::set<std::string>::iterator it = official_tracks.begin();
|
|
while (it != official_tracks.end())
|
|
{
|
|
if (m_available_kts.second.find(*it) == m_available_kts.second.end())
|
|
{
|
|
it = official_tracks.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
if (official_tracks.empty())
|
|
{
|
|
Log::error("ServerLobby", "No official tracks for playing!");
|
|
return;
|
|
}
|
|
RandomGenerator rg;
|
|
it = official_tracks.begin();
|
|
std::advance(it, rg.get((int)official_tracks.size()));
|
|
m_default_vote->m_track_name = *it;
|
|
switch (race_manager->getMinorMode())
|
|
{
|
|
case RaceManager::MINOR_MODE_NORMAL_RACE:
|
|
case RaceManager::MINOR_MODE_TIME_TRIAL:
|
|
case RaceManager::MINOR_MODE_FOLLOW_LEADER:
|
|
{
|
|
Track* t = track_manager->getTrack(*it);
|
|
assert(t);
|
|
m_default_vote->m_num_laps = t->getDefaultNumberOfLaps();
|
|
m_default_vote->m_reverse = rg.get(2) == 0;
|
|
break;
|
|
}
|
|
case RaceManager::MINOR_MODE_FREE_FOR_ALL:
|
|
{
|
|
m_default_vote->m_num_laps = 0;
|
|
m_default_vote->m_reverse = rg.get(2) == 0;
|
|
break;
|
|
}
|
|
case RaceManager::MINOR_MODE_CAPTURE_THE_FLAG:
|
|
{
|
|
m_default_vote->m_num_laps = 0;
|
|
m_default_vote->m_reverse = 0;
|
|
break;
|
|
}
|
|
case RaceManager::MINOR_MODE_SOCCER:
|
|
{
|
|
if (m_game_setup->isSoccerGoalTarget())
|
|
{
|
|
m_default_vote->m_num_laps =
|
|
(uint8_t)(UserConfigParams::m_num_goals);
|
|
if (m_default_vote->m_num_laps > 10)
|
|
m_default_vote->m_num_laps = (uint8_t)5;
|
|
}
|
|
else
|
|
{
|
|
m_default_vote->m_num_laps =
|
|
(uint8_t)(UserConfigParams::m_soccer_time_limit);
|
|
if (m_default_vote->m_num_laps > 15)
|
|
m_default_vote->m_num_laps = (uint8_t)7;
|
|
}
|
|
m_default_vote->m_reverse = rg.get(2) == 0;
|
|
break;
|
|
}
|
|
default:
|
|
assert(false);
|
|
break;
|
|
}
|
|
|
|
if (!allowJoinedPlayersWaiting())
|
|
{
|
|
ProtocolManager::lock()->findAndTerminate(PROTOCOL_CONNECTION);
|
|
if (NetworkConfig::get()->isWAN())
|
|
{
|
|
unregisterServer(false/*now*/);
|
|
}
|
|
}
|
|
|
|
startVotingPeriod(ServerConfig::m_voting_timeout);
|
|
NetworkString *ns = getNetworkString(1);
|
|
// Start selection - must be synchronous since the receiver pushes
|
|
// a new screen, which must be done from the main thread.
|
|
ns->setSynchronous(true);
|
|
ns->addUInt8(LE_START_SELECTION)
|
|
.addFloat(ServerConfig::m_voting_timeout)
|
|
.addUInt8(m_game_setup->isGrandPrixStarted() ? 1 : 0)
|
|
.addUInt8(ServerConfig::m_auto_game_time_ratio > 0.0f ? 1 : 0)
|
|
.addUInt8(ServerConfig::m_track_voting ? 1 : 0);
|
|
|
|
const auto& all_k = m_available_kts.first;
|
|
const auto& all_t = m_available_kts.second;
|
|
ns->addUInt16((uint16_t)all_k.size()).addUInt16((uint16_t)all_t.size());
|
|
for (const std::string& kart : all_k)
|
|
{
|
|
ns->encodeString(kart);
|
|
}
|
|
for (const std::string& track : all_t)
|
|
{
|
|
ns->encodeString(track);
|
|
}
|
|
|
|
sendMessageToPeers(ns, /*reliable*/true);
|
|
delete ns;
|
|
|
|
m_state = SELECTING;
|
|
if (!allowJoinedPlayersWaiting())
|
|
{
|
|
// Drop all pending players and keys if doesn't allow joinning-waiting
|
|
for (auto& p : m_pending_connection)
|
|
{
|
|
if (auto peer = p.first.lock())
|
|
peer->disconnect();
|
|
}
|
|
m_pending_connection.clear();
|
|
std::unique_lock<std::mutex> ul(m_keys_mutex);
|
|
m_keys.clear();
|
|
ul.unlock();
|
|
}
|
|
|
|
// Will be changed after the first vote received
|
|
m_timeout.store(std::numeric_limits<int64_t>::max());
|
|
} // 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 uint64_t POLL_INTERVAL = 5000;
|
|
static uint64_t last_poll_time = 0;
|
|
if (StkTime::getRealTimeMs() < last_poll_time + POLL_INTERVAL ||
|
|
StkTime::getRealTimeMs() > m_last_success_poll_time.load() + 30000 ||
|
|
m_server_id_online.load() == 0)
|
|
return;
|
|
|
|
// Keep the port open, it can be sent to anywhere as we will send to the
|
|
// correct peer later in ConnectToPeer.
|
|
if (ServerConfig::m_firewalled_server)
|
|
{
|
|
BareNetworkString data;
|
|
data.addUInt8(0);
|
|
STKHost::get()->sendRawPacket(data, STKHost::get()->getStunAddress());
|
|
}
|
|
|
|
// Now poll the stk server
|
|
last_poll_time = StkTime::getRealTimeMs();
|
|
|
|
// ========================================================================
|
|
class PollServerRequest : public Online::XMLRequest
|
|
{
|
|
private:
|
|
std::weak_ptr<ServerLobby> m_server_lobby;
|
|
protected:
|
|
virtual void afterOperation()
|
|
{
|
|
Online::XMLRequest::afterOperation();
|
|
const XMLNode* result = getXMLData();
|
|
std::string success;
|
|
|
|
if (!result->get("success", &success) || success != "yes")
|
|
{
|
|
Log::error("ServerLobby", "Poll server request failed: %s",
|
|
StringUtils::wideToUtf8(getInfo()).c_str());
|
|
return;
|
|
}
|
|
|
|
// Now start a ConnectToPeer protocol for each connection request
|
|
const XMLNode * users_xml = result->getNode("users");
|
|
std::map<uint32_t, KeyData> keys;
|
|
auto sl = m_server_lobby.lock();
|
|
if (!sl)
|
|
return;
|
|
sl->m_last_success_poll_time.store(StkTime::getRealTimeMs());
|
|
if (sl->m_state.load() != WAITING_FOR_START_GAME &&
|
|
!sl->allowJoinedPlayersWaiting())
|
|
{
|
|
sl->replaceKeys(keys);
|
|
return;
|
|
}
|
|
|
|
sl->removeExpiredPeerConnection();
|
|
for (unsigned int i = 0; i < users_xml->getNumNodes(); i++)
|
|
{
|
|
uint32_t addr, id;
|
|
uint16_t port;
|
|
users_xml->getNode(i)->get("ip", &addr);
|
|
users_xml->getNode(i)->get("port", &port);
|
|
users_xml->getNode(i)->get("id", &id);
|
|
users_xml->getNode(i)->get("aes-key", &keys[id].m_aes_key);
|
|
users_xml->getNode(i)->get("aes-iv", &keys[id].m_aes_iv);
|
|
users_xml->getNode(i)->get("username", &keys[id].m_name);
|
|
keys[id].m_tried = false;
|
|
if (ServerConfig::m_firewalled_server)
|
|
{
|
|
TransportAddress peer_addr(addr, port);
|
|
std::string peer_addr_str = peer_addr.toString();
|
|
if (sl->m_pending_peer_connection.find(peer_addr_str) !=
|
|
sl->m_pending_peer_connection.end())
|
|
{
|
|
continue;
|
|
}
|
|
std::make_shared<ConnectToPeer>(peer_addr)->requestStart();
|
|
sl->addPeerConnection(peer_addr_str);
|
|
}
|
|
}
|
|
sl->replaceKeys(keys);
|
|
}
|
|
public:
|
|
PollServerRequest(std::shared_ptr<ServerLobby> sl)
|
|
: XMLRequest(true), m_server_lobby(sl)
|
|
{
|
|
m_disable_sending_log = true;
|
|
}
|
|
}; // PollServerRequest
|
|
// ========================================================================
|
|
|
|
PollServerRequest* request = new PollServerRequest(
|
|
std::dynamic_pointer_cast<ServerLobby>(shared_from_this()));
|
|
NetworkConfig::get()->setServerDetails(request,
|
|
"poll-connection-requests");
|
|
const TransportAddress &addr = STKHost::get()->getPublicAddress();
|
|
request->addParameter("address", addr.getIP() );
|
|
request->addParameter("port", addr.getPort());
|
|
request->addParameter("current-players",
|
|
STKHost::get()->getTotalPlayers());
|
|
request->addParameter("game-started",
|
|
m_state.load() == WAITING_FOR_START_GAME ? 0 : 1);
|
|
Track* current_track = getPlayingTrack();
|
|
if (current_track)
|
|
request->addParameter("current-track", current_track->getIdent());
|
|
request->queue();
|
|
|
|
} // 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;
|
|
|
|
Log::info("ServerLobby", "The game is considered finished.");
|
|
// notify the network world that it is stopped
|
|
RaceEventManager::getInstance()->stop();
|
|
|
|
// stop race protocols before going back to lobby (end race)
|
|
RaceEventManager::getInstance()->getProtocol()->requestTerminate();
|
|
GameProtocol::lock()->requestTerminate();
|
|
|
|
// Save race result before delete the world
|
|
m_result_ns->clear();
|
|
m_result_ns->addUInt8(LE_RACE_FINISHED);
|
|
if (m_game_setup->isGrandPrix())
|
|
{
|
|
// fastest lap
|
|
int fastest_lap =
|
|
static_cast<LinearWorld*>(World::getWorld())->getFastestLapTicks();
|
|
m_result_ns->addUInt32(fastest_lap);
|
|
m_result_ns->encodeString(static_cast<LinearWorld*>(World::getWorld())
|
|
->getFastestLapKartName());
|
|
|
|
// all gp tracks
|
|
m_result_ns->addUInt8((uint8_t)m_game_setup->getTotalGrandPrixTracks())
|
|
.addUInt8((uint8_t)m_game_setup->getAllTracks().size());
|
|
for (const std::string& gp_track : m_game_setup->getAllTracks())
|
|
m_result_ns->encodeString(gp_track);
|
|
|
|
// each kart score and total time
|
|
m_result_ns->addUInt8((uint8_t)race_manager->getNumPlayers());
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
int last_score = race_manager->getKartScore(i);
|
|
int cur_score = last_score;
|
|
float overall_time = race_manager->getOverallTime(i);
|
|
if (auto player =
|
|
race_manager->getKartInfo(i).getNetworkPlayerProfile().lock())
|
|
{
|
|
last_score = player->getScore();
|
|
cur_score += last_score;
|
|
overall_time = overall_time + player->getOverallTime();
|
|
player->setScore(cur_score);
|
|
player->setOverallTime(overall_time);
|
|
}
|
|
m_result_ns->addUInt32(last_score).addUInt32(cur_score)
|
|
.addFloat(overall_time);
|
|
}
|
|
}
|
|
else if (race_manager->modeHasLaps())
|
|
{
|
|
int fastest_lap =
|
|
static_cast<LinearWorld*>(World::getWorld())->getFastestLapTicks();
|
|
m_result_ns->addUInt32(fastest_lap);
|
|
m_result_ns->encodeString(static_cast<LinearWorld*>(World::getWorld())
|
|
->getFastestLapKartName());
|
|
}
|
|
if (ServerConfig::m_ranked)
|
|
{
|
|
computeNewRankings();
|
|
submitRankingsToAddons();
|
|
}
|
|
m_state.store(WAIT_FOR_RACE_STOPPED);
|
|
} // checkRaceFinished
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Compute the new player's rankings used in ranked servers
|
|
*/
|
|
void ServerLobby::computeNewRankings()
|
|
{
|
|
// No ranking for battle mode
|
|
if (!race_manager->modeHasLaps())
|
|
return;
|
|
|
|
// Using a vector of vector, it would be possible to fill
|
|
// all j < i v[i][j] with -v[j][i]
|
|
// Would this be worth it ?
|
|
std::vector<double> scores_change;
|
|
std::vector<double> new_scores;
|
|
|
|
unsigned player_count = race_manager->getNumPlayers();
|
|
for (unsigned i = 0; i < player_count; i++)
|
|
{
|
|
const uint32_t id = race_manager->getKartInfo(i).getOnlineId();
|
|
new_scores.push_back(m_scores.at(id));
|
|
new_scores[i] += distributeBasePoints(id);
|
|
}
|
|
|
|
// First, update the number of ranked races
|
|
for (unsigned i = 0; i < player_count; i++)
|
|
{
|
|
const uint32_t id = race_manager->getKartInfo(i).getOnlineId();
|
|
m_num_ranked_races.at(id)++;
|
|
}
|
|
|
|
// Now compute points exchanges
|
|
for (unsigned i = 0; i < player_count; i++)
|
|
{
|
|
scores_change.push_back(0.0);
|
|
|
|
World* w = World::getWorld();
|
|
assert(w);
|
|
double player1_scores = new_scores[i];
|
|
// If the player has quitted before the race end,
|
|
// the value will be incorrect, but it will not be used
|
|
double player1_time = race_manager->getKartRaceTime(i);
|
|
double player1_factor =
|
|
computeRankingFactor(race_manager->getKartInfo(i).getOnlineId());
|
|
double player1_handicap = ( w->getKart(i)->getPerPlayerDifficulty()
|
|
== PLAYER_DIFFICULTY_HANDICAP ) ? HANDICAP_OFFSET : 0;
|
|
|
|
for (unsigned j = 0; j < player_count; j++)
|
|
{
|
|
// Don't compare a player with himself
|
|
if (i == j)
|
|
continue;
|
|
|
|
double result = 0.0;
|
|
double expected_result = 0.0;
|
|
double ranking_importance = 0.0;
|
|
double max_time = 0.0;
|
|
|
|
// No change between two quitting players
|
|
if (w->getKart(i)->isEliminated() &&
|
|
w->getKart(j)->isEliminated())
|
|
continue;
|
|
|
|
double player2_scores = new_scores[j];
|
|
double player2_time = race_manager->getKartRaceTime(j);
|
|
double player2_handicap = ( w->getKart(j)->getPerPlayerDifficulty()
|
|
== PLAYER_DIFFICULTY_HANDICAP ) ? HANDICAP_OFFSET : 0;
|
|
|
|
// Compute the result and race ranking importance
|
|
double player_factors = std::min(player1_factor,
|
|
computeRankingFactor(
|
|
race_manager->getKartInfo(j).getOnlineId()));
|
|
|
|
double mode_factor = getModeFactor();
|
|
|
|
if (w->getKart(i)->isEliminated())
|
|
{
|
|
result = 0.0;
|
|
player1_time = player2_time; // for getTimeSpread
|
|
max_time = MAX_SCALING_TIME;
|
|
}
|
|
else if (w->getKart(j)->isEliminated())
|
|
{
|
|
result = 1.0;
|
|
player2_time = player1_time;
|
|
max_time = MAX_SCALING_TIME;
|
|
}
|
|
else
|
|
{
|
|
// If time difference > 2,5% ; the result is 1 or 0
|
|
// Otherwise, it is averaged between 0 and 1.
|
|
if (player1_time <= player2_time)
|
|
{
|
|
result =
|
|
(player2_time - player1_time) / (player1_time / 20.0);
|
|
result = std::min(1.0, 0.5 + result);
|
|
}
|
|
else
|
|
{
|
|
result =
|
|
(player1_time - player2_time) / (player2_time / 20.0);
|
|
result = std::max(0.0, 0.5 - result);
|
|
}
|
|
|
|
max_time = std::min(std::max(player1_time, player2_time),
|
|
MAX_SCALING_TIME);
|
|
}
|
|
|
|
ranking_importance = mode_factor *
|
|
scalingValueForTime(max_time) * player_factors;
|
|
|
|
// Compute the expected result using an ELO-like function
|
|
double diff = player2_scores - player1_scores;
|
|
|
|
if (!w->getKart(i)->isEliminated() && !w->getKart(j)->isEliminated())
|
|
diff += player1_handicap - player2_handicap;
|
|
|
|
double uncertainty = std::max(getUncertaintySpread(race_manager->getKartInfo(i).getOnlineId()),
|
|
getUncertaintySpread(race_manager->getKartInfo(j).getOnlineId()) );
|
|
|
|
expected_result = 1.0/ (1.0 + std::pow(10.0,
|
|
diff / ( BASE_RANKING_POINTS / 2.0
|
|
* getModeSpread()
|
|
* getTimeSpread(std::min(player1_time, player2_time))
|
|
* uncertainty )));
|
|
|
|
// Compute the ranking change
|
|
scores_change[i] +=
|
|
ranking_importance * (result - expected_result);
|
|
}
|
|
}
|
|
|
|
// Don't merge it in the main loop as new_scores value are used there
|
|
for (unsigned i = 0; i < player_count; i++)
|
|
{
|
|
new_scores[i] += scores_change[i];
|
|
const uint32_t id = race_manager->getKartInfo(i).getOnlineId();
|
|
m_scores.at(id) = new_scores[i];
|
|
if (m_scores.at(id) > m_max_scores.at(id))
|
|
m_max_scores.at(id) = m_scores.at(id);
|
|
}
|
|
} // computeNewRankings
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Compute the ranking factor, used to make top rankings more stable
|
|
* and to allow new players to faster get to an appropriate ranking
|
|
*/
|
|
double ServerLobby::computeRankingFactor(uint32_t online_id)
|
|
{
|
|
double max_points = m_max_scores.at(online_id);
|
|
unsigned num_races = m_num_ranked_races.at(online_id);
|
|
|
|
if (max_points >= (BASE_RANKING_POINTS * 2.0))
|
|
return 0.6;
|
|
else if (max_points >= (BASE_RANKING_POINTS * 1.75) || num_races > 500)
|
|
return 0.7;
|
|
else if (max_points >= (BASE_RANKING_POINTS * 1.5) || num_races > 250)
|
|
return 0.8;
|
|
else if (max_points >= (BASE_RANKING_POINTS * 1.25) || num_races > 100)
|
|
return 1.0;
|
|
// The base ranking points are not distributed all at once
|
|
// So it's not guaranteed a player reach them
|
|
else if (max_points >= (BASE_RANKING_POINTS) || num_races > 50)
|
|
return 1.2;
|
|
else
|
|
return 1.5;
|
|
|
|
} // computeRankingFactor
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns the mode race importance factor,
|
|
* used to make ranking move slower in more random modes.
|
|
*/
|
|
double ServerLobby::getModeFactor()
|
|
{
|
|
if (race_manager->isTimeTrialMode())
|
|
return 1.0;
|
|
return 0.7;
|
|
} // getModeFactor
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns the mode spread factor, used so that a similar difference in
|
|
* skill will result in a similar ranking difference in more random modes.
|
|
*/
|
|
double ServerLobby::getModeSpread()
|
|
{
|
|
if (race_manager->isTimeTrialMode())
|
|
return 1.0;
|
|
|
|
//TODO: the value used here for normal races is a wild guess.
|
|
// When hard data to the spread tendencies of time-trial
|
|
// and normal mode becomes available, update this to make
|
|
// the spreads more comparable
|
|
return 1.5;
|
|
} // getModeSpread
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns the time spread factor.
|
|
* Short races are more random, so the expected result changes depending
|
|
* on race duration.
|
|
*/
|
|
double ServerLobby::getTimeSpread(double time)
|
|
{
|
|
return sqrt(120.0 / time);
|
|
} // getTimeSpread
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Returns the uncertainty spread factor.
|
|
* The ranking of new players is not yet reliable,
|
|
* so weight the expected results twoards 0.5 by using a > 1 spread
|
|
*/
|
|
double ServerLobby::getUncertaintySpread(uint32_t online_id)
|
|
{
|
|
unsigned num_races = m_num_ranked_races.at(online_id);
|
|
if (num_races <= 60)
|
|
return 0.5 + (4.0/sqrt(num_races+3));
|
|
else
|
|
return 1.0;
|
|
} // getUncertaintySpread
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Compute the scaling value of a given time
|
|
* This is linear to race duration, getTimeSpread takes care
|
|
* of expecting a more random result in shorter races.
|
|
*/
|
|
double ServerLobby::scalingValueForTime(double time)
|
|
{
|
|
return time * MAX_POINTS_PER_SECOND;
|
|
} // scalingValueForTime
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Manages the distribution of the base points.
|
|
* Gives half of the points progressively
|
|
* by smaller and smaller chuncks from race 1 to 60.
|
|
* The race count is incremented after this is called, so num_races
|
|
* is between 0 and 59.
|
|
* The first half is distributed when the player enters
|
|
* for the first time in a ranked server.
|
|
*/
|
|
double ServerLobby::distributeBasePoints(uint32_t online_id)
|
|
{
|
|
unsigned num_races = m_num_ranked_races.at(online_id);
|
|
if (num_races < 60)
|
|
{
|
|
return BASE_RANKING_POINTS / 8000.0 * std::max((96u - num_races), 41u);
|
|
}
|
|
else
|
|
return 0.0;
|
|
} // distributeBasePoints
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when a client disconnects.
|
|
* \param event The disconnect event.
|
|
*/
|
|
void ServerLobby::clientDisconnected(Event* event)
|
|
{
|
|
auto players_on_peer = event->getPeer()->getPlayerProfiles();
|
|
if (players_on_peer.empty())
|
|
return;
|
|
|
|
NetworkString* msg = getNetworkString(2);
|
|
const bool waiting_peer_disconnected =
|
|
event->getPeer()->isWaitingForGame();
|
|
msg->setSynchronous(true);
|
|
msg->addUInt8(LE_PLAYER_DISCONNECTED);
|
|
msg->addUInt8((uint8_t)players_on_peer.size())
|
|
.addUInt32(event->getPeer()->getHostId());
|
|
for (auto p : players_on_peer)
|
|
{
|
|
std::string name = StringUtils::wideToUtf8(p->getName());
|
|
msg->encodeString(name);
|
|
Log::info("ServerLobby", "%s disconnected", name.c_str());
|
|
}
|
|
|
|
// Don't show waiting peer disconnect message to in game player
|
|
STKHost::get()->sendPacketToAllPeersWith([waiting_peer_disconnected]
|
|
(STKPeer* p)
|
|
{
|
|
if (!p->isValidated())
|
|
return false;
|
|
if (!p->isWaitingForGame() && waiting_peer_disconnected)
|
|
return false;
|
|
return true;
|
|
}, msg);
|
|
updatePlayerList();
|
|
delete msg;
|
|
} // clientDisconnected
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::clearDisconnectedRankedPlayer()
|
|
{
|
|
for (auto it = m_ranked_players.begin(); it != m_ranked_players.end();)
|
|
{
|
|
if (it->second.expired())
|
|
{
|
|
const uint32_t id = it->first;
|
|
m_scores.erase(id);
|
|
m_max_scores.erase(id);
|
|
m_num_ranked_races.erase(id);
|
|
it = m_ranked_players.erase(it);
|
|
}
|
|
else
|
|
{
|
|
it++;
|
|
}
|
|
}
|
|
} // clearDisconnectedRankedPlayer
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::connectionRequested(Event* event)
|
|
{
|
|
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
|
NetworkString& data = event->data();
|
|
if (!checkDataSize(event, 14)) return;
|
|
|
|
peer->cleanPlayerProfiles();
|
|
|
|
// can we add the player ?
|
|
if (!allowJoinedPlayersWaiting() &&
|
|
(m_state.load() != WAITING_FOR_START_GAME ||
|
|
m_game_setup->isGrandPrixStarted()))
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BUSY);
|
|
// send only to the peer that made the request and disconect it now
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: selection started");
|
|
return;
|
|
}
|
|
|
|
// Check server version
|
|
int version = data.getUInt32();
|
|
if (version < stk_config->m_min_server_version ||
|
|
version > stk_config->m_max_server_version)
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED)
|
|
.addUInt8(RR_INCOMPATIBLE_DATA);
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: wrong server version");
|
|
return;
|
|
}
|
|
std::string user_version;
|
|
data.decodeString(&user_version);
|
|
event->getPeer()->setUserVersion(user_version);
|
|
|
|
unsigned list_caps = data.getUInt16();
|
|
std::vector<std::string> caps;
|
|
for (unsigned i = 0; i < list_caps; i++)
|
|
{
|
|
std::string cap;
|
|
data.decodeString(&cap);
|
|
caps.push_back(cap);
|
|
}
|
|
event->getPeer()->setClientCapabilities(caps);
|
|
|
|
std::set<std::string> client_karts, client_tracks;
|
|
const unsigned kart_num = data.getUInt16();
|
|
const unsigned track_num = data.getUInt16();
|
|
for (unsigned i = 0; i < kart_num; i++)
|
|
{
|
|
std::string kart;
|
|
data.decodeString(&kart);
|
|
client_karts.insert(kart);
|
|
}
|
|
for (unsigned i = 0; i < track_num; i++)
|
|
{
|
|
std::string track;
|
|
data.decodeString(&track);
|
|
client_tracks.insert(track);
|
|
}
|
|
|
|
// Drop this player if he doesn't have at least 1 kart / track the same
|
|
// as server
|
|
float okt = 0.0f;
|
|
float ott = 0.0f;
|
|
for (auto& client_kart : client_karts)
|
|
{
|
|
if (m_official_kts.first.find(client_kart) !=
|
|
m_official_kts.first.end())
|
|
okt += 1.0f;
|
|
}
|
|
okt = okt / (float)m_official_kts.first.size();
|
|
for (auto& client_track : client_tracks)
|
|
{
|
|
if (m_official_kts.second.find(client_track) !=
|
|
m_official_kts.second.end())
|
|
ott += 1.0f;
|
|
}
|
|
ott = ott / (float)m_official_kts.second.size();
|
|
|
|
std::set<std::string> karts_erase, tracks_erase;
|
|
for (const std::string& server_kart : m_available_kts.first)
|
|
{
|
|
if (client_karts.find(server_kart) == client_karts.end())
|
|
{
|
|
karts_erase.insert(server_kart);
|
|
}
|
|
}
|
|
for (const std::string& server_track : m_available_kts.second)
|
|
{
|
|
if (client_tracks.find(server_track) == client_tracks.end())
|
|
{
|
|
tracks_erase.insert(server_track);
|
|
}
|
|
}
|
|
|
|
if (karts_erase.size() == m_available_kts.first.size() ||
|
|
tracks_erase.size() == m_available_kts.second.size() ||
|
|
okt < ServerConfig::m_official_karts_threshold ||
|
|
ott < ServerConfig::m_official_tracks_threshold)
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED)
|
|
.addUInt8(RR_INCOMPATIBLE_DATA);
|
|
peer->cleanPlayerProfiles();
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player has incompatible karts / tracks.");
|
|
return;
|
|
}
|
|
|
|
// Save available karts and tracks from clients in STKPeer so if this peer
|
|
// disconnects later in lobby it won't affect current players
|
|
peer->setAvailableKartsTracks(client_karts, client_tracks);
|
|
|
|
unsigned player_count = data.getUInt8();
|
|
uint32_t online_id = 0;
|
|
uint32_t encrypted_size = 0;
|
|
online_id = data.getUInt32();
|
|
encrypted_size = data.getUInt32();
|
|
|
|
bool is_banned = isBannedForIP(peer->getAddress());
|
|
if (online_id != 0 && !is_banned)
|
|
{
|
|
if (m_online_id_ban_list.find(online_id) !=
|
|
m_online_id_ban_list.end() &&
|
|
(uint32_t)StkTime::getTimeSinceEpoch() <
|
|
m_online_id_ban_list.at(online_id))
|
|
{
|
|
is_banned = true;
|
|
}
|
|
}
|
|
|
|
if (is_banned)
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_BANNED);
|
|
// For future we can say the reason here
|
|
// and use encodeString instead
|
|
message->addUInt8(0);
|
|
peer->cleanPlayerProfiles();
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: banned");
|
|
return;
|
|
}
|
|
|
|
unsigned total_players = 0;
|
|
STKHost::get()->updatePlayers(NULL, NULL, &total_players);
|
|
if (total_players + player_count >
|
|
(unsigned)ServerConfig::m_server_max_players)
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_TOO_MANY_PLAYERS);
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: too many players");
|
|
return;
|
|
}
|
|
|
|
// Reject non-valiated player joinning if WAN server and not disabled
|
|
// encforement of validation, unless it's player from localhost or lan
|
|
// And no duplicated online id or split screen players in ranked server
|
|
std::set<uint32_t> all_online_ids =
|
|
STKHost::get()->getAllPlayerOnlineIds();
|
|
bool duplicated_ranked_player =
|
|
all_online_ids.find(online_id) != all_online_ids.end();
|
|
|
|
if (((encrypted_size == 0 || online_id == 0) &&
|
|
!(peer->getAddress().isPublicAddressLocalhost() ||
|
|
peer->getAddress().isLAN()) &&
|
|
NetworkConfig::get()->isWAN() &&
|
|
ServerConfig::m_validating_player) ||
|
|
(ServerConfig::m_strict_players &&
|
|
(player_count != 1 || online_id == 0 || duplicated_ranked_player)))
|
|
{
|
|
NetworkString* message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_INVALID_PLAYER);
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: invalid player");
|
|
return;
|
|
}
|
|
|
|
if (encrypted_size != 0)
|
|
{
|
|
m_pending_connection[peer] = std::make_pair(online_id,
|
|
BareNetworkString(data.getCurrentData(), encrypted_size));
|
|
}
|
|
else
|
|
{
|
|
core::stringw online_name;
|
|
if (online_id > 0)
|
|
data.decodeStringW(&online_name);
|
|
handleUnencryptedConnection(peer, data, online_id, online_name);
|
|
}
|
|
} // connectionRequested
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::handleUnencryptedConnection(std::shared_ptr<STKPeer> peer,
|
|
BareNetworkString& data, uint32_t online_id,
|
|
const core::stringw& online_name)
|
|
{
|
|
if (data.size() < 2) return;
|
|
|
|
// Check for password
|
|
std::string password;
|
|
data.decodeString(&password);
|
|
const std::string& server_pw = ServerConfig::m_private_server_password;
|
|
if (password != server_pw)
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED)
|
|
.addUInt8(RR_INCORRECT_PASSWORD);
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: incorrect password");
|
|
return;
|
|
}
|
|
|
|
// Check again for duplicated player in ranked server
|
|
std::set<uint32_t> all_online_ids =
|
|
STKHost::get()->getAllPlayerOnlineIds();
|
|
bool duplicated_ranked_player =
|
|
all_online_ids.find(online_id) != all_online_ids.end();
|
|
if (ServerConfig::m_ranked && duplicated_ranked_player)
|
|
{
|
|
NetworkString* message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED).addUInt8(RR_INVALID_PLAYER);
|
|
peer->sendPacket(message, true/*reliable*/, false/*encrypted*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby", "Player refused: invalid player");
|
|
return;
|
|
}
|
|
|
|
unsigned player_count = data.getUInt8();
|
|
auto red_blue = STKHost::get()->getAllPlayersTeamInfo();
|
|
for (unsigned i = 0; i < player_count; i++)
|
|
{
|
|
core::stringw name;
|
|
data.decodeStringW(&name);
|
|
if (name.empty())
|
|
name = L"unnamed";
|
|
float default_kart_color = data.getFloat();
|
|
PerPlayerDifficulty per_player_difficulty =
|
|
(PerPlayerDifficulty)data.getUInt8();
|
|
auto player = std::make_shared<NetworkPlayerProfile>
|
|
(peer, i == 0 && !online_name.empty() ? online_name : name,
|
|
peer->getHostId(), default_kart_color, i == 0 ? online_id : 0,
|
|
per_player_difficulty, (uint8_t)i, KART_TEAM_NONE,
|
|
""/* reserved for country id */);
|
|
if (ServerConfig::m_team_choosing)
|
|
{
|
|
KartTeam cur_team = KART_TEAM_NONE;
|
|
if (red_blue.first > red_blue.second)
|
|
{
|
|
cur_team = KART_TEAM_BLUE;
|
|
red_blue.second++;
|
|
}
|
|
else
|
|
{
|
|
cur_team = KART_TEAM_RED;
|
|
red_blue.first++;
|
|
}
|
|
player->setTeam(cur_team);
|
|
}
|
|
peer->addPlayer(player);
|
|
}
|
|
|
|
peer->setValidated();
|
|
|
|
// send a message to the one that asked to connect
|
|
NetworkString* server_info = getNetworkString();
|
|
server_info->setSynchronous(true);
|
|
server_info->addUInt8(LE_SERVER_INFO);
|
|
m_game_setup->addServerInfo(server_info);
|
|
peer->sendPacket(server_info);
|
|
delete server_info;
|
|
|
|
const bool game_started = m_state.load() != WAITING_FOR_START_GAME;
|
|
NetworkString* message_ack = getNetworkString(4);
|
|
message_ack->setSynchronous(true);
|
|
// connection success -- return the host id of peer
|
|
float auto_start_timer = 0.0f;
|
|
if (m_timeout.load() == std::numeric_limits<int64_t>::max())
|
|
auto_start_timer = std::numeric_limits<float>::max();
|
|
else
|
|
{
|
|
auto_start_timer =
|
|
(m_timeout.load() - (int64_t)StkTime::getRealTimeMs()) / 1000.0f;
|
|
}
|
|
message_ack->addUInt8(LE_CONNECTION_ACCEPTED).addUInt32(peer->getHostId())
|
|
.addUInt32(ServerConfig::m_server_version);
|
|
|
|
// Reserved for future to supply list of network capabilities
|
|
message_ack->addUInt16(0);
|
|
|
|
message_ack->addFloat(auto_start_timer)
|
|
.addUInt32(ServerConfig::m_state_frequency)
|
|
.addUInt8(ServerConfig::m_chat ? 1 : 0);
|
|
|
|
peer->setSpectator(false);
|
|
if (game_started)
|
|
{
|
|
peer->setWaitingForGame(true);
|
|
updatePlayerList();
|
|
peer->sendPacket(message_ack);
|
|
delete message_ack;
|
|
}
|
|
else
|
|
{
|
|
peer->setWaitingForGame(false);
|
|
m_peers_ready[peer] = false;
|
|
for (std::shared_ptr<NetworkPlayerProfile>& npp :
|
|
peer->getPlayerProfiles())
|
|
{
|
|
Log::info("ServerLobby",
|
|
"New player %s with online id %u from %s with %s.",
|
|
StringUtils::wideToUtf8(npp->getName()).c_str(),
|
|
npp->getOnlineId(), peer->getAddress().toString().c_str(),
|
|
peer->getUserVersion().c_str());
|
|
}
|
|
updatePlayerList();
|
|
peer->sendPacket(message_ack);
|
|
delete message_ack;
|
|
|
|
if (ServerConfig::m_ranked)
|
|
{
|
|
getRankingForPlayer(peer->getPlayerProfiles()[0]);
|
|
}
|
|
}
|
|
} // handleUnencryptedConnection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Called when any players change their setting (team for example), or
|
|
* connection / disconnection, it will use the game_started parameter to
|
|
* determine if this should be send to all peers in server or just in game.
|
|
* \param update_when_reset_server If true, this message will be sent to
|
|
* all peers.
|
|
*/
|
|
void ServerLobby::updatePlayerList(bool update_when_reset_server)
|
|
{
|
|
const bool game_started = m_state.load() != WAITING_FOR_START_GAME &&
|
|
!update_when_reset_server;
|
|
|
|
// No need to update player list (for started grand prix currently)
|
|
if (!allowJoinedPlayersWaiting() &&
|
|
m_state.load() > WAITING_FOR_START_GAME && !update_when_reset_server)
|
|
return;
|
|
|
|
auto all_profiles = STKHost::get()->getAllPlayerProfiles();
|
|
NetworkString* pl = getNetworkString();
|
|
pl->setSynchronous(true);
|
|
pl->addUInt8(LE_UPDATE_PLAYER_LIST)
|
|
.addUInt8((uint8_t)(game_started ? 1 : 0))
|
|
.addUInt8((uint8_t)all_profiles.size());
|
|
for (auto profile : all_profiles)
|
|
{
|
|
pl->addUInt32(profile->getHostId()).addUInt32(profile->getOnlineId())
|
|
.addUInt8(profile->getLocalPlayerId())
|
|
.encodeString(profile->getName());
|
|
std::shared_ptr<STKPeer> p = profile->getPeer();
|
|
uint8_t boolean_combine = 0;
|
|
if (p && p->isWaitingForGame())
|
|
boolean_combine |= 1;
|
|
if (p && p->isSpectator())
|
|
boolean_combine |= (1 << 1);
|
|
if (p && m_server_owner_id.load() == p->getHostId())
|
|
boolean_combine |= (1 << 2);
|
|
if (ServerConfig::m_owner_less && !game_started &&
|
|
m_peers_ready.find(p) != m_peers_ready.end() &&
|
|
m_peers_ready.at(p))
|
|
boolean_combine |= (1 << 3);
|
|
pl->addUInt8(boolean_combine);
|
|
pl->addUInt8(profile->getPerPlayerDifficulty());
|
|
if (ServerConfig::m_team_choosing &&
|
|
race_manager->teamEnabled())
|
|
pl->addUInt8(profile->getTeam());
|
|
else
|
|
pl->addUInt8(KART_TEAM_NONE);
|
|
pl->encodeString(profile->getCountryId());
|
|
}
|
|
|
|
// Don't send this message to in-game players
|
|
STKHost::get()->sendPacketToAllPeersWith([game_started]
|
|
(STKPeer* p)
|
|
{
|
|
if (!p->isValidated())
|
|
return false;
|
|
if (!p->isWaitingForGame() && game_started)
|
|
return false;
|
|
return true;
|
|
}, pl);
|
|
delete pl;
|
|
} // updatePlayerList
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::updateServerOwner()
|
|
{
|
|
if (m_state.load() < WAITING_FOR_START_GAME ||
|
|
m_state.load() > RESULT_DISPLAY ||
|
|
ServerConfig::m_owner_less)
|
|
return;
|
|
if (!m_server_owner.expired())
|
|
return;
|
|
auto peers = STKHost::get()->getPeers();
|
|
if (peers.empty())
|
|
return;
|
|
std::sort(peers.begin(), peers.end(), [](const std::shared_ptr<STKPeer> a,
|
|
const std::shared_ptr<STKPeer> b)->bool
|
|
{
|
|
return a->getHostId() < b->getHostId();
|
|
});
|
|
|
|
std::shared_ptr<STKPeer> owner;
|
|
for (auto peer: peers)
|
|
{
|
|
// Only 127.0.0.1 can be server owner in case of graphics-client-server
|
|
if (peer->isValidated() &&
|
|
(NetworkConfig::get()->getServerIdFile().empty() ||
|
|
peer->getAddress().getIP() == 0x7f000001))
|
|
{
|
|
owner = peer;
|
|
break;
|
|
}
|
|
}
|
|
if (owner)
|
|
{
|
|
NetworkString* ns = getNetworkString();
|
|
ns->setSynchronous(true);
|
|
ns->addUInt8(LE_SERVER_OWNERSHIP);
|
|
owner->sendPacket(ns);
|
|
delete ns;
|
|
m_server_owner = owner;
|
|
m_server_owner_id.store(owner->getHostId());
|
|
updatePlayerList();
|
|
}
|
|
} // updateServerOwner
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! \brief Called when a player asks to select karts.
|
|
* \param event : Event providing the information.
|
|
*/
|
|
void ServerLobby::kartSelectionRequested(Event* event)
|
|
{
|
|
if (m_state != SELECTING || m_game_setup->isGrandPrixStarted())
|
|
{
|
|
Log::warn("ServerLobby", "Received kart selection while in state %d.",
|
|
m_state.load());
|
|
return;
|
|
}
|
|
|
|
if (!checkDataSize(event, 1) ||
|
|
event->getPeer()->getPlayerProfiles().empty())
|
|
return;
|
|
|
|
const NetworkString& data = event->data();
|
|
STKPeer* peer = event->getPeer();
|
|
setPlayerKarts(data, peer);
|
|
} // kartSelectionRequested
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! \brief Called when a player votes for track(s), it will auto correct client
|
|
* data if it sends some invalid data.
|
|
* \param event : Event providing the information.
|
|
*/
|
|
void ServerLobby::handlePlayerVote(Event* event)
|
|
{
|
|
if (m_state != SELECTING || !ServerConfig::m_track_voting)
|
|
{
|
|
Log::warn("ServerLobby", "Received track vote while in state %d.",
|
|
m_state.load());
|
|
return;
|
|
}
|
|
|
|
if (!checkDataSize(event, 4) ||
|
|
event->getPeer()->getPlayerProfiles().empty() ||
|
|
event->getPeer()->isWaitingForGame())
|
|
return;
|
|
|
|
if (isVotingOver()) return;
|
|
|
|
NetworkString& data = event->data();
|
|
PeerVote vote(data);
|
|
Log::debug("ServerLobby",
|
|
"Vote from client: host %d, track %s, laps %d, reverse %d.",
|
|
event->getPeer()->getHostId(), vote.m_track_name.c_str(),
|
|
vote.m_num_laps, vote.m_reverse);
|
|
|
|
Track* t = track_manager->getTrack(vote.m_track_name);
|
|
if (!t)
|
|
{
|
|
vote.m_track_name = *m_available_kts.second.begin();
|
|
t = track_manager->getTrack(vote.m_track_name);
|
|
assert(t);
|
|
}
|
|
|
|
// Remove / adjust any invalid settings
|
|
if (race_manager->modeHasLaps())
|
|
{
|
|
if (ServerConfig::m_auto_game_time_ratio > 0.0f)
|
|
{
|
|
vote.m_num_laps =
|
|
(uint8_t)(fmaxf(1.0f, (float)t->getDefaultNumberOfLaps() *
|
|
ServerConfig::m_auto_game_time_ratio));
|
|
}
|
|
else if (vote.m_num_laps == 0 || vote.m_num_laps > 20)
|
|
vote.m_num_laps = (uint8_t)3;
|
|
if (!t->reverseAvailable() && vote.m_reverse)
|
|
vote.m_reverse = false;
|
|
}
|
|
else if (race_manager->isSoccerMode())
|
|
{
|
|
if (m_game_setup->isSoccerGoalTarget())
|
|
{
|
|
if (ServerConfig::m_auto_game_time_ratio > 0.0f)
|
|
{
|
|
vote.m_num_laps = (uint8_t)(ServerConfig::m_auto_game_time_ratio *
|
|
UserConfigParams::m_num_goals);
|
|
}
|
|
else if (vote.m_num_laps > 10)
|
|
vote.m_num_laps = (uint8_t)5;
|
|
}
|
|
else
|
|
{
|
|
if (ServerConfig::m_auto_game_time_ratio > 0.0f)
|
|
{
|
|
vote.m_num_laps = (uint8_t)(ServerConfig::m_auto_game_time_ratio *
|
|
UserConfigParams::m_soccer_time_limit);
|
|
}
|
|
else if (vote.m_num_laps > 15)
|
|
vote.m_num_laps = (uint8_t)7;
|
|
}
|
|
}
|
|
else if (race_manager->getMinorMode() ==
|
|
RaceManager::MINOR_MODE_FREE_FOR_ALL)
|
|
{
|
|
vote.m_num_laps = 0;
|
|
}
|
|
else if (race_manager->getMinorMode() ==
|
|
RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
|
|
{
|
|
vote.m_num_laps = 0;
|
|
vote.m_reverse = false;
|
|
}
|
|
|
|
// Store vote:
|
|
vote.m_player_name = event->getPeer()->getPlayerProfiles()[0]->getName();
|
|
addVote(event->getPeer()->getHostId(), vote);
|
|
|
|
// Now inform all clients about the vote
|
|
NetworkString other = NetworkString(PROTOCOL_LOBBY_ROOM);
|
|
other.setSynchronous(true);
|
|
other.addUInt8(LE_VOTE);
|
|
other.addUInt32(event->getPeer()->getHostId());
|
|
vote.encode(&other);
|
|
sendMessageToPeers(&other);
|
|
|
|
} // handlePlayerVote
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Select the track to be used based on all votes being received.
|
|
* \param winner_vote The PeerVote that was picked.
|
|
* \param winner_peer_id The host id of winner (unchanged if no vote).
|
|
* \return True if race can go on, otherwise wait.
|
|
*/
|
|
bool ServerLobby::handleAllVotes(PeerVote* winner_vote,
|
|
uint32_t* winner_peer_id)
|
|
{
|
|
// Determine majority agreement when 35% of voting time remains,
|
|
// reserve some time for kart selection so it's not 50%
|
|
if (getRemainingVotingTime() / getMaxVotingTime() > 0.35f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// First remove all votes from disconnected hosts
|
|
auto it = m_peers_votes.begin();
|
|
while (it != m_peers_votes.end())
|
|
{
|
|
auto peer = STKHost::get()->findPeerByHostId(it->first);
|
|
if (peer == nullptr)
|
|
{
|
|
it = m_peers_votes.erase(it);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
|
|
if (m_peers_votes.empty())
|
|
{
|
|
if (isVotingOver())
|
|
{
|
|
*winner_vote = *m_default_vote;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Count number of players
|
|
float cur_players = 0.0f;
|
|
auto peers = STKHost::get()->getPeers();
|
|
for (auto peer : peers)
|
|
{
|
|
if (peer->hasPlayerProfiles() && !peer->isWaitingForGame())
|
|
cur_players += 1.0f;
|
|
}
|
|
|
|
std::string top_track = m_default_vote->m_track_name;
|
|
int top_laps = m_default_vote->m_num_laps;
|
|
bool top_reverse = m_default_vote->m_reverse;
|
|
|
|
std::map<std::string, unsigned> tracks;
|
|
std::map<unsigned, unsigned> laps;
|
|
std::map<bool, unsigned> reverses;
|
|
|
|
// Ratio to determine majority agreement
|
|
float tracks_rate = 0.0f;
|
|
float laps_rate = 0.0f;
|
|
float reverses_rate = 0.0f;
|
|
RandomGenerator rg;
|
|
|
|
for (auto& p : m_peers_votes)
|
|
{
|
|
auto track_vote = tracks.find(p.second.m_track_name);
|
|
if (track_vote == tracks.end())
|
|
tracks[p.second.m_track_name] = 1;
|
|
else
|
|
track_vote->second++;
|
|
auto lap_vote = laps.find(p.second.m_num_laps);
|
|
if (lap_vote == laps.end())
|
|
laps[p.second.m_num_laps] = 1;
|
|
else
|
|
lap_vote->second++;
|
|
auto reverse_vote = reverses.find(p.second.m_reverse);
|
|
if (reverse_vote == reverses.end())
|
|
reverses[p.second.m_reverse] = 1;
|
|
else
|
|
reverse_vote->second++;
|
|
}
|
|
|
|
unsigned vote = 0;
|
|
auto track_vote = tracks.begin();
|
|
// rg.get(2) == 0 will allow not always the "less" in map get picked
|
|
for (auto c_vote = tracks.begin(); c_vote != tracks.end(); c_vote++)
|
|
{
|
|
if (c_vote->second > vote ||
|
|
(c_vote->second >= vote && rg.get(2) == 0))
|
|
{
|
|
vote = c_vote->second;
|
|
track_vote = c_vote;
|
|
}
|
|
}
|
|
if (track_vote != tracks.end())
|
|
{
|
|
top_track = track_vote->first;
|
|
tracks_rate = float(track_vote->second) / cur_players;
|
|
}
|
|
|
|
vote = 0;
|
|
auto lap_vote = laps.begin();
|
|
for (auto c_vote = laps.begin(); c_vote != laps.end(); c_vote++)
|
|
{
|
|
if (c_vote->second > vote ||
|
|
(c_vote->second >= vote && rg.get(2) == 0))
|
|
{
|
|
vote = c_vote->second;
|
|
lap_vote = c_vote;
|
|
}
|
|
}
|
|
if (lap_vote != laps.end())
|
|
{
|
|
top_laps = lap_vote->first;
|
|
laps_rate = float(lap_vote->second) / cur_players;
|
|
}
|
|
|
|
vote = 0;
|
|
auto reverse_vote = reverses.begin();
|
|
for (auto c_vote = reverses.begin(); c_vote != reverses.end(); c_vote++)
|
|
{
|
|
if (c_vote->second > vote ||
|
|
(c_vote->second >= vote && rg.get(2) == 0))
|
|
{
|
|
vote = c_vote->second;
|
|
reverse_vote = c_vote;
|
|
}
|
|
}
|
|
if (reverse_vote != reverses.end())
|
|
{
|
|
top_reverse = reverse_vote->first;
|
|
reverses_rate = float(reverse_vote->second) / cur_players;
|
|
}
|
|
|
|
// End early if there is majority agreement which is all entries rate > 0.5
|
|
it = m_peers_votes.begin();
|
|
if (tracks_rate > 0.5f && laps_rate > 0.5f && reverses_rate > 0.5f)
|
|
{
|
|
while (it != m_peers_votes.end())
|
|
{
|
|
if (it->second.m_track_name == top_track &&
|
|
it->second.m_num_laps == top_laps &&
|
|
it->second.m_reverse == top_reverse)
|
|
break;
|
|
else
|
|
it++;
|
|
}
|
|
if (it == m_peers_votes.end())
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Missing track %s from majority.", top_track.c_str());
|
|
it = m_peers_votes.begin();
|
|
}
|
|
*winner_peer_id = it->first;
|
|
*winner_vote = it->second;
|
|
return true;
|
|
}
|
|
else if (isVotingOver())
|
|
{
|
|
// Pick the best lap (or soccer goal / time) from only the top track
|
|
// if no majority agreement from all
|
|
int diff = std::numeric_limits<int>::max();
|
|
auto closest_lap = m_peers_votes.begin();
|
|
while (it != m_peers_votes.end())
|
|
{
|
|
if (it->second.m_track_name == top_track &&
|
|
std::abs((int)it->second.m_num_laps - top_laps) < diff)
|
|
{
|
|
closest_lap = it;
|
|
diff = std::abs((int)it->second.m_num_laps - top_laps);
|
|
}
|
|
else
|
|
it++;
|
|
}
|
|
*winner_peer_id = closest_lap->first;
|
|
*winner_vote = closest_lap->second;
|
|
return true;
|
|
}
|
|
return false;
|
|
} // handleAllVotes
|
|
|
|
// ----------------------------------------------------------------------------
|
|
void ServerLobby::getHitCaptureLimit()
|
|
{
|
|
int hit_capture_limit = std::numeric_limits<int>::max();
|
|
float time_limit = 0.0f;
|
|
if (race_manager->getMinorMode() ==
|
|
RaceManager::MINOR_MODE_CAPTURE_THE_FLAG)
|
|
{
|
|
if (ServerConfig::m_capture_limit > 0)
|
|
hit_capture_limit = ServerConfig::m_capture_limit;
|
|
if (ServerConfig::m_time_limit_ctf > 0)
|
|
time_limit = (float)ServerConfig::m_time_limit_ctf;
|
|
}
|
|
else
|
|
{
|
|
if (ServerConfig::m_hit_limit > 0)
|
|
hit_capture_limit = ServerConfig::m_hit_limit;
|
|
if (ServerConfig::m_time_limit_ffa > 0.0f)
|
|
time_limit = (float)ServerConfig::m_time_limit_ffa;
|
|
}
|
|
m_battle_hit_capture_limit = hit_capture_limit;
|
|
m_battle_time_limit = time_limit;
|
|
} // getHitCaptureLimit
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** 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()
|
|
{
|
|
for (auto p : m_peers_ready)
|
|
{
|
|
if (auto peer = p.first.lock())
|
|
peer->updateLastActivity();
|
|
}
|
|
m_server_has_loaded_world.store(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)
|
|
{
|
|
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
|
peer->updateLastActivity();
|
|
m_peers_ready.at(peer) = true;
|
|
Log::info("ServerLobby", "Peer %d has finished loading world at %lf",
|
|
peer->getHostId(), StkTime::getRealTime());
|
|
} // finishedLoadingWorldClient
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** 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)
|
|
{
|
|
if (m_rs_state.load() == RS_ASYNC_RESET ||
|
|
m_state.load() != RESULT_DISPLAY)
|
|
return;
|
|
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
|
m_peers_ready.at(peer) = true;
|
|
} // playerFinishedResult
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::updateBanList()
|
|
{
|
|
m_ip_ban_list.clear();
|
|
m_online_id_ban_list.clear();
|
|
|
|
for (auto& ban : ServerConfig::m_server_ip_ban_list)
|
|
{
|
|
if (ban.first == "0.0.0.0/0" ||
|
|
(uint32_t)StkTime::getTimeSinceEpoch() > ban.second)
|
|
continue;
|
|
uint32_t netbits = 0;
|
|
std::vector<std::string> ip_and_netbits =
|
|
StringUtils::split(ban.first, '/');
|
|
if (ip_and_netbits.size() != 2 ||
|
|
!StringUtils::fromString(ip_and_netbits[1], netbits) ||
|
|
netbits > 32)
|
|
{
|
|
Log::error("STKHost", "Wrong CIDR: %s", ban.first.c_str());
|
|
continue;
|
|
}
|
|
TransportAddress addr(ip_and_netbits[0]);
|
|
if (addr.getIP() == 0)
|
|
{
|
|
Log::error("STKHost", "Wrong CIDR: %s", ban.first.c_str());
|
|
continue;
|
|
}
|
|
uint32_t mask = ~((1 << (32 - netbits)) - 1);
|
|
uint32_t ip_start = addr.getIP() & mask;
|
|
uint32_t ip_end = (addr.getIP() & mask) | ~mask;
|
|
m_ip_ban_list[ip_start] =
|
|
std::make_tuple(ip_end, ban.first, ban.second);
|
|
}
|
|
|
|
std::map<std::string, uint32_t> final_ip_ban_list;
|
|
for (auto it = m_ip_ban_list.begin();
|
|
it != m_ip_ban_list.end();)
|
|
{
|
|
auto next_itr = std::next(it);
|
|
if (next_itr != m_ip_ban_list.end() &&
|
|
next_itr->first <= std::get<0>(it->second))
|
|
{
|
|
Log::warn("ServerLobby", "%s overlaps %s, removing the first one.",
|
|
std::get<1>(next_itr->second).c_str(),
|
|
std::get<1>(it->second).c_str());
|
|
m_ip_ban_list.erase(next_itr);
|
|
continue;
|
|
}
|
|
final_ip_ban_list[std::get<1>(it->second)] =
|
|
ServerConfig::m_server_ip_ban_list.at(std::get<1>(it->second));
|
|
it++;
|
|
}
|
|
ServerConfig::m_server_ip_ban_list = final_ip_ban_list;
|
|
// Default guided entry
|
|
ServerConfig::m_server_ip_ban_list["0.0.0.0/0"] = 0;
|
|
|
|
std::map<uint32_t, uint32_t> final_online_id_ban_list;
|
|
for (auto& ban : ServerConfig::m_server_online_id_ban_list)
|
|
{
|
|
if (ban.first == 0 ||
|
|
(uint32_t)StkTime::getTimeSinceEpoch() > ban.second)
|
|
continue;
|
|
m_online_id_ban_list[ban.first] = ban.second;
|
|
final_online_id_ban_list[ban.first] =
|
|
ServerConfig::m_server_online_id_ban_list.at(ban.first);
|
|
}
|
|
ServerConfig::m_server_online_id_ban_list = final_online_id_ban_list;
|
|
ServerConfig::m_server_online_id_ban_list[0] = 0;
|
|
} // updateBanList
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ServerLobby::waitingForPlayers() const
|
|
{
|
|
if (m_game_setup->isGrandPrix() && m_game_setup->isGrandPrixStarted())
|
|
return false;
|
|
return m_state.load() >= WAITING_FOR_START_GAME;
|
|
} // waitingForPlayers
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::handlePendingConnection()
|
|
{
|
|
std::lock_guard<std::mutex> lock(m_keys_mutex);
|
|
|
|
for (auto it = m_pending_connection.begin();
|
|
it != m_pending_connection.end();)
|
|
{
|
|
auto peer = it->first.lock();
|
|
if (!peer)
|
|
{
|
|
it = m_pending_connection.erase(it);
|
|
}
|
|
else
|
|
{
|
|
const uint32_t online_id = it->second.first;
|
|
auto key = m_keys.find(online_id);
|
|
if (key != m_keys.end() && key->second.m_tried == false)
|
|
{
|
|
try
|
|
{
|
|
if (decryptConnectionRequest(peer, it->second.second,
|
|
key->second.m_aes_key, key->second.m_aes_iv, online_id,
|
|
key->second.m_name))
|
|
{
|
|
it = m_pending_connection.erase(it);
|
|
m_keys.erase(online_id);
|
|
continue;
|
|
}
|
|
else
|
|
key->second.m_tried = true;
|
|
}
|
|
catch (std::exception& e)
|
|
{
|
|
Log::error("ServerLobby",
|
|
"handlePendingConnection error: %s", e.what());
|
|
key->second.m_tried = true;
|
|
}
|
|
}
|
|
it++;
|
|
}
|
|
}
|
|
} // handlePendingConnection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ServerLobby::decryptConnectionRequest(std::shared_ptr<STKPeer> peer,
|
|
BareNetworkString& data, const std::string& key, const std::string& iv,
|
|
uint32_t online_id, const core::stringw& online_name)
|
|
{
|
|
auto crypto = std::unique_ptr<Crypto>(new Crypto(
|
|
Crypto::decode64(key), Crypto::decode64(iv)));
|
|
if (crypto->decryptConnectionRequest(data))
|
|
{
|
|
peer->setCrypto(std::move(crypto));
|
|
Log::info("ServerLobby", "%s validated",
|
|
StringUtils::wideToUtf8(online_name).c_str());
|
|
handleUnencryptedConnection(peer, data, online_id,
|
|
online_name);
|
|
return true;
|
|
}
|
|
return false;
|
|
} // decryptConnectionRequest
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::getRankingForPlayer(std::shared_ptr<NetworkPlayerProfile> p)
|
|
{
|
|
Online::XMLRequest* request = new Online::XMLRequest();
|
|
NetworkConfig::get()->setUserDetails(request, "get-ranking");
|
|
|
|
const uint32_t id = p->getOnlineId();
|
|
request->addParameter("id", id);
|
|
request->executeNow();
|
|
|
|
const XMLNode* result = request->getXMLData();
|
|
std::string rec_success;
|
|
|
|
// Default result
|
|
double score = 2000.0;
|
|
double max_score = 2000.0;
|
|
unsigned num_races = 0;
|
|
if (result->get("success", &rec_success))
|
|
{
|
|
if (rec_success == "yes")
|
|
{
|
|
result->get("scores", &score);
|
|
result->get("max-scores", &max_score);
|
|
result->get("num-races-done", &num_races);
|
|
}
|
|
else
|
|
{
|
|
Log::error("ServerLobby", "No ranking info found.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Log::error("ServerLobby", "No ranking info found.");
|
|
}
|
|
m_ranked_players[id] = p;
|
|
m_scores[id] = score;
|
|
m_max_scores[id] = max_score;
|
|
m_num_ranked_races[id] = num_races;
|
|
delete request;
|
|
} // getRankingForPlayer
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::submitRankingsToAddons()
|
|
{
|
|
// No ranking for battle mode
|
|
if (!race_manager->modeHasLaps())
|
|
return;
|
|
|
|
// ========================================================================
|
|
class SumbitRankingRequest : public Online::XMLRequest
|
|
{
|
|
public:
|
|
SumbitRankingRequest(uint32_t online_id, double scores,
|
|
double max_scores, unsigned num_races)
|
|
: XMLRequest(true)
|
|
{
|
|
addParameter("id", online_id);
|
|
addParameter("scores", scores);
|
|
addParameter("max-scores", max_scores);
|
|
addParameter("num-races-done", num_races);
|
|
}
|
|
virtual void afterOperation()
|
|
{
|
|
Online::XMLRequest::afterOperation();
|
|
const XMLNode* result = getXMLData();
|
|
std::string rec_success;
|
|
if (!(result->get("success", &rec_success) &&
|
|
rec_success == "yes"))
|
|
{
|
|
Log::error("ServerLobby", "Failed to submit scores.");
|
|
}
|
|
}
|
|
}; // UpdatePlayerRankingRequest
|
|
// ========================================================================
|
|
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
const uint32_t id = race_manager->getKartInfo(i).getOnlineId();
|
|
SumbitRankingRequest* request = new SumbitRankingRequest
|
|
(id, m_scores.at(id), m_max_scores.at(id),
|
|
m_num_ranked_races.at(id));
|
|
NetworkConfig::get()->setUserDetails(request, "submit-ranking");
|
|
Log::info("ServerLobby", "Submiting ranking for %s (%d) : %lf, %lf %d",
|
|
StringUtils::wideToUtf8(
|
|
race_manager->getKartInfo(i).getPlayerName()).c_str(), id,
|
|
m_scores.at(id), m_max_scores.at(id), m_num_ranked_races.at(id));
|
|
request->queue();
|
|
}
|
|
} // submitRankingsToAddons
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** This function is called when all clients have loaded the world and
|
|
* are therefore ready to start the race. It determine the start time in
|
|
* network timer for client and server based on pings and then switches state
|
|
* to WAIT_FOR_RACE_STARTED.
|
|
*/
|
|
void ServerLobby::configPeersStartTime()
|
|
{
|
|
uint32_t max_ping = 0;
|
|
const unsigned max_ping_from_peers = ServerConfig::m_max_ping;
|
|
for (auto p : m_peers_ready)
|
|
{
|
|
auto peer = p.first.lock();
|
|
if (!peer)
|
|
continue;
|
|
if (peer->getAveragePing() > max_ping_from_peers)
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Peer %s cannot catch up with max ping %d.",
|
|
peer->getAddress().toString().c_str(), max_ping);
|
|
continue;
|
|
}
|
|
max_ping = std::max(peer->getAveragePing(), max_ping);
|
|
}
|
|
if (ServerConfig::m_live_players && race_manager->supportsLiveJoining())
|
|
{
|
|
Log::info("ServerLobby", "Max ping to ServerConfig::m_max_ping for "
|
|
"live joining.");
|
|
max_ping = ServerConfig::m_max_ping;
|
|
}
|
|
// Start up time will be after 2500ms, so even if this packet is sent late
|
|
// (due to packet loss), the start time will still ahead of current time
|
|
uint64_t start_time = STKHost::get()->getNetworkTimer() + (uint64_t)2500;
|
|
powerup_manager->setRandomSeed(start_time);
|
|
NetworkString* ns = getNetworkString(10);
|
|
ns->setSynchronous(true);
|
|
ns->addUInt8(LE_START_RACE).addUInt64(start_time);
|
|
const uint8_t cc = (uint8_t)CheckManager::get()->getCheckStructureCount();
|
|
ns->addUInt8(cc);
|
|
*ns += *m_items_complete_state;
|
|
m_client_starting_time = start_time;
|
|
sendMessageToPeers(ns, /*reliable*/true);
|
|
|
|
const unsigned jitter_tolerance = ServerConfig::m_jitter_tolerance;
|
|
Log::info("ServerLobby", "Max ping from peers: %d, jitter tolerance: %d",
|
|
max_ping, jitter_tolerance);
|
|
// Delay server for max ping / 2 from peers and jitter tolerance.
|
|
m_server_delay = (uint64_t)(max_ping / 2) + (uint64_t)jitter_tolerance;
|
|
start_time += m_server_delay;
|
|
m_server_started_at = start_time;
|
|
delete ns;
|
|
m_state = WAIT_FOR_RACE_STARTED;
|
|
|
|
World::getWorld()->setPhase(WorldStatus::SERVER_READY_PHASE);
|
|
joinStartGameThread();
|
|
m_start_game_thread = std::thread([start_time, this]()
|
|
{
|
|
const uint64_t cur_time = STKHost::get()->getNetworkTimer();
|
|
assert(start_time > cur_time);
|
|
int sleep_time = (int)(start_time - cur_time);
|
|
Log::info("ServerLobby", "Start game after %dms", sleep_time);
|
|
std::this_thread::sleep_for(std::chrono::milliseconds(sleep_time));
|
|
Log::info("ServerLobby", "Started at %lf", StkTime::getRealTime());
|
|
m_state.store(RACING);
|
|
});
|
|
} // configPeersStartTime
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ServerLobby::allowJoinedPlayersWaiting() const
|
|
{
|
|
return !m_game_setup->isGrandPrix();
|
|
} // allowJoinedPlayersWaiting
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::addWaitingPlayersToGame()
|
|
{
|
|
auto all_profiles = STKHost::get()->getAllPlayerProfiles();
|
|
for (auto& profile : all_profiles)
|
|
{
|
|
auto peer = profile->getPeer();
|
|
if (!peer || !peer->isValidated())
|
|
continue;
|
|
|
|
peer->setWaitingForGame(false);
|
|
peer->setSpectator(false);
|
|
if (m_peers_ready.find(peer) == m_peers_ready.end())
|
|
{
|
|
m_peers_ready[peer] = false;
|
|
Log::info("ServerLobby",
|
|
"New player %s with online id %u from %s with %s.",
|
|
StringUtils::wideToUtf8(profile->getName()).c_str(),
|
|
profile->getOnlineId(), peer->getAddress().toString().c_str(),
|
|
peer->getUserVersion().c_str());
|
|
}
|
|
uint32_t online_id = profile->getOnlineId();
|
|
if (ServerConfig::m_ranked &&
|
|
(m_ranked_players.find(online_id) == m_ranked_players.end() ||
|
|
(m_ranked_players.find(online_id) != m_ranked_players.end() &&
|
|
m_ranked_players.at(online_id).expired())))
|
|
{
|
|
getRankingForPlayer(peer->getPlayerProfiles()[0]);
|
|
}
|
|
}
|
|
} // addWaitingPlayersToGame
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::resetServer()
|
|
{
|
|
addWaitingPlayersToGame();
|
|
resetPeersReady();
|
|
updatePlayerList(true/*update_when_reset_server*/);
|
|
NetworkString* server_info = getNetworkString();
|
|
server_info->setSynchronous(true);
|
|
server_info->addUInt8(LE_SERVER_INFO);
|
|
m_game_setup->addServerInfo(server_info);
|
|
sendMessageToPeersInServer(server_info);
|
|
delete server_info;
|
|
setup();
|
|
m_state = NetworkConfig::get()->isLAN() ?
|
|
WAITING_FOR_START_GAME : REGISTER_SELF_ADDRESS;
|
|
} // resetServer
|
|
|
|
//-----------------------------------------------------------------------------
|
|
bool ServerLobby::isBannedForIP(const TransportAddress& addr) const
|
|
{
|
|
uint32_t ip_decimal = addr.getIP();
|
|
auto lb = m_ip_ban_list.lower_bound(addr.getIP());
|
|
bool is_banned = false;
|
|
if (lb != m_ip_ban_list.end() && ip_decimal >= lb->first/*ip_start*/)
|
|
{
|
|
if (ip_decimal <= std::get<0>(lb->second)/*ip_end*/ &&
|
|
(uint32_t)StkTime::getTimeSinceEpoch() < std::get<2>(lb->second))
|
|
is_banned = true;
|
|
}
|
|
else if (lb != m_ip_ban_list.begin())
|
|
{
|
|
lb--;
|
|
if (ip_decimal>= lb->first/*ip_start*/ &&
|
|
ip_decimal <= std::get<0>(lb->second)/*ip_end*/ &&
|
|
(uint32_t)StkTime::getTimeSinceEpoch() < std::get<2>(lb->second))
|
|
is_banned = true;
|
|
}
|
|
if (is_banned)
|
|
{
|
|
Log::info("ServerLobby", "%s is banned by CIDR %s",
|
|
addr.toString(false/*show_port*/).c_str(),
|
|
std::get<1>(lb->second).c_str());
|
|
}
|
|
return is_banned;
|
|
} // isBannedForIP
|
|
|
|
//-----------------------------------------------------------------------------
|
|
float ServerLobby::getStartupBoostOrPenaltyForKart(uint32_t ping,
|
|
unsigned kart_id)
|
|
{
|
|
AbstractKart* k = World::getWorld()->getKart(kart_id);
|
|
if (k->getStartupBoost() != 0.0f)
|
|
return k->getStartupBoost();
|
|
uint64_t now = STKHost::get()->getNetworkTimer();
|
|
uint64_t client_time = now - ping / 2;
|
|
uint64_t server_time = client_time + m_server_delay;
|
|
int ticks = stk_config->time2Ticks(
|
|
(float)(server_time - m_server_started_at) / 1000.0f);
|
|
if (ticks < stk_config->time2Ticks(1.0f))
|
|
{
|
|
PlayerController* pc =
|
|
dynamic_cast<PlayerController*>(k->getController());
|
|
pc->displayPenaltyWarning();
|
|
return -1.0f;
|
|
}
|
|
float f = k->getStartupBoostFromStartTicks(ticks);
|
|
k->setStartupBoost(f);
|
|
return f;
|
|
} // getStartupBoostOrPenaltyForKart
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! \brief Called when the server owner request to change game mode or
|
|
* difficulty.
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1 2
|
|
* -----------------------------------------------
|
|
* Size | 1 | 1 | 1 |
|
|
* Data | difficulty | game mode | soccer goal target |
|
|
* -----------------------------------------------
|
|
*/
|
|
void ServerLobby::handleServerConfiguration(Event* event)
|
|
{
|
|
if (m_state != WAITING_FOR_START_GAME)
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Received handleServerConfiguration while being in state %d.",
|
|
m_state.load());
|
|
return;
|
|
}
|
|
if (!ServerConfig::m_server_configurable)
|
|
{
|
|
Log::warn("ServerLobby", "server-configurable is not enabled.");
|
|
return;
|
|
}
|
|
if (event->getPeerSP() != m_server_owner.lock())
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"Client %d is not authorised to config server.",
|
|
event->getPeer()->getHostId());
|
|
return;
|
|
}
|
|
NetworkString& data = event->data();
|
|
int new_difficulty = data.getUInt8();
|
|
int new_game_mode = data.getUInt8();
|
|
bool new_soccer_goal_target = data.getUInt8() == 1;
|
|
auto modes = ServerConfig::getLocalGameMode(new_game_mode);
|
|
if (modes.second == RaceManager::MAJOR_MODE_GRAND_PRIX)
|
|
{
|
|
Log::warn("ServerLobby", "Grand prix is used for new mode.");
|
|
return;
|
|
}
|
|
|
|
race_manager->setMinorMode(modes.first);
|
|
race_manager->setMajorMode(modes.second);
|
|
race_manager->setDifficulty(RaceManager::Difficulty(new_difficulty));
|
|
m_game_setup->resetExtraServerInfo();
|
|
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_SOCCER)
|
|
m_game_setup->setSoccerGoalTarget(new_soccer_goal_target);
|
|
|
|
if (NetworkConfig::get()->isWAN() &&
|
|
(m_difficulty.load() != new_difficulty ||
|
|
m_game_mode.load() != new_game_mode))
|
|
{
|
|
Log::info("ServerLobby", "Updating server info with new "
|
|
"difficulty: %d, game mode: %d to stk-addons.", new_difficulty,
|
|
new_game_mode);
|
|
Online::XMLRequest* request =
|
|
new Online::XMLRequest(true/*manage_memory*/);
|
|
NetworkConfig::get()->setServerDetails(request, "update-config");
|
|
request->addParameter("address", m_server_address.getIP());
|
|
request->addParameter("port", m_server_address.getPort());
|
|
request->addParameter("new-difficulty", new_difficulty);
|
|
request->addParameter("new-game-mode", new_game_mode);
|
|
request->queue();
|
|
}
|
|
m_difficulty.store(new_difficulty);
|
|
m_game_mode.store(new_game_mode);
|
|
updateTracksForMode();
|
|
|
|
auto peers = STKHost::get()->getPeers();
|
|
for (auto& peer : peers)
|
|
{
|
|
auto assets = peer->getClientAssets();
|
|
if (!peer->isValidated() || assets.second.empty())
|
|
continue;
|
|
std::set<std::string> tracks_erase;
|
|
for (const std::string& server_track : m_available_kts.second)
|
|
{
|
|
if (assets.second.find(server_track) == assets.second.end())
|
|
{
|
|
tracks_erase.insert(server_track);
|
|
}
|
|
}
|
|
if (tracks_erase.size() == m_available_kts.second.size())
|
|
{
|
|
NetworkString *message = getNetworkString(2);
|
|
message->setSynchronous(true);
|
|
message->addUInt8(LE_CONNECTION_REFUSED)
|
|
.addUInt8(RR_INCOMPATIBLE_DATA);
|
|
peer->cleanPlayerProfiles();
|
|
peer->sendPacket(message, true/*reliable*/);
|
|
peer->reset();
|
|
delete message;
|
|
Log::verbose("ServerLobby",
|
|
"Player has incompatible tracks for new game mode.");
|
|
}
|
|
}
|
|
NetworkString* server_info = getNetworkString();
|
|
server_info->setSynchronous(true);
|
|
server_info->addUInt8(LE_SERVER_INFO);
|
|
m_game_setup->addServerInfo(server_info);
|
|
sendMessageToPeers(server_info);
|
|
delete server_info;
|
|
updatePlayerList();
|
|
} // handleServerConfiguration
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/*! \brief Called when a player want to change his handicap
|
|
* \param event : Event providing the information.
|
|
*
|
|
* Format of the data :
|
|
* Byte 0 1
|
|
* ----------------------------------
|
|
* Size | 1 | 1 |
|
|
* Data | local player id | new handicap |
|
|
* ----------------------------------
|
|
*/
|
|
void ServerLobby::changeHandicap(Event* event)
|
|
{
|
|
NetworkString& data = event->data();
|
|
if (m_state.load() != WAITING_FOR_START_GAME &&
|
|
!event->getPeer()->isWaitingForGame())
|
|
{
|
|
Log::warn("ServerLobby", "Set handicap at wrong time.");
|
|
return;
|
|
}
|
|
uint8_t local_id = data.getUInt8();
|
|
auto& player = event->getPeer()->getPlayerProfiles().at(local_id);
|
|
uint8_t difficulty_id = data.getUInt8();
|
|
if (difficulty_id >= PLAYER_DIFFICULTY_COUNT)
|
|
{
|
|
Log::warn("ServerLobby", "Wrong handicap %d.", difficulty_id);
|
|
return;
|
|
}
|
|
PerPlayerDifficulty d = (PerPlayerDifficulty)difficulty_id;
|
|
player->setPerPlayerDifficulty(d);
|
|
updatePlayerList();
|
|
} // changeHandicap
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Update and see if any player disconnects, if so eliminate the kart in
|
|
* world, so this function must be called in main thread.
|
|
*/
|
|
void ServerLobby::handlePlayerDisconnection() const
|
|
{
|
|
if (!World::getWorld() ||
|
|
World::getWorld()->getPhase() < WorldStatus::MUSIC_PHASE)
|
|
{
|
|
return;
|
|
}
|
|
|
|
int red_count = 0;
|
|
int blue_count = 0;
|
|
unsigned total = 0;
|
|
for (unsigned i = 0; i < race_manager->getNumPlayers(); i++)
|
|
{
|
|
RemoteKartInfo& rki = race_manager->getKartInfo(i);
|
|
if (rki.isReserved())
|
|
continue;
|
|
bool disconnected = rki.disconnected();
|
|
if (race_manager->getKartInfo(i).getKartTeam() == KART_TEAM_RED &&
|
|
!disconnected)
|
|
red_count++;
|
|
else if (race_manager->getKartInfo(i).getKartTeam() ==
|
|
KART_TEAM_BLUE && !disconnected)
|
|
blue_count++;
|
|
|
|
if (!disconnected)
|
|
{
|
|
total++;
|
|
continue;
|
|
}
|
|
else
|
|
rki.makeReserved();
|
|
|
|
AbstractKart* k = World::getWorld()->getKart(i);
|
|
if (!k->isEliminated() && !k->hasFinishedRace())
|
|
{
|
|
CaptureTheFlag* ctf = dynamic_cast<CaptureTheFlag*>
|
|
(World::getWorld());
|
|
if (ctf)
|
|
ctf->loseFlagForKart(k->getWorldKartId());
|
|
|
|
World::getWorld()->eliminateKart(i,
|
|
false/*notify_of_elimination*/);
|
|
k->setPosition(
|
|
World::getWorld()->getCurrentNumKarts() + 1);
|
|
k->finishedRace(World::getWorld()->getTime(), true/*from_server*/);
|
|
}
|
|
}
|
|
|
|
// If live players is enabled, don't end the game if unfair team
|
|
if (!ServerConfig::m_live_players &&
|
|
total != 1 && World::getWorld()->hasTeam() &&
|
|
(red_count == 0 || blue_count == 0))
|
|
World::getWorld()->setUnfairTeam(true);
|
|
|
|
} // handlePlayerDisconnection
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Add reserved players for live join later if required.
|
|
*/
|
|
void ServerLobby::addLiveJoinPlaceholder(
|
|
std::vector<std::shared_ptr<NetworkPlayerProfile> >& players) const
|
|
{
|
|
if (!ServerConfig::m_live_players || !race_manager->supportsLiveJoining())
|
|
return;
|
|
if (race_manager->getMinorMode() == RaceManager::MINOR_MODE_FREE_FOR_ALL)
|
|
{
|
|
Track* t = track_manager->getTrack(m_game_setup->getCurrentTrack());
|
|
assert(t);
|
|
int max_players = std::min((int)ServerConfig::m_server_max_players,
|
|
(int)t->getMaxArenaPlayers());
|
|
int add_size = max_players - (int)players.size();
|
|
assert(add_size >= 0);
|
|
for (int i = 0; i < add_size; i++)
|
|
{
|
|
players.push_back(
|
|
NetworkPlayerProfile::getReservedProfile(KART_TEAM_NONE));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// CTF or soccer, reserve at most 7 players on each team
|
|
int red_count = 0;
|
|
int blue_count = 0;
|
|
for (unsigned i = 0; i < players.size(); i++)
|
|
{
|
|
if (players[i]->getTeam() == KART_TEAM_RED)
|
|
red_count++;
|
|
else
|
|
blue_count++;
|
|
}
|
|
red_count = red_count >= 7 ? 0 : 7 - red_count;
|
|
blue_count = blue_count >= 7 ? 0 : 7 - blue_count;
|
|
for (int i = 0; i < red_count; i++)
|
|
{
|
|
players.push_back(
|
|
NetworkPlayerProfile::getReservedProfile(KART_TEAM_RED));
|
|
}
|
|
for (int i = 0; i < blue_count; i++)
|
|
{
|
|
players.push_back(
|
|
NetworkPlayerProfile::getReservedProfile(KART_TEAM_BLUE));
|
|
}
|
|
}
|
|
} // addLiveJoinPlaceholder
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::setPlayerKarts(const NetworkString& ns, STKPeer* peer) const
|
|
{
|
|
unsigned player_count = ns.getUInt8();
|
|
for (unsigned i = 0; i < player_count; i++)
|
|
{
|
|
std::string kart;
|
|
ns.decodeString(&kart);
|
|
if (m_available_kts.first.find(kart) == m_available_kts.first.end())
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
peer->getPlayerProfiles()[i]->setKartName(kart);
|
|
}
|
|
}
|
|
} // setPlayerKarts
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Tell the client \ref RemoteKartInfo of a player when some player joining
|
|
* live.
|
|
*/
|
|
void ServerLobby::handleKartInfo(Event* event)
|
|
{
|
|
World* w = World::getWorld();
|
|
if (!w)
|
|
return;
|
|
|
|
STKPeer* peer = event->getPeer();
|
|
const NetworkString& data = event->data();
|
|
uint8_t kart_id = data.getUInt8();
|
|
if (kart_id > race_manager->getNumPlayers())
|
|
return;
|
|
|
|
AbstractKart* k = w->getKart(kart_id);
|
|
int live_join_util_ticks = k->getLiveJoinUntilTicks();
|
|
|
|
const RemoteKartInfo& rki = race_manager->getKartInfo(kart_id);
|
|
|
|
NetworkString* ns = getNetworkString(1);
|
|
ns->setSynchronous(true);
|
|
ns->addUInt8(LE_KART_INFO).addUInt32(live_join_util_ticks)
|
|
.addUInt8(kart_id) .encodeString(rki.getPlayerName())
|
|
.addUInt32(rki.getHostId()).addFloat(rki.getDefaultKartColor())
|
|
.addUInt32(rki.getOnlineId()).addUInt8(rki.getDifficulty())
|
|
.addUInt8((uint8_t)rki.getLocalPlayerId())
|
|
.encodeString(rki.getKartName()).encodeString(rki.getCountryId());
|
|
peer->sendPacket(ns, true/*reliable*/);
|
|
delete ns;
|
|
} // handleKartInfo
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Client if currently in-game (including spectator) wants to go back to
|
|
* lobby.
|
|
*/
|
|
void ServerLobby::clientInGameWantsToBackLobby(Event* event)
|
|
{
|
|
World* w = World::getWorld();
|
|
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
|
|
|
if (!w || !worldIsActive() || peer->isWaitingForGame())
|
|
{
|
|
Log::warn("ServerLobby", "%s try to leave the game at wrong time.",
|
|
peer->getAddress().toString().c_str());
|
|
return;
|
|
}
|
|
|
|
for (const int id : peer->getAvailableKartIDs())
|
|
{
|
|
RemoteKartInfo& rki = race_manager->getKartInfo(id);
|
|
if (rki.getHostId() == peer->getHostId())
|
|
{
|
|
Log::info("ServerLobby", "%s left the game with kart id %d.",
|
|
peer->getAddress().toString().c_str(), id);
|
|
rki.setNetworkPlayerProfile(
|
|
std::shared_ptr<NetworkPlayerProfile>());
|
|
}
|
|
else
|
|
{
|
|
Log::error("ServerLobby", "%s doesn't exist anymore in server.",
|
|
peer->getAddress().toString().c_str());
|
|
}
|
|
}
|
|
NetworkItemManager* nim =
|
|
dynamic_cast<NetworkItemManager*>(ItemManager::get());
|
|
assert(nim);
|
|
nim->erasePeerInGame(peer);
|
|
m_peers_ready.erase(peer);
|
|
peer->setWaitingForGame(true);
|
|
peer->setSpectator(false);
|
|
|
|
NetworkString* reset = getNetworkString(2);
|
|
reset->setSynchronous(true);
|
|
reset->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE);
|
|
peer->sendPacket(reset, /*reliable*/true);
|
|
delete reset;
|
|
updatePlayerList();
|
|
NetworkString* server_info = getNetworkString();
|
|
server_info->setSynchronous(true);
|
|
server_info->addUInt8(LE_SERVER_INFO);
|
|
m_game_setup->addServerInfo(server_info);
|
|
peer->sendPacket(server_info, /*reliable*/true);
|
|
delete server_info;
|
|
} // clientInGameWantsToBackLobby
|
|
|
|
//-----------------------------------------------------------------------------
|
|
/** Client if currently select assets wants to go back to lobby.
|
|
*/
|
|
void ServerLobby::clientSelectingAssetsWantsToBackLobby(Event* event)
|
|
{
|
|
std::shared_ptr<STKPeer> peer = event->getPeerSP();
|
|
|
|
if (m_state.load() != SELECTING || peer->isWaitingForGame())
|
|
{
|
|
Log::warn("ServerLobby",
|
|
"%s try to leave selecting assets at wrong time.",
|
|
peer->getAddress().toString().c_str());
|
|
return;
|
|
}
|
|
|
|
m_peers_ready.erase(peer);
|
|
peer->setWaitingForGame(true);
|
|
peer->setSpectator(false);
|
|
|
|
NetworkString* reset = getNetworkString(2);
|
|
reset->setSynchronous(true);
|
|
reset->addUInt8(LE_BACK_LOBBY).addUInt8(BLR_NONE);
|
|
peer->sendPacket(reset, /*reliable*/true);
|
|
delete reset;
|
|
updatePlayerList();
|
|
NetworkString* server_info = getNetworkString();
|
|
server_info->setSynchronous(true);
|
|
server_info->addUInt8(LE_SERVER_INFO);
|
|
m_game_setup->addServerInfo(server_info);
|
|
peer->sendPacket(server_info, /*reliable*/true);
|
|
delete server_info;
|
|
} // clientSelectingAssetsWantsToBackLobby
|
|
|
|
//-----------------------------------------------------------------------------
|
|
void ServerLobby::saveInitialItems()
|
|
{
|
|
m_items_complete_state->getBuffer().clear();
|
|
m_items_complete_state->reset();
|
|
NetworkItemManager* nim =
|
|
dynamic_cast<NetworkItemManager*>(ItemManager::get());
|
|
assert(nim);
|
|
nim->saveCompleteState(m_items_complete_state);
|
|
} // saveInitialItems
|