From 5ee8f91f5d7aa39270817bbeb066f74f060b815c Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 22 Feb 2025 10:20:30 -0600 Subject: [PATCH 1/9] fix replay show_debug_help --- src/game/boe.actions.cpp | 4 +++- src/game/boe.main.cpp | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 6ed969e0f..43e74605d 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -2364,7 +2364,9 @@ void show_debug_help() { if(action.action != &show_debug_help){ button.attachClickHandler([action](cDialog& me, std::string, eKeyMod) -> bool { me.toast(false); - action.action(); + // In a replay, the action will have been recorded next anyway, so the dialog doesn't need to trigger it. + if(!replaying) + action.action(); return true; }); } diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index 66b685287..f397ad9a3 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -910,8 +910,9 @@ static void replay_action(Element& action) { cancel_item_target(did_something, need_redraw, need_reprint); }else if(t == "easter_egg"){ easter_egg(boost::lexical_cast(action.GetText())); - }else if(t == "show_debug_panel"){ + }else if(t == "show_debug_help"){ show_debug_help(); + return; }else if(t == "debug_fight_encounter"){ debug_fight_encounter(str_to_bool(action.GetText())); }else if(t == "preview_every_dialog_xml"){ From 64702b3a88b0575db001802d83f1ae58eb5bc214 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 22 Feb 2025 10:04:51 -0600 Subject: [PATCH 2/9] support for recording replay in memory --- src/game/boe.main.cpp | 6 ++++++ src/tools/replay.cpp | 23 ++++++++++++++++++----- src/tools/replay.hpp | 1 + 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index f397ad9a3..1f9c8b874 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -304,6 +304,7 @@ extern bool record_verbose; extern bool replay_verbose; extern bool replay_strict; +bool record_in_memory = true; static void process_args(int argc, char* argv[]) { preprocess_args(argc, argv); @@ -374,6 +375,11 @@ static void process_args(int argc, char* argv[]) { exit(1); } // Don't return, because we want to support recording a run that starts with a party from the CLI. + }else if(record_in_memory){ + if(!init_action_log("record", "")){ + std::cerr << "Failed to start recording in memory." << std::endl; + exit(1); + } } if(saved_game){ diff --git a/src/tools/replay.cpp b/src/tools/replay.cpp index 528c90f52..d912ed790 100644 --- a/src/tools/replay.cpp +++ b/src/tools/replay.cpp @@ -44,6 +44,16 @@ std::string log_file; Element* next_action; boost::optional replay_fps_limit; +static void save_log() { + if(!log_file.empty()) log_document.SaveFile(log_file); +} + +void start_log_file(std::string file) { + log_file = file; + std::cout << "Recording this session: " << log_file << std::endl; + save_log(); +} + bool init_action_log(std::string command, std::string file) { if(command == "record-unique") { // If a filename is given, use it as a base, but insert a timestamp for uniqueness. @@ -72,9 +82,12 @@ bool init_action_log(std::string command, std::string file) { root_element.SetAttribute("Repo", GIT_REPO); #endif log_document.InsertEndChild(root_element); - log_document.SaveFile(log_file); recording = true; - std::cout << "Recording this session: " << log_file << std::endl; + if(log_file.empty()){ + std::cout << "Recording this session in memory." << std::endl; + }else{ + start_log_file(log_file); + } } catch(...) { std::cout << "Failed to write to file " << log_file << std::endl; } @@ -125,7 +138,7 @@ void record_action(std::string action_type, std::string inner_text, bool cdata) action_text.SetCDATA(cdata); next_action.InsertEndChild(action_text); root->InsertEndChild(next_action); - log_document.SaveFile(log_file); + save_log(); } void record_action(std::string action_type, std::map info) { @@ -138,13 +151,13 @@ void record_action(std::string action_type, std::map in next_action.InsertEndChild(next_child); } root->InsertEndChild(next_action); - log_document.SaveFile(log_file); + save_log(); } void record_action(Element& action) { Element* root = log_document.FirstChildElement(); root->InsertEndChild(action); - log_document.SaveFile(log_file); + save_log(); } void record_field_input(cKey key) { diff --git a/src/tools/replay.hpp b/src/tools/replay.hpp index e75ec363f..5ac6073b2 100644 --- a/src/tools/replay.hpp +++ b/src/tools/replay.hpp @@ -47,6 +47,7 @@ extern short short_from_action(Element& action); extern cKey key_from_action(Element& action); extern word_rect_t word_rect_from_action(Element& action); extern void record_click_talk_rect(word_rect_t word_rect, bool preset); +extern void start_log_file(std::string file); extern const std::string replay_warning; extern const std::string replay_error; From 60b178d6e892aaab26d8ed1fb71de6b10bb8d2e5 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 22 Feb 2025 10:20:53 -0600 Subject: [PATCH 3/9] Debug action to save out (and keep saving) the log in memory --- src/game/boe.actions.cpp | 10 ++++++++++ src/game/boe.actions.hpp | 1 + 2 files changed, 11 insertions(+) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 43e74605d..6eb76ee14 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -2427,6 +2427,7 @@ void init_debug_actions() { add_debug_action({'%'}, "Fight wandering encounter from this section", []() -> void {debug_fight_encounter(true);}); add_debug_action({'^'}, "Fight special encounter from this section", []() -> void {debug_fight_encounter(false);}); add_debug_action({'/', '?'}, "Bring up this window", show_debug_help); + add_debug_action({'Z'}, "Save the current action log for bug reporting", save_replay_log); } // Later we might want to know whether the key is used or not @@ -3969,4 +3970,13 @@ void preview_every_dialog_xml() { preview_dialog_xml(path); }); } +} + +void save_replay_log(){ + // This doesn't need to be recorded or replayed. + if(replaying) return; + + fs::path out_file = nav_put_rsrc({"xml"}); + + start_log_file(out_file.string()); } \ No newline at end of file diff --git a/src/game/boe.actions.hpp b/src/game/boe.actions.hpp index e9c236f89..d5aaf3f1e 100644 --- a/src/game/boe.actions.hpp +++ b/src/game/boe.actions.hpp @@ -117,5 +117,6 @@ void update_item_stats_area(bool& need_reprint); void easter_egg(int idx); void preview_dialog_xml(); void preview_every_dialog_xml(); +void save_replay_log(); #endif From 8ba173e5ebf1ab3a560671425530217d417e4814 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 22 Feb 2025 10:43:49 -0600 Subject: [PATCH 4/9] Record replays into AppData --- src/fileio/fileio.cpp | 7 ++++++- src/tools/replay.cpp | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/fileio/fileio.cpp b/src/fileio/fileio.cpp index 69a2c149e..7558cc5a8 100644 --- a/src/fileio/fileio.cpp +++ b/src/fileio/fileio.cpp @@ -33,7 +33,7 @@ bool mac_is_intel(){ } return _mac_is_intel; } -fs::path progDir, tempDir, scenDir; +fs::path progDir, tempDir, scenDir, replayDir; // This is here to avoid unnecessarily duplicating it in platform-specific files. cursor_type Cursor::current = sword_curs; @@ -77,6 +77,10 @@ void init_directories(const char* exec_path) { #endif scenDir = tempDir/"Scenarios"; fs::create_directories(scenDir); + + replayDir = tempDir/"Replays"; + fs::create_directories(replayDir); + add_resmgr_paths(tempDir/"data"); tempDir /= "Temporary Files"; @@ -100,6 +104,7 @@ void init_directories(const char* exec_path) { std::cout << "Program directory: " << progDir << std::endl; std::cout << "Scenario directory: " << scenDir << std::endl; std::cout << "Temporary directory: " << tempDir << std::endl; + std::cout << "Replay directory: " << replayDir << std::endl; } #if !defined(_WIN32) && !defined(_WIN64) && !defined(__APPLE__) diff --git a/src/tools/replay.cpp b/src/tools/replay.cpp index d912ed790..28508fa20 100644 --- a/src/tools/replay.cpp +++ b/src/tools/replay.cpp @@ -40,12 +40,12 @@ const std::string replay_error = "Replay system internal error! "; using namespace ticpp; Document log_document; -std::string log_file; +fs::path log_file; Element* next_action; boost::optional replay_fps_limit; static void save_log() { - if(!log_file.empty()) log_document.SaveFile(log_file); + if(!log_file.empty()) log_document.SaveFile(log_file.string()); } void start_log_file(std::string file) { @@ -54,6 +54,8 @@ void start_log_file(std::string file) { save_log(); } +extern fs::path replayDir; + bool init_action_log(std::string command, std::string file) { if(command == "record-unique") { // If a filename is given, use it as a base, but insert a timestamp for uniqueness. @@ -73,6 +75,9 @@ bool init_action_log(std::string command, std::string file) { } if(command == "record") { log_file = file; + if(!log_file.empty() && log_file == log_file.filename()){ + log_file = replayDir / log_file; + } try { Element root_element("actions"); #ifndef MSBUILD_GITREV @@ -86,7 +91,7 @@ bool init_action_log(std::string command, std::string file) { if(log_file.empty()){ std::cout << "Recording this session in memory." << std::endl; }else{ - start_log_file(log_file); + start_log_file(log_file.string()); } } catch(...) { std::cout << "Failed to write to file " << log_file << std::endl; @@ -95,7 +100,11 @@ bool init_action_log(std::string command, std::string file) { } else if (command == "replay") { try { - log_document.LoadFile(file); + fs::path file_path = file; + if(file_path == file_path.filename() && !fs::exists(file_path)){ + file_path = replayDir / file_path; + } + log_document.LoadFile(file_path.string()); Element* root = log_document.FirstChildElement(); next_action = root->FirstChildElement(); From 651348cf7e82ec52eaf1e565e67d61b3ce6b5186 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 22 Feb 2025 20:12:02 -0600 Subject: [PATCH 5/9] record debug end scenario --- src/game/boe.actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 6eb76ee14..dbdc22c98 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -2403,7 +2403,7 @@ void init_debug_actions() { add_debug_action({'J'}, "Preview a dialog's layout", preview_dialog_xml); add_debug_action({'U'}, "Preview EVERY dialog's layout", preview_every_dialog_xml); add_debug_action({'K'}, "Kill everything", debug_kill); - add_debug_action({'N'}, "End scenario", []() -> void {handle_victory(true);}); + add_debug_action({'N'}, "End scenario", []() -> void {handle_victory(true, true);}); add_debug_action({'O'}, "Print your location", debug_print_location); add_debug_action({'Q'}, "Magic map", debug_magic_map); add_debug_action({'R'}, "Return to start", debug_return_to_start); From 13532fe09a71efc76237c80ab3e6dd0342adfa00 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 23 Feb 2025 09:27:07 -0600 Subject: [PATCH 6/9] update unused key list --- src/game/boe.actions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index dbdc22c98..98e08137b 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -2387,7 +2387,7 @@ void show_debug_help() { } // Non-comprehensive list of unused keys: -// UYZ chijklnoqvy @#$-_+[]{},.'"`~/\|;: +// Y chijklnoqvy @#$-_+[]{},.'"`~/\|;: void init_debug_actions() { add_debug_action({'B'}, "Leave town", debug_leave_town); add_debug_action({'C'}, "Get cleaned up (lose negative status effects)", debug_clean_up); From f601678f332040a925bccb265c14115e9cd1cc5e Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 23 Feb 2025 14:09:52 -0600 Subject: [PATCH 7/9] replay handle_victory must pass force --- src/game/boe.main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index 1f9c8b874..c6de16912 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -824,7 +824,7 @@ static void replay_action(Element& action) { debug_return_to_start(); return; }else if(t == "handle_victory"){ - handle_victory(); + handle_victory(true); // This is for the debug action which forces it. return; }else if(t == "debug_increase_age"){ debug_increase_age(); From 32e644b9f52538e455282f0d7c63833222f05620 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 23 Feb 2025 14:35:38 -0600 Subject: [PATCH 8/9] some actions shouldn't be recorded in every context --- src/game/boe.infodlg.cpp | 8 ++++---- src/game/boe.infodlg.hpp | 4 ++-- src/game/boe.main.cpp | 6 +++--- src/pcedit/pc.editors.cpp | 2 +- src/pcedit/pc.main.cpp | 2 +- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/game/boe.infodlg.cpp b/src/game/boe.infodlg.cpp index 8fae0039d..0ac75e0a7 100644 --- a/src/game/boe.infodlg.cpp +++ b/src/game/boe.infodlg.cpp @@ -112,8 +112,8 @@ static bool display_spells_event_filter(cDialog& me, std::string item_hit, eSkil return true; } //short force_spell; // if 100, ignore -void display_spells(eSkill mode,short force_spell,cDialog* parent) { - if(recording){ +void display_spells(eSkill mode,short force_spell,cDialog* parent, bool record) { + if(recording && record){ std::map info; info["mode"] = boost::lexical_cast(mode); info["force_spell"] = boost::lexical_cast(force_spell); @@ -174,8 +174,8 @@ static bool display_skills_event_filter(cDialog& me, std::string item_hit, eKeyM return true; } -void display_skills(eSkill force_skill,cDialog* parent) { - if(recording){ +void display_skills(eSkill force_skill,cDialog* parent, bool record) { + if(recording && record){ record_action("display_skills", boost::lexical_cast(force_skill)); } if(force_skill != eSkill::INVALID) diff --git a/src/game/boe.infodlg.hpp b/src/game/boe.infodlg.hpp index cc7a11098..67d8b8e35 100644 --- a/src/game/boe.infodlg.hpp +++ b/src/game/boe.infodlg.hpp @@ -8,8 +8,8 @@ #include "universe/creature.hpp" class cDialog; -void display_spells(eSkill mode,short force_spell,cDialog* parent); -void display_skills(eSkill force_skill,cDialog* parent); +void display_spells(eSkill mode,short force_spell,cDialog* parent, bool record=false); +void display_skills(eSkill force_skill,cDialog* parent, bool record=false); void display_pc_item(short pc_num,short item,class cItem si,cDialog* parent); void display_monst(short array_pos,cCreature *which_m,short mode); void display_alchemy(); diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index c6de16912..420ab418f 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -1494,13 +1494,13 @@ void handle_menu_choice(eMenu item_hit) { dialogToShow = "about-boe"; break; case eMenu::LIBRARY_MAGE: - display_spells(eSkill::MAGE_SPELLS,100,nullptr); + display_spells(eSkill::MAGE_SPELLS,100,nullptr,true); break; case eMenu::LIBRARY_PRIEST: - display_spells(eSkill::PRIEST_SPELLS,100,nullptr); + display_spells(eSkill::PRIEST_SPELLS,100,nullptr,true); break; case eMenu::LIBRARY_SKILLS: - display_skills(eSkill::INVALID,nullptr); + display_skills(eSkill::INVALID,nullptr,true); break; case eMenu::LIBRARY_ALCHEMY: // TODO: Create a dedicated dialog for alchemy info diff --git a/src/pcedit/pc.editors.cpp b/src/pcedit/pc.editors.cpp index f476c28ec..33c2732e2 100644 --- a/src/pcedit/pc.editors.cpp +++ b/src/pcedit/pc.editors.cpp @@ -31,7 +31,7 @@ void display_alchemy(bool allowEdit,cDialog* parent); bool spend_xp(short pc_num, short mode, cDialog* parent); // TODO: There's probably a more logical way of arranging this -void display_skills(eSkill skill,cDialog* parent); +void display_skills(eSkill skill,cDialog* parent, bool record = false); extern cUniverse univ; extern short store_flags[3]; diff --git a/src/pcedit/pc.main.cpp b/src/pcedit/pc.main.cpp index 2cc2c3dc2..9a897f3ed 100644 --- a/src/pcedit/pc.main.cpp +++ b/src/pcedit/pc.main.cpp @@ -459,7 +459,7 @@ bool verify_restore_quit(std::string dlog) { return true; } -void display_skills(eSkill skill,cDialog* parent) { +void display_skills(eSkill skill,cDialog* parent, bool record) { extern std::map skill_cost; extern std::map skill_max; extern std::map skill_g_cost; From 6ff155434c3044771cf1e0ea164a72e29bb9f8bf Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sun, 23 Feb 2025 14:40:49 -0600 Subject: [PATCH 9/9] don't access uninitialized sf::Event --- src/game/boe.specials.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/boe.specials.cpp b/src/game/boe.specials.cpp index afb183fd9..8c9c12888 100644 --- a/src/game/boe.specials.cpp +++ b/src/game/boe.specials.cpp @@ -2046,7 +2046,7 @@ void run_special(eSpecCtx which_mode, eSpecCtxType which_type, spec_num_t start_ if(replaying && has_next_action("step_through_exit")){ pop_next_action(); univ.node_step_through = false; - }else if(evt.type == sf::Event::KeyPressed && evt.key.code == sf::Keyboard::Escape){ + }else if(!replaying && evt.type == sf::Event::KeyPressed && evt.key.code == sf::Keyboard::Escape){ record_action("step_through_exit", ""); univ.node_step_through = false; }