Skip to content

Commit

Permalink
Restart game works as "multistep procedure".
Browse files Browse the repository at this point in the history
  • Loading branch information
stgatilov committed Aug 2, 2021
1 parent a584ecd commit 5165fe8
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 89 deletions.
234 changes: 145 additions & 89 deletions plugins/dm.gameconnection/GameConnection.cpp
Expand Up @@ -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() {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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<int(int)>();
break;
}
}
}

bool GameConnection::isAlive() const {
return _connection && _connection->isAlive();
}
Expand Down Expand Up @@ -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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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<std::string, std::string> 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();
}

//-------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions plugins/dm.gameconnection/GameConnection.h
Expand Up @@ -131,6 +131,12 @@ class GameConnection :
std::size_t _seqnoInProgress = 0;
//response from current in-progress request will be saved here
std::vector<char> _response;
//function for the current multistep procedure
std::function<int(int)> _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;
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 5165fe8

Please sign in to comment.