Преглед изворни кода

Gamestate is now passed as shared pointer

Ivan Savenko пре 8 месеци
родитељ
комит
6d65641a43

+ 5 - 8
CCallback.cpp

@@ -365,11 +365,10 @@ void CCallback::buildBoat( const IShipyard *obj )
 	sendRequest(bb);
 }
 
-CCallback::CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C)
+CCallback::CCallback(std::shared_ptr<CGameState> gamestate, std::optional<PlayerColor> Player, CClient * C)
 	: CBattleCallback(Player, C)
+	, gamestate(gamestate)
 {
-	gs = GS;
-
 	waitTillRealize = false;
 	unlockGsWhenWaiting = false;
 }
@@ -379,7 +378,7 @@ CCallback::~CCallback() = default;
 bool CCallback::canMoveBetween(const int3 &a, const int3 &b)
 {
 	//bidirectional
-	return gs->getMap().canMoveBetween(a, b);
+	return gameState()->getMap().canMoveBetween(a, b);
 }
 
 std::optional<PlayerColor> CCallback::getPlayerID() const
@@ -389,10 +388,10 @@ std::optional<PlayerColor> CCallback::getPlayerID() const
 
 int3 CCallback::getGuardingCreaturePosition(int3 tile)
 {
-	if (!gs->getMap().isInTheMap(tile))
+	if (!gameState()->getMap().isInTheMap(tile))
 		return int3(-1,-1,-1);
 
-	return gs->getMap().guardingCreaturePositions[tile.z][tile.x][tile.y];
+	return gameState()->getMap().guardingCreaturePositions[tile.z][tile.x][tile.y];
 }
 
 void CCallback::dig( const CGObjectInstance *hero )
