1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-29 23:07:48 +02:00

Merge pull request #2635 from Nordsoft91/loading-bar

Loading progress bar
This commit is contained in:
Nordsoft91
2023-08-23 17:28:19 +04:00
committed by GitHub
21 changed files with 229 additions and 43 deletions

View File

@@ -181,7 +181,10 @@ void CClient::newGame(CGameState * initializedGameState)
gs->preInit(VLC); gs->preInit(VLC);
logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff()); logNetwork->trace("\tCreating gamestate: %i", CSH->th->getDiff());
if(!initializedGameState) if(!initializedGameState)
gs->init(&mapService, CSH->si.get(), settings["general"]["saveRandomMaps"].Bool()); {
Load::ProgressAccumulator progressTracking;
gs->init(&mapService, CSH->si.get(), progressTracking, settings["general"]["saveRandomMaps"].Bool());
}
logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff()); logNetwork->trace("Initializing GameState (together): %d ms", CSH->th->getDiff());
initMapHandler(); initMapHandler();

View File

@@ -53,6 +53,7 @@ public:
virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override; virtual void visitLobbyChatMessage(LobbyChatMessage & pack) override;
virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override; virtual void visitLobbyGuiAction(LobbyGuiAction & pack) override;
virtual void visitLobbyStartGame(LobbyStartGame & pack) override; virtual void visitLobbyStartGame(LobbyStartGame & pack) override;
virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) override;
virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override; virtual void visitLobbyUpdateState(LobbyUpdateState & pack) override;
virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override; virtual void visitLobbyShowMessage(LobbyShowMessage & pack) override;
}; };

View File

@@ -59,6 +59,9 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyClientDisconnected(LobbyClient
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack) void ApplyOnLobbyScreenNetPackVisitor::visitLobbyClientDisconnected(LobbyClientDisconnected & pack)
{ {
if(auto w = GH.windows().topWindow<CLoadingScreen>())
GH.windows().popWindow(w);
if(GH.windows().count() > 0) if(GH.windows().count() > 0)
GH.windows().popWindows(1); GH.windows().popWindows(1);
} }
@@ -122,16 +125,31 @@ void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pac
handler.si = pack.initializedStartInfo; handler.si = pack.initializedStartInfo;
handler.si->mode = modeBackup; handler.si->mode = modeBackup;
} }
if(settings["session"]["headless"].Bool()) handler.startGameplay(pack.initializedGameState);
handler.startGameplay(pack.initializedGameState);
} }
void ApplyOnLobbyScreenNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack) void ApplyOnLobbyScreenNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
{ {
if(pack.clientId != -1 && pack.clientId != handler.c->connectionID) if(auto w = GH.windows().topWindow<CLoadingScreen>())
return; {
w->finish();
w->tick(0);
w->redraw();
}
else
GH.windows().createAndPushWindow<CLoadingScreen>();
}
GH.windows().createAndPushWindow<CLoadingScreen>(std::bind(&CServerHandler::startGameplay, &handler, pack.initializedGameState)); void ApplyOnLobbyScreenNetPackVisitor::visitLobbyLoadProgress(LobbyLoadProgress & pack)
{
if(auto w = GH.windows().topWindow<CLoadingScreen>())
{
w->set(pack.progress);
w->tick(0);
w->redraw();
}
else
GH.windows().createAndPushWindow<CLoadingScreen>();
} }
void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & pack) void ApplyOnLobbyHandlerNetPackVisitor::visitLobbyUpdateState(LobbyUpdateState & pack)

View File

