Extracted interface through which GameHandler interacts with VCMIServer

This commit is contained in:
Ivan Savenko
2025-07-19 18:04:20 +03:00
parent 6b382a979a
commit 3d63284c51
11 changed files with 198 additions and 128 deletions
+1 -1
View File
@@ -144,7 +144,7 @@ void CClient::loadGame(std::shared_ptr<CGameState> initializedGameState)
logNetwork->info("Game state was transferred over network, loading.");
gamestate = initializedGameState;
gamestate->preInit(LIBRARY);
gamestate->updateOnLoad(GAME->server().si.get());
gamestate->updateOnLoad(*GAME->server().si);
logNetwork->info("Game loaded, initialize interfaces.");
initMapHandler();
+6 -6
View File
@@ -280,15 +280,15 @@ void CGameState::updateEntity(Metatype metatype, int32_t index, const JsonNode &
}
}
void CGameState::updateOnLoad(StartInfo * si)
void CGameState::updateOnLoad(const StartInfo & si)
{
assert(services);
scenarioOps->playerInfos = si->playerInfos;
for(auto & i : si->playerInfos)
scenarioOps->playerInfos = si.playerInfos;
for(auto & i : si.playerInfos)
players.at(i.first).human = i.second.isControlledByHuman();
scenarioOps->extraOptionsInfo = si->extraOptionsInfo;
scenarioOps->turnTimerInfo = si->turnTimerInfo;
scenarioOps->simturnsInfo = si->simturnsInfo;
scenarioOps->extraOptionsInfo = si.extraOptionsInfo;
scenarioOps->turnTimerInfo = si.turnTimerInfo;
scenarioOps->simturnsInfo = si.simturnsInfo;
}
void CGameState::initNewGame(const IMapService * mapService, vstd::RNG & randomGenerator, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
+1 -1
View File
@@ -75,7 +75,7 @@ public:
void preInit(Services * services);
void init(const IMapService * mapService, StartInfo * si, IGameRandomizer & gameRandomizer, Load::ProgressAccumulator &, bool allowSavingRandomMap = true);
void updateOnLoad(StartInfo * si);
void updateOnLoad(const StartInfo & si);
ui32 day; //total number of days in game
std::map<PlayerColor, PlayerState> players;
+38 -95
View File
@@ -128,9 +128,9 @@ events::EventBus * CGameHandler::eventBus() const
return serverEventBus.get();
}
CVCMIServer & CGameHandler::gameLobby() const
IGameServer & CGameHandler::gameServer() const
{
return *lobby;
return server;
}
void CGameHandler::levelUpHero(const CGHeroInstance * hero, SecondarySkill skill)
@@ -439,38 +439,36 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh
void CGameHandler::handleClientDisconnection(const std::shared_ptr<CConnection> & c)
{
if(gameLobby().getState() == EServerState::SHUTDOWN || !gameState().getStartInfo())
if(gameServer().getState() == EServerState::SHUTDOWN || !gameState().getStartInfo())
{
assert(0); // game should have shut down before reaching this point!
return;
}
for(auto & playerConnections : connections)
{
PlayerColor playerId = playerConnections.first;
auto playerConnection = vstd::find(playerConnections.second, c);
if(playerConnection == playerConnections.second.end())
std::vector<PlayerColor> disconnectedPlayers;
std::vector<PlayerColor> remainingPlayers;
// this player have left the game - broadcast infowindow to all in-game players
for (const auto & player : gameState().players)
{
if (gameInfo().getPlayerState(player.first)->status != EPlayerStatus::INGAME)
continue;
logGlobal->trace("Player %s disconnected. Notifying remaining players", playerId.toString());
if (gameServer().hasPlayerAt(player.first, c))
disconnectedPlayers.push_back(player.first);
else
remainingPlayers.push_back(player.first);
}
// this player have left the game - broadcast infowindow to all in-game players
for (const auto & player : gameState().players)
for (const auto & inGamePlayer : remainingPlayers)
{
for (const auto & lostPlayer : disconnectedPlayers)
{
if (player.first == playerId)
continue;
if (gameInfo().getPlayerState(player.first)->status != EPlayerStatus::INGAME)
continue;
logGlobal->trace("Notifying player %s", player.first);
InfoWindow out;
out.player = player.first;
out.player = inGamePlayer;
out.text.appendTextID("vcmi.server.errors.playerLeft");
out.text.replaceName(playerId);
out.components.emplace_back(ComponentType::FLAG, playerId);
out.text.replaceName(lostPlayer);
out.components.emplace_back(ComponentType::FLAG, lostPlayer);
sendAndApply(out);
}
}
@@ -526,8 +524,8 @@ void CGameHandler::handleReceivedPack(std::shared_ptr<CConnection> connection, C
}
}
CGameHandler::CGameHandler(CVCMIServer * lobby)
: lobby(lobby)
CGameHandler::CGameHandler(IGameServer & server)
: server(server)
, heroPool(std::make_unique<HeroPoolProcessor>(this))
, battles(std::make_unique<BattleProcessor>(this))
, queries(std::make_unique<QueriesProcessor>())
@@ -741,19 +739,6 @@ void CGameHandler::start(bool resume)
{
LOG_TRACE_PARAMS(logGlobal, "resume=%d", resume);
for (const auto & cc : gameLobby().activeConnections)
{
auto players = gameLobby().getAllClientPlayers(cc->connectionID);
std::stringstream sbuffer;
sbuffer << "Connection " << cc->connectionID << " will handle " << players.size() << " player: ";
for (PlayerColor color : players)
{
sbuffer << color << " ";
connections[color].insert(cc);
}
logGlobal->info(sbuffer.str());
}
#if SCRIPTING_ENABLED
services()->scripts()->run(serverScripts);
#endif
@@ -1516,8 +1501,7 @@ void CGameHandler::heroExchange(ObjectInstanceID hero1, ObjectInstanceID hero2)
void CGameHandler::sendToAllClients(const CPackForClient & pack)
{
logNetwork->trace("\tSending to all clients: %s", typeid(pack).name());
for (const auto & c : gameLobby().activeConnections)
c->sendPack(pack);
gameServer().broadcastPack(pack);
}
void CGameHandler::sendAndApply(CPackForClient & pack)
@@ -1625,64 +1609,23 @@ void CGameHandler::save(const std::string & filename)
}
}
bool CGameHandler::load(const std::string & filename)
void CGameHandler::load(const StartInfo &info)
{
logGlobal->info("Loading from %s", filename);
const auto stem = FileInfo::GetPathStem(filename);
logGlobal->info("Loading from %s", info.fileURI);
const auto stem = FileInfo::GetPathStem(info.fileURI);
reinitScripting();
try
{
CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourcePath(stem.to_string(), EResType::SAVEGAME)), gs.get());
gs = std::make_shared<CGameState>();
randomizer = std::make_unique<GameRandomizer>(*gs);
gs->loadGame(lf);
logGlobal->info("Loading server state");
lf.load(*this);
logGlobal->info("Game has been successfully loaded!");
}
catch(const ModIncompatibility & e)
{
logGlobal->error("Failed to load game: %s", e.what());
MetaString errorMsg;
if(!e.whatMissing().empty())
{
errorMsg.appendTextID("vcmi.server.errors.modsToEnable");
errorMsg.appendRawString("\n");
errorMsg.appendRawString(e.whatMissing());
}
if(!e.whatExcessive().empty())
{
errorMsg.appendTextID("vcmi.server.errors.modsToDisable");
errorMsg.appendRawString("\n");
errorMsg.appendRawString(e.whatExcessive());
}
gameLobby().announceMessage(errorMsg);
return false;
}
catch(const IdentifierResolutionException & e)
{
logGlobal->error("Failed to load game: %s", e.what());
MetaString errorMsg;
errorMsg.appendTextID("vcmi.server.errors.unknownEntity");
errorMsg.replaceRawString(e.identifierName);
gameLobby().announceMessage(errorMsg);
return false;
}
CLoadFile lf(*CResourceHandler::get()->getResourceName(ResourcePath(stem.to_string(), EResType::SAVEGAME)), gs.get());
gs = std::make_shared<CGameState>();
randomizer = std::make_unique<GameRandomizer>(*gs);
gs->loadGame(lf);
logGlobal->info("Loading server state");
lf.load(*this);
logGlobal->info("Game has been successfully loaded!");
catch(const std::exception & e)
{
logGlobal->error("Failed to load game: %s", e.what());
auto str = MetaString::createFromTextID("vcmi.broadcast.failedLoadGame");
str.appendRawString(": ");
str.appendRawString(e.what());
gameLobby().announceMessage(str);
return false;
}
gs->preInit(LIBRARY);
gs->updateOnLoad(gameLobby().si.get());
return true;
gs->updateOnLoad(info);
}
bool CGameHandler::bulkSplitStack(SlotID slotSrc, ObjectInstanceID srcOwner, si32 howMany)
@@ -2097,12 +2040,12 @@ bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8
bool CGameHandler::hasPlayerAt(PlayerColor player, const std::shared_ptr<CConnection> & c) const
{
return connections.count(player) && connections.at(player).count(c);
return gameServer().hasPlayerAt(player, c);
}
bool CGameHandler::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
{
return connections.count(left) && connections.count(right) && connections.at(left) == connections.at(right);
return gameServer().hasBothPlayersAtSameConnection(left, right);
}
bool CGameHandler::disbandCreature(ObjectInstanceID id, SlotID pos)
@@ -3589,7 +3532,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
if(p->human)
{
gameLobby().setState(EServerState::SHUTDOWN);
gameServer().setState(EServerState::SHUTDOWN);
}
}
else
+5 -6
View File
@@ -57,10 +57,11 @@ class TurnTimerHandler;
class QueriesProcessor;
class CObjectVisitQuery;
class NewTurnProcessor;
class IGameServer;
class CGameHandler : public Environment, public IGameEventCallback
{
CVCMIServer * lobby;
IGameServer & server;
public:
std::unique_ptr<HeroPoolProcessor> heroPool;
@@ -81,8 +82,6 @@ public:
std::unique_ptr<PlayerMessageProcessor> playerMessages;
std::map<PlayerColor, std::set<std::shared_ptr<CConnection>>> connections; //player color -> connection to client with interface of that player
//queries stuff
QueryID QID;
@@ -92,7 +91,7 @@ public:
const GameCb * game() const override;
vstd::CLoggerBase * logger() const override;
events::EventBus * eventBus() const override;
CVCMIServer & gameLobby() const;
IGameServer & gameServer() const;
ServerCallback * spellcastEnvironment() const;
bool isBlockedByQueries(const CPackForServer *pack, PlayerColor player);
@@ -110,7 +109,7 @@ public:
void createHole(const int3 & visitablePosition, PlayerColor initiator);
void newObject(std::shared_ptr<CGObjectInstance> object, PlayerColor initiator);
explicit CGameHandler(CVCMIServer * lobby);
explicit CGameHandler(IGameServer & server);
~CGameHandler();
//////////////////////////////////////////////////////////////////////////
@@ -237,7 +236,7 @@ public:
bool bulkMergeStacks(SlotID slotSrc, ObjectInstanceID srcOwner);
bool bulkSplitAndRebalanceStack(SlotID slotSrc, ObjectInstanceID srcOwner);
void save(const std::string &fname);
bool load(const std::string &fname);
void load(const StartInfo &info);
void onPlayerTurnStarted(PlayerColor which);
void onPlayerTurnEnded(PlayerColor which);
+1
View File
@@ -47,6 +47,7 @@ set(vcmiservercommon_HEADERS
CGameHandler.h
GlobalLobbyProcessor.h
IGameServer.h
ServerSpellCastEnvironment.h
CVCMIServer.h
LobbyNetPackVisitors.h
+89 -2
View File
@@ -23,6 +23,7 @@
#include "../lib/gameState/CGameState.h"
#include "../lib/mapping/CMapInfo.h"
#include "../lib/mapping/CMapHeader.h"
#include "../lib/modding/ModIncompatibility.h"
#include "../lib/rmg/CMapGenOptions.h"
#include "../lib/serializer/CMemorySerializer.h"
#include "../lib/serializer/Connection.h"
@@ -250,7 +251,7 @@ bool CVCMIServer::prepareToStartGame()
}
});
gh = std::make_shared<CGameHandler>(this);
gh = std::make_shared<CGameHandler>(*this);
switch(si->mode)
{
case EStartMode::CAMPAIGN:
@@ -271,7 +272,7 @@ bool CVCMIServer::prepareToStartGame()
case EStartMode::LOAD_GAME:
logNetwork->info("Preparing to start loaded game");
if(!gh->load(si->mapname))
if(!loadSavedGame(*si))
{
current.finish();
progressTrackingThread.join();
@@ -295,6 +296,17 @@ void CVCMIServer::startGameImmediately()
for(auto activeConnection : activeConnections)
activeConnection->setCallback(gh->gameInfo());
for(auto activeConnection : activeConnections)
{
auto players = getAllClientPlayers(activeConnection->connectionID);
std::stringstream sbuffer;
sbuffer << "Connection " << activeConnection->connectionID << " will handle " << players.size() << " player: ";
for (PlayerColor color : players)
sbuffer << color << " ";
logGlobal->info(sbuffer.str());
}
gh->start(si->mode == EStartMode::LOAD_GAME);
setState(EServerState::GAMEPLAY);
lastTimerUpdateTime = gameplayStartTime = std::chrono::steady_clock::now();
@@ -1077,3 +1089,78 @@ INetworkServer & CVCMIServer::getNetworkServer()
{
return *networkServer;
}
bool CVCMIServer::loadSavedGame(const StartInfo &info)
{
try
{
gh->load(info);
}
catch(const ModIncompatibility & e)
{
logGlobal->error("Failed to load game: %s", e.what());
MetaString errorMsg;
if(!e.whatMissing().empty())
{
errorMsg.appendTextID("vcmi.server.errors.modsToEnable");
errorMsg.appendRawString("\n");
errorMsg.appendRawString(e.whatMissing());
}
if(!e.whatExcessive().empty())
{
errorMsg.appendTextID("vcmi.server.errors.modsToDisable");
errorMsg.appendRawString("\n");
errorMsg.appendRawString(e.whatExcessive());
}
announceMessage(errorMsg);
return false;
}
catch(const IdentifierResolutionException & e)
{
logGlobal->error("Failed to load game: %s", e.what());
MetaString errorMsg;
errorMsg.appendTextID("vcmi.server.errors.unknownEntity");
errorMsg.replaceRawString(e.identifierName);
announceMessage(errorMsg);
return false;
}
catch(const std::exception & e)
{
logGlobal->error("Failed to load game: %s", e.what());
auto str = MetaString::createFromTextID("vcmi.broadcast.failedLoadGame");
str.appendRawString(": ");
str.appendRawString(e.what());
announceMessage(str);
return false;
}
return true;
}
bool CVCMIServer::isPlayerHost(const PlayerColor & color) const
{
return LobbyInfo::isPlayerHost(color);
}
bool CVCMIServer::hasPlayerAt(PlayerColor player, const std::shared_ptr<CConnection> & c) const
{
return vstd::contains(getAllClientPlayers(c->connectionID), player);
}
bool CVCMIServer::hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const
{
for (const auto & c : activeConnections)
{
if (hasPlayerAt(left, c) && hasPlayerAt(right, c))
return true;
}
return false;
}
void CVCMIServer::broadcastPack(const CPackForClient & pack)
{
for (const auto & c : activeConnections)
c->sendPack(pack);
}
+14 -11
View File
@@ -9,6 +9,8 @@
*/
#pragma once
#include "IGameServer.h"
#include "../lib/network/NetworkInterface.h"
#include "../lib/StartInfo.h"
@@ -32,14 +34,7 @@ class CBaseForServerApply;
class CBaseForGHApply;
class GlobalLobbyProcessor;
enum class EServerState : ui8
{
LOBBY,
GAMEPLAY,
SHUTDOWN
};
class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkTimerListener
class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INetworkTimerListener, public IGameServer
{
/// Network server instance that receives and processes incoming connections on active socket
std::unique_ptr<INetworkServer> networkServer;
@@ -59,7 +54,18 @@ class CVCMIServer : public LobbyInfo, public INetworkServerListener, public INet
uint16_t port;
bool runByClient;
bool loadSavedGame(const StartInfo &info);
public:
// IGameServer impl
void setState(EServerState value) override;
EServerState getState() const override;
bool isPlayerHost(const PlayerColor & color) const override;
bool hasPlayerAt(PlayerColor player, const std::shared_ptr<CConnection> & c) const override;
bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const override;
void broadcastPack(const CPackForClient & pack) override;
/// List of all active connections
std::vector<std::shared_ptr<CConnection>> activeConnections;
@@ -109,9 +115,6 @@ public:
INetworkHandler & getNetworkHandler();
INetworkServer & getNetworkServer();
void setState(EServerState value);
EServerState getState() const;
// Work with LobbyInfo
void setPlayer(PlayerColor clickedColor);
void setPlayerName(PlayerColor player, const std::string & name);
+37
View File
@@ -0,0 +1,37 @@
/*
* IGameServer.h, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
#pragma once
VCMI_LIB_NAMESPACE_BEGIN
class PlayerColor;
class CConnection;
struct CPackForClient;
VCMI_LIB_NAMESPACE_END
enum class EServerState : ui8
{
LOBBY,
GAMEPLAY,
SHUTDOWN
};
/// Interface through which GameHandler can interact with server that controls it
class IGameServer
{
public:
virtual ~IGameServer() = default;
virtual void setState(EServerState value) = 0;
virtual EServerState getState() const = 0;
virtual bool isPlayerHost(const PlayerColor & color) const = 0;
virtual bool hasPlayerAt(PlayerColor player, const std::shared_ptr<CConnection> & c) const = 0;
virtual bool hasBothPlayersAtSameConnection(PlayerColor left, PlayerColor right) const = 0;
virtual void broadcastPack(const CPackForClient & pack) = 0;
};
+5 -5
View File
@@ -70,17 +70,17 @@ void PlayerMessageProcessor::playerMessage(PlayerColor player, const std::string
void PlayerMessageProcessor::commandExit(PlayerColor player, const std::vector<std::string> & words)
{
bool isHost = gameHandler->gameLobby().isPlayerHost(player);
bool isHost = gameHandler->gameServer().isPlayerHost(player);
if(!isHost)
return;
broadcastSystemMessage(MetaString::createFromTextID("vcmi.broadcast.gameTerminated"));
gameHandler->gameLobby().setState(EServerState::SHUTDOWN);
gameHandler->gameServer().setState(EServerState::SHUTDOWN);
}
void PlayerMessageProcessor::commandKick(PlayerColor player, const std::vector<std::string> & words)
{
bool isHost = gameHandler->gameLobby().isPlayerHost(player);
bool isHost = gameHandler->gameServer().isPlayerHost(player);
if(!isHost)
return;
@@ -112,7 +112,7 @@ void PlayerMessageProcessor::commandKick(PlayerColor player, const std::vector<s
void PlayerMessageProcessor::commandSave(PlayerColor player, const std::vector<std::string> & words)
{
bool isHost = gameHandler->gameLobby().isPlayerHost(player);
bool isHost = gameHandler->gameServer().isPlayerHost(player);
if(!isHost)
return;
@@ -147,7 +147,7 @@ void PlayerMessageProcessor::commandCheaters(PlayerColor player, const std::vect
void PlayerMessageProcessor::commandStatistic(PlayerColor player, const std::vector<std::string> & words)
{
bool isHost = gameHandler->gameLobby().isPlayerHost(player);
bool isHost = gameHandler->gameServer().isPlayerHost(player);
if(!isHost)
return;
+1 -1
View File
@@ -260,7 +260,7 @@ void TurnOrderProcessor::doStartNewDay()
if(!activePlayer)
{
gameHandler->gameLobby().setState(EServerState::SHUTDOWN);
gameHandler->gameServer().setState(EServerState::SHUTDOWN);
return;
}