diff --git a/src/karts/controller/local_player_controller.cpp b/src/karts/controller/local_player_controller.cpp index 0a5e7db24..05b69dd93 100644 --- a/src/karts/controller/local_player_controller.cpp +++ b/src/karts/controller/local_player_controller.cpp @@ -193,7 +193,8 @@ bool LocalPlayerController::action(PlayerAction action, int value, // If this is a client, send the action to networking layer if (NetworkConfig::get()->isNetworking() && NetworkConfig::get()->isClient() && - !RewindManager::get()->isRewinding()) + !RewindManager::get()->isRewinding() && + World::getWorld() && !World::getWorld()->isLiveJoinWorld()) { if (auto gp = GameProtocol::lock()) { diff --git a/src/karts/kart.cpp b/src/karts/kart.cpp index c4b5e0738..9b42f022e 100644 --- a/src/karts/kart.cpp +++ b/src/karts/kart.cpp @@ -1412,7 +1412,7 @@ void Kart::update(int ticks) // Hover the kart above reset position before entering the game if (m_live_join_util != 0 && - (m_live_join_util < World::getWorld()->getTicksSinceStart() || + (m_live_join_util > World::getWorld()->getTicksSinceStart() || World::getWorld()->isLiveJoinWorld())) { btRigidBody *body = getBody(); diff --git a/src/modes/world.hpp b/src/modes/world.hpp index 67d5b0a61..7a4274bf8 100644 --- a/src/modes/world.hpp +++ b/src/modes/world.hpp @@ -326,6 +326,8 @@ public: unsigned int getCurrentNumPlayers() const { return m_num_players - m_eliminated_players;} // ------------------------------------------------------------------------ + virtual void addReservedKart(int kart_id) { m_eliminated_karts--; } + // ------------------------------------------------------------------------ /** The code that draws the timer should call this first to know * whether the game mode wants a timer drawn. */ virtual bool shouldDrawTimer() const diff --git a/src/modes/world_status.cpp b/src/modes/world_status.cpp index 7e84adf79..1c6d9284f 100644 --- a/src/modes/world_status.cpp +++ b/src/modes/world_status.cpp @@ -275,7 +275,7 @@ void WorldStatus::updateTime(int ticks) // Add 3 seconds delay before telling server finish loading // world, so previous (if any) disconnected player has left // fully - if (m_auxiliary_ticks == 360) + if (m_auxiliary_ticks == stk_config->time2Ticks(3.0f)) { auto lobby = LobbyProtocol::get(); assert(lobby); @@ -545,4 +545,8 @@ void WorldStatus::endLiveJoinWorld(int ticks_now) { m_live_join_world = false; m_auxiliary_ticks = 0; + m_phase = RACE_PHASE; + startEngines(); + music_manager->startMusic(); + setTicksForRewind(ticks_now); } // endLiveJoinWorld diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index ee6931f4c..b53fc2daf 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -27,6 +27,8 @@ #include "input/device_manager.hpp" #include "items/item_manager.hpp" #include "items/powerup_manager.hpp" +#include "karts/abstract_kart.hpp" +#include "karts/controller/controller.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/linear_world.hpp" #include "network/crypto.hpp" @@ -109,6 +111,8 @@ ClientLobby::~ClientLobby() void ClientLobby::setup() { m_auto_back_to_lobby_time = std::numeric_limits::max(); + m_start_live_game_time = std::numeric_limits::max(); + m_live_join_ticks = -1; m_received_server_result = false; TracksScreen::getInstance()->resetVote(); LobbyProtocol::setup(); @@ -155,6 +159,7 @@ bool ClientLobby::notifyEvent(Event* event) case LE_SERVER_OWNERSHIP: becomingServerOwner(); break; case LE_BAD_TEAM: handleBadTeam(); break; case LE_BAD_CONNECTION: handleBadConnection(); break; + case LE_LIVE_JOIN_ACK: liveJoinAcknowledged(event); break; default: return false; break; @@ -272,6 +277,22 @@ void ClientLobby::addAllPlayers(Event* event) // Disable until render gui during loading is bug free //StateManager::get()->enterGameState(); + // Live join if state is CONNECTED + if (m_state.load() == CONNECTED) + { + World* w = World::getWorld(); + w->setLiveJoinWorld(true); + for (unsigned i = 0; i < w->getNumKarts(); i++) + { + AbstractKart* k = w->getKart(i); + // The final joining ticks will be set by server later + if (k->getController()->isLocalPlayerController()) + k->setLiveJoinKart(-1); + else + k->getNode()->setVisible(false); + } + } + // Switch to assign mode in case a player hasn't chosen any karts input_manager->getDeviceManager()->setAssignMode(ASSIGN); } // addAllPlayers @@ -374,6 +395,11 @@ void ClientLobby::update(int ticks) break; case REQUESTING_CONNECTION: case CONNECTED: + if (m_start_live_game_time != std::numeric_limits::max() && + STKHost::get()->getNetworkTimer() >= m_start_live_game_time) + { + finishLiveJoin(); + } if (NetworkConfig::get()->isAutoConnect() && !m_auto_started) { // Send a message to the server to start @@ -931,7 +957,51 @@ void ClientLobby::exitResultScreen(Event *event) void ClientLobby::finishedLoadingWorld() { NetworkString* ns = getNetworkString(1); + // Live join if state is CONNECTED + ns->setSynchronous(m_state.load() == CONNECTED); ns->addUInt8(LE_CLIENT_LOADED_WORLD); sendToServer(ns, true); delete ns; } // finishedLoadingWorld + +//----------------------------------------------------------------------------- +void ClientLobby::liveJoinAcknowledged(Event* event) +{ + World* w = World::getWorld(); + if (!w) + return; + + const NetworkString& data = event->data(); + m_start_live_game_time = data.getUInt64(); + powerup_manager->setRandomSeed(m_start_live_game_time); + m_start_live_game_time = data.getUInt64(); + m_live_join_ticks = data.getUInt32(); + for (unsigned i = 0; i < w->getNumKarts(); i++) + { + AbstractKart* k = w->getKart(i); + if (k->getController()->isLocalPlayerController()) + k->setLiveJoinKart(m_live_join_ticks); + } +} // liveJoinAcknowledged + +//----------------------------------------------------------------------------- +void ClientLobby::finishLiveJoin() +{ + m_start_live_game_time = std::numeric_limits::max(); + World* w = World::getWorld(); + if (!w) + return; + Log::info("ClientLobby", "Live join started at %lf", + StkTime::getRealTime()); + + w->setLiveJoinWorld(false); + w->endLiveJoinWorld(m_live_join_ticks); + for (unsigned i = 0; i < w->getNumKarts(); i++) + { + AbstractKart* k = w->getKart(i); + if (!k->getController()->isLocalPlayerController() && + !k->isEliminated()) + k->getNode()->setVisible(true); + } + m_state.store(RACING); +} // finishLiveJoin diff --git a/src/network/protocols/client_lobby.hpp b/src/network/protocols/client_lobby.hpp index 5b6d6da72..95511d28f 100644 --- a/src/network/protocols/client_lobby.hpp +++ b/src/network/protocols/client_lobby.hpp @@ -81,6 +81,10 @@ private: uint64_t m_auto_back_to_lobby_time; + uint64_t m_start_live_game_time; + + int m_live_join_ticks; + /** The state of the finite state machine. */ std::atomic m_state; @@ -95,6 +99,8 @@ private: irr::core::stringw m_total_players; + void liveJoinAcknowledged(Event* event); + void finishLiveJoin(); public: ClientLobby(const TransportAddress& a, std::shared_ptr s); virtual ~ClientLobby(); diff --git a/src/network/protocols/game_protocol.cpp b/src/network/protocols/game_protocol.cpp index 10ad731ad..e50cc4f2f 100644 --- a/src/network/protocols/game_protocol.cpp +++ b/src/network/protocols/game_protocol.cpp @@ -173,6 +173,9 @@ void GameProtocol::controllerAction(int kart_id, PlayerAction action, */ void GameProtocol::handleControllerAction(Event *event) { + STKPeer* peer = event->getPeer(); + if (NetworkConfig::get()->isServer() && peer->isWaitingForGame()) + return; NetworkString &data = event->data(); uint8_t count = data.getUInt8(); bool will_trigger_rewind = false; @@ -193,10 +196,10 @@ void GameProtocol::handleControllerAction(Event *event) } uint8_t kart_id = data.getUInt8(); if (NetworkConfig::get()->isServer() && - !event->getPeer()->availableKartID(kart_id)) + !peer->availableKartID(kart_id)) { Log::warn("GameProtocol", "Wrong kart id %d from %s.", - kart_id, event->getPeer()->getAddress().toString().c_str()); + kart_id, peer->getAddress().toString().c_str()); return; } @@ -227,9 +230,9 @@ void GameProtocol::handleControllerAction(Event *event) { // Send update to all clients except the original sender if the event // is after the server time - event->getPeer()->updateLastActivity(); + peer->updateLastActivity(); if (!will_trigger_rewind) - STKHost::get()->sendPacketExcept(event->getPeer(), &data, false); + STKHost::get()->sendPacketExcept(peer, &data, false); // FIXME unless there is a network jitter more than 100ms (more than // server delay), time adjust is not necessary diff --git a/src/network/protocols/lobby_protocol.hpp b/src/network/protocols/lobby_protocol.hpp index 3c84f0c57..e774188d9 100644 --- a/src/network/protocols/lobby_protocol.hpp +++ b/src/network/protocols/lobby_protocol.hpp @@ -68,7 +68,8 @@ public: LE_BAD_CONNECTION, LE_CONFIG_SERVER, LE_CHANGE_HANDICAP, - LE_LIVE_JOIN + LE_LIVE_JOIN, + LE_LIVE_JOIN_ACK }; enum RejectReason : uint8_t diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index cef3b41f9..7bbabb035 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -21,6 +21,7 @@ #include "config/user_config.hpp" #include "items/item_manager.hpp" #include "items/powerup_manager.hpp" +#include "graphics/render_info.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/player_controller.hpp" #include "karts/kart_properties.hpp" @@ -298,6 +299,7 @@ bool ServerLobby::notifyEvent(Event* event) { case LE_RACE_FINISHED_ACK: playerFinishedResult(event); break; case LE_LIVE_JOIN: liveJoinRequest(event); break; + case LE_CLIENT_LOADED_WORLD: finishedLoadingLiveJoinClient(event); break; default: Log::error("ServerLobby", "Unknown message type %d - ignored.", message_type); break; @@ -647,10 +649,230 @@ NetworkString* ServerLobby::getLoadWorldMessage( } // getLoadWorldMessage //----------------------------------------------------------------------------- +bool ServerLobby::canLiveJoinNow() const +{ + return race_manager->supportsLiveJoining() && + World::getWorld() && RaceEventManager::getInstance()->isRunning() && + !RaceEventManager::getInstance()->isRaceOver() && + (World::getWorld()->getPhase() == WorldStatus::RACE_PHASE || + World::getWorld()->getPhase() == WorldStatus::GOAL_PHASE); +} // canLiveJoinNow + +//----------------------------------------------------------------------------- +/** \ref peer will be reset back to the lobby. + */ +void ServerLobby::rejectLiveJoin(STKPeer* peer) +{ + NetworkString* reset = getNetworkString(1); + reset->setSynchronous(true); + reset->addUInt8(LE_EXIT_RESULT); + 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); + return; + } + setPlayerKarts(data, peer); + + std::vector 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); + return; + } + + peer->clearAvailableKartIDs(); + 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); + } + std::vector > players; + for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) + { + const RemoteKartInfo& rki = race_manager->getKartInfo(i); + std::shared_ptr player = + rki.getNetworkPlayerProfile().lock(); + if (!player) + { + player = NetworkPlayerProfile::getReservedProfile( + race_manager->getMinorMode() == + RaceManager::MINOR_MODE_FREE_FOR_ALL ? + KART_TEAM_NONE : rki.getKartTeam()); + } + players.push_back(player); + } + NetworkString* load_world_message = getLoadWorldMessage(players); + peer->sendPacket(load_world_message, true/*reliable*/); + delete load_world_message; + peer->updateLastActivity(); } // liveJoinRequest +//----------------------------------------------------------------------------- +/** Decide where to put the live join player depends on his team and game mode. + */ +int ServerLobby::getReservedId(std::shared_ptr& 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 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) +{ + STKPeer* peer = event->getPeer(); + if (!canLiveJoinNow()) + { + rejectLiveJoin(peer); + 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); + return; + } + World* w = World::getWorld(); + assert(w); + + // Give 3 seconds for all peers to get new kart info + const int 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; + + for (const int id : peer->getAvailableKartIDs()) + { + const RemoteKartInfo& rki = race_manager->getKartInfo(id); + AbstractKart* k = w->getKart(id); + k->changeKart(rki.getKartName(), rki.getDifficulty(), + rki.getKartTeam() == KART_TEAM_RED ? + std::make_shared(1.0f) : + rki.getKartTeam() == KART_TEAM_BLUE ? + std::make_shared(0.66f) : + std::make_shared(rki.getDefaultKartColor())); + k->setLiveJoinKart(live_join_util_ticks); + w->addReservedKart(id); + } + Log::info("ServerLobby", "%s live-joining succeeded.", + peer->getAddress().toString().c_str()); + + NetworkString* ns = getNetworkString(10); + ns->setSynchronous(true); + ns->addUInt8(LE_LIVE_JOIN_ACK).addUInt64(m_client_starting_time) + .addUInt64(live_join_start_time).addUInt32(live_join_util_ticks); + peer->setWaitingForGame(false); + 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 @@ -660,8 +882,7 @@ void ServerLobby::update(int ticks) { World* w = World::getWorld(); int sec = ServerConfig::m_kick_idle_player_seconds; - if (NetworkConfig::get()->isWAN() && - sec > 0 && m_state.load() >= WAIT_FOR_WORLD_LOADED && + if (sec > 0 && m_state.load() >= WAIT_FOR_WORLD_LOADED && m_state.load() <= RACING && m_server_has_loaded_world.load() == true) { for (unsigned i = 0; i < race_manager->getNumPlayers(); i++) @@ -672,15 +893,27 @@ void ServerLobby::update(int ticks) if (!player) continue; auto peer = player->getPeer(); - if (peer && peer->idleForSeconds() > sec && - !peer->isDisconnected()) + if (peer && peer->idleForSeconds() > sec) { - if (w && w->getKart(i)->hasFinishedRace()) + if (w && w->getKart(i)->isEliminated()) + { + // Remove loading world too long live join peer + Log::info("ServerLobby", "%s hasn't live-joined within" + " %d seconds, remove it.", + peer->getAddress().toString().c_str(), sec); + RemoteKartInfo& rki = race_manager->getKartInfo(i); + rki.makeReserved(); continue; - Log::info("ServerLobby", "%s has been idle for more than" - " %d seconds, kick.", - peer->getAddress().toString().c_str(), sec); - peer->kick(); + } + if (!peer->isDisconnected() && NetworkConfig::get()->isWAN()) + { + if (w && w->getKart(i)->hasFinishedRace()) + continue; + Log::info("ServerLobby", "%s has been idle for more than" + " %d seconds, kick.", + peer->getAddress().toString().c_str(), sec); + peer->kick(); + } } } } @@ -2963,10 +3196,12 @@ void ServerLobby::handlePlayerDisconnection() const total++; continue; } + else + rki.makeReserved(); + AbstractKart* k = World::getWorld()->getKart(i); if (!k->isEliminated() && !k->hasFinishedRace()) { - rki.makeReserved(); CaptureTheFlag* ctf = dynamic_cast (World::getWorld()); if (ctf) diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 4fec46041..fc7c2f2b6 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -183,6 +183,7 @@ private: void playerFinishedResult(Event *event); bool registerServer(bool now); void finishedLoadingWorldClient(Event *event); + void finishedLoadingLiveJoinClient(Event *event); void kickHost(Event* event); void changeTeam(Event* event); void handleChat(Event* event); @@ -276,6 +277,10 @@ private: std::vector >& players) const; void setPlayerKarts(const NetworkString& ns, STKPeer* peer) const; void liveJoinRequest(Event* event); + void rejectLiveJoin(STKPeer* peer); + bool canLiveJoinNow() const; + int getReservedId(std::shared_ptr& p, + unsigned local_id) const; public: ServerLobby(); virtual ~ServerLobby(); diff --git a/src/network/stk_peer.hpp b/src/network/stk_peer.hpp index bc2f35951..7a2f6176b 100644 --- a/src/network/stk_peer.hpp +++ b/src/network/stk_peer.hpp @@ -207,6 +207,9 @@ public: bool availableKartID(unsigned id) { return m_available_kart_ids.find(id) != m_available_kart_ids.end(); } // ------------------------------------------------------------------------ + const std::set& getAvailableKartIDs() const + { return m_available_kart_ids; } + // ------------------------------------------------------------------------ void setUserVersion(const std::string& uv) { m_user_version = uv; } // ------------------------------------------------------------------------ const std::string& getUserVersion() const { return m_user_version; }