diff --git a/src/citymania/cm_console_cmds.cpp b/src/citymania/cm_console_cmds.cpp index 3cae1f14..7f1facaa 100644 --- a/src/citymania/cm_console_cmds.cpp +++ b/src/citymania/cm_console_cmds.cpp @@ -7,9 +7,11 @@ #include "../command_func.h" #include "../console_func.h" #include "../console_type.h" +#include "../date_func.h" #include "../fileio_type.h" #include "../map_type.h" #include "../map_func.h" +#include "../network/network_server.h" #include "../strings_func.h" #include "../town.h" #include "../tree_map.h" @@ -24,11 +26,26 @@ bool ReadHeightMap(DetailedFileType dft, const char *filename, uint *x, uint *y, namespace citymania { +uint32 _replay_save_interval = 0; +uint32 _replay_last_save = 0; +uint32 _replay_ticks = 0; +bool _replay_started = false; + static void IConsoleHelp(const char *str) { IConsolePrintF(CC_WARNING, "- %s", str); } +bool ConGameSpeed(byte argc, char *argv[]) { + if (argc == 0 || argc > 2) { + IConsoleHelp("Changes game speed. Usage: 'cmgamespeed [n]'"); + return true; + } + _game_speed = (argc > 1 ? atoi(argv[1]) : 100); + + return true; +} + bool ConStep(byte argc, char *argv[]) { if (argc == 0 || argc > 2) { IConsoleHelp("Advances the game for a certain amount of ticks (default 1). Usage: 'cmstep [n]'"); @@ -135,6 +152,8 @@ bool ConResetTownGrowth(byte argc, char *argv[]) { struct FakeCommand { Date date; DateFract date_fract; + uint res; + uint32 seed; uint company_id; uint cmd; TileIndex tile; @@ -144,56 +163,138 @@ struct FakeCommand { static std::queue _fake_commands; +void MakeReplaySave() { + char *filename = str_fmt("replay_%d.sav", _replay_ticks); + if (SaveOrLoad(filename, SLO_SAVE, DFT_GAME_FILE, SAVE_DIR) != SL_OK) { + IConsolePrintF(CC_ERROR, "Replay save failed"); + } else { + IConsolePrintF(CC_DEFAULT, "Replay saved to %s", filename); + } + _replay_last_save = _replay_ticks; +} + +bool DatePredate(Date date1, DateFract date_fract1, Date date2, DateFract date_fract2) { + return date1 < date2 || (date1 == date2 && date_fract1 < date_fract2); +} + +void SkipFakeCommands(Date date, DateFract date_fract) { + uint commands_skipped = 0; + + while (!_fake_commands.empty() && DatePredate(_fake_commands.front().date, _fake_commands.front().date_fract, date, date_fract)) { + _fake_commands.pop(); + commands_skipped++; + } + + if (commands_skipped) { + fprintf(stderr, "Skipped %u commands that predate the current date (%d/%hu)\n", commands_skipped, date, date_fract); + } +} + void ExecuteFakeCommands(Date date, DateFract date_fract) { + if (!_replay_started) { + SkipFakeCommands(_date, _date_fract); + _replay_started = true; + } + auto backup_company = _current_company; - while (!_fake_commands.empty() && _fake_commands.front().date <= date && _fake_commands.front().date_fract <= date_fract) { + while (!_fake_commands.empty() && !DatePredate(date, date_fract, _fake_commands.front().date, _fake_commands.front().date_fract)) { auto &x = _fake_commands.front(); - if (x.date < date || x.date_fract < date_fract) IConsolePrintF(CC_WARNING, - "Queued command is earlier than execution date: %d/%hu vs %d/%hu", - x.date, x.date_fract, date, date_fract); - fprintf(stderr, "Executing command: company=%u cmd=%u tile=%u p1=%u p2=%u text=%s ... ", x.company_id, x.cmd, x.tile, x.p1, x.p2, x.text.c_str()); + + fprintf(stderr, "Executing command: company=%u cmd=%u(%s) tile=%u p1=%u p2=%u text=%s ... ", x.company_id, x.cmd, GetCommandName(x.cmd), x.tile, x.p1, x.p2, x.text.c_str()); + if (x.res == 0) { + fprintf(stderr, "REJECTED\n"); + _fake_commands.pop(); + continue; + } + + if (_networking) { + CommandPacket cp; + cp.tile = x.tile; + cp.p1 = x.p1; + cp.p2 = x.p2; + cp.cmd = x.cmd; + strecpy(cp.text, x.text.c_str(), lastof(cp.text)); + cp.company = (CompanyID)x.company_id; + cp.frame = _frame_counter_max + 1; + cp.callback = nullptr; + cp.my_cmd = false; + + for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) { + if (cs->status >= NetworkClientSocket::STATUS_MAP) { + cs->outgoing_queue.Append(&cp); + } + } + } _current_company = (CompanyID)x.company_id; auto res = DoCommandPInternal(x.tile, x.p1, x.p2, x.cmd | CMD_NETWORK_COMMAND, nullptr, x.text.c_str(), false, false); - if (res.Failed()) { - if (res.GetErrorMessage() != INVALID_STRING_ID) { + if (res.Failed() != (x.res != 1)) { + if (!res.Failed()) { + fprintf(stderr, "FAIL (Failing command succeeded)\n"); + } else if (res.GetErrorMessage() != INVALID_STRING_ID) { char buf[DRAW_STRING_BUFFER]; GetString(buf, res.GetErrorMessage(), lastof(buf)); - fprintf(stderr, "%s\n", buf); + fprintf(stderr, "FAIL (Successful command failed: %s)\n", buf); } else { - fprintf(stderr, "FAIL\n"); + fprintf(stderr, "FAIL (Successful command failed)\n"); } } else { fprintf(stderr, "OK\n"); } + if (x.seed != (_random.state[0] & 255)) { + fprintf(stderr, "*** DESYNC expected seed %u vs current %u ***\n", x.seed, _random.state[0] & 255); + } _fake_commands.pop(); } _current_company = backup_company; } -bool ConLoadCommands(byte argc, char *argv[]) { - if (argc == 0) { - IConsoleHelp("Loads a file with command queue to execute"); - IConsoleHelp("Usage: 'cmloadcommands '"); - return true; +void CheckIntervalSave() { + if (_pause_mode == PM_UNPAUSED) { + _replay_ticks++; + if (_replay_save_interval && _replay_ticks - _replay_last_save >= _replay_save_interval) { + MakeReplaySave(); + } } +} - if (argc != 2) return false; +void SetReplaySaveInterval(uint32 interval) { + _replay_save_interval = interval; + _replay_last_save = 0; + _replay_ticks = 0; + + if (_replay_save_interval) MakeReplaySave(); +} +void LoadCommands(const std::string &filename) { std::queue().swap(_fake_commands); // clear queue - std::ifstream file(argv[1], std::ios::in); + std::ifstream file(filename, std::ios::in); std::string str; - while(std::getline(file, str)) - { + while(std::getline(file, str)) { std::istringstream ss(str); FakeCommand cmd; - ss >> cmd.date >> cmd.date_fract >> cmd.company_id >> cmd.cmd >> cmd.p1 >> cmd.p2 >> cmd.tile; + ss >> cmd.date >> cmd.date_fract >> cmd.res >> cmd.seed >> cmd.company_id >> cmd.cmd >> cmd.tile >> cmd.p1 >> cmd.p2; std::string s; ss.get(); std::getline(ss, cmd.text); - // fprintf(stderr, "PARSED: company=%u cmd=%u tile=%u p1=%u p2=%u text=%s\n", cmd.company_id, cmd.cmd, cmd.tile, cmd.p1, cmd.p2, cmd.text.c_str()); _fake_commands.push(cmd); } + + _replay_started = false; +} + +bool ConLoadCommands(byte argc, char *argv[]) { + if (argc == 0) { + IConsoleHelp("Loads a file with command queue to execute"); + IConsoleHelp("Usage: 'cmloadcommands '"); + return true; + } + + if (argc > 3) return false; + + LoadCommands(argv[1]); + SetReplaySaveInterval(argc > 2 ? atoi(argv[2]) : 0); + return true; } diff --git a/src/citymania/cm_console_cmds.hpp b/src/citymania/cm_console_cmds.hpp index f14df235..5d80ab12 100644 --- a/src/citymania/cm_console_cmds.hpp +++ b/src/citymania/cm_console_cmds.hpp @@ -5,6 +5,12 @@ namespace citymania { +void SkipFakeCommands(Date date, DateFract date_fract); +void SetReplaySaveInterval(uint32 interval); +void LoadCommands(const std::string &filename); +void CheckIntervalSave(); + +bool ConGameSpeed(byte argc, char *argv[]); bool ConStep(byte argc, char *argv[]); bool ConExport(byte argc, char *argv[]); bool ConTreeMap(byte argc, char *argv[]); diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp index 240c02ca..3351aef7 100644 --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -2459,8 +2459,10 @@ void IConsoleStdLibRegister() IConsoleCmdRegister("dump_info", ConDumpInfo); IConsoleCmdRegister("cmexport", citymania::ConExport); - IConsoleCmdRegister("cmstep", citymania::ConStep, ConHookNoNetwork); IConsoleCmdRegister("cmtreemap", citymania::ConTreeMap, ConHookNoNetwork); - IConsoleCmdRegister("cmresettowngrowth", citymania::ConResetTownGrowth, ConHookNoNetwork); - IConsoleCmdRegister("cmloadcommands", citymania::ConLoadCommands, ConHookNoNetwork); + + IConsoleCmdRegister("cmstep", citymania::ConStep); + IConsoleCmdRegister("cmresettowngrowth", citymania::ConResetTownGrowth); + IConsoleCmdRegister("cmloadcommands", citymania::ConLoadCommands); + IConsoleCmdRegister("cmgamespeed", citymania::ConGameSpeed); } diff --git a/src/gfx.cpp b/src/gfx.cpp index 7f035723..c3022ac3 100644 --- a/src/gfx.cpp +++ b/src/gfx.cpp @@ -46,7 +46,7 @@ bool _exit_game; GameMode _game_mode; SwitchMode _switch_mode; ///< The next mainloop command. PauseMode _pause_mode; -uint32 _pause_countdown; +uint32 _pause_countdown = 0; Palette _cur_palette; static byte _stringwidth_table[FS_END][224]; ///< Cache containing width of often used characters. @see GetCharacterWidth() diff --git a/src/network/network.cpp b/src/network/network.cpp index 8fd0f01d..95689de0 100644 --- a/src/network/network.cpp +++ b/src/network/network.cpp @@ -34,6 +34,8 @@ #include "../gfx_func.h" #include "../error.h" +#include "citymania/cm_console_cmds.hpp" + #include "../safeguards.h" #ifdef DEBUG_DUMP_COMMANDS @@ -1000,6 +1002,8 @@ void NetworkGameLoop() NetworkExecuteLocalCommandQueue(); if (_pause_countdown > 0 && --_pause_countdown == 0) DoCommandP(0, PM_PAUSED_NORMAL, 1, CMD_PAUSE); + citymania::ExecuteFakeCommands(_date, _date_fract); + /* Then we make the frame */ StateGameLoop(); diff --git a/src/openttd.cpp b/src/openttd.cpp index c72187b1..8d98820b 100644 --- a/src/openttd.cpp +++ b/src/openttd.cpp @@ -203,6 +203,8 @@ static void ShowHelp() " -c config_file = Use 'config_file' instead of 'openttd.cfg'\n" " -x = Never save configuration changes to disk\n" " -q savegame = Write some information about the savegame and exit\n" + " -C commands = Load commands to execute (CityMania addition)\n" + " -T ticks = Save game every n ticks with filename replay_.sav (CityMania addition)\n" "\n", lastof(buf) ); @@ -542,6 +544,8 @@ static const OptionData _options[] = { GETOPT_SHORT_NOVAL('x'), GETOPT_SHORT_VALUE('q'), GETOPT_SHORT_NOVAL('h'), + GETOPT_SHORT_VALUE('C'), + GETOPT_SHORT_VALUE('T'), GETOPT_END() }; @@ -677,6 +681,19 @@ int openttd_main(int argc, char *argv[]) case 'G': scanner->generation_seed = strtoul(mgo.opt, nullptr, 10); break; case 'c': _config_file = mgo.opt; break; case 'x': scanner->save_config = false; break; + case 'C': { + DeterminePaths(argv[0]); + if (StrEmpty(mgo.opt)) { + ret = 1; + return ret; + } + citymania::LoadCommands(mgo.opt); + break; + } + case 'T': { + citymania::SetReplaySaveInterval(strtol(mgo.opt, nullptr, 10)); + break; + } case 'h': i = -2; // Force printing of help. break; @@ -1530,6 +1547,7 @@ void GameLoop() citymania::ExecuteFakeCommands(_date, _date_fract); /* Singleplayer */ StateGameLoop(); + citymania::CheckIntervalSave(); } if (!_pause_mode && HasBit(_display_opt, DO_FULL_ANIMATION)) DoPaletteAnimations(); diff --git a/src/video/null_v.cpp b/src/video/null_v.cpp index 4f7843b7..c9e36572 100644 --- a/src/video/null_v.cpp +++ b/src/video/null_v.cpp @@ -8,8 +8,11 @@ /** @file null_v.cpp The video driver that doesn't blit. */ #include "../stdafx.h" +#include "../console_func.h" +#include "../date_func.h" #include "../gfx_func.h" #include "../blitter/factory.hpp" +#include "../saveload/saveload.h" #include "../window_func.h" #include "null_v.h" @@ -28,6 +31,7 @@ const char *VideoDriver_Null::Start(const StringList &parm) this->UpdateAutoResolution(); this->ticks = GetDriverParamInt(parm, "ticks", 1000); + this->savefile = GetDriverParam(parm, "save"); _screen.width = _screen.pitch = _cur_resolution.width; _screen.height = _cur_resolution.height; _screen.dst_ptr = nullptr; @@ -47,10 +51,22 @@ void VideoDriver_Null::MainLoop() { uint i; - for (i = 0; i < this->ticks; i++) { + uint16 old_tick; + for (i = 0; i < this->ticks; ) { + old_tick = _tick_counter; ::GameLoop(); ::InputLoop(); ::UpdateWindows(); + if (old_tick != _tick_counter) i++; + else _pause_mode = PM_UNPAUSED; + } + fprintf(stderr, "Null driver ran for %u tics, save: %s\n", this->ticks, this->savefile.c_str()); + if (!this->savefile.empty()) { + if (SaveOrLoad(this->savefile.c_str(), SLO_SAVE, DFT_GAME_FILE, SAVE_DIR) != SL_OK) { + IConsolePrintF(CC_ERROR, "Error saving the final game state."); + } else { + IConsolePrintF(CC_DEFAULT, "Saved the final game state to %s", this->savefile.c_str()); + } } } diff --git a/src/video/null_v.h b/src/video/null_v.h index ee83de7e..eabbf64e 100644 --- a/src/video/null_v.h +++ b/src/video/null_v.h @@ -16,6 +16,7 @@ class VideoDriver_Null : public VideoDriver { private: uint ticks; ///< Amount of ticks to run. + std::string savefile; public: const char *Start(const StringList ¶m) override;