1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-23 22:37:55 +02:00

Merge pull request #1000 from vcmi/multiplayer

Multiplayer
This commit is contained in:
Andrii Danylchenko
2022-10-03 20:16:39 +03:00
committed by GitHub
14 changed files with 164 additions and 58 deletions

View File

@@ -1367,7 +1367,7 @@ static void handleEvent(SDL_Event & ev)
break;
case EUserEvent::RESTART_GAME:
{
CSH->sendStartGame();
CSH->sendRestartGame();
}
break;
case EUserEvent::CAMPAIGN_START_SCENARIO:

View File

@@ -512,6 +512,14 @@ void CServerHandler::sendGuiAction(ui8 action) const
sendLobbyPack(lga);
}
void CServerHandler::sendRestartGame() const
{
LobbyEndGame endGame;
endGame.closeConnection = false;
endGame.restart = true;
sendLobbyPack(endGame);
}
void CServerHandler::sendStartGame(bool allowOnlyAI) const
{
verifyStateBeforeStart(allowOnlyAI ? true : settings["session"]["onlyai"].Bool());
@@ -524,9 +532,11 @@ void CServerHandler::sendStartGame(bool allowOnlyAI) const
* si = * lsg.initializedStartInfo;
}
sendLobbyPack(lsg);
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
}
void CServerHandler::startGameplay()
void CServerHandler::startGameplay(CGameState * gameState)
{
if(CMM)
CMM->disable();
@@ -535,13 +545,13 @@ void CServerHandler::startGameplay()
switch(si->mode)
{
case StartInfo::NEW_GAME:
client->newGame();
client->newGame(gameState);
break;
case StartInfo::CAMPAIGN:
client->newGame();
client->newGame(gameState);
break;
case StartInfo::LOAD_GAME:
client->loadGame();
client->loadGame(gameState);
break;
default:
throw std::runtime_error("Invalid mode");
@@ -576,6 +586,9 @@ void CServerHandler::endGameplay(bool closeConnection, bool restart)
GH.curInt = CMainMenu::create().get();
}
}
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
}
void CServerHandler::startCampaignScenario(std::shared_ptr<CCampaignState> cs)

View File

@@ -21,6 +21,7 @@ class PlayerColor;
struct StartInfo;
class CMapInfo;
class CGameState;
struct ClientPlayer;
struct CPack;
struct CPackForLobby;
@@ -69,6 +70,7 @@ public:
virtual void sendMessage(const std::string & txt) const = 0;
virtual void sendGuiAction(ui8 action) const = 0; // TODO: possibly get rid of it?
virtual void sendStartGame(bool allowOnlyAI = false) const = 0;
virtual void sendRestartGame() const = 0;
};
/// structure to handle running server and connecting to it
@@ -142,9 +144,10 @@ public:
void setTurnLength(int npos) const override;
void sendMessage(const std::string & txt) const override;
void sendGuiAction(ui8 action) const override;
void sendRestartGame() const override;
void sendStartGame(bool allowOnlyAI = false) const override;
void startGameplay();
void startGameplay(CGameState * gameState = nullptr);
void endGameplay(bool closeConnection = true, bool restart = false);
void startCampaignScenario(std::shared_ptr<CCampaignState> cs = {});
void showServerError(std::string txt);

View File