@@ -582,36 +582,67 @@ void CSimpleJoinScreen::connectThread(const std::string & addr, ui16 port)
}); });
} }
CLoadingScreen::CLoadingScreen(std::function<void()> loader) CLoadingScreen::CLoadingScreen()
: CWindowObject(BORDERED, getBackground()), loadingThread(loader) : CWindowObject(BORDERED, getBackground())
{ {
OBJ_CONSTRUCTION_CAPTURING_ALL_NO_DISPOSE;
addUsedEvents(TIME);
CCS->musich->stopMusic(5000); CCS->musich->stopMusic(5000);
const auto & conf = CMainMenuConfig::get().getConfig()["loading"];
if(conf.isStruct())
{
const int posx = conf["x"].Integer(), posy = conf["y"].Integer();
const int blockSize = conf["size"].Integer();
const int blocksAmount = conf["amount"].Integer();
for(int i = 0; i < blocksAmount; ++i)
{
progressBlocks.push_back(std::make_shared<CAnimImage>(conf["name"].String(), i, 0, posx + i * blockSize, posy));
progressBlocks.back()->deactivate();
progressBlocks.back()->visible = false;
}
}
} }
CLoadingScreen::~CLoadingScreen() CLoadingScreen::~CLoadingScreen()
{ {
loadingThread.join();
} }
void CLoadingScreen::showAll(Canvas & to) void CLoadingScreen::tick(uint32_t msPassed)
{ {
//FIXME: filling screen with transparency? BLACK intended? if(!progressBlocks.empty())
//Rect rect(0, 0, to->w, to->h); {
//CSDL_Ext::fillRect(to, rect, Colors::TRANSPARENCY); int status = float(get()) / 255.f * progressBlocks.size();
CWindowObject::showAll(to); for(int i = 0; i < status; ++i)
{
progressBlocks.at(i)->activate();
progressBlocks.at(i)->visible = true;
}
}
} }
std::string CLoadingScreen::getBackground() std::string CLoadingScreen::getBackground()
{ {
const auto & conf = CMainMenuConfig::get().getConfig()["loading"].Vector(); std::string fname = "loadbar";
const auto & conf = CMainMenuConfig::get().getConfig()["loading"];
if(conf.empty()) if(conf.isStruct())
{ {
return "loadbar"; if(conf["background"].isVector())
} return RandomGeneratorUtil::nextItem(conf["background"].Vector(), CRandomGenerator::getDefault())->String();
else
{ if(conf["background"].isString())
return RandomGeneratorUtil::nextItem(conf, CRandomGenerator::getDefault())->String(); return conf["background"].String();
return fname;
} }
if(conf.isVector() && !conf.Vector().empty())
return RandomGeneratorUtil::nextItem(conf.Vector(), CRandomGenerator::getDefault())->String();
return fname;
} }

View File

@@ -11,6 +11,7 @@
#include "../windows/CWindowObject.h" #include "../windows/CWindowObject.h"
#include "../../lib/JsonNode.h" #include "../../lib/JsonNode.h"
#include "../../lib/LoadProgress.h"
VCMI_LIB_NAMESPACE_BEGIN VCMI_LIB_NAMESPACE_BEGIN
@@ -22,9 +23,11 @@ class CTextInput;
class CGStatusBar; class CGStatusBar;
class CTextBox; class CTextBox;
class CTabbedInt; class CTabbedInt;
class CAnimImage;
class CAnimation; class CAnimation;
class CButton; class CButton;
class CFilledTexture; class CFilledTexture;
class CLabel;
// TODO: Find new location for these enums // TODO: Find new location for these enums
@@ -178,17 +181,17 @@ public:
CSimpleJoinScreen(bool host = true); CSimpleJoinScreen(bool host = true);
}; };
class CLoadingScreen : public CWindowObject class CLoadingScreen : virtual public CWindowObject, virtual public Load::Progress
{ {
boost::thread loadingThread; std::vector<std::shared_ptr<CAnimImage>> progressBlocks;
std::string getBackground(); std::string getBackground();
public: public:
CLoadingScreen(std::function<void()> loader); CLoadingScreen();
~CLoadingScreen(); ~CLoadingScreen();
void showAll(Canvas & to) override; void tick(uint32_t msPassed) override;
}; };
extern std::shared_ptr<CMainMenu> CMM; extern std::shared_ptr<CMainMenu> CMM;

View File

@@ -2,7 +2,11 @@
//images used in game selection screen //images used in game selection screen
"game-select" : ["gamselb0", "gamselb1"], "game-select" : ["gamselb0", "gamselb1"],
"loading" : ["loadbar"], "loading" :
{
"background" : ["loadbar"],
"x": 395, "y": 548, "size": 18, "amount": 20, "name": "loadprog"
},
//Main menu window, consists of several sub-menus aka items //Main menu window, consists of several sub-menus aka items
"window": "window":

View File

