From 5165fe8029ce52a0e90747ddc9855a3627209111 Mon Sep 17 00:00:00 2001 From: stgatilov Date: Mon, 2 Aug 2021 17:37:55 +0700 Subject: [PATCH] Restart game works as "multistep procedure". --- plugins/dm.gameconnection/GameConnection.cpp | 234 ++++++++++++------- plugins/dm.gameconnection/GameConnection.h | 8 + 2 files changed, 153 insertions(+), 89 deletions(-) diff --git a/plugins/dm.gameconnection/GameConnection.cpp b/plugins/dm.gameconnection/GameConnection.cpp index da293c6fe2..9fa9b9f8d3 100644 --- a/plugins/dm.gameconnection/GameConnection.cpp +++ b/plugins/dm.gameconnection/GameConnection.cpp @@ -68,6 +68,7 @@ void GameConnection::sendRequest(const std::string &request) { std::string fullMessage = seqnoPreamble(seqno) + request; _connection->writeMessage(fullMessage.data(), fullMessage.size()); _seqnoInProgress = seqno; + signal_StatusChanged.emit(0); } bool GameConnection::sendAnyPendingAsync() { @@ -95,6 +96,12 @@ void GameConnection::think() { //mark request as "no longer in progress" //note: response can be used in outer function _seqnoInProgress = 0; + + if (responseSeqno == _multistepWaitsForSeqno) { + _multistepWaitsForSeqno = 0; + continueMultistepProcedure(); + } + signal_StatusChanged.emit(0); } } else { @@ -147,6 +154,18 @@ std::string GameConnection::executeRequest(const std::string &request) { return std::string(_response.begin(), _response.end()); } +void GameConnection::continueMultistepProcedure() { + if (!_multistepProcedureFunction) + return; + while (!_multistepWaitsForSeqno) { + _multistepProcedureStep = _multistepProcedureFunction(_multistepProcedureStep); + if (_multistepProcedureStep < 0) { + _multistepProcedureFunction = std::function(); + break; + } + } +} + bool GameConnection::isAlive() const { return _connection && _connection->isAlive(); } @@ -222,111 +241,148 @@ GameConnection::~GameConnection() { void GameConnection::restartGame(bool dmap) { - //BIG TODO!! - static const char *TODO_TDM_DIR = R"(G:\TheDarkMod\darkmod)"; - static const char *TODO_MISSION = "bakery_job"; - static const char *TODO_MAP = "bakery.map"; - - std::string savedViewPos; - if (isAlive()) { - //save current position - savedViewPos = executeRequest(composeConExecRequest("getviewpos")); - } + auto implementation = [this, dmap](int step) -> int { + //BIG TODO!! + static const char *TODO_TDM_DIR = R"(G:\TheDarkMod\darkmod)"; + static const char *TODO_MISSION = "bakery_job"; + static const char *TODO_MAP = "bakery.map"; - //save .map file - saveMapIfNeeded(); + static bool changingFm; + static std::string savedViewPos; + + if (step == 0) { + + if (isAlive()) { + //save current position + savedViewPos = executeRequest(composeConExecRequest("getviewpos")); + } - //try to attach to TDM with automation enabled - bool attached = connect(); + //save .map file + saveMapIfNeeded(); - if (!attached) { - //run new TDM process + //try to attach to TDM with automation enabled + bool attached = connect(); + + if (!attached) { + //run new TDM process #ifdef _WIN32 - static const char *TDM_NAME = "TheDarkModx64.exe"; + static const char *TDM_NAME = "TheDarkModx64.exe"; #else - static const char *TDM_NAME = "thedarkmod.x64"; + static const char *TDM_NAME = "thedarkmod.x64"; #endif - wxExecuteEnv env; - env.cwd = TODO_TDM_DIR; - wxString cmdline = wxString::Format("%s/%s +set com_automation 1", TODO_TDM_DIR, TDM_NAME); - long res = wxExecute(cmdline, wxEXEC_ASYNC, nullptr, &env); - if (res <= 0) { - showError("Failed to run TheDarkMod executable."); - return; + wxExecuteEnv env; + env.cwd = TODO_TDM_DIR; + wxString cmdline = wxString::Format("%s/%s +set com_automation 1", TODO_TDM_DIR, TDM_NAME); + long res = wxExecute(cmdline, wxEXEC_ASYNC, nullptr, &env); + if (res <= 0) { + showError("Failed to run TheDarkMod executable."); + return -1; + } + + //attach to the new process + static const int TDM_LAUNCH_TIMEOUT = 5000; //in milliseconds + wxLongLong timestampStart = wxGetUTCTimeMillis(); + do { + wxMilliSleep(500); + if (wxGetUTCTimeMillis() - timestampStart > TDM_LAUNCH_TIMEOUT) { + showError("Timeout when connecting to just started TheDarkMod process.\nMake sure the game is in main menu, has com_automation enabled, and firewall does not block it."); + return -1; + } + } while (!connect()); + } + + //check the current status + std::map statusProps = executeQueryStatus(); + changingFm = false; + if (statusProps["currentfm"] != TODO_MISSION) { + //change mission/mod and restart TDM engine + std::string request = actionPreamble("installfm") + "content:\n" + TODO_MISSION + "\n"; + sendRequest(request); + _multistepWaitsForSeqno = _seqnoInProgress; + changingFm = true; + } + + return 1; } - //attach to the new process - static const int TDM_LAUNCH_TIMEOUT = 5000; //in milliseconds - wxLongLong timestampStart = wxGetUTCTimeMillis(); - do { - wxMilliSleep(500); - if (wxGetUTCTimeMillis() - timestampStart > TDM_LAUNCH_TIMEOUT) { - showError("Timeout when connecting to just started TheDarkMod process.\nMake sure the game is in main menu, has com_automation enabled, and firewall does not block it."); - return; + if (step == 1) { + std::string response(_response.begin(), _response.end()); + if (changingFm && response != "done") { + showError("Failed to change installed mission in TheDarkMod.\nMake sure ?DR mission? is configured properly and game version is 2.09 or above."); + return -1; + } + std::map statusProps = executeQueryStatus(); + if (statusProps["currentfm"] != TODO_MISSION) { + showError(fmt::format("Installed mission is {} despite trying to change it.", statusProps["currentfm"])); + return -1; + } + + if (dmap) { + //run dmap command + std::string request = composeConExecRequest("dmap " + std::string(TODO_MAP)); + sendRequest(request); + _multistepWaitsForSeqno = _seqnoInProgress; } - } while (!connect()); - } - //check the current status - std::map statusProps = executeQueryStatus(); - if (statusProps["currentfm"] != TODO_MISSION) { - //change mission/mod and restart TDM engine - std::string request = actionPreamble("installfm") + "content:\n" + TODO_MISSION + "\n"; - std::string response = executeRequest(request); - if (response != "done") { - showError("Failed to change installed mission in TheDarkMod.\nMake sure ?DR mission? is configured properly and game version is 2.09 or above."); - return; + return 2; } - } - statusProps = executeQueryStatus(); - if (statusProps["currentfm"] != TODO_MISSION) { - showError(fmt::format("Installed mission is {} despite trying to change it.", statusProps["currentfm"])); - return; - } - if (dmap) { - //run dmap command - std::string request = composeConExecRequest("dmap " + std::string(TODO_MAP)); - std::string response = executeRequest(request); - if (response.find("ERROR:") != std::string::npos) { - showError("Dmap printed error.\nPlease look at TheDarkMod console."); - return; + if (step == 2) { + if (dmap) { + std::string response(_response.begin(), _response.end()); + if (response.find("ERROR:") != std::string::npos) { + showError("Dmap printed error.\nPlease look at TheDarkMod console."); + return -1; + } + } + + //start map + std::string request = composeConExecRequest("map " + std::string(TODO_MAP)); + sendRequest(request); + _multistepWaitsForSeqno = _seqnoInProgress; + + return 3; } - } - { - //start map - std::string request = composeConExecRequest("map " + std::string(TODO_MAP)); - std::string response = executeRequest(request); - } - statusProps = executeQueryStatus(); - if (statusProps["currentfm"] != TODO_MISSION) { - showError(fmt::format("Installed mission is still {}.", statusProps["currentfm"])); - return; - } - if (statusProps["mapname"] != TODO_MAP) { - showError(fmt::format("Active map is {} despite trying to start the map.", statusProps["mapname"])); - return; - } - if (statusProps["guiactive"] != "") { - showError(fmt::format("GUI {} is active while we expect the game to start", statusProps["guiactive"])); - return; - } + if (step == 3) { + std::string response(_response.begin(), _response.end()); - //confirm player is ready - std::string waitUntilReady = executeGetCvarValue("tdm_player_wait_until_ready"); - if (waitUntilReady != "0") { - //button0 is "attack" button - //numbers in parens mean: hold for 100 gameplay milliseconds (time is stopped at waiting screen) - std::string request = actionPreamble("gamectrl") + "content:\n" + "timemode \"game\"\n" + "button0 (1 1 0 0 0 0.1)\n"; - std::string response = executeRequest(request); - } + std::map statusProps = executeQueryStatus(); + if (statusProps["currentfm"] != TODO_MISSION) { + showError(fmt::format("Installed mission is still {}.", statusProps["currentfm"])); + return -1; + } + if (statusProps["mapname"] != TODO_MAP) { + showError(fmt::format("Active map is {} despite trying to start the map.", statusProps["mapname"])); + return -1; + } + if (statusProps["guiactive"] != "") { + showError(fmt::format("GUI {} is active while we expect the game to start", statusProps["guiactive"])); + return -1; + } - if (!savedViewPos.empty()) { - //restore camera position - std::string request = composeConExecRequest(fmt::format("setviewpos {}", savedViewPos)); - std::string response = executeRequest(request); - } + //confirm player is ready + std::string waitUntilReady = executeGetCvarValue("tdm_player_wait_until_ready"); + if (waitUntilReady != "0") { + //button0 is "attack" button + //numbers in parens mean: hold for 100 gameplay milliseconds (time is stopped at waiting screen) + std::string request = actionPreamble("gamectrl") + "content:\n" + "timemode \"game\"\n" + "button0 (1 1 0 0 0 0.1)\n"; + std::string response = executeRequest(request); + } + + if (!savedViewPos.empty()) { + //restore camera position + std::string request = composeConExecRequest(fmt::format("setviewpos {}", savedViewPos)); + std::string response = executeRequest(request); + } + + return -1; + } + }; + + _multistepProcedureFunction = implementation; + _multistepProcedureStep = 0; + continueMultistepProcedure(); } //------------------------------------------------------------- diff --git a/plugins/dm.gameconnection/GameConnection.h b/plugins/dm.gameconnection/GameConnection.h index 093c7eedae..0e174a1e1c 100644 --- a/plugins/dm.gameconnection/GameConnection.h +++ b/plugins/dm.gameconnection/GameConnection.h @@ -131,6 +131,12 @@ class GameConnection : std::size_t _seqnoInProgress = 0; //response from current in-progress request will be saved here std::vector _response; + //function for the current multistep procedure + std::function _multistepProcedureFunction; + //current step index for multistep procedure + int _multistepProcedureStep = -1; + //multistep procedure waits for this request + int _multistepWaitsForSeqno = 0; //true <=> cameraOutData holds new camera position, which should be sent to TDM bool _cameraOutPending = false; @@ -161,6 +167,8 @@ class GameConnection : //send given request synchronously, i.e. wait until its completition (blocking) //returns response content std::string executeRequest(const std::string &request); + //execute next step of the current multistep procedure + void continueMultistepProcedure(); //given a command to be executed in game console (no EOLs), returns its full request text (except for seqno) static std::string composeConExecRequest(std::string consoleLine);