Huge makeover for debug keys

This commit is contained in:
2025-01-25 19:45:08 -06:00
parent 2e804c1aa9
commit b1efc03029
6 changed files with 182 additions and 126 deletions

View File

@@ -3,31 +3,41 @@
<dialog defbtn='okay'>
<pict type='dlog' num='16' top='8' left='8'/>
<text top='8' left='50' width='400' height='16'>DEBUG MODE HELP</text>
<text top='22' left='50' width='400' height='250'>
<text top='22' left='50' width='400' height='50'>
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.<br/><br/>
Debug hot keys<br/>
B - Leave town<br/>
C - Get cleaned up (lose negative status effects)<br/>
D - Toggle Debug mode<br/>
E - Stealth, Detect Life, Firewalk<br/>
F - Flight<br/>
G - Toggle Ghost mode (letting you pass through walls)<br/>
H - Heal<br/>
K - Kill everything<br/>
N - End Scenario<br/>
O - Location<br/>
Q - Magic map<br/>
R - Return to Start<br/>
S - Set stuff done flags<br/>
T - Enter Town<br/>
W - Refresh jobs/shops<br/>
= - Heal, increase magic skills<br/>
&lt; - Make one day pass<br/>
&gt; - Reset towns (excludes the one you're in, if any)<br/>
! - Toggle Special Node Step-through Mode<br/>
/ - Bring up this list<br/>
Debug hot keys:<br/>
</text>
<button name='okay' type='regular' top='280' left='387'>OK</button>
<button name='btn1' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='3'></button>
<button name='btn2' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn3' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn4' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn5' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn6' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn7' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn8' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn9' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn10' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn11' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn12' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn13' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn14' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn15' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn16' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn17' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn18' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn19' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn20' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn21' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn22' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn23' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn24' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn25' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn26' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn27' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn28' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn29' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='btn30' type='tiny' relative='pos-in pos' rel-anchor='prev' left='0' top='1'></button>
<button name='okay' type='regular' top='400' left='387'>OK</button>
</dialog>

View File

@@ -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;

View File

@@ -57,6 +57,8 @@ struct cKey {
eKeyMod mod;
};
cKey charToKey(char ch);
/// Combine two key modifiers.
/// @param lhs @param rhs
/// @return lhs + rhs

View File

@@ -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<char,key_action_t> debug_actions;
std::vector<std::vector<char>> debug_actions_help_order;
void add_debug_action(std::vector<char> 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;

View File

@@ -2,11 +2,18 @@
#ifndef BOE_GAME_ACTIONS_H
#define BOE_GAME_ACTIONS_H
#include <vector>
#include <SFML/Window/Event.hpp>
#include "location.hpp"
#include "dialogxml/keycodes.hpp"
#include "tools/framerate_limiter.hpp"
struct key_action_t {
std::vector<char> 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();

View File

@@ -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<int>(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);