Merge pull request #534 from NQNStudios:fix-479
Fixing text buffer texture/font corruption (#479) * #479 demonstrates that the contents of the text buffer are NOT irrelevant for reproducing bugs. So I set up recording/replay for the burma shave easter egg. This also makes an easy way to mess with the buffer state when debugging (just mash &/\*/&/\*/&/\* n times) * When a replay throws an error, it puts up a showError() dialog. If the next action is a control_click, the system will try to click that control on the error dialog--which is totally divergent from the replay's intended behavior. So we should just stop replaying when an error happens. * If you have a long replay and want to run it very fast, but then slow down when you get to the sequence that reproduces your bug, now you can add a `<change_fps>` to your replay to achieve that. * Fixes for the 2 legacy replay errors that I opened recently Fix #479 Fix #532 Fix #533
This commit is contained in:
@@ -2232,6 +2232,24 @@ void cancel_item_target(bool& did_something, bool& need_redraw, bool& need_repri
|
||||
did_something = need_redraw = need_reprint = true;
|
||||
}
|
||||
|
||||
// I'm finally adding the easter egg to the replay system
|
||||
// because it allows forcing the text buffer into a specific state
|
||||
// which I'm debugging.
|
||||
std::vector<std::string> easter_egg_messages = {
|
||||
"If Valorim ...",
|
||||
"You want to save ...",
|
||||
"Back up your save files ...",
|
||||
"Burma Shave."
|
||||
};
|
||||
|
||||
void easter_egg(int idx) {
|
||||
if(recording){
|
||||
record_action("easter_egg", boost::lexical_cast<std::string>(idx));
|
||||
}
|
||||
add_string_to_buf(easter_egg_messages[idx]);
|
||||
print_buf();
|
||||
}
|
||||
|
||||
bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){
|
||||
bool are_done = false;
|
||||
location pass_point; // TODO: This isn't needed
|
||||
@@ -2387,20 +2405,16 @@ bool handle_keystroke(const sf::Event& event, cFramerateLimiter& fps_limiter){
|
||||
switch(chr) {
|
||||
|
||||
case '&':
|
||||
add_string_to_buf("If Valorim ...");
|
||||
print_buf();
|
||||
easter_egg(0);
|
||||
break;
|
||||
case '*':
|
||||
add_string_to_buf("You want to save ...");
|
||||
print_buf();
|
||||
easter_egg(1);
|
||||
break;
|
||||
case '(':
|
||||
add_string_to_buf("Back up your save files ...");
|
||||
print_buf();
|
||||
easter_egg(2);
|
||||
break;
|
||||
case ')':
|
||||
add_string_to_buf("Burma Shave.");
|
||||
print_buf();
|
||||
easter_egg(3);
|
||||
break;
|
||||
|
||||
case '?':
|
||||
@@ -3749,11 +3763,11 @@ bool is_sign(ter_num_t ter) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool check_for_interrupt(){
|
||||
bool check_for_interrupt(std::string confirm_dialog){
|
||||
using kb = sf::Keyboard;
|
||||
bool interrupt = false;
|
||||
sf::Event evt;
|
||||
if(replaying && has_next_action() && next_action_type() == "handle_interrupt"){
|
||||
if(replaying && confirm_dialog == "confirm-interrupt-special" && has_next_action() && next_action_type() == "handle_interrupt"){
|
||||
pop_next_action();
|
||||
interrupt = true;
|
||||
}
|
||||
@@ -3770,8 +3784,16 @@ bool check_for_interrupt(){
|
||||
if(recording){
|
||||
record_action("handle_interrupt", "");
|
||||
}
|
||||
cChoiceDlog confirm("confirm-interrupt", {"quit","cancel"});
|
||||
if(confirm.show() == "quit") return true;
|
||||
cChoiceDlog confirm(confirm_dialog, {"quit","cancel"});
|
||||
bool was_replaying = replaying;
|
||||
if(confirm_dialog == "confirm-interrupt-replay"){
|
||||
// There's a slight chance the next action could be snatched up by the replay system to respond
|
||||
// to the yes/no prompt, so suspend the replay loop
|
||||
replaying = false;
|
||||
}
|
||||
std::string result = confirm.show();
|
||||
replaying = was_replaying;
|
||||
if(result == "quit") return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ void setup_outdoors(location where);
|
||||
short get_outdoor_num();
|
||||
short count_walls(location loc);
|
||||
bool is_sign(ter_num_t ter);
|
||||
bool check_for_interrupt();
|
||||
bool check_for_interrupt(std::string confirm_dialog = "confirm-interrupt-special");
|
||||
|
||||
void handle_startup_button_click(eStartButton btn, eKeyMod mods);
|
||||
void handle_switch_pc(short which_pc, bool& need_redraw, bool& need_reprint);
|
||||
@@ -102,5 +102,6 @@ void show_item_info(short item_hit);
|
||||
void close_map(bool record = false);
|
||||
void cancel_item_target(bool& did_something, bool& need_redraw, bool& need_reprint);
|
||||
void update_item_stats_area(bool& need_reprint);
|
||||
void easter_egg(int idx);
|
||||
|
||||
#endif
|
||||
|
@@ -1599,7 +1599,8 @@ void do_combat_cast(location target) {
|
||||
if(ashes_loc.x > 0){
|
||||
// If ashes are going to appear, there'd better be a visible blast on the spot.
|
||||
if(!hit_ashes_loc){
|
||||
add_explosion(ashes_loc,0,0,get_boom_type(eDamageType::FIRE),1,0);
|
||||
// the last argument is true so this doesn't break RNG of older replays:
|
||||
add_explosion(ashes_loc,0,0,get_boom_type(eDamageType::FIRE),1,0,true);
|
||||
}
|
||||
|
||||
univ.town.set_ash(ashes_loc.x,ashes_loc.y,true);
|
||||
|
@@ -10,6 +10,7 @@
|
||||
#define BOE_GAME_CONSTS_H
|
||||
|
||||
#include <set>
|
||||
#include <map>
|
||||
|
||||
/* overall mode; some seem to be missing */
|
||||
enum eGameMode {
|
||||
@@ -84,14 +85,9 @@ enum eTrapType {
|
||||
|
||||
// Startup buttons
|
||||
enum eStartButton {
|
||||
// Left Column
|
||||
STARTBTN_TUTORIAL = 0,
|
||||
STARTBTN_LOAD = 1,
|
||||
STARTBTN_PREFS = 2,
|
||||
// Right Column
|
||||
STARTBTN_NEW = 3,
|
||||
STARTBTN_JOIN = 4,
|
||||
STARTBTN_SCROLL = 5,
|
||||
STARTBTN_TUTORIAL = 0, STARTBTN_NEW = 3,
|
||||
STARTBTN_LOAD = 1, STARTBTN_JOIN = 4,
|
||||
STARTBTN_PREFS = 2, STARTBTN_SCROLL = 5,
|
||||
// Keep last:
|
||||
MAX_eStartButton = 6
|
||||
};
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
#include <map>
|
||||
#include "boe.consts.hpp"
|
||||
|
||||
#define ASB add_string_to_buf
|
||||
@@ -26,4 +27,8 @@ struct effect_pat_type {
|
||||
unsigned short pattern[9][9];
|
||||
};
|
||||
|
||||
extern std::map<std::string, int> startup_button_indices;
|
||||
extern std::map<int, std::string> startup_button_names;
|
||||
extern std::map<int, std::string> startup_button_names_v1;
|
||||
|
||||
#endif
|
||||
|
@@ -414,12 +414,9 @@ void draw_startup_stats() {
|
||||
void draw_start_button(eStartButton which_position,short which_button) {
|
||||
rectangle from_rect,to_rect;
|
||||
const char *button_labels[MAX_eStartButton];
|
||||
button_labels[STARTBTN_TUTORIAL] = "Tutorial";
|
||||
button_labels[STARTBTN_LOAD] = "Load Game";
|
||||
button_labels[STARTBTN_PREFS] = "Preferences";
|
||||
button_labels[STARTBTN_NEW] = "Make New Party";
|
||||
button_labels[STARTBTN_JOIN] = "Start Scenario";
|
||||
button_labels[STARTBTN_SCROLL] = "";
|
||||
for(int i = 0; i < MAX_eStartButton; ++i){
|
||||
button_labels[i] = startup_button_names[i].c_str();
|
||||
}
|
||||
|
||||
// The 0..65535 version of the blue component was 14472; the commented version was 43144431
|
||||
sf::Color base_color = {0,0,57};
|
||||
|
@@ -333,6 +333,9 @@ static void process_args(int argc, char* argv[]) {
|
||||
cli.writeToStream(std::cout);
|
||||
exit(0);
|
||||
}
|
||||
// This obsolete preference should always be true unless running an old replay
|
||||
// (which will set it false after this line if it needs to)
|
||||
set_pref("DrawTerrainFrills", true);
|
||||
if(replay){
|
||||
if(record_to){
|
||||
std::cout << "Warning: flag --record conflicts with --replay and will be ignored." << std::endl;
|
||||
@@ -502,6 +505,29 @@ static void handle_scenario_args() {
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, int> startup_button_indices = {
|
||||
// Button layout since 11/30/24
|
||||
{"Tutorial", 0}, {"Make New Party", 3},
|
||||
{"Load Game", 1}, {"Start Scenario", 4},
|
||||
{"Preferences", 2},
|
||||
|
||||
// Buttons that don't exist anymore
|
||||
{"Custom Scenario", -1},
|
||||
};
|
||||
|
||||
std::map<int, std::string> startup_button_names = {
|
||||
{0, "Tutorial"}, {3, "Make New Party"},
|
||||
{1, "Load Game"}, {4, "Start Scenario"},
|
||||
{2, "Preferences"}, {5, ""},
|
||||
};
|
||||
|
||||
// Map legacy int indices onto new string-mapped layout
|
||||
std::map<int, std::string> startup_button_names_v1 = {
|
||||
{0, "Load Game"}, {3, "Start Scenario"},
|
||||
{1, "Make New Party"}, {4, "Custom Scenario"},
|
||||
{2, "Preferences"},
|
||||
};
|
||||
|
||||
void replay_action(Element& action) {
|
||||
bool did_something = false, need_redraw = false, need_reprint = false;
|
||||
|
||||
@@ -512,10 +538,30 @@ void replay_action(Element& action) {
|
||||
// NOTE: Action replay blocks need to return early unless the action advances time
|
||||
if(overall_mode == MODE_STARTUP && t == "startup_button_click"){
|
||||
auto info = info_from_action(action);
|
||||
eStartButton btn = static_cast<eStartButton>(std::stoi(info["btn"]));
|
||||
int btn_idx = -1;
|
||||
try{
|
||||
// Legacy replays use ints to encode startup buttons
|
||||
btn_idx = std::stoi(info["btn"]);
|
||||
}catch(std::invalid_argument& err){
|
||||
// Newer replays use strings to encode startup buttons
|
||||
btn_idx = startup_button_indices[info["btn"]];
|
||||
}
|
||||
// No-op button
|
||||
if(btn_idx == -1){
|
||||
return;
|
||||
}
|
||||
eStartButton btn = static_cast<eStartButton>(btn_idx);
|
||||
eKeyMod mods = static_cast<eKeyMod>(std::stoi(info["mods"]));
|
||||
handle_startup_button_click(btn, mods);
|
||||
return;
|
||||
}else if(t == "change_fps"){
|
||||
extern boost::optional<cFramerateLimiter> replay_fps_limit;
|
||||
// default new fps: slow the replay down substantially
|
||||
int new_fps = 2;
|
||||
if(!action.GetText().empty()){
|
||||
new_fps = boost::lexical_cast<int>(action.GetText());
|
||||
}
|
||||
replay_fps_limit.emplace(new_fps);
|
||||
}else if(t == "load_party"){
|
||||
decode_file(action.GetText(), tempDir / "temp.exg");
|
||||
load_party(tempDir / "temp.exg", univ);
|
||||
@@ -832,12 +878,15 @@ void replay_action(Element& action) {
|
||||
return;
|
||||
}else if(t == "cancel_item_target"){
|
||||
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 == "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;
|
||||
}else{
|
||||
std::ostringstream sstr;
|
||||
sstr << "Couldn't replay action: " << action;
|
||||
replaying = false;
|
||||
throw sstr.str();
|
||||
}
|
||||
|
||||
@@ -1009,6 +1058,10 @@ void handle_events() {
|
||||
|
||||
while(!All_Done) {
|
||||
if(replaying && has_next_action()){
|
||||
if(check_for_interrupt("confirm-interrupt-replay")){
|
||||
replaying = false;
|
||||
continue;
|
||||
}
|
||||
replay_next_action();
|
||||
}else{
|
||||
#ifdef __APPLE__
|
||||
|
@@ -302,7 +302,9 @@ void mondo_boom(location l,short type,short snd) {
|
||||
end_missile_anim();
|
||||
}
|
||||
|
||||
void add_explosion(location dest,short val_to_place,short place_type,short boom_type,short x_adj,short y_adj) {
|
||||
void add_explosion(location dest,short val_to_place,short place_type,short boom_type,short x_adj,short y_adj, bool use_unique_ran) {
|
||||
if(!get_bool_pref("DrawTerrainFrills", true))
|
||||
return;
|
||||
if(!boom_anim_active)
|
||||
return;
|
||||
// lose redundant explosions
|
||||
@@ -316,7 +318,7 @@ void add_explosion(location dest,short val_to_place,short place_type,short boom_
|
||||
for(short i = 0; i < 30; i++)
|
||||
if(store_booms[i].boom_type < 0) {
|
||||
have_boom = true;
|
||||
store_booms[i].offset = (i == 0) ? 0 : -1 * get_ran(1,0,2);
|
||||
store_booms[i].offset = (i == 0) ? 0 : -1 * get_ran(1,0,2,use_unique_ran);
|
||||
store_booms[i].dest = dest;
|
||||
store_booms[i].val_to_place = val_to_place;
|
||||
store_booms[i].place_type = place_type;
|
||||
|
@@ -56,7 +56,7 @@ void run_a_missile(location from,location fire_to,miss_num_t miss_type,short pat
|
||||
void run_a_boom(location boom_where,short type,short x_adj,short y_adj,short snd = -1);
|
||||
void mondo_boom(location l,short type,short snd = -1);
|
||||
void add_missile(location dest,miss_num_t missile_type,short path_type,short x_adj,short y_adj);
|
||||
void add_explosion(location dest,short val_to_place,short place_type,short boom_type,short x_adj,short y_adj);
|
||||
void add_explosion(location dest,short val_to_place,short place_type,short boom_type,short x_adj,short y_adj, bool use_unique_ran = false);
|
||||
void do_missile_anim(short num_steps,location missile_origin,short sound_num) ;
|
||||
void do_explosion_anim(short sound_num,short expand,short snd = -1);
|
||||
void click_shop_rect(rectangle area_rect);
|
||||
|
@@ -2222,10 +2222,11 @@ bool pick_pc_graphic(short pc_num,short mode,cDialog* parent) {
|
||||
static bool pc_name_event_filter(cDialog& me, short store_train_pc) {
|
||||
std::string pcName = me["name"].getText();
|
||||
|
||||
if(!isalpha(pcName[0])) {
|
||||
if(pcName.empty()){
|
||||
me["error"].setText("Cannot be empty.");
|
||||
}else if(!isalpha(pcName[0])){
|
||||
me["error"].setText("Must begin with a letter.");
|
||||
}
|
||||
else {
|
||||
}else{
|
||||
// TODO: This was originally truncated to 18 characters; is that really necessary?
|
||||
univ.party[store_train_pc].name = pcName;
|
||||
me.toast(true);
|
||||
|
@@ -42,7 +42,7 @@ enum_map(eStartButton, rectangle) startup_button;
|
||||
void handle_startup_button_click(eStartButton btn, eKeyMod mods) {
|
||||
if(recording){
|
||||
std::map<std::string, std::string> info;
|
||||
info["btn"] = boost::lexical_cast<std::string>(btn);
|
||||
info["btn"] = startup_button_names[btn];
|
||||
info["mods"] = boost::lexical_cast<std::string>(mods);
|
||||
record_action("startup_button_click", info);
|
||||
}
|
||||
|
Reference in New Issue
Block a user