1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-26 22:57:00 +02:00
vcmi/client/Client.cpp
Arseniy Shestakov 9fd1cff090 Refactoring: always use std prefix for shared_ptr, unique_ptr and make_shared
Long time ago it's was used without prefix to make future switch from boost to std version easier.
I discusses this with Ivan and decide to drop these using from Global.h now.

This change wouldn't break anything because there was already code with prefix for each of three cases.
2015-12-29 05:43:33 +03:00

1048 lines
29 KiB
C++

#include "StdInc.h"
#include "Client.h"
#include <SDL.h>
#include "CMusicHandler.h"
#include "../lib/mapping/CCampaignHandler.h"
#include "../CCallback.h"
#include "../lib/CConsoleHandler.h"
#include "CGameInfo.h"
#include "../lib/CGameState.h"
#include "CPlayerInterface.h"
#include "../lib/StartInfo.h"
#include "../lib/BattleState.h"
#include "../lib/CModHandler.h"
#include "../lib/CArtHandler.h"
#include "../lib/CGeneralTextHandler.h"
#include "../lib/CHeroHandler.h"
#include "../lib/CTownHandler.h"
#include "../lib/CBuildingHandler.h"
#include "../lib/spells/CSpellHandler.h"
#include "../lib/Connection.h"
#ifndef VCMI_ANDROID
#include "../lib/Interprocess.h"
#endif
#include "../lib/NetPacks.h"
#include "../lib/VCMI_Lib.h"
#include "../lib/VCMIDirs.h"
#include "../lib/mapping/CMap.h"
#include "../lib/JsonNode.h"
#include "mapHandler.h"
#include "../lib/CConfigHandler.h"
#include "Client.h"
#include "CPreGame.h"
#include "battle/CBattleInterface.h"
#include "../lib/CThreadHelper.h"
#include "../lib/CScriptingModule.h"
#include "../lib/registerTypes/RegisterTypes.h"
#include "gui/CGuiHandler.h"
#include "CMT.h"
extern std::string NAME;
#ifndef VCMI_ANDROID
namespace intpr = boost::interprocess;
#endif
/*
* Client.cpp, part of VCMI engine
*
* Authors: listed in file AUTHORS in main folder
*
* License: GNU General Public License v2.0 or later
* Full text of license available in license.txt file, in main folder
*
*/
template <typename T> class CApplyOnCL;
class CBaseForCLApply
{
public:
virtual void applyOnClAfter(CClient *cl, void *pack) const =0;
virtual void applyOnClBefore(CClient *cl, void *pack) const =0;
virtual ~CBaseForCLApply(){}
template<typename U> static CBaseForCLApply *getApplier(const U * t=nullptr)
{
return new CApplyOnCL<U>;
}
};
template <typename T> class CApplyOnCL : public CBaseForCLApply
{
public:
void applyOnClAfter(CClient *cl, void *pack) const override
{
T *ptr = static_cast<T*>(pack);
ptr->applyCl(cl);
}
void applyOnClBefore(CClient *cl, void *pack) const override
{
T *ptr = static_cast<T*>(pack);
ptr->applyFirstCl(cl);
}
};
template <> class CApplyOnCL<CPack> : public CBaseForCLApply
{
public:
void applyOnClAfter(CClient *cl, void *pack) const override
{
logGlobal->errorStream() << "Cannot apply on CL plain CPack!";
assert(0);
}
void applyOnClBefore(CClient *cl, void *pack) const override
{
logGlobal->errorStream() << "Cannot apply on CL plain CPack!";
assert(0);
}
};
static CApplier<CBaseForCLApply> *applier = nullptr;
void CClient::init()
{
hotSeat = false;
connectionHandler = nullptr;
pathInfo = nullptr;
applier = new CApplier<CBaseForCLApply>;
registerTypesClientPacks1(*applier);
registerTypesClientPacks2(*applier);
IObjectInterface::cb = this;
serv = nullptr;
gs = nullptr;
erm = nullptr;
terminate = false;
}
CClient::CClient(void)
{
init();
}
CClient::CClient(CConnection *con, StartInfo *si)
{
init();
newGame(con,si);
}
CClient::~CClient(void)
{
delete applier;
}
void CClient::waitForMoveAndSend(PlayerColor color)
{
try
{
setThreadName("CClient::waitForMoveAndSend");
assert(vstd::contains(battleints, color));
BattleAction ba = battleints[color]->activeStack(gs->curB->battleGetStackByID(gs->curB->activeStack, false));
if(ba.actionType != Battle::CANCEL)
{
logNetwork->traceStream() << "Send battle action to server: " << ba;
MakeAction temp_action(ba);
sendRequest(&temp_action, color);
}
return;
}
catch(boost::thread_interrupted&)
{
logNetwork->debugStream() << "Wait for move thread was interrupted and no action will be send. Was a battle ended by spell?";
return;
}
catch(...)
{
handleException();
return;
}
logNetwork->errorStream() << "We should not be here!";
}
void CClient::run()
{
setThreadName("CClient::run");
try
{
while(!terminate)
{
CPack *pack = serv->retreivePack(); //get the package from the server
if (terminate)
{
vstd::clear_pointer(pack);
break;
}
handlePack(pack);
}
}
//catch only asio exceptions
catch (const boost::system::system_error& e)
{
logNetwork->errorStream() << "Lost connection to server, ending listening thread!";
logNetwork->errorStream() << e.what();
if(!terminate) //rethrow (-> boom!) only if closing connection was unexpected
{
logNetwork->errorStream() << "Something wrong, lost connection while game is still ongoing...";
throw;
}
}
}
void CClient::save(const std::string & fname)
{
if(gs->curB)
{
logNetwork->errorStream() << "Game cannot be saved during battle!";
return;
}
SaveGame save_game(fname);
sendRequest((CPackForClient*)&save_game, PlayerColor::NEUTRAL);
}
void CClient::endGame( bool closeConnection /*= true*/ )
{
//suggest interfaces to finish their stuff (AI should interrupt any bg working threads)
for(auto i : playerint)
i.second->finish();
// Game is ending
// Tell the network thread to reach a stable state
if(closeConnection)
stopConnection();
logNetwork->infoStream() << "Closed connection.";
GH.curInt = nullptr;
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
logNetwork->infoStream() << "Ending current game!";
if(GH.topInt())
GH.topInt()->deactivate();
GH.listInt.clear();
GH.objsToBlit.clear();
GH.statusbar = nullptr;
logNetwork->infoStream() << "Removed GUI.";
vstd::clear_pointer(const_cast<CGameInfo*>(CGI)->mh);
vstd::clear_pointer(gs);
logNetwork->infoStream() << "Deleted mapHandler and gameState.";
LOCPLINT = nullptr;
}
playerint.clear();
battleints.clear();
callbacks.clear();
battleCallbacks.clear();
logNetwork->infoStream() << "Deleted playerInts.";
logNetwork->infoStream() << "Client stopped.";
}
#if 1
void CClient::loadGame(const std::string & fname, const bool server, const std::vector<int>& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const std::string & port)
{
PlayerColor player(player_); //intentional shadowing
logNetwork->infoStream() << "Loading procedure started!";
std::string realPort;
if(settings["testing"]["enabled"].Bool())
realPort = settings["testing"]["port"].String();
else if(port.size())
realPort = port;
else
realPort = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
CServerHandler sh;
if(server)
sh.startServer();
else
serv = sh.justConnectToServer(ipaddr, realPort);
CStopWatch tmh;
std::unique_ptr<CLoadFile> loader;
try
{
std::string clientSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::CLIENT_SAVEGAME));
std::string controlServerSaveName;
if (CResourceHandler::get("local")->existsResource(ResourceID(fname, EResType::SERVER_SAVEGAME)))
{
controlServerSaveName = *CResourceHandler::get("local")->getResourceName(ResourceID(fname, EResType::SERVER_SAVEGAME));
}
else// create entry for server savegame. Triggered if save was made after launch and not yet present in res handler
{
controlServerSaveName = clientSaveName.substr(0, clientSaveName.find_last_of(".")) + ".vsgm1";
CResourceHandler::get("local")->createResource(controlServerSaveName, true);
}
if(clientSaveName.empty())
throw std::runtime_error("Cannot open client part of " + fname);
if(controlServerSaveName.empty())
throw std::runtime_error("Cannot open server part of " + fname);
{
CLoadIntegrityValidator checkingLoader(clientSaveName, controlServerSaveName, minSupportedVersion);
loadCommonState(checkingLoader);
loader = checkingLoader.decay();
}
logNetwork->infoStream() << "Loaded common part of save " << tmh.getDiff();
const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
const_cast<CGameInfo*>(CGI)->mh->map = gs->map;
pathInfo = make_unique<CPathsInfo>(getMapSize());
CGI->mh->init();
logNetwork->infoStream() <<"Initing maphandler: "<<tmh.getDiff();
}
catch(std::exception &e)
{
logGlobal->errorStream() << "Cannot load game " << fname << ". Error: " << e.what();
throw; //obviously we cannot continue here
}
/*
if(!server)
player = PlayerColor(player_);
*/
std::set<PlayerColor> clientPlayers;
if(server)
serv = sh.connectToServer();
//*loader >> *this;
if(server)
{
tmh.update();
ui8 pom8;
*serv << ui8(3) << ui8(loadNumPlayers); //load game; one client if single-player
*serv << fname;
*serv >> pom8;
if(pom8)
throw std::runtime_error("Server cannot open the savegame!");
else
logNetwork->infoStream() << "Server opened savegame properly.";
}
if(server)
{
for(auto & elem : gs->scenarioOps->playerInfos)
if(!std::count(humanplayerindices.begin(),humanplayerindices.end(),elem.first.getNum()) || elem.first==player)
{
clientPlayers.insert(elem.first);
}
clientPlayers.insert(PlayerColor::NEUTRAL);
}
else
{
clientPlayers.insert(player);
}
std::cout << "CLIENTPLAYERS:\n";
for(auto x : clientPlayers)
std::cout << x << std::endl;
std::cout << "ENDCLIENTPLAYERS\n";
serialize(loader->serializer,0,clientPlayers);
*serv << ui32(clientPlayers.size());
for(auto & elem : clientPlayers)
*serv << ui8(elem.getNum());
serv->addStdVecItems(gs); /*why is this here?*/
//*loader >> *this;
logNetwork->infoStream() << "Loaded client part of save " << tmh.getDiff();
logNetwork->infoStream() <<"Sent info to server: "<<tmh.getDiff();
//*serv << clientPlayers;
serv->enableStackSendingByID();
serv->disableSmartPointerSerialization();
// logGlobal->traceStream() << "Objects:";
// for(int i = 0; i < gs->map->objects.size(); i++)
// {
// auto o = gs->map->objects[i];
// if(o)
// logGlobal->traceStream() << boost::format("\tindex=%5d, id=%5d; address=%5d, pos=%s, name=%s") % i % o->id % (int)o.get() % o->pos % o->getHoverText();
// else
// logGlobal->traceStream() << boost::format("\tindex=%5d --- nullptr") % i;
// }
}
#endif
void CClient::newGame( CConnection *con, StartInfo *si )
{
enum {SINGLE, HOST, GUEST} networkMode = SINGLE;
if (con == nullptr)
{
CServerHandler sh;
serv = sh.connectToServer();
}
else
{
serv = con;
networkMode = (con->connectionID == 1) ? HOST : GUEST;
}
CConnection &c = *serv;
////////////////////////////////////////////////////
logNetwork->infoStream() <<"\tWill send info to server...";
CStopWatch tmh;
if(networkMode == SINGLE)
{
ui8 pom8;
c << ui8(2) << ui8(1); //new game; one client
c << *si;
c >> pom8;
if(pom8) throw std::runtime_error("Server cannot open the map!");
}
c >> si;
logNetwork->infoStream() <<"\tSending/Getting info to/from the server: "<<tmh.getDiff();
c.enableStackSendingByID();
c.disableSmartPointerSerialization();
// Initialize game state
gs = new CGameState();
logNetwork->infoStream() <<"\tCreating gamestate: "<<tmh.getDiff();
gs->scenarioOps = si;
gs->init(si);
logNetwork->infoStream() <<"Initializing GameState (together): "<<tmh.getDiff();
// Now after possible random map gen, we know exact player count.
// Inform server about how many players client handles
std::set<PlayerColor> myPlayers;
for(auto & elem : gs->scenarioOps->playerInfos)
{
if((networkMode == SINGLE) //single - one client has all player
|| (networkMode != SINGLE && serv->connectionID == elem.second.playerID) //multi - client has only "its players"
|| (networkMode == HOST && elem.second.playerID == PlayerSettings::PLAYER_AI))//multi - host has all AI players
{
myPlayers.insert(elem.first); //add player
}
}
if(networkMode != GUEST)
myPlayers.insert(PlayerColor::NEUTRAL);
c << myPlayers;
// Init map handler
if(gs->map)
{
const_cast<CGameInfo*>(CGI)->mh = new CMapHandler();
CGI->mh->map = gs->map;
logNetwork->infoStream() <<"Creating mapHandler: "<<tmh.getDiff();
CGI->mh->init();
pathInfo = make_unique<CPathsInfo>(getMapSize());
logNetwork->infoStream() <<"Initializing mapHandler (together): "<<tmh.getDiff();
}
int humanPlayers = 0;
for(auto & elem : gs->scenarioOps->playerInfos)//initializing interfaces for players
{
PlayerColor color = elem.first;
gs->currentPlayer = color;
if(!vstd::contains(myPlayers, color))
continue;
logNetwork->traceStream() << "Preparing interface for player " << color;
if(si->mode != StartInfo::DUEL)
{
if(elem.second.playerID == PlayerSettings::PLAYER_AI)
{
auto AiToGive = aiNameForPlayer(elem.second, false);
logNetwork->infoStream() << boost::format("Player %s will be lead by %s") % color % AiToGive;
installNewPlayerInterface(CDynLibHandler::getNewAI(AiToGive), color);
}
else
{
installNewPlayerInterface(std::make_shared<CPlayerInterface>(color), color);
humanPlayers++;
}
}
else
{
std::string AItoGive = aiNameForPlayer(elem.second, true);
installNewBattleInterface(CDynLibHandler::getNewBattleAI(AItoGive), color);
}
}
if(si->mode == StartInfo::DUEL)
{
if(!gNoGUI)
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
auto p = std::make_shared<CPlayerInterface>(PlayerColor::NEUTRAL);
p->observerInDuelMode = true;
installNewPlayerInterface(p, boost::none);
GH.curInt = p.get();
}
battleStarted(gs->curB);
}
else
{
loadNeutralBattleAI();
}
serv->addStdVecItems(gs);
hotSeat = (humanPlayers > 1);
// std::vector<FileInfo> scriptModules;
// CFileUtility::getFilesWithExt(scriptModules, LIB_DIR "/scripting", "." LIB_EXT);
// for(FileInfo &m : scriptModules)
// {
// CScriptingModule * nm = CDynLibHandler::getNewScriptingModule(m.name);
// privilagedGameEventReceivers.push_back(nm);
// privilagedBattleEventReceivers.push_back(nm);
// nm->giveActionCB(this);
// nm->giveInfoCB(this);
// nm->init();
//
// erm = nm; //something tells me that there'll at most one module and it'll be ERM
// }
}
void CClient::serialize(COSer & h, const int version)
{
assert(h.saving);
h & hotSeat;
{
ui8 players = playerint.size();
h & players;
for(auto i = playerint.begin(); i != playerint.end(); i++)
{
LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
assert(i->first == i->second->playerID);
h & i->first & i->second->dllName & i->second->human;
i->second->saveGame(h, version);
}
}
}
void CClient::serialize(CISer & h, const int version)
{
assert(!h.saving);
h & hotSeat;
{
ui8 players = 0; //fix for uninitialized warning
h & players;
for(int i=0; i < players; i++)
{
std::string dllname;
PlayerColor pid;
bool isHuman = false;
h & pid & dllname & isHuman;
LOG_TRACE_PARAMS(logGlobal, "Loading player %s interface", pid);
std::shared_ptr<CGameInterface> nInt;
if(dllname.length())
{
if(pid == PlayerColor::NEUTRAL)
{
installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);
//TODO? consider serialization
continue;
}
else
{
assert(!isHuman);
nInt = CDynLibHandler::getNewAI(dllname);
}
}
else
{
assert(isHuman);
nInt = std::make_shared<CPlayerInterface>(pid);
}
nInt->dllName = dllname;
nInt->human = isHuman;
nInt->playerID = pid;
installNewPlayerInterface(nInt, pid);
nInt->loadGame(h, version); //another evil cast, check above
}
if(!vstd::contains(battleints, PlayerColor::NEUTRAL))
loadNeutralBattleAI();
}
}
void CClient::serialize(COSer & h, const int version, const std::set<PlayerColor> & playerIDs)
{
assert(h.saving);
h & hotSeat;
{
ui8 players = playerint.size();
h & players;
for(auto i = playerint.begin(); i != playerint.end(); i++)
{
LOG_TRACE_PARAMS(logGlobal, "Saving player %s interface", i->first);
assert(i->first == i->second->playerID);
h & i->first & i->second->dllName & i->second->human;
i->second->saveGame(h, version);
}
}
}
void CClient::serialize(CISer & h, const int version, const std::set<PlayerColor> & playerIDs)
{
assert(!h.saving);
h & hotSeat;
{
ui8 players = 0; //fix for uninitialized warning
h & players;
for(int i=0; i < players; i++)
{
std::string dllname;
PlayerColor pid;
bool isHuman = false;
h & pid & dllname & isHuman;
LOG_TRACE_PARAMS(logGlobal, "Loading player %s interface", pid);
std::shared_ptr<CGameInterface> nInt;
if(dllname.length())
{
if(pid == PlayerColor::NEUTRAL)
{
if(playerIDs.count(pid))
installNewBattleInterface(CDynLibHandler::getNewBattleAI(dllname), pid);
//TODO? consider serialization
continue;
}
else
{
assert(!isHuman);
nInt = CDynLibHandler::getNewAI(dllname);
}
}
else
{
assert(isHuman);
nInt = std::make_shared<CPlayerInterface>(pid);
}
nInt->dllName = dllname;
nInt->human = isHuman;
nInt->playerID = pid;
if(playerIDs.count(pid))
installNewPlayerInterface(nInt, pid);
nInt->loadGame(h, version);
}
if(playerIDs.count(PlayerColor::NEUTRAL))
loadNeutralBattleAI();
}
}
void CClient::handlePack( CPack * pack )
{
CBaseForCLApply *apply = applier->apps[typeList.getTypeID(pack)]; //find the applier
if(apply)
{
boost::unique_lock<boost::recursive_mutex> guiLock(*LOCPLINT->pim);
apply->applyOnClBefore(this,pack);
logNetwork->traceStream() << "\tMade first apply on cl";
gs->apply(pack);
logNetwork->traceStream() << "\tApplied on gs";
apply->applyOnClAfter(this,pack);
logNetwork->traceStream() << "\tMade second apply on cl";
}
else
{
logNetwork->errorStream() << "Message cannot be applied, cannot find applier! TypeID " << typeList.getTypeID(pack);
}
delete pack;
}
void CClient::finishCampaign( std::shared_ptr<CCampaignState> camp )
{
}
void CClient::proposeNextMission(std::shared_ptr<CCampaignState> camp)
{
GH.pushInt(new CBonusSelection(camp));
}
void CClient::stopConnection()
{
terminate = true;
if (serv) //request closing connection
{
logNetwork->infoStream() << "Connection has been requested to be closed.";
boost::unique_lock<boost::mutex>(*serv->wmx);
CloseServer close_server;
sendRequest(&close_server, PlayerColor::NEUTRAL);
logNetwork->infoStream() << "Sent closing signal to the server";
}
if(connectionHandler)//end connection handler
{
if(connectionHandler->get_id() != boost::this_thread::get_id())
connectionHandler->join();
logNetwork->infoStream() << "Connection handler thread joined";
delete connectionHandler;
connectionHandler = nullptr;
}
if (serv) //and delete connection
{
serv->close();
delete serv;
serv = nullptr;
logNetwork->warnStream() << "Our socket has been closed.";
}
}
void CClient::battleStarted(const BattleInfo * info)
{
for(auto &battleCb : battleCallbacks)
{
if(vstd::contains_if(info->sides, [&](const SideInBattle& side) {return side.color == battleCb.first; })
|| battleCb.first >= PlayerColor::PLAYER_LIMIT)
{
battleCb.second->setBattle(info);
}
}
// for(ui8 side : info->sides)
// if(battleCallbacks.count(side))
// battleCallbacks[side]->setBattle(info);
std::shared_ptr<CPlayerInterface> att, def;
auto &leftSide = info->sides[0], &rightSide = info->sides[1];
//If quick combat is not, do not prepare interfaces for battleint
if(!settings["adventure"]["quickCombat"].Bool())
{
if(vstd::contains(playerint, leftSide.color) && playerint[leftSide.color]->human)
att = std::dynamic_pointer_cast<CPlayerInterface>( playerint[leftSide.color] );
if(vstd::contains(playerint, rightSide.color) && playerint[rightSide.color]->human)
def = std::dynamic_pointer_cast<CPlayerInterface>( playerint[rightSide.color] );
}
if(!gNoGUI && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL))
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero,
Rect((screen->w - 800)/2,
(screen->h - 600)/2, 800, 600), att, def);
GH.pushInt(bi);
}
auto callBattleStart = [&](PlayerColor color, ui8 side){
if(vstd::contains(battleints, color))
battleints[color]->battleStart(leftSide.armyObject, rightSide.armyObject, info->tile, leftSide.hero, rightSide.hero, side);
};
callBattleStart(leftSide.color, 0);
callBattleStart(rightSide.color, 1);
callBattleStart(PlayerColor::UNFLAGGABLE, 1);
if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color))
{
boost::thread(&CClient::commenceTacticPhaseForInt, this, battleints[info->sides[info->tacticsSide].color]);
}
}
void CClient::battleFinished()
{
for(auto & side : gs->curB->sides)
if(battleCallbacks.count(side.color))
battleCallbacks[side.color]->setBattle(nullptr);
}
void CClient::loadNeutralBattleAI()
{
installNewBattleInterface(CDynLibHandler::getNewBattleAI(settings["server"]["neutralAI"].String()), PlayerColor::NEUTRAL);
}
void CClient::commitPackage( CPackForClient *pack )
{
CommitPackage cp;
cp.freePack = false;
cp.packToCommit = pack;
sendRequest(&cp, PlayerColor::NEUTRAL);
}
PlayerColor CClient::getLocalPlayer() const
{
if(LOCPLINT)
return LOCPLINT->playerID;
return getCurrentPlayer();
}
void CClient::commenceTacticPhaseForInt(std::shared_ptr<CBattleGameInterface> battleInt)
{
setThreadName("CClient::commenceTacticPhaseForInt");
try
{
battleInt->yourTacticPhase(gs->curB->tacticDistance);
if(gs && !!gs->curB && gs->curB->tacticDistance) //while awaiting for end of tactics phase, many things can happen (end of battle... or game)
{
MakeAction ma(BattleAction::makeEndOFTacticPhase(gs->curB->playerToSide(battleInt->playerID)));
sendRequest(&ma, battleInt->playerID);
}
}
catch(...)
{
handleException();
}
}
void CClient::invalidatePaths()
{
// turn pathfinding info into invalid. It will be regenerated later
boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);
pathInfo->hero = nullptr;
}
const CPathsInfo * CClient::getPathsInfo(const CGHeroInstance *h)
{
assert(h);
boost::unique_lock<boost::mutex> pathLock(pathInfo->pathMx);
if (pathInfo->hero != h)
{
gs->calculatePaths(h, *pathInfo.get());
}
return pathInfo.get();
}
int CClient::sendRequest(const CPack *request, PlayerColor player)
{
static ui32 requestCounter = 0;
ui32 requestID = requestCounter++;
logNetwork->traceStream() << boost::format("Sending a request \"%s\". It'll have an ID=%d.")
% typeid(*request).name() % requestID;
waitingRequest.pushBack(requestID);
serv->sendPackToServer(*request, player, requestID);
if(vstd::contains(playerint, player))
playerint[player]->requestSent(dynamic_cast<const CPackForServer*>(request), requestID);
return requestID;
}
void CClient::campaignMapFinished( std::shared_ptr<CCampaignState> camp )
{
endGame(false);
GH.curInt = CGPreGame::create();
auto & epilogue = camp->camp->scenarios[camp->mapsConquered.back()].epilog;
auto finisher = [=]()
{
if(camp->mapsRemaining.size())
proposeNextMission(camp);
else
finishCampaign(camp);
};
if(epilogue.hasPrologEpilog)
{
GH.pushInt(new CPrologEpilogVideo(epilogue, finisher));
}
else
{
finisher();
}
}
void CClient::installNewPlayerInterface(std::shared_ptr<CGameInterface> gameInterface, boost::optional<PlayerColor> color)
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
if(!color)
privilagedGameEventReceivers.push_back(gameInterface);
playerint[colorUsed] = gameInterface;
logGlobal->traceStream() << boost::format("\tInitializing the interface for player %s") % colorUsed;
auto cb = std::make_shared<CCallback>(gs, color, this);
callbacks[colorUsed] = cb;
battleCallbacks[colorUsed] = cb;
gameInterface->init(cb);
installNewBattleInterface(gameInterface, color, false);
}
void CClient::installNewBattleInterface(std::shared_ptr<CBattleGameInterface> battleInterface, boost::optional<PlayerColor> color, bool needCallback /*= true*/)
{
boost::unique_lock<boost::recursive_mutex> un(*LOCPLINT->pim);
PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE);
if(!color)
privilagedBattleEventReceivers.push_back(battleInterface);
battleints[colorUsed] = battleInterface;
if(needCallback)
{
logGlobal->traceStream() << boost::format("\tInitializing the battle interface for player %s") % *color;
auto cbc = std::make_shared<CBattleCallback>(gs, color, this);
battleCallbacks[colorUsed] = cbc;
battleInterface->init(cbc);
}
}
std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI)
{
if(ps.name.size())
{
const boost::filesystem::path aiPath =
VCMIDirs::get().libraryPath() / "AI" / VCMIDirs::get().libraryName(ps.name);
if (boost::filesystem::exists(aiPath))
return ps.name;
}
const int sensibleAILimit = settings["session"]["oneGoodAI"].Bool() ? 1 : PlayerColor::PLAYER_LIMIT_I;
std::string goodAI = battleAI ? settings["server"]["neutralAI"].String() : settings["server"]["playerAI"].String();
std::string badAI = battleAI ? "StupidAI" : "EmptyAI";
//TODO what about human players
if(battleints.size() >= sensibleAILimit)
return badAI;
return goodAI;
}
void CServerHandler::startServer()
{
th.update();
serverThread = new boost::thread(&CServerHandler::callServer, this); //runs server executable;
if(verbose)
logNetwork->infoStream() << "Setting up thread calling server: " << th.getDiff();
}
void CServerHandler::waitForServer()
{
if(!serverThread)
startServer();
th.update();
#ifndef VCMI_ANDROID
intpr::scoped_lock<intpr::interprocess_mutex> slock(shared->sr->mutex);
while(!shared->sr->ready)
{
shared->sr->cond.wait(slock);
}
#endif
if(verbose)
logNetwork->infoStream() << "Waiting for server: " << th.getDiff();
}
CConnection * CServerHandler::connectToServer()
{
#ifndef VCMI_ANDROID
if(!shared->sr->ready)
waitForServer();
#else
waitForServer();
#endif
th.update(); //put breakpoint here to attach to server before it does something stupid
CConnection *ret = justConnectToServer(settings["server"]["server"].String(), port);
if(verbose)
logNetwork->infoStream()<<"\tConnecting to the server: "<<th.getDiff();
return ret;
}
CServerHandler::CServerHandler(bool runServer /*= false*/)
{
serverThread = nullptr;
shared = nullptr;
if(settings["testing"]["enabled"].Bool())
port = settings["testing"]["port"].String();
else
port = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
verbose = true;
#ifndef VCMI_ANDROID
boost::interprocess::shared_memory_object::remove("vcmi_memory"); //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it
try
{
shared = new SharedMem();
}
catch(...)
{
logNetwork->error("Cannot open interprocess memory.");
handleException();
throw;
}
#endif
}
CServerHandler::~CServerHandler()
{
delete shared;
delete serverThread; //detaches, not kills thread
}
void CServerHandler::callServer()
{
setThreadName("CServerHandler::callServer");
const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string();
const std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + port + " > \"" + logName + '\"';
int result = std::system(comm.c_str());
if (result == 0)
logNetwork->infoStream() << "Server closed correctly";
else
{
logNetwork->errorStream() << "Error: server failed to close correctly or crashed!";
logNetwork->errorStream() << "Check " << logName << " for more info";
exit(1);// exit in case of error. Othervice without working server VCMI will hang
}
}
CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port)
{
std::string realPort;
if(settings["testing"]["enabled"].Bool())
realPort = settings["testing"]["port"].String();
else if(port.size())
realPort = port;
else
realPort = boost::lexical_cast<std::string>(settings["server"]["port"].Float());
CConnection *ret = nullptr;
while(!ret)
{
try
{
logNetwork->infoStream() << "Establishing connection...";
ret = new CConnection( host.size() ? host : settings["server"]["server"].String(),
realPort,
NAME);
}
catch(...)
{
logNetwork->errorStream() << "\nCannot establish connection! Retrying within 2 seconds";
SDL_Delay(2000);
}
}
return ret;
}