@@ -18,6 +18,11 @@ Progress::Progress(): _progress(std::numeric_limits<Type>::min())
setupSteps(100); setupSteps(100);
} }
Progress::Progress(int steps): _progress(std::numeric_limits<Type>::min())
{
setupSteps(steps);
}
Type Progress::get() const Type Progress::get() const
{ {
if(_step >= _maxSteps) if(_step >= _maxSteps)
@@ -82,3 +87,49 @@ void Progress::step(int count)
_step += count; _step += count;
} }
} }
void ProgressAccumulator::include(const Progress & p)
{
boost::unique_lock<boost::mutex> guard(_mx);
_progress.emplace_back(p);
}
void ProgressAccumulator::exclude(const Progress & p)
{
boost::unique_lock<boost::mutex> guard(_mx);
for(auto i = _progress.begin(); i != _progress.end(); ++i)
{
if(&i->get() == &p)
{
_accumulated += static_cast<long long>(p.get()) * p._maxSteps;
_steps += p._maxSteps;
_progress.erase(i);
return;
}
}
}
bool ProgressAccumulator::finished() const
{
boost::unique_lock<boost::mutex> guard(_mx);
for(auto i : _progress)
if(!i.get().finished())
return false;
return true;
}
Type ProgressAccumulator::get() const
{
boost::unique_lock<boost::mutex> guard(_mx);
auto sum = _accumulated;
auto totalSteps = _steps;
for(auto p : _progress)
{
sum += static_cast<long long>(p.get().get()) * p.get()._maxSteps;
totalSteps += p.get()._maxSteps;
}
if(totalSteps)
sum /= totalSteps;
return static_cast<Type>(sum);
}

View File

