diff --git a/src/modes/world.cpp b/src/modes/world.cpp index aad338574..b8b0f70d3 100644 --- a/src/modes/world.cpp +++ b/src/modes/world.cpp @@ -253,12 +253,15 @@ void World::init() if (Camera::getNumCameras() == 0) { + auto cl = LobbyProtocol::get(); if ( (NetworkConfig::get()->isServer() && !ProfileWorld::isNoGraphics() ) || - race_manager->isWatchingReplay() ) + race_manager->isWatchingReplay() || + (cl && cl->isSpectator())) { - // In case that the server is running with gui or watching replay, - // create a camera and attach it to the first kart. + // In case that the server is running with gui, watching replay or + // spectating the game, create a camera and attach it to the first + // kart. Camera::createCamera(World::getWorld()->getKart(0), 0); } // if server with graphics of is watching replay diff --git a/src/network/game_setup.cpp b/src/network/game_setup.cpp index 74baa35b5..7d1117ecb 100644 --- a/src/network/game_setup.cpp +++ b/src/network/game_setup.cpp @@ -155,6 +155,7 @@ void GameSetup::addServerInfo(NetworkString* ns) ns->encodeString16(m_message_of_today); ns->addUInt8((uint8_t)ServerConfig::m_server_configurable); + ns->addUInt8(race_manager->supportsLiveJoining() ? 1 : 0); } // addServerInfo //----------------------------------------------------------------------------- diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 5778e0334..9e2121990 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -29,6 +29,7 @@ #include "items/powerup_manager.hpp" #include "karts/abstract_kart.hpp" #include "karts/controller/controller.hpp" +#include "karts/kart_properties.hpp" #include "karts/kart_properties_manager.hpp" #include "modes/linear_world.hpp" #include "network/crypto.hpp" @@ -91,6 +92,7 @@ ClientLobby::ClientLobby(const TransportAddress& a, std::shared_ptr s) m_disconnected_msg[PDI_BAD_CONNECTION] = _("Bad network connection is detected."); m_first_connect = true; + m_spectator = false; } // ClientLobby //----------------------------------------------------------------------------- @@ -110,6 +112,7 @@ ClientLobby::~ClientLobby() //----------------------------------------------------------------------------- void ClientLobby::setup() { + m_spectator = false; m_auto_back_to_lobby_time = std::numeric_limits::max(); m_start_live_game_time = std::numeric_limits::max(); m_received_server_result = false; @@ -649,6 +652,9 @@ void ClientLobby::handleServerInfo(Event* event) } bool server_config = data.getUInt8() == 1; NetworkingLobby::getInstance()->toggleServerConfigButton(server_config); + bool live_join_spectator = data.getUInt8() == 1; + NetworkingLobby::getInstance() + ->toggleServerLiveJoinable(live_join_spectator); } // handleServerInfo //----------------------------------------------------------------------------- @@ -1101,3 +1107,21 @@ void ClientLobby::handleKartInfo(Event* event) SFXManager::get()->quickSound("energy_bar_full"); MessageQueue::add(MessageQueue::MT_FRIEND, msg); } // handleKartInfo + +//----------------------------------------------------------------------------- +void ClientLobby::startLiveJoinKartSelection() +{ + NetworkKartSelectionScreen::getInstance()->setLiveJoin(true); + std::vector all_k = + kart_properties_manager->getKartsInGroup("standard"); + std::set karts; + for (int kart : all_k) + { + const KartProperties* kp = kart_properties_manager->getKartById(kart); + if (!kp->isAddon()) + karts.insert(kp->getIdent()); + } + NetworkKartSelectionScreen::getInstance() + ->setAvailableKartsFromServer(karts); + NetworkKartSelectionScreen::getInstance()->push(); +} // startLiveJoinKartSelection diff --git a/src/network/protocols/client_lobby.hpp b/src/network/protocols/client_lobby.hpp index 0e427de61..b030b6f03 100644 --- a/src/network/protocols/client_lobby.hpp +++ b/src/network/protocols/client_lobby.hpp @@ -79,6 +79,8 @@ private: bool m_first_connect; + bool m_spectator; + uint64_t m_auto_back_to_lobby_time; uint64_t m_start_live_game_time; @@ -125,6 +127,9 @@ public: bool isServerAutoGameTime() const { return m_server_auto_game_time; } virtual bool isRacing() const OVERRIDE { return m_state.load() == RACING; } void requestKartInfo(uint8_t kart_id); + void setSpectator(bool val) { m_spectator = val; } + bool isSpectator() const { return m_spectator; } + void startLiveJoinKartSelection(); }; #endif // CLIENT_LOBBY_HPP diff --git a/src/network/protocols/game_protocol.cpp b/src/network/protocols/game_protocol.cpp index e50cc4f2f..01668c118 100644 --- a/src/network/protocols/game_protocol.cpp +++ b/src/network/protocols/game_protocol.cpp @@ -174,7 +174,8 @@ void GameProtocol::controllerAction(int kart_id, PlayerAction action, void GameProtocol::handleControllerAction(Event *event) { STKPeer* peer = event->getPeer(); - if (NetworkConfig::get()->isServer() && peer->isWaitingForGame()) + if (NetworkConfig::get()->isServer() && (peer->isWaitingForGame() || + peer->getAvailableKartIDs().empty())) return; NetworkString &data = event->data(); uint8_t count = data.getUInt8(); diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index c749deeaf..bac5832e7 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -696,38 +696,48 @@ void ServerLobby::liveJoinRequest(Event* event) rejectLiveJoin(peer, BLR_NO_GAME_FOR_LIVE_JOIN); return; } - setPlayerKarts(data, peer); + bool spectator = data.getUInt8() == 1; + if (!spectator) + { + 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()) - { + std::vector used_id; 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(); + int id = getReservedId(peer->getPlayerProfiles()[i], i); + if (id == -1) + break; + used_id.push_back(id); } - Log::info("ServerLobby", "Too many players (%d) try to live join", - (int)peer->getPlayerProfiles().size()); - rejectLiveJoin(peer, BLR_NO_PLACE_FOR_LIVE_JOIN); - return; + 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; + } + + 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); + } + } + else + { + Log::info("ServerLobby", "%s spectating now.", + peer->getAddress().toString().c_str()); } - 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++) { @@ -856,9 +866,14 @@ void ServerLobby::finishedLoadingLiveJoinClient(Event* event) World::getWorld()->addReservedKart(id); const RemoteKartInfo& rki = race_manager->getKartInfo(id); addLiveJoiningKart(id, rki, m_last_live_join_util_ticks); + Log::info("ServerLobby", "%s live-joining succeeded 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()); } - Log::info("ServerLobby", "%s live-joining succeeded.", - peer->getAddress().toString().c_str()); NetworkString* ns = getNetworkString(10); ns->setSynchronous(true); diff --git a/src/states_screens/online/network_kart_selection.cpp b/src/states_screens/online/network_kart_selection.cpp index ef47f3f93..749a4678c 100644 --- a/src/states_screens/online/network_kart_selection.cpp +++ b/src/states_screens/online/network_kart_selection.cpp @@ -104,7 +104,9 @@ void NetworkKartSelectionScreen::allPlayersDone() if (m_live_join) { kart.setSynchronous(true); - kart.addUInt8(LobbyProtocol::LE_LIVE_JOIN); + kart.addUInt8(LobbyProtocol::LE_LIVE_JOIN) + // not spectator + .addUInt8(0); } else kart.addUInt8(LobbyProtocol::LE_KART_SELECTION); diff --git a/src/states_screens/online/networking_lobby.cpp b/src/states_screens/online/networking_lobby.cpp index 4520c0f03..d7bdb7e33 100644 --- a/src/states_screens/online/networking_lobby.cpp +++ b/src/states_screens/online/networking_lobby.cpp @@ -41,6 +41,7 @@ #include "network/protocols/client_lobby.hpp" #include "network/server.hpp" #include "network/stk_host.hpp" +#include "network/network_timer_synchronizer.hpp" #include "states_screens/dialogs/splitscreen_player_dialog.hpp" #include "states_screens/dialogs/network_user_dialog.hpp" #include "states_screens/dialogs/server_configuration_dialog.hpp" @@ -122,6 +123,10 @@ void NetworkingLobby::loadedFromFile() (file_manager->getAsset(FileManager::GUI_ICON, "hourglass.png")); video::ITexture* icon_5 = irr_driver->getTexture (file_manager->getAsset(FileManager::GUI_ICON, "green_check.png")); + m_config_texture = irr_driver->getTexture + (file_manager->getAsset(FileManager::GUI_ICON, "main_options.png")); + m_spectate_texture = irr_driver->getTexture + (file_manager->getAsset(FileManager::GUI_ICON, "screen_other.png")); m_icon_bank->addTextureAsSprite(icon_1); m_icon_bank->addTextureAsSprite(icon_2); m_icon_bank->addTextureAsSprite(icon_3); @@ -161,6 +166,8 @@ void NetworkingLobby::init() m_player_names.clear(); m_allow_change_team = false; m_has_auto_start_in_server = false; + m_server_live_joinable = false; + m_client_live_joinable = false; m_ping_update_timer = 0.0f; m_start_timeout = std::numeric_limits::max(); m_cur_starting_timer = std::numeric_limits::max(); @@ -248,6 +255,11 @@ void NetworkingLobby::onUpdate(float delta) if (NetworkConfig::get()->isServer() || !STKHost::existHost()) return; + m_start_button->setVisible(false); + m_config_button->setVisible(false); + m_config_button->setImage(m_config_texture); + m_client_live_joinable = false; + m_ping_update_timer += delta; if (m_player_list && m_ping_update_timer > 2.0f) { @@ -317,6 +329,18 @@ void NetworkingLobby::onUpdate(float delta) //wait before the current game finish msg = _("Please wait for the current game's end."); } + + // You can live join or spectator if u have the current play track + // and network timer is synchronized + if (t && + STKHost::get()->getNetworkTimerSynchronizer()->isSynchronised() && + m_server_live_joinable) + { + m_client_live_joinable = true; + } + else + m_client_live_joinable = false; + m_timeout_message->setText(msg, false); core::stringw total_msg; for (auto& string : m_server_info) @@ -326,6 +350,19 @@ void NetworkingLobby::onUpdate(float delta) } m_text_bubble->setText(total_msg, true); m_cur_starting_timer = std::numeric_limits::max(); + + if (m_client_live_joinable) + { + m_start_button->setVisible(true); + //I18N: Live join is displayed in networking lobby to allow players + //to join the current started in-progress game + m_start_button->setLabel(_("Live join")); + //I18N: Spectate is displayed in networking lobby to allow players + //to join the current started in-progress game + m_config_button->setLabel(_("Spectate")); + m_config_button->setImage(m_spectate_texture); + m_config_button->setVisible(true); + } return; } @@ -509,14 +546,34 @@ void NetworkingLobby::eventCallback(Widget* widget, const std::string& name, } // send chat message else if (name == m_start_button->m_properties[PROP_ID]) { - // Send a message to the server to start - NetworkString start(PROTOCOL_LOBBY_ROOM); - start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN); - STKHost::get()->sendToServer(&start, true); + if (m_client_live_joinable) + { + auto cl = LobbyProtocol::get(); + if (cl) + cl->startLiveJoinKartSelection(); + } + else + { + // Send a message to the server to start + NetworkString start(PROTOCOL_LOBBY_ROOM); + start.addUInt8(LobbyProtocol::LE_REQUEST_BEGIN); + STKHost::get()->sendToServer(&start, true); + } } else if (name == m_config_button->m_properties[PROP_ID]) { auto cl = LobbyProtocol::get(); + if (m_client_live_joinable && cl) + { + cl->setSpectator(true); + NetworkString start(PROTOCOL_LOBBY_ROOM); + start.setSynchronous(true); + start.addUInt8(LobbyProtocol::LE_LIVE_JOIN) + // is spectating + .addUInt8(1); + STKHost::get()->sendToServer(&start, true); + return; + } if (cl) { new ServerConfigurationDialog( diff --git a/src/states_screens/online/networking_lobby.hpp b/src/states_screens/online/networking_lobby.hpp index 7931fde89..408891ec5 100644 --- a/src/states_screens/online/networking_lobby.hpp +++ b/src/states_screens/online/networking_lobby.hpp @@ -77,7 +77,11 @@ private: unsigned m_min_start_game_players; bool m_allow_change_team, m_has_auto_start_in_server, - m_server_configurable; + m_server_configurable, m_server_live_joinable, + m_client_live_joinable; + + video::ITexture* m_config_texture; + video::ITexture* m_spectate_texture; GUIEngine::IconButtonWidget* m_back_widget; GUIEngine::LabelWidget* m_header; @@ -145,6 +149,7 @@ public: float start_timeout, unsigned server_max_player); void setStartingTimerTo(float t); void toggleServerConfigButton(bool val) { m_server_configurable = val; } + void toggleServerLiveJoinable(bool val) { m_server_live_joinable = val; } }; // class NetworkingLobby #endif