From bb31d6b2265b86f5128f940584cee5ae3f033eaf Mon Sep 17 00:00:00 2001 From: Benau Date: Sun, 6 Jan 2019 00:21:51 +0800 Subject: [PATCH] Allow player going back to lobby without disconnecting the server --- src/items/network_item_manager.hpp | 3 + src/karts/kart_rewinder.cpp | 11 ++- src/network/protocols/client_lobby.cpp | 18 +++-- src/network/protocols/lobby_protocol.hpp | 55 +++++++------- src/network/protocols/server_lobby.cpp | 75 ++++++++++++++++++- src/network/protocols/server_lobby.hpp | 2 + .../dialogs/race_paused_dialog.cpp | 75 ++++++++++++------- 7 files changed, 175 insertions(+), 64 deletions(-) diff --git a/src/items/network_item_manager.hpp b/src/items/network_item_manager.hpp index 3769d55a4..af0d3fe47 100644 --- a/src/items/network_item_manager.hpp +++ b/src/items/network_item_manager.hpp @@ -101,6 +101,9 @@ public: void addLiveJoinPeer(std::weak_ptr peer) { m_last_confirmed_item_ticks[peer] = 0; } // ------------------------------------------------------------------------ + void erasePeerInGame(std::weak_ptr peer) + { m_last_confirmed_item_ticks.erase(peer); } + // ------------------------------------------------------------------------ void saveCompleteState(BareNetworkString* buffer) const; // ------------------------------------------------------------------------ void restoreCompleteState(const BareNetworkString& buffer); diff --git a/src/karts/kart_rewinder.cpp b/src/karts/kart_rewinder.cpp index a25cb475f..846612d56 100644 --- a/src/karts/kart_rewinder.cpp +++ b/src/karts/kart_rewinder.cpp @@ -18,8 +18,10 @@ #include "karts/kart_rewinder.hpp" +#include "audio/sfx_manager.hpp" #include "items/attachment.hpp" #include "items/powerup.hpp" +#include "guiengine/message_queue.hpp" #include "karts/abstract_kart.hpp" #include "karts/explosion_animation.hpp" #include "karts/rescue_animation.hpp" @@ -65,8 +67,6 @@ void KartRewinder::reset() m_has_server_state = false; } // reset - - // ---------------------------------------------------------------------------- /** This function is called immediately before a rewind is done and saves * the current transform for the kart. The difference between this saved @@ -110,6 +110,13 @@ void KartRewinder::computeError() { const int kartid = getWorldKartId(); Log::debug("KartRewinder", "Kart id %d disconnected.", kartid); + + SFXManager::get()->quickSound("appear"); + core::stringw player_name = getController()->getName(); + // I18N: Message shown in game to tell player left the game in network + core::stringw msg = _("%s left the game.", player_name); + + MessageQueue::add(MessageQueue::MT_FRIEND, msg); World::getWorld()->eliminateKart(kartid, false/*notify_of_elimination*/); setPosition(World::getWorld()->getCurrentNumKarts() + 1); diff --git a/src/network/protocols/client_lobby.cpp b/src/network/protocols/client_lobby.cpp index 0c63ffb92..55b759d3e 100644 --- a/src/network/protocols/client_lobby.cpp +++ b/src/network/protocols/client_lobby.cpp @@ -508,14 +508,18 @@ void ClientLobby::disconnectedPlayer(Event* event) if (!checkDataSize(event, 1)) return; NetworkString &data = event->data(); - SFXManager::get()->quickSound("appear"); unsigned disconnected_player_count = data.getUInt8(); uint32_t host_id = data.getUInt32(); m_peers_votes.erase(host_id); + // If world exists the kart rewinder will know which player disconnects + if (!World::getWorld()) + SFXManager::get()->quickSound("appear"); for (unsigned i = 0; i < disconnected_player_count; i++) { std::string name; data.decodeString(&name); + if (!World::getWorld()) + continue; core::stringw player_name = StringUtils::utf8ToWide(name); core::stringw msg = _("%s disconnected.", player_name); // Use the friend icon to avoid an error-like message @@ -961,10 +965,14 @@ void ClientLobby::backToLobby(Event *event) m_auto_started = false; m_state.store(CONNECTED); - RaceEventManager::getInstance()->stop(); - auto gep = RaceEventManager::getInstance()->getProtocol(); - if (gep) - gep->requestTerminate(); + if (RaceEventManager::getInstance()) + { + RaceEventManager::getInstance()->stop(); + auto gep = RaceEventManager::getInstance()->getProtocol(); + // Game events protocol is main thread event only + if (gep) + gep->requestTerminate(); + } auto gp = GameProtocol::lock(); if (gp) { diff --git a/src/network/protocols/lobby_protocol.hpp b/src/network/protocols/lobby_protocol.hpp index 99040c796..679fcee07 100644 --- a/src/network/protocols/lobby_protocol.hpp +++ b/src/network/protocols/lobby_protocol.hpp @@ -46,33 +46,34 @@ public: /** Lists all lobby events (LE). */ enum : uint8_t { - LE_CONNECTION_REQUESTED = 1, // a connection to the server - LE_CONNECTION_REFUSED, // Connection to server refused - LE_CONNECTION_ACCEPTED, // Connection to server accepted - LE_SERVER_INFO, // inform client about server info - LE_REQUEST_BEGIN, // begin of kart selection - LE_UPDATE_PLAYER_LIST, // inform client about player list update - LE_KART_SELECTION, // Player selected kart - LE_PLAYER_DISCONNECTED, // Client disconnected - LE_CLIENT_LOADED_WORLD, // Client finished loading world - LE_LOAD_WORLD, // Clients should load world - LE_START_RACE, // Server to client to start race - LE_START_SELECTION, // inform client to start selection - LE_RACE_FINISHED, // race has finished, display result - LE_RACE_FINISHED_ACK, // client went back to lobby - LE_BACK_LOBBY, // Force clients to go back to lobby - LE_VOTE, // Track vote - LE_CHAT, - LE_SERVER_OWNERSHIP, - LE_KICK_HOST, - LE_CHANGE_TEAM, - LE_BAD_TEAM, - LE_BAD_CONNECTION, - LE_CONFIG_SERVER, - LE_CHANGE_HANDICAP, - LE_LIVE_JOIN, - LE_LIVE_JOIN_ACK, - LE_KART_INFO + LE_CONNECTION_REQUESTED = 1, // a connection to the server + LE_CONNECTION_REFUSED, // Connection to server refused + LE_CONNECTION_ACCEPTED, // Connection to server accepted + LE_SERVER_INFO, // inform client about server info + LE_REQUEST_BEGIN, // begin of kart selection + LE_UPDATE_PLAYER_LIST, // inform client about player list update + LE_KART_SELECTION, // Player selected kart + LE_PLAYER_DISCONNECTED, // Client disconnected + LE_CLIENT_LOADED_WORLD, // Client finished loading world + LE_LOAD_WORLD, // Clients should load world + LE_START_RACE, // Server to client to start race + LE_START_SELECTION, // inform client to start selection + LE_RACE_FINISHED, // race has finished, display result + LE_RACE_FINISHED_ACK, // client went back to lobby + LE_BACK_LOBBY, // Force clients to go back to lobby + LE_VOTE, // Track vote + LE_CHAT, // Client chat message + LE_SERVER_OWNERSHIP, // Tell client he is now the server owner + LE_KICK_HOST, // Server owner kicks some other peer in game + LE_CHANGE_TEAM, // Client wants to change his team + LE_BAD_TEAM, // Tell server owner that the team is unbalanced + LE_BAD_CONNECTION, // High ping or too many packets loss + LE_CONFIG_SERVER, // Server owner config server game mode or difficulty + LE_CHANGE_HANDICAP, // Client changes handicap + LE_LIVE_JOIN, // Client live join or spectate + LE_LIVE_JOIN_ACK, // Server tell client live join or spectate succeed + LE_KART_INFO, // Client or server exchange new kart info + LE_CLIENT_BACK_LOBBY // Client tell server to go back lobby }; enum RejectReason : uint8_t diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp index 44195d43c..c7151e2a7 100644 --- a/src/network/protocols/server_lobby.cpp +++ b/src/network/protocols/server_lobby.cpp @@ -303,6 +303,7 @@ bool ServerLobby::notifyEvent(Event* event) 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: clientWantsToBackLobby(event); break; default: Log::error("ServerLobby", "Unknown message type %d - ignored.", message_type); break; @@ -656,15 +657,25 @@ NetworkString* ServerLobby::getLoadWorldMessage( } // getLoadWorldMessage //----------------------------------------------------------------------------- +/** Returns true if server can be live joined or spectating + */ bool ServerLobby::canLiveJoinNow() const { return ServerConfig::m_live_players && - race_manager->supportsLiveJoining() && - World::getWorld() && RaceEventManager::getInstance()->isRunning() && + race_manager->supportsLiveJoining() && worldIsActive(); +} // 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 || World::getWorld()->getPhase() == WorldStatus::GOAL_PHASE); -} // canLiveJoinNow +} // worldIsActive //----------------------------------------------------------------------------- /** \ref STKPeer peer will be reset back to the lobby with reason @@ -701,6 +712,7 @@ void ServerLobby::liveJoinRequest(Event* event) return; } bool spectator = data.getUInt8() == 1; + peer->clearAvailableKartIDs(); if (!spectator) { setPlayerKarts(data, peer); @@ -728,7 +740,6 @@ void ServerLobby::liveJoinRequest(Event* event) return; } - peer->clearAvailableKartIDs(); for (int id : used_id) { Log::info("ServerLobby", "%s live joining with reserved kart id %d.", @@ -3352,6 +3363,9 @@ void ServerLobby::setPlayerKarts(const NetworkString& ns, STKPeer* peer) const } // setPlayerKarts //----------------------------------------------------------------------------- +/** Tell the client \ref RemoteKartInfo of a player when some player joining + * live. + */ void ServerLobby::handleKartInfo(Event* event) { World* w = World::getWorld(); @@ -3380,3 +3394,56 @@ void ServerLobby::handleKartInfo(Event* event) peer->sendPacket(ns, true/*reliable*/); delete ns; } // handleKartInfo + +//----------------------------------------------------------------------------- +/** Client if currently in-game (including spectator) wants to go back to + * lobby. + */ +void ServerLobby::clientWantsToBackLobby(Event* event) +{ + World* w = World::getWorld(); + std::shared_ptr 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()); + } + else + { + Log::error("ServerLobby", "%s doesn't exist anymore in server.", + peer->getAddress().toString().c_str()); + } + } + NetworkItemManager* nim = + dynamic_cast(ItemManager::get()); + assert(nim); + nim->erasePeerInGame(peer); + m_peers_ready.erase(peer); + peer->setWaitingForGame(true); + + 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; +} // clientWantsToBackLobby diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp index 0e4eb9ef7..43b2acbe9 100644 --- a/src/network/protocols/server_lobby.hpp +++ b/src/network/protocols/server_lobby.hpp @@ -279,9 +279,11 @@ private: void liveJoinRequest(Event* event); void rejectLiveJoin(STKPeer* peer, BackLobbyReason blr); bool canLiveJoinNow() const; + bool worldIsActive() const; int getReservedId(std::shared_ptr& p, unsigned local_id) const; void handleKartInfo(Event* event); + void clientWantsToBackLobby(Event* event); public: ServerLobby(); virtual ~ServerLobby(); diff --git a/src/states_screens/dialogs/race_paused_dialog.cpp b/src/states_screens/dialogs/race_paused_dialog.cpp index aeac50f7d..427312954 100644 --- a/src/states_screens/dialogs/race_paused_dialog.cpp +++ b/src/states_screens/dialogs/race_paused_dialog.cpp @@ -28,7 +28,9 @@ #include "io/file_manager.hpp" #include "modes/overworld.hpp" #include "modes/world.hpp" +#include "network/protocols/lobby_protocol.hpp" #include "network/network_config.hpp" +#include "network/network_string.hpp" #include "network/stk_host.hpp" #include "race/race_manager.hpp" #include "states_screens/help_screen_1.hpp" @@ -155,14 +157,21 @@ GUIEngine::EventPropagation } race_manager->exitRace(); race_manager->setAIKartOverride(""); - StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); - if (race_manager->raceWasStartedFromOverworld()) + if (NetworkConfig::get()->isNetworking()) { - OverWorld::enterOverWorld(); + StateManager::get()->resetAndSetStack( + NetworkConfig::get()->getResetScreens().data()); + NetworkConfig::get()->unsetNetworking(); + } + else + { + StateManager::get()->resetAndGoToScreen(MainMenuScreen::getInstance()); + if (race_manager->raceWasStartedFromOverworld()) + { + OverWorld::enterOverWorld(); + } } - - NetworkConfig::get()->unsetNetworking(); return GUIEngine::EVENT_BLOCK; } else if (selection == "help") @@ -187,23 +196,26 @@ GUIEngine::EventPropagation else if (selection == "newrace") { ModalDialog::dismiss(); - if (STKHost::existHost()) + if (NetworkConfig::get()->isNetworking()) { - STKHost::get()->shutdown(); + // back lobby + NetworkString back(PROTOCOL_LOBBY_ROOM); + back.setSynchronous(true); + back.addUInt8(LobbyProtocol::LE_CLIENT_BACK_LOBBY); + STKHost::get()->sendToServer(&back, true); + } + else + { + World::getWorld()->scheduleUnpause(); + race_manager->exitRace(); + Screen* new_stack[] = + { + MainMenuScreen::getInstance(), + RaceSetupScreen::getInstance(), + NULL + }; + StateManager::get()->resetAndSetStack(new_stack); } - World::getWorld()->scheduleUnpause(); - race_manager->exitRace(); - Screen* new_stack[] = - { - MainMenuScreen::getInstance(), - RaceSetupScreen::getInstance(), - NULL - }; - StateManager::get()->resetAndSetStack( - NetworkConfig::get()->isNetworking() ? - NetworkConfig::get()->getResetScreens().data() : - new_stack); - NetworkConfig::get()->unsetNetworking(); return GUIEngine::EVENT_BLOCK; } else if (selection == "endrace") @@ -224,7 +236,6 @@ GUIEngine::EventPropagation } // processEvent // ---------------------------------------------------------------------------- - void RacePausedDialog::beforeAddingWidgets() { GUIEngine::RibbonWidget* choice_ribbon = @@ -238,7 +249,8 @@ void RacePausedDialog::beforeAddingWidgets() // Disable in game menu to avoid timer desync if not racing in network // game if (NetworkConfig::get()->isNetworking() && - World::getWorld()->getPhase() != WorldStatus::RACE_PHASE) + !(World::getWorld()->getPhase() == WorldStatus::MUSIC_PHASE || + World::getWorld()->getPhase() == WorldStatus::RACE_PHASE)) { index = choice_ribbon->findItemNamed("help"); if (index != -1) @@ -246,8 +258,19 @@ void RacePausedDialog::beforeAddingWidgets() index = choice_ribbon->findItemNamed("options"); if (index != -1) choice_ribbon->setItemVisible(index, false); + index = choice_ribbon->findItemNamed("newrace"); + if (index != -1) + choice_ribbon->setItemVisible(index, false); } - -} - -// ---------------------------------------------------------------------------- + if (NetworkConfig::get()->isNetworking()) + { + IconButtonWidget* new_race = dynamic_cast + (choice_ribbon->findWidgetNamed("newrace")); + if (new_race) + { + //I18N show in race paused dialog in network to allow user to go + //back to lobby to end spectating (for example) + new_race->setText(_("Back to lobby")); + } + } +} // beforeAddingWidgets