@@ -18,6 +18,8 @@ namespace Load
using Type = unsigned char; using Type = unsigned char;
class ProgressAccumulator;
/* /*
* Purpose of that class is to track progress of computations * Purpose of that class is to track progress of computations
* Derive from this class if you want to translate user or system * Derive from this class if you want to translate user or system
@@ -29,8 +31,9 @@ class DLL_LINKAGE Progress
public: public:
//Sets current state to 0. //Sets current state to 0.
//Amount of steps to finish progress will be equal to 100 //Amount of steps to finish progress will be equal to 100 for default constructor
Progress(); Progress();
Progress(int steps);
virtual ~Progress() = default; virtual ~Progress() = default;
//Returns current state of the progress //Returns current state of the progress
@@ -67,5 +70,25 @@ public:
private: private:
std::atomic<Type> _progress, _target; std::atomic<Type> _progress, _target;
std::atomic<int> _step, _maxSteps; std::atomic<int> _step, _maxSteps;
friend class ProgressAccumulator;
}; };
class DLL_LINKAGE ProgressAccumulator
{
public:
ProgressAccumulator() = default;
void include(const Progress &);
void exclude(const Progress &);
bool finished() const;
Type get() const;
private:
mutable boost::mutex _mx;
long long _accumulated = 0, _steps = 0;
std::vector<std::reference_wrapper<const Progress>> _progress;
};
} }

View File

@@ -145,6 +145,7 @@ public:
virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) {} virtual void visitLobbyClientDisconnected(LobbyClientDisconnected & pack) {}
virtual void visitLobbyChatMessage(LobbyChatMessage & pack) {} virtual void visitLobbyChatMessage(LobbyChatMessage & pack) {}
virtual void visitLobbyGuiAction(LobbyGuiAction & pack) {} virtual void visitLobbyGuiAction(LobbyGuiAction & pack) {}
virtual void visitLobbyLoadProgress(LobbyLoadProgress & pack) {}
virtual void visitLobbyEndGame(LobbyEndGame & pack) {} virtual void visitLobbyEndGame(LobbyEndGame & pack) {}
virtual void visitLobbyStartGame(LobbyStartGame & pack) {} virtual void visitLobbyStartGame(LobbyStartGame & pack) {}
virtual void visitLobbyChangeHost(LobbyChangeHost & pack) {} virtual void visitLobbyChangeHost(LobbyChangeHost & pack) {}

View File

@@ -688,6 +688,11 @@ void LobbyGuiAction::visitTyped(ICPackVisitor & visitor)
visitor.visitLobbyGuiAction(*this); visitor.visitLobbyGuiAction(*this);
} }
void LobbyLoadProgress::visitTyped(ICPackVisitor & visitor)
{
visitor.visitLobbyLoadProgress(*this);
}
void LobbyEndGame::visitTyped(ICPackVisitor & visitor) void LobbyEndGame::visitTyped(ICPackVisitor & visitor)
{ {
visitor.visitLobbyEndGame(*this); visitor.visitLobbyEndGame(*this);

View File

@@ -99,6 +99,18 @@ struct DLL_LINKAGE LobbyGuiAction : public CLobbyPackToPropagate
} }
}; };
struct DLL_LINKAGE LobbyLoadProgress : public CLobbyPackToPropagate
{
unsigned char progress;
virtual void visitTyped(ICPackVisitor & visitor) override;
template <typename Handler> void serialize(Handler &h, const int version)
{
h & progress;
}
};
struct DLL_LINKAGE LobbyEndGame : public CLobbyPackToPropagate struct DLL_LINKAGE LobbyEndGame : public CLobbyPackToPropagate
{ {
bool closeConnection = false, restart = false; bool closeConnection = false, restart = false;

View File

@@ -404,7 +404,7 @@ void CGameState::preInit(Services * services)
this->services = services; this->services = services;
} }
void CGameState::init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap) void CGameState::init(const IMapService * mapService, StartInfo * si, Load::ProgressAccumulator & progressTracking, bool allowSavingRandomMap)
{ {
preInitAuto(); preInitAuto();
logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed); logGlobal->info("\tUsing random seed: %d", si->seedToBeUsed);
@@ -416,7 +416,7 @@ void CGameState::init(const IMapService * mapService, StartInfo * si, bool allow
switch(scenarioOps->mode) switch(scenarioOps->mode)
{ {
case StartInfo::NEW_GAME: case StartInfo::NEW_GAME:
initNewGame(mapService, allowSavingRandomMap); initNewGame(mapService, allowSavingRandomMap, progressTracking);
break; break;
case StartInfo::CAMPAIGN: case StartInfo::CAMPAIGN:
initCampaign(); initCampaign();
@@ -535,7 +535,7 @@ void CGameState::preInitAuto()
} }
} }
void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap) void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking)
{ {
if(scenarioOps->createRandomMap()) if(scenarioOps->createRandomMap())
{ {
@@ -544,8 +544,10 @@ void CGameState::initNewGame(const IMapService * mapService, bool allowSavingRan
// Gen map // Gen map
CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed); CMapGenerator mapGenerator(*scenarioOps->mapGenOptions, scenarioOps->seedToBeUsed);
progressTracking.include(mapGenerator);
std::unique_ptr<CMap> randomMap = mapGenerator.generate(); std::unique_ptr<CMap> randomMap = mapGenerator.generate();
progressTracking.exclude(mapGenerator);
if(allowSavingRandomMap) if(allowSavingRandomMap)
{ {

View File

@@ -11,6 +11,7 @@
#include "bonuses/CBonusSystemNode.h" #include "bonuses/CBonusSystemNode.h"
#include "IGameCallback.h" #include "IGameCallback.h"
#include "LoadProgress.h"
namespace boost namespace boost
{ {
@@ -89,7 +90,7 @@ public:
void preInit(Services * services); void preInit(Services * services);
void init(const IMapService * mapService, StartInfo * si, bool allowSavingRandomMap = false); void init(const IMapService * mapService, StartInfo * si, Load::ProgressAccumulator &, bool allowSavingRandomMap = false);
void updateOnLoad(StartInfo * si); void updateOnLoad(StartInfo * si);
ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized) ConstTransitivePtr<StartInfo> scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized)
@@ -166,7 +167,7 @@ public:
private: private:
// ----- initialization ----- // ----- initialization -----
void preInitAuto(); void preInitAuto();
void initNewGame(const IMapService * mapService, bool allowSavingRandomMap); void initNewGame(const IMapService * mapService, bool allowSavingRandomMap, Load::ProgressAccumulator & progressTracking);
void checkMapChecksum(); void checkMapChecksum();
void initGlobalBonuses(); void initGlobalBonuses();
void initGrailPosition(); void initGrailPosition();

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 643 B

View File

@@ -579,7 +579,7 @@ void CGameHandler::reinitScripting()
#endif #endif
} }
void CGameHandler::init(StartInfo *si) void CGameHandler::init(StartInfo *si, Load::ProgressAccumulator & progressTracking)
{ {
if (si->seedToBeUsed == 0) if (si->seedToBeUsed == 0)
{ {
@@ -589,7 +589,7 @@ void CGameHandler::init(StartInfo *si)
gs = new CGameState(); gs = new CGameState();
gs->preInit(VLC); gs->preInit(VLC);
logGlobal->info("Gamestate created!"); logGlobal->info("Gamestate created!");
gs->init(&mapService, si); gs->init(&mapService, si, progressTracking);
logGlobal->info("Gamestate initialized!"); logGlobal->info("Gamestate initialized!");
// reset seed, so that clients can't predict any following random values // reset seed, so that clients can't predict any following random values

View File

@@ -13,6 +13,7 @@
#include "../lib/IGameCallback.h" #include "../lib/IGameCallback.h"
#include "../lib/battle/CBattleInfoCallback.h" #include "../lib/battle/CBattleInfoCallback.h"
#include "../lib/LoadProgress.h"
#include "../lib/ScriptHandler.h" #include "../lib/ScriptHandler.h"
#include "TurnTimerHandler.h" #include "TurnTimerHandler.h"
@@ -201,7 +202,7 @@ public:
void expGiven(const CGHeroInstance *hero); //triggers needed level-ups, handles also commander of this hero void expGiven(const CGHeroInstance *hero); //triggers needed level-ups, handles also commander of this hero
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
void init(StartInfo *si); void init(StartInfo *si, Load::ProgressAccumulator & progressTracking);
void handleClientDisconnection(std::shared_ptr<CConnection> c); void handleClientDisconnection(std::shared_ptr<CConnection> c);
void handleReceivedPack(CPackForServer * pack); void handleReceivedPack(CPackForServer * pack);
PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const; PlayerColor getPlayerAt(std::shared_ptr<CConnection> c) const;

View File

@@ -279,6 +279,26 @@ void CVCMIServer::prepareToRestart()
bool CVCMIServer::prepareToStartGame() bool CVCMIServer::prepareToStartGame()
{ {
Load::ProgressAccumulator progressTracking;
Load::Progress current(1);
progressTracking.include(current);
Load::Type currentProgress = std::numeric_limits<Load::Type>::max();
auto progressTrackingThread = boost::thread([this, &progressTracking, &currentProgress]()
{
while(!progressTracking.finished())
{
if(progressTracking.get() != currentProgress)
{
currentProgress = progressTracking.get();
std::unique_ptr<LobbyLoadProgress> loadProgress(new LobbyLoadProgress);
loadProgress->progress = currentProgress;
addToAnnounceQueue(std::move(loadProgress));
}
boost::this_thread::sleep(boost::posix_time::milliseconds(50));
}
});
gh = std::make_shared<CGameHandler>(this); gh = std::make_shared<CGameHandler>(this);
switch(si->mode) switch(si->mode)
{ {
@@ -286,18 +306,22 @@ bool CVCMIServer::prepareToStartGame()
logNetwork->info("Preparing to start new campaign"); logNetwork->info("Preparing to start new campaign");
si->campState->setCurrentMap(campaignMap); si->campState->setCurrentMap(campaignMap);
si->campState->setCurrentMapBonus(campaignBonus); si->campState->setCurrentMapBonus(campaignBonus);
gh->init(si.get()); gh->init(si.get(), progressTracking);
break; break;
case StartInfo::NEW_GAME: case StartInfo::NEW_GAME:
logNetwork->info("Preparing to start new game"); logNetwork->info("Preparing to start new game");
gh->init(si.get()); gh->init(si.get(), progressTracking);
break; break;
case StartInfo::LOAD_GAME: case StartInfo::LOAD_GAME:
logNetwork->info("Preparing to start loaded game"); logNetwork->info("Preparing to start loaded game");
if(!gh->load(si->mapname)) if(!gh->load(si->mapname))
{
current.finish();
progressTrackingThread.join();
return false; return false;
}
break; break;
default: default:
logNetwork->error("Wrong mode in StartInfo!"); logNetwork->error("Wrong mode in StartInfo!");
@@ -305,7 +329,9 @@ bool CVCMIServer::prepareToStartGame()
break; break;
} }
state = EServerState::GAMEPLAY_STARTING; current.finish();
progressTrackingThread.join();
return true; return true;
} }

View File

@@ -289,6 +289,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
result = false; result = false;
return; return;
} }
// Server will prepare gamestate and we announce StartInfo to clients // Server will prepare gamestate and we announce StartInfo to clients
if(!srv.prepareToStartGame()) if(!srv.prepareToStartGame())
{ {
@@ -299,6 +300,7 @@ void ApplyOnServerNetPackVisitor::visitLobbyStartGame(LobbyStartGame & pack)
pack.initializedStartInfo = std::make_shared<StartInfo>(*srv.gh->getStartInfo(true)); pack.initializedStartInfo = std::make_shared<StartInfo>(*srv.gh->getStartInfo(true));
pack.initializedGameState = srv.gh->gameState(); pack.initializedGameState = srv.gh->gameState();
srv.state = EServerState::GAMEPLAY_STARTING;
result = true; result = true;
} }

View File

@@ -178,7 +178,8 @@ public:
} }
gameState->init(&mapService, &si, false); Load::ProgressAccumulator progressTracker;
gameState->init(&mapService, &si, progressTracker, false);
ASSERT_NE(map, nullptr); ASSERT_NE(map, nullptr);
ASSERT_EQ(map->heroesOnMap.size(), 2); ASSERT_EQ(map->heroesOnMap.size(), 2);