@@ -180,14 +180,15 @@ events::EventBus * CClient::eventBus() const
return clientEventBus.get();
}
void CClient::newGame()
void CClient::newGame(CGameState * initializedGameState)
{
CSH->th->update();
CMapService mapService;
gs = new CGameState();
gs = initializedGameState ? initializedGameState : new CGameState();
gs->preInit(VLC);
logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool());
if(!initializedGameState)
gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool());
logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff());
initMapHandler();
@@ -196,53 +197,64 @@ void CClient::newGame()
initPlayerInterfaces();
}
void CClient::loadGame()
void CClient::loadGame(CGameState * initializedGameState)
{
logNetwork->info("Loading procedure started!");
std::unique_ptr<CLoadFile> loader;
try
if(initializedGameState)
{
boost::filesystem::path clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
boost::filesystem::path controlServerSaveName;
if(CResourceHandler::get("local")->existsResource(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME)))
{
controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME));
}
else // create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
{
controlServerSaveName = boost::filesystem::path(clientSaveName).replace_extension(".vsgm1");
CResourceHandler::get("local")->createResource(controlServerSaveName.string(), true);
}
if(clientSaveName.empty())
throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
if(controlServerSaveName.empty() || !boost::filesystem::exists(controlServerSaveName))
throw std::runtime_error("Cannot open server part of " + CSH->si->mapname);
{
CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, MINIMAL_SERIALIZATION_VERSION);
loadCommonState(checkingLoader);
loader = checkingLoader.decay();
}
logNetwork->info("Game state was transferred over network, loading.");
gs = initializedGameState;
}
catch(std::exception & e)
else
{
logGlobal->error("Cannot load game %s. Error: %s", CSH->si->mapname, e.what());
throw; //obviously we cannot continue here
try
{
boost::filesystem::path clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::CLIENT_SAVEGAME));
boost::filesystem::path controlServerSaveName;
if(CResourceHandler::get("local")->existsResource(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME)))
{
controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(CSH->si->mapname, EResType::SERVER_SAVEGAME));
}
else // create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
{
controlServerSaveName = boost::filesystem::path(clientSaveName).replace_extension(".vsgm1");
CResourceHandler::get("local")->createResource(controlServerSaveName.string(), true);
}
if(clientSaveName.empty())
throw std::runtime_error("Cannot open client part of " + CSH->si->mapname);
if(controlServerSaveName.empty() || !boost::filesystem::exists(controlServerSaveName))
throw std::runtime_error("Cannot open server part of " + CSH->si->mapname);
{
CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, MINIMAL_SERIALIZATION_VERSION);
loadCommonState(checkingLoader);
loader = checkingLoader.decay();
}
}
catch(std::exception & e)
{
logGlobal->error("Cannot load game %s. Error: %s", CSH->si->mapname, e.what());
throw; //obviously we cannot continue here
}
logNetwork->trace("Loaded common part of save %d ms", CSH->th->getDiff());
}
logNetwork->trace("Loaded common part of save %d ms", CSH->th->getDiff());
gs->preInit(VLC);
gs->updateOnLoad(CSH->si.get());
logNetwork->info("Game loaded, initialize interfaces.");
initMapHandler();
reinitScripting();
initPlayerEnvironments();
serialize(loader->serializer, loader->serializer.fileVersion);
if(loader)
serialize(loader->serializer, loader->serializer.fileVersion);
initPlayerInterfaces();
}

View File

@@ -150,8 +150,8 @@ public:
vstd::CLoggerBase * logger() const override;
events::EventBus * eventBus() const override;
void newGame();
void loadGame();
void newGame(CGameState * gameState);
void loadGame(CGameState * gameState);
void serialize(BinarySerializer & h, const int version);
void serialize(BinaryDeserializer & h, const int version);

View File

@@ -57,7 +57,8 @@ bool LobbyClientDisconnected::applyOnLobbyHandler(CServerHandler * handler)
void LobbyClientDisconnected::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
GH.popInts(1);
if(GH.listInt.size())
GH.popInts(1);
}
void LobbyChatMessage::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
@@ -93,25 +94,34 @@ void LobbyGuiAction::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * h
}
}
bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
bool LobbyEndGame::applyOnLobbyHandler(CServerHandler * handler)
{
if(handler->state == EClientState::GAMEPLAY)
{
handler->endGameplay(false, true);
handler->endGameplay(closeConnection, restart);
}
if(restart)
handler->sendStartGame();
return true;
}
bool LobbyStartGame::applyOnLobbyHandler(CServerHandler * handler)
{
handler->state = EClientState::STARTING;
if(handler->si->mode != StartInfo::LOAD_GAME)
{
handler->si = initializedStartInfo;
}
if(settings["session"]["headless"].Bool())
handler->startGameplay();
handler->startGameplay(initializedGameState);
return true;
}
void LobbyStartGame::applyOnLobbyScreen(CLobbyScreen * lobby, CServerHandler * handler)
{
GH.pushIntT<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, handler));
GH.pushIntT<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, handler, initializedGameState));
}
bool LobbyUpdateState::applyOnLobbyHandler(CServerHandler * handler)

View File

