|
|
@@ -16,6 +16,7 @@
|
|
|
#include "gui/CGuiHandler.h"
|
|
|
#include "gui/WindowHandler.h"
|
|
|
|
|
|
+#include "globalLobby/GlobalLobbyClient.h"
|
|
|
#include "lobby/CSelectionBase.h"
|
|
|
#include "lobby/CLobbyScreen.h"
|
|
|
#include "windows/InfoWindows.h"
|
|
|
@@ -46,16 +47,15 @@
|
|
|
#include "../lib/mapObjects/MiscObjects.h"
|
|
|
#include "../lib/modding/ModIncompatibility.h"
|
|
|
#include "../lib/rmg/CMapGenOptions.h"
|
|
|
+#include "../lib/serializer/Connection.h"
|
|
|
#include "../lib/filesystem/Filesystem.h"
|
|
|
#include "../lib/registerTypes/RegisterTypesLobbyPacks.h"
|
|
|
-#include "../lib/serializer/Connection.h"
|
|
|
#include "../lib/serializer/CMemorySerializer.h"
|
|
|
#include "../lib/UnlockGuard.h"
|
|
|
|
|
|
#include <boost/uuid/uuid.hpp>
|
|
|
#include <boost/uuid/uuid_io.hpp>
|
|
|
#include <boost/uuid/uuid_generators.hpp>
|
|
|
-#include <boost/asio.hpp>
|
|
|
#include "../lib/serializer/Cast.h"
|
|
|
#include "LobbyClientNetPackVisitors.h"
|
|
|
|
|
|
@@ -67,8 +67,6 @@
|
|
|
|
|
|
template<typename T> class CApplyOnLobby;
|
|
|
|
|
|
-const std::string CServerHandler::localhostAddress{"127.0.0.1"};
|
|
|
-
|
|
|
#if defined(VCMI_ANDROID) && !defined(SINGLE_PROCESS_APP)
|
|
|
extern std::atomic_bool androidTestServerReadyFlag;
|
|
|
#endif
|
|
|
@@ -76,8 +74,8 @@ extern std::atomic_bool androidTestServerReadyFlag;
|
|
|
class CBaseForLobbyApply
|
|
|
{
|
|
|
public:
|
|
|
- virtual bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const = 0;
|
|
|
- virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const = 0;
|
|
|
+ virtual bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const = 0;
|
|
|
+ virtual void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const = 0;
|
|
|
virtual ~CBaseForLobbyApply(){};
|
|
|
template<typename U> static CBaseForLobbyApply * getApplier(const U * t = nullptr)
|
|
|
{
|
|
|
@@ -88,124 +86,137 @@ public:
|
|
|
template<typename T> class CApplyOnLobby : public CBaseForLobbyApply
|
|
|
{
|
|
|
public:
|
|
|
- bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
|
|
|
+ bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
|
|
|
{
|
|
|
- boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
-
|
|
|
- T * ptr = static_cast<T *>(pack);
|
|
|
+ auto & ref = static_cast<T&>(pack);
|
|
|
ApplyOnLobbyHandlerNetPackVisitor visitor(*handler);
|
|
|
|
|
|
- logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ptr).name());
|
|
|
- ptr->visit(visitor);
|
|
|
+ logNetwork->trace("\tImmediately apply on lobby: %s", typeid(ref).name());
|
|
|
+ ref.visit(visitor);
|
|
|
|
|
|
return visitor.getResult();
|
|
|
}
|
|
|
|
|
|
- void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override
|
|
|
+ void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
|
|
|
{
|
|
|
- T * ptr = static_cast<T *>(pack);
|
|
|
+ auto & ref = static_cast<T &>(pack);
|
|
|
ApplyOnLobbyScreenNetPackVisitor visitor(*handler, lobby);
|
|
|
|
|
|
- logNetwork->trace("\tApply on lobby from queue: %s", typeid(ptr).name());
|
|
|
- ptr->visit(visitor);
|
|
|
+ logNetwork->trace("\tApply on lobby from queue: %s", typeid(ref).name());
|
|
|
+ ref.visit(visitor);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
template<> class CApplyOnLobby<CPack>: public CBaseForLobbyApply
|
|
|
{
|
|
|
public:
|
|
|
- bool applyOnLobbyHandler(CServerHandler * handler, void * pack) const override
|
|
|
+ bool applyOnLobbyHandler(CServerHandler * handler, CPackForLobby & pack) const override
|
|
|
{
|
|
|
logGlobal->error("Cannot apply plain CPack!");
|
|
|
assert(0);
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, void * pack) const override
|
|
|
+ void applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler, CPackForLobby & pack) const override
|
|
|
{
|
|
|
logGlobal->error("Cannot apply plain CPack!");
|
|
|
assert(0);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-static const std::string NAME_AFFIX = "client";
|
|
|
-static const std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name
|
|
|
+CServerHandler::~CServerHandler()
|
|
|
+{
|
|
|
+ networkHandler->stop();
|
|
|
+ try
|
|
|
+ {
|
|
|
+ threadNetwork.join();
|
|
|
+ }
|
|
|
+ catch (const std::runtime_error & e)
|
|
|
+ {
|
|
|
+ logGlobal->error("Failed to shut down network thread! Reason: %s", e.what());
|
|
|
+ assert(0);
|
|
|
+ }
|
|
|
+}
|
|
|
|
|
|
CServerHandler::CServerHandler()
|
|
|
- : state(EClientState::NONE), mx(std::make_shared<boost::recursive_mutex>()), client(nullptr), loadMode(0), campaignStateToSend(nullptr), campaignServerRestartLock(false)
|
|
|
+ : networkHandler(INetworkHandler::createHandler())
|
|
|
+ , lobbyClient(std::make_unique<GlobalLobbyClient>())
|
|
|
+ , applier(std::make_unique<CApplier<CBaseForLobbyApply>>())
|
|
|
+ , threadNetwork(&CServerHandler::threadRunNetwork, this)
|
|
|
+ , state(EClientState::NONE)
|
|
|
+ , serverPort(0)
|
|
|
+ , campaignStateToSend(nullptr)
|
|
|
+ , screenType(ESelectionScreen::unknown)
|
|
|
+ , serverMode(EServerMode::NONE)
|
|
|
+ , loadMode(ELoadMode::NONE)
|
|
|
+ , client(nullptr)
|
|
|
{
|
|
|
uuid = boost::uuids::to_string(boost::uuids::random_generator()());
|
|
|
- //read from file to restore last session
|
|
|
- if(!settings["server"]["uuid"].isNull() && !settings["server"]["uuid"].String().empty())
|
|
|
- uuid = settings["server"]["uuid"].String();
|
|
|
- applier = std::make_shared<CApplier<CBaseForLobbyApply>>();
|
|
|
registerTypesLobbyPacks(*applier);
|
|
|
}
|
|
|
|
|
|
-CServerHandler::~CServerHandler() = default;
|
|
|
+void CServerHandler::threadRunNetwork()
|
|
|
+{
|
|
|
+ logGlobal->info("Starting network thread");
|
|
|
+ setThreadName("runNetwork");
|
|
|
+ networkHandler->run();
|
|
|
+ logGlobal->info("Ending network thread");
|
|
|
+}
|
|
|
|
|
|
-void CServerHandler::resetStateForLobby(const StartInfo::EMode mode, const std::vector<std::string> * names)
|
|
|
+void CServerHandler::resetStateForLobby(EStartMode mode, ESelectionScreen screen, EServerMode newServerMode, const std::vector<std::string> & names)
|
|
|
{
|
|
|
hostClientId = -1;
|
|
|
- state = EClientState::NONE;
|
|
|
+ setState(EClientState::NONE);
|
|
|
+ serverMode = newServerMode;
|
|
|
mapToStart = nullptr;
|
|
|
th = std::make_unique<CStopWatch>();
|
|
|
- packsForLobbyScreen.clear();
|
|
|
- c.reset();
|
|
|
+ logicConnection.reset();
|
|
|
si = std::make_shared<StartInfo>();
|
|
|
playerNames.clear();
|
|
|
si->difficulty = 1;
|
|
|
si->mode = mode;
|
|
|
+ screenType = screen;
|
|
|
myNames.clear();
|
|
|
- if(names && !names->empty()) //if have custom set of player names - use it
|
|
|
- myNames = *names;
|
|
|
+ if(!names.empty()) //if have custom set of player names - use it
|
|
|
+ myNames = names;
|
|
|
else
|
|
|
myNames.push_back(settings["general"]["playerName"].String());
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::startLocalServerAndConnect()
|
|
|
+GlobalLobbyClient & CServerHandler::getGlobalLobby()
|
|
|
+{
|
|
|
+ return *lobbyClient;
|
|
|
+}
|
|
|
+
|
|
|
+INetworkHandler & CServerHandler::getNetworkHandler()
|
|
|
+{
|
|
|
+ return *networkHandler;
|
|
|
+}
|
|
|
+
|
|
|
+void CServerHandler::startLocalServerAndConnect(bool connectToLobby)
|
|
|
{
|
|
|
- if(threadRunLocalServer)
|
|
|
- threadRunLocalServer->join();
|
|
|
+ if(threadRunLocalServer.joinable())
|
|
|
+ threadRunLocalServer.join();
|
|
|
|
|
|
th->update();
|
|
|
-
|
|
|
- auto errorMsg = CGI->generaltexth->translate("vcmi.server.errors.existingProcess");
|
|
|
- try
|
|
|
- {
|
|
|
- CConnection testConnection(localhostAddress, getDefaultPort(), NAME, uuid);
|
|
|
- logNetwork->error("Port is busy, check if another instance of vcmiserver is working");
|
|
|
- CInfoWindow::showInfoDialog(errorMsg, {});
|
|
|
- return;
|
|
|
- }
|
|
|
- catch(std::runtime_error & error)
|
|
|
- {
|
|
|
- //no connection means that port is not busy and we can start local server
|
|
|
- }
|
|
|
-
|
|
|
+
|
|
|
#if defined(SINGLE_PROCESS_APP)
|
|
|
boost::condition_variable cond;
|
|
|
- std::vector<std::string> args{"--uuid=" + uuid, "--port=" + std::to_string(getHostPort())};
|
|
|
- if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool())
|
|
|
- {
|
|
|
- args.push_back("--lobby=" + settings["session"]["address"].String());
|
|
|
- args.push_back("--connections=" + settings["session"]["hostConnections"].String());
|
|
|
- args.push_back("--lobby-port=" + std::to_string(settings["session"]["port"].Integer()));
|
|
|
- args.push_back("--lobby-uuid=" + settings["session"]["hostUuid"].String());
|
|
|
- }
|
|
|
- threadRunLocalServer = std::make_shared<boost::thread>([&cond, args, this] {
|
|
|
+ std::vector<std::string> args{"--port=" + std::to_string(getLocalPort())};
|
|
|
+ if(connectToLobby)
|
|
|
+ args.push_back("--lobby");
|
|
|
+
|
|
|
+ threadRunLocalServer = boost::thread([&cond, args] {
|
|
|
setThreadName("CVCMIServer");
|
|
|
CVCMIServer::create(&cond, args);
|
|
|
- onServerFinished();
|
|
|
});
|
|
|
- threadRunLocalServer->detach();
|
|
|
#elif defined(VCMI_ANDROID)
|
|
|
{
|
|
|
CAndroidVMHelper envHelper;
|
|
|
envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "startServer", true);
|
|
|
}
|
|
|
#else
|
|
|
- threadRunLocalServer = std::make_shared<boost::thread>(&CServerHandler::threadRunServer, this); //runs server executable;
|
|
|
+ threadRunLocalServer = boost::thread(&CServerHandler::threadRunServer, this, connectToLobby); //runs server executable;
|
|
|
#endif
|
|
|
logNetwork->trace("Setting up thread calling server: %d ms", th->getDiff());
|
|
|
|
|
|
@@ -236,7 +247,7 @@ void CServerHandler::startLocalServerAndConnect()
|
|
|
while(!androidTestServerReadyFlag.load())
|
|
|
{
|
|
|
logNetwork->info("still waiting...");
|
|
|
- boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
|
|
|
+ boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
|
|
}
|
|
|
logNetwork->info("waiting for server finished...");
|
|
|
androidTestServerReadyFlag = false;
|
|
|
@@ -245,151 +256,165 @@ void CServerHandler::startLocalServerAndConnect()
|
|
|
|
|
|
th->update(); //put breakpoint here to attach to server before it does something stupid
|
|
|
|
|
|
- justConnectToServer(localhostAddress, 0);
|
|
|
+ connectToServer(getLocalHostname(), getLocalPort());
|
|
|
|
|
|
logNetwork->trace("\tConnecting to the server: %d ms", th->getDiff());
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::justConnectToServer(const std::string & addr, const ui16 port)
|
|
|
+void CServerHandler::connectToServer(const std::string & addr, const ui16 port)
|
|
|
{
|
|
|
- state = EClientState::CONNECTING;
|
|
|
- while(!c && state != EClientState::CONNECTION_CANCELLED)
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- logNetwork->info("Establishing connection...");
|
|
|
- c = std::make_shared<CConnection>(
|
|
|
- addr.size() ? addr : getHostAddress(),
|
|
|
- port ? port : getHostPort(),
|
|
|
- NAME, uuid);
|
|
|
-
|
|
|
- nextClient = std::make_unique<CClient>();
|
|
|
- c->iser.cb = nextClient.get();
|
|
|
- }
|
|
|
- catch(std::runtime_error & error)
|
|
|
- {
|
|
|
- logNetwork->warn("\nCannot establish connection. %s Retrying in 1 second", error.what());
|
|
|
- boost::this_thread::sleep_for(boost::chrono::milliseconds(1000));
|
|
|
- }
|
|
|
- }
|
|
|
+ logNetwork->info("Establishing connection to %s:%d...", addr, port);
|
|
|
+ setState(EClientState::CONNECTING);
|
|
|
+ serverHostname = addr;
|
|
|
+ serverPort = port;
|
|
|
|
|
|
- if(state == EClientState::CONNECTION_CANCELLED)
|
|
|
+ if (!isServerLocal())
|
|
|
{
|
|
|
- logNetwork->info("Connection aborted by player!");
|
|
|
- return;
|
|
|
+ Settings remoteAddress = settings.write["server"]["remoteHostname"];
|
|
|
+ remoteAddress->String() = addr;
|
|
|
+
|
|
|
+ Settings remotePort = settings.write["server"]["remotePort"];
|
|
|
+ remotePort->Integer() = port;
|
|
|
}
|
|
|
|
|
|
- c->handler = std::make_shared<boost::thread>(&CServerHandler::threadHandleConnection, this);
|
|
|
+ networkHandler->connectToRemote(*this, addr, port);
|
|
|
+}
|
|
|
+
|
|
|
+void CServerHandler::onConnectionFailed(const std::string & errorMessage)
|
|
|
+{
|
|
|
+ assert(getState() == EClientState::CONNECTING);
|
|
|
+ boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
|
|
|
- if(!addr.empty() && addr != getHostAddress())
|
|
|
+ if (isServerLocal())
|
|
|
{
|
|
|
- Settings serverAddress = settings.write["server"]["server"];
|
|
|
- serverAddress->String() = addr;
|
|
|
+ // retry - local server might be still starting up
|
|
|
+ logNetwork->debug("\nCannot establish connection. %s. Retrying...", errorMessage);
|
|
|
+ networkHandler->createTimer(*this, std::chrono::milliseconds(100));
|
|
|
}
|
|
|
- if(port && port != getHostPort())
|
|
|
+ else
|
|
|
{
|
|
|
- Settings serverPort = settings.write["server"]["port"];
|
|
|
- serverPort->Integer() = port;
|
|
|
+ // remote server refused connection - show error message
|
|
|
+ setState(EClientState::NONE);
|
|
|
+ CInfoWindow::showInfoDialog(CGI->generaltexth->translate("vcmi.mainMenu.serverConnectionFailed"), {});
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::applyPacksOnLobbyScreen()
|
|
|
+void CServerHandler::onTimer()
|
|
|
{
|
|
|
- if(!c || !c->handler)
|
|
|
- return;
|
|
|
+ boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
|
|
|
- boost::unique_lock<boost::recursive_mutex> lock(*mx);
|
|
|
- while(!packsForLobbyScreen.empty())
|
|
|
+ if(getState() == EClientState::CONNECTION_CANCELLED)
|
|
|
{
|
|
|
- boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
- CPackForLobby * pack = packsForLobbyScreen.front();
|
|
|
- packsForLobbyScreen.pop_front();
|
|
|
- CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(pack)); //find the applier
|
|
|
- apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
|
|
|
- GH.windows().totalRedraw();
|
|
|
- delete pack;
|
|
|
+ logNetwork->info("Connection aborted by player!");
|
|
|
+ return;
|
|
|
}
|
|
|
+
|
|
|
+ assert(isServerLocal());
|
|
|
+ networkHandler->connectToRemote(*this, getLocalHostname(), getLocalPort());
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::stopServerConnection()
|
|
|
+void CServerHandler::onConnectionEstablished(const NetworkConnectionPtr & netConnection)
|
|
|
{
|
|
|
- if(c->handler)
|
|
|
+ assert(getState() == EClientState::CONNECTING);
|
|
|
+
|
|
|
+ boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
+
|
|
|
+ networkConnection = netConnection;
|
|
|
+
|
|
|
+ logNetwork->info("Connection established");
|
|
|
+
|
|
|
+ if (serverMode == EServerMode::LOBBY_GUEST)
|
|
|
{
|
|
|
- while(!c->handler->timed_join(boost::chrono::milliseconds(50)))
|
|
|
- applyPacksOnLobbyScreen();
|
|
|
- c->handler->join();
|
|
|
+ // say hello to lobby to switch connection to proxy mode
|
|
|
+ getGlobalLobby().sendProxyConnectionLogin(netConnection);
|
|
|
}
|
|
|
+
|
|
|
+ logicConnection = std::make_shared<CConnection>(netConnection);
|
|
|
+ logicConnection->uuid = uuid;
|
|
|
+ logicConnection->enterLobbyConnectionMode();
|
|
|
+ sendClientConnecting();
|
|
|
}
|
|
|
|
|
|
-std::set<PlayerColor> CServerHandler::getHumanColors()
|
|
|
+void CServerHandler::applyPackOnLobbyScreen(CPackForLobby & pack)
|
|
|
{
|
|
|
- return clientHumanColors(c->connectionID);
|
|
|
+ const CBaseForLobbyApply * apply = applier->getApplier(CTypeList::getInstance().getTypeID(&pack)); //find the applier
|
|
|
+ apply->applyOnLobbyScreen(dynamic_cast<CLobbyScreen *>(SEL), this, pack);
|
|
|
+ GH.windows().totalRedraw();
|
|
|
}
|
|
|
|
|
|
+std::set<PlayerColor> CServerHandler::getHumanColors()
|
|
|
+{
|
|
|
+ return clientHumanColors(logicConnection->connectionID);
|
|
|
+}
|
|
|
|
|
|
PlayerColor CServerHandler::myFirstColor() const
|
|
|
{
|
|
|
- return clientFirstColor(c->connectionID);
|
|
|
+ return clientFirstColor(logicConnection->connectionID);
|
|
|
}
|
|
|
|
|
|
bool CServerHandler::isMyColor(PlayerColor color) const
|
|
|
{
|
|
|
- return isClientColor(c->connectionID, color);
|
|
|
+ return isClientColor(logicConnection->connectionID, color);
|
|
|
}
|
|
|
|
|
|
ui8 CServerHandler::myFirstId() const
|
|
|
{
|
|
|
- return clientFirstId(c->connectionID);
|
|
|
+ return clientFirstId(logicConnection->connectionID);
|
|
|
}
|
|
|
|
|
|
-bool CServerHandler::isServerLocal() const
|
|
|
+EClientState CServerHandler::getState() const
|
|
|
{
|
|
|
- if(threadRunLocalServer)
|
|
|
- return true;
|
|
|
+ return state;
|
|
|
+}
|
|
|
|
|
|
- return false;
|
|
|
+void CServerHandler::setState(EClientState newState)
|
|
|
+{
|
|
|
+ state = newState;
|
|
|
+}
|
|
|
+
|
|
|
+bool CServerHandler::isServerLocal() const
|
|
|
+{
|
|
|
+ return threadRunLocalServer.joinable();
|
|
|
}
|
|
|
|
|
|
bool CServerHandler::isHost() const
|
|
|
{
|
|
|
- return c && hostClientId == c->connectionID;
|
|
|
+ return logicConnection && hostClientId == logicConnection->connectionID;
|
|
|
}
|
|
|
|
|
|
bool CServerHandler::isGuest() const
|
|
|
{
|
|
|
- return !c || hostClientId != c->connectionID;
|
|
|
+ return !logicConnection || hostClientId != logicConnection->connectionID;
|
|
|
}
|
|
|
|
|
|
-ui16 CServerHandler::getDefaultPort()
|
|
|
+const std::string & CServerHandler::getLocalHostname() const
|
|
|
{
|
|
|
- return static_cast<ui16>(settings["server"]["port"].Integer());
|
|
|
+ return settings["server"]["localHostname"].String();
|
|
|
}
|
|
|
|
|
|
-std::string CServerHandler::getDefaultPortStr()
|
|
|
+ui16 CServerHandler::getLocalPort() const
|
|
|
{
|
|
|
- return std::to_string(getDefaultPort());
|
|
|
+ return settings["server"]["localPort"].Integer();
|
|
|
}
|
|
|
|
|
|
-std::string CServerHandler::getHostAddress() const
|
|
|
+const std::string & CServerHandler::getRemoteHostname() const
|
|
|
{
|
|
|
- if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool())
|
|
|
- return settings["server"]["server"].String();
|
|
|
-
|
|
|
- if(settings["session"]["host"].Bool())
|
|
|
- return localhostAddress;
|
|
|
-
|
|
|
- return settings["session"]["address"].String();
|
|
|
+ return settings["server"]["remoteHostname"].String();
|
|
|
}
|
|
|
|
|
|
-ui16 CServerHandler::getHostPort() const
|
|
|
+ui16 CServerHandler::getRemotePort() const
|
|
|
{
|
|
|
- if(settings["session"]["lobby"].isNull() || !settings["session"]["lobby"].Bool())
|
|
|
- return getDefaultPort();
|
|
|
-
|
|
|
- if(settings["session"]["host"].Bool())
|
|
|
- return getDefaultPort();
|
|
|
-
|
|
|
- return settings["session"]["port"].Integer();
|
|
|
+ return settings["server"]["remotePort"].Integer();
|
|
|
+}
|
|
|
+
|
|
|
+const std::string & CServerHandler::getCurrentHostname() const
|
|
|
+{
|
|
|
+ return serverHostname;
|
|
|
+}
|
|
|
+
|
|
|
+ui16 CServerHandler::getCurrentPort() const
|
|
|
+{
|
|
|
+ return serverPort;
|
|
|
}
|
|
|
|
|
|
void CServerHandler::sendClientConnecting() const
|
|
|
@@ -404,13 +429,16 @@ void CServerHandler::sendClientConnecting() const
|
|
|
void CServerHandler::sendClientDisconnecting()
|
|
|
{
|
|
|
// FIXME: This is workaround needed to make sure client not trying to sent anything to non existed server
|
|
|
- if(state == EClientState::DISCONNECTING)
|
|
|
+ if(getState() == EClientState::DISCONNECTING)
|
|
|
+ {
|
|
|
+ assert(0);
|
|
|
return;
|
|
|
+ }
|
|
|
|
|
|
- state = EClientState::DISCONNECTING;
|
|
|
+ setState(EClientState::DISCONNECTING);
|
|
|
mapToStart = nullptr;
|
|
|
LobbyClientDisconnected lcd;
|
|
|
- lcd.clientId = c->connectionID;
|
|
|
+ lcd.clientId = logicConnection->connectionID;
|
|
|
logNetwork->info("Connection has been requested to be closed.");
|
|
|
if(isServerLocal())
|
|
|
{
|
|
|
@@ -422,18 +450,14 @@ void CServerHandler::sendClientDisconnecting()
|
|
|
logNetwork->info("Sent leaving signal to the server");
|
|
|
}
|
|
|
sendLobbyPack(lcd);
|
|
|
-
|
|
|
- {
|
|
|
- // Network thread might be applying network pack at this moment
|
|
|
- auto unlockInterface = vstd::makeUnlockGuard(GH.interfaceMutex);
|
|
|
- c->close();
|
|
|
- c.reset();
|
|
|
- }
|
|
|
+ networkConnection->close();
|
|
|
+ networkConnection.reset();
|
|
|
+ logicConnection.reset();
|
|
|
}
|
|
|
|
|
|
void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign)
|
|
|
{
|
|
|
- state = EClientState::LOBBY_CAMPAIGN;
|
|
|
+ setState(EClientState::LOBBY_CAMPAIGN);
|
|
|
LobbySetCampaign lsc;
|
|
|
lsc.ourCampaign = newCampaign;
|
|
|
sendLobbyPack(lsc);
|
|
|
@@ -441,7 +465,7 @@ void CServerHandler::setCampaignState(std::shared_ptr<CampaignState> newCampaign
|
|
|
|
|
|
void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
|
|
|
{
|
|
|
- if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
|
|
|
+ if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
|
|
|
return;
|
|
|
|
|
|
LobbySetCampaignMap lscm;
|
|
|
@@ -451,7 +475,7 @@ void CServerHandler::setCampaignMap(CampaignScenarioID mapId) const
|
|
|
|
|
|
void CServerHandler::setCampaignBonus(int bonusId) const
|
|
|
{
|
|
|
- if(state == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
|
|
|
+ if(getState() == EClientState::GAMEPLAY) // FIXME: UI shouldn't sent commands in first place
|
|
|
return;
|
|
|
|
|
|
LobbySetCampaignBonus lscb;
|
|
|
@@ -575,9 +599,7 @@ void CServerHandler::sendRestartGame() const
|
|
|
{
|
|
|
GH.windows().createAndPushWindow<CLoadingScreen>();
|
|
|
|
|
|
- LobbyEndGame endGame;
|
|
|
- endGame.closeConnection = false;
|
|
|
- endGame.restart = true;
|
|
|
+ LobbyRestartGame endGame;
|
|
|
sendLobbyPack(endGame);
|
|
|
}
|
|
|
|
|
|
@@ -621,17 +643,18 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
|
|
|
if(!settings["session"]["headless"].Bool())
|
|
|
GH.windows().createAndPushWindow<CLoadingScreen>();
|
|
|
|
|
|
+ LobbyPrepareStartGame lpsg;
|
|
|
+ sendLobbyPack(lpsg);
|
|
|
+
|
|
|
LobbyStartGame lsg;
|
|
|
if(client)
|
|
|
{
|
|
|
lsg.initializedStartInfo = std::make_shared<StartInfo>(* const_cast<StartInfo *>(client->getStartInfo(true)));
|
|
|
- lsg.initializedStartInfo->mode = StartInfo::NEW_GAME;
|
|
|
+ lsg.initializedStartInfo->mode = EStartMode::NEW_GAME;
|
|
|
lsg.initializedStartInfo->seedToBeUsed = lsg.initializedStartInfo->seedPostInit = 0;
|
|
|
* si = * lsg.initializedStartInfo;
|
|
|
}
|
|
|
sendLobbyPack(lsg);
|
|
|
- c->enterLobbyConnectionMode();
|
|
|
- c->disableStackSendingByID();
|
|
|
}
|
|
|
|
|
|
void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to)
|
|
|
@@ -644,83 +667,54 @@ void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameSta
|
|
|
if(CMM)
|
|
|
CMM->disable();
|
|
|
|
|
|
- std::swap(client, nextClient);
|
|
|
-
|
|
|
highScoreCalc = nullptr;
|
|
|
|
|
|
switch(si->mode)
|
|
|
{
|
|
|
- case StartInfo::NEW_GAME:
|
|
|
+ case EStartMode::NEW_GAME:
|
|
|
client->newGame(gameState);
|
|
|
break;
|
|
|
- case StartInfo::CAMPAIGN:
|
|
|
+ case EStartMode::CAMPAIGN:
|
|
|
client->newGame(gameState);
|
|
|
break;
|
|
|
- case StartInfo::LOAD_GAME:
|
|
|
+ case EStartMode::LOAD_GAME:
|
|
|
client->loadGame(gameState);
|
|
|
break;
|
|
|
default:
|
|
|
throw std::runtime_error("Invalid mode");
|
|
|
}
|
|
|
// After everything initialized we can accept CPackToClient netpacks
|
|
|
- c->enterGameplayConnectionMode(client->gameState());
|
|
|
- state = EClientState::GAMEPLAY;
|
|
|
-
|
|
|
- //store settings to continue game
|
|
|
- if(!isServerLocal() && isGuest())
|
|
|
- {
|
|
|
- Settings saveSession = settings.write["server"]["reconnect"];
|
|
|
- saveSession->Bool() = true;
|
|
|
- Settings saveUuid = settings.write["server"]["uuid"];
|
|
|
- saveUuid->String() = uuid;
|
|
|
- Settings saveNames = settings.write["server"]["names"];
|
|
|
- saveNames->Vector().clear();
|
|
|
- for(auto & name : myNames)
|
|
|
- {
|
|
|
- JsonNode jsonName;
|
|
|
- jsonName.String() = name;
|
|
|
- saveNames->Vector().push_back(jsonName);
|
|
|
- }
|
|
|
- }
|
|
|
+ logicConnection->enterGameplayConnectionMode(client->gameState());
|
|
|
+ setState(EClientState::GAMEPLAY);
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::endGameplay(bool closeConnection, bool restart)
|
|
|
+void CServerHandler::endGameplay()
|
|
|
{
|
|
|
- if(closeConnection)
|
|
|
- {
|
|
|
- // Game is ending
|
|
|
- // Tell the network thread to reach a stable state
|
|
|
- CSH->sendClientDisconnecting();
|
|
|
- logNetwork->info("Closed connection.");
|
|
|
- }
|
|
|
+ // Game is ending
|
|
|
+ // Tell the network thread to reach a stable state
|
|
|
+ CSH->sendClientDisconnecting();
|
|
|
+ logNetwork->info("Closed connection.");
|
|
|
|
|
|
client->endGame();
|
|
|
client.reset();
|
|
|
|
|
|
- if(!restart)
|
|
|
+ if(CMM)
|
|
|
{
|
|
|
- if(CMM)
|
|
|
- {
|
|
|
- GH.curInt = CMM.get();
|
|
|
- CMM->enable();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- GH.curInt = CMainMenu::create().get();
|
|
|
- }
|
|
|
+ GH.curInt = CMM.get();
|
|
|
+ CMM->enable();
|
|
|
}
|
|
|
-
|
|
|
- if(c)
|
|
|
+ else
|
|
|
{
|
|
|
- nextClient = std::make_unique<CClient>();
|
|
|
- c->iser.cb = nextClient.get();
|
|
|
- c->enterLobbyConnectionMode();
|
|
|
- c->disableStackSendingByID();
|
|
|
+ GH.curInt = CMainMenu::create().get();
|
|
|
}
|
|
|
-
|
|
|
- //reset settings
|
|
|
- Settings saveSession = settings.write["server"]["reconnect"];
|
|
|
- saveSession->Bool() = false;
|
|
|
+}
|
|
|
+
|
|
|
+void CServerHandler::restartGameplay()
|
|
|
+{
|
|
|
+ client->endGame();
|
|
|
+ client.reset();
|
|
|
+
|
|
|
+ logicConnection->enterLobbyConnectionMode();
|
|
|
}
|
|
|
|
|
|
void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared_ptr<CampaignState> cs)
|
|
|
@@ -741,7 +735,6 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
|
|
|
|
|
GH.dispatchMainThread([ourCampaign, this]()
|
|
|
{
|
|
|
- CSH->campaignServerRestartLock.set(true);
|
|
|
CSH->endGameplay();
|
|
|
|
|
|
auto & epilogue = ourCampaign->scenario(*ourCampaign->lastScenario()).epilog;
|
|
|
@@ -764,13 +757,14 @@ void CServerHandler::startCampaignScenario(HighScoreParameter param, std::shared
|
|
|
GH.windows().createAndPushWindow<CHighScoreInputScreen>(true, *highScoreCalc);
|
|
|
}
|
|
|
};
|
|
|
+
|
|
|
+ threadRunLocalServer.join();
|
|
|
if(epilogue.hasPrologEpilog)
|
|
|
{
|
|
|
GH.windows().createAndPushWindow<CPrologEpilogVideo>(epilogue, finisher);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- CSH->campaignServerRestartLock.waitUntil(false);
|
|
|
finisher();
|
|
|
}
|
|
|
});
|
|
|
@@ -796,15 +790,15 @@ int CServerHandler::howManyPlayerInterfaces()
|
|
|
return playerInts;
|
|
|
}
|
|
|
|
|
|
-ui8 CServerHandler::getLoadMode()
|
|
|
+ELoadMode CServerHandler::getLoadMode()
|
|
|
{
|
|
|
- if(loadMode != ELoadMode::TUTORIAL && state == EClientState::GAMEPLAY)
|
|
|
+ if(loadMode != ELoadMode::TUTORIAL && getState() == EClientState::GAMEPLAY)
|
|
|
{
|
|
|
if(si->campState)
|
|
|
return ELoadMode::CAMPAIGN;
|
|
|
for(auto pn : playerNames)
|
|
|
{
|
|
|
- if(pn.second.connection != c->connectionID)
|
|
|
+ if(pn.second.connection != logicConnection->connectionID)
|
|
|
return ELoadMode::MULTI;
|
|
|
}
|
|
|
if(howManyPlayerInterfaces() > 1) //this condition will work for hotseat mode OR multiplayer with allowed more than 1 color per player to control
|
|
|
@@ -815,48 +809,24 @@ ui8 CServerHandler::getLoadMode()
|
|
|
return loadMode;
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::restoreLastSession()
|
|
|
-{
|
|
|
- auto loadSession = [this]()
|
|
|
- {
|
|
|
- uuid = settings["server"]["uuid"].String();
|
|
|
- for(auto & name : settings["server"]["names"].Vector())
|
|
|
- myNames.push_back(name.String());
|
|
|
- resetStateForLobby(StartInfo::LOAD_GAME, &myNames);
|
|
|
- screenType = ESelectionScreen::loadGame;
|
|
|
- justConnectToServer(settings["server"]["server"].String(), settings["server"]["port"].Integer());
|
|
|
- };
|
|
|
-
|
|
|
- auto cleanUpSession = []()
|
|
|
- {
|
|
|
- //reset settings
|
|
|
- Settings saveSession = settings.write["server"]["reconnect"];
|
|
|
- saveSession->Bool() = false;
|
|
|
- };
|
|
|
-
|
|
|
- CInfoWindow::showYesNoDialog(VLC->generaltexth->translate("vcmi.server.confirmReconnect"), {}, loadSession, cleanUpSession);
|
|
|
-}
|
|
|
-
|
|
|
void CServerHandler::debugStartTest(std::string filename, bool save)
|
|
|
{
|
|
|
logGlobal->info("Starting debug test with file: %s", filename);
|
|
|
auto mapInfo = std::make_shared<CMapInfo>();
|
|
|
if(save)
|
|
|
{
|
|
|
- resetStateForLobby(StartInfo::LOAD_GAME);
|
|
|
+ resetStateForLobby(EStartMode::LOAD_GAME, ESelectionScreen::loadGame, EServerMode::LOCAL, {});
|
|
|
mapInfo->saveInit(ResourcePath(filename, EResType::SAVEGAME));
|
|
|
- screenType = ESelectionScreen::loadGame;
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- resetStateForLobby(StartInfo::NEW_GAME);
|
|
|
+ resetStateForLobby(EStartMode::NEW_GAME, ESelectionScreen::newGame, EServerMode::LOCAL, {});
|
|
|
mapInfo->mapInit(filename);
|
|
|
- screenType = ESelectionScreen::newGame;
|
|
|
}
|
|
|
if(settings["session"]["donotstartserver"].Bool())
|
|
|
- justConnectToServer(localhostAddress, 3030);
|
|
|
+ connectToServer(getLocalHostname(), getLocalPort());
|
|
|
else
|
|
|
- startLocalServerAndConnect();
|
|
|
+ startLocalServerAndConnect(false);
|
|
|
|
|
|
boost::this_thread::sleep_for(boost::chrono::milliseconds(100));
|
|
|
|
|
|
@@ -899,91 +869,71 @@ public:
|
|
|
{
|
|
|
}
|
|
|
|
|
|
- virtual bool callTyped() override { return false; }
|
|
|
+ bool callTyped() override { return false; }
|
|
|
|
|
|
- virtual void visitForLobby(CPackForLobby & lobbyPack) override
|
|
|
+ void visitForLobby(CPackForLobby & lobbyPack) override
|
|
|
{
|
|
|
handler.visitForLobby(lobbyPack);
|
|
|
}
|
|
|
|
|
|
- virtual void visitForClient(CPackForClient & clientPack) override
|
|
|
+ void visitForClient(CPackForClient & clientPack) override
|
|
|
{
|
|
|
handler.visitForClient(clientPack);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-void CServerHandler::threadHandleConnection()
|
|
|
+void CServerHandler::onPacketReceived(const std::shared_ptr<INetworkConnection> &, const std::vector<std::byte> & message)
|
|
|
{
|
|
|
- setThreadName("handleConnection");
|
|
|
- c->enterLobbyConnectionMode();
|
|
|
+ boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
|
|
|
- try
|
|
|
+ if(getState() == EClientState::DISCONNECTING)
|
|
|
{
|
|
|
- sendClientConnecting();
|
|
|
- while(c && c->connected)
|
|
|
- {
|
|
|
- while(state == EClientState::STARTING)
|
|
|
- boost::this_thread::sleep_for(boost::chrono::milliseconds(10));
|
|
|
-
|
|
|
- CPack * pack = c->retrievePack();
|
|
|
- if(state == EClientState::DISCONNECTING)
|
|
|
- {
|
|
|
- // FIXME: server shouldn't really send netpacks after it's tells client to disconnect
|
|
|
- // Though currently they'll be delivered and might cause crash.
|
|
|
- vstd::clear_pointer(pack);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- ServerHandlerCPackVisitor visitor(*this);
|
|
|
- pack->visit(visitor);
|
|
|
- }
|
|
|
- }
|
|
|
+ assert(0); //Should not be possible - socket must be closed at this point
|
|
|
+ return;
|
|
|
}
|
|
|
- //catch only asio exceptions
|
|
|
- catch(const boost::system::system_error & e)
|
|
|
+
|
|
|
+ CPack * pack = logicConnection->retrievePack(message);
|
|
|
+ ServerHandlerCPackVisitor visitor(*this);
|
|
|
+ pack->visit(visitor);
|
|
|
+}
|
|
|
+
|
|
|
+void CServerHandler::onDisconnected(const std::shared_ptr<INetworkConnection> & connection, const std::string & errorMessage)
|
|
|
+{
|
|
|
+ if(getState() == EClientState::DISCONNECTING)
|
|
|
{
|
|
|
- if(state == EClientState::DISCONNECTING)
|
|
|
- {
|
|
|
- logNetwork->info("Successfully closed connection to server, ending listening thread!");
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- if (e.code() == boost::asio::error::eof)
|
|
|
- logNetwork->error("Lost connection to server, ending listening thread! Connection has been closed");
|
|
|
- else
|
|
|
- logNetwork->error("Lost connection to server, ending listening thread! Reason: %s", e.what());
|
|
|
+ assert(networkConnection == nullptr);
|
|
|
+ // Note: this branch can be reached on app shutdown, when main thread holds mutex till destruction
|
|
|
+ logNetwork->info("Successfully closed connection to server!");
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
- if(client)
|
|
|
- {
|
|
|
- state = EClientState::DISCONNECTING;
|
|
|
-
|
|
|
- GH.dispatchMainThread([]()
|
|
|
- {
|
|
|
- CSH->endGameplay();
|
|
|
- GH.defActionsDef = 63;
|
|
|
- CMM->menu->switchToTab("main");
|
|
|
- });
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- auto lcd = new LobbyClientDisconnected();
|
|
|
- lcd->clientId = c->connectionID;
|
|
|
- boost::unique_lock<boost::recursive_mutex> lock(*mx);
|
|
|
- packsForLobbyScreen.push_back(lcd);
|
|
|
- }
|
|
|
- }
|
|
|
+ boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
+
|
|
|
+ logNetwork->error("Lost connection to server! Connection has been closed");
|
|
|
+
|
|
|
+ if(client)
|
|
|
+ {
|
|
|
+ CSH->endGameplay();
|
|
|
+ GH.defActionsDef = 63;
|
|
|
+ CMM->menu->switchToTab("main");
|
|
|
+ CSH->showServerError(CGI->generaltexth->translate("vcmi.server.errors.disconnected"));
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ LobbyClientDisconnected lcd;
|
|
|
+ lcd.clientId = logicConnection->connectionID;
|
|
|
+ applyPackOnLobbyScreen(lcd);
|
|
|
}
|
|
|
+
|
|
|
+ networkConnection.reset();
|
|
|
}
|
|
|
|
|
|
void CServerHandler::visitForLobby(CPackForLobby & lobbyPack)
|
|
|
{
|
|
|
- if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, &lobbyPack))
|
|
|
+ if(applier->getApplier(CTypeList::getInstance().getTypeID(&lobbyPack))->applyOnLobbyHandler(this, lobbyPack))
|
|
|
{
|
|
|
if(!settings["session"]["headless"].Bool())
|
|
|
- {
|
|
|
- boost::unique_lock<boost::recursive_mutex> lock(*mx);
|
|
|
- packsForLobbyScreen.push_back(&lobbyPack);
|
|
|
- }
|
|
|
+ applyPackOnLobbyScreen(lobbyPack);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
@@ -992,22 +942,16 @@ void CServerHandler::visitForClient(CPackForClient & clientPack)
|
|
|
client->handlePack(&clientPack);
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::threadRunServer()
|
|
|
+void CServerHandler::threadRunServer(bool connectToLobby)
|
|
|
{
|
|
|
#if !defined(VCMI_MOBILE)
|
|
|
setThreadName("runServer");
|
|
|
const std::string logName = (VCMIDirs::get().userLogsPath() / "server_log.txt").string();
|
|
|
std::string comm = VCMIDirs::get().serverPath().string()
|
|
|
- + " --port=" + std::to_string(getHostPort())
|
|
|
- + " --run-by-client"
|
|
|
- + " --uuid=" + uuid;
|
|
|
- if(settings["session"]["lobby"].Bool() && settings["session"]["host"].Bool())
|
|
|
- {
|
|
|
- comm += " --lobby=" + settings["session"]["address"].String();
|
|
|
- comm += " --connections=" + settings["session"]["hostConnections"].String();
|
|
|
- comm += " --lobby-port=" + std::to_string(settings["session"]["port"].Integer());
|
|
|
- comm += " --lobby-uuid=" + settings["session"]["hostUuid"].String();
|
|
|
- }
|
|
|
+ + " --port=" + std::to_string(getLocalPort())
|
|
|
+ + " --run-by-client";
|
|
|
+ if(connectToLobby)
|
|
|
+ comm += " --lobby";
|
|
|
|
|
|
comm += " > \"" + logName + '\"';
|
|
|
logGlobal->info("Server command line: %s", comm);
|
|
|
@@ -1035,22 +979,31 @@ void CServerHandler::threadRunServer()
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
+ boost::mutex::scoped_lock interfaceLock(GH.interfaceMutex);
|
|
|
+
|
|
|
+ if (getState() == EClientState::CONNECTING)
|
|
|
+ {
|
|
|
+ showServerError(CGI->generaltexth->translate("vcmi.server.errors.existingProcess"));
|
|
|
+ setState(EClientState::CONNECTION_CANCELLED); // stop attempts to reconnect
|
|
|
+ }
|
|
|
logNetwork->error("Error: server failed to close correctly or crashed!");
|
|
|
logNetwork->error("Check %s for more info", logName);
|
|
|
}
|
|
|
- onServerFinished();
|
|
|
#endif
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::onServerFinished()
|
|
|
+void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
|
|
|
{
|
|
|
- threadRunLocalServer.reset();
|
|
|
- if (CSH)
|
|
|
- CSH->campaignServerRestartLock.setn(false);
|
|
|
+ if(getState() != EClientState::STARTING)
|
|
|
+ logicConnection->sendPack(&pack);
|
|
|
}
|
|
|
|
|
|
-void CServerHandler::sendLobbyPack(const CPackForLobby & pack) const
|
|
|
+bool CServerHandler::inLobbyRoom() const
|
|
|
+{
|
|
|
+ return CSH->serverMode == EServerMode::LOBBY_HOST || CSH->serverMode == EServerMode::LOBBY_GUEST;
|
|
|
+}
|
|
|
+
|
|
|
+bool CServerHandler::inGame() const
|
|
|
{
|
|
|
- if(state != EClientState::STARTING)
|
|
|
- c->sendPack(&pack);
|
|
|
+ return logicConnection != nullptr;
|
|
|
}
|