Merge pull request #551 from NQNStudios:keys

Refactor Debug Keys

* Collect debug keys into a central location so that the debug help dialog can automatically list every defined debug key.
* Add buttons so that debug actions can be taken directly from the debug help dialog.
* Add new debug keys to immediately fight an encounter from the current outdoor section.
This commit is contained in:
2025-02-02 13:56:38 -05:00
committed by GitHub
11 changed files with 290 additions and 158 deletions

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"
@@ -1887,9 +1888,24 @@ void handle_menu_spell(eSpell spell_picked) {
advance_time(did_something, need_redraw, need_reprint);
}
void initiate_outdoor_combat(short i) {
static void set_up_combat() {
location to_place;
for(cPlayer& pc : univ.party)
if(pc.main_status == eMainStatus::ALIVE)
to_place = pc.combat_pos;
else for(cItem& item : pc.items)
if(item.variety != eItemType::NO_ITEM) {
place_item(item,to_place);
item.variety = eItemType::NO_ITEM;
}
overall_mode = MODE_COMBAT;
center = univ.current_pc().combat_pos;
draw_terrain();
}
void initiate_outdoor_combat(short i) {
// Make sure the player can see the monster they stepped next to
draw_terrain();
// Is combat too easy?
@@ -1900,24 +1916,11 @@ void initiate_outdoor_combat(short i) {
return;
}
// Delay((long) 100,&dummy);
start_outdoor_combat(univ.party.out_c[i], univ.party.out_loc,count_walls(univ.party.out_loc));
univ.party.out_c[i].exists = false;
for(cPlayer& pc : univ.party)
if(pc.main_status == eMainStatus::ALIVE)
to_place = pc.combat_pos;
else for(cItem& item : pc.items)
if(item.variety != eItemType::NO_ITEM) {
place_item(item,to_place);
item.variety = eItemType::NO_ITEM;
}
overall_mode = MODE_COMBAT;
center = univ.current_pc().combat_pos;
draw_terrain();
set_up_combat();
}
void show_inventory() {
@@ -1955,6 +1958,37 @@ void toggle_debug_mode() {
print_buf();
}
void debug_fight_encounter(bool wandering) {
if(recording){
record_action("debug_fight_encounter", bool_to_str(wandering));
}
if(!is_out()){
ASB("Debug outdoor encounter: You have to be");
ASB("outdoors!");
print_buf();
return;
}
std::string prompt = "Which ";
if(wandering){
prompt += "wandering encounter?";
}else{
prompt += "special encounter?";
}
int i = get_num_response(0, 3, prompt);
cOutdoors::cWandering encounter;
if(wandering){
encounter = univ.out->wandering[i];
}else{
encounter = univ.out->special_enc[i];
}
start_outdoor_combat(encounter, univ.party.out_loc, count_walls(univ.party.out_loc));
set_up_combat();
}
void debug_give_item() {
if(recording){
record_action("debug_give_item", "");
@@ -2265,6 +2299,111 @@ 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);
// std::bind won't work here for reasons
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);
}
// 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
@@ -2443,6 +2582,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;
@@ -2488,116 +2632,15 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){
}
break;
case 'D':
toggle_debug_mode();
break;
// 'z', Really? 'i' is not used
case 'z':
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;
@@ -2704,6 +2747,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,9 @@ 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_fight_encounter(bool wandering);
void debug_give_item();
void debug_print_location();
void debug_step_through();

View File

@@ -251,11 +251,7 @@ effect_pat_type field[8] = {
bool center_on_monst;
void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short num_walls) {
void start_outdoor_combat(cOutdoors::cWandering encounter,location where,short num_walls) {
short how_many,num_tries = 0;
short low[10] = {15,7,4,3,2,1,1,7,2,1};
short high[10] = {30,10,6,5,3,2,1,10,4,1};
@@ -266,7 +262,7 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short nu
nums[i] = get_ran(1,low[i],high[i]);
for(short i = 0; i < 3; i++)
nums[i + 7] = get_ran(1,low[i + 7],high[i + 7]);
notify_out_combat_began(encounter.what_monst,nums);
notify_out_combat_began(encounter,nums);
print_buf();
play_sound(23);
@@ -286,15 +282,15 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short nu
for(short i = 0; i < 7; i++) {
how_many = nums[i];
if(encounter.what_monst.monst[i] != 0)
if(encounter.monst[i] != 0)
for(short j = 0; j < how_many; j++)
set_up_monst(eAttitude::HOSTILE_A,encounter.what_monst.monst[i]);
set_up_monst(eAttitude::HOSTILE_A,encounter.monst[i]);
}
for(short i = 0; i < 3; i++) {
how_many = nums[i + 7];
if(encounter.what_monst.friendly[i] != 0)
if(encounter.friendly[i] != 0)
for(short j = 0; j < how_many; j++)
set_up_monst(eAttitude::FRIENDLY,encounter.what_monst.friendly[i]);
set_up_monst(eAttitude::FRIENDLY,encounter.friendly[i]);
}
// place PCs
@@ -371,6 +367,10 @@ void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short nu
}
void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short num_walls) {
start_outdoor_combat(encounter.what_monst, where, num_walls);
}
bool pc_combat_move(location destination) {
std::string create_line;
short s1;

View File

@@ -9,6 +9,9 @@
#include "boe.global.hpp"
#include "spell.hpp"
// For debug purposes, allow directly starting an outdoor encounter without
// a wandering monster
void start_outdoor_combat(cOutdoors::cWandering encounter,location where,short num_walls);
void start_outdoor_combat(cOutdoors::cCreature encounter,location where,short num_walls);
bool pc_combat_move(location destination);
void char_parry();

View File

@@ -873,6 +873,10 @@ 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 == "debug_fight_encounter"){
debug_fight_encounter(str_to_bool(action.GetText()));
}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 +946,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);