@@ -859,9 +859,6 @@ PlayerColor CStackInstance::getOwner() const
void CStackInstance::deserializationFix()
{
const CCreature *backup = type;
type = nullptr;
setType(backup);
const CArmedInstance *armyBackup = _armyObj;
_armyObj = nullptr;
setArmyObj(armyBackup);

View File

@@ -12,6 +12,7 @@
#include "HeroBonus.h"
#include "GameConstants.h"
#include "CArtHandler.h"
#include "CCreatureHandler.h"
VCMI_LIB_NAMESPACE_BEGIN
@@ -40,7 +41,20 @@ public:
template <typename Handler> void serialize(Handler &h, const int version)
{
h & type;
if(h.saving)
{
CreatureID idNumber = type ? type->idNumber : CreatureID(CreatureID::NONE);
h & idNumber;
}
else
{
CreatureID idNumber;
h & idNumber;
if(idNumber != CreatureID::NONE)
setType(VLC->creh->objects[idNumber]);
else
type = nullptr;
}
h & count;
}

View File

@@ -138,12 +138,29 @@ struct LobbyGuiAction : public CLobbyPackToPropagate
}
};
struct LobbyEndGame : public CLobbyPackToPropagate
{
bool closeConnection = false, restart = false;
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnServer(CVCMIServer * srv);
void applyOnServerAfterAnnounce(CVCMIServer * srv);
bool applyOnLobbyHandler(CServerHandler * handler);
template <typename Handler> void serialize(Handler &h, const int version)
{
h & closeConnection;
h & restart;
}
};
struct LobbyStartGame : public CLobbyPackToPropagate
{
// Set by server
std::shared_ptr<StartInfo> initializedStartInfo;
CGameState * initializedGameState;
LobbyStartGame() : initializedStartInfo(nullptr) {}
LobbyStartGame() : initializedStartInfo(nullptr), initializedGameState(nullptr) {}
bool checkClientPermissions(CVCMIServer * srv) const;
bool applyOnServer(CVCMIServer * srv);
void applyOnServerAfterAnnounce(CVCMIServer * srv);
@@ -153,6 +170,10 @@ struct LobbyStartGame : public CLobbyPackToPropagate
template <typename Handler> void serialize(Handler &h, const int version)
{
h & initializedStartInfo;
bool sps = h.smartPointerSerialization;
h.smartPointerSerialization = true;
h & initializedGameState;
h.smartPointerSerialization = sps;
}
};

View File

@@ -330,7 +330,7 @@ public:
h & players;
h & howManyTeams;
h & allowedHeroes;
h & triggeredEvents;
//Do not serialize triggeredEvents in header as they can contain information about heroes and armies
h & victoryMessage;
h & victoryIconIndex;
h & defeatMessage;
@@ -424,6 +424,7 @@ public:
void serialize(Handler &h, const int formatVersion)
{
h & static_cast<CMapHeader&>(*this);
h & triggeredEvents; //from CMapHeader
h & rumors;
h & allowedSpell;
h & allowedAbilities;

View File

@@ -372,6 +372,7 @@ void registerTypesLobbyPacks(Serializer &s)
s.template registerType<CLobbyPackToPropagate, LobbyChatMessage>();
// Only host client send
s.template registerType<CLobbyPackToPropagate, LobbyGuiAction>();
s.template registerType<CLobbyPackToPropagate, LobbyEndGame>();
s.template registerType<CLobbyPackToPropagate, LobbyStartGame>();
s.template registerType<CLobbyPackToPropagate, LobbyChangeHost>();
// Only server send

View File

@@ -220,7 +220,7 @@ void CVCMIServer::threadAnnounceLobby()
}
}
bool CVCMIServer::prepareToStartGame()
void CVCMIServer::prepareToRestart()
{
if(state == EServerState::GAMEPLAY)
{
@@ -232,8 +232,18 @@ bool CVCMIServer::prepareToStartGame()
boost::this_thread::sleep(boost::posix_time::milliseconds(1000));
}
if(!gh)
gh = std::make_shared<CGameHandler>(this);
for(auto c : connections)
{
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
}
boost::unique_lock<boost::recursive_mutex> queueLock(mx);
gh = nullptr;
}
bool CVCMIServer::prepareToStartGame()
{
gh = std::make_shared<CGameHandler>(this);
switch(si->mode)
{
case StartInfo::CAMPAIGN:

View File

@@ -69,6 +69,7 @@ public:
~CVCMIServer();
void run();
bool prepareToStartGame();
void prepareToRestart();
void startGameImmidiately();
void startAsyncAccept();

View File

@@ -161,6 +161,27 @@ bool LobbyGuiAction::checkClientPermissions(CVCMIServer * srv) const
return srv->isClientHost(c->connectionID);
}
bool LobbyEndGame::checkClientPermissions(CVCMIServer * srv) const
{
return srv->isClientHost(c->connectionID);
}
bool LobbyEndGame::applyOnServer(CVCMIServer * srv)
{
srv->prepareToRestart();
return true;
}
void LobbyEndGame::applyOnServerAfterAnnounce(CVCMIServer * srv)
{
boost::unique_lock<boost::mutex> stateLock(srv->stateMutex);
for(auto & c : srv->connections)
{
c->enterLobbyConnectionMode();
c->disableStackSendingByID();
}
}
bool LobbyStartGame::checkClientPermissions(CVCMIServer * srv) const
{
return srv->isClientHost(c->connectionID);
@@ -181,6 +202,8 @@ bool LobbyStartGame::applyOnServer(CVCMIServer * srv)
return false;
initializedStartInfo = std::make_shared<StartInfo>(*srv->gh->getStartInfo(true));
initializedGameState = srv->gh->gameState();
return true;
}