@@ -437,7 +436,6 @@ CBattleCallback::CBattleCallback(std::optional<PlayerColor> player, CClient * C)
 
 void CBattleCallback::battleMakeUnitAction(const BattleID & battleID, const BattleAction & action)
 {
-	assert(!cl->gs->getBattle(battleID)->tacticDistance);
 	MakeAction ma;
 	ma.ba = action;
 	ma.battleID = battleID;
@@ -446,7 +444,6 @@ void CBattleCallback::battleMakeUnitAction(const BattleID & battleID, const Batt
 
 void CBattleCallback::battleMakeTacticAction(const BattleID & battleID, const BattleAction & action )
 {
-	assert(cl->gs->getBattle(battleID)->tacticDistance);
 	MakeAction ma;
 	ma.ba = action;
 	ma.battleID = battleID;

+ 6 - 1
CCallback.h

@@ -150,8 +150,13 @@ public:
 
 class CCallback : public CPlayerSpecificInfoCallback, public CBattleCallback, public IGameActionCallback
 {
+	std::shared_ptr<CGameState> gamestate;
+
+	CGameState * gameState() final { return gamestate.get(); }
+	const CGameState * gameState() const final { return gamestate.get(); }
+
 public:
-	CCallback(CGameState * GS, std::optional<PlayerColor> Player, CClient * C);
+	CCallback(std::shared_ptr<CGameState> gamestate, std::optional<PlayerColor> Player, CClient * C);
 	virtual ~CCallback();
 
 	//client-specific functionalities (pathfinding)

+ 1 - 1
client/CServerHandler.cpp

@@ -606,7 +606,7 @@ void CServerHandler::startMapAfterConnection(std::shared_ptr<CMapInfo> to)
 	mapToStart = to;
 }
 
-void CServerHandler::startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState)
+void CServerHandler::startGameplay(std::shared_ptr<CGameState> gameState)
 {
 	if(GAME->mainmenu())
 		GAME->mainmenu()->disable();

+ 1 - 1
client/CServerHandler.h

@@ -200,7 +200,7 @@ public:
 	bool validateGameStart(bool allowOnlyAI = false) const;
 	void debugStartTest(std::string filename, bool save = false);
 
-	void startGameplay(VCMI_LIB_WRAP_NAMESPACE(CGameState) * gameState = nullptr);
+	void startGameplay(std::shared_ptr<CGameState> gameState);
 	void showHighScoresAndEndGameplay(PlayerColor player, bool victory, const StatisticDataSet & statistic);
 	void endNetwork();
 	void endGameplay();

+ 22 - 30
client/Client.cpp

@@ -83,7 +83,6 @@ const CPlayerEnvironment::GameCb * CPlayerEnvironment::game() const
 CClient::CClient()
 {
 	waitingRequest.clear();
-	gs = nullptr;
 }
 
 CClient::~CClient() = default;
@@ -113,20 +112,14 @@ events::EventBus * CClient::eventBus() const
 	return clientEventBus.get();
 }
 
-void CClient::newGame(CGameState * initializedGameState)
+void CClient::newGame(std::shared_ptr<CGameState> initializedGameState)
 {
 	GAME->server().th->update();
 	CMapService mapService;
 	assert(initializedGameState);
-	gs = initializedGameState;
-	gs->preInit(LIBRARY, this);
+	gamestate = initializedGameState;
+	gamestate->preInit(LIBRARY, this);
 	logNetwork->trace("\tCreating gamestate: %i", GAME->server().th->getDiff());
-	if(!initializedGameState)
-	{
-		Load::ProgressAccumulator progressTracking;
-		gs->init(&mapService, GAME->server().si.get(), progressTracking, settings["general"]["saveRandomMaps"].Bool());
-	}
-	logNetwork->trace("Initializing GameState (together): %d ms", GAME->server().th->getDiff());
 
 	initMapHandler();
 	reinitScripting();
@@ -134,15 +127,14 @@ void CClient::newGame(CGameState * initializedGameState)
 	initPlayerInterfaces();
 }
 
-void CClient::loadGame(CGameState * initializedGameState)
+void CClient::loadGame(std::shared_ptr<CGameState> initializedGameState)
 {
 	logNetwork->info("Loading procedure started!");
 
 	logNetwork->info("Game state was transferred over network, loading.");
-	gs = initializedGameState;
-
-	gs->preInit(LIBRARY, this);
-	gs->updateOnLoad(GAME->server().si.get());
+	gamestate = initializedGameState;
+	gamestate->preInit(LIBRARY, this);
+	gamestate->updateOnLoad(GAME->server().si.get());
 	logNetwork->info("Game loaded, initialize interfaces.");
 
 	initMapHandler();
@@ -155,7 +147,7 @@ void CClient::loadGame(CGameState * initializedGameState)
 
 void CClient::save(const std::string & fname)
 {
-	if(!gs->currentBattles.empty())
+	if(!gameState()->currentBattles.empty())
 	{
 		logNetwork->error("Game cannot be saved during battle!");
 		return;
@@ -199,7 +191,7 @@ void CClient::endGame()
 	removeGUI();
 
 	GAME->setMapInstance(nullptr);
-	vstd::clear_pointer(gs);
+	gamestate.reset();
 
 	logNetwork->info("Deleted mapHandler and gameState.");
 
@@ -219,7 +211,7 @@ void CClient::initMapHandler()
 	// During loading CPlayerInterface from serialized state it's depend on MH
 	if(!settings["session"]["headless"].Bool())
 	{
-		GAME->setMapInstance(std::make_unique<CMapHandler>(&gs->getMap()));
+		GAME->setMapInstance(std::make_unique<CMapHandler>(&gameState()->getMap()));
 		logNetwork->trace("Creating mapHandler: %d ms", GAME->server().th->getDiff());
 	}
 }
@@ -233,9 +225,9 @@ void CClient::initPlayerEnvironments()
 	for(auto & color : allPlayers)
 	{
 		logNetwork->info("Preparing environment for player %s", color.toString());
-		playerEnvironments[color] = std::make_shared<CPlayerEnvironment>(color, this, std::make_shared<CCallback>(gs, color, this));
+		playerEnvironments[color] = std::make_shared<CPlayerEnvironment>(color, this, std::make_shared<CCallback>(gamestate, color, this));
 		
-		if(!hasHumanPlayer && gs->players[color].isHuman())
+		if(!hasHumanPlayer && gameState()->players[color].isHuman())
 			hasHumanPlayer = true;
 	}
 
@@ -249,13 +241,13 @@ void CClient::initPlayerEnvironments()
 	
 	if(settings["session"]["spectate"].Bool())
 	{
-		playerEnvironments[PlayerColor::SPECTATOR] = std::make_shared<CPlayerEnvironment>(PlayerColor::SPECTATOR, this, std::make_shared<CCallback>(gs, std::nullopt, this));
+		playerEnvironments[PlayerColor::SPECTATOR] = std::make_shared<CPlayerEnvironment>(PlayerColor::SPECTATOR, this, std::make_shared<CCallback>(gamestate, std::nullopt, this));
 	}
 }
 
 void CClient::initPlayerInterfaces()
 {
-	for(auto & playerInfo : gs->getStartInfo()->playerInfos)
+	for(auto & playerInfo : gameState()->getStartInfo()->playerInfos)
 	{
 		PlayerColor color = playerInfo.first;
 		if(!vstd::contains(GAME->server().getAllClientPlayers(GAME->server().logicConnection->connectionID), color))
@@ -267,8 +259,8 @@ void CClient::initPlayerInterfaces()
 			if(playerInfo.second.isControlledByAI() || settings["session"]["onlyai"].Bool())
 			{
 				bool alliedToHuman = false;
-				for(auto & allyInfo : gs->getStartInfo()->playerInfos)
-					if (gs->getPlayerTeam(allyInfo.first) == gs->getPlayerTeam(playerInfo.first) && allyInfo.second.isControlledByHuman())
+				for(auto & allyInfo : gameState()->getStartInfo()->playerInfos)
+					if (gameState()->getPlayerTeam(allyInfo.first) == gameState()->getPlayerTeam(playerInfo.first) && allyInfo.second.isControlledByHuman())
 						alliedToHuman = true;
 
 				auto AiToGive = aiNameForPlayer(playerInfo.second, false, alliedToHuman);
@@ -326,7 +318,7 @@ void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInte
 	playerint[color] = gameInterface;
 
 	logGlobal->trace("\tInitializing the interface for player %s", color.toString());
-	auto cb = std::make_shared<CCallback>(gs, color, this);
+	auto cb = std::make_shared<CCallback>(gamestate, color, this);
 	battleCallbacks[color] = cb;
 	gameInterface->initGameInterface(playerEnvironments.at(color), cb);
 
@@ -355,7 +347,7 @@ void CClient::handlePack(CPackForClient & pack)
 	logNetwork->trace("\tMade first apply on cl: %s", typeid(pack).name());
 	{
 		std::unique_lock lock(CGameState::mutex);
-		gs->apply(pack);
+		gameState()->apply(pack);
 	}
 	logNetwork->trace("\tApplied on gs: %s", typeid(pack).name());
 	pack.visit(afterVisitor);
@@ -465,8 +457,8 @@ void CClient::battleFinished(const BattleID & battleID)
 {
 	for(auto side : { BattleSide::ATTACKER, BattleSide::DEFENDER })
 	{
-		if(battleCallbacks.count(gs->getBattle(battleID)->getSide(side).color))
-			battleCallbacks[gs->getBattle(battleID)->getSide(side).color]->onBattleEnded(battleID);
+		if(battleCallbacks.count(gameState()->getBattle(battleID)->getSide(side).color))
+			battleCallbacks[gameState()->getBattle(battleID)->getSide(side).color]->onBattleEnded(battleID);
 	}
 
 	if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool())
@@ -484,11 +476,11 @@ void CClient::startPlayerBattleAction(const BattleID & battleID, PlayerColor col
 	{
 		// we want to avoid locking gamestate and causing UI to freeze while AI is making turn
 		auto unlockInterface = vstd::makeUnlockGuard(ENGINE->interfaceMutex);
-		battleint->activeStack(battleID, gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->activeStack, false));
+		battleint->activeStack(battleID, gameState()->getBattle(battleID)->battleGetStackByID(gameState()->getBattle(battleID)->activeStack, false));
 	}
 	else
 	{
-		battleint->activeStack(battleID, gs->getBattle(battleID)->battleGetStackByID(gs->getBattle(battleID)->activeStack, false));
+		battleint->activeStack(battleID, gameState()->getBattle(battleID)->battleGetStackByID(gameState()->getBattle(battleID)->activeStack, false));
 	}
 }
 

+ 6 - 2
client/Client.h

@@ -122,6 +122,7 @@ public:
 /// Class which handles client - server logic
 class CClient : public IGameCallback, public Environment
 {
+	std::shared_ptr<CGameState> gamestate;
 public:
 	std::map<PlayerColor, std::shared_ptr<CGameInterface>> playerint;
 	std::map<PlayerColor, std::shared_ptr<CBattleGameInterface>> battleints;
@@ -139,8 +140,11 @@ public:
 	vstd::CLoggerBase * logger() const override;
 	events::EventBus * eventBus() const override;
 
-	void newGame(CGameState * gameState);
-	void loadGame(CGameState * gameState);
+	CGameState * gameState() final { return gamestate.get(); }
+	const CGameState * gameState() const final { return gamestate.get(); }
+
+	void newGame(std::shared_ptr<CGameState> gameState);
+	void loadGame(std::shared_ptr<CGameState> gameState);
 
 	void save(const std::string & fname);
 	void endNetwork();

+ 1 - 1
client/lobby/CSavingScreen.cpp

@@ -54,7 +54,7 @@ const CMapInfo * CSavingScreen::getMapInfo()
 const StartInfo * CSavingScreen::getStartInfo()
 {
 	if (localMi)
-		return localMi->scenarioOptionsOfSave;
+		return localMi->scenarioOptionsOfSave.get();
 	return GAME->interface()->cb->getStartInfo();
 }
 

+ 79 - 115
lib/CGameInfoCallback.cpp

@@ -53,22 +53,22 @@ int CGameInfoCallback::getResource(PlayerColor Player, GameResID which) const
 
 const PlayerSettings * CGameInfoCallback::getPlayerSettings(PlayerColor color) const
 {
-	return &gs->getStartInfo()->getIthPlayersSettings(color);
+	return &gameState()->getStartInfo()->getIthPlayersSettings(color);
 }
 
 bool CGameInfoCallback::isAllowed(SpellID id) const
 {
-	return gs->getMap().allowedSpells.count(id) != 0;
+	return gameState()->getMap().allowedSpells.count(id) != 0;
 }
 
 bool CGameInfoCallback::isAllowed(ArtifactID id) const
 {
-	return gs->getMap().allowedArtifact.count(id) != 0;
+	return gameState()->getMap().allowedArtifact.count(id) != 0;
 }
 
 bool CGameInfoCallback::isAllowed(SecondarySkill id) const
 {
-	return gs->getMap().allowedAbilities.count(id) != 0;
+	return gameState()->getMap().allowedAbilities.count(id) != 0;
 }
 
 std::optional<PlayerColor> CGameInfoCallback::getPlayerID() const
@@ -89,8 +89,8 @@ const PlayerState * CGameInfoCallback::getPlayerState(PlayerColor color, bool ve
 	{
 		return nullptr;
 	}
-	auto player = gs->players.find(color);
-	if (player != gs->players.end())
+	auto player = gameState()->players.find(color);
+	if (player != gameState()->players.end())
 	{
 		if (hasAccess(color))
 			return &player->second;
@@ -116,8 +116,8 @@ TurnTimerInfo CGameInfoCallback::getPlayerTurnTime(PlayerColor color) const
 		return TurnTimerInfo{};
 	}
 	
-	auto player = gs->players.find(color);
-	if(player != gs->players.end())
+	auto player = gameState()->players.find(color);
+	if(player != gameState()->players.end())
 	{
 		return player->second.turnTimer;
 	}
@@ -131,7 +131,7 @@ TurnTimerInfo CGameInfoCallback::getPlayerTurnTime(PlayerColor color) const
 
 const CGObjectInstance* CGameInfoCallback::getObj(ObjectInstanceID objid, bool verbose) const
 {
-	const CGObjectInstance *ret = gs->getMap().getObject(objid);
+	const CGObjectInstance *ret = gameState()->getMap().getObject(objid);
 	if(!ret)
 	{
 		if(verbose)
@@ -177,29 +177,29 @@ const IMarket * CGameInfoCallback::getMarket(ObjectInstanceID objid) const
 
 void CGameInfoCallback::fillUpgradeInfo(const CArmedInstance *obj, SlotID stackPos, UpgradeInfo & out) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_IF(!canGetFullInfo(obj), "Cannot get info about not owned object!");
 	ERROR_RET_IF(!obj->hasStackAtSlot(stackPos), "There is no such stack!");
-	gs->fillUpgradeInfo(obj, stackPos, out);
-	//return gs->fillUpgradeInfo(obj->getStack(stackPos));
+	gameState()->fillUpgradeInfo(obj, stackPos, out);
+	//return gameState()->fillUpgradeInfo(obj->getStack(stackPos));
 }
 
 const StartInfo * CGameInfoCallback::getStartInfo() const
 {
-	return gs->getStartInfo();
+	return gameState()->getStartInfo();
 }
 
 const StartInfo * CGameInfoCallback::getInitialStartInfo() const
 {
-	return gs->getInitialStartInfo();
+	return gameState()->getInitialStartInfo();
 }
 
 int32_t CGameInfoCallback::getSpellCost(const spells::Spell * sp, const CGHeroInstance * caster) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_VAL_IF(!canGetFullInfo(caster), "Cannot get info about caster!", -1);
 	//if there is a battle
-	auto casterBattle = gs->getBattle(caster->getOwner());
+	auto casterBattle = gameState()->getBattle(caster->getOwner());
 
 	if(casterBattle)
 		return casterBattle->battleGetSpellCost(sp, caster);
@@ -210,7 +210,7 @@ int32_t CGameInfoCallback::getSpellCost(const spells::Spell * sp, const CGHeroIn
 
 int64_t CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroInstance * hero) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 
 	ERROR_RET_VAL_IF(hero && !canGetFullInfo(hero), "Cannot get info about caster!", -1);
 
@@ -222,26 +222,26 @@ int64_t CGameInfoCallback::estimateSpellDamage(const CSpell * sp, const CGHeroIn
 
 void CGameInfoCallback::getThievesGuildInfo(SThievesGuildInfo & thi, const CGObjectInstance * obj)
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_IF(!obj, "No guild object!");
 	ERROR_RET_IF(obj->ID == Obj::TOWN && !canGetFullInfo(obj), "Cannot get info about town guild object!");
 	//TODO: advmap object -> check if they're visited by our hero
 
 	if(obj->ID == Obj::TOWN || obj->ID == Obj::TAVERN)
 	{
-		int taverns = gs->players[*getPlayerID()].valOfBonuses(BonusType::THIEVES_GUILD_ACCESS);
-		gs->obtainPlayersStats(thi, taverns);
+		int taverns = gameState()->players[*getPlayerID()].valOfBonuses(BonusType::THIEVES_GUILD_ACCESS);
+		gameState()->obtainPlayersStats(thi, taverns);
 	}
 	else if(obj->ID == Obj::DEN_OF_THIEVES)
 	{
-		gs->obtainPlayersStats(thi, 20);
+		gameState()->obtainPlayersStats(thi, 20);
 	}
 }
 
 int CGameInfoCallback::howManyTowns(PlayerColor Player) const
 {
 	ERROR_RET_VAL_IF(!hasAccess(Player), "Access forbidden!", -1);
-	return static_cast<int>(gs->players[Player].getTowns().size());
+	return static_cast<int>(gameState()->players.at(Player).getTowns().size());
 }
 
 bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown & dest, const CGObjectInstance * selectedObject) const
@@ -269,20 +269,20 @@ bool CGameInfoCallback::getTownInfo(const CGObjectInstance * town, InfoAboutTown
 
 const IGameSettings & CGameInfoCallback::getSettings() const
 {
-	return gs->getSettings();
+	return gameState()->getSettings();
 }
 
 int3 CGameInfoCallback::guardingCreaturePosition (int3 pos) const //FIXME: redundant?
 {
 	ERROR_RET_VAL_IF(!isVisible(pos), "Tile is not visible!", int3(-1,-1,-1));
-	return gs->guardingCreaturePosition(pos);
+	return gameState()->guardingCreaturePosition(pos);
 }
 
 std::vector<const CGObjectInstance*> CGameInfoCallback::getGuardingCreatures (int3 pos) const
 {
 	ERROR_RET_VAL_IF(!isVisible(pos), "Tile is not visible!", std::vector<const CGObjectInstance*>());
 	std::vector<const CGObjectInstance*> ret;
-	for(auto * cr : gs->guardingCreatures(pos))
+	for(auto * cr : gameState()->guardingCreatures(pos))
 	{
 		ret.push_back(cr);
 	}
@@ -291,7 +291,7 @@ std::vector<const CGObjectInstance*> CGameInfoCallback::getGuardingCreatures (in
 
 bool CGameInfoCallback::isTileGuardedUnchecked(int3 tile) const
 {
-	return !gs->guardingCreatures(tile).empty();
+	return !gameState()->guardingCreatures(tile).empty();
 }
 
 bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero & dest, const CGObjectInstance * selectedObject) const
@@ -307,7 +307,7 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 
 	if (infoLevel == InfoAboutHero::EInfoLevel::BASIC)
 	{
-		auto ourBattle = gs->getBattle(*getPlayerID());
+		auto ourBattle = gameState()->getBattle(*getPlayerID());
 
 		if(ourBattle && ourBattle->playerHasAccessToHeroInfo(*getPlayerID(), h)) //if it's battle we can get enemy hero full data
 			infoLevel = InfoAboutHero::EInfoLevel::INBATTLE;
@@ -412,14 +412,14 @@ bool CGameInfoCallback::getHeroInfo(const CGObjectInstance * hero, InfoAboutHero
 
 int CGameInfoCallback::getDate(Date mode) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
-	return gs->getDate(mode);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
+	return gameState()->getDate(mode);
 }
 
 bool CGameInfoCallback::isVisible(int3 pos, const std::optional<PlayerColor> & Player) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
-	return gs->isVisible(pos, Player);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
+	return gameState()->isVisible(pos, Player);
 }
 
 bool CGameInfoCallback::isVisible(int3 pos) const
@@ -429,7 +429,7 @@ bool CGameInfoCallback::isVisible(int3 pos) const
 
 bool CGameInfoCallback::isVisible(const CGObjectInstance * obj, const std::optional<PlayerColor> & Player) const
 {
-	return gs->isVisible(obj, Player);
+	return gameState()->isVisible(obj, Player);
 }
 
 bool CGameInfoCallback::isVisible(const CGObjectInstance *obj) const
@@ -438,7 +438,7 @@ bool CGameInfoCallback::isVisible(const CGObjectInstance *obj) const
 }
 // const CCreatureSet* CInfoCallback::getGarrison(const CGObjectInstance *obj) const
 // {
-// 	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+// 	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 // 	if()
 // 	const CArmedInstance *armi = dynamic_cast<const CArmedInstance*>(obj);
 // 	if(!armi)
@@ -477,7 +477,7 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getVisitableObjs(int3
 std::vector<const CGObjectInstance *> CGameInfoCallback::getAllVisitableObjs() const
 {
 	std::vector<const CGObjectInstance *> ret;
-	for(auto & obj : gs->getMap().getObjects())
+	for(auto & obj : gameState()->getMap().getObjects())
 		if(obj->isVisitable() && obj->ID != Obj::EVENT && isVisible(obj))
 			ret.push_back(obj);
 
@@ -505,7 +505,7 @@ std::vector <const CGObjectInstance *> CGameInfoCallback::getFlaggableObjects(in
 
 int3 CGameInfoCallback::getMapSize() const
 {
-	return int3(gs->getMap().width, gs->getMap().height, gs->getMap().twoLevel ? 2 : 1);
+	return int3(gameState()->getMap().width, gameState()->getMap().height, gameState()->getMap().twoLevel ? 2 : 1);
 }
 
 std::vector<const CGHeroInstance *> CGameInfoCallback::getAvailableHeroes(const CGObjectInstance * townOrTavern) const
@@ -517,7 +517,7 @@ std::vector<const CGHeroInstance *> CGameInfoCallback::getAvailableHeroes(const
 	const CGTownInstance * town = getTown(townOrTavern->id);
 
 	if(townOrTavern->ID == Obj::TAVERN || (town && town->hasBuilt(BuildingID::TAVERN)))
-		return gs->heroesPool->getHeroesFor(*getPlayerID());
+		return gameState()->heroesPool->getHeroesFor(*getPlayerID());
 
 	return ret;
 }
@@ -525,7 +525,7 @@ std::vector<const CGHeroInstance *> CGameInfoCallback::getAvailableHeroes(const
 const TerrainTile * CGameInfoCallback::getTile(int3 tile, bool verbose) const
 {
 	if(isVisible(tile))
-		return &gs->getMap().getTile(tile);
+		return &gameState()->getMap().getTile(tile);
 
 	if(verbose)
 		logGlobal->error("\r\n%s: %s\r\n", BOOST_CURRENT_FUNCTION, tile.toString() + " is not visible!");
@@ -535,7 +535,7 @@ const TerrainTile * CGameInfoCallback::getTile(int3 tile, bool verbose) const
 const TerrainTile * CGameInfoCallback::getTileUnchecked(int3 tile) const
 {
 	if (isInTheMap(tile))
-		return &gs->getMap().getTile(tile);
+		return &gameState()->getMap().getTile(tile);
 
 	return nullptr;
 }
@@ -545,7 +545,7 @@ EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) cons
 	if(!isVisible(tile))
 		return EDiggingStatus::UNKNOWN;
 
-	for(const auto & object : gs->getMap().getObjects())
+	for(const auto & object : gameState()->getMap().getObjects())
 	{
 		if(object->ID == Obj::HOLE && object->anchorPos() == tile)
 			return EDiggingStatus::TILE_OCCUPIED;
@@ -553,32 +553,6 @@ EDiggingStatus CGameInfoCallback::getTileDigStatus(int3 tile, bool verbose) cons
 	return getTile(tile)->getDiggingStatus();
 }
 
-//TODO: typedef?
-std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> CGameInfoCallback::getAllVisibleTiles() const
-{
-	assert(getPlayerID().has_value());
-	const auto * team = getPlayerTeam(getPlayerID().value());
-
-	size_t width = gs->getMap().width;
-	size_t height = gs->getMap().height;
-	size_t levels = gs->getMap().levels();
-
-	auto * ptr = new boost::multi_array<TerrainTile *, 3>(boost::extents[levels][width][height]);
-
-	int3 tile;
-	for(tile.z = 0; tile.z < levels; tile.z++)
-		for(tile.x = 0; tile.x < width; tile.x++)
-			for(tile.y = 0; tile.y < height; tile.y++)
-			{
-				if (team->fogOfWarMap[tile.z][tile.x][tile.y])
-					(*ptr)[tile.z][tile.x][tile.y] = &gs->getMap().getTile(tile);
-				else
-					(*ptr)[tile.z][tile.x][tile.y] = nullptr;
-			}
-
-	return std::shared_ptr<const boost::multi_array<TerrainTile*, 3>>(ptr);
-}
-
 EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, BuildingID ID )
 {
 	ERROR_RET_VAL_IF(!canGetFullInfo(t), "Town is not owned!", EBuildingState::TOWN_NOT_OWNED);
@@ -650,17 +624,17 @@ EBuildingState CGameInfoCallback::canBuildStructure( const CGTownInstance *t, Bu
 
 const CMapHeader * CGameInfoCallback::getMapHeader() const
 {
-	return &gs->getMap();
+	return &gameState()->getMap();
 }
 
 bool CGameInfoCallback::hasAccess(std::optional<PlayerColor> playerId) const
 {
-	return !getPlayerID() || getPlayerID()->isSpectator() || gs->getPlayerRelations(*playerId, *getPlayerID()) != PlayerRelations::ENEMIES;
+	return !getPlayerID() || getPlayerID()->isSpectator() || gameState()->getPlayerRelations(*playerId, *getPlayerID()) != PlayerRelations::ENEMIES;
 }
 
 EPlayerStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bool verbose) const
 {
-	const PlayerState *ps = gs->getPlayerState(player, verbose);
+	const PlayerState *ps = gameState()->getPlayerState(player, verbose);
 	ERROR_VERBOSE_OR_NOT_RET_VAL_IF(!ps, verbose, "No such player!", EPlayerStatus::WRONG);
 
 	return ps->status;
@@ -672,11 +646,11 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
 	text.appendLocalString(EMetaText::GENERAL_TXT, 216);
 	
 	std::string extraText;
-	if(gs->currentRumor.type == RumorState::TYPE_NONE)
+	if(gameState()->currentRumor.type == RumorState::TYPE_NONE)
 		return text.toString();
 
-	auto rumor = gs->currentRumor.last[gs->currentRumor.type];
-	switch(gs->currentRumor.type)
+	auto rumor = gameState()->currentRumor.last.at(gameState()->currentRumor.type);
+	switch(gameState()->currentRumor.type)
 	{
 	case RumorState::TYPE_SPECIAL:
 		text.replaceLocalString(EMetaText::GENERAL_TXT, rumor.first);
@@ -687,7 +661,7 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
 
 		break;
 	case RumorState::TYPE_MAP:
-		text.replaceRawString(gs->getMap().rumors[rumor.first].text.toString());
+		text.replaceRawString(gameState()->getMap().rumors[rumor.first].text.toString());
 		break;
 
 	case RumorState::TYPE_RAND:
@@ -700,7 +674,7 @@ std::string CGameInfoCallback::getTavernRumor(const CGObjectInstance * townOrTav
 
 PlayerRelations CGameInfoCallback::getPlayerRelations( PlayerColor color1, PlayerColor color2 ) const
 {
-	return gs->getPlayerRelations(color1, color2);
+	return gameState()->getPlayerRelations(color1, color2);
 }
 
 bool CGameInfoCallback::canGetFullInfo(const CGObjectInstance *obj) const
@@ -711,7 +685,7 @@ bool CGameInfoCallback::canGetFullInfo(const CGObjectInstance *obj) const
 int CGameInfoCallback::getHeroCount( PlayerColor player, bool includeGarrisoned ) const
 {
 	int ret = 0;
-	const PlayerState *p = gs->getPlayerState(player);
+	const PlayerState *p = gameState()->getPlayerState(player);
 	ERROR_RET_VAL_IF(!p, "No such player!", -1);
 
 	if(includeGarrisoned)
@@ -736,31 +710,21 @@ bool CGameInfoCallback::isOwnedOrVisited(const CGObjectInstance *obj) const
 
 bool CGameInfoCallback::isPlayerMakingTurn(PlayerColor player) const
 {
-	return gs->actingPlayers.count(player);
-}
-
-CGameInfoCallback::CGameInfoCallback():
-	gs(nullptr)
-{
-}
-
-CGameInfoCallback::CGameInfoCallback(CGameState * GS):
-	gs(GS)
-{
+	return gameState()->actingPlayers.count(player);
 }
 
 int CPlayerSpecificInfoCallback::howManyTowns() const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_VAL_IF(!getPlayerID(), "Applicable only for player callbacks", -1);
 	return CGameInfoCallback::howManyTowns(*getPlayerID());
 }
 
 std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo(bool onlyOur) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	auto ret = std::vector < const CGTownInstance *>();
-	for(const auto & i : gs->players)
+	for(const auto & i : gameState()->players)
 	{
 		for(const auto & town : i.second.getTowns())
 		{
@@ -769,12 +733,12 @@ std::vector < const CGTownInstance *> CPlayerSpecificInfoCallback::getTownsInfo(
 				ret.push_back(town);
 			}
 		}
-	} //	for ( std::map<int, PlayerState>::iterator i=gs->players.begin() ; i!=gs->players.end();i++)
+	} //	for ( std::map<int, PlayerState>::iterator i=gameState()->players.begin() ; i!=gameState()->players.end();i++)
 	return ret;
 }
 std::vector < const CGHeroInstance *> CPlayerSpecificInfoCallback::getHeroesInfo() const
 {
-	const auto * playerState = gs->getPlayerState(*getPlayerID());
+	const auto * playerState = gameState()->getPlayerState(*getPlayerID());
 	return playerState->getHeroes();
 }
 
@@ -784,7 +748,7 @@ int CPlayerSpecificInfoCallback::getHeroSerial(const CGHeroInstance * hero, bool
 		return -1;
 
 	size_t index = 0;
-	const auto & heroes = gs->players[*getPlayerID()].getHeroes();
+	const auto & heroes = gameState()->players.at(*getPlayerID()).getHeroes();
 
 	for (auto & possibleHero : heroes)
 	{
@@ -799,35 +763,35 @@ int CPlayerSpecificInfoCallback::getHeroSerial(const CGHeroInstance * hero, bool
 
 int3 CPlayerSpecificInfoCallback::getGrailPos( double *outKnownRatio )
 {
-	if (!getPlayerID() || gs->getMap().obeliskCount == 0)
+	if (!getPlayerID() || gameState()->getMap().obeliskCount == 0)
 	{
 		*outKnownRatio = 0.0;
 	}
 	else
 	{
-		TeamID t = gs->getPlayerTeam(*getPlayerID())->id;
+		TeamID t = gameState()->getPlayerTeam(*getPlayerID())->id;
 		double visited = 0.0;
-		if(gs->getMap().obelisksVisited.count(t))
-			visited = static_cast<double>(gs->getMap().obelisksVisited[t]);
+		if(gameState()->getMap().obelisksVisited.count(t))
+			visited = static_cast<double>(gameState()->getMap().obelisksVisited[t]);
 
-		*outKnownRatio = visited / gs->getMap().obeliskCount;
+		*outKnownRatio = visited / gameState()->getMap().obeliskCount;
 	}
-	return gs->getMap().grailPos;
+	return gameState()->getMap().grailPos;
 }
 
 std::vector < const CGObjectInstance * > CPlayerSpecificInfoCallback::getMyObjects() const
 {
-	return gs->getPlayerState(*getPlayerID())->getOwnedObjects();
+	return gameState()->getPlayerState(*getPlayerID())->getOwnedObjects();
 }
 
 std::vector <QuestInfo> CPlayerSpecificInfoCallback::getMyQuests() const
 {
-	return gs->getPlayerState(*getPlayerID())->quests;
+	return gameState()->getPlayerState(*getPlayerID())->quests;
 }
 
 int CPlayerSpecificInfoCallback::howManyHeroes(bool includeGarrisoned) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_VAL_IF(!getPlayerID(), "Applicable only for player callbacks", -1);
 	return getHeroCount(*getPlayerID(), includeGarrisoned);
 }
@@ -859,24 +823,24 @@ const CGTownInstance* CPlayerSpecificInfoCallback::getTownBySerial(int serialId)
 
 int CPlayerSpecificInfoCallback::getResourceAmount(GameResID type) const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_VAL_IF(!getPlayerID(), "Applicable only for player callbacks", -1);
 	return getResource(*getPlayerID(), type);
 }
 
 TResources CPlayerSpecificInfoCallback::getResourceAmount() const
 {
-	//std::shared_lock<std::shared_mutex> lock(*gs->mx);
+	//std::shared_lock<std::shared_mutex> lock(*gameState()->mx);
 	ERROR_RET_VAL_IF(!getPlayerID(), "Applicable only for player callbacks", TResources());
-	return gs->players[*getPlayerID()].resources;
+	return gameState()->players.at(*getPlayerID()).resources;
 }
 
 const TeamState * CGameInfoCallback::getTeam( TeamID teamID ) const
 {
 	//rewritten by hand, AI calls this function a lot
 
-	auto team = gs->teams.find(teamID);
-	if (team != gs->teams.end())
+	auto team = gameState()->teams.find(teamID);
+	if (team != gameState()->teams.end())
 	{
 		const TeamState *ret = &team->second;
 		if(!getPlayerID().has_value()) //neutral (or invalid) player
@@ -901,8 +865,8 @@ const TeamState * CGameInfoCallback::getTeam( TeamID teamID ) const
 
 const TeamState * CGameInfoCallback::getPlayerTeam( PlayerColor color ) const
 {
-	auto player = gs->players.find(color);
-	if (player != gs->players.end())
+	auto player = gameState()->players.find(color);
+	if (player != gameState()->players.end())
 	{
 		return getTeam (player->second.team);
 	}
@@ -914,32 +878,32 @@ const TeamState * CGameInfoCallback::getPlayerTeam( PlayerColor color ) const
 
 bool CGameInfoCallback::isInTheMap(const int3 &pos) const
 {
-	return gs->getMap().isInTheMap(pos);
+	return gameState()->getMap().isInTheMap(pos);
 }
 
 void CGameInfoCallback::getVisibleTilesInRange(std::unordered_set<int3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula) const
 {
-	gs->getTilesInRange(tiles, pos, radious, ETileVisibility::REVEALED, *getPlayerID(),  distanceFormula);
+	gameState()->getTilesInRange(tiles, pos, radious, ETileVisibility::REVEALED, *getPlayerID(),  distanceFormula);
 }
 
 void CGameInfoCallback::calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const
 {
-	gs->calculatePaths(config);
+	gameState()->calculatePaths(config);
 }
 
 const CArtifactInstance * CGameInfoCallback::getArtInstance( ArtifactInstanceID aid ) const
 {
-	return gs->getMap().getArtifactInstance(aid);
+	return gameState()->getMap().getArtifactInstance(aid);
 }
 
 const CGObjectInstance * CGameInfoCallback::getObjInstance( ObjectInstanceID oid ) const
 {
-	return gs->getMap().getObject((oid));
+	return gameState()->getMap().getObject((oid));
 }
 
 const CArtifactSet * CGameInfoCallback::getArtSet(const ArtifactLocation & loc) const
 {
-	return gs->getArtSet(loc);
+	return gameState()->getArtSet(loc);
 }
 
 std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::vector<ObjectInstanceID> ids, PlayerColor player) const
@@ -954,12 +918,12 @@ std::vector<ObjectInstanceID> CGameInfoCallback::getVisibleTeleportObjects(std::
 
 std::vector<ObjectInstanceID> CGameInfoCallback::getTeleportChannelEntrances(TeleportChannelID id, PlayerColor player) const
 {
-	return getVisibleTeleportObjects(gs->getMap().teleportChannels[id]->entrances, player);
+	return getVisibleTeleportObjects(gameState()->getMap().teleportChannels.at(id)->entrances, player);
 }
 
 std::vector<ObjectInstanceID> CGameInfoCallback::getTeleportChannelExits(TeleportChannelID id, PlayerColor player) const
 {
-	return getVisibleTeleportObjects(gs->getMap().teleportChannels[id]->exits, player);
+	return getVisibleTeleportObjects(gameState()->getMap().teleportChannels.at(id)->exits, player);
 }
 
 ETeleportChannelType CGameInfoCallback::getTeleportChannelType(TeleportChannelID id, PlayerColor player) const

+ 2 - 5
lib/CGameInfoCallback.h

@@ -102,7 +102,6 @@ public:
 //	const CMapHeader * getMapHeader()const;
 //	int3 getMapSize() const; //returns size of map - z is 1 for one - level map and 2 for two level map
 //	const TerrainTile * getTile(int3 tile, bool verbose = true) const;
-//	std::shared_ptr<boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
 //	bool isInTheMap(const int3 &pos) const;
 
 	//town
@@ -133,10 +132,9 @@ public:
 class DLL_LINKAGE CGameInfoCallback : public IGameInfoCallback
 {
 protected:
-	CGameState * gs;//todo: replace with protected const getter, only actual Server and Client objects should hold game state
+	virtual CGameState * gameState() = 0;
+	virtual const CGameState * gameState() const = 0;
 
-	CGameInfoCallback();
-	CGameInfoCallback(CGameState * GS);
 	bool hasAccess(std::optional<PlayerColor> playerId) const;
 
 	bool canGetFullInfo(const CGObjectInstance *obj) const; //true we player owns obj or ally owns obj or privileged mode
@@ -203,7 +201,6 @@ public:
 	virtual int3 getMapSize() const; //returns size of map - z is 1 for one - level map and 2 for two level map
 	virtual const TerrainTile * getTile(int3 tile, bool verbose = true) const;
 	virtual const TerrainTile * getTileUnchecked(int3 tile) const;
-	virtual std::shared_ptr<const boost::multi_array<TerrainTile*, 3>> getAllVisibleTiles() const;
 	virtual bool isInTheMap(const int3 &pos) const;
 	virtual void getVisibleTilesInRange(std::unordered_set<int3> &tiles, int3 pos, int radious, int3::EDistanceFormula distanceFormula = int3::DIST_2D) const;
 	virtual void calculatePaths(const std::shared_ptr<PathfinderConfig> & config) const;

+ 20 - 25
lib/IGameCallback.cpp

@@ -58,17 +58,17 @@ VCMI_LIB_NAMESPACE_BEGIN
 void CPrivilegedInfoCallback::getFreeTiles(std::vector<int3> & tiles) const
 {
 	std::vector<int> floors;
-	floors.reserve(gs->getMap().levels());
-	for(int b = 0; b < gs->getMap().levels(); ++b)
+	floors.reserve(gameState()->getMap().levels());
+	for(int b = 0; b < gameState()->getMap().levels(); ++b)
 	{
 		floors.push_back(b);
 	}
 	const TerrainTile * tinfo = nullptr;
 	for (auto zd : floors)
 	{
-		for (int xd = 0; xd < gs->getMap().width; xd++)
+		for (int xd = 0; xd < gameState()->getMap().width; xd++)
 		{
-			for (int yd = 0; yd < gs->getMap().height; yd++)
+			for (int yd = 0; yd < gameState()->getMap().height; yd++)
 			{
 				tinfo = getTile(int3 (xd,yd,zd));
 				if (tinfo->isLand() && tinfo->getTerrain()->isPassable() && !tinfo->blocked()) //land and free
@@ -94,10 +94,10 @@ void CPrivilegedInfoCallback::getTilesInRange(std::unordered_set<int3> & tiles,
 		getAllTiles (tiles, player, -1, [](auto * tile){return true;});
 	else
 	{
-		const TeamState * team = !player ? nullptr : gs->getPlayerTeam(*player);
-		for (int xd = std::max<int>(pos.x - radious , 0); xd <= std::min<int>(pos.x + radious, gs->getMap().width - 1); xd++)
+		const TeamState * team = !player ? nullptr : gameState()->getPlayerTeam(*player);
+		for (int xd = std::max<int>(pos.x - radious , 0); xd <= std::min<int>(pos.x + radious, gameState()->getMap().width - 1); xd++)
 		{
-			for (int yd = std::max<int>(pos.y - radious, 0); yd <= std::min<int>(pos.y + radious, gs->getMap().height - 1); yd++)
+			for (int yd = std::max<int>(pos.y - radious, 0); yd <= std::min<int>(pos.y + radious, gameState()->getMap().height - 1); yd++)
 			{
 				int3 tilePos(xd,yd,pos.z);
 				int distance = pos.dist(tilePos, distanceFormula);
@@ -126,7 +126,7 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3> & tiles, std:
 	std::vector<int> floors;
 	if(level == -1)
 	{
-		for(int b = 0; b < gs->getMap().levels(); ++b)
+		for(int b = 0; b < gameState()->getMap().levels(); ++b)
 		{
 			floors.push_back(b);
 		}
@@ -136,9 +136,9 @@ void CPrivilegedInfoCallback::getAllTiles(std::unordered_set<int3> & tiles, std:
 
 	for(auto zd: floors)
 	{
-		for(int xd = 0; xd < gs->getMap().width; xd++)
+		for(int xd = 0; xd < gameState()->getMap().width; xd++)
 		{
-			for(int yd = 0; yd < gs->getMap().height; yd++)
+			for(int yd = 0; yd < gameState()->getMap().height; yd++)
 			{
 				int3 coordinates(xd, yd, zd);
 				if (filter(getTile(coordinates)))
@@ -160,7 +160,7 @@ void CPrivilegedInfoCallback::pickAllowedArtsSet(std::vector<ArtifactID> & out,
 
 void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::optional<ui16> level)
 {
-	for (auto const & spellID : gs->getMap().allowedSpells)
+	for (auto const & spellID : gameState()->getMap().allowedSpells)
 	{
 		const auto * spell = spellID.toEntity(LIBRARY);
 
@@ -174,18 +174,13 @@ void CPrivilegedInfoCallback::getAllowedSpells(std::vector<SpellID> & out, std::
 	}
 }
 
-CGameState * CPrivilegedInfoCallback::gameState()
-{
-	return gs;
-}
-
 void CPrivilegedInfoCallback::loadCommonState(CLoadFile & in)
 {
 	logGlobal->info("Loading lib part of game...");
 	in.checkMagicBytes(SAVEGAME_MAGIC);
 
 	CMapHeader dum;
-	StartInfo * si = nullptr;
+	StartInfo si;
 	ActiveModsInSaveList activeMods;
 
 	logGlobal->info("\tReading header");
@@ -198,7 +193,7 @@ void CPrivilegedInfoCallback::loadCommonState(CLoadFile & in)
 	in.serializer & activeMods;
 
 	logGlobal->info("\tReading gamestate");
-	in.serializer & gs;
+	in.serializer & *gameState();
 }
 
 void CPrivilegedInfoCallback::saveCommonState(CSaveFile & out) const
@@ -208,20 +203,20 @@ void CPrivilegedInfoCallback::saveCommonState(CSaveFile & out) const
 	logGlobal->info("Saving lib part of game...");
 	out.putMagicBytes(SAVEGAME_MAGIC);
 	logGlobal->info("\tSaving header");
-	out.serializer & static_cast<CMapHeader&>(gs->getMap());
+	out.serializer & static_cast<const CMapHeader&>(gameState()->getMap());
 	logGlobal->info("\tSaving options");
-	out.serializer & gs->getStartInfo();
+	out.serializer & *gameState()->getStartInfo();
 	logGlobal->info("\tSaving mod list");
 	out.serializer & activeMods;
 	logGlobal->info("\tSaving gamestate");
-	out.serializer & gs;
+	out.serializer & *gameState();
 }
 
 TerrainTile * CNonConstInfoCallback::getTile(const int3 & pos)
 {
-	if(!gs->getMap().isInTheMap(pos))
+	if(!gameState()->getMap().isInTheMap(pos))
 		return nullptr;
-	return &gs->getMap().getTile(pos);
+	return &gameState()->getMap().getTile(pos);
 }
 
 CGHeroInstance * CNonConstInfoCallback::getHero(const ObjectInstanceID & objid)
@@ -251,12 +246,12 @@ PlayerState * CNonConstInfoCallback::getPlayerState(const PlayerColor & color, b
 
 CArtifactInstance * CNonConstInfoCallback::getArtInstance(const ArtifactInstanceID & aid)
 {
-	return gs->getMap().getArtifactInstance(aid);
+	return gameState()->getMap().getArtifactInstance(aid);
 }
 
 CGObjectInstance * CNonConstInfoCallback::getObjInstance(const ObjectInstanceID & oid)
 {
-	return gs->getMap().getObject(oid);
+	return gameState()->getMap().getObject(oid);
 }
 
 CArmedInstance * CNonConstInfoCallback::getArmyInstance(const ObjectInstanceID & oid)

+ 1 - 1
lib/IGameCallback.h

@@ -58,7 +58,7 @@ namespace scripting
 class DLL_LINKAGE CPrivilegedInfoCallback : public CGameInfoCallback
 {
 public:
-	CGameState *gameState();
+	using CGameInfoCallback::gameState; // make public
 
 	//used for random spawns
 	void getFreeTiles(std::vector<int3> &tiles) const;

+ 7 - 8
lib/gameState/CGameState.cpp

@@ -141,7 +141,6 @@ int CGameState::getDate(Date mode) const
 
 CGameState::CGameState()
 {
-	gs = this;
 	heroesPool = std::make_unique<TavernHeroesPool>(this);
 	globalEffects.setNodeType(CBonusSystemNode::GLOBAL_EFFECTS);
 }
@@ -275,7 +274,7 @@ void CGameState::updateOnLoad(StartInfo * si)
 	assert(callback);
 	scenarioOps->playerInfos = si->playerInfos;
 	for(auto & i : si->playerInfos)
-		gs->players[i.first].human = i.second.isControlledByHuman();
+		players[i.first].human = i.second.isControlledByHuman();
 	scenarioOps->extraOptionsInfo = si->extraOptionsInfo;
 	scenarioOps->turnTimerInfo = si->turnTimerInfo;
 	scenarioOps->simturnsInfo = si->simturnsInfo;
@@ -606,7 +605,7 @@ void CGameState::initHeroes()
 		{
 			auto handler = LIBRARY->objtypeh->getHandlerFor(Obj::BOAT, hero->getBoatType().getNum());
 			auto boat = std::dynamic_pointer_cast<CGBoat>(handler->create(callback, nullptr));
-			handler->configureObject(boat.get(), gs->getRandomGenerator());
+			handler->configureObject(boat.get(), getRandomGenerator());
 
 			boat->setAnchorPos(hero->anchorPos());
 			boat->appearance = handler->getTemplates().front();
@@ -1031,7 +1030,7 @@ BattleField CGameState::battleGetBattlefieldType(int3 tile, vstd::RNG & rand)
 	const TerrainTile &t = map->getTile(tile);
 
 	ObjectInstanceID topObjectID = t.visitableObjects.front();
-	const CGObjectInstance * topObject = gs->getObjInstance(topObjectID);
+	const CGObjectInstance * topObject = getObjInstance(topObjectID);
 	if(topObject && topObject->getBattlefield() != BattleField::NONE)
 	{
 		return topObject->getBattlefield();
@@ -1183,7 +1182,7 @@ std::vector<const CGObjectInstance*> CGameState::guardingCreatures (int3 pos) co
 
 int3 CGameState::guardingCreaturePosition (int3 pos) const
 {
-	return gs->getMap().guardingCreaturePositions[pos.z][pos.x][pos.y];
+	return getMap().guardingCreaturePositions[pos.z][pos.x][pos.y];
 }
 
 bool CGameState::isVisible(int3 pos, const std::optional<PlayerColor> & player) const
@@ -1376,7 +1375,7 @@ bool CGameState::checkForVictory(const PlayerColor & player, const EventConditio
 		}
 		case EventCondition::DAYS_PASSED:
 		{
-			return (si32)gs->day > condition.value;
+			return (si32)day > condition.value;
 		}
 		case EventCondition::IS_HUMAN:
 		{
@@ -1498,7 +1497,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 	}
 	if(level >= 3) //obelisks found
 	{
-		FILL_FIELD(obelisks, Statistic::getObeliskVisited(gs, gs->getPlayerTeam(g->second.color)->id))
+		FILL_FIELD(obelisks, Statistic::getObeliskVisited(this, getPlayerTeam(g->second.color)->id))
 	}
 	if(level >= 4) //artifacts
 	{
@@ -1510,7 +1509,7 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
 	}
 	if(level >= 5) //income
 	{
-		FILL_FIELD(income, Statistic::getIncome(gs, &g->second))
+		FILL_FIELD(income, Statistic::getIncome(this, &g->second))
 	}
 	if(level >= 2) //best hero's stats
 	{

+ 3 - 0
lib/gameState/CGameState.h

@@ -69,6 +69,9 @@ public:
 	CGameState();
 	virtual ~CGameState();
 
+	CGameState * gameState() final { return this; }
+	const CGameState * gameState() const final { return this; }
+
 	void preInit(Services * services, IGameCallback * callback);
 
 	void init(const IMapService * mapService, StartInfo * si, Load::ProgressAccumulator &, bool allowSavingRandomMap = true);

+ 2 - 2
lib/mapObjects/MiscObjects.cpp

@@ -320,7 +320,7 @@ bool CGTeleport::isConnected(const CGObjectInstance * src, const CGObjectInstanc
 	return isConnected(srcObj, dstObj);
 }
 
-bool CGTeleport::isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj)
+bool CGTeleport::isExitPassable(const CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj)
 {
 	ObjectInstanceID topObjectID = gs->getMap().getTile(obj->visitablePos()).topVisitableObj();
 	const CGObjectInstance * topObject = gs->getObjInstance(topObjectID);
@@ -341,7 +341,7 @@ bool CGTeleport::isExitPassable(CGameState * gs, const CGHeroInstance * h, const
 	return true;
 }
 
-std::vector<ObjectInstanceID> CGTeleport::getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits)
+std::vector<ObjectInstanceID> CGTeleport::getPassableExits(const CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits)
 {
 	vstd::erase_if(exits, [&](const ObjectInstanceID & exit) -> bool 
 	{

+ 2 - 2
lib/mapObjects/MiscObjects.h

@@ -209,8 +209,8 @@ public:
 	static bool isConnected(const CGTeleport * src, const CGTeleport * dst);
 	static bool isConnected(const CGObjectInstance * src, const CGObjectInstance * dst);
 	static void addToChannel(std::map<TeleportChannelID, std::shared_ptr<TeleportChannel> > &channelsList, const CGTeleport * obj);
-	static std::vector<ObjectInstanceID> getPassableExits(CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits);
-	static bool isExitPassable(CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj);
+	static std::vector<ObjectInstanceID> getPassableExits(const CGameState * gs, const CGHeroInstance * h, std::vector<ObjectInstanceID> exits);
+	static bool isExitPassable(const CGameState * gs, const CGHeroInstance * h, const CGObjectInstance * obj);
 
 	template <typename Handler> void serialize(Handler &h)
 	{

+ 2 - 5
lib/mapping/CMapInfo.cpp

@@ -31,15 +31,12 @@
 VCMI_LIB_NAMESPACE_BEGIN
 
 CMapInfo::CMapInfo()
-	: scenarioOptionsOfSave(nullptr), amountOfPlayersOnMap(0), amountOfHumanControllablePlayers(0),	amountOfHumanPlayersInSave(0), isRandomMap(false)
+	: amountOfPlayersOnMap(0), amountOfHumanControllablePlayers(0),	amountOfHumanPlayersInSave(0), isRandomMap(false)
 {
 
 }
 
-CMapInfo::~CMapInfo()
-{
-	vstd::clear_pointer(scenarioOptionsOfSave);
-}
+CMapInfo::~CMapInfo() = default;
 
 std::string CMapInfo::getFullFileURI(const ResourcePath & file) const
 {

+ 1 - 1
lib/mapping/CMapInfo.h

@@ -28,7 +28,7 @@ class DLL_LINKAGE CMapInfo : public Serializeable
 public:
 	std::unique_ptr<CMapHeader> mapHeader; //may be nullptr if campaign
 	std::unique_ptr<Campaign> campaign; //may be nullptr if scenario
-	StartInfo * scenarioOptionsOfSave; // Options with which scenario has been started (used only with saved games)
+	std::unique_ptr<StartInfo> scenarioOptionsOfSave; // Options with which scenario has been started (used only with saved games)
 	std::string fileURI;
 	std::string originalFileURI; // no need to serialize
 	std::string fullFileURI; // no need to serialize

+ 2 - 2
lib/networkPacks/PacksForLobby.h

@@ -136,8 +136,8 @@ struct DLL_LINKAGE LobbyPrepareStartGame : public CLobbyPackToPropagate
 struct DLL_LINKAGE LobbyStartGame : public CLobbyPackToPropagate
 {
 	// Set by server
-	std::shared_ptr<StartInfo> initializedStartInfo = nullptr;
-	CGameState * initializedGameState = nullptr;
+	std::shared_ptr<StartInfo> initializedStartInfo;
+	std::shared_ptr<CGameState> initializedGameState;
 	int clientId = -1; //-1 means to all clients
 
 	void visitTyped(ICPackVisitor & visitor) override;

+ 12 - 12
lib/pathfinder/CPathfinder.cpp

@@ -74,8 +74,8 @@ void CPathfinderHelper::calculateNeighbourTiles(NeighbourTilesVector & result, c
 	}
 }
 
-CPathfinder::CPathfinder(CGameState * _gs, std::shared_ptr<PathfinderConfig> config): 
-	gamestate(_gs),
+CPathfinder::CPathfinder(CGameState * gamestate, std::shared_ptr<PathfinderConfig> config):
+	gamestate(gamestate),
 	config(std::move(config))
 {
 	initializeGraph();
@@ -111,9 +111,9 @@ void CPathfinder::calculatePaths()
 
 	for(auto * initialNode : initialNodes)
 	{
-		if(!gamestate->isInTheMap(initialNode->coord)/* || !gs->getMap().isInTheMap(dest)*/) //check input
+		if(!gamestate->isInTheMap(initialNode->coord)/* || !gameState()->getMap().isInTheMap(dest)*/) //check input
 		{
-			logGlobal->error("CGameState::calculatePaths: Hero outside the gs->map? How dare you...");
+			logGlobal->error("CGameState::calculatePaths: Hero outside the gameState()->map? How dare you...");
 			throw std::runtime_error("Wrong checksum");
 		}
 
@@ -251,12 +251,12 @@ TeleporterTilesVector CPathfinderHelper::getAllowedTeleportChannelExits(const Te
 			auto pos = obj->getBlockedPos();
 			for(const auto & p : pos)
 			{
-				ObjectInstanceID topObject = gs->getMap().getTile(p).topVisitableObj();
+				ObjectInstanceID topObject = gameState()->getMap().getTile(p).topVisitableObj();
 				if(topObject.hasValue() && getObj(topObject)->ID == obj->ID)
 					allowedExits.push_back(p);
 			}
 		}
-		else if(obj && CGTeleport::isExitPassable(gs, hero, obj))
+		else if(obj && CGTeleport::isExitPassable(gameState(), hero, obj))
 			allowedExits.push_back(obj->visitablePos());
 	}
 
@@ -402,7 +402,7 @@ void CPathfinderHelper::initializePatrol()
 		if(hero->patrol.patrolRadius)
 		{
 			state = PATROL_RADIUS;
-			gs->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadius, ETileVisibility::REVEALED, std::optional<PlayerColor>(), int3::DIST_MANHATTAN);
+			gameState()->getTilesInRange(patrolTiles, hero->patrol.initialPos, hero->patrol.patrolRadius, ETileVisibility::REVEALED, std::optional<PlayerColor>(), int3::DIST_MANHATTAN);
 		}
 		else
 			state = PATROL_LOCKED;
@@ -419,7 +419,7 @@ void CPathfinder::initializeGraph()
 
 bool CPathfinderHelper::canMoveBetween(const int3 & a, const int3 & b) const
 {
-	return gs->checkForVisitableDir(a, b);
+	return gameState()->checkForVisitableDir(a, b);
 }
 
 bool CPathfinderHelper::isAllowedTeleportEntrance(const CGTeleport * obj) const
@@ -448,7 +448,7 @@ bool CPathfinderHelper::addTeleportOneWay(const CGTeleport * obj) const
 {
 	if(options.useTeleportOneWay && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
 	{
-		auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner));
+		auto passableExits = CGTeleport::getPassableExits(gameState(), hero, getTeleportChannelExits(obj->channel, hero->tempOwner));
 		if(passableExits.size() == 1)
 			return true;
 	}
@@ -459,7 +459,7 @@ bool CPathfinderHelper::addTeleportOneWayRandom(const CGTeleport * obj) const
 {
 	if(options.useTeleportOneWayRandom && isTeleportChannelUnidirectional(obj->channel, hero->tempOwner))
 	{
-		auto passableExits = CGTeleport::getPassableExits(gs, hero, getTeleportChannelExits(obj->channel, hero->tempOwner));
+		auto passableExits = CGTeleport::getPassableExits(gameState(), hero, getTeleportChannelExits(obj->channel, hero->tempOwner));
 		if(passableExits.size() > 1)
 			return true;
 	}
@@ -498,8 +498,8 @@ int CPathfinderHelper::getGuardiansCount(int3 tile) const
 }
 
 CPathfinderHelper::CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options):
-	CGameInfoCallback(gs),
 	turn(-1),
+	gs(gs),
 	hero(Hero),
 	options(Options),
 	owner(Hero->tempOwner)
@@ -572,7 +572,7 @@ void CPathfinderHelper::getNeighbours(
 	const boost::logic::tribool & onLand,
 	const bool limitCoastSailing) const
 {
-	const CMap * map = &gs->getMap();
+	const CMap * map = &gameState()->getMap();
 	const TerrainType * sourceTerrain = sourceTile.getTerrain();
 
 	static constexpr std::array dirs = {

+ 3 - 0
lib/pathfinder/CPathfinder.h

@@ -69,6 +69,7 @@ class DLL_LINKAGE CPathfinderHelper : private CGameInfoCallback
 	/// returns base movement cost for movement between specific tiles. Does not accounts for diagonal movement or last tile exception
 	ui32 getTileMovementCost(const TerrainTile & dest, const TerrainTile & from, const TurnInfo * ti) const;
 
+	CGameState * gs;
 public:
 	enum EPatrolState
 	{
@@ -89,6 +90,8 @@ public:
 
 	CPathfinderHelper(CGameState * gs, const CGHeroInstance * Hero, const PathfinderOptions & Options);
 	virtual ~CPathfinderHelper();
+	CGameState * gameState() final { return gs; }
+	const CGameState * gameState() const final { return gs; }
 	void initializePatrol();
 	bool isHeroPatrolLocked() const;
 	bool canMoveFromNode(const PathNodeInfo & source) const;

+ 47 - 49
server/CGameHandler.cpp

@@ -113,7 +113,7 @@ const Services * CGameHandler::services() const
 
 const CGameHandler::BattleCb * CGameHandler::battle(const BattleID & battleID) const
 {
-	return gs->getBattle(battleID);
+	return gameState()->getBattle(battleID);
 }
 
 const CGameHandler::GameCb * CGameHandler::game() const
@@ -348,8 +348,8 @@ void CGameHandler::giveExperience(const CGHeroInstance * hero, TExpType amountTo
 	TExpType maxExp = LIBRARY->heroh->reqExp(LIBRARY->heroh->maxSupportedLevel());
 	TExpType currExp = hero->exp;
 
-	if (gs->getMap().levelLimit != 0)
-		maxExp = LIBRARY->heroh->reqExp(gs->getMap().levelLimit);
+	if (gameState()->getMap().levelLimit != 0)
+		maxExp = LIBRARY->heroh->reqExp(gameState()->getMap().levelLimit);
 
 	TExpType canGainExp = 0;
 	if (maxExp > currExp)
@@ -423,7 +423,7 @@ void CGameHandler::changeSecSkill(const CGHeroInstance * hero, SecondarySkill wh
 
 void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 {
-	if(gameLobby().getState() == EServerState::SHUTDOWN || !gs || !gs->getStartInfo())
+	if(gameLobby().getState() == EServerState::SHUTDOWN || !gameState() || !gameState()->getStartInfo())
 	{
 		assert(0); // game should have shut down before reaching this point!
 		return;
@@ -432,7 +432,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 	for(auto & playerConnections : connections)
 	{
 		PlayerColor playerId = playerConnections.first;
-		auto * playerSettings = gs->getStartInfo()->getPlayersSettings(playerId.getNum());
+		auto * playerSettings = gameState()->getStartInfo()->getPlayersSettings(playerId.getNum());
 		if(!playerSettings)
 			continue;
 		
@@ -443,7 +443,7 @@ void CGameHandler::handleClientDisconnection(std::shared_ptr<CConnection> c)
 		logGlobal->trace("Player %s disconnected. Notifying remaining players", playerId.toString());
 
 		// this player have left the game - broadcast infowindow to all in-game players
-		for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
+		for (auto i = gameState()->players.cbegin(); i!=gameState()->players.cend(); i++)
 		{
 			if (i->first == playerId)
 				continue;
@@ -526,8 +526,6 @@ CGameHandler::CGameHandler(CVCMIServer * lobby)
 CGameHandler::~CGameHandler()
 {
 	delete spellEnv;
-	delete gs;
-	gs = nullptr;
 }
 
 void CGameHandler::reinitScripting()
@@ -546,16 +544,16 @@ void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTrack
 	logGlobal->info("Using random seed: %d", randomNumberGenerator->nextInt());
 
 	CMapService mapService;
-	gs = new CGameState();
+	gs = std::make_shared<CGameState>();
 	gs->preInit(LIBRARY, this);
 	logGlobal->info("Gamestate created!");
 	gs->init(&mapService, si, progressTracking);
 	logGlobal->info("Gamestate initialized!");
 
-	for (auto & elem : gs->players)
+	for (auto & elem : gameState()->players)
 		turnOrder->addPlayer(elem.first);
 
-//	for (auto & elem : gs->getMap().allHeroes)
+//	for (auto & elem : gameState()->getMap().allHeroes)
 //	{
 //		if(elem)
 //			heroPool->getHeroSkillsRandomGenerator(elem->getHeroTypeID()); // init RMG seed
@@ -620,12 +618,12 @@ void CGameHandler::onPlayerTurnEnded(PlayerColor which)
 
 void CGameHandler::addStatistics(StatisticDataSet &stat) const
 {
-	for (const auto & elem : gs->players)
+	for (const auto & elem : gameState()->players)
 	{
 		if (elem.first == PlayerColor::NEUTRAL || !elem.first.isValidPlayer())
 			continue;
 
-		auto data = StatisticDataSet::createEntry(&elem.second, gs);
+		auto data = StatisticDataSet::createEntry(&elem.second, gameState());
 
 		stat.add(data);
 	}
@@ -633,14 +631,14 @@ void CGameHandler::addStatistics(StatisticDataSet &stat) const
 
 void CGameHandler::onNewTurn()
 {
-	logGlobal->trace("Turn %d", gs->day+1);
+	logGlobal->trace("Turn %d", gameState()->day+1);
 
 	bool firstTurn = !getDate(Date::DAY);
 	bool newMonth = getDate(Date::DAY_OF_MONTH) == 28;
 
 	if (firstTurn)
 	{
-		for (auto obj : gs->getMap().getObjects<CGHeroInstance>())
+		for (auto obj : gameState()->getMap().getObjects<CGHeroInstance>())
 		{
 			if (obj->ID == Obj::PRISON) //give imprisoned hero 0 exp to level him up. easiest to do at this point
 			{
@@ -648,7 +646,7 @@ void CGameHandler::onNewTurn()
 			}
 		}
 
-		for (auto & elem : gs->players)
+		for (auto & elem : gameState()->players)
 			heroPool->onNewWeek(elem.first);
 
 	}
@@ -677,7 +675,7 @@ void CGameHandler::onNewTurn()
 		auto t = gameState()->getTown(townID);
 		if (t->hasBonusOfType (BonusType::DARKNESS))
 		{
-			for (auto & player : gs->players)
+			for (auto & player : gameState()->players)
 			{
 				if (getPlayerStatus(player.first) == EPlayerStatus::INGAME &&
 					getPlayerRelations(player.first, t->tempOwner) == PlayerRelations::ENEMIES)
@@ -700,7 +698,7 @@ void CGameHandler::onNewTurn()
 		checkVictoryLossConditionsForAll(); // check for map turn limit
 
 	//call objects
-	for (auto & elem : gs->getMap().getObjects())
+	for (auto & elem : gameState()->getMap().getObjects())
 	{
 		if (elem)
 			elem->newTurn(getRandomGenerator());
@@ -734,7 +732,7 @@ void CGameHandler::start(bool resume)
 	{
 		onNewTurn();
 		events::TurnStarted::defaultExecute(serverEventBus.get());
-		for(auto & player : gs->players)
+		for(auto & player : gameState()->players)
 			turnTimerHandler->onGameplayStart(player.first);
 	}
 	else
@@ -804,7 +802,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	// not turn of that hero or player can't simply teleport hero (at least not with this function)
 	if(!h || (asker != PlayerColor::NEUTRAL && movementMode != EMovementMode::STANDARD))
 	{
-		if(h && getStartInfo()->turnTimerInfo.isEnabled() && gs->players[h->getOwner()].turnTimer.turnTimer == 0)
+		if(h && getStartInfo()->turnTimerInfo.isEnabled() && gameState()->players[h->getOwner()].turnTimer.turnTimer == 0)
 			return true; //timer expired, no error
 		
 		logGlobal->error("Illegal call to move hero!");
@@ -814,14 +812,14 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	logGlobal->trace("Player %d (%s) wants to move hero %d from %s to %s", asker, asker.toString(), hid.getNum(), h->anchorPos().toString(), dst.toString());
 	const int3 hmpos = h->convertToVisitablePos(dst);
 
-	if (!gs->getMap().isInTheMap(hmpos))
+	if (!gameState()->getMap().isInTheMap(hmpos))
 	{
 		logGlobal->error("Destination tile is outside the map!");
 		return false;
 	}
 
 	const TerrainTile t = *getTile(hmpos);
-	const int3 guardPos = gs->guardingCreaturePosition(hmpos);
+	const int3 guardPos = gameState()->guardingCreaturePosition(hmpos);
 	CGObjectInstance * objectToVisit = nullptr;
 	CGObjectInstance * guardian = nullptr;
 
@@ -853,7 +851,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	tmh.movePoints = h->movementPointsRemaining();
 
 	//check if destination tile is available
-	auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gs, h, PathfinderOptions(this));
+	auto pathfinderHelper = std::make_unique<CPathfinderHelper>(gameState(), h, PathfinderOptions(this));
 	auto ti = pathfinderHelper->getTurnInfo();
 
 	const bool canFly = ti->hasFlyingMovement() || (h->boat && h->boat->layer == EPathfindingLayer::AIR);
@@ -913,7 +911,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 	// should be called if hero changes tile but before applying TryMoveHero package
 	auto leaveTile = [&]()
 	{
-		for(const auto & objID : gs->getMap().getTile(h->visitablePos()).visitableObjects)
+		for(const auto & objID : gameState()->getMap().getTile(h->visitablePos()).visitableObjects)
 			gameState()->getObjInstance(objID)->onHeroLeave(h);
 
 		this->getTilesInRange(tmh.fowRevealed, h->getSightCenter()+(tmh.end-tmh.start), h->getSightRadius(), ETileVisibility::HIDDEN, h->tempOwner);
@@ -1042,7 +1040,7 @@ bool CGameHandler::moveHero(ObjectInstanceID hid, int3 dst, EMovementMode moveme
 
 		turnTimerHandler->setEndTurnAllowed(h->getOwner(), !movingOntoWater && !movingOntoObstacle);
 		doMove(TryMoveHero::SUCCESS, lookForGuards, visitDest, LEAVING_TILE);
-		gs->statistic.accumulatedValues[asker].movementPointsUsed += tmh.movePoints;
+		gameState()->statistic.accumulatedValues[asker].movementPointsUsed += tmh.movePoints;
 		return true;
 	}
 }
@@ -1086,7 +1084,7 @@ void CGameHandler::setOwner(const CGObjectInstance * obj, const PlayerColor owne
 	const CGTownInstance * town = dynamic_cast<const CGTownInstance *>(obj);
 	if (town) //town captured
 	{
-		gs->statistic.accumulatedValues[owner].lastCapturedTownDay = gs->getDate(Date::DAY);
+		gameState()->statistic.accumulatedValues[owner].lastCapturedTownDay = gameState()->getDate(Date::DAY);
 
 		if (owner.isValidPlayer()) //new owner is real player
 		{
@@ -1455,8 +1453,8 @@ void CGameHandler::sendToAllClients(CPackForClient & pack)
 void CGameHandler::sendAndApply(CPackForClient & pack)
 {
 	sendToAllClients(pack);
-	gs->apply(pack);
-	logNetwork->trace("\tApplied on gs: %s", typeid(pack).name());
+	gameState()->apply(pack);
+	logNetwork->trace("\tApplied on gameState(): %s", typeid(pack).name());
 }
 
 void CGameHandler::sendAndApply(CGarrisonOperationPack & pack)
@@ -1615,8 +1613,8 @@ bool CGameHandler::load(const std::string & filename)
 		gameLobby().announceMessage(str);
 		return false;
 	}
-	gs->preInit(LIBRARY, this);
-	gs->updateOnLoad(gameLobby().si.get());
+	gameState()->preInit(LIBRARY, this);
+	gameState()->updateOnLoad(gameLobby().si.get());
 	return true;
 }
 
@@ -2191,7 +2189,7 @@ bool CGameHandler::buildStructure(ObjectInstanceID tid, BuildingID requestedID,
 	if(!force)
 	{
 		giveResources(t->tempOwner, -requestedBuilding->resources);
-		gs->statistic.accumulatedValues[t->tempOwner].spentResourcesForBuildings += requestedBuilding->resources;
+		gameState()->statistic.accumulatedValues[t->tempOwner].spentResourcesForBuildings += requestedBuilding->resources;
 	}
 
 	//We know what has been built, apply changes. Do this as final step to properly update town window
@@ -2279,7 +2277,7 @@ bool CGameHandler::razeStructure (ObjectInstanceID tid, BuildingID bid)
 
 bool CGameHandler::spellResearch(ObjectInstanceID tid, SpellID spellAtSlot, bool accepted)
 {
-	CGTownInstance *t = gs->getTown(tid);
+	CGTownInstance *t = gameState()->getTown(tid);
 
 	if(!getSettings().getBoolean(EGameSettings::TOWNS_SPELL_RESEARCH) && complain("Spell research not allowed!"))
 		return false;
@@ -2391,7 +2389,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst
 	//recruit
 	TResources cost = (c->getFullRecruitCost() * cram);
 	giveResources(army->tempOwner, -cost);
-	gs->statistic.accumulatedValues[army->tempOwner].spentResourcesForArmy += cost;
+	gameState()->statistic.accumulatedValues[army->tempOwner].spentResourcesForArmy += cost;
 
 	SetAvailableCreatures sac;
 	sac.tid = objid;
@@ -2454,7 +2452,7 @@ bool CGameHandler::upgradeCreature(ObjectInstanceID objid, SlotID pos, CreatureI
 
 	//take resources
 	giveResources(player, -totalCost);
-	gs->statistic.accumulatedValues[player].spentResourcesForArmy += totalCost;
+	gameState()->statistic.accumulatedValues[player].spentResourcesForArmy += totalCost;
 
 	//upgrade creature
 	changeStackType(StackLocation(obj->id, pos), upgID.toCreature());
@@ -3066,7 +3064,7 @@ bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, GameRe
 	if(dynamic_cast<const CGTownInstance *>(m))
 	{
 		saa.id = ObjectInstanceID::NONE;
-		saa.arts = gs->getMap().townMerchantArtifacts;
+		saa.arts = gameState()->getMap().townMerchantArtifacts;
 	}
 	else if(const CGBlackMarket *bm = dynamic_cast<const CGBlackMarket *>(m)) //black market
 	{
@@ -3156,8 +3154,8 @@ bool CGameHandler::tradeResources(const IMarket *market, ui32 amountToSell, Play
 	giveResource(player, toSell, -b1 * amountToBoy);
 	giveResource(player, toBuy, b2 * amountToBoy);
 
-	gs->statistic.accumulatedValues[player].tradeVolume[toSell] += -b1 * amountToBoy;
-	gs->statistic.accumulatedValues[player].tradeVolume[toBuy] += b2 * amountToBoy;
+	gameState()->statistic.accumulatedValues[player].tradeVolume[toSell] += -b1 * amountToBoy;
+	gameState()->statistic.accumulatedValues[player].tradeVolume[toBuy] += b2 * amountToBoy;
 
 	return true;
 }
@@ -3485,7 +3483,7 @@ bool CGameHandler::buildBoat(ObjectInstanceID objid, PlayerColor playerID)
 	}
 
 	int3 tile = obj->bestLocation();
-	if (!gs->getMap().isInTheMap(tile))
+	if (!gameState()->getMap().isInTheMap(tile))
 	{
 		complain("Cannot find appropriate tile for a boat!");
 		return false;
@@ -3521,7 +3519,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 
 	if(!p || p->status != EPlayerStatus::INGAME) return;
 
-	auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player);
+	auto victoryLossCheckResult = gameState()->checkForVictoryAndLoss(player);
 
 	if (victoryLossCheckResult.victory() || victoryLossCheckResult.loss())
 	{
@@ -3539,7 +3537,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 		if (victoryLossCheckResult.victory())
 		{
 			//one player won -> all enemies lost
-			for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
+			for (auto i = gameState()->players.cbegin(); i!=gameState()->players.cend(); i++)
 			{
 				if (i->first != player && getPlayerState(i->first)->status == EPlayerStatus::INGAME)
 				{
@@ -3575,7 +3573,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 			}
 
 			//player lost -> all his objects become unflagged (neutral)
-			for (auto obj : gs->getMap().getObjects()) //unflag objs
+			for (auto obj : gameState()->getMap().getObjects()) //unflag objs
 			{
 				if (obj && obj->tempOwner == player)
 					setOwner(obj, PlayerColor::NEUTRAL);
@@ -3585,7 +3583,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
 			std::set<PlayerColor> playerColors;
 
 			//do not copy player state (CBonusSystemNode) by value
-			for (auto &p : gs->players) //players may have different colors, iterate over players and not integers
+			for (auto &p : gameState()->players) //players may have different colors, iterate over players and not integers
 			{
 				if (p.first != player)
 					playerColors.insert(p.first);
@@ -3631,7 +3629,7 @@ bool CGameHandler::dig(const CGHeroInstance *h)
 	InfoWindow iw;
 	iw.type = EInfoWindowMode::AUTO;
 	iw.player = h->tempOwner;
-	if (gs->getMap().grailPos == h->visitablePos())
+	if (gameState()->getMap().grailPos == h->visitablePos())
 	{
 		ArtifactID grail = ArtifactID::GRAIL;
 
@@ -4062,7 +4060,7 @@ void CGameHandler::spawnWanderingMonsters(CreatureID creatureID)
 void CGameHandler::synchronizeArtifactHandlerLists()
 {
 	UpdateArtHandlerLists uahl;
-	uahl.allocatedArtifacts = gs->allocatedArtifacts;
+	uahl.allocatedArtifacts = gameState()->allocatedArtifacts;
 	sendAndApply(uahl);
 }
 
@@ -4253,15 +4251,15 @@ std::shared_ptr<CGObjectInstance> CGameHandler::createNewObject(const int3 & vis
 {
 	TerrainId terrainType = ETerrainId::NONE;
 
-	if (!gs->isInTheMap(visitablePosition))
+	if (!gameState()->isInTheMap(visitablePosition))
 		throw std::runtime_error("Attempt to create object outside map at " + visitablePosition.toString());
 
-	const TerrainTile & t = gs->getMap().getTile(visitablePosition);
+	const TerrainTile & t = gameState()->getMap().getTile(visitablePosition);
 	terrainType = t.getTerrainID();
 
 	auto handler = LIBRARY->objtypeh->getHandlerFor(objectID, subID);
 
-	auto o = handler->create(gs->callback, nullptr);
+	auto o = handler->create(gameState()->callback, nullptr);
 	handler->configureObject(o.get(), getRandomGenerator());
 	assert(o->ID == objectID);
 
@@ -4292,7 +4290,7 @@ void CGameHandler::createWanderingMonster(const int3 & visitablePosition, Creatu
 	cre->character = 2;
 	cre->gainedArtifact = ArtifactID::NONE;
 	cre->identifier = -1;
-	cre->addToSlot(SlotID(0), std::make_unique<CStackInstance>(gs->callback, creature, -1)); //add placeholder stack
+	cre->addToSlot(SlotID(0), std::make_unique<CStackInstance>(gameState()->callback, creature, -1)); //add placeholder stack
 
 	newObject(createdObject, PlayerColor::NEUTRAL);
 }
@@ -4311,7 +4309,7 @@ void CGameHandler::createHole(const int3 & visitablePosition, PlayerColor initia
 
 void CGameHandler::newObject(std::shared_ptr<CGObjectInstance> object, PlayerColor initiator)
 {
-	object->initObj(gs->getRandomGenerator());
+	object->initObj(gameState()->getRandomGenerator());
 
 	NewObject no;
 	no.newObject = object;

+ 4 - 0
server/CGameHandler.h

@@ -65,6 +65,7 @@ public:
 	std::unique_ptr<TurnTimerHandler> turnTimerHandler;
 	std::unique_ptr<NewTurnProcessor> newTurnProcessor;
 	std::unique_ptr<CRandomGenerator> randomNumberGenerator;
+	std::shared_ptr<CGameState> gs;
 
 	//use enums as parameters, because doMove(sth, true, false, true) is not readable
 	enum EGuardLook {CHECK_FOR_GUARDS, IGNORE_GUARDS};
@@ -91,6 +92,9 @@ public:
 	bool isAllowedExchange(ObjectInstanceID id1, ObjectInstanceID id2);
 	void giveSpells(const CGTownInstance *t, const CGHeroInstance *h);
 
+	CGameState * gameState() final { return gs.get(); }
+	const CGameState * gameState() const final { return gs.get(); }
+
 	// Helpers to create new object of specified type
 
 	std::shared_ptr<CGObjectInstance> createNewObject(const int3 & visitablePosition, MapObjectID objectID, MapObjectSubID subID);

+ 1 - 1
server/CVCMIServer.cpp

@@ -294,7 +294,7 @@ bool CVCMIServer::prepareToStartGame()
 void CVCMIServer::startGameImmediately()
 {
 	for(auto activeConnection : activeConnections)
-		activeConnection->enterGameplayConnectionMode(gh->gs);
+		activeConnection->enterGameplayConnectionMode(gh->gs.get());
 
 	gh->start(si->mode == EStartMode::LOAD_GAME);
 	setState(EServerState::GAMEPLAY);

+ 1 - 1
server/NetPacksLobbyServer.cpp

@@ -226,7 +226,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
 	}
 	
 	pack.initializedStartInfo = std::make_shared<StartInfo>(*srv.gh->getInitialStartInfo());
-	pack.initializedGameState = srv.gh->gameState();
+	pack.initializedGameState = srv.gh->gs;
 	result = true;
 }
 

+ 1 - 1
test/game/CGameStateTest.cpp

@@ -49,7 +49,7 @@ public:
 	void SetUp() override
 	{
 		gameState = std::make_shared<CGameState>();
-		gameCallback->setGameState(gameState.get());
+		gameCallback->setGameState(gameState);
 		gameState->preInit(&services, gameCallback.get());
 	}
 

+ 2 - 2
test/mock/mock_IGameCallback.cpp

@@ -22,9 +22,9 @@ GameCallbackMock::~GameCallbackMock()
 
 }
 
-void GameCallbackMock::setGameState(CGameState * gameState)
+void GameCallbackMock::setGameState(std::shared_ptr<CGameState> newGameState)
 {
-	gs = gameState;
+	gamestate = newGameState;
 }
 
 void GameCallbackMock::sendAndApply(CPackForClient & pack)

+ 5 - 1
test/mock/mock_IGameCallback.h

@@ -20,13 +20,17 @@
 
 class GameCallbackMock : public IGameCallback
 {
+	std::shared_ptr<CGameState> gamestate;
 public:
 	using UpperCallback = ::ServerCallback;
 
 	GameCallbackMock(UpperCallback * upperCallback_);
 	virtual ~GameCallbackMock();
 
-	void setGameState(CGameState * gameState);
+	void setGameState(std::shared_ptr<CGameState> gameState);
+	CGameState * gameState() final { return gamestate.get(); }
+	const CGameState * gameState() const final { return gamestate.get(); }
+
 
 	///STUBS, to be removed as long as same methods moved from GameHandler