From b1efc03029bead6679ca39bd9d6549183575f098 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Sat, 25 Jan 2025 19:45:08 -0600 Subject: [PATCH] Huge makeover for debug keys --- rsrc/dialogs/help-debug.xml | 56 ++++++---- src/dialogxml/keycodes.cpp | 24 ++++ src/dialogxml/keycodes.hpp | 2 + src/game/boe.actions.cpp | 214 +++++++++++++++++++----------------- src/game/boe.actions.hpp | 9 ++ src/game/boe.main.cpp | 3 + 6 files changed, 182 insertions(+), 126 deletions(-) diff --git a/rsrc/dialogs/help-debug.xml b/rsrc/dialogs/help-debug.xml index eac6f7d4..8dd5a598 100644 --- a/rsrc/dialogs/help-debug.xml +++ b/rsrc/dialogs/help-debug.xml @@ -3,31 +3,41 @@ DEBUG MODE HELP - + Debug mode is intended to aid you in testing your scenario. While in debug mode, monsters don't move in combat and are killed in one hit. In addition, you have access to a lot of additional hotkeys.

- Debug hot keys
- B - Leave town
- C - Get cleaned up (lose negative status effects)
- D - Toggle Debug mode
- E - Stealth, Detect Life, Firewalk
- F - Flight
- G - Toggle Ghost mode (letting you pass through walls)
- H - Heal
- K - Kill everything
- N - End Scenario
- O - Location
- Q - Magic map
- R - Return to Start
- S - Set stuff done flags
- T - Enter Town
- W - Refresh jobs/shops
- = - Heal, increase magic skills
- < - Make one day pass
- > - Reset towns (excludes the one you're in, if any)
- ! - Toggle Special Node Step-through Mode
- / - Bring up this list
+ Debug hot keys:
- + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/src/dialogxml/keycodes.cpp b/src/dialogxml/keycodes.cpp index b73a05ea..9e492dac 100644 --- a/src/dialogxml/keycodes.cpp +++ b/src/dialogxml/keycodes.cpp @@ -18,6 +18,30 @@ //#include "cursors.hpp" #include "keymods.hpp" +// NOTE: This only supports a few characters including the ones +// that are currently used for debug key shortcuts! +// It is also very hard-coded to the key layout on my laptop (which may only be standard in the US!) +cKey charToKey(char ch) { + if(ch >= 'a' && ch <= 'z'){ + return {false, ch}; + }else if(ch >= 'A' && ch <= 'Z'){ + return {false, tolower(ch), mod_shift}; + } + switch(ch){ + case '=': case '/': + return {false, ch}; + case '<': + return {false, ',', mod_shift}; + case '>': + return {false, '.', mod_shift}; + case '!': + return {false, '1', mod_shift}; + case '?': + return {false, '/', mod_shift}; + } + throw "Tried to convert unsupported char to cKey!"; +} + eKeyMod operator + (eKeyMod lhs, eKeyMod rhs){ if(lhs == rhs) return lhs; else if(lhs == mod_none) return rhs; diff --git a/src/dialogxml/keycodes.hpp b/src/dialogxml/keycodes.hpp index fbc16557..19d1ea72 100644 --- a/src/dialogxml/keycodes.hpp +++ b/src/dialogxml/keycodes.hpp @@ -57,6 +57,8 @@ struct cKey { eKeyMod mod; }; +cKey charToKey(char ch); + /// Combine two key modifiers. /// @param lhs @param rhs /// @return lhs + rhs diff --git a/src/game/boe.actions.cpp b/src/game/boe.actions.cpp index 8b65689d..8886b621 100644 --- a/src/game/boe.actions.cpp +++ b/src/game/boe.actions.cpp @@ -27,6 +27,7 @@ #include "boe.ui.hpp" #include "mathutil.hpp" #include "fileio/fileio.hpp" +#include "fileio/resmgr/res_dialog.hpp" #include "dialogxml/dialogs/choicedlog.hpp" #include "dialogxml/dialogs/dialog.hpp" #include "dialogxml/widgets/scrollbar.hpp" @@ -2273,8 +2274,108 @@ void easter_egg(int idx) { print_buf(); } +std::map debug_actions; +std::vector> debug_actions_help_order; + +void add_debug_action(std::vector keys, std::string name, void (*action)()) { + key_action_t shortcut = {keys, name, action}; + for(char ch: keys){ + debug_actions[ch] = shortcut; + } + debug_actions_help_order.push_back(keys); +} + +void show_debug_help() { + if(recording){ + record_action("show_debug_help", ""); + } + cDialog debug_panel(*ResMgr::dialogs.get("help-debug")); + int idx; + for(idx = 0; idx < debug_actions_help_order.size(); ++idx){ + std::ostringstream btn_name; + btn_name << "btn" << (idx+1); + cControl& button = debug_panel[btn_name.str()]; + key_action_t action = debug_actions[debug_actions_help_order[idx][0]]; + std::ostringstream btn_text; + btn_text << action.keys[0]; + for(int key_idx = 1; key_idx < action.keys.size(); ++key_idx){ + btn_text << ", " << action.keys[key_idx]; + } + btn_text << " - " << action.name; + button.setText(btn_text.str()); + // TODO it's unfortunate that the button can only have one key if actions might have + // more than one + button.attachKey(charToKey(action.keys[0])); + // Don't recursively open the panel + if(action.action != &show_debug_help){ + button.attachClickHandler([action](cDialog& me, std::string, eKeyMod) -> bool { + me.toast(false); + action.action(); + return true; + }); + } + } + for(; idx < 30; ++idx){ + std::ostringstream btn_name; + btn_name << "btn" << (idx+1); + cControl& button = debug_panel[btn_name.str()]; + button.hide(); + } + + debug_panel.attachClickHandlers([](cDialog& me, std::string, eKeyMod) -> bool { + me.toast(false); + return true; + }, {"okay"}); + + debug_panel.run(); +} + // Non-comprehensive list of unused keys: // JUXYZ 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); + add_debug_action({'D'}, "Toggle Debug mode", toggle_debug_mode); + add_debug_action({'E'}, "Stealth, Detect Life, Firewalk", debug_stealth_detect_life_firewalk); + add_debug_action({'F'}, "Flight", debug_fly); + add_debug_action({'G'}, "Toggle Ghost mode (letting you pass through walls)", debug_ghost_mode); + add_debug_action({'H'}, "Heal", debug_heal); + // This one was missing from the old help dialog: + add_debug_action({'I'}, "Give item", debug_give_item); + add_debug_action({'K'}, "Kill everything", debug_kill); + add_debug_action({'N'}, "End scenario", []() -> void {handle_victory(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); + add_debug_action({'S'}, "Set stuff done flags", []() -> void { + // edit_stuff_done() is used in the character editor which + // doesn't have replays, so its replay action is recorded + // external to the function definition unlike most actions. + if(recording){ + record_action("edit_stuff_done", ""); + } + edit_stuff_done(); + }); + add_debug_action({'T'}, "Enter town", debug_enter_town); + add_debug_action({'W'}, "Refresh jobs/shops", debug_refresh_stores); + add_debug_action({'='}, "Heal, increase magic skills", debug_heal_plus_extra); + add_debug_action({'<'}, "Make one day pass", debug_increase_age); + add_debug_action({'>'}, "Reset towns (excludes the one you're in, if any)", debug_towns_forget); + add_debug_action({'!'}, "Toggle Special Node Step-through Mode", debug_step_through); + add_debug_action({'/', '?'}, "Bring up this window", show_debug_help); +} + +// Later we might want to know whether the key is used or not +bool handle_debug_key(char key) { + if(!univ.debug_mode) + return false; + if(debug_actions.find(key) != debug_actions.end()){ + debug_actions[key].action(); + return true; + } + return false; +} + bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){ bool are_done = false; location pass_point; // TODO: This isn't needed @@ -2453,6 +2554,11 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){ break; case '?': + // In debug mode, override basic help menus with a debug mode cheat-sheet + if(univ.debug_mode){ + show_debug_help(); + break; + } if(overall_mode == MODE_SHOPPING) { give_help_and_record(226,27); break; @@ -2498,7 +2604,6 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){ } break; - case 'D': toggle_debug_mode(); break; @@ -2508,108 +2613,6 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){ show_inventory(); break; - case '=': - if(!univ.debug_mode) break; - debug_heal_plus_extra(); - break; - - case 'B': - if(!univ.debug_mode) break; - debug_leave_town(); - break; - - case 'C': - if(!univ.debug_mode) break; - debug_clean_up(); - break; - - case 'E': - if(!univ.debug_mode) break; - debug_stealth_detect_life_firewalk(); - break; - - case 'F': - if(!univ.debug_mode) break; - debug_fly(); - break; - - case 'G': - if(!univ.debug_mode) break; - debug_ghost_mode(); - break; - - case 'H': - if(!univ.debug_mode) break; - debug_heal(); - break; - - case 'K': - if(!univ.debug_mode) break; - debug_kill(); - break; - - case 'N': - if(!univ.debug_mode) break; - handle_victory(true); - break; - - case 'O': - if(!univ.debug_mode) break; - debug_print_location(); - break; - - case 'I': - if(!univ.debug_mode) break; - debug_give_item(); - break; - - case 'Q': - if(!univ.debug_mode) break; - debug_magic_map(); - break; - - case 'R': - if(!univ.debug_mode) break; - debug_return_to_start(); - break; - - case 'S': - if(!univ.debug_mode) break; - // edit_stuff_done() is used in the character editor which - // doesn't have replays, so its replay action is recorded - // external to the function definition unlike most actions. - if(recording){ - record_action("edit_stuff_done", ""); - } - edit_stuff_done(); - break; - - case 'T': - if(!univ.debug_mode) break; - debug_enter_town(); - break; - - case 'W': - if(!univ.debug_mode) break; - debug_refresh_stores(); - break; - - case '<': - if(!univ.debug_mode) break; - debug_increase_age(); - break; - case '>': - if(!univ.debug_mode) break; - debug_towns_forget(); - break; - case '!': - if(!univ.debug_mode) break; - debug_step_through(); - break; - case '/': - if(!univ.debug_mode) break; - show_dialog_action("help-debug"); - break; case 'a': // Show automap display_map(); break; @@ -2716,6 +2719,11 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){ advance_time(did_something, need_redraw, need_reprint); } break; + + // Debug action keyboard shortcuts: + default: + handle_debug_key(chr); + break; } spell_forced = false; return are_done; diff --git a/src/game/boe.actions.hpp b/src/game/boe.actions.hpp index 247e9ebf..732bf38e 100644 --- a/src/game/boe.actions.hpp +++ b/src/game/boe.actions.hpp @@ -2,11 +2,18 @@ #ifndef BOE_GAME_ACTIONS_H #define BOE_GAME_ACTIONS_H +#include #include #include "location.hpp" #include "dialogxml/keycodes.hpp" #include "tools/framerate_limiter.hpp" +struct key_action_t { + std::vector keys; + std::string name; + void (*action)(); +}; + void init_screen_locs(); bool prime_time(); bool handle_action(const sf::Event& event, cFramerateLimiter& fps_limiter); @@ -75,6 +82,8 @@ void handle_use_space_select(bool& need_reprint); void handle_use_space(location destination, bool& did_something, bool& need_redraw); void show_inventory(); void toggle_debug_mode(); +void init_debug_actions(); +void show_debug_help(); void debug_give_item(); void debug_print_location(); void debug_step_through(); diff --git a/src/game/boe.main.cpp b/src/game/boe.main.cpp index 3adf75fa..ec0efa3c 100644 --- a/src/game/boe.main.cpp +++ b/src/game/boe.main.cpp @@ -873,6 +873,8 @@ 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"){ + show_debug_help(); }else if(t == "advance_time"){ // This is bad regardless of strictness, because visual changes may have occurred which won't get redrawn/reprinted throw std::string { "Replay system internal error! advance_time() was supposed to be called by the last action, but wasn't: " } + _last_action_type; @@ -942,6 +944,7 @@ void init_boe(int argc, char* argv[]) { cUniverse::print_result = iLiving::print_result = add_string_to_buf; cPlayer::give_help = give_help; init_fileio(); + init_debug_actions(); init_spell_menus(); init_mini_map(); redraw_screen(REFRESH_NONE);