From c66c66c5ae1e20b055229fbba685e224c504517e Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 29 May 2017 12:39:08 +0300 Subject: [PATCH 01/34] Fixed https://bugs.vcmi.eu/view.php?id=2686 --- lib/CTownHandler.cpp | 1 - lib/mapping/MapFormatH3M.cpp | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 5f75b59bf..4315eb9b7 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -82,7 +82,6 @@ CFaction::~CFaction() CTown::CTown() : faction(nullptr), mageLevel(0), primaryRes(0), moatDamage(0), defaultTavernChance(0) { - faction = nullptr; } CTown::~CTown() diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 73cedfaf8..d9dd05d24 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -2231,7 +2231,7 @@ void CMapLoaderH3M::afterRead() for(auto obj : t.visitableObjects) { - if(obj->ID = Obj::TOWN) + if(obj->ID == Obj::TOWN || obj->ID == Obj::RANDOM_TOWN) { mainTown = obj; break; From db5a52a0f857b482b07e0c1e1a3f4fad3e162968 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 30 Oct 2016 02:43:06 +0300 Subject: [PATCH 02/34] Multiplayer: gracefully handle player loss unless it's a host We don't want server to shutdown after just one of players lost the game. --- client/Client.cpp | 5 +++-- lib/serializer/Connection.cpp | 5 +++++ lib/serializer/Connection.h | 1 + server/CGameHandler.cpp | 6 +++++- 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 7779aabe8..d07726690 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -391,7 +391,7 @@ void CClient::newGame( CConnection *con, StartInfo *si ) else { serv = con; - networkMode = (con->connectionID == 1) ? HOST : GUEST; + networkMode = con->isHost() ? HOST : GUEST; } CConnection &c = *serv; @@ -693,7 +693,7 @@ void CClient::stopConnection() { terminate = true; - if (serv) //request closing connection + if (serv && serv->isHost()) //request closing connection { logNetwork->infoStream() << "Connection has been requested to be closed."; boost::unique_lock(*serv->wmx); @@ -1075,6 +1075,7 @@ CConnection * CServerHandler::justConnectToServer(const std::string &host, const ret = new CConnection( host.size() ? host : settings["server"]["server"].String(), realPort, NAME); + ret->connectionID = 1; // TODO: Refactoring for the server so IDs set outside of CConnection } catch(...) { diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 6a664edc8..6cc13643b 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -201,6 +201,11 @@ bool CConnection::isOpen() const return socket && connected; } +bool CConnection::isHost() const +{ + return connectionID == 1; +} + void CConnection::reportState(CLogger * out) { out->debugStream() << "CConnection"; diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index de53215e2..2402d4db3 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -74,6 +74,7 @@ public: void close(); bool isOpen() const; + bool isHost() const; template CConnection &operator&(const T&); virtual ~CConnection(void); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 8b190f338..d14538573 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1097,7 +1097,7 @@ void CGameHandler::handleConnection(std::set players, CConnection & { assert(!c.connected); //make sure that connection has been marked as broken logGlobal->error(e.what()); - end2 = true; + conns -= &c; } catch(...) { @@ -2631,6 +2631,9 @@ void CGameHandler::sendToAllClients(CPackForClient * info) logNetwork->trace("Sending to all clients a package of type %s", typeid(*info).name()); for (auto & elem : conns) { + if(!elem->isOpen()) + continue; + boost::unique_lock lock(*(elem)->wmx); *elem << info; } @@ -2703,6 +2706,7 @@ void CGameHandler::close() { exit(0); } + end2 = true; //for (CConnection *cc : conns) // if (cc && cc->socket && cc->socket->is_open()) From 9b867808a7efd5a307159a89d412858ec3953e00 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Fri, 4 Nov 2016 01:06:10 +0300 Subject: [PATCH 03/34] Multiplayer: threat disconnected player just like if he lost Now even if player disconnected on it's own turn game will continue. --- server/CGameHandler.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index d14538573..4320b5f77 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1098,6 +1098,14 @@ void CGameHandler::handleConnection(std::set players, CConnection & assert(!c.connected); //make sure that connection has been marked as broken logGlobal->error(e.what()); conns -= &c; + for(auto playerConn : connections) + { + if(playerConn.second == &c) + { + gs->getPlayer(playerConn.first)->enteredLosingCheatCode = 1; + checkVictoryLossConditionsForPlayer(playerConn.first); + } + } } catch(...) { From 9d5d291e6b7d6c7434571a5a356d6c89db5793bb Mon Sep 17 00:00:00 2001 From: Piotr Date: Thu, 1 Jun 2017 22:36:46 +0200 Subject: [PATCH 04/34] Do not show whether Witch Hut is visited by hero, if was not visited by player yet, fixes #2604 (#304) --- lib/mapObjects/MiscObjects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 5baf7e315..ef9635d1c 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1477,7 +1477,7 @@ std::string CGWitchHut::getHoverText(PlayerColor player) const std::string CGWitchHut::getHoverText(const CGHeroInstance * hero) const { std::string hoverName = getHoverText(hero->tempOwner); - if(hero->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability + if(wasVisited(hero->tempOwner) && hero->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned) return hoverName; } From cc163c4e058f791aa9ceac016f32ad19510b8389 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 4 Dec 2016 12:54:26 +0300 Subject: [PATCH 05/34] Refactoring of networking code on server and client * Avoid server crash on dummy connect / disconnect. * Avoid server crash when host left from PreGame. * Server print it's state with name when it's waiting for connection or in pregame. * Server will use random port if specified port is busy. --- client/CMT.cpp | 7 +- client/Client.cpp | 8 +- lib/Interprocess.h | 5 +- lib/VCMI_lib.vcxproj | 2 +- lib/VCMI_lib.vcxproj.filters | 2 +- lib/serializer/Connection.cpp | 3 +- server/CGameHandler.cpp | 14 ++- server/CVCMIServer.cpp | 157 ++++++++++++++++++++-------------- server/CVCMIServer.h | 1 + 9 files changed, 120 insertions(+), 79 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index a76a9f58e..11bbfa097 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -1273,8 +1273,11 @@ static void mainLoop() void startGame(StartInfo * options, CConnection *serv/* = nullptr*/) { - serverAlive.waitWhileTrue(); - serverAlive.setn(true); + if(!CServerHandler::DO_NOT_START_SERVER) + { + serverAlive.waitWhileTrue(); + serverAlive.setn(true); + } if(vm.count("onlyAI")) { diff --git a/client/Client.cpp b/client/Client.cpp index d07726690..85b3786d9 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -708,16 +708,13 @@ void CClient::stopConnection() connectionHandler->join(); logNetwork->infoStream() << "Connection handler thread joined"; - - delete connectionHandler; - connectionHandler = nullptr; + vstd::clear_pointer(connectionHandler); } if (serv) //and delete connection { serv->close(); - delete serv; - serv = nullptr; + vstd::clear_pointer(serv); logNetwork->warnStream() << "Our socket has been closed."; } } @@ -990,6 +987,7 @@ CConnection * CServerHandler::connectToServer() #ifndef VCMI_ANDROID if(!shared->sr->ready) waitForServer(); + port = boost::lexical_cast(shared->sr->port); #else waitForServer(); #endif diff --git a/lib/Interprocess.h b/lib/Interprocess.h index 80a1de9c1..8798aa580 100644 --- a/lib/Interprocess.h +++ b/lib/Interprocess.h @@ -19,19 +19,22 @@ struct ServerReady { bool ready; + uint16_t port; //ui16? boost::interprocess::interprocess_mutex mutex; boost::interprocess::interprocess_condition cond; ServerReady() { ready = false; + port = 0; } - void setToTrueAndNotify() + void setToTrueAndNotify(uint16_t Port) { { boost::unique_lock lock(mutex); ready = true; + port = Port; } cond.notify_all(); } diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 6ff25d246..e2aff1646 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -424,4 +424,4 @@ - \ No newline at end of file + diff --git a/lib/VCMI_lib.vcxproj.filters b/lib/VCMI_lib.vcxproj.filters index 6ec81cf4b..803970053 100644 --- a/lib/VCMI_lib.vcxproj.filters +++ b/lib/VCMI_lib.vcxproj.filters @@ -669,4 +669,4 @@ Header Files - \ No newline at end of file + diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 6cc13643b..606687fe4 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -191,8 +191,7 @@ void CConnection::close() if(socket) { socket->close(); - delete socket; - socket = nullptr; + vstd::clear_pointer(socket); } } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 4320b5f77..df758743f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -2716,10 +2716,16 @@ void CGameHandler::close() } end2 = true; - //for (CConnection *cc : conns) - // if (cc && cc->socket && cc->socket->is_open()) - // cc->socket->close(); - //exit(0); + for (auto & elem : conns) + { + if(!elem->isOpen()) + continue; + + boost::unique_lock lock(*(elem)->wmx); + elem->close(); + elem->connected = false; + } + exit(0); } bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player) diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 0222763c4..b84c818ee 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -45,7 +45,6 @@ std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX namespace intpr = boost::interprocess; #endif bool end2 = false; -int port = 3030; boost::program_options::variables_map cmdLineOptions; @@ -106,6 +105,10 @@ void CPregameServer::handleConnection(CConnection *cpc) auto unlock = vstd::makeUnlockGuard(mx); while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } + else if(quitting) // Server must be stopped if host is leaving from lobby to avoid crash + { + end2 = true; + } } } catch (const std::exception& e) @@ -206,20 +209,29 @@ void CPregameServer::connectionAccepted(const boost::system::error_code& ec) return; } - logNetwork->info("We got a new connection! :)"); - CConnection *pc = new CConnection(upcomingConnection, NAME); - initConnection(pc); - upcomingConnection = nullptr; + try + { + logNetwork->info("We got a new connection! :)"); + std::string name = NAME; + CConnection *pc = new CConnection(upcomingConnection, name.append(" STATE_PREGAME")); + initConnection(pc); + upcomingConnection = nullptr; - startListeningThread(pc); + startListeningThread(pc); - *pc << (ui8)pc->connectionID << curmap; + *pc << (ui8)pc->connectionID << curmap; - announceTxt(pc->name + " joins the game"); - auto pj = new PlayerJoined(); - pj->playerName = pc->name; - pj->connectionID = pc->connectionID; - toAnnounce.push_back(pj); + announceTxt(pc->name + " joins the game"); + auto pj = new PlayerJoined(); + pj->playerName = pc->name; + pj->connectionID = pc->connectionID; + toAnnounce.push_back(pj); + } + catch(std::exception& e) + { + upcomingConnection = nullptr; + logNetwork->info("I guess it was just my imagination!"); + } start_async_accept(); } @@ -314,9 +326,23 @@ void CPregameServer::startListeningThread(CConnection * pc) } CVCMIServer::CVCMIServer() -: io(new boost::asio::io_service()), acceptor(new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port))), firstConnection(nullptr) + : port(3030), io(new boost::asio::io_service()), firstConnection(nullptr) { logNetwork->trace("CVCMIServer created!"); + if(cmdLineOptions.count("port")) + port = cmdLineOptions["port"].as(); + logNetwork->info("Port %d will be used", port); + try + { + acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), port)); + } + catch(...) + { + logNetwork->info("Port %d is busy, trying to use random port instead", port); + acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0)); + port = acceptor->local_endpoint().port(); + } + logNetwork->info("Listening for connections at port %d", port); } CVCMIServer::~CVCMIServer() { @@ -413,50 +439,62 @@ void CVCMIServer::start() #endif boost::system::error_code error; - logNetwork->info("Listening for connections at port %d", acceptor->local_endpoint().port()); - auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service()); - boost::thread acc(std::bind(vaccept,acceptor,s,&error)); -#ifndef VCMI_ANDROID - sr->setToTrueAndNotify(); - delete mr; + for (;;) + { + try + { + auto s = new boost::asio::ip::tcp::socket(acceptor->get_io_service()); + boost::thread acc(std::bind(vaccept,acceptor,s,&error)); +#ifdef VCMI_ANDROID + { // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm + CAndroidVMHelper envHelper; + envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady"); + logNetwork->info("Sending server ready message to client"); + } #else - { // in block to clean-up vm helper after use, because we don't need to keep this thread attached to vm - CAndroidVMHelper envHelper; - envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "onServerReady"); - logNetwork->info("Sending server ready message to client"); - } + sr->setToTrueAndNotify(port); + delete mr; #endif - acc.join(); - if (error) - { - logNetwork->warnStream() << "Got connection but there is an error " << error; - return; - } - logNetwork->info("We've accepted someone... "); - firstConnection = new CConnection(s, NAME); - logNetwork->info("Got connection!"); - while (!end2) - { - ui8 mode; - *firstConnection >> mode; - switch (mode) + acc.join(); + if (error) + { + logNetwork->warnStream()<<"Got connection but there is an error " << error; + return; + } + logNetwork->info("We've accepted someone... "); + std::string name = NAME; + firstConnection = new CConnection(s, name.append(" STATE_WAITING")); + logNetwork->info("Got connection!"); + while(!end2) + { + ui8 mode; + *firstConnection >> mode; + switch (mode) + { + case 0: + firstConnection->close(); + exit(0); + case 1: + firstConnection->close(); + return; + case 2: + newGame(); + break; + case 3: + loadGame(); + break; + case 4: + newPregame(); + break; + } + } + break; + } + catch(std::exception& e) { - case 0: - firstConnection->close(); - exit(0); - case 1: - firstConnection->close(); - return; - case 2: - newGame(); - break; - case 3: - loadGame(); - break; - case 4: - newPregame(); - break; + vstd::clear_pointer(firstConnection); + logNetwork->info("I guess it was just my imagination!"); } } } @@ -507,7 +545,7 @@ static void handleCommandOptions(int argc, char *argv[]) opts.add_options() ("help,h", "display help and exit") ("version,v", "display version information and exit") - ("port", po::value()->default_value(3030), "port at which server will listen to connections from client") + ("port", po::value(), "port at which server will listen to connections from client") ("resultsFile", po::value()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode."); if(argc > 1) @@ -584,12 +622,7 @@ int main(int argc, char** argv) logConfig.configureDefault(); logGlobal->info(NAME); - handleCommandOptions(argc, argv); - if (cmdLineOptions.count("port")) - port = cmdLineOptions["port"].as(); - logNetwork->info("Port %d will be used.", port); - preinitDLL(console); settings.init(); logConfig.configure(); @@ -630,11 +663,9 @@ int main(int argc, char** argv) CAndroidVMHelper envHelper; envHelper.callStaticVoidMethod(CAndroidVMHelper::NATIVE_METHODS_DEFAULT_CLASS, "killServer"); #endif - delete VLC; - VLC = nullptr; + vstd::clear_pointer(VLC); CResourceHandler::clear(); - - return 0; + return 0; } #ifdef VCMI_ANDROID diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 037e219d5..9371133bb 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -43,6 +43,7 @@ typedef boost::asio::basic_stream_socket < boost::asio::ip::tcp , boost::asio::s class CVCMIServer { + ui16 port; boost::asio::io_service *io; TAcceptor * acceptor; From c7e7a4d7be41e510465de4ef1a3a483253423258 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Wed, 31 May 2017 07:13:24 +0300 Subject: [PATCH 06/34] Make usage of boost::interprocess optional If shared memory allocation failed on client server will be started without shared memory option. Only downside of this is that server wouldn't be able to fallback to random port if default is busy. --- client/Client.cpp | 29 ++++++++++++++++++++++------- server/CVCMIServer.cpp | 41 +++++++++++++++++++++++++++-------------- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 85b3786d9..ae7bd3958 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -963,10 +963,13 @@ void CServerHandler::waitForServer() th.update(); #ifndef VCMI_ANDROID - intpr::scoped_lock slock(shared->sr->mutex); - while(!shared->sr->ready) + if(shared) { - shared->sr->cond.wait(slock); + intpr::scoped_lock slock(shared->sr->mutex); + while(!shared->sr->ready) + { + shared->sr->cond.wait(slock); + } } #else logNetwork->infoStream() << "waiting for server"; @@ -985,9 +988,12 @@ void CServerHandler::waitForServer() CConnection * CServerHandler::connectToServer() { #ifndef VCMI_ANDROID - if(!shared->sr->ready) - waitForServer(); - port = boost::lexical_cast(shared->sr->port); + if(shared) + { + if(!shared->sr->ready) + waitForServer(); + port = boost::lexical_cast(shared->sr->port); + } #else waitForServer(); #endif @@ -1013,6 +1019,9 @@ CServerHandler::CServerHandler(bool runServer /*= false*/) verbose = true; #ifndef VCMI_ANDROID + if(DO_NOT_START_SERVER) + return; + 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 { @@ -1020,6 +1029,7 @@ CServerHandler::CServerHandler(bool runServer /*= false*/) } catch(...) { + vstd::clear_pointer(shared); logNetwork->error("Cannot open interprocess memory."); handleException(); throw; @@ -1038,7 +1048,12 @@ void CServerHandler::callServer() #ifndef VCMI_ANDROID 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 + '\"'; + const std::string comm = VCMIDirs::get().serverPath().string() + + " --port=" + port + + " --run-by-client" + + (shared ? " --use-shm" : "") + + " > \"" + logName + '\"'; + int result = std::system(comm.c_str()); if (result == 0) { diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index b84c818ee..1da17d1e0 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -339,6 +339,11 @@ CVCMIServer::CVCMIServer() catch(...) { logNetwork->info("Port %d is busy, trying to use random port instead", port); + if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("use-shm")) + { + logNetwork->error("Cant pass port number to client without shared memory!", port); + exit(0); + } acceptor = new TAcceptor(*io, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 0)); port = acceptor->local_endpoint().port(); } @@ -422,19 +427,22 @@ void CVCMIServer::start() #ifndef VCMI_ANDROID ServerReady *sr = nullptr; intpr::mapped_region *mr; - try + if(cmdLineOptions.count("use-shm")) { - intpr::shared_memory_object smo(intpr::open_only,"vcmi_memory",intpr::read_write); - smo.truncate(sizeof(ServerReady)); - mr = new intpr::mapped_region(smo,intpr::read_write); - sr = reinterpret_cast(mr->get_address()); - } - catch(...) - { - intpr::shared_memory_object smo(intpr::create_only,"vcmi_memory",intpr::read_write); - smo.truncate(sizeof(ServerReady)); - mr = new intpr::mapped_region(smo,intpr::read_write); - sr = new(mr->get_address())ServerReady(); + try + { + intpr::shared_memory_object smo(intpr::open_only,"vcmi_memory",intpr::read_write); + smo.truncate(sizeof(ServerReady)); + mr = new intpr::mapped_region(smo,intpr::read_write); + sr = reinterpret_cast(mr->get_address()); + } + catch(...) + { + intpr::shared_memory_object smo(intpr::create_only,"vcmi_memory",intpr::read_write); + smo.truncate(sizeof(ServerReady)); + mr = new intpr::mapped_region(smo,intpr::read_write); + sr = new(mr->get_address())ServerReady(); + } } #endif @@ -452,8 +460,11 @@ void CVCMIServer::start() logNetwork->info("Sending server ready message to client"); } #else - sr->setToTrueAndNotify(port); - delete mr; + if(cmdLineOptions.count("use-shm")) + { + sr->setToTrueAndNotify(port); + delete mr; + } #endif acc.join(); @@ -545,6 +556,8 @@ static void handleCommandOptions(int argc, char *argv[]) opts.add_options() ("help,h", "display help and exit") ("version,v", "display version information and exit") + ("run-by-client", "indicate that server launched by client on same machine") + ("use-shm", "enable usage of shared memory") ("port", po::value(), "port at which server will listen to connections from client") ("resultsFile", po::value()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode."); From 4b0f702e7e7d5ff4f339d7b79275116bccdb1592 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Wed, 31 May 2017 09:45:26 +0300 Subject: [PATCH 07/34] Add LeaveGame netpack and avoid replying on it and CloseServer --- client/CPreGame.cpp | 2 +- client/Client.cpp | 6 ++++ lib/NetPacks.h | 7 +++++ lib/registerTypes/RegisterTypes.h | 1 + server/CGameHandler.cpp | 52 ++++++++++++++++++++++++------- server/CGameHandler.h | 6 ++++ server/NetPacksServer.cpp | 6 ++++ 7 files changed, 68 insertions(+), 12 deletions(-) diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index e0d0e7867..b20b4561b 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -83,7 +83,7 @@ struct EvilHlpStruct void reset() { - vstd::clear_pointer(serv); +// vstd::clear_pointer(serv); vstd::clear_pointer(sInfo); } diff --git a/client/Client.cpp b/client/Client.cpp index ae7bd3958..99cb331e9 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -701,6 +701,12 @@ void CClient::stopConnection() sendRequest(&close_server, PlayerColor::NEUTRAL); logNetwork->infoStream() << "Sent closing signal to the server"; } + else + { + LeaveGame leave_Game; + sendRequest(&leave_Game, PlayerColor::NEUTRAL); + logNetwork->infoStream() << "Sent leaving signal to the server"; + } if(connectionHandler)//end connection handler { diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 758e7ded8..629f942af 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1774,6 +1774,13 @@ struct CloseServer : public CPackForServer {} }; +struct LeaveGame : public CPackForServer +{ + bool applyGh(CGameHandler *gh); + template void serialize(Handler &h, const int version) + {} +}; + struct EndTurn : public CPackForServer { bool applyGh(CGameHandler *gh); diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index f6a74c10f..4d79e5dfa 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -314,6 +314,7 @@ void registerTypesServerPacks(Serializer &s) { s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index df758743f..2e8d7cd3a 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1031,6 +1031,21 @@ void CGameHandler::handleConnection(std::set players, CConnection & { setThreadName("CGameHandler::handleConnection"); + auto handleDisconnection = [&](const std::exception & e) + { + assert(!c.connected); //make sure that connection has been marked as broken + logGlobal->error(e.what()); + conns -= &c; + for(auto playerConn : connections) + { + if(playerConn.second == &c) + { + gs->getPlayer(playerConn.first)->enteredLosingCheatCode = 1; + checkVictoryLossConditionsForPlayer(playerConn.first); + } + } + }; + try { while(1)//server should never shut connection first //was: while(!end2) @@ -1042,6 +1057,8 @@ void CGameHandler::handleConnection(std::set players, CConnection & { boost::unique_lock lock(*c.rmx); + if(!c.connected) + throw clientDisconnectedException(); c >> player >> requestID >> pack; //get the package if (!pack) @@ -1060,6 +1077,11 @@ void CGameHandler::handleConnection(std::set players, CConnection & //prepare struct informing that action was applied auto sendPackageResponse = [&](bool succesfullyApplied) { + //dont reply to disconnected client + //TODO: this must be implemented as option of CPackForServer + if(dynamic_cast(pack) || dynamic_cast(pack)) + return; + PackageApplied applied; applied.player = player; applied.result = succesfullyApplied; @@ -1095,17 +1117,11 @@ void CGameHandler::handleConnection(std::set players, CConnection & } catch(boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection { - assert(!c.connected); //make sure that connection has been marked as broken - logGlobal->error(e.what()); - conns -= &c; - for(auto playerConn : connections) - { - if(playerConn.second == &c) - { - gs->getPlayer(playerConn.first)->enteredLosingCheatCode = 1; - checkVictoryLossConditionsForPlayer(playerConn.first); - } - } + handleDisconnection(e); + } + catch(clientDisconnectedException & e) + { + handleDisconnection(e); } catch(...) { @@ -2728,6 +2744,20 @@ void CGameHandler::close() exit(0); } +void CGameHandler::playerLeftGame(int cid) +{ + for (auto & elem : conns) + { + if(elem->isOpen() && elem->connectionID == cid) + { + boost::unique_lock lock(*(elem)->wmx); + elem->close(); + elem->connected = false; + break; + } + } +} + bool CGameHandler::arrangeStacks(ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player) { const CArmedInstance * s1 = static_cast(getObjInstance(id1)), diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 1faf3e70a..5e1fe9a8b 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -223,6 +223,7 @@ public: bool arrangeStacks( ObjectInstanceID id1, ObjectInstanceID id2, ui8 what, SlotID p1, SlotID p2, si32 val, PlayerColor player); void save(const std::string &fname); void close(); + void playerLeftGame(int cid); void handleTimeEvents(); void handleTownEvents(CGTownInstance *town, NewTurn &n); bool complain(const std::string &problem); //sends message to all clients, prints on the logs and return true @@ -298,4 +299,9 @@ private: void checkVictoryLossConditionsForAll(); }; +class clientDisconnectedException : public std::exception +{ + +}; + void makeStackDoNothing(); diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 756f20486..956eb0220 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -65,6 +65,12 @@ bool CloseServer::applyGh( CGameHandler *gh ) return true; } +bool LeaveGame::applyGh( CGameHandler *gh ) +{ + gh->playerLeftGame(c->connectionID); + return true; +} + bool EndTurn::applyGh( CGameHandler *gh ) { PlayerColor player = GS(gh)->currentPlayer; From bc6f65af048077dfe0dabf31a78cc5b4e3e36a7d Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Fri, 2 Jun 2017 03:34:50 +0300 Subject: [PATCH 08/34] PlayerCheated: new netpack to apply losing / winning cheat code --- lib/NetPacks.h | 15 +++++++++++++++ lib/NetPacksLib.cpp | 6 ++++++ lib/registerTypes/RegisterTypes.h | 1 + server/CGameHandler.cpp | 15 ++++++++++++--- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 629f942af..e353e5715 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -152,6 +152,21 @@ struct PlayerBlocked : public CPackForClient } }; +struct PlayerCheated : public CPackForClient +{ + PlayerCheated() : losingCheatCode(false), winningCheatCode(false) {} + DLL_LINKAGE void applyGs(CGameState *gs); + + PlayerColor player; + bool losingCheatCode; + bool winningCheatCode; + + template void serialize(Handler &h, const int version) + { + h & player & losingCheatCode & winningCheatCode; + } +}; + struct YourTurn : public CPackForClient { YourTurn(){} diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 8ef51c747..61992ae09 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1840,6 +1840,12 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs) } } +DLL_LINKAGE void PlayerCheated::applyGs(CGameState *gs) +{ + gs->getPlayer(player)->enteredLosingCheatCode = losingCheatCode; + gs->getPlayer(player)->enteredWinningCheatCode = winningCheatCode; +} + DLL_LINKAGE void YourTurn::applyGs(CGameState *gs) { gs->currentPlayer = player; diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 4d79e5dfa..caca90f9c 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -210,6 +210,7 @@ void registerTypesClientPacks1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 2e8d7cd3a..1c5ab149f 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1040,7 +1040,10 @@ void CGameHandler::handleConnection(std::set players, CConnection & { if(playerConn.second == &c) { - gs->getPlayer(playerConn.first)->enteredLosingCheatCode = 1; + PlayerCheated pc; + pc.player = playerConn.first; + pc.losingCheatCode = true; + sendAndApply(&pc); checkVictoryLossConditionsForPlayer(playerConn.first); } } @@ -6210,12 +6213,18 @@ void CGameHandler::handleCheatCode(std::string & cheat, PlayerColor player, cons else if (cheat == "vcmisilmaril") { ///Player wins - gs->getPlayer(player)->enteredWinningCheatCode = 1; + PlayerCheated pc; + pc.player = player; + pc.winningCheatCode = true; + sendAndApply(&pc); } else if (cheat == "vcmimelkor") { ///Player looses - gs->getPlayer(player)->enteredLosingCheatCode = 1; + PlayerCheated pc; + pc.player = player; + pc.losingCheatCode = true; + sendAndApply(&pc); } else if (cheat == "vcmieagles" || cheat == "vcmiungoliant") { From 4a302d4fe555c97ffa9ed23231e9c108d1197a39 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sat, 3 Jun 2017 04:28:03 +0300 Subject: [PATCH 09/34] CBattleHero::clickRight: more elegant code for side detection --- client/battle/CBattleInterfaceClasses.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 58c83ac5e..423bda176 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -214,11 +214,8 @@ void CBattleHero::clickRight(tribool down, bool previousState) if (down && myOwner->myTurn) { - if (myHero != nullptr) - targetHero.initFromHero(myHero, InfoAboutHero::EInfoLevel::INBATTLE); - else - targetHero = myOwner->enemyHero(); - + auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance; + targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE); GH.pushInt(new CHeroInfoWindow(targetHero, &windowPosition)); } } From 6642816b1e33ab00d102b93b6923d8065910f9ca Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 4 Jun 2017 08:49:23 +0300 Subject: [PATCH 10/34] Client: server port and testing options cleanup Now we only pass port as ui16 instead of std::string --- client/CMT.cpp | 20 ++++----- client/CPlayerInterface.cpp | 9 +--- client/CPreGame.cpp | 6 +-- client/CPreGame.h | 2 +- client/Client.cpp | 50 ++++++++++----------- client/Client.h | 7 +-- launcher/settingsView/csettingsview_moc.cpp | 2 +- lib/serializer/Connection.cpp | 4 +- lib/serializer/Connection.h | 2 +- 9 files changed, 45 insertions(+), 57 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 11bbfa097..ece9d6ed5 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -254,9 +254,9 @@ int main(int argc, char** argv) ("loadplayer", po::value(),"specifies which player we are in multiplayer loaded games (0=Red, etc.)") ("loadserverip",po::value(),"IP for loaded game server") ("loadserverport",po::value(),"port for loaded game server") - ("testingport",po::value(),"port for testing, override specified in config file") - ("testingfileprefix",po::value(),"prefix for auto save files") - ("testingsavefrequency",po::value(),"how often auto save should be created"); + ("serverport", po::value(), "override port specified in config file") + ("saveprefix", po::value(), "prefix for auto save files") + ("savefrequency", po::value(), "limit auto save creation to each N days"); if(argc > 1) { @@ -316,14 +316,10 @@ int main(int argc, char** argv) settings.init(); // Init special testing settings - Settings testingSettings = settings.write["testing"]; - if(vm.count("testingport") && vm.count("testingfileprefix")) - { - testingSettings["enabled"].Bool() = true; - testingSettings["port"].String() = vm["testingport"].as(); - testingSettings["prefix"].String() = vm["testingfileprefix"].as(); - testingSettings["savefrequency"].Float() = vm.count("testingsavefrequency") ? vm["testingsavefrequency"].as() : 1; - } + Settings session = settings.write["session"]; + session["serverport"].Integer() = vm.count("serverport") ? vm["serverport"].as() : 0; + session["saveprefix"].String() = vm.count("saveprefix") ? vm["saveprefix"].as() : ""; + session["savefrequency"].Integer() = vm.count("savefrequency") ? vm["savefrequency"].as() : 1; // Initialize logging based on settings logConfig.configure(); @@ -1309,7 +1305,7 @@ void startGame(StartInfo * options, CConnection *serv/* = nullptr*/) if(!vm.count("loadplayer")) client->loadGame(fname); else - client->loadGame(fname,vm.count("loadserver"),vm.count("loadhumanplayerindices") ? vm["loadhumanplayerindices"].as>() : std::vector(),vm.count("loadnumplayers") ? vm["loadnumplayers"].as() : 1,vm["loadplayer"].as(),vm.count("loadserverip") ? vm["loadserverip"].as() : "", vm.count("loadserverport") ? vm["loadserverport"].as() : "3030"); + client->loadGame(fname,vm.count("loadserver"),vm.count("loadhumanplayerindices") ? vm["loadhumanplayerindices"].as>() : std::vector(),vm.count("loadnumplayers") ? vm["loadnumplayers"].as() : 1,vm["loadplayer"].as(),vm.count("loadserverip") ? vm["loadserverip"].as() : "", vm.count("loadserverport") ? vm["loadserverport"].as() : CServerHandler::getDefaultPort()); break; } diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 10ae7da76..3ebe3eda5 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -171,12 +171,7 @@ void CPlayerInterface::yourTurn() GH.curInt = this; adventureInt->selection = nullptr; - std::string prefix = ""; - if (settings["testing"]["enabled"].Bool()) - { - prefix = settings["testing"]["prefix"].String(); - } - + std::string prefix = settings["session"]["saveprefix"].String(); if (firstCall) { if (howManyPeople == 1) @@ -192,7 +187,7 @@ void CPlayerInterface::yourTurn() } firstCall = 0; } - else if (settings["testing"].isNull() || cb->getDate() % static_cast(settings["testing"]["savefrequency"].Float()) == 0) + else if (cb->getDate() % static_cast(settings["session"]["savefrequency"].Integer()) == 0) { LOCPLINT->cb->save("Saves/" + prefix + "Autosave_" + boost::lexical_cast(autosaveCount++ + 1)); autosaveCount %= 5; diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index b20b4561b..f76b05b74 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -559,7 +559,7 @@ void CGPreGame::removeFromGui() GH.popInt(GH.topInt()); //remove background } -CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/, const std::map * Names /*= nullptr*/, const std::string & Address /*=""*/, const std::string & Port /*= ""*/) +CSelectionScreen::CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer /*= CMenuScreen::SINGLE_PLAYER*/, const std::map * Names /*= nullptr*/, const std::string & Address /*=""*/, const ui16 Port) : ISelectionScreenInfo(Names), serverHandlingThread(nullptr), mx(new boost::recursive_mutex), serv(nullptr), ongoingClosing(false), myNameID(255) { @@ -4320,7 +4320,7 @@ CSimpleJoinScreen::CSimpleJoinScreen(CMenuScreen::EMultiMode mode) cancel = new CButton(Point(142, 142), "MUBCANC.DEF", CGI->generaltexth->zelp[561], std::bind(&CGuiHandler::popIntTotally, std::ref(GH), this), SDLK_ESCAPE); bar = new CGStatusBar(new CPicture(Rect(7, 186, 218, 18), 0)); - port->setText(boost::lexical_cast(settings["server"]["port"].Float()), true); + port->setText(CServerHandler::getDefaultPortStr(), true); address->setText(settings["server"]["server"].String(), true); address->giveFocus(); } @@ -4332,7 +4332,7 @@ void CSimpleJoinScreen::enterSelectionScreen(CMenuScreen::EMultiMode mode) GH.popIntTotally(this); - GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, textPort)); + GH.pushInt(new CSelectionScreen(CMenuScreen::newGame, mode, nullptr, textAddress, boost::lexical_cast(textPort))); } void CSimpleJoinScreen::onChange(const std::string & newText) { diff --git a/client/CPreGame.h b/client/CPreGame.h index 89be9799e..f4052799d 100644 --- a/client/CPreGame.h +++ b/client/CPreGame.h @@ -371,7 +371,7 @@ public: bool ongoingClosing; ui8 myNameID; //used when networking - otherwise all player are "mine" - CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer = CMenuScreen::SINGLE_PLAYER, const std::map * Names = nullptr, const std::string & Address = "", const std::string & Port = ""); + CSelectionScreen(CMenuScreen::EState Type, CMenuScreen::EMultiMode MultiPlayer = CMenuScreen::SINGLE_PLAYER, const std::map * Names = nullptr, const std::string & Address = "", const ui16 Port = 0); ~CSelectionScreen(); void toggleTab(CIntObject *tab); void changeSelection(const CMapInfo *to); diff --git a/client/Client.cpp b/client/Client.cpp index 99cb331e9..7b1e674a7 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -251,24 +251,16 @@ void CClient::endGame(bool closeConnection /*= true*/) } #if 1 -void CClient::loadGame(const std::string & fname, const bool server, const std::vector& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const std::string & port) +void CClient::loadGame(const std::string & fname, const bool server, const std::vector& humanplayerindices, const int loadNumPlayers, int player_, const std::string & ipaddr, const ui16 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(settings["server"]["port"].Float()); - CServerHandler sh; if(server) sh.startServer(); else - serv = sh.justConnectToServer(ipaddr, realPort); + serv = sh.justConnectToServer(ipaddr, port); CStopWatch tmh; std::unique_ptr loader; @@ -998,7 +990,6 @@ CConnection * CServerHandler::connectToServer() { if(!shared->sr->ready) waitForServer(); - port = boost::lexical_cast(shared->sr->port); } #else waitForServer(); @@ -1006,7 +997,11 @@ CConnection * CServerHandler::connectToServer() th.update(); //put breakpoint here to attach to server before it does something stupid - CConnection *ret = justConnectToServer(settings["server"]["server"].String(), port); +#ifndef VCMI_ANDROID + CConnection *ret = justConnectToServer(settings["server"]["server"].String(), shared ? shared->sr->port : 0); +#else + CConnection *ret = justConnectToServer(settings["server"]["server"].String()); +#endif if(verbose) logNetwork->infoStream()<<"\tConnecting to the server: "<(getDefaultPort()); +} + CServerHandler::CServerHandler(bool runServer /*= false*/) { serverThread = nullptr; shared = nullptr; - if(settings["testing"]["enabled"].Bool()) - port = settings["testing"]["port"].String(); - else - port = boost::lexical_cast(settings["server"]["port"].Float()); verbose = true; #ifndef VCMI_ANDROID @@ -1055,7 +1059,7 @@ 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 + + " --port=" + getDefaultPortStr() + " --run-by-client" + (shared ? " --use-shm" : "") + " > \"" + logName + '\"'; @@ -1075,16 +1079,8 @@ void CServerHandler::callServer() #endif } -CConnection * CServerHandler::justConnectToServer(const std::string &host, const std::string &port) +CConnection * CServerHandler::justConnectToServer(const std::string &host, const ui16 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(settings["server"]["port"].Float()); - CConnection *ret = nullptr; while(!ret) { @@ -1092,7 +1088,7 @@ CConnection * CServerHandler::justConnectToServer(const std::string &host, const { logNetwork->infoStream() << "Establishing connection..."; ret = new CConnection( host.size() ? host : settings["server"]["server"].String(), - realPort, + port ? port : getDefaultPort(), NAME); ret->connectionID = 1; // TODO: Refactoring for the server so IDs set outside of CConnection } diff --git a/client/Client.h b/client/Client.h index ef7c55836..ecd5c201a 100644 --- a/client/Client.h +++ b/client/Client.h @@ -48,7 +48,6 @@ public: boost::thread *serverThread; //thread that called system to run server SharedMem *shared; //interprocess memory (for waiting for server) bool verbose; //whether to print log msgs - std::string port; //port number in text form //functions setting up local server void startServer(); //creates a thread with callServer @@ -56,7 +55,9 @@ public: CConnection * connectToServer(); //connects to server ////////////////////////////////////////////////////////////////////////// - static CConnection * justConnectToServer(const std::string &host = "", const std::string &port = ""); //connects to given host without taking any other actions (like setting up server) + static CConnection * justConnectToServer(const std::string &host = "", const ui16 port = 0); //connects to given host without taking any other actions (like setting up server) + static ui16 getDefaultPort(); + static std::string getDefaultPortStr(); CServerHandler(bool runServer = false); virtual ~CServerHandler(); @@ -154,7 +155,7 @@ public: void endGame(bool closeConnection = true); void stopConnection(); void save(const std::string & fname); - void loadGame(const std::string & fname, const bool server = true, const std::vector& humanplayerindices = std::vector(), const int loadnumplayers = 1, int player_ = -1, const std::string & ipaddr = "", const std::string & port = ""); + void loadGame(const std::string & fname, const bool server = true, const std::vector& humanplayerindices = std::vector(), const int loadnumplayers = 1, int player_ = -1, const std::string & ipaddr = "", const ui16 port = 0); void run(); void campaignMapFinished( std::shared_ptr camp ); void finishCampaign( std::shared_ptr camp ); diff --git a/launcher/settingsView/csettingsview_moc.cpp b/launcher/settingsView/csettingsview_moc.cpp index ab6fb4572..8d12ee54a 100644 --- a/launcher/settingsView/csettingsview_moc.cpp +++ b/launcher/settingsView/csettingsview_moc.cpp @@ -55,7 +55,7 @@ void CSettingsView::loadSettings() ui->comboBoxEnemyAI->setCurrentIndex(enemyAIIndex); ui->comboBoxPlayerAI->setCurrentIndex(playerAIIndex); - ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Float()); + ui->spinBoxNetworkPort->setValue(settings["server"]["port"].Integer()); ui->comboBoxAutoCheck->setCurrentIndex(settings["launcher"]["autoCheckRepositories"].Bool()); // all calls to plainText will trigger textChanged() signal overwriting config. Create backup before editing widget diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 606687fe4..7a292d3cb 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -61,14 +61,14 @@ void CConnection::init() iser.fileVersion = SERIALIZATION_VERSION; } -CConnection::CConnection(std::string host, std::string port, std::string Name) +CConnection::CConnection(std::string host, ui16 port, std::string Name) :iser(this), oser(this), io_service(new asio::io_service), name(Name) { int i; boost::system::error_code error = asio::error::host_not_found; socket = new tcp::socket(*io_service); tcp::resolver resolver(*io_service); - tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host,port),error); + tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, boost::lexical_cast(port)),error); if(error) { logNetwork->errorStream() << "Problem with resolving: \n" << error; diff --git a/lib/serializer/Connection.h b/lib/serializer/Connection.h index 2402d4db3..6d069c7c8 100644 --- a/lib/serializer/Connection.h +++ b/lib/serializer/Connection.h @@ -68,7 +68,7 @@ public: bool receivedStop, sendStop; - CConnection(std::string host, std::string port, std::string Name); + CConnection(std::string host, ui16 port, std::string Name); CConnection(TAcceptor * acceptor, boost::asio::io_service *Io_service, std::string Name); CConnection(TSocket * Socket, std::string Name); //use immediately after accepting connection into socket From d84f61fc96ae502a6fa4fe12ca54619f1474e44a Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 4 Jun 2017 16:48:04 +0300 Subject: [PATCH 11/34] CConnection: use std::static for port conversion Apperantly boost::lexical on Windows will add commas into output. --- lib/serializer/Connection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/serializer/Connection.cpp b/lib/serializer/Connection.cpp index 7a292d3cb..6bf935e0e 100644 --- a/lib/serializer/Connection.cpp +++ b/lib/serializer/Connection.cpp @@ -68,7 +68,7 @@ CConnection::CConnection(std::string host, ui16 port, std::string Name) boost::system::error_code error = asio::error::host_not_found; socket = new tcp::socket(*io_service); tcp::resolver resolver(*io_service); - tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, boost::lexical_cast(port)),error); + tcp::resolver::iterator end, pom, endpoint_iterator = resolver.resolve(tcp::resolver::query(host, std::to_string(port)),error); if(error) { logNetwork->errorStream() << "Problem with resolving: \n" << error; From 7e1b0d71c59febaa6d810386126b59fa45425d4d Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Sun, 4 Jun 2017 20:33:28 +0300 Subject: [PATCH 12/34] Added option for saving generated maps on client side (#307) * new configuration option 'general.saveRandomMaps' * maps being saved to 'userCachePath/RandomMaps' * no deletion of old random maps * map filename generated based on template name and random seed --- client/Client.cpp | 5 ++--- config/schemas/settings.json | 10 ++++++--- lib/CGameState.cpp | 39 +++++++++++++++++++++++++++++++---- lib/CGameState.h | 4 ++-- lib/mapping/CMapService.cpp | 18 ++++++++++++++++ lib/mapping/CMapService.h | 1 + lib/mapping/MapFormatJson.cpp | 5 +++++ 7 files changed, 70 insertions(+), 12 deletions(-) diff --git a/client/Client.cpp b/client/Client.cpp index 7b1e674a7..f00c05eb1 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -408,10 +408,9 @@ void CClient::newGame( CConnection *con, StartInfo *si ) // Initialize game state gs = new CGameState(); - logNetwork->infoStream() <<"\tCreating gamestate: "<info("\tCreating gamestate: %i",tmh.getDiff()); - gs->scenarioOps = si; - gs->init(si); + gs->init(si, settings["general"]["saveRandomMaps"].Bool()); logNetwork->infoStream() <<"Initializing GameState (together): "<infoStream() << "\tUsing random seed: "<< si->seedToBeUsed; getRandomGenerator().setSeed(si->seedToBeUsed); @@ -709,7 +711,7 @@ void CGameState::init(StartInfo * si) switch(scenarioOps->mode) { case StartInfo::NEW_GAME: - initNewGame(); + initNewGame(allowSavingRandomMap); break; case StartInfo::CAMPAIGN: initCampaign(); @@ -771,7 +773,7 @@ void CGameState::init(StartInfo * si) } } -void CGameState::initNewGame() +void CGameState::initNewGame(bool allowSavingRandomMap) { if(scenarioOps->createRandomMap()) { @@ -780,8 +782,37 @@ void CGameState::initNewGame() // Gen map CMapGenerator mapGenerator; - map = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed).release(); + std::unique_ptr randomMap = mapGenerator.generate(scenarioOps->mapGenOptions.get(), scenarioOps->seedToBeUsed); + + if(allowSavingRandomMap) + { + try + { + auto path = VCMIDirs::get().userCachePath() / "RandomMaps"; + boost::filesystem::create_directories(path); + + std::shared_ptr options = scenarioOps->mapGenOptions; + + const std::string templateName = options->getMapTemplate()->getName(); + const ui32 seed = scenarioOps->seedToBeUsed; + + const std::string fileName = boost::str(boost::format("%s_%d.vmap") % templateName % seed ); + const auto fullPath = path / fileName; + + CMapService::saveMap(randomMap, fullPath); + + logGlobal->info("Random map has been saved to:"); + logGlobal->info(fullPath.string()); + } + catch(...) + { + logGlobal->error("Saving random map failed with exception"); + handleException(); + } + } + + map = randomMap.release(); // Update starting options for(int i = 0; i < map->players.size(); ++i) { diff --git a/lib/CGameState.h b/lib/CGameState.h index 69de689f8..61eafd9f5 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -201,7 +201,7 @@ public: CGameState(); virtual ~CGameState(); - void init(StartInfo * si); + void init(StartInfo * si, bool allowSavingRandomMap = false); ConstTransitivePtr scenarioOps, initialOpts; //second one is a copy of settings received from pregame (not randomized) PlayerColor currentPlayer; //ID of player currently having turn @@ -283,7 +283,7 @@ private: // ----- initialization ----- - void initNewGame(); + void initNewGame(bool allowSavingRandomMap); void initCampaign(); void initDuel(); void checkMapChecksum(); diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 4b58c7ae6..144b1ed4c 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -5,6 +5,7 @@ #include "../filesystem/CBinaryReader.h" #include "../filesystem/CCompressedStream.h" #include "../filesystem/CMemoryStream.h" +#include "../filesystem/CMemoryBuffer.h" #include "CMap.h" @@ -52,6 +53,23 @@ std::unique_ptr CMapService::loadMapHeader(const ui8 * buffer, int s return header; } +void CMapService::saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath) +{ + CMemoryBuffer serializeBuffer; + { + CMapSaverJson saver(&serializeBuffer); + saver.saveMap(map); + } + { + boost::filesystem::remove(fullPath); + boost::filesystem::ofstream tmp(fullPath, boost::filesystem::ofstream::binary); + + tmp.write((const char *)serializeBuffer.getBuffer().data(),serializeBuffer.getSize()); + tmp.flush(); + tmp.close(); + } +} + std::unique_ptr CMapService::getStreamFromFS(const std::string & name) { return CResourceHandler::get()->load(ResourceID(name, EResType::MAP)); diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index 67289ec8f..3979e9046 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -69,6 +69,7 @@ public: */ static std::unique_ptr loadMapHeader(const ui8 * buffer, int size, const std::string & name); + static void saveMap(const std::unique_ptr & map, boost::filesystem::path fullPath); private: /** * Gets a map input stream object specified by a map name. diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index ad8626524..dc2ac7d93 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -1212,6 +1212,8 @@ void CMapSaverJson::saveMap(const std::unique_ptr& map) void CMapSaverJson::writeHeader() { + logGlobal->trace("Saving header"); + JsonNode header; JsonSerializer handler(mapObjectResolver.get(), header); @@ -1283,6 +1285,7 @@ JsonNode CMapSaverJson::writeTerrainLevel(const int index) void CMapSaverJson::writeTerrain() { + logGlobal->trace("Saving terrain"); //todo: multilevel map save support JsonNode surface = writeTerrainLevel(0); @@ -1297,12 +1300,14 @@ void CMapSaverJson::writeTerrain() void CMapSaverJson::writeObjects() { + logGlobal->trace("Saving objects"); JsonNode data(JsonNode::DATA_STRUCT); JsonSerializer handler(mapObjectResolver.get(), data); for(CGObjectInstance * obj : map->objects) { + logGlobal->trace("\t%s", obj->instanceName); auto temp = handler.enterStruct(obj->instanceName); obj->serializeJson(handler); From e25ed4f35895e1030a1be9f8505f82a68dd9f480 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Sun, 4 Jun 2017 21:11:58 +0300 Subject: [PATCH 13/34] [Tweak] Use unique_ptr for map header in pregame --- client/CPreGame.cpp | 19 +++++++++++-------- client/CPreGame.h | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index f76b05b74..06fa30305 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -131,10 +131,10 @@ void setPlayer(PlayerSettings &pset, ui8 player, const std::map &playerNames) +void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr & mapHeader, const std::map &playerNames) { sInfo.playerInfos.clear(); - if(!mapHeader) + if(!mapHeader.get()) { return; } @@ -799,7 +799,12 @@ void CSelectionScreen::changeSelection(const CMapInfo * to) SEL->sInfo.difficulty = to->scenarioOpts->difficulty; if(screenType != CMenuScreen::campaignList) { - updateStartInfo(to ? to->fileURI : "", sInfo, to ? to->mapHeader.get() : nullptr); + std::unique_ptr emptyHeader; + if(to) + updateStartInfo(to->fileURI, sInfo, to->mapHeader); + else + updateStartInfo("", sInfo, emptyHeader); + if(screenType == CMenuScreen::newGame) { if(to && to->isRandomMap) @@ -3219,7 +3224,7 @@ void CHotSeatPlayers::enterSelectionScreen() void CBonusSelection::init() { highlightedRegion = nullptr; - ourHeader = nullptr; + ourHeader.reset(); diffLb = nullptr; diffRb = nullptr; bonuses = nullptr; @@ -3342,7 +3347,6 @@ CBonusSelection::CBonusSelection(const std::string & campaignFName) CBonusSelection::~CBonusSelection() { SDL_FreeSurface(background); - delete ourHeader; sFlags->unload(); } @@ -3423,10 +3427,9 @@ void CBonusSelection::selectMap(int mapNr, bool initialSelect) scenarioName += ':' + boost::lexical_cast(selectedMap); //get header - delete ourHeader; std::string & headerStr = ourCampaign->camp->mapPieces.find(mapNr)->second; auto buffer = reinterpret_cast(headerStr.data()); - ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName).release(); + ourHeader = CMapService::loadMapHeader(buffer, headerStr.size(), scenarioName); std::map names; names[1] = settings["general"]["playerName"].String(); @@ -3922,7 +3925,7 @@ ISelectionScreenInfo::~ISelectionScreenInfo() SEL = nullptr; } -void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader) +void ISelectionScreenInfo::updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr & mapHeader) { ::updateStartInfo(filename, sInfo, mapHeader, playerNames); } diff --git a/client/CPreGame.h b/client/CPreGame.h index f4052799d..8d27ebbba 100644 --- a/client/CPreGame.h +++ b/client/CPreGame.h @@ -341,7 +341,7 @@ public: virtual void postChatMessage(const std::string &txt){}; void setPlayer(PlayerSettings &pset, ui8 player); - void updateStartInfo( std::string filename, StartInfo & sInfo, const CMapHeader * mapHeader ); + void updateStartInfo(std::string filename, StartInfo & sInfo, const std::unique_ptr & mapHeader); ui8 getIdOfFirstUnallocatedPlayer(); //returns 0 if none bool isGuest() const; @@ -539,7 +539,7 @@ private: int selectedMap; boost::optional selectedBonus; StartInfo startInfo; - CMapHeader * ourHeader; + std::unique_ptr ourHeader; }; /// Campaign selection screen From c82afe7156710eb9db0aa008a6f944c0f1d17a68 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Sun, 4 Jun 2017 22:42:48 +0300 Subject: [PATCH 14/34] switch CMapService API to ResourceID --- lib/CGameState.cpp | 4 ++-- lib/mapping/CMapInfo.cpp | 4 +++- lib/mapping/CMapService.cpp | 23 +++++++++-------------- lib/mapping/CMapService.h | 8 +++++--- lib/registerTypes/TypesClientPacks1.cpp | 1 - lib/registerTypes/TypesClientPacks2.cpp | 1 - lib/registerTypes/TypesMapObjects1.cpp | 1 - lib/registerTypes/TypesMapObjects2.cpp | 1 - lib/registerTypes/TypesMapObjects3.cpp | 1 - lib/registerTypes/TypesServerPacks.cpp | 1 - server/CVCMIServer.h | 1 - test/CMapEditManagerTest.cpp | 5 +++-- 12 files changed, 22 insertions(+), 29 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 1cc16acac..c3f0e53b4 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -16,7 +16,6 @@ #include "StartInfo.h" #include "NetPacks.h" #include "registerTypes/RegisterTypes.h" -#include "mapping/CMapInfo.h" #include "BattleInfo.h" #include "JsonNode.h" #include "filesystem/Filesystem.h" @@ -840,7 +839,8 @@ void CGameState::initNewGame(bool allowSavingRandomMap) else { logGlobal->infoStream() << "Open map file: " << scenarioOps->mapname; - map = CMapService::loadMap(scenarioOps->mapname).release(); + const ResourceID mapURI(scenarioOps->mapname, EResType::MAP); + map = CMapService::loadMap(mapURI).release(); } } diff --git a/lib/mapping/CMapInfo.cpp b/lib/mapping/CMapInfo.cpp index 4fec37fe3..112b44737 100644 --- a/lib/mapping/CMapInfo.cpp +++ b/lib/mapping/CMapInfo.cpp @@ -1,6 +1,7 @@ #include "StdInc.h" #include "CMapInfo.h" +#include "../filesystem/ResourceID.h" #include "../StartInfo.h" #include "../GameConstants.h" #include "CMapService.h" @@ -58,7 +59,7 @@ CMapInfo::~CMapInfo() void CMapInfo::mapInit(const std::string & fname) { fileURI = fname; - mapHeader = CMapService::loadMapHeader(fname); + mapHeader = CMapService::loadMapHeader(ResourceID(fname, EResType::MAP)); countPlayers(); } @@ -81,3 +82,4 @@ CMapInfo & CMapInfo::operator=(CMapInfo &&tmp) return *this; } +#undef STEAL diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index 144b1ed4c..928e1dcc0 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -13,24 +13,16 @@ #include "MapFormatJson.h" -std::unique_ptr CMapService::loadMap(const std::string & name) +std::unique_ptr CMapService::loadMap(const ResourceID & name) { auto stream = getStreamFromFS(name); - std::unique_ptr map(getMapLoader(stream)->loadMap()); - std::unique_ptr header(map.get()); - - getMapPatcher(name)->patchMapHeader(header); - header.release(); - - return map; + return getMapLoader(stream)->loadMap(); } -std::unique_ptr CMapService::loadMapHeader(const std::string & name) +std::unique_ptr CMapService::loadMapHeader(const ResourceID & name) { auto stream = getStreamFromFS(name); - std::unique_ptr header = getMapLoader(stream)->loadMapHeader(); - getMapPatcher(name)->patchMapHeader(header); - return header; + return getMapLoader(stream)->loadMapHeader(); } std::unique_ptr CMapService::loadMap(const ui8 * buffer, int size, const std::string & name) @@ -39,6 +31,7 @@ std::unique_ptr CMapService::loadMap(const ui8 * buffer, int size, const s std::unique_ptr map(getMapLoader(stream)->loadMap()); std::unique_ptr header(map.get()); + //might be original campaign and require patch getMapPatcher(name)->patchMapHeader(header); header.release(); @@ -49,6 +42,8 @@ std::unique_ptr CMapService::loadMapHeader(const ui8 * buffer, int s { auto stream = getStreamFromMem(buffer, size); std::unique_ptr header = getMapLoader(stream)->loadMapHeader(); + + //might be original campaign and require patch getMapPatcher(name)->patchMapHeader(header); return header; } @@ -70,9 +65,9 @@ void CMapService::saveMap(const std::unique_ptr & map, boost::filesystem:: } } -std::unique_ptr CMapService::getStreamFromFS(const std::string & name) +std::unique_ptr CMapService::getStreamFromFS(const ResourceID & name) { - return CResourceHandler::get()->load(ResourceID(name, EResType::MAP)); + return CResourceHandler::get()->load(name); } std::unique_ptr CMapService::getStreamFromMem(const ui8 * buffer, int size) diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index 3979e9046..a1d2a29cb 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -11,6 +11,8 @@ #pragma once +class ResourceID; + class CMap; class CMapHeader; class CInputStream; @@ -31,7 +33,7 @@ public: * @param name the name of the map * @return a unique ptr to the loaded map class */ - static std::unique_ptr loadMap(const std::string & name); + static std::unique_ptr loadMap(const ResourceID & name); /** * Loads the VCMI/H3 map header specified by the name. @@ -39,7 +41,7 @@ public: * @param name the name of the map * @return a unique ptr to the loaded map header class */ - static std::unique_ptr loadMapHeader(const std::string & name); + static std::unique_ptr loadMapHeader(const ResourceID & name); /** * Loads the VCMI/H3 map file from a buffer. This method is temporarily @@ -77,7 +79,7 @@ private: * @param name the name of the map * @return a unique ptr to the input stream class */ - static std::unique_ptr getStreamFromFS(const std::string & name); + static std::unique_ptr getStreamFromFS(const ResourceID & name); /** * Gets a map input stream from a buffer. diff --git a/lib/registerTypes/TypesClientPacks1.cpp b/lib/registerTypes/TypesClientPacks1.cpp index 7c756576f..ac68d26e6 100644 --- a/lib/registerTypes/TypesClientPacks1.cpp +++ b/lib/registerTypes/TypesClientPacks1.cpp @@ -1,7 +1,6 @@ #include "StdInc.h" #include "RegisterTypes.h" -#include "../mapping/CMapInfo.h" #include "../StartInfo.h" #include "../CGameState.h" #include "../mapping/CMap.h" diff --git a/lib/registerTypes/TypesClientPacks2.cpp b/lib/registerTypes/TypesClientPacks2.cpp index 1f91f0f80..a38b35f3a 100644 --- a/lib/registerTypes/TypesClientPacks2.cpp +++ b/lib/registerTypes/TypesClientPacks2.cpp @@ -1,7 +1,6 @@ #include "StdInc.h" #include "RegisterTypes.h" -#include "../mapping/CMapInfo.h" #include "../StartInfo.h" #include "../CStack.h" #include "../BattleInfo.h" diff --git a/lib/registerTypes/TypesMapObjects1.cpp b/lib/registerTypes/TypesMapObjects1.cpp index 1dd0fb09f..c43e4a714 100644 --- a/lib/registerTypes/TypesMapObjects1.cpp +++ b/lib/registerTypes/TypesMapObjects1.cpp @@ -1,7 +1,6 @@ #include "StdInc.h" #include "RegisterTypes.h" -#include "../mapping/CMapInfo.h" #include "../StartInfo.h" #include "../CGameState.h" #include "../mapping/CMap.h" diff --git a/lib/registerTypes/TypesMapObjects2.cpp b/lib/registerTypes/TypesMapObjects2.cpp index f06a180db..03184fded 100644 --- a/lib/registerTypes/TypesMapObjects2.cpp +++ b/lib/registerTypes/TypesMapObjects2.cpp @@ -1,7 +1,6 @@ #include "StdInc.h" #include "RegisterTypes.h" -#include "../mapping/CMapInfo.h" #include "../StartInfo.h" #include "../CStack.h" #include "../BattleInfo.h" diff --git a/lib/registerTypes/TypesMapObjects3.cpp b/lib/registerTypes/TypesMapObjects3.cpp index 713e0c303..6c5329cef 100644 --- a/lib/registerTypes/TypesMapObjects3.cpp +++ b/lib/registerTypes/TypesMapObjects3.cpp @@ -1,7 +1,6 @@ #include "StdInc.h" #include "RegisterTypes.h" -#include "../mapping/CMapInfo.h" #include "../StartInfo.h" #include "../CGameState.h" #include "../mapping/CMap.h" diff --git a/lib/registerTypes/TypesServerPacks.cpp b/lib/registerTypes/TypesServerPacks.cpp index 7f19b9c30..ebcbd97f4 100644 --- a/lib/registerTypes/TypesServerPacks.cpp +++ b/lib/registerTypes/TypesServerPacks.cpp @@ -1,7 +1,6 @@ #include "StdInc.h" #include "RegisterTypes.h" -#include "../mapping/CMapInfo.h" #include "../StartInfo.h" #include "../CGameState.h" #include "../mapping/CMap.h" diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 9371133bb..478072905 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -74,7 +74,6 @@ public: std::list toAnnounce; boost::recursive_mutex mx; - //std::vector maps; TAcceptor *acceptor; TSocket *upcomingConnection; diff --git a/test/CMapEditManagerTest.cpp b/test/CMapEditManagerTest.cpp index 47a2a99a6..3f6f4ee71 100644 --- a/test/CMapEditManagerTest.cpp +++ b/test/CMapEditManagerTest.cpp @@ -115,9 +115,10 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) logGlobal->info("CMapEditManager_DrawTerrain_View start"); try { + const ResourceID testMap("test/TerrainViewTest", EResType::MAP); // Load maps and json config - const auto originalMap = CMapService::loadMap("test/TerrainViewTest"); - auto map = CMapService::loadMap("test/TerrainViewTest"); + const auto originalMap = CMapService::loadMap(testMap); + auto map = CMapService::loadMap(testMap); logGlobal->info("Loaded test map successfully."); // Validate edit manager From 66cfc2fef90b6a19cab02e447ddf4f7be45eefb3 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 5 Jun 2017 21:30:25 +0300 Subject: [PATCH 15/34] CSpell::canBeCast tweaks --- client/windows/CSpellWindow.cpp | 2 +- lib/CBattleCallback.cpp | 16 ---------------- lib/spells/CSpellHandler.cpp | 21 ++++++++++++++++++--- 3 files changed, 19 insertions(+), 20 deletions(-) diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index d1a6ecc9b..9e032588d 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -337,7 +337,7 @@ void CSpellWindow::computeSpellsPerArea() spellsCurSite.reserve(mySpells.size()); for(const CSpell * spell : mySpells) { - if(spell->combatSpell ^ !battleSpellsOnly + if(spell->isCombatSpell() ^ !battleSpellsOnly && ((selectedTab == 4) || spell->school.at((ESpellSchool)selectedTab)) ) { diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 0c1e56a3d..af4053715 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1716,14 +1716,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster."; return ESpellCastProblem::INVALID; } - const PlayerColor player = caster->getOwner(); - const si8 side = playerToSide(player); - - if(side < 0) - return ESpellCastProblem::INVALID; - - if(!battleDoWeKnowAbout(side)) - return ESpellCastProblem::INVALID; ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode); if(genProblem != ESpellCastProblem::OK) @@ -1750,14 +1742,6 @@ ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell break; } - if(!spell->combatSpell) - return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL; - - //effect like Recanter's Cloak. Blocks also passive casting. - //TODO: check creature abilities to block - if(battleMaxSpellLevel(side) < spell->level) - return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED; - return spell->canBeCast(this, mode, caster); } diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 7ca3accb6..1fbe2c791 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -157,10 +157,25 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const { - const ESpellCastProblem::ESpellCastProblem generalProblem = mechanics->canBeCast(cb, mode, caster); + if(!isCombatSpell()) + return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL; - if(generalProblem != ESpellCastProblem::OK) - return generalProblem; + const PlayerColor player = caster->getOwner(); + const si8 side = cb->playerToSide(player); + + if(side < 0) + return ESpellCastProblem::INVALID; + + //effect like Recanter's Cloak. Blocks also passive casting. + //TODO: check creature abilities to block + //TODO: check any possible caster + if(cb->battleMaxSpellLevel(side) < level) + return ESpellCastProblem::SPELL_LEVEL_LIMIT_EXCEEDED; + + const ESpellCastProblem::ESpellCastProblem specificProblem = mechanics->canBeCast(cb, mode, caster); + + if(specificProblem != ESpellCastProblem::OK) + return specificProblem; //check for creature target existence //allow to cast spell if there is at least one smart target From 1d1519db5c988348f405846f20ebee5ab332f9e9 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 5 Jun 2017 21:41:27 +0300 Subject: [PATCH 16/34] ENCHANTER_CASTING trigger tweak --- lib/spells/ISpellMechanics.cpp | 6 +++++- lib/spells/ISpellMechanics.h | 3 ++- server/CGameHandler.cpp | 16 +++++++--------- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index 78aa07d73..4259bd74c 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -87,10 +87,14 @@ void BattleSpellCastParameters::cast(const SpellCastEnvironment * env) spell->battleCast(env, *this); } -void BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env) +bool BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env) { if(ESpellCastProblem::OK == cb->battleCanCastThisSpell(caster, spell, mode)) + { cast(env); + return true; + } + return false; } BattleHex BattleSpellCastParameters::getFirstDestinationHex() const diff --git a/lib/spells/ISpellMechanics.h b/lib/spells/ISpellMechanics.h index 98a960845..3ee4059d1 100644 --- a/lib/spells/ISpellMechanics.h +++ b/lib/spells/ISpellMechanics.h @@ -57,7 +57,8 @@ public: void cast(const SpellCastEnvironment * env); ///cast with silent check for permitted cast - void castIfPossible(const SpellCastEnvironment * env); + ///returns true if cast was permitted + bool castIfPossible(const SpellCastEnvironment * env); BattleHex getFirstDestinationHex() const; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1c5ab149f..0fd0c4350 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4631,14 +4631,14 @@ void CGameHandler::stackTurnTrigger(const CStack *st) const CSpell * spell = SpellID(spellID).toSpell(); bl.remove_if([&bonus](const Bonus* b){return b==bonus.get();}); - if (gs->curB->battleCanCastThisSpell(st, spell, ECastingMode::ENCHANTER_CASTING) == ESpellCastProblem::OK) - { - BattleSpellCastParameters parameters(gs->curB, st, spell); - parameters.spellLvl = bonus->val; - parameters.effectLevel = bonus->val;//todo: recheck - parameters.mode = ECastingMode::ENCHANTER_CASTING; - parameters.cast(spellEnv); + BattleSpellCastParameters parameters(gs->curB, st, spell); + parameters.spellLvl = bonus->val; + parameters.effectLevel = bonus->val;//todo: recheck + parameters.mode = ECastingMode::ENCHANTER_CASTING; + cast = parameters.castIfPossible(spellEnv); + if(cast) + { //todo: move to mechanics BattleSetStackProperty ssp; ssp.which = BattleSetStackProperty::ENCHANTER_COUNTER; @@ -4646,8 +4646,6 @@ void CGameHandler::stackTurnTrigger(const CStack *st) ssp.val = bonus->additionalInfo; //increase cooldown counter ssp.stackID = st->ID; sendAndApply(&ssp); - - cast = true; } } } From 4d430f6ad80ce529e6da8f90d68a39430dd53845 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 5 Jun 2017 22:16:12 +0300 Subject: [PATCH 17/34] get rid of CPlayerBattleCallback::battleCanCastSpell --- AI/BattleAI/BattleAI.cpp | 2 +- client/battle/CBattleInterface.cpp | 29 ++++++++++++++++------- client/battle/CBattleInterfaceClasses.cpp | 2 +- client/windows/CSpellWindow.cpp | 2 +- lib/CBattleCallback.cpp | 20 ---------------- lib/CBattleCallback.h | 1 - 6 files changed, 23 insertions(+), 33 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index 7fda09008..a9828d7e3 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -186,7 +186,7 @@ void CBattleAI::attemptCastingSpell() if(!hero) return; - if(!cb->battleCanCastSpell()) + if(cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING) != ESpellCastProblem::OK) return; LOGL("Casting spells sounds like fun. Let's see..."); diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 9b010abef..e5b59015a 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -871,19 +871,24 @@ void CBattleInterface::bSpellf() if (spellDestSelectMode) //we are casting a spell return; - CCS->curh->changeGraphic(ECursor::ADVENTURE,0); - if (!myTurn) return; auto myHero = currentHero(); - ESpellCastProblem::ESpellCastProblem spellCastProblem; - if (curInt->cb->battleCanCastSpell(&spellCastProblem)) + if(!myHero) + return; + + CCS->curh->changeGraphic(ECursor::ADVENTURE,0); + + ESpellCastProblem::ESpellCastProblem spellCastProblem = curInt->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING); + + if(spellCastProblem == ESpellCastProblem::OK) { GH.pushInt(new CSpellWindow(myHero, curInt.get())); } else if (spellCastProblem == ESpellCastProblem::MAGIC_IS_BLOCKED) { + //TODO: move to spell mechanics, add more information to spell cast problem //Handle Orb of Inhibition-like effects -> we want to display dialog with info, why casting is impossible auto blockingBonus = currentHero()->getBonusLocalFirst(Selector::type(Bonus::BLOCK_ALL_MAGIC)); if (!blockingBonus) @@ -1849,11 +1854,17 @@ void CBattleInterface::showQueue() void CBattleInterface::blockUI(bool on) { - ESpellCastProblem::ESpellCastProblem spellcastingProblem; - bool canCastSpells = curInt->cb->battleCanCastSpell(&spellcastingProblem); - //if magic is blocked, we leave button active, so the message can be displayed (cf bug #97) - if (!canCastSpells) - canCastSpells = spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED; + bool canCastSpells = false; + auto hero = curInt->cb->battleGetMyHero(); + + if(hero) + { + ESpellCastProblem::ESpellCastProblem spellcastingProblem = curInt->cb->battleCanCastSpell(hero, ECastingMode::HERO_CASTING); + + //if magic is blocked, we leave button active, so the message can be displayed after button click + canCastSpells = spellcastingProblem == ESpellCastProblem::OK || spellcastingProblem == ESpellCastProblem::MAGIC_IS_BLOCKED; + } + bool canWait = activeStack ? !activeStack->waited() : false; bOptions->block(on); diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 423bda176..1aa6f63a8 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -191,7 +191,7 @@ void CBattleHero::clickLeft(tribool down, bool previousState) if(myOwner->spellDestSelectMode) //we are casting a spell return; - if(myHero != nullptr && !down && myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell()) //check conditions + if(myHero != nullptr && !down && myOwner->myTurn && myOwner->getCurrentPlayerInterface()->cb->battleCanCastSpell(myHero, ECastingMode::HERO_CASTING) == ESpellCastProblem::OK) //check conditions { for(int it=0; itcombatSpell && owner->myInt->battleInt && owner->myInt->cb->battleCanCastSpell()) //if battle window is open + if(mySpell->combatSpell && owner->myInt->battleInt) //if battle window is open { ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(mySpell); switch (problem) diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index af4053715..2b181bdf2 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -2153,26 +2153,6 @@ int CPlayerBattleCallback::battleGetSurrenderCost() const return CBattleInfoCallback::battleGetSurrenderCost(*player); } -bool CPlayerBattleCallback::battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem /*= nullptr*/) const -{ - RETURN_IF_NOT_BATTLE(false); - ASSERT_IF_CALLED_WITH_PLAYER - - const CGHeroInstance * hero = battleGetMyHero(); - if(!hero) - { - if(outProblem) - *outProblem = ESpellCastProblem::NO_HERO_TO_CAST_SPELL; - return false; - } - - auto problem = CBattleInfoCallback::battleCanCastSpell(hero, ECastingMode::HERO_CASTING); - if(outProblem) - *outProblem = problem; - - return problem == ESpellCastProblem::OK; -} - const CGHeroInstance * CPlayerBattleCallback::battleGetMyHero() const { return CBattleInfoEssentials::battleGetFightingHero(battleGetMySide()); diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index b76a19aed..da2759b78 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -340,7 +340,6 @@ public: int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible - bool battleCanCastSpell(ESpellCastProblem::ESpellCastProblem *outProblem = nullptr) const; //returns true, if caller can cast a spell. If not, if pointer is given via arg, the reason will be written. const CGHeroInstance * battleGetMyHero() const; InfoAboutHero battleGetEnemyHero() const; }; From 2cfb2e6ae0155668b3fa83822b39b2e1d069b9d2 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 5 Jun 2017 22:53:42 +0300 Subject: [PATCH 18/34] get rid of CPlayerBattleCallback::battleCanCastThisSpell --- AI/BattleAI/BattleAI.cpp | 4 ++-- client/windows/CSpellWindow.cpp | 4 ++-- lib/CBattleCallback.cpp | 12 ------------ lib/CBattleCallback.h | 1 - 4 files changed, 4 insertions(+), 17 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index a9828d7e3..cc4da6bf6 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -192,9 +192,9 @@ void CBattleAI::attemptCastingSpell() LOGL("Casting spells sounds like fun. Let's see..."); //Get all spells we can cast std::vector possibleSpells; - vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this] (const CSpell *s) -> bool + vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool { - auto problem = getCbc()->battleCanCastThisSpell(s); + auto problem = getCbc()->battleCanCastThisSpell(hero, s, ECastingMode::HERO_CASTING); return problem == ESpellCastProblem::OK; }); LOGFL("I can cast %d spells.", possibleSpells.size()); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index ff09ba16a..15c012e7c 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -547,9 +547,9 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) } //we will cast a spell - if(mySpell->combatSpell && owner->myInt->battleInt) //if battle window is open + if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open { - ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(mySpell); + ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(owner->myHero, mySpell, ECastingMode::HERO_CASTING); switch (problem) { case ESpellCastProblem::OK: diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 2b181bdf2..c0d9523bf 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -2111,18 +2111,6 @@ ReachabilityInfo::Parameters::Parameters(const CStack *Stack) knownAccessible = stack->getHexes(); } -ESpellCastProblem::ESpellCastProblem CPlayerBattleCallback::battleCanCastThisSpell(const CSpell * spell) const -{ - RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); - ASSERT_IF_CALLED_WITH_PLAYER - - const ISpellCaster * hero = battleGetMyHero(); - if(hero == nullptr) - return ESpellCastProblem::INVALID; - else - return CBattleInfoCallback::battleCanCastThisSpell(hero, spell, ECastingMode::HERO_CASTING); -} - bool CPlayerBattleCallback::battleCanFlee() const { RETURN_IF_NOT_BATTLE(false); diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index da2759b78..04d7cb9c2 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -336,7 +336,6 @@ class DLL_LINKAGE CPlayerBattleCallback : public CBattleInfoCallback public: bool battleCanFlee() const; //returns true if caller can flee from the battle TStacks battleGetStacks(EStackOwnership whose = MINE_AND_ENEMY, bool onlyAlive = true) const; //returns stacks on battlefield - ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const CSpell * spell) const; //determines if given spell can be cast (and returns problem description) int battleGetSurrenderCost() const; //returns cost of surrendering battle, -1 if surrendering is not possible From 6c308956f92a2918da2b714bae7fe9a58760372e Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 5 Jun 2017 23:25:48 +0300 Subject: [PATCH 19/34] get rid of CBattleInfoCallback::battleCanCastThisSpellHere --- client/battle/CBattleInterface.cpp | 5 ++--- lib/CBattleCallback.cpp | 18 +----------------- lib/CBattleCallback.h | 1 - lib/spells/CSpellHandler.cpp | 4 ++++ server/CGameHandler.cpp | 4 ++-- 5 files changed, 9 insertions(+), 23 deletions(-) diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index e5b59015a..34696b825 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -1663,8 +1663,7 @@ void CBattleInterface::enterCreatureCastingMode() { const ISpellCaster *caster = activeStack; const CSpell *spell = SpellID(creatureSpellToCast).toSpell(); - - const bool isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, spell, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK); + const bool isCastingPossible = (spell->canBeCastAt(curInt->cb.get(), caster, ECastingMode::CREATURE_ACTIVE_CASTING, BattleHex::INVALID) == ESpellCastProblem::OK); if (isCastingPossible) { @@ -2522,7 +2521,7 @@ bool CBattleInterface::isCastingPossibleHere(const CStack *sactive, const CStack else { const ECastingMode::ECastingMode mode = creatureCasting ? ECastingMode::CREATURE_ACTIVE_CASTING : ECastingMode::HERO_CASTING; - isCastingPossible = (curInt->cb->battleCanCastThisSpellHere(caster, sp, mode, myNumber) == ESpellCastProblem::OK); + isCastingPossible = (sp->canBeCastAt(curInt->cb.get(), caster, mode, myNumber) == ESpellCastProblem::OK); } } else diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index c0d9523bf..8e8c84feb 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1773,22 +1773,6 @@ ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInst return ret - manaReduction + manaIncrease; } -ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const -{ - RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); - if(caster == nullptr) - { - logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpellHere: no spellcaster."; - return ESpellCastProblem::INVALID; - } - - ESpellCastProblem::ESpellCastProblem problem = battleCanCastThisSpell(caster, spell, mode); - if(problem != ESpellCastProblem::OK) - return problem; - - return spell->canBeCastAt(this, caster, mode, dest); -} - const CStack * CBattleInfoCallback::getStackIf(std::function pred) const { RETURN_IF_NOT_BATTLE(nullptr); @@ -1871,7 +1855,7 @@ SpellID CBattleInfoCallback::getRandomBeneficialSpell(CRandomGenerator & rand, c if(subject->hasBonus(Selector::source(Bonus::SPELL_EFFECT, spellID), Selector::all, cachingStr.str()) //TODO: this ability has special limitations - || battleCanCastThisSpellHere(subject, spellID.toSpell(), ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK) + || spellID.toSpell()->canBeCastAt(this, subject, ECastingMode::CREATURE_ACTIVE_CASTING, subject->position) != ESpellCastProblem::OK) continue; switch (spellID) diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 04d7cb9c2..9676806a0 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -294,7 +294,6 @@ public: ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell - ESpellCastProblem::ESpellCastProblem battleCanCastThisSpellHere(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode, BattleHex dest) const; //checks if given player can cast given spell at given tile in given mode SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const; SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const; diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index 1fbe2c791..c8ff206e1 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -383,6 +383,10 @@ void CSpell::getEffects(std::vector & lst, const int level) const ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const { + ESpellCastProblem::ESpellCastProblem problem = cb->battleCanCastThisSpell(caster, this, mode); + if(problem != ESpellCastProblem::OK) + return problem; + SpellTargetingContext ctx(this, mode, caster, caster->getSpellSchoolLevel(this), destination); return mechanics->canBeCast(cb, ctx); diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 0fd0c4350..e18e6e9d9 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4458,7 +4458,7 @@ bool CGameHandler::makeCustomAction(BattleAction &ba) if (ba.selectedStack >= 0) parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false)); - ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast(battleCanCastThisSpellHere)? + ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast? if (escp != ESpellCastProblem::OK) { logGlobal->warn("Spell cannot be cast! Problem: %d", escp); @@ -5300,7 +5300,7 @@ void CGameHandler::attackCasting(const BattleAttack & bat, Bonus::BonusType atta vstd::amin(chance, 100); const CSpell * spell = SpellID(spellID).toSpell(); - if (gs->curB->battleCanCastThisSpellHere(attacker, spell, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK) + if(spell->canBeCastAt(gs->curB, attacker, ECastingMode::AFTER_ATTACK_CASTING, oneOfAttacked->position) != ESpellCastProblem::OK) continue; //check if spell should be cast (probability handling) From 195e979a187f33c24eae58fbf0e28d9a5d25afab Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Mon, 5 Jun 2017 23:46:55 +0300 Subject: [PATCH 20/34] get rid of CBattleInfoCallback::battleCanCastThisSpell --- AI/BattleAI/BattleAI.cpp | 3 +-- client/windows/CSpellWindow.cpp | 2 +- lib/CBattleCallback.cpp | 37 --------------------------------- lib/CBattleCallback.h | 1 - lib/spells/CSpellHandler.cpp | 27 +++++++++++++++++++++++- lib/spells/ISpellMechanics.cpp | 2 +- server/CGameHandler.cpp | 2 +- 7 files changed, 30 insertions(+), 44 deletions(-) diff --git a/AI/BattleAI/BattleAI.cpp b/AI/BattleAI/BattleAI.cpp index cc4da6bf6..275cfc5c2 100644 --- a/AI/BattleAI/BattleAI.cpp +++ b/AI/BattleAI/BattleAI.cpp @@ -194,8 +194,7 @@ void CBattleAI::attemptCastingSpell() std::vector possibleSpells; vstd::copy_if(VLC->spellh->objects, std::back_inserter(possibleSpells), [this, hero] (const CSpell *s) -> bool { - auto problem = getCbc()->battleCanCastThisSpell(hero, s, ECastingMode::HERO_CASTING); - return problem == ESpellCastProblem::OK; + return s->canBeCast(getCbc().get(), ECastingMode::HERO_CASTING, hero) == ESpellCastProblem::OK; }); LOGFL("I can cast %d spells.", possibleSpells.size()); diff --git a/client/windows/CSpellWindow.cpp b/client/windows/CSpellWindow.cpp index 15c012e7c..ed4c0c3f1 100644 --- a/client/windows/CSpellWindow.cpp +++ b/client/windows/CSpellWindow.cpp @@ -549,7 +549,7 @@ void CSpellWindow::SpellArea::clickLeft(tribool down, bool previousState) //we will cast a spell if(mySpell->isCombatSpell() && owner->myInt->battleInt) //if battle window is open { - ESpellCastProblem::ESpellCastProblem problem = owner->myInt->cb->battleCanCastThisSpell(owner->myHero, mySpell, ECastingMode::HERO_CASTING); + ESpellCastProblem::ESpellCastProblem problem = mySpell->canBeCast(owner->myInt->cb.get(), ECastingMode::HERO_CASTING, owner->myHero); switch (problem) { case ESpellCastProblem::OK: diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 8e8c84feb..0cbeb40bb 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -1708,43 +1708,6 @@ std::vector CBattleInfoCallback::getAttackableBattleHexes() const return attackableBattleHexes; } -ESpellCastProblem::ESpellCastProblem CBattleInfoCallback::battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const -{ - RETURN_IF_NOT_BATTLE(ESpellCastProblem::INVALID); - if(caster == nullptr) - { - logGlobal->errorStream() << "CBattleInfoCallback::battleCanCastThisSpell: no spellcaster."; - return ESpellCastProblem::INVALID; - } - - ESpellCastProblem::ESpellCastProblem genProblem = battleCanCastSpell(caster, mode); - if(genProblem != ESpellCastProblem::OK) - return genProblem; - - switch(mode) - { - case ECastingMode::HERO_CASTING: - { - const CGHeroInstance * castingHero = dynamic_cast(caster);//todo: unify hero|creature spell cost - if(!castingHero) - { - logGlobal->error("battleCanCastThisSpell: invalid caster"); - return ESpellCastProblem::INVALID; - } - - if(!castingHero->getArt(ArtifactPosition::SPELLBOOK)) - return ESpellCastProblem::NO_SPELLBOOK; - if(!castingHero->canCastThisSpell(spell)) - return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL; - if(castingHero->mana < battleGetSpellCost(spell, castingHero)) //not enough mana - return ESpellCastProblem::NOT_ENOUGH_MANA; - } - break; - } - - return spell->canBeCast(this, mode, caster); -} - ui32 CBattleInfoCallback::battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const { RETURN_IF_NOT_BATTLE(-1); diff --git a/lib/CBattleCallback.h b/lib/CBattleCallback.h index 9676806a0..b1e45ca59 100644 --- a/lib/CBattleCallback.h +++ b/lib/CBattleCallback.h @@ -293,7 +293,6 @@ public: si8 battleMaxSpellLevel(ui8 side) const; //calculates minimum spell level possible to be cast on battlefield - takes into account artifacts of both heroes; if no effects are set, 0 is returned ui32 battleGetSpellCost(const CSpell * sp, const CGHeroInstance * caster) const; //returns cost of given spell ESpellCastProblem::ESpellCastProblem battleCanCastSpell(const ISpellCaster * caster, ECastingMode::ECastingMode mode) const; //returns true if there are no general issues preventing from casting a spell - ESpellCastProblem::ESpellCastProblem battleCanCastThisSpell(const ISpellCaster * caster, const CSpell * spell, ECastingMode::ECastingMode mode) const; //checks if given player can cast given spell SpellID battleGetRandomStackSpell(CRandomGenerator & rand, const CStack * stack, ERandomSpell mode) const; SpellID getRandomBeneficialSpell(CRandomGenerator & rand, const CStack * subject) const; diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index c8ff206e1..7ea37f1dc 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -157,6 +157,31 @@ ui32 CSpell::calculateDamage(const ISpellCaster * caster, const CStack * affecte ESpellCastProblem::ESpellCastProblem CSpell::canBeCast(const CBattleInfoCallback * cb, ECastingMode::ECastingMode mode, const ISpellCaster * caster) const { + ESpellCastProblem::ESpellCastProblem genProblem = cb->battleCanCastSpell(caster, mode); + if(genProblem != ESpellCastProblem::OK) + return genProblem; + + switch(mode) + { + case ECastingMode::HERO_CASTING: + { + const CGHeroInstance * castingHero = dynamic_cast(caster);//todo: unify hero|creature spell cost + if(!castingHero) + { + logGlobal->debug("CSpell::canBeCast: invalid caster"); + return ESpellCastProblem::NO_HERO_TO_CAST_SPELL; + } + + if(!castingHero->getArt(ArtifactPosition::SPELLBOOK)) + return ESpellCastProblem::NO_SPELLBOOK; + if(!castingHero->canCastThisSpell(this)) + return ESpellCastProblem::HERO_DOESNT_KNOW_SPELL; + if(castingHero->mana < cb->battleGetSpellCost(this, castingHero)) //not enough mana + return ESpellCastProblem::NOT_ENOUGH_MANA; + } + break; + } + if(!isCombatSpell()) return ESpellCastProblem::ADVMAP_SPELL_INSTEAD_OF_BATTLE_SPELL; @@ -383,7 +408,7 @@ void CSpell::getEffects(std::vector & lst, const int level) const ESpellCastProblem::ESpellCastProblem CSpell::canBeCastAt(const CBattleInfoCallback * cb, const ISpellCaster * caster, ECastingMode::ECastingMode mode, BattleHex destination) const { - ESpellCastProblem::ESpellCastProblem problem = cb->battleCanCastThisSpell(caster, this, mode); + ESpellCastProblem::ESpellCastProblem problem = canBeCast(cb, mode, caster); if(problem != ESpellCastProblem::OK) return problem; diff --git a/lib/spells/ISpellMechanics.cpp b/lib/spells/ISpellMechanics.cpp index 4259bd74c..9ffc6ec1b 100644 --- a/lib/spells/ISpellMechanics.cpp +++ b/lib/spells/ISpellMechanics.cpp @@ -89,7 +89,7 @@ void BattleSpellCastParameters::cast(const SpellCastEnvironment * env) bool BattleSpellCastParameters::castIfPossible(const SpellCastEnvironment * env) { - if(ESpellCastProblem::OK == cb->battleCanCastThisSpell(caster, spell, mode)) + if(ESpellCastProblem::OK == spell->canBeCast(cb, mode, caster)) { cast(env); return true; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index e18e6e9d9..bf5161fa6 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -4458,7 +4458,7 @@ bool CGameHandler::makeCustomAction(BattleAction &ba) if (ba.selectedStack >= 0) parameters.aimToStack(gs->curB->battleGetStackByID(ba.selectedStack, false)); - ESpellCastProblem::ESpellCastProblem escp = gs->curB->battleCanCastThisSpell(h, s, ECastingMode::HERO_CASTING);//todo: should we check aimed cast? + ESpellCastProblem::ESpellCastProblem escp = s->canBeCast(gs->curB, ECastingMode::HERO_CASTING, h);//todo: should we check aimed cast? if (escp != ESpellCastProblem::OK) { logGlobal->warn("Spell cannot be cast! Problem: %d", escp); From 4b2a118ffa84fdb63ef2501b74de19c28040beea Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Tue, 6 Jun 2017 02:01:24 +0300 Subject: [PATCH 21/34] fixed assertion if hero instance in VCMI map have 0 at one of primary skills --- lib/mapObjects/CGHeroInstance.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index ab66d879d..c7944ee32 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1644,8 +1644,7 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) { int value = 0; handler.serializeInt(PrimarySkill::names[i], value, 0); - if(value != 0) - pushPrimSkill(static_cast(i), value); + pushPrimSkill(static_cast(i), value); } } } From 9f1451c1a3d21298ebff900e1e57cd5e7b728fe7 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Tue, 6 Jun 2017 02:37:07 +0300 Subject: [PATCH 22/34] [Map format] more correct handling of default hero primary skills --- lib/mapObjects/CGHeroInstance.cpp | 45 +++++++++++++------------------ 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index c7944ee32..135756ab0 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1610,40 +1610,33 @@ void CGHeroInstance::serializeCommonOptions(JsonSerializeFormat & handler) } } + //primary skills + if(handler.saving) { - if(handler.saving) - { - bool haveSkills = false; + const bool haveSkills = hasBonus(Selector::type(Bonus::PRIMARY_SKILL).And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); - for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - { - if(valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL))) != 0) - { - haveSkills = true; - break; - } - } - - if(haveSkills) - { - auto primarySkills = handler.enterStruct("primarySkills"); - - for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - { - int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); - - handler.serializeInt(PrimarySkill::names[i], value, 0); - } - } - } - else + if(haveSkills) { auto primarySkills = handler.enterStruct("primarySkills"); for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) { - int value = 0; + int value = valOfBonuses(Selector::typeSubtype(Bonus::PRIMARY_SKILL, i).And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); + handler.serializeInt(PrimarySkill::names[i], value, 0); + } + } + } + else + { + auto primarySkills = handler.enterStruct("primarySkills"); + + if(primarySkills.get().getType() == JsonNode::DATA_STRUCT) + { + for(int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) + { + int value = 0; + primarySkills->serializeInt(PrimarySkill::names[i], value, 0); pushPrimSkill(static_cast(i), value); } } From f7f7fe1d328783fcd1da87bf9b79fbf65dc08c15 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 12 Mar 2017 10:54:24 +0300 Subject: [PATCH 23/34] Client: fix headless mode and add automated testing mode Command-line option --noGUI replaced with --headless. Added option --testmap that will run specified map with AI players --- client/CMT.cpp | 59 ++++++++++++++++++++++++++++----------- client/Client.cpp | 16 +++++++---- client/NetPacksClient.cpp | 41 +++++++++++++++------------ 3 files changed, 76 insertions(+), 40 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index ece9d6ed5..57532744f 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -91,7 +91,6 @@ SDL_Surface *screen = nullptr, //main screen surface std::queue events; boost::mutex eventsM; -bool gNoGUI = false; CondSh serverAlive(false); static po::variables_map vm; @@ -114,6 +113,29 @@ void endGame(); #include #endif +void startTestMap(const std::string &mapname) +{ + StartInfo si; + si.mapname = mapname; + si.mode = StartInfo::NEW_GAME; + for (int i = 0; i < 8; i++) + { + PlayerSettings &pset = si.playerInfos[PlayerColor(i)]; + pset.color = PlayerColor(i); + pset.name = CGI->generaltexth->allTexts[468];//Computer + pset.playerID = i; + pset.compOnly = true; + pset.castle = 0; + pset.hero = -1; + pset.heroPortrait = -1; + pset.handicap = PlayerSettings::NO_HANDICAP; + } + + while(GH.topInt()) + GH.popIntTotally(GH.topInt()); + startGame(&si); +} + void startGameFromFile(const bfs::path &fname) { StartInfo si; @@ -150,7 +172,7 @@ void init() logGlobal->infoStream()<<"Initializing VCMI_Lib: "<curh = new CCursorHandler; @@ -240,8 +262,9 @@ int main(int argc, char** argv) ("version,v", "display version information and exit") ("battle,b", po::value(), "runs game in duel mode (battle-only") ("start", po::value(), "starts game from saved StartInfo file") + ("testmap", po::value(), "") ("onlyAI", "runs without human player, all players will be default AI") - ("noGUI", "runs without GUI, implies --onlyAI") + ("headless", "runs without GUI, implies --onlyAI") ("ai", po::value>(), "AI to be used for the player, can be specified several times for the consecutive players") ("oneGoodAI", "puts one default AI and the rest will be EmptyAI") ("autoSkip", "automatically skip turns in GUI") @@ -281,11 +304,6 @@ int main(int argc, char** argv) prog_version(); return 0; } - if(vm.count("noGUI")) - { - gNoGUI = true; - vm.insert(std::pair("onlyAI", po::variable_value())); - } if(vm.count("donotstartserver")) { CServerHandler::DO_NOT_START_SERVER = true; @@ -314,9 +332,14 @@ int main(int argc, char** argv) // Init filesystem and settings preinitDLL(::console); settings.init(); + Settings session = settings.write["session"]; + if(vm.count("headless")) + { + session["headless"].Bool() = true; + vm.insert(std::pair("onlyAI", po::variable_value())); + } // Init special testing settings - Settings session = settings.write["session"]; session["serverport"].Integer() = vm.count("serverport") ? vm["serverport"].as() : 0; session["saveprefix"].String() = vm.count("saveprefix") ? vm["saveprefix"].as() : ""; session["savefrequency"].Integer() = vm.count("savefrequency") ? vm["savefrequency"].as() : 1; @@ -364,7 +387,7 @@ int main(int argc, char** argv) exit(EXIT_FAILURE); } - if(!gNoGUI) + if(!settings["session"]["headless"].Bool()) { if(SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER|SDL_INIT_AUDIO|SDL_INIT_NOPARACHUTE)) { @@ -427,7 +450,7 @@ int main(int argc, char** argv) #ifdef DISABLE_VIDEO CCS->videoh = new CEmptyVideoPlayer; #else - if (!gNoGUI && !vm.count("disable-video")) + if (!settings["session"]["headless"].Bool() && !vm.count("disable-video")) CCS->videoh = new CVideoPlayer; else CCS->videoh = new CEmptyVideoPlayer; @@ -456,7 +479,7 @@ int main(int argc, char** argv) init(); #endif - if(!gNoGUI ) + if(!settings["session"]["headless"].Bool()) { if(!vm.count("battle") && !vm.count("nointro") && settings["video"]["showIntro"].Bool()) playIntro(); @@ -480,7 +503,6 @@ int main(int argc, char** argv) if(!vm.count("battle")) { - Settings session = settings.write["session"]; session["autoSkip"].Bool() = vm.count("autoSkip"); session["oneGoodAI"].Bool() = vm.count("oneGoodAI"); session["aiSolo"].Bool() = false; @@ -488,8 +510,13 @@ int main(int argc, char** argv) bfs::path fileToStartFrom; //none by default if(vm.count("start")) fileToStartFrom = vm["start"].as(); + std::string testmap; + if(vm.count("testmap")) + testmap = vm["testmap"].as(); - if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom)) + if(!testmap.empty()) + startTestMap(testmap); + else if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom)) startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file else { @@ -511,7 +538,7 @@ int main(int argc, char** argv) startGame(si); } - if(!gNoGUI) + if(!settings["session"]["headless"].Bool()) { mainLoop(); } @@ -1327,7 +1354,7 @@ void handleQuit(bool ask/* = true*/) dispose(); vstd::clear_pointer(console); boost::this_thread::sleep(boost::posix_time::milliseconds(750)); - if(!gNoGUI) + if(!settings["session"]["headless"].Bool()) { cleanupRenderer(); SDL_Quit(); diff --git a/client/Client.cpp b/client/Client.cpp index f00c05eb1..75afa739d 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -432,10 +432,13 @@ void CClient::newGame( CConnection *con, StartInfo *si ) // Init map handler if(gs->map) { - const_cast(CGI)->mh = new CMapHandler(); - CGI->mh->map = gs->map; - logNetwork->infoStream() << "Creating mapHandler: " << tmh.getDiff(); - CGI->mh->init(); + if(!settings["session"]["headless"].Bool()) + { + const_cast(CGI)->mh = new CMapHandler(); + CGI->mh->map = gs->map; + logNetwork->infoStream() << "Creating mapHandler: " << tmh.getDiff(); + CGI->mh->init(); + } pathInfo = make_unique(getMapSize()); logNetwork->infoStream() << "Initializing mapHandler (together): " << tmh.getDiff(); } @@ -472,7 +475,7 @@ void CClient::newGame( CConnection *con, StartInfo *si ) if(si->mode == StartInfo::DUEL) { - if(!gNoGUI) + if(!settings["session"]["headless"].Bool()) { boost::unique_lock un(*CPlayerInterface::pim); auto p = std::make_shared(PlayerColor::NEUTRAL); @@ -744,7 +747,8 @@ void CClient::battleStarted(const BattleInfo * info) def = std::dynamic_pointer_cast( playerint[rightSide.color] ); } - if(!gNoGUI && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL)) + if(!settings["session"]["headless"].Bool() + && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL)) { boost::unique_lock un(*CPlayerInterface::pim); auto bi = new CBattleInterface(leftSide.armyObject, rightSide.armyObject, leftSide.hero, rightSide.hero, diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index fac4a627d..1a9949389 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -283,13 +283,13 @@ void GiveBonus::applyCl(CClient *cl) void ChangeObjPos::applyFirstCl(CClient *cl) { CGObjectInstance *obj = GS(cl)->getObjInstance(objid); - if(flags & 1) + if(flags & 1 && CGI->mh) CGI->mh->hideObject(obj); } void ChangeObjPos::applyCl(CClient *cl) { CGObjectInstance *obj = GS(cl)->getObjInstance(objid); - if(flags & 1) + if(flags & 1 && CGI->mh) CGI->mh->printObject(obj); cl->invalidatePaths(); @@ -335,7 +335,8 @@ void RemoveObject::applyFirstCl(CClient *cl) { const CGObjectInstance *o = cl->getObj(id); - CGI->mh->hideObject(o, true); + if(CGI->mh) + CGI->mh->hideObject(o, true); //notify interfaces about removal for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++) @@ -365,10 +366,12 @@ void TryMoveHero::applyFirstCl(CClient *cl) humanKnows = true; } + if(!CGI->mh) + return; + if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK || !humanKnows) CGI->mh->hideObject(h, result == EMBARK && humanKnows); - if(result == DISEMBARK) CGI->mh->printObject(h->boat); } @@ -378,13 +381,14 @@ void TryMoveHero::applyCl(CClient *cl) const CGHeroInstance *h = cl->getHero(id); cl->invalidatePaths(); - if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK) + if(CGI->mh) { - CGI->mh->printObject(h, result == DISEMBARK); - } + if(result == TELEPORTATION || result == EMBARK || result == DISEMBARK) + CGI->mh->printObject(h, result == DISEMBARK); - if(result == EMBARK) - CGI->mh->hideObject(h->boat); + if(result == EMBARK) + CGI->mh->hideObject(h->boat); + } PlayerColor player = h->tempOwner; @@ -403,10 +407,10 @@ void TryMoveHero::applyCl(CClient *cl) } } - if(!humanKnows) //maphandler didn't get update from playerint, do it now - { //TODO: restructure nicely + //maphandler didn't get update from playerint, do it now + //TODO: restructure nicely + if(!humanKnows && CGI->mh) CGI->mh->printObject(h); - } } void NewStructures::applyCl(CClient *cl) @@ -482,22 +486,22 @@ void HeroRecruited::applyCl(CClient *cl) needsPrinting = false; } } - if (needsPrinting) - { + if(needsPrinting && CGI->mh) CGI->mh->printObject(h); - } } void GiveHero::applyCl(CClient *cl) { CGHeroInstance *h = GS(cl)->getHero(id); - CGI->mh->printObject(h); + if(CGI->mh) + CGI->mh->printObject(h); cl->playerint[h->tempOwner]->heroCreated(h); } void GiveHero::applyFirstCl(CClient *cl) { - CGI->mh->hideObject(GS(cl)->getHero(id)); + if(CGI->mh) + CGI->mh->hideObject(GS(cl)->getHero(id)); } void InfoWindow::applyCl(CClient *cl) @@ -904,7 +908,8 @@ void NewObject::applyCl(CClient *cl) cl->invalidatePaths(); const CGObjectInstance *obj = cl->getObj(id); - CGI->mh->printObject(obj, true); + if(CGI->mh) + CGI->mh->printObject(obj, true); for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++) { From d95c74941bcf15618dd8a7db7bc4bc5050bc692f Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Tue, 14 Mar 2017 02:40:39 +0300 Subject: [PATCH 24/34] Client: add onlyAI option support for saved games When --onlyAI option used all human players will be replaced with AIs during loading. --- client/CMT.cpp | 37 +++++++++++++++++-------------------- client/CMT.h | 4 +--- client/Client.cpp | 17 +++++++++++++++++ client/Client.h | 1 + 4 files changed, 36 insertions(+), 23 deletions(-) diff --git a/client/CMT.cpp b/client/CMT.cpp index 57532744f..15f1b434a 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -333,10 +333,11 @@ int main(int argc, char** argv) preinitDLL(::console); settings.init(); Settings session = settings.write["session"]; + session["onlyai"].Bool() = vm.count("onlyAI"); if(vm.count("headless")) { session["headless"].Bool() = true; - vm.insert(std::pair("onlyAI", po::variable_value())); + session["onlyai"].Bool() = true; } // Init special testing settings @@ -581,6 +582,20 @@ void printInfoAboutIntObject(const CIntObject *obj, int level) printInfoAboutIntObject(child, level+1); } +void removeGUI() +{ + // CClient::endGame + GH.curInt = nullptr; + if(GH.topInt()) + GH.topInt()->deactivate(); + GH.listInt.clear(); + GH.objsToBlit.clear(); + GH.statusbar = nullptr; + logGlobal->infoStream() << "Removed GUI."; + + LOCPLINT = nullptr; +}; + void processCommand(const std::string &message) { std::istringstream readed; @@ -701,10 +716,6 @@ void processCommand(const std::string &message) *ptr = 666; //disaster! } - else if(cn == "onlyai") - { - vm.insert(std::pair("onlyAI", po::variable_value())); - } else if(cn == "mp" && adventureInt) { if(const CGHeroInstance *h = dynamic_cast(adventureInt->selection)) @@ -840,20 +851,6 @@ void processCommand(const std::string &message) } } - auto removeGUI = [&]() - { - // CClient::endGame - GH.curInt = nullptr; - if(GH.topInt()) - GH.topInt()->deactivate(); - GH.listInt.clear(); - GH.objsToBlit.clear(); - GH.statusbar = nullptr; - logNetwork->infoStream() << "Removed GUI."; - - LOCPLINT = nullptr; - - }; auto giveTurn = [&](PlayerColor player) { YourTurn yt; @@ -1302,7 +1299,7 @@ void startGame(StartInfo * options, CConnection *serv/* = nullptr*/) serverAlive.setn(true); } - if(vm.count("onlyAI")) + if(settings["session"]["onlyai"].Bool()) { auto ais = vm.count("ai") ? vm["ai"].as>() : std::vector(); diff --git a/client/CMT.h b/client/CMT.h index a0318a11d..f75e2661c 100644 --- a/client/CMT.h +++ b/client/CMT.h @@ -11,9 +11,7 @@ extern SDL_Surface *screen; // main screen surface extern SDL_Surface *screen2; // and hlp surface (used to store not-active interfaces layer) extern SDL_Surface *screenBuf; // points to screen (if only advmapint is present) or screen2 (else) - should be used when updating controls which are not regularly redrawed - -extern bool gNoGUI; //if true there is no client window and game is silently played between AIs - extern CondSh serverAlive; //used to prevent game start from executing if server is already running +void removeGUI(); void handleQuit(bool ask = true); diff --git a/client/Client.cpp b/client/Client.cpp index 75afa739d..bf9d12878 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -642,6 +642,18 @@ void CClient::serialize(BinaryDeserializer & h, const int version, const std::se installNewPlayerInterface(nInt, pid); nInt->loadGame(h, version); + if(settings["session"]["onlyai"].Bool() && isHuman) + { + removeGUI(); + nInt.reset(); + dllname = aiNameForPlayer(false); + nInt = CDynLibHandler::getNewAI(dllname); + nInt->dllName = dllname; + nInt->human = false; + nInt->playerID = pid; + installNewPlayerInterface(nInt, pid); + GH.totalRedraw(); + } } if(playerIDs.count(PlayerColor::NEUTRAL)) @@ -922,6 +934,11 @@ std::string CClient::aiNameForPlayer(const PlayerSettings &ps, bool battleAI) return ps.name; } + return aiNameForPlayer(battleAI); +} + +std::string CClient::aiNameForPlayer(bool battleAI) +{ 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"; diff --git a/client/Client.h b/client/Client.h index ecd5c201a..27ebec442 100644 --- a/client/Client.h +++ b/client/Client.h @@ -151,6 +151,7 @@ public: void installNewPlayerInterface(std::shared_ptr gameInterface, boost::optional color); void installNewBattleInterface(std::shared_ptr battleInterface, boost::optional color, bool needCallback = true); std::string aiNameForPlayer(const PlayerSettings &ps, bool battleAI); //empty means no AI -> human + std::string aiNameForPlayer(bool battleAI); void endGame(bool closeConnection = true); void stopConnection(); From 18161d3688fdf2600bae20da7734bc032a46ff0b Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sat, 3 Jun 2017 08:25:10 +0300 Subject: [PATCH 25/34] Client: implement spectator mode via command-line options If running with --spectate/-s CPlayerInterface will appear even without human players. Following command-line options also available: --spectate-ignore-hero --spectate-hero-speed=N --spectate-battle-speed=N --spectate-skip-battle --spectate-skip-battle-result Boolean options can also be changed in runtime via client console: set spectate-ignore-hero on / off Spectator mode also: - Work with --onlyAI option when starting game or loading saves. - Allow to use any cheat codes. - Give recon on towns and heroes. --- ChangeLog | 5 ++ client/CMT.cpp | 36 +++++++++++--- client/CMessage.cpp | 2 + client/CPlayerInterface.cpp | 26 ++++++++-- client/CPlayerInterface.h | 1 + client/Client.cpp | 58 ++++++++++++++++++----- client/Client.h | 2 +- client/NetPacksClient.cpp | 30 +++++++----- client/battle/CBattleInterface.cpp | 17 ++++++- client/battle/CBattleInterface.h | 2 +- client/battle/CBattleInterfaceClasses.cpp | 3 +- client/gui/CGuiHandler.cpp | 3 +- client/mapHandler.cpp | 9 ++-- client/windows/CAdvmapInterface.cpp | 16 ++++++- client/windows/CWindowObject.cpp | 6 ++- client/windows/InfoWindows.cpp | 2 + lib/CBattleCallback.cpp | 2 +- lib/CGameInfoCallback.cpp | 2 +- lib/CGameState.cpp | 3 ++ lib/GameConstants.cpp | 6 +++ lib/GameConstants.h | 2 + lib/NetPacksLib.cpp | 3 ++ server/CGameHandler.cpp | 7 ++- server/NetPacksServer.cpp | 7 ++- 24 files changed, 196 insertions(+), 54 deletions(-) diff --git a/ChangeLog b/ChangeLog index 53890c598..afb527b7b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,8 @@ +0.99 -> 1.00 + +GENERAL: +* Spectator mode was implemented through command-line options + 0.98 -> 0.99 GENERAL: diff --git a/client/CMT.cpp b/client/CMT.cpp index 15f1b434a..14498e873 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -123,7 +123,7 @@ void startTestMap(const std::string &mapname) PlayerSettings &pset = si.playerInfos[PlayerColor(i)]; pset.color = PlayerColor(i); pset.name = CGI->generaltexth->allTexts[468];//Computer - pset.playerID = i; + pset.playerID = PlayerSettings::PLAYER_AI; pset.compOnly = true; pset.castle = 0; pset.hero = -1; @@ -263,6 +263,12 @@ int main(int argc, char** argv) ("battle,b", po::value(), "runs game in duel mode (battle-only") ("start", po::value(), "starts game from saved StartInfo file") ("testmap", po::value(), "") + ("spectate,s", "enable spectator interface for AI-only games") + ("spectate-ignore-hero", "wont follow heroes on adventure map") + ("spectate-hero-speed", po::value(), "hero movement speed on adventure map") + ("spectate-battle-speed", po::value(), "battle animation speed for spectator") + ("spectate-skip-battle", "skip battles in spectator view") + ("spectate-skip-battle-result", "skip battle result window") ("onlyAI", "runs without human player, all players will be default AI") ("headless", "runs without GUI, implies --onlyAI") ("ai", po::value>(), "AI to be used for the player, can be specified several times for the consecutive players") @@ -515,18 +521,34 @@ int main(int argc, char** argv) if(vm.count("testmap")) testmap = vm["testmap"].as(); + session["spectate"].Bool() = vm.count("spectate"); + if(session["spectate"].Bool()) + { + session["spectate-ignore-hero"].Bool() = vm.count("spectate-ignore-hero"); + session["spectate-skip-battle"].Bool() = vm.count("spectate-skip-battle"); + session["spectate-skip-battle-result"].Bool() = vm.count("spectate-skip-battle-result"); + if(vm.count("spectate-hero-speed")) + session["spectate-hero-speed"].Integer() = vm["spectate-hero-speed"].as(); + if(vm.count("spectate-battle-speed")) + session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as(); + } if(!testmap.empty()) + { startTestMap(testmap); - else if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom)) - startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file + } else { - if(!fileToStartFrom.empty()) + if(!fileToStartFrom.empty() && bfs::exists(fileToStartFrom)) + startGameFromFile(fileToStartFrom); //ommit pregame and start the game using settings from file + else { - logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom - << "). Falling back to main menu."; + if(!fileToStartFrom.empty()) + { + logGlobal->warnStream() << "Warning: cannot find given file to start from (" << fileToStartFrom + << "). Falling back to main menu."; + } + GH.curInt = CGPreGame::create(); //will set CGP pointer to itself } - GH.curInt = CGPreGame::create(); //will set CGP pointer to itself } } else diff --git a/client/CMessage.cpp b/client/CMessage.cpp index 0106f8637..eb29a32fa 100644 --- a/client/CMessage.cpp +++ b/client/CMessage.cpp @@ -296,6 +296,8 @@ void CMessage::drawIWindow(CInfoWindow * ret, std::string text, PlayerColor play void CMessage::drawBorder(PlayerColor playerColor, SDL_Surface * ret, int w, int h, int x, int y) { + if(playerColor.isSpectator()) + playerColor = PlayerColor(1); std::vector &box = piecesOfBox.at(playerColor.getNum()); // Note: this code assumes that the corner dimensions are all the same. diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 3ebe3eda5..8970b8963 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -245,6 +245,9 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) if (LOCPLINT != this) return; + if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-ignore-hero"].Bool()) + return; + const CGHeroInstance * hero = cb->getHero(details.id); //object representing this hero int3 hp = details.start; @@ -321,7 +324,12 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) } ui32 speed; - if (makingTurn) // our turn, our hero moves + if(settings["session"]["spectate"].Bool()) + { + if(!settings["session"]["spectate-hero-speed"].isNull()) + speed = settings["session"]["spectate-hero-speed"].Integer(); + } + else if (makingTurn) // our turn, our hero moves speed = settings["adventure"]["heroSpeed"].Float(); else speed = settings["adventure"]["enemySpeed"].Float(); @@ -334,7 +342,6 @@ void CPlayerInterface::heroMoved(const TryMoveHero & details) return; // no animation } - adventureInt->centerOn(hero); //actualizing screen pos adventureInt->minimap.redraw(); adventureInt->heroList.redraw(); @@ -471,6 +478,14 @@ int3 CPlayerInterface::repairScreenPos(int3 pos) pos.y = CGI->mh->sizes.y - adventureInt->terrain.tilesh + CGI->mh->frameH; return pos; } + +void CPlayerInterface::activateForSpectator() +{ + adventureInt->state = CAdvMapInt::INGAME; + adventureInt->activate(); + adventureInt->minimap.activate(); +} + void CPlayerInterface::heroPrimarySkillChanged(const CGHeroInstance * hero, int which, si64 val) { EVENT_HANDLER_CALLED_BY_CLIENT; @@ -1637,7 +1652,8 @@ void CPlayerInterface::update() } //in some conditions we may receive calls before selection is initialized - we must ignore them - if (adventureInt && !adventureInt->selection && GH.topInt() == adventureInt) + if(adventureInt && GH.topInt() == adventureInt + && (!adventureInt->selection && !settings["session"]["spectate"].Bool())) { return; } @@ -2131,7 +2147,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul --howManyPeople; - if (howManyPeople == 0) //all human players eliminated + if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated { if (adventureInt) { @@ -2152,7 +2168,7 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul } else { - if (howManyPeople == 0) //all human players eliminated + if(howManyPeople == 0 && !settings["session"]["spectate"].Bool()) //all human players eliminated { requestReturningToMainMenu(); } diff --git a/client/CPlayerInterface.h b/client/CPlayerInterface.h index 08d95c3dd..fc5e54d8d 100644 --- a/client/CPlayerInterface.h +++ b/client/CPlayerInterface.h @@ -236,6 +236,7 @@ public: void updateInfo(const CGObjectInstance * specific); void init(std::shared_ptr CB) override; int3 repairScreenPos(int3 pos); //returns position closest to pos we can center screen on + void activateForSpectator(); // TODO: spectator probably need own player interface class // show dialogs void showInfoDialog(const std::string &text, CComponent * component); diff --git a/client/Client.cpp b/client/Client.cpp index bf9d12878..f2a1c45e5 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -487,6 +487,10 @@ void CClient::newGame( CConnection *con, StartInfo *si ) } else { + if(settings["session"]["spectate"].Bool()) + { + installNewPlayerInterface(std::make_shared(PlayerColor::SPECTATOR), PlayerColor::SPECTATOR, true); + } loadNeutralBattleAI(); } @@ -638,9 +642,6 @@ void CClient::serialize(BinaryDeserializer & h, const int version, const std::se nInt->human = isHuman; nInt->playerID = pid; - if(playerIDs.count(pid)) - installNewPlayerInterface(nInt, pid); - nInt->loadGame(h, version); if(settings["session"]["onlyai"].Bool() && isHuman) { @@ -654,6 +655,20 @@ void CClient::serialize(BinaryDeserializer & h, const int version, const std::se installNewPlayerInterface(nInt, pid); GH.totalRedraw(); } + else + { + if(playerIDs.count(pid)) + installNewPlayerInterface(nInt, pid); + } + } + if(settings["session"]["spectate"].Bool()) + { + removeGUI(); + auto p = std::make_shared(PlayerColor::SPECTATOR); + installNewPlayerInterface(p, PlayerColor::SPECTATOR, true); + GH.curInt = p.get(); + LOCPLINT->activateForSpectator(); + GH.totalRedraw(); } if(playerIDs.count(PlayerColor::NEUTRAL)) @@ -759,15 +774,29 @@ void CClient::battleStarted(const BattleInfo * info) def = std::dynamic_pointer_cast( playerint[rightSide.color] ); } - if(!settings["session"]["headless"].Bool() - && (!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL)) + if(!settings["session"]["headless"].Bool()) { - boost::unique_lock un(*CPlayerInterface::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); + if(!!att || !!def || gs->scenarioOps->mode == StartInfo::DUEL) + { + boost::unique_lock un(*CPlayerInterface::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); + GH.pushInt(bi); + } + else if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool()) + { + //TODO: This certainly need improvement + auto spectratorInt = std::dynamic_pointer_cast(playerint[PlayerColor::SPECTATOR]); + spectratorInt->cb->setBattle(info); + boost::unique_lock un(*CPlayerInterface::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, spectratorInt); + + GH.pushInt(bi); + } } auto callBattleStart = [&](PlayerColor color, ui8 side){ @@ -778,6 +807,8 @@ void CClient::battleStarted(const BattleInfo * info) callBattleStart(leftSide.color, 0); callBattleStart(rightSide.color, 1); callBattleStart(PlayerColor::UNFLAGGABLE, 1); + if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool()) + callBattleStart(PlayerColor::SPECTATOR, 1); if(info->tacticDistance && vstd::contains(battleints,info->sides[info->tacticsSide].color)) { @@ -790,6 +821,9 @@ void CClient::battleFinished() for(auto & side : gs->curB->sides) if(battleCallbacks.count(side.color)) battleCallbacks[side.color]->setBattle(nullptr); + + if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool()) + battleCallbacks[PlayerColor::SPECTATOR]->setBattle(nullptr); } void CClient::loadNeutralBattleAI() @@ -887,7 +921,7 @@ void CClient::campaignMapFinished( std::shared_ptr camp ) } } -void CClient::installNewPlayerInterface(std::shared_ptr gameInterface, boost::optional color) +void CClient::installNewPlayerInterface(std::shared_ptr gameInterface, boost::optional color, bool battlecb) { boost::unique_lock un(*CPlayerInterface::pim); PlayerColor colorUsed = color.get_value_or(PlayerColor::UNFLAGGABLE); @@ -903,7 +937,7 @@ void CClient::installNewPlayerInterface(std::shared_ptr gameInte battleCallbacks[colorUsed] = cb; gameInterface->init(cb); - installNewBattleInterface(gameInterface, color, false); + installNewBattleInterface(gameInterface, color, battlecb); } void CClient::installNewBattleInterface(std::shared_ptr battleInterface, boost::optional color, bool needCallback /*= true*/) diff --git a/client/Client.h b/client/Client.h index 27ebec442..3431e6f8d 100644 --- a/client/Client.h +++ b/client/Client.h @@ -148,7 +148,7 @@ public: void newGame(CConnection *con, StartInfo *si); //con - connection to server void loadNeutralBattleAI(); - void installNewPlayerInterface(std::shared_ptr gameInterface, boost::optional color); + void installNewPlayerInterface(std::shared_ptr gameInterface, boost::optional color, bool battlecb = false); void installNewBattleInterface(std::shared_ptr battleInterface, boost::optional color, bool needCallback = true); std::string aiNameForPlayer(const PlayerSettings &ps, bool battleAI); //empty means no AI -> human std::string aiNameForPlayer(bool battleAI); diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 1a9949389..4e513322a 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -96,6 +96,10 @@ #define BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(function,...) \ CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0].color, function, __VA_ARGS__) \ CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1].color, function, __VA_ARGS__) \ + if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool()) \ + { \ + CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, function, __VA_ARGS__) \ + } \ BATTLE_INTERFACE_CALL_RECEIVERS(function, __VA_ARGS__) /* * NetPacksClient.cpp, part of VCMI engine @@ -358,12 +362,12 @@ void TryMoveHero::applyFirstCl(CClient *cl) //check if playerint will have the knowledge about movement - if not, directly update maphandler for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++) { - if(i->first >= PlayerColor::PLAYER_LIMIT) - continue; - TeamState *t = GS(cl)->getPlayerTeam(i->first); - if((t->fogOfWarMap[start.x-1][start.y][start.z] || t->fogOfWarMap[end.x-1][end.y][end.z]) - && GS(cl)->getPlayer(i->first)->human) - humanKnows = true; + auto ps = GS(cl)->getPlayer(i->first); + if(ps && (GS(cl)->isVisible(start - int3(1, 0, 0), i->first) || GS(cl)->isVisible(end - int3(1, 0, 0), i->first))) + { + if(ps->human) + humanKnows = true; + } } if(!CGI->mh) @@ -399,9 +403,8 @@ void TryMoveHero::applyCl(CClient *cl) //notify interfaces about move for(auto i=cl->playerint.begin(); i!=cl->playerint.end(); i++) { - if(i->first >= PlayerColor::PLAYER_LIMIT) continue; - TeamState *t = GS(cl)->getPlayerTeam(i->first); - if(t->fogOfWarMap[start.x-1][start.y][start.z] || t->fogOfWarMap[end.x-1][end.y][end.z]) + if(GS(cl)->isVisible(start - int3(1, 0, 0), i->first) + || GS(cl)->isVisible(end - int3(1, 0, 0), i->first)) { i->second->heroMoved(*this); } @@ -592,6 +595,8 @@ void BattleStart::applyFirstCl(CClient *cl) info->tile, info->sides[0].hero, info->sides[1].hero); CALL_ONLY_THAT_BATTLE_INTERFACE(info->sides[1].color, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject, info->tile, info->sides[0].hero, info->sides[1].hero); + CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject, + info->tile, info->sides[0].hero, info->sides[1].hero); BATTLE_INTERFACE_CALL_RECEIVERS(battleStartBefore, info->sides[0].armyObject, info->sides[1].armyObject, info->tile, info->sides[0].hero, info->sides[1].hero); } @@ -711,7 +716,7 @@ void BattleResultsApplied::applyCl(CClient *cl) { INTERFACE_CALL_IF_PRESENT(player1, battleResultsApplied); INTERFACE_CALL_IF_PRESENT(player2, battleResultsApplied); - INTERFACE_CALL_IF_PRESENT(PlayerColor::UNFLAGGABLE, battleResultsApplied); + INTERFACE_CALL_IF_PRESENT(PlayerColor::SPECTATOR, battleResultsApplied); if(GS(cl)->initialOpts->mode == StartInfo::DUEL) { handleQuit(); @@ -812,7 +817,10 @@ void PlayerMessage::applyCl(CClient *cl) logNetwork->debugStream() << "Player "<< player <<" sends a message: " << text; std::ostringstream str; - str << cl->getPlayer(player)->nodeName() <<": " << text; + if(player.isSpectator()) + str << "Spectator: " << text; + else + str << cl->getPlayer(player)->nodeName() <<": " << text; if(LOCPLINT) LOCPLINT->cingconsole->print(str.str()); } diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 34696b825..1ab76bb8e 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -95,7 +95,7 @@ void CBattleInterface::addNewAnim(CBattleAnimation *anim) CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, - std::shared_ptr att, std::shared_ptr defen) + std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt) : background(nullptr), queue(nullptr), attackingHeroInstance(hero1), defendingHeroInstance(hero2), animCount(0), activeStack(nullptr), mouseHoveredStack(nullptr), stackToActivate(nullptr), selectedStack(nullptr), previouslyHoveredHex(-1), currentlyHoveredHex(-1), attackingHex(-1), stackCanCastSpell(false), creatureCasting(false), spellDestSelectMode(false), spellToCast(nullptr), sp(nullptr), @@ -105,12 +105,15 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet { OBJ_CONSTRUCTION; - if (!curInt) + if(spectatorInt) + curInt = spectatorInt; + else if(!curInt) { //May happen when we are defending during network MP game -> attacker interface is just not present curInt = defenderInt; } + animsAreDisplayed.setn(false); pos = myRect; strongInterest = true; @@ -377,6 +380,8 @@ CBattleInterface::CBattleInterface(const CCreatureSet *army1, const CCreatureSet currentAction = INVALID; selectedAction = INVALID; addUsedEvents(RCLICK | MOVE | KEYBOARD); + + blockUI(settings["session"]["spectate"].Bool()); } CBattleInterface::~CBattleInterface() @@ -1246,6 +1251,11 @@ void CBattleInterface::battleFinished(const BattleResult& br) void CBattleInterface::displayBattleFinished() { CCS->curh->changeGraphic(ECursor::ADVENTURE,0); + if(settings["session"]["spectate"].Bool() && settings["session"]["spectate-skip-battle-result"].Bool()) + { + GH.popIntTotally(this); + return; + } SDL_Rect temp_rect = genRect(561, 470, (screen->w - 800)/2 + 165, (screen->h - 600)/2 + 19); resWindow = new CBattleResultWindow(*bresult, temp_rect, *this->curInt); @@ -1531,6 +1541,9 @@ void CBattleInterface::setAnimSpeed(int set) int CBattleInterface::getAnimSpeed() const { + if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-battle-speed"].isNull()) + return vstd::round(settings["session"]["spectate-battle-speed"].Float() *100); + return vstd::round(settings["battle"]["animationSpeed"].Float() *100); } diff --git a/client/battle/CBattleInterface.h b/client/battle/CBattleInterface.h index d61e52901..cfe6b2cb9 100644 --- a/client/battle/CBattleInterface.h +++ b/client/battle/CBattleInterface.h @@ -269,7 +269,7 @@ public: ui32 animIDhelper; //for giving IDs for animations static CondSh animsAreDisplayed; //for waiting with the end of battle for end of anims - CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr att, std::shared_ptr defen); //c-tor + CBattleInterface(const CCreatureSet *army1, const CCreatureSet *army2, const CGHeroInstance *hero1, const CGHeroInstance *hero2, const SDL_Rect & myRect, std::shared_ptr att, std::shared_ptr defen, std::shared_ptr spectatorInt = nullptr); //c-tor virtual ~CBattleInterface(); //d-tor //std::vector timeinterested; //animation handling diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 1aa6f63a8..1c474380b 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -211,8 +211,7 @@ void CBattleHero::clickRight(tribool down, bool previousState) windowPosition.y = myOwner->pos.y + 135; InfoAboutHero targetHero; - - if (down && myOwner->myTurn) + if(down && (myOwner->myTurn || settings["session"]["spectate"].Bool())) { auto h = flip ? myOwner->defendingHeroInstance : myOwner->attackingHeroInstance; targetHero.initFromHero(h, InfoAboutHero::EInfoLevel::INBATTLE); diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index aaa00637f..f26d7ac9a 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -361,7 +361,8 @@ void CGuiHandler::simpleRedraw() //update only top interface and draw background if(objsToBlit.size() > 1) blitAt(screen2,0,0,screen); //blit background - objsToBlit.back()->show(screen); //blit active interface/window + if(!objsToBlit.empty()) + objsToBlit.back()->show(screen); //blit active interface/window } void CGuiHandler::handleMoveInterested(const SDL_MouseMotionEvent & motion) diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 7efac572a..93b97cc29 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -54,7 +54,7 @@ struct NeighborTilesInfo if ( dx + pos.x < 0 || dx + pos.x >= sizes.x || dy + pos.y < 0 || dy + pos.y >= sizes.y) return false; - return visibilityMap[dx+pos.x][dy+pos.y][pos.z]; + return settings["session"]["spectate"].Bool() ? true : visibilityMap[dx+pos.x][dy+pos.y][pos.z]; }; d7 = getTile(-1, -1); //789 d8 = getTile( 0, -1); //456 @@ -563,7 +563,7 @@ void CMapHandler::CMapWorldViewBlitter::drawTileOverlay(SDL_Surface * targetSurf const CGObjectInstance * obj = object.obj; const bool sameLevel = obj->pos.z == pos.z; - const bool isVisible = (*info->visibilityMap)[pos.x][pos.y][pos.z]; + const bool isVisible = settings["session"]["spectate"].Bool() ? true : (*info->visibilityMap)[pos.x][pos.y][pos.z]; const bool isVisitable = obj->visitableAt(pos.x, pos.y); if(sameLevel && isVisible && isVisitable) @@ -895,7 +895,7 @@ void CMapHandler::CMapBlitter::blit(SDL_Surface * targetSurf, const MapDrawingIn { const TerrainTile2 & tile = parent->ttiles[pos.x][pos.y][pos.z]; - if (!(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain) + if(!settings["session"]["spectate"].Bool() && !(*info->visibilityMap)[pos.x][pos.y][topTile.z] && !info->showAllTerrain) drawFow(targetSurf); // overlay needs to be drawn over fow, because of artifacts-aura-like spells @@ -1099,6 +1099,9 @@ bool CMapHandler::CMapBlitter::canDrawObject(const CGObjectInstance * obj) const bool CMapHandler::CMapBlitter::canDrawCurrentTile() const { + if(settings["session"]["spectate"].Bool()) + return true; + const NeighborTilesInfo neighbors(pos, parent->sizes, *info->visibilityMap); return !neighbors.areAllHidden(); } diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index 522068eb6..e82e34a9e 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -1021,7 +1021,12 @@ void CAdvMapInt::show(SDL_Surface * to) #endif for(int i = 0; i < 4; i++) - gems[i]->setFrame(LOCPLINT->playerID.getNum()); + { + if(settings["session"]["spectate"].Bool()) + gems[i]->setFrame(PlayerColor(1).getNum()); + else + gems[i]->setFrame(LOCPLINT->playerID.getNum()); + } if(updateScreen) { int3 betterPos = LOCPLINT->repairScreenPos(position); @@ -1481,7 +1486,8 @@ void CAdvMapInt::setPlayer(PlayerColor Player) void CAdvMapInt::startTurn() { state = INGAME; - if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID) + if(LOCPLINT->cb->getCurrentPlayer() == LOCPLINT->playerID + || settings["session"]["spectate"].Bool()) { adjustActiveness(false); minimap.setAIRadar(false); @@ -1490,6 +1496,9 @@ void CAdvMapInt::startTurn() void CAdvMapInt::endingTurn() { + if(settings["session"]["spectate"].Bool()) + return; + if(LOCPLINT->cingconsole->active) LOCPLINT->cingconsole->deactivate(); LOCPLINT->makingTurn = false; @@ -1817,6 +1826,9 @@ const IShipyard * CAdvMapInt::ourInaccessibleShipyard(const CGObjectInstance *ob void CAdvMapInt::aiTurnStarted() { + if(settings["session"]["spectate"].Bool()) + return; + adjustActiveness(true); CCS->musich->playMusicFromSet("enemy-turn", true); adventureInt->minimap.setAIRadar(true); diff --git a/client/windows/CWindowObject.cpp b/client/windows/CWindowObject.cpp index e31e27c0e..05c2489d4 100644 --- a/client/windows/CWindowObject.cpp +++ b/client/windows/CWindowObject.cpp @@ -225,9 +225,13 @@ void CWindowObject::setShadow(bool on) void CWindowObject::showAll(SDL_Surface *to) { + auto color = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); + if(settings["session"]["spectate"].Bool()) + color = PlayerColor(1); // TODO: Spectator shouldn't need special code for UI colors + CIntObject::showAll(to); if ((options & BORDERED) && (pos.h != to->h || pos.w != to->w)) - CMessage::drawBorder(LOCPLINT ? LOCPLINT->playerID : PlayerColor(1), to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); + CMessage::drawBorder(color, to, pos.w+28, pos.h+29, pos.x-14, pos.y-15); } void CWindowObject::close() diff --git a/client/windows/InfoWindows.cpp b/client/windows/InfoWindows.cpp index dcc6a3137..108440df8 100644 --- a/client/windows/InfoWindows.cpp +++ b/client/windows/InfoWindows.cpp @@ -335,6 +335,8 @@ void CRClickPopup::close() void CRClickPopup::createAndPush(const std::string &txt, const CInfoWindow::TCompsInfo &comps) { PlayerColor player = LOCPLINT ? LOCPLINT->playerID : PlayerColor(1); //if no player, then use blue + if(settings["session"]["spectate"].Bool())//TODO: there must be better way to implement this + player = PlayerColor(1); CSimpleWindow * temp = new CInfoWindow(txt, player, comps); temp->center(Point(GH.current->motion)); //center on mouse diff --git a/lib/CBattleCallback.cpp b/lib/CBattleCallback.cpp index 0cbeb40bb..fc5021e23 100644 --- a/lib/CBattleCallback.cpp +++ b/lib/CBattleCallback.cpp @@ -237,7 +237,7 @@ const CGTownInstance * CBattleInfoEssentials::battleGetDefendedTown() const BattlePerspective::BattlePerspective CBattleInfoEssentials::battleGetMySide() const { RETURN_IF_NOT_BATTLE(BattlePerspective::INVALID); - if(!player) + if(!player || player.get().isSpectator()) return BattlePerspective::ALL_KNOWING; if(*player == getBattle()->sides[0].color) return BattlePerspective::LEFT_SIDE; diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index bb334d85b..28fd0b988 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -595,7 +595,7 @@ const CMapHeader * CGameInfoCallback::getMapHeader() const bool CGameInfoCallback::hasAccess(boost::optional playerId) const { - return !player || gs->getPlayerRelations( *playerId, *player ) != PlayerRelations::ENEMIES; + return !player || player.get().isSpectator() || gs->getPlayerRelations( *playerId, *player ) != PlayerRelations::ENEMIES; } EPlayerStatus::EStatus CGameInfoCallback::getPlayerStatus(PlayerColor player, bool verbose) const diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index c3f0e53b4..2a9dfcf89 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2208,6 +2208,9 @@ bool CGameState::isVisible(int3 pos, PlayerColor player) { if(player == PlayerColor::NEUTRAL) return false; + if(player.isSpectator()) + return true; + return getPlayerTeam(player)->fogOfWarMap[pos.x][pos.y][pos.z]; } diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 6cbe2c43f..f6b73581a 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -29,6 +29,7 @@ const SlotID SlotID::SUMMONED_SLOT_PLACEHOLDER = SlotID(-3); const SlotID SlotID::WAR_MACHINES_SLOT = SlotID(-4); const SlotID SlotID::ARROW_TOWERS_SLOT = SlotID(-5); +const PlayerColor PlayerColor::SPECTATOR = PlayerColor(252); const PlayerColor PlayerColor::CANNOT_DETERMINE = PlayerColor(253); const PlayerColor PlayerColor::UNFLAGGABLE = PlayerColor(254); const PlayerColor PlayerColor::NEUTRAL = PlayerColor(255); @@ -72,6 +73,11 @@ bool PlayerColor::isValidPlayer() const return num < PLAYER_LIMIT_I; } +bool PlayerColor::isSpectator() const +{ + return num == 252; +} + std::string PlayerColor::getStr(bool L10n) const { std::string ret = "unnamed"; diff --git a/lib/GameConstants.h b/lib/GameConstants.h index 4d48ec186..7fa93a4e5 100644 --- a/lib/GameConstants.h +++ b/lib/GameConstants.h @@ -257,12 +257,14 @@ class PlayerColor : public BaseForID PLAYER_LIMIT_I = 8 }; + DLL_LINKAGE static const PlayerColor SPECTATOR; //252 DLL_LINKAGE static const PlayerColor CANNOT_DETERMINE; //253 DLL_LINKAGE static const PlayerColor UNFLAGGABLE; //254 - neutral objects (pandora, banks) DLL_LINKAGE static const PlayerColor NEUTRAL; //255 DLL_LINKAGE static const PlayerColor PLAYER_LIMIT; //player limit per map DLL_LINKAGE bool isValidPlayer() const; //valid means < PLAYER_LIMIT (especially non-neutral) + DLL_LINKAGE bool isSpectator() const; DLL_LINKAGE std::string getStr(bool L10n = false) const; DLL_LINKAGE std::string getStrCap(bool L10n = false) const; diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 61992ae09..0268c5727 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -1842,6 +1842,9 @@ DLL_LINKAGE void BattleSetStackProperty::applyGs(CGameState *gs) DLL_LINKAGE void PlayerCheated::applyGs(CGameState *gs) { + if(!player.isValidPlayer()) + return; + gs->getPlayer(player)->enteredLosingCheatCode = losingCheatCode; gs->getPlayer(player)->enteredWinningCheatCode = winningCheatCode; } diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index bf5161fa6..67dfbc6a4 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -1878,7 +1878,8 @@ void CGameHandler::run(bool resume) sbuffer << color << " "; { boost::unique_lock lock(gsm); - connections[color] = cc; + if(!color.isSpectator()) // there can be more than one spectator + connections[color] = cc; } } logGlobal->info(sbuffer.str()); @@ -4430,7 +4431,9 @@ void CGameHandler::playerMessage(PlayerColor player, const std::string &message, { SystemMessage temp_message(VLC->generaltexth->allTexts.at(260)); sendAndApply(&temp_message); - checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature + + if(!player.isSpectator()) + checkVictoryLossConditionsForPlayer(player);//Player enter win code or got required art\creature } } diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 956eb0220..b5ed4d514 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -283,8 +283,11 @@ bool CastAdvSpell::applyGh( CGameHandler *gh ) bool PlayerMessage::applyGh( CGameHandler *gh ) { - ERROR_IF_NOT(player); - if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN; + if(!player.isSpectator()) // TODO: clearly not a great way to verify permissions + { + ERROR_IF_NOT(player); + if(gh->getPlayerAt(c) != player) ERROR_AND_RETURN; + } gh->playerMessage(player,text, currObj); return true; } From 3f7cb9f89397a4176bd8bc5d4cf110b29dd703ef Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Mon, 5 Jun 2017 18:43:02 +0300 Subject: [PATCH 26/34] Client: add some shortcuts for spectator mode Following mode only work when client is started in spectator mode: F5 - Pause / resume game by locking of pim F6 - Toggle spectate-ignore-hero F7 - Toggle spectate-skip-battle F8 - Toggle spectate-skip-battle-result F9 - Skip current battle --- client/NetPacksClient.cpp | 2 +- client/battle/CBattleInterface.cpp | 1 + client/gui/CGuiHandler.cpp | 41 ++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 4e513322a..f2fcb7bd3 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -96,7 +96,7 @@ #define BATTLE_INTERFACE_CALL_IF_PRESENT_FOR_BOTH_SIDES(function,...) \ CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[0].color, function, __VA_ARGS__) \ CALL_ONLY_THAT_BATTLE_INTERFACE(GS(cl)->curB->sides[1].color, function, __VA_ARGS__) \ - if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool()) \ + if(settings["session"]["spectate"].Bool() && !settings["session"]["spectate-skip-battle"].Bool() && LOCPLINT->battleInt) \ { \ CALL_ONLY_THAT_BATTLE_INTERFACE(PlayerColor::SPECTATOR, function, __VA_ARGS__) \ } \ diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 1ab76bb8e..e1c61e087 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -453,6 +453,7 @@ CBattleInterface::~CBattleInterface() int terrain = LOCPLINT->cb->getTile(adventureInt->selection->visitablePos())->terType; CCS->musich->playMusicFromSet("terrain", terrain, true); } + animsAreDisplayed.setn(false); } void CBattleInterface::setPrintCellBorders(bool set) diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index f26d7ac9a..c4eb536ad 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -12,6 +12,7 @@ #include "../../lib/CConfigHandler.h" #include "../CMT.h" #include "../CPlayerInterface.h" +#include "../battle/CBattleInterface.h" extern std::queue events; extern boost::mutex eventsM; @@ -191,6 +192,46 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent) if (sEvent->type==SDL_KEYDOWN || sEvent->type==SDL_KEYUP) { SDL_KeyboardEvent key = sEvent->key; + if(sEvent->type == SDL_KEYDOWN && key.keysym.sym >= SDLK_F1 && key.keysym.sym <= SDLK_F15 && settings["session"]["spectate"].Bool()) + { + //TODO: we need some central place for all interface-independent hotkeys + Settings s = settings.write["session"]; + switch(key.keysym.sym) + { + case SDLK_F5: + if(settings["session"]["spectate-locked-pim"].Bool()) + LOCPLINT->pim->unlock(); + else + LOCPLINT->pim->lock(); + s["spectate-locked-pim"].Bool() = !settings["session"]["spectate-locked-pim"].Bool(); + break; + + case SDLK_F6: + s["spectate-ignore-hero"].Bool() = !settings["session"]["spectate-ignore-hero"].Bool(); + break; + + case SDLK_F7: + s["spectate-skip-battle"].Bool() = !settings["session"]["spectate-skip-battle"].Bool(); + break; + + case SDLK_F8: + s["spectate-skip-battle-result"].Bool() = !settings["session"]["spectate-skip-battle-result"].Bool(); + break; + + case SDLK_F9: + //not working yet since CClient::run remain locked after CBattleInterface removal + if(LOCPLINT->battleInt) + { + GH.popIntTotally(GH.topInt()); + vstd::clear_pointer(LOCPLINT->battleInt); + } + break; + + default: + break; + } + return; + } //translate numpad keys if(key.keysym.sym == SDLK_KP_ENTER) From a2284c3209197d904f5cc10a8b0b56d5bfb201dd Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 4 Jun 2017 11:59:26 +0300 Subject: [PATCH 27/34] Automated testing: graceful shutdown for when game is ended Before when CloseServer / LeaveGame applied there was no thread sync on server. Now server use std::atomic bool for synchronization and graceful shutdown. --- Global.h | 1 + client/CMT.cpp | 9 +++++---- client/Client.cpp | 25 ++++++++++++++----------- client/NetPacksClient.cpp | 4 ++++ server/CGameHandler.cpp | 20 ++++++++++---------- server/CVCMIServer.cpp | 10 +++++----- 6 files changed, 39 insertions(+), 30 deletions(-) diff --git a/Global.h b/Global.h index 98dcefaf7..37726f9ac 100644 --- a/Global.h +++ b/Global.h @@ -143,6 +143,7 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include //The only available version is 3, as of Boost 1.50 #include diff --git a/client/CMT.cpp b/client/CMT.cpp index 14498e873..e686a6196 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -517,9 +517,10 @@ int main(int argc, char** argv) bfs::path fileToStartFrom; //none by default if(vm.count("start")) fileToStartFrom = vm["start"].as(); - std::string testmap; if(vm.count("testmap")) - testmap = vm["testmap"].as(); + { + session["testmap"].String() = vm["testmap"].as(); + } session["spectate"].Bool() = vm.count("spectate"); if(session["spectate"].Bool()) @@ -532,9 +533,9 @@ int main(int argc, char** argv) if(vm.count("spectate-battle-speed")) session["spectate-battle-speed"].Float() = vm["spectate-battle-speed"].as(); } - if(!testmap.empty()) + if(!session["testmap"].isNull()) { - startTestMap(testmap); + startTestMap(session["testmap"].String()); } else { diff --git a/client/Client.cpp b/client/Client.cpp index f2a1c45e5..33706cc24 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -714,19 +714,22 @@ void CClient::stopConnection() { terminate = true; - if (serv && serv->isHost()) //request closing connection + if(serv) { - logNetwork->infoStream() << "Connection has been requested to be closed."; boost::unique_lock(*serv->wmx); - CloseServer close_server; - sendRequest(&close_server, PlayerColor::NEUTRAL); - logNetwork->infoStream() << "Sent closing signal to the server"; - } - else - { - LeaveGame leave_Game; - sendRequest(&leave_Game, PlayerColor::NEUTRAL); - logNetwork->infoStream() << "Sent leaving signal to the server"; + if(serv->isHost()) //request closing connection + { + logNetwork->infoStream() << "Connection has been requested to be closed."; + CloseServer close_server; + sendRequest(&close_server, PlayerColor::NEUTRAL); + logNetwork->infoStream() << "Sent closing signal to the server"; + } + else + { + LeaveGame leave_Game; + sendRequest(&leave_Game, PlayerColor::NEUTRAL); + logNetwork->infoStream() << "Sent leaving signal to the server"; + } } if(connectionHandler)//end connection handler diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index f2fcb7bd3..99bba1032 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -302,6 +302,10 @@ void ChangeObjPos::applyCl(CClient *cl) void PlayerEndsGame::applyCl(CClient *cl) { CALL_IN_ALL_INTERFACES(gameOver, player, victoryLossCheckResult); + + // In auto testing mode we always close client if red player won or lose + if(!settings["session"]["testmap"].isNull() && player == PlayerColor(0)) + handleQuit(settings["session"]["spectate"].Bool()); // if spectator is active ask to close client or not } void RemoveBonus::applyCl(CClient *cl) diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 67dfbc6a4..54dbc20ba 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -48,7 +48,7 @@ #ifndef _MSC_VER #include #endif -extern bool end2; +extern std::atomic serverShuttingDown; #ifdef min #undef min #endif @@ -1033,12 +1033,13 @@ void CGameHandler::handleConnection(std::set players, CConnection & auto handleDisconnection = [&](const std::exception & e) { + boost::unique_lock lock(*c.wmx); assert(!c.connected); //make sure that connection has been marked as broken logGlobal->error(e.what()); conns -= &c; for(auto playerConn : connections) { - if(playerConn.second == &c) + if(!serverShuttingDown && playerConn.second == &c) { PlayerCheated pc; pc.player = playerConn.first; @@ -1128,7 +1129,7 @@ void CGameHandler::handleConnection(std::set players, CConnection & } catch(...) { - end2 = true; + serverShuttingDown = true; handleException(); throw; } @@ -1902,7 +1903,7 @@ void CGameHandler::run(bool resume) if (gs->scenarioOps->mode == StartInfo::DUEL) { runBattle(); - end2 = true; + serverShuttingDown = true; while(conns.size() && (*conns.begin())->isOpen()) @@ -1913,7 +1914,7 @@ void CGameHandler::run(bool resume) auto playerTurnOrder = generatePlayerTurnOrder(); - while(!end2) + while(!serverShuttingDown) { if (!resume) newTurn(); @@ -1954,7 +1955,7 @@ void CGameHandler::run(bool resume) //wait till turn is done boost::unique_lock lock(states.mx); - while (states.players.at(playerColor).makingTurn && !end2) + while(states.players.at(playerColor).makingTurn && !serverShuttingDown) { static time_duration p = milliseconds(100); states.cv.timed_wait(lock, p); @@ -1970,7 +1971,7 @@ void CGameHandler::run(bool resume) activePlayer = true; } if (!activePlayer) - end2 = true; + serverShuttingDown = true; } while(conns.size() && (*conns.begin())->isOpen()) boost::this_thread::sleep(boost::posix_time::milliseconds(5)); //give time client to close socket @@ -2734,7 +2735,7 @@ void CGameHandler::close() { exit(0); } - end2 = true; + serverShuttingDown = true; for (auto & elem : conns) { @@ -2745,7 +2746,6 @@ void CGameHandler::close() elem->close(); elem->connected = false; } - exit(0); } void CGameHandler::playerLeftGame(int cid) @@ -5103,7 +5103,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player) if (p->human) { - end2 = true; + serverShuttingDown = true; if (gs->scenarioOps->campState) { diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 1da17d1e0..a40911aa2 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -44,7 +44,7 @@ std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX #ifndef VCMI_ANDROID namespace intpr = boost::interprocess; #endif -bool end2 = false; +std::atomic serverShuttingDown(false); boost::program_options::variables_map cmdLineOptions; @@ -107,7 +107,7 @@ void CPregameServer::handleConnection(CConnection *cpc) } else if(quitting) // Server must be stopped if host is leaving from lobby to avoid crash { - end2 = true; + serverShuttingDown = true; } } } @@ -477,7 +477,7 @@ void CVCMIServer::start() std::string name = NAME; firstConnection = new CConnection(s, name.append(" STATE_WAITING")); logNetwork->info("Got connection!"); - while(!end2) + while(!serverShuttingDown) { ui8 mode; *firstConnection >> mode; @@ -649,7 +649,7 @@ int main(int argc, char** argv) try { - while (!end2) + while(!serverShuttingDown) { server.start(); } @@ -658,7 +658,7 @@ int main(int argc, char** argv) catch (boost::system::system_error &e) //for boost errors just log, not crash - probably client shut down connection { logNetwork->error(e.what()); - end2 = true; + serverShuttingDown = true; } catch (...) { From 1a60c1a94b4d18da9c9f28c1ba6f61acc5926067 Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Sun, 4 Jun 2017 15:59:11 +0300 Subject: [PATCH 28/34] Shared memory refactoring and command line control options Now client accept following options: --disable-shm - disable shared memory usage --enable-shm-uuid - use UUID for shared memory identifier UUID is useful when a lot of clients starting simultaneously. Needed for testing and was easier to implement than alternatives. --- Global.h | 3 +++ client/CMT.cpp | 5 +++++ client/Client.cpp | 44 +++++++++++++++++++----------------------- client/Client.h | 5 +++-- lib/Interprocess.h | 44 ++++++++++++++++++++++++++++++------------ server/CVCMIServer.cpp | 36 ++++++++++++---------------------- server/CVCMIServer.h | 2 ++ 7 files changed, 77 insertions(+), 62 deletions(-) diff --git a/Global.h b/Global.h index 37726f9ac..c26892b7e 100644 --- a/Global.h +++ b/Global.h @@ -185,6 +185,9 @@ static_assert(sizeof(bool) == 1, "Bool needs to be 1 byte in size."); #include #include #include +#include +#include +#include #ifndef M_PI # define M_PI 3.14159265358979323846 diff --git a/client/CMT.cpp b/client/CMT.cpp index e686a6196..63c95453b 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -260,6 +260,8 @@ int main(int argc, char** argv) opts.add_options() ("help,h", "display help and exit") ("version,v", "display version information and exit") + ("disable-shm", "force disable shared memory usage") + ("enable-shm-uuid", "use UUID for shared memory identifier") ("battle,b", po::value(), "runs game in duel mode (battle-only") ("start", po::value(), "starts game from saved StartInfo file") ("testmap", po::value(), "") @@ -345,6 +347,9 @@ int main(int argc, char** argv) session["headless"].Bool() = true; session["onlyai"].Bool() = true; } + // Shared memory options + session["disable-shm"].Bool() = vm.count("disable-shm"); + session["enable-shm-uuid"].Bool() = vm.count("enable-shm-uuid"); // Init special testing settings session["serverport"].Integer() = vm.count("serverport") ? vm["serverport"].as() : 0; diff --git a/client/Client.cpp b/client/Client.cpp index 33706cc24..0c429cb08 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -41,9 +41,7 @@ #include "CMT.h" extern std::string NAME; -#ifndef VCMI_ANDROID -namespace intpr = boost::interprocess; -#else +#ifdef VCMI_ANDROID #include "lib/CAndroidVMHelper.h" #endif @@ -1019,13 +1017,7 @@ void CServerHandler::waitForServer() #ifndef VCMI_ANDROID if(shared) - { - intpr::scoped_lock slock(shared->sr->mutex); - while(!shared->sr->ready) - { - shared->sr->cond.wait(slock); - } - } + shared->sr->waitTillReady(); #else logNetwork->infoStream() << "waiting for server"; while (!androidTestServerReadyFlag.load()) @@ -1042,15 +1034,7 @@ void CServerHandler::waitForServer() CConnection * CServerHandler::connectToServer() { -#ifndef VCMI_ANDROID - if(shared) - { - if(!shared->sr->ready) - waitForServer(); - } -#else waitForServer(); -#endif th.update(); //put breakpoint here to attach to server before it does something stupid @@ -1084,15 +1068,21 @@ CServerHandler::CServerHandler(bool runServer /*= false*/) serverThread = nullptr; shared = nullptr; verbose = true; + uuid = boost::uuids::to_string(boost::uuids::random_generator()()); #ifndef VCMI_ANDROID - if(DO_NOT_START_SERVER) + if(DO_NOT_START_SERVER || settings["session"]["disable-shm"].Bool()) return; - 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 + std::string sharedMemoryName = "vcmi_memory"; + if(settings["session"]["enable-shm-uuid"].Bool()) + { + //used or automated testing when multiple clients start simultaneously + sharedMemoryName += "_" + uuid; + } try { - shared = new SharedMem(); + shared = new SharedMemory(sharedMemoryName, true); } catch(...) { @@ -1115,11 +1105,17 @@ void CServerHandler::callServer() #ifndef VCMI_ANDROID setThreadName("CServerHandler::callServer"); const std::string logName = (VCMIDirs::get().userCachePath() / "server_log.txt").string(); - const std::string comm = VCMIDirs::get().serverPath().string() + std::string comm = VCMIDirs::get().serverPath().string() + " --port=" + getDefaultPortStr() + " --run-by-client" - + (shared ? " --use-shm" : "") - + " > \"" + logName + '\"'; + + " --uuid=" + uuid; + if(shared) + { + comm += " --enable-shm"; + if(settings["session"]["enable-shm-uuid"].Bool()) + comm += " --enable-shm-uuid"; + } + comm += " > \"" + logName + '\"'; int result = std::system(comm.c_str()); if (result == 0) diff --git a/client/Client.h b/client/Client.h index 3431e6f8d..7e2cbf45f 100644 --- a/client/Client.h +++ b/client/Client.h @@ -28,7 +28,7 @@ class CGameInterface; class CConnection; class CCallback; struct BattleAction; -struct SharedMem; +struct SharedMemory; class CClient; class CScriptingModule; struct CPathsInfo; @@ -46,7 +46,8 @@ public: CStopWatch th; boost::thread *serverThread; //thread that called system to run server - SharedMem *shared; //interprocess memory (for waiting for server) + SharedMemory * shared; + std::string uuid; bool verbose; //whether to print log msgs //functions setting up local server diff --git a/lib/Interprocess.h b/lib/Interprocess.h index 8798aa580..cca2c80b7 100644 --- a/lib/Interprocess.h +++ b/lib/Interprocess.h @@ -20,8 +20,8 @@ struct ServerReady { bool ready; uint16_t port; //ui16? - boost::interprocess::interprocess_mutex mutex; - boost::interprocess::interprocess_condition cond; + boost::interprocess::interprocess_mutex mutex; + boost::interprocess::interprocess_condition cond; ServerReady() { @@ -29,10 +29,19 @@ struct ServerReady port = 0; } - void setToTrueAndNotify(uint16_t Port) + void waitTillReady() + { + boost::interprocess::scoped_lock slock(mutex); + while(!ready) + { + cond.wait(slock); + } + } + + void setToReadyAndNotify(const uint16_t Port) { { - boost::unique_lock lock(mutex); + boost::unique_lock lock(mutex); ready = true; port = Port; } @@ -40,22 +49,33 @@ struct ServerReady } }; -struct SharedMem +struct SharedMemory { + const char * name; boost::interprocess::shared_memory_object smo; - boost::interprocess::mapped_region *mr; - ServerReady *sr; + boost::interprocess::mapped_region * mr; + ServerReady * sr; - SharedMem() //c-tor - :smo(boost::interprocess::open_or_create,"vcmi_memory",boost::interprocess::read_write) + SharedMemory(std::string Name, bool initialize = false) + : name(Name.c_str()) { + if(initialize) + { + //if the application has previously crashed, the memory may not have been removed. to avoid problems - try to destroy it + boost::interprocess::shared_memory_object::remove(name); + } + smo = boost::interprocess::shared_memory_object(boost::interprocess::open_or_create, name, boost::interprocess::read_write); smo.truncate(sizeof(ServerReady)); mr = new boost::interprocess::mapped_region(smo,boost::interprocess::read_write); - sr = new(mr->get_address())ServerReady(); + if(initialize) + sr = new(mr->get_address())ServerReady(); + else + sr = reinterpret_cast(mr->get_address()); }; - ~SharedMem() //d-tor + + ~SharedMemory() { delete mr; - boost::interprocess::shared_memory_object::remove("vcmi_memory"); + boost::interprocess::shared_memory_object::remove(name); } }; diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index a40911aa2..1542cf3b6 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -41,9 +41,6 @@ std::string NAME_AFFIX = "server"; std::string NAME = GameConstants::VCMI_VERSION + std::string(" (") + NAME_AFFIX + ')'; //application name -#ifndef VCMI_ANDROID -namespace intpr = boost::interprocess; -#endif std::atomic serverShuttingDown(false); boost::program_options::variables_map cmdLineOptions; @@ -326,7 +323,7 @@ void CPregameServer::startListeningThread(CConnection * pc) } CVCMIServer::CVCMIServer() - : port(3030), io(new boost::asio::io_service()), firstConnection(nullptr) + : port(3030), io(new boost::asio::io_service()), firstConnection(nullptr), shared(nullptr) { logNetwork->trace("CVCMIServer created!"); if(cmdLineOptions.count("port")) @@ -339,7 +336,7 @@ CVCMIServer::CVCMIServer() catch(...) { logNetwork->info("Port %d is busy, trying to use random port instead", port); - if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("use-shm")) + if(cmdLineOptions.count("run-by-client") && !cmdLineOptions.count("enable-shm")) { logNetwork->error("Cant pass port number to client without shared memory!", port); exit(0); @@ -425,24 +422,14 @@ void CVCMIServer::newPregame() void CVCMIServer::start() { #ifndef VCMI_ANDROID - ServerReady *sr = nullptr; - intpr::mapped_region *mr; - if(cmdLineOptions.count("use-shm")) + if(cmdLineOptions.count("enable-shm")) { - try + std::string sharedMemoryName = "vcmi_memory"; + if(cmdLineOptions.count("enable-shm-uuid") && cmdLineOptions.count("uuid")) { - intpr::shared_memory_object smo(intpr::open_only,"vcmi_memory",intpr::read_write); - smo.truncate(sizeof(ServerReady)); - mr = new intpr::mapped_region(smo,intpr::read_write); - sr = reinterpret_cast(mr->get_address()); - } - catch(...) - { - intpr::shared_memory_object smo(intpr::create_only,"vcmi_memory",intpr::read_write); - smo.truncate(sizeof(ServerReady)); - mr = new intpr::mapped_region(smo,intpr::read_write); - sr = new(mr->get_address())ServerReady(); + sharedMemoryName += "_" + cmdLineOptions["uuid"].as(); } + shared = new SharedMemory(sharedMemoryName); } #endif @@ -460,10 +447,9 @@ void CVCMIServer::start() logNetwork->info("Sending server ready message to client"); } #else - if(cmdLineOptions.count("use-shm")) + if(shared) { - sr->setToTrueAndNotify(port); - delete mr; + shared->sr->setToReadyAndNotify(port); } #endif @@ -557,7 +543,9 @@ static void handleCommandOptions(int argc, char *argv[]) ("help,h", "display help and exit") ("version,v", "display version information and exit") ("run-by-client", "indicate that server launched by client on same machine") - ("use-shm", "enable usage of shared memory") + ("uuid", po::value(), "") + ("enable-shm-uuid", "use UUID for shared memory identifier") + ("enable-shm", "enable usage of shared memory") ("port", po::value(), "port at which server will listen to connections from client") ("resultsFile", po::value()->default_value("./results.txt"), "file to which the battle result will be appended. Used only in the DUEL mode."); diff --git a/server/CVCMIServer.h b/server/CVCMIServer.h index 478072905..8374cc789 100644 --- a/server/CVCMIServer.h +++ b/server/CVCMIServer.h @@ -17,6 +17,7 @@ class CMapInfo; class CConnection; struct CPackForSelectionScreen; class CGameHandler; +struct SharedMemory; namespace boost { @@ -46,6 +47,7 @@ class CVCMIServer ui16 port; boost::asio::io_service *io; TAcceptor * acceptor; + SharedMemory * shared; CConnection *firstConnection; public: From a31c28ec3345643129fde2870011bbd6b44d554a Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Fri, 26 May 2017 19:51:45 +0300 Subject: [PATCH 29/34] Unified war machine mechanics. * it is possible to define new war machines * added warMachine field to artifact configuration --- client/widgets/CArtifactHolder.cpp | 19 ++--- client/windows/CCastleInterface.cpp | 3 +- client/windows/GUIClasses.cpp | 2 +- config/artifacts.json | 14 ++-- config/schemas/artifact.json | 8 +- lib/BattleInfo.cpp | 38 +++++++--- lib/CArtHandler.cpp | 111 +++++++++++----------------- lib/CArtHandler.h | 20 +++-- lib/CCreatureHandler.cpp | 20 +++++ lib/CCreatureHandler.h | 13 ++++ lib/CTownHandler.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 41 ++++++---- lib/mapping/MapFormatH3M.cpp | 12 ++- lib/serializer/CSerializer.h | 2 +- server/CGameHandler.cpp | 67 +++++++++-------- server/CGameHandler.h | 1 + 16 files changed, 221 insertions(+), 152 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index c0cbd80ac..6b641716d 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -161,16 +161,18 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) { const CArtifact * const cur = ourOwner->commonInfo->src.art->artType; - switch(cur->id) + if(cur->id == ArtifactID::CATAPULT) { - case ArtifactID::CATAPULT: //should not happen, catapult cannot be selected - assert(cur->id != ArtifactID::CATAPULT); - break; - case ArtifactID::BALLISTA: case ArtifactID::AMMO_CART: case ArtifactID::FIRST_AID_TENT: //war machines cannot go to backpack + logGlobal->error("Attempt to move Catapult"); + } + else if (cur->isBig()) + { + //war machines cannot go to backpack LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name())); - break; - default: + } + else + { setMeAsDest(); vstd::amin(ourOwner->commonInfo->dst.slotID, ArtifactPosition( ourOwner->curHero->artifactsInBackpack.size() + GameConstants::BACKPACK_START)); @@ -184,7 +186,6 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) deselect(); else ourOwner->realizeCurrentTransaction(); - break; } } } @@ -366,7 +367,7 @@ bool CHeroArtPlace::fitsHere(const CArtifactInstance * art) const // Anything but War Machines can be placed in backpack. if (slotID >= GameConstants::BACKPACK_START) - return !CGI->arth->isBigArtifact(art->artType->id); + return !art->artType->isBig(); return art->canBePutAt(ArtifactLocation(ourOwner->curHero, slotID), true); } diff --git a/client/windows/CCastleInterface.cpp b/client/windows/CCastleInterface.cpp index 32c408f15..28fd8e6bc 100644 --- a/client/windows/CCastleInterface.cpp +++ b/client/windows/CCastleInterface.cpp @@ -754,7 +754,8 @@ void CCastleBuildings::enterBlacksmith(ArtifactID artifactID) } int price = CGI->arth->artifacts[artifactID]->price; bool possible = LOCPLINT->cb->getResourceAmount(Res::GOLD) >= price && !hero->hasArt(artifactID); - GH.pushInt(new CBlacksmithDialog(possible, CArtHandler::machineIDToCreature(artifactID), artifactID, hero->id)); + CreatureID cre = artifactID.toArtifact()->warMachine; + GH.pushInt(new CBlacksmithDialog(possible, cre, artifactID, hero->id)); } void CCastleBuildings::enterBuilding(BuildingID building) diff --git a/client/windows/GUIClasses.cpp b/client/windows/GUIClasses.cpp index 815ada1ab..2acecebce 100644 --- a/client/windows/GUIClasses.cpp +++ b/client/windows/GUIClasses.cpp @@ -205,7 +205,7 @@ void CRecruitmentWindow::buy() CreatureID crid = selected->creature->idNumber; SlotID dstslot = dst-> getSlotFor(crid); - if(!dstslot.validSlot() && !vstd::contains(CGI->arth->bigArtifacts,CGI->arth->creatureToMachineID(crid))) //no available slot + if(!dstslot.validSlot() && (selected->creature->warMachine == ArtifactID::NONE)) //no available slot { std::string txt; if(dst->ID == Obj::HERO) diff --git a/config/artifacts.json b/config/artifacts.json index bd7c46409..d8611e574 100644 --- a/config/artifacts.json +++ b/config/artifacts.json @@ -17,22 +17,26 @@ "catapult": { "index" : 3, - "type" : ["HERO"] + "type" : ["HERO"], + "warMachine":"catapult" }, "ballista": { "index" : 4, - "type" : ["HERO"] + "type" : ["HERO"], + "warMachine":"ballista" }, "ammoCart": { "index" : 5, - "type" : ["HERO"] + "type" : ["HERO"], + "warMachine":"ammoCart" }, "firstAidTent": { "index" : 6, - "type" : ["HERO"] + "type" : ["HERO"], + "warMachine":"firstAidTent" }, "centaurAxe": { @@ -1333,7 +1337,7 @@ "subtype" : 1, "val" : 0, "valueType" : "BASE_NUMBER" - } + } ], "index" : 93, "type" : ["HERO"] diff --git a/config/schemas/artifact.json b/config/schemas/artifact.json index b42b3278a..5cf42b663 100644 --- a/config/schemas/artifact.json +++ b/config/schemas/artifact.json @@ -4,7 +4,7 @@ "title" : "VCMI artifact format", "description" : "Format used to define new artifacts in VCMI", "required" : [ "class", "text", "type", "value" ], - + "definitions" : { "growingBonusList" : { "type" : "array", @@ -117,6 +117,12 @@ "value": { "type":"number", "description": "Cost of this artifact, in gold" + }, + "warMachine": + { + "type":"string", + "description": "Creature id to use on battle field. If set, this artifact is war machine" } + } } diff --git a/lib/BattleInfo.cpp b/lib/BattleInfo.cpp index f377c096a..9f914a460 100644 --- a/lib/BattleInfo.cpp +++ b/lib/BattleInfo.cpp @@ -432,22 +432,36 @@ BattleInfo * BattleInfo::setupBattle( int3 tile, ETerrainType terrain, BFieldTyp if(!creatureBank) { //Checks if hero has artifact and create appropriate stack - auto handleWarMachine= [&](int side, ArtifactPosition artslot, CreatureID cretype, BattleHex hex) + auto handleWarMachine= [&](int side, ArtifactPosition artslot, BattleHex hex) { - if(heroes[side] && heroes[side]->getArt(artslot)) - stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cretype, 1), !side, SlotID::WAR_MACHINES_SLOT, hex)); + const CArtifactInstance * warMachineArt = heroes[side]->getArt(artslot); + + if(nullptr != warMachineArt) + { + CreatureID cre = warMachineArt->artType->warMachine; + + if(cre != CreatureID::NONE) + stacks.push_back(curB->generateNewStack(CStackBasicDescriptor(cre, 1), !side, SlotID::WAR_MACHINES_SLOT, hex)); + } }; - handleWarMachine(0, ArtifactPosition::MACH1, CreatureID::BALLISTA, 52); - handleWarMachine(0, ArtifactPosition::MACH2, CreatureID::AMMO_CART, 18); - handleWarMachine(0, ArtifactPosition::MACH3, CreatureID::FIRST_AID_TENT, 154); - if(town && town->hasFort()) - handleWarMachine(0, ArtifactPosition::MACH4, CreatureID::CATAPULT, 120); + if(heroes[0]) + { - if(!town) //defending hero shouldn't receive ballista (bug #551) - handleWarMachine(1, ArtifactPosition::MACH1, CreatureID::BALLISTA, 66); - handleWarMachine(1, ArtifactPosition::MACH2, CreatureID::AMMO_CART, 32); - handleWarMachine(1, ArtifactPosition::MACH3, CreatureID::FIRST_AID_TENT, 168); + handleWarMachine(0, ArtifactPosition::MACH1, 52); + handleWarMachine(0, ArtifactPosition::MACH2, 18); + handleWarMachine(0, ArtifactPosition::MACH3, 154); + if(town && town->hasFort()) + handleWarMachine(0, ArtifactPosition::MACH4, 120); + } + + if(heroes[1]) + { + if(!town) //defending hero shouldn't receive ballista (bug #551) + handleWarMachine(1, ArtifactPosition::MACH1, 66); + handleWarMachine(1, ArtifactPosition::MACH2, 32); + handleWarMachine(1, ArtifactPosition::MACH3, 168); + } } //war machines added diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 3f6f96bce..4bf502fd2 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -15,6 +15,7 @@ #include "CGeneralTextHandler.h" #include "VCMI_Lib.h" #include "CModHandler.h" +#include "CCreatureHandler.h" #include "spells/CSpellHandler.h" #include "mapObjects/MapObjects.h" #include "NetPacksBase.h" @@ -61,14 +62,21 @@ const std::string & CArtifact::EventText() const return eventText; } -bool CArtifact::isBig () const +bool CArtifact::isBig() const { - return VLC->arth->isBigArtifact(id); + return warMachine != CreatureID::NONE; } -bool CArtifact::isTradable () const +bool CArtifact::isTradable() const { - return VLC->arth->isTradableArtifact(id); + switch(id) + { + case ArtifactID::SPELLBOOK: + case ArtifactID::GRAIL: + return false; + default: + return !isBig(); + } } CArtifact::CArtifact() @@ -120,6 +128,26 @@ void CArtifact::addNewBonus(const std::shared_ptr& b) CBonusSystemNode::addNewBonus(b); } +void CArtifact::fillWarMachine() +{ + switch (id) + { + case ArtifactID::CATAPULT: + warMachine = CreatureID::CATAPULT; + break; + case ArtifactID::BALLISTA: + warMachine = CreatureID::BALLISTA; + break; + case ArtifactID::FIRST_AID_TENT: + warMachine = CreatureID::FIRST_AID_TENT; + break; + case ArtifactID::AMMO_CART: + warMachine = CreatureID::AMMO_CART; + break; + } + warMachine = CreatureID::NONE; //this artifact is not a creature +} + void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art) { auto b = std::make_shared(); @@ -146,11 +174,6 @@ void CGrowingArtifact::levelUpArtifact (CArtifactInstance * art) CArtHandler::CArtHandler() { - //VLC->arth = this; - - // War machines are the default big artifacts. - for (ArtifactID i = ArtifactID::CATAPULT; i <= ArtifactID::FIRST_AID_TENT; i.advance(1)) - bigArtifacts.insert(i); } CArtHandler::~CArtHandler() @@ -310,6 +333,19 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node, const std::string & auto bonus = JsonUtils::parseBonus(b); art->addNewBonus(bonus); } + + const JsonNode & warMachine = node["warMachine"]; + if(warMachine.getType() == JsonNode::DATA_STRING && warMachine.String() != "") + { + VLC->modh->identifiers.requestIdentifier("creature", warMachine, [=](si32 id) + { + art->warMachine = CreatureID(id); + + //this assumes that creature object is stored before registration + VLC->creh->creatures.at(id)->warMachine = art->id; + }); + } + return art; } @@ -453,47 +489,6 @@ void CArtHandler::loadGrowingArt(CGrowingArtifact * art, const JsonNode & node) } } -//TODO: use bimap -ArtifactID CArtHandler::creatureToMachineID(CreatureID id) -{ - switch (id) - { - case CreatureID::CATAPULT: //Catapult - return ArtifactID::CATAPULT; - break; - case CreatureID::BALLISTA: //Ballista - return ArtifactID::BALLISTA; - break; - case CreatureID::FIRST_AID_TENT: //First Aid tent - return ArtifactID::FIRST_AID_TENT; - break; - case CreatureID::AMMO_CART: //Ammo cart - return ArtifactID::AMMO_CART; - break; - } - return ArtifactID::NONE; //this creature is not artifact -} - -CreatureID CArtHandler::machineIDToCreature(ArtifactID id) -{ - switch (id) - { - case ArtifactID::CATAPULT: - return CreatureID::CATAPULT; - break; - case ArtifactID::BALLISTA: - return CreatureID::BALLISTA; - break; - case ArtifactID::FIRST_AID_TENT: - return CreatureID::FIRST_AID_TENT; - break; - case ArtifactID::AMMO_CART: - return CreatureID::AMMO_CART; - break; - } - return CreatureID::NONE; //this artifact is not a creature -} - ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts) { auto getAllowedArts = [&](std::vector > &out, std::vector *arts, CArtifact::EartClass flag) @@ -635,22 +630,6 @@ bool CArtHandler::legalArtifact(ArtifactID id) art->aClass <= CArtifact::ART_RELIC); } -bool CArtHandler::isTradableArtifact(ArtifactID id) const -{ - switch (id) - { - case ArtifactID::SPELLBOOK: - case ArtifactID::GRAIL: - case ArtifactID::CATAPULT: - case ArtifactID::BALLISTA: - case ArtifactID::AMMO_CART: - case ArtifactID::FIRST_AID_TENT: - return false; - default: - return true; - } -} - void CArtHandler::initAllowedArtifactsList(const std::vector &allowed) { allowedArtifacts.clear(); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 1e9d50b33..0e77dad4e 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -61,6 +61,7 @@ public: std::vector constituentOf; // Reverse map of constituents - combined arts that include this art EartClass aClass; ArtifactID id; + CreatureID warMachine; const std::string &Name() const; //getter const std::string &Description() const; //getter @@ -84,12 +85,23 @@ public: { h & identifier; } + + if(version >= 771) + { + h & warMachine; + } + else if(!h.saving) + { + fillWarMachine(); + } } CArtifact(); ~CArtifact(); friend class CArtHandler; +private: + void fillWarMachine(); }; class DLL_LINKAGE CGrowingArtifact : public CArtifact //for example commander artifacts getting bonuses after battle @@ -213,7 +225,6 @@ public: std::vector< ConstTransitivePtr > artifacts; std::vector allowedArtifacts; - std::set bigArtifacts; // Artifacts that cannot be moved to backpack, e.g. war machines. std::set growingArtifacts; void addBonuses(CArtifact *art, const JsonNode &bonusList); @@ -231,13 +242,7 @@ public: ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts); bool legalArtifact(ArtifactID id); - //void getAllowedArts(std::vector > &out, std::vector *arts, int flag); - //void getAllowed(std::vector > &out, int flags); - bool isBigArtifact (ArtifactID artID) const {return bigArtifacts.find(artID) != bigArtifacts.end();} - bool isTradableArtifact (ArtifactID id) const; void initAllowedArtifactsList(const std::vector &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed - static ArtifactID creatureToMachineID(CreatureID id); - static CreatureID machineIDToCreature(ArtifactID id); void makeItCreatureArt (CArtifact * a, bool onlyCreature = true); void makeItCreatureArt (ArtifactID aid, bool onlyCreature = true); void makeItCommanderArt (CArtifact * a, bool onlyCommander = true); @@ -264,7 +269,6 @@ public: { h & artifacts & allowedArtifacts & treasures & minors & majors & relics & growingArtifacts; - //if(!h.saving) sortArts(); } private: diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 36fe19da1..5c07a1eec 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -152,6 +152,26 @@ void CCreature::setId(CreatureID ID) CBonusSystemNode::treeHasChanged(); } +void CCreature::fillWarMachine() +{ + switch (idNumber) + { + case CreatureID::CATAPULT: //Catapult + warMachine = ArtifactID::CATAPULT; + break; + case CreatureID::BALLISTA: //Ballista + warMachine = ArtifactID::BALLISTA; + break; + case CreatureID::FIRST_AID_TENT: //First Aid tent + warMachine = ArtifactID::FIRST_AID_TENT; + break; + case CreatureID::AMMO_CART: //Ammo cart + warMachine = ArtifactID::AMMO_CART; + break; + } + warMachine = ArtifactID::NONE; //this creature is not artifact +} + static void AddAbility(CCreature *cre, const JsonVector &ability_vec) { auto nsf = std::make_shared(); diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index fd9262985..1f278b12a 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -98,6 +98,8 @@ public: } } sounds; + ArtifactID warMachine; + bool isItNativeTerrain(int terrain) const; bool isDoubleWide() const; //returns true if unit is double wide on battlefield bool isFlying() const; //returns true if it is a flying unit @@ -142,9 +144,20 @@ public: { h & identifier; } + if(version >= 771) + { + h & warMachine; + } + else if(!h.saving) + { + fillWarMachine(); + } } CCreature(); + +private: + void fillWarMachine(); }; class DLL_LINKAGE CCreatureHandler : public IHandlerBase diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 4315eb9b7..d90636bfb 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -584,7 +584,7 @@ void CTownHandler::loadTown(CTown &town, const JsonNode & source) VLC->modh->identifiers.requestIdentifier("creature", source["warMachine"], [&town](si32 creature) { - town.warMachine = CArtHandler::creatureToMachineID(CreatureID(creature)); + town.warMachine = CreatureID(creature).toCreature()->warMachine; }); town.moatDamage = source["moatDamage"].Float(); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 135756ab0..8fc7d7f7c 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -352,34 +352,43 @@ void CGHeroInstance::initArmy(CRandomGenerator & rand, IArmyDescriptor *dst /*= int count = rand.nextInt(stack.minAmount, stack.maxAmount); - if(stack.creature >= CreatureID::CATAPULT && - stack.creature <= CreatureID::ARROW_TOWERS) //war machine + const CCreature * creature = stack.creature.toCreature(); + + if(creature == nullptr) + { + logGlobal->error("Hero %s has invalid creature with id %d in initial army", name, stack.creature.toEnum()); + continue; + } + + if(creature->warMachine != ArtifactID::NONE) //war machine { warMachinesGiven++; if(dst != this) continue; int slot = -1; - ArtifactID aid = ArtifactID::NONE; - switch (stack.creature) + ArtifactID aid = creature->warMachine; + const CArtifact * art = aid.toArtifact(); + + if(art != nullptr && !art->possibleSlots.at(ArtBearer::HERO).empty()) { - case CreatureID::CATAPULT: - slot = ArtifactPosition::MACH4; - aid = ArtifactID::CATAPULT; - break; - default: - aid = CArtHandler::creatureToMachineID(stack.creature); - slot = 9 + aid; - break; + //TODO: should we try another possible slots? + ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front(); + + if(!getArt(slot)) + putArtifact(slot, CArtifactInstance::createNewArtifactInstance(aid)); + else + logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid; } - auto convSlot = ArtifactPosition(slot); - if(!getArt(convSlot)) - putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid)); else - logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid; + { + logGlobal->error("Hero %s has invalid war machine in initial army", name); + } } else + { dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count); + } } } diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index d9dd05d24..4950cdbd8 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -872,9 +872,17 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot) bool isArt = aid != artmask; if(isArt) { - if(vstd::contains(VLC->arth->bigArtifacts, aid) && slot >= GameConstants::BACKPACK_START) + const CArtifact * art = ArtifactID(aid).toArtifact(); + + if(nullptr == art) { - logGlobal->warnStream() << "Warning: A big artifact (war machine) in hero's backpack, ignoring..."; + logGlobal->warnStream() << "Invalid artifact in hero's backpack, ignoring..."; + return false; + } + + if(art->isBig() && slot >= GameConstants::BACKPACK_START) + { + logGlobal->warnStream() << "A big artifact (war machine) in hero's backpack, ignoring..."; return false; } if(aid == 0 && slot == ArtifactPosition::MISC5) diff --git a/lib/serializer/CSerializer.h b/lib/serializer/CSerializer.h index b6090d147..0b72b2fc6 100644 --- a/lib/serializer/CSerializer.h +++ b/lib/serializer/CSerializer.h @@ -14,7 +14,7 @@ #include "../ConstTransitivePtr.h" #include "../GameConstants.h" -const ui32 SERIALIZATION_VERSION = 770; +const ui32 SERIALIZATION_VERSION = 771; const ui32 MINIMAL_SERIALIZATION_VERSION = 753; const std::string SAVEGAME_MAGIC = "VCMISVG"; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 54dbc20ba..db0da07d3 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -3135,7 +3135,7 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst const CGDwelling * dw = static_cast(getObj(objid)); const CArmedInstance *dst = nullptr; const CCreature *c = VLC->creh->creatures.at(crid); - bool warMachine = c->hasBonusOfType(Bonus::SIEGE_WEAPON); + const bool warMachine = c->warMachine != ArtifactID::NONE; //TODO: test for owning //TODO: check if dst can recruit objects (e.g. hero is actually visiting object, town and source are same, etc) @@ -3186,24 +3186,18 @@ bool CGameHandler::recruitCreatures(ObjectInstanceID objid, ObjectInstanceID dst if (warMachine) { const CGHeroInstance *h = dynamic_cast(dst); - if (!h) - COMPLAIN_RET("Only hero can buy war machines"); - switch(crid) - { - case CreatureID::BALLISTA: - giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::BALLISTA], ArtifactPosition::MACH1); - break; - case CreatureID::FIRST_AID_TENT: - giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::FIRST_AID_TENT], ArtifactPosition::MACH3); - break; - case CreatureID::AMMO_CART: - giveHeroNewArtifact(h, VLC->arth->artifacts[ArtifactID::AMMO_CART], ArtifactPosition::MACH2); - break; - default: - complain("This war machine cannot be recruited!"); - return false; - } + COMPLAIN_RET_FALSE_IF(!h, "Only hero can buy war machines"); + + ArtifactID artId = c->warMachine; + + COMPLAIN_RET_FALSE_IF(artId == ArtifactID::CATAPULT, "Catapult cannot be recruited!"); + + const CArtifact * art = artId.toArtifact(); + + COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid war machine artifact"); + + return giveHeroNewArtifact(h, art); } else { @@ -3444,7 +3438,10 @@ bool CGameHandler::assembleArtifacts (ObjectInstanceID heroID, ArtifactPosition bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid) { const CGHeroInstance * hero = getHero(hid); + COMPLAIN_RET_FALSE_IF(nullptr == hero, "Invalid hero index"); const CGTownInstance * town = hero->visitedTown; + COMPLAIN_RET_FALSE_IF(nullptr == town, "Hero not in town"); + if (aid==ArtifactID::SPELLBOOK) { if ((!town->hasBuilt(BuildingID::MAGES_GUILD_1) && complain("Cannot buy a spellbook, no mage guild in the town!")) @@ -3459,26 +3456,24 @@ bool CGameHandler::buyArtifact(ObjectInstanceID hid, ArtifactID aid) giveSpells(town,hero); return true; } - else if (aid < 7 && aid > 3) //war machine + else { - int price = VLC->arth->artifacts[aid]->price; + const CArtifact * art = aid.toArtifact(); + COMPLAIN_RET_FALSE_IF(nullptr == art, "Invalid artifact index to buy"); + COMPLAIN_RET_FALSE_IF(art->warMachine == CreatureID::NONE, "War machine artifact required"); + COMPLAIN_RET_FALSE_IF(hero->hasArt(aid),"Hero already has this machine!"); + const int price = art->price; + COMPLAIN_RET_FALSE_IF(getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price, "Not enough gold!"); - if ((hero->getArt(ArtifactPosition(9+aid)) && complain("Hero already has this machine!")) - || (getPlayer(hero->getOwner())->resources.at(Res::GOLD) < price && complain("Not enough gold!"))) - { - return false; - } if ((town->hasBuilt(BuildingID::BLACKSMITH) && town->town->warMachine == aid) || ((town->hasBuilt(BuildingID::BALLISTA_YARD, ETownType::STRONGHOLD)) && aid == ArtifactID::BALLISTA)) { giveResource(hero->getOwner(),Res::GOLD,-price); - giveHeroNewArtifact(hero, VLC->arth->artifacts[aid], ArtifactPosition(9+aid)); - return true; + return giveHeroNewArtifact(hero, art); } else COMPLAIN_RET("This machine is unavailable here!"); } - return false; } bool CGameHandler::buyArtifact(const IMarket *m, const CGHeroInstance *h, Res::ERes rid, ArtifactID aid) @@ -6053,6 +6048,18 @@ void CGameHandler::putArtifact(const ArtifactLocation &al, const CArtifactInstan sendAndApply(&pa); } +bool CGameHandler::giveHeroNewArtifact(const CGHeroInstance* h, const CArtifact* art) +{ + COMPLAIN_RET_FALSE_IF(art->possibleSlots.at(ArtBearer::HERO).empty(),"Not a hero artifact!"); + + ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front(); + + COMPLAIN_RET_FALSE_IF(nullptr != h->getArt(slot, false), "Hero already has artifact in slot"); + + giveHeroNewArtifact(h, art, slot); + return true; +} + void CGameHandler::giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) { CArtifactInstance *a = nullptr; @@ -6415,10 +6422,12 @@ CasualtiesAfterBattle::CasualtiesAfterBattle(const CArmedInstance * _army, Battl } else if (st->slot == SlotID::WAR_MACHINES_SLOT) { - auto warMachine = VLC->arth->creatureToMachineID(st->type->idNumber); + auto warMachine = st->type->warMachine; if (warMachine == ArtifactID::NONE) + { logGlobal->error("Invalid creature in war machine virtual slot. Stack: %s", st->nodeName()); + } //catapult artifact remain even if "creature" killed in siege else if (warMachine != ArtifactID::CATAPULT && !st->count) { diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 5e1fe9a8b..97564185d 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -143,6 +143,7 @@ public: void removeAfterVisit(const CGObjectInstance *object) override; + bool giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *art); void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override; void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override; void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override; From 0190c9804eccee6d65a503b1039b000e05484e95 Mon Sep 17 00:00:00 2001 From: AlexVinS Date: Tue, 6 Jun 2017 19:45:34 +0300 Subject: [PATCH 30/34] formatting --- client/widgets/CArtifactHolder.cpp | 2 +- config/artifacts.json | 8 ++++---- server/CGameHandler.cpp | 4 ++-- server/CGameHandler.h | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/client/widgets/CArtifactHolder.cpp b/client/widgets/CArtifactHolder.cpp index 6b641716d..d1e47176f 100644 --- a/client/widgets/CArtifactHolder.cpp +++ b/client/widgets/CArtifactHolder.cpp @@ -166,7 +166,7 @@ void CHeroArtPlace::clickLeft(tribool down, bool previousState) //should not happen, catapult cannot be selected logGlobal->error("Attempt to move Catapult"); } - else if (cur->isBig()) + else if(cur->isBig()) { //war machines cannot go to backpack LOCPLINT->showInfoDialog(boost::str(boost::format(CGI->generaltexth->allTexts[153]) % cur->Name())); diff --git a/config/artifacts.json b/config/artifacts.json index d8611e574..869e33dc6 100644 --- a/config/artifacts.json +++ b/config/artifacts.json @@ -18,25 +18,25 @@ { "index" : 3, "type" : ["HERO"], - "warMachine":"catapult" + "warMachine" : "catapult" }, "ballista": { "index" : 4, "type" : ["HERO"], - "warMachine":"ballista" + "warMachine" : "ballista" }, "ammoCart": { "index" : 5, "type" : ["HERO"], - "warMachine":"ammoCart" + "warMachine" : "ammoCart" }, "firstAidTent": { "index" : 6, "type" : ["HERO"], - "warMachine":"firstAidTent" + "warMachine" : "firstAidTent" }, "centaurAxe": { diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index db0da07d3..f6ea1eec2 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -6048,9 +6048,9 @@ void CGameHandler::putArtifact(const ArtifactLocation &al, const CArtifactInstan sendAndApply(&pa); } -bool CGameHandler::giveHeroNewArtifact(const CGHeroInstance* h, const CArtifact* art) +bool CGameHandler::giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * art) { - COMPLAIN_RET_FALSE_IF(art->possibleSlots.at(ArtBearer::HERO).empty(),"Not a hero artifact!"); + COMPLAIN_RET_FALSE_IF(art->possibleSlots.at(ArtBearer::HERO).empty(), "Not a hero artifact!"); ArtifactPosition slot = art->possibleSlots.at(ArtBearer::HERO).front(); diff --git a/server/CGameHandler.h b/server/CGameHandler.h index 97564185d..126f05b7d 100644 --- a/server/CGameHandler.h +++ b/server/CGameHandler.h @@ -143,7 +143,7 @@ public: void removeAfterVisit(const CGObjectInstance *object) override; - bool giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *art); + bool giveHeroNewArtifact(const CGHeroInstance * h, const CArtifact * art); void giveHeroNewArtifact(const CGHeroInstance *h, const CArtifact *artType, ArtifactPosition pos) override; void giveHeroArtifact(const CGHeroInstance *h, const CArtifactInstance *a, ArtifactPosition pos) override; void putArtifact(const ArtifactLocation &al, const CArtifactInstance *a) override; From 02fa478bfb3d6cd7704473ae5a7392d950c7be4c Mon Sep 17 00:00:00 2001 From: Fay Date: Wed, 7 Jun 2017 20:16:18 +0200 Subject: [PATCH 31/34] Refactor: unified logic for notyfing CIntObjs about left/right mouse click events; Added support for middle-click; --- client/VCMI_client.vcxproj | 2 - client/VCMI_client.vcxproj.filters | 10 +-- client/gui/CGuiHandler.cpp | 110 +++++++++++-------------- client/gui/CGuiHandler.h | 3 + client/gui/CIntObject.cpp | 34 +++++--- client/gui/CIntObject.h | 17 ++-- client/widgets/AdventureMapClasses.cpp | 2 +- client/widgets/Buttons.cpp | 2 +- 8 files changed, 89 insertions(+), 91 deletions(-) diff --git a/client/VCMI_client.vcxproj b/client/VCMI_client.vcxproj index ec30a83ab..9d0476656 100644 --- a/client/VCMI_client.vcxproj +++ b/client/VCMI_client.vcxproj @@ -292,6 +292,4 @@ - - \ No newline at end of file diff --git a/client/VCMI_client.vcxproj.filters b/client/VCMI_client.vcxproj.filters index d389ba18b..b044f1d67 100644 --- a/client/VCMI_client.vcxproj.filters +++ b/client/VCMI_client.vcxproj.filters @@ -109,17 +109,10 @@ gui - - - - Source Files - + - - Header Files - @@ -254,5 +247,6 @@ gui + \ No newline at end of file diff --git a/client/gui/CGuiHandler.cpp b/client/gui/CGuiHandler.cpp index c4eb536ad..57345a1e4 100644 --- a/client/gui/CGuiHandler.cpp +++ b/client/gui/CGuiHandler.cpp @@ -60,6 +60,7 @@ void CGuiHandler::processLists(const ui16 activityFlag, std::functioncurh->cursorMove(sEvent->motion.x, sEvent->motion.y); handleMouseMotion(sEvent); } - else if (sEvent->type==SDL_MOUSEBUTTONDOWN) + else if(sEvent->type == SDL_MOUSEBUTTONDOWN) { - if(sEvent->button.button == SDL_BUTTON_LEFT) + switch(sEvent->button.button) { - - if(lastClick == sEvent->motion && (SDL_GetTicks() - lastClickTime) < 300) + case SDL_BUTTON_LEFT: + if(lastClick == sEvent->motion && (SDL_GetTicks() - lastClickTime) < 300) { std::list hlp = doubleClickInterested; - for(auto i=hlp.begin(); i != hlp.end() && current; i++) + for(auto i = hlp.begin(); i != hlp.end() && current; i++) { - if(!vstd::contains(doubleClickInterested,*i)) continue; - if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) + if(!vstd::contains(doubleClickInterested, *i)) continue; + if(isItIn(&(*i)->pos, sEvent->motion.x, sEvent->motion.y)) { (*i)->onDoubleClick(); } @@ -282,31 +283,16 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent) lastClick = sEvent->motion; lastClickTime = SDL_GetTicks(); - std::list hlp = lclickable; - for(auto i=hlp.begin(); i != hlp.end() && current; i++) - { - if(!vstd::contains(lclickable,*i)) continue; - if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) - { - prev = (*i)->pressedL; - (*i)->pressedL = true; - (*i)->clickLeft(true, prev); - } - } - } - else if (sEvent->button.button == SDL_BUTTON_RIGHT) - { - std::list hlp = rclickable; - for(auto i=hlp.begin(); i != hlp.end() && current; i++) - { - if(!vstd::contains(rclickable,*i)) continue; - if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) - { - prev = (*i)->pressedR; - (*i)->pressedR = true; - (*i)->clickRight(true, prev); - } - } + handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, true); + break; + case SDL_BUTTON_RIGHT: + handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, true); + break; + case SDL_BUTTON_MIDDLE: + handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, true); + break; + default: + break; } } else if (sEvent->type == SDL_MOUSEWHEEL) @@ -336,41 +322,45 @@ void CGuiHandler::handleEvent(SDL_Event *sEvent) } } //todo: muiltitouch - else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_LEFT)) + else if(sEvent->type == SDL_MOUSEBUTTONUP) { - std::list hlp = lclickable; - for(auto i=hlp.begin(); i != hlp.end() && current; i++) + switch(sEvent->button.button) { - if(!vstd::contains(lclickable,*i)) continue; - prev = (*i)->pressedL; - (*i)->pressedL = false; - if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) - { - (*i)->clickLeft(false, prev); - } - else - (*i)->clickLeft(boost::logic::indeterminate, prev); - } - } - else if ((sEvent->type==SDL_MOUSEBUTTONUP) && (sEvent->button.button == SDL_BUTTON_RIGHT)) - { - std::list hlp = rclickable; - for(auto i=hlp.begin(); i != hlp.end() && current; i++) - { - if(!vstd::contains(rclickable,*i)) continue; - prev = (*i)->pressedR; - (*i)->pressedR = false; - if (isItIn(&(*i)->pos,sEvent->motion.x,sEvent->motion.y)) - { - (*i)->clickRight(false, prev); - } - else - (*i)->clickRight(boost::logic::indeterminate, prev); + case SDL_BUTTON_LEFT: + handleMouseButtonClick(lclickable, EIntObjMouseBtnType::LEFT, false); + break; + case SDL_BUTTON_RIGHT: + handleMouseButtonClick(rclickable, EIntObjMouseBtnType::RIGHT, false); + break; + case SDL_BUTTON_MIDDLE: + handleMouseButtonClick(mclickable, EIntObjMouseBtnType::MIDDLE, false); + break; } } current = nullptr; } //event end +void CGuiHandler::handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed) +{ + auto hlp = interestedObjs; + for(auto i = hlp.begin(); i != hlp.end() && current; i++) + { + if(!vstd::contains(interestedObjs, *i)) continue; + + auto prev = (*i)->mouseState(btn); + if(!isPressed) + (*i)->updateMouseState(btn, isPressed); + if(isItIn(&(*i)->pos, current->motion.x, current->motion.y)) + { + if(isPressed) + (*i)->updateMouseState(btn, isPressed); + (*i)->click(btn, isPressed, prev); + } + else if(!isPressed) + (*i)->click(btn, boost::logic::indeterminate, prev); + } +} + void CGuiHandler::handleMouseMotion(SDL_Event *sEvent) { //sending active, hovered hoverable objects hover() call diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index 3449dd76c..ad3fef61b 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -10,6 +10,7 @@ class CIntObject; class IUpdateable; class IShowActivatable; class IShowable; +enum class EIntObjMouseBtnType; template struct CondSh; /* @@ -53,6 +54,7 @@ private: //active GUI elements (listening for events CIntObjectList lclickable, rclickable, + mclickable, hoverable, keyinterested, motioninterested, @@ -62,6 +64,7 @@ private: textInterested; + void handleMouseButtonClick(CIntObjectList &interestedObjs, EIntObjMouseBtnType btn, bool isPressed); void processLists(const ui16 activityFlag, std::function *)> cb); public: void handleElementActivate(CIntObject * elem, ui16 activityFlag); diff --git a/client/gui/CIntObject.cpp b/client/gui/CIntObject.cpp index 8fa3b3025..40e7c281f 100644 --- a/client/gui/CIntObject.cpp +++ b/client/gui/CIntObject.cpp @@ -16,7 +16,7 @@ CIntObject::CIntObject(int used_, Point pos_): parent(parent_m), active(active_m) { - pressedL = pressedR = hovered = captureAllKeys = strongInterest = false; + hovered = captureAllKeys = strongInterest = false; toNextTick = timerDelay = 0; used = used_; @@ -134,6 +134,23 @@ CIntObject::~CIntObject() parent_m->removeChild(this); } +void CIntObject::click(EIntObjMouseBtnType btn, tribool down, bool previousState) +{ + switch(btn) + { + default: + case EIntObjMouseBtnType::LEFT: + clickLeft(down, previousState); + break; + case EIntObjMouseBtnType::MIDDLE: + clickMiddle(down, previousState); + break; + case EIntObjMouseBtnType::RIGHT: + clickRight(down, previousState); + break; + } +} + void CIntObject::printAtLoc( const std::string & text, int x, int y, EFonts font, SDL_Color kolor/*=Colors::WHITE*/, SDL_Surface * dst/*=screen*/ ) { graphics->fonts[font]->renderTextLeft(dst, text, kolor, Point(pos.x + x, pos.y + y)); @@ -340,16 +357,9 @@ void CKeyShortcut::keyPressed(const SDL_KeyboardEvent & key) if(vstd::contains(assignedKeys,key.keysym.sym) || vstd::contains(assignedKeys, CGuiHandler::numToDigit(key.keysym.sym))) { - bool prev = pressedL; - if(key.state == SDL_PRESSED) - { - pressedL = true; - clickLeft(true, prev); - } - else - { - pressedL = false; - clickLeft(false, prev); - } + bool prev = mouseState(EIntObjMouseBtnType::LEFT); + updateMouseState(EIntObjMouseBtnType::LEFT, key.state == SDL_PRESSED); + clickLeft(key.state == SDL_PRESSED, prev); + } } diff --git a/client/gui/CIntObject.h b/client/gui/CIntObject.h index 75d8c2507..31f479738 100644 --- a/client/gui/CIntObject.h +++ b/client/gui/CIntObject.h @@ -61,6 +61,7 @@ public: virtual ~IShowActivatable(){}; //d-tor }; +enum class EIntObjMouseBtnType { LEFT, MIDDLE, RIGHT }; //typedef ui16 ActivityFlag; // Base UI element @@ -73,6 +74,8 @@ class CIntObject : public IShowActivatable //interface object int toNextTick; int timerDelay; + std::map currentMouseState; + void onTimer(int timePassed); //non-const versions of fields to allow changing them in CIntObject @@ -104,13 +107,13 @@ public: CIntObject(int used=0, Point offset=Point()); virtual ~CIntObject(); //d-tor - //l-clicks handling - /*const*/ bool pressedL; //for determining if object is L-pressed - virtual void clickLeft(tribool down, bool previousState){} + void updateMouseState(EIntObjMouseBtnType btn, bool state) { currentMouseState[btn] = state; } + bool mouseState(EIntObjMouseBtnType btn) const { return currentMouseState.count(btn) ? currentMouseState.at(btn) : false; } - //r-clicks handling - /*const*/ bool pressedR; //for determining if object is R-pressed - virtual void clickRight(tribool down, bool previousState){} + virtual void click(EIntObjMouseBtnType btn, tribool down, bool previousState); + virtual void clickLeft(tribool down, bool previousState) {} + virtual void clickRight(tribool down, bool previousState) {} + virtual void clickMiddle(tribool down, bool previousState) {} //hover handling /*const*/ bool hovered; //for determining if object is hovered @@ -138,7 +141,7 @@ public: //double click virtual void onDoubleClick(){} - enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, ALL=0xffff}; + enum {LCLICK=1, RCLICK=2, HOVER=4, MOVE=8, KEYBOARD=16, TIME=32, GENERAL=64, WHEEL=128, DOUBLECLICK=256, TEXTINPUT=512, MCLICK=1024, ALL=0xffff}; const ui16 & active; void addUsedEvents(ui16 newActions); void removeUsedEvents(ui16 newActions); diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 1cea6ad3c..2f67737d5 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -587,7 +587,7 @@ void CMinimap::hover(bool on) void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent) { - if (pressedL) + if (mouseState(EIntObjMouseBtnType::LEFT)) moveAdvMapSelection(); } diff --git a/client/widgets/Buttons.cpp b/client/widgets/Buttons.cpp index 8784e0d1a..7abc4a5c5 100644 --- a/client/widgets/Buttons.cpp +++ b/client/widgets/Buttons.cpp @@ -623,7 +623,7 @@ void CSlider::clickLeft(tribool down, bool previousState) return; // if (rw>1) return; // if (rw<0) return; - slider->clickLeft(true, slider->pressedL); + slider->clickLeft(true, slider->mouseState(EIntObjMouseBtnType::LEFT)); moveTo(rw * positions + 0.5); return; } From 01bae590f1aee585c19afe5117f5b894fe2d012d Mon Sep 17 00:00:00 2001 From: Fay Date: Wed, 7 Jun 2017 22:42:41 +0200 Subject: [PATCH 32/34] Added swipe support for non-android platforms; --- client/windows/CAdvmapInterface.cpp | 68 +++++++++++++++++------------ client/windows/CAdvmapInterface.h | 9 ++-- config/schemas/settings.json | 2 +- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/client/windows/CAdvmapInterface.cpp b/client/windows/CAdvmapInterface.cpp index e82e34a9e..131e510e2 100644 --- a/client/windows/CAdvmapInterface.cpp +++ b/client/windows/CAdvmapInterface.cpp @@ -98,7 +98,7 @@ CTerrainRect::CTerrainRect() pos.w=ADVOPT.advmapW; pos.h=ADVOPT.advmapH; moveX = moveY = 0; - addUsedEvents(LCLICK | RCLICK | HOVER | MOVE); + addUsedEvents(LCLICK | RCLICK | MCLICK | HOVER | MOVE); } CTerrainRect::~CTerrainRect() @@ -124,17 +124,10 @@ void CTerrainRect::clickLeft(tribool down, bool previousState) #ifdef VCMI_ANDROID if(adventureInt->swipeEnabled) { - if(down == true) + if(handleSwipeStateChange(down == true)) { - swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0); - swipeInitialMapPos = int3(adventureInt->position); return; // if swipe is enabled, we don't process "down" events and wait for "up" (to make sure this wasn't a swiping gesture) } - else if(isSwiping) // only accept this touch if it wasn't a swipe - { - isSwiping = false; - return; - } } else { @@ -165,22 +158,32 @@ void CTerrainRect::clickRight(tribool down, bool previousState) adventureInt->tileRClicked(mp); } +void CTerrainRect::clickMiddle(tribool down, bool previousState) +{ + handleSwipeStateChange(down == true); +} + void CTerrainRect::mouseMoved(const SDL_MouseMotionEvent & sEvent) { handleHover(sEvent); -#ifdef VCMI_ANDROID - if(!adventureInt->swipeEnabled || sEvent.state == 0) + if(!adventureInt->swipeEnabled) return; handleSwipeMove(sEvent); -#endif // !VCMI_ANDROID } -#ifdef VCMI_ANDROID - void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent) { +#ifdef VCMI_ANDROID + if(sEvent.state == 0) // any "button" is enough on android +#else //!VCMI_ANDROID + if((sEvent.state & SDL_BUTTON_MMASK) == 0) // swipe only works with middle mouse on other platforms +#endif //!VCMI_ANDROID + { + return; + } + if(!isSwiping) { // try to distinguish if this touch was meant to be a swipe or just fat-fingering press @@ -201,7 +204,21 @@ void CTerrainRect::handleSwipeMove(const SDL_MouseMotionEvent & sEvent) } } -#endif // VCMI_ANDROID +bool CTerrainRect::handleSwipeStateChange(bool btnPressed) +{ + if(btnPressed) + { + swipeInitialRealPos = int3(GH.current->motion.x, GH.current->motion.y, 0); + swipeInitialMapPos = int3(adventureInt->position); + return true; + } + else if(isSwiping) // only accept this touch if it wasn't a swipe + { + isSwiping = false; + return true; + } + return false; +} void CTerrainRect::handleHover(const SDL_MouseMotionEvent &sEvent) { @@ -534,11 +551,9 @@ CAdvMapInt::CAdvMapInt(): infoBar(Rect(ADVOPT.infoboxX, ADVOPT.infoboxY, 192, 192)), state(NA), spellBeingCasted(nullptr), position(int3(0, 0, 0)), selection(nullptr), updateScreen(false), anim(0), animValHitCount(0), heroAnim(0), heroAnimValHitCount(0), - activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false) -#ifdef VCMI_ANDROID - , swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false), + activeMapPanel(nullptr), duringAITurn(false), scrollingDir(0), scrollingState(false), + swipeEnabled(settings["general"]["swipe"].Bool()), swipeMovementRequested(false), swipeTargetPosition(int3(-1, -1, -1)) -#endif { adventureInt = this; pos.x = pos.y = 0; @@ -1007,18 +1022,16 @@ void CAdvMapInt::show(SDL_Surface * to) } ++heroAnim; -#ifdef VCMI_ANDROID if(swipeEnabled) { handleSwipeUpdate(); } +#ifdef VCMI_ANDROID // on android, map-moving mode is exclusive (TODO technically it might work with both enabled; to be checked) else +#endif // VCMI_ANDROID { -#endif // !VCMI_ANDROID handleMapScrollingUpdate(); -#ifdef VCMI_ANDROID } -#endif for(int i = 0; i < 4; i++) { @@ -1089,14 +1102,13 @@ void CAdvMapInt::handleMapScrollingUpdate() } } -#ifdef VCMI_ANDROID - void CAdvMapInt::handleSwipeUpdate() { if(swipeMovementRequested) { - position.x = swipeTargetPosition.x; - position.y = swipeTargetPosition.y; + auto fixedPos = LOCPLINT->repairScreenPos(swipeTargetPosition); + position.x = fixedPos.x; + position.y = fixedPos.y; CCS->curh->changeGraphic(ECursor::DEFAULT, 0); updateScreen = true; minimap.redraw(); @@ -1104,8 +1116,6 @@ void CAdvMapInt::handleSwipeUpdate() } } -#endif - void CAdvMapInt::selectionChanged() { const CGTownInstance *to = LOCPLINT->towns[townList.getSelectedIndex()]; diff --git a/client/windows/CAdvmapInterface.h b/client/windows/CAdvmapInterface.h index 8ae36b743..03c1c0830 100644 --- a/client/windows/CAdvmapInterface.h +++ b/client/windows/CAdvmapInterface.h @@ -63,9 +63,9 @@ class CTerrainRect static constexpr float SwipeTouchSlop = 16.0f; void handleHover(const SDL_MouseMotionEvent &sEvent); -#ifdef VCMI_ANDROID void handleSwipeMove(const SDL_MouseMotionEvent &sEvent); -#endif // VCMI_ANDROID + /// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled + bool handleSwipeStateChange(bool btnPressed); public: int tilesw, tilesh; //width and height of terrain to blit in tiles int3 curHoveredTile; @@ -77,6 +77,7 @@ public: void deactivate() override; void clickLeft(tribool down, bool previousState) override; void clickRight(tribool down, bool previousState) override; + void clickMiddle(tribool down, bool previousState) override; void hover(bool on) override; void mouseMoved (const SDL_MouseMotionEvent & sEvent) override; void show(SDL_Surface * to) override; @@ -132,11 +133,9 @@ public: enum{LEFT=1, RIGHT=2, UP=4, DOWN=8}; ui8 scrollingDir; //uses enum: LEFT RIGHT, UP, DOWN bool scrollingState; -#ifdef VCMI_ANDROID bool swipeEnabled; bool swipeMovementRequested; int3 swipeTargetPosition; -#endif // !VCMI_ANDROID enum{NA, INGAME, WAITING} state; @@ -260,9 +259,7 @@ public: void changeMode(EAdvMapMode newMode, float newScale = 0.36f); void handleMapScrollingUpdate(); -#ifdef VCMI_ANDROID void handleSwipeUpdate(); -#endif }; diff --git a/config/schemas/settings.json b/config/schemas/settings.json index 22e0f75dc..995d67eca 100644 --- a/config/schemas/settings.json +++ b/config/schemas/settings.json @@ -41,7 +41,7 @@ }, "swipe" : { "type" : "boolean", - "default" : false + "default" : true }, "saveRandomMaps" : { "type" : "boolean", From 65e88639a35bdb8fa4b7780541ff9f74a0bf9ab0 Mon Sep 17 00:00:00 2001 From: Fay Date: Thu, 8 Jun 2017 21:07:09 +0200 Subject: [PATCH 33/34] Minor: code format; --- client/gui/CGuiHandler.h | 2 +- client/widgets/AdventureMapClasses.cpp | 2 +- client/windows/CAdvmapInterface.h | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/gui/CGuiHandler.h b/client/gui/CGuiHandler.h index ad3fef61b..9a10d24eb 100644 --- a/client/gui/CGuiHandler.h +++ b/client/gui/CGuiHandler.h @@ -64,7 +64,7 @@ private: textInterested; - void handleMouseButtonClick(CIntObjectList &interestedObjs, EIntObjMouseBtnType btn, bool isPressed); + void handleMouseButtonClick(CIntObjectList & interestedObjs, EIntObjMouseBtnType btn, bool isPressed); void processLists(const ui16 activityFlag, std::function *)> cb); public: void handleElementActivate(CIntObject * elem, ui16 activityFlag); diff --git a/client/widgets/AdventureMapClasses.cpp b/client/widgets/AdventureMapClasses.cpp index 2f67737d5..12019ad92 100644 --- a/client/widgets/AdventureMapClasses.cpp +++ b/client/widgets/AdventureMapClasses.cpp @@ -587,7 +587,7 @@ void CMinimap::hover(bool on) void CMinimap::mouseMoved(const SDL_MouseMotionEvent & sEvent) { - if (mouseState(EIntObjMouseBtnType::LEFT)) + if(mouseState(EIntObjMouseBtnType::LEFT)) moveAdvMapSelection(); } diff --git a/client/windows/CAdvmapInterface.h b/client/windows/CAdvmapInterface.h index 03c1c0830..fca0b3da1 100644 --- a/client/windows/CAdvmapInterface.h +++ b/client/windows/CAdvmapInterface.h @@ -62,8 +62,8 @@ class CTerrainRect bool isSwiping; static constexpr float SwipeTouchSlop = 16.0f; - void handleHover(const SDL_MouseMotionEvent &sEvent); - void handleSwipeMove(const SDL_MouseMotionEvent &sEvent); + void handleHover(const SDL_MouseMotionEvent & sEvent); + void handleSwipeMove(const SDL_MouseMotionEvent & sEvent); /// handles start/finish of swipe (press/release of corresponding button); returns true if state change was handled bool handleSwipeStateChange(bool btnPressed); public: From 859c8ede8a74ac506ac239a008ed1e80657bcdec Mon Sep 17 00:00:00 2001 From: Arseniy Shestakov Date: Fri, 9 Jun 2017 15:06:31 +0300 Subject: [PATCH 34/34] Update Travis CI configuration * Remove Clang 3.5 since compilation with 3.4 and 3.6 is enough. * Building for Coverity Scan with 3 threads and without launcher since it's timeout otherwise. * Add Slack notifications to #notifications channel. * Direct email notifications to noreply at vcmi.eu. We can later add redirection to those who want them. --- .travis.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 699170a99..98710872e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -36,9 +36,6 @@ matrix: - os: linux compiler: clang env: VCMI_PLATFORM='linux' REAL_CC=clang-3.6 REAL_CXX=clang++-3.6 PACKAGE=clang-3.6 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0' - - os: linux - compiler: clang - env: VCMI_PLATFORM='linux' REAL_CC=clang-3.5 REAL_CXX=clang++-3.5 PACKAGE=clang-3.5 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0' - os: linux compiler: clang env: VCMI_PLATFORM='linux' REAL_CC=clang-3.4 REAL_CXX=clang++-3.4 PACKAGE=clang-3.4 SUPPORT=libstdc++-4.8-dev VCMI_CMAKE_FLAGS='-DENABLE_TEST=0' @@ -57,14 +54,15 @@ addons: name: vcmi/vcmi description: Build submitted via Travis CI notification_email: coverity@arseniyshestakov.com - build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=1 -DENABLE_TEST=0" - build_command: ninja -j 2 + build_command_prepend: "cov-configure --compiler clang-3.6 --comptype clangcc && cov-configure --comptype clangcxx --compiler clang++-3.6 && cmake -G Ninja .. -DCMAKE_BUILD_TYPE=DEBUG -DENABLE_LAUNCHER=0 -DENABLE_TEST=0" + build_command: ninja -j 3 branch_pattern: coverity_scan notifications: email: recipients: - - vcmi.fail@mixaill.tk - - saven.ivan@gmail.com + - noreply@vcmi.eu on_success: change on_failure: always + slack: + secure: "KHXFe14FFKtw5mErWbj730+utqy7i/3AUobWfAMAGvWI5sJYlhbBU+KvvCoD2SlRQg3mQqgwVw8NBJF1Mffs7WcRmrFFFmuMqZxFLAfKBd3T0CxWpAGfnfNgDmlfV4OfEgQWk1pakEPOymhxbbmLUuCjykZDuTcioxAk0UAHDwY="