Implement Go Back stack for talk mode. Fix #726

This commit is contained in:
2025-05-06 11:43:17 -05:00
parent 4519e83a45
commit 3f96328736
4 changed files with 94 additions and 44 deletions

View File

@@ -83,6 +83,22 @@ short store_talk_face_pic;
int current_talk_node;
extern std::vector<word_rect_t> talk_words;
// "Go Back" stack: When pressing the "Go Back" button, we don't just want to reverse through
// the nodes the player has seen, because they might change game state the first time, causing
// you to go backwards to a node that would behave differently than it did before.
// The "Go Back" stack allows you to reverse through exactly what the dialog engine has showed you,
// in a read-only fashion as long as you only go backwards.
struct dialog_history_t {
bool can_record;
bool special_node;
// Store node numbers for shop nodes.
int node;
// Store text for normal dialog nodes.
std::pair<std::string, std::string> text;
};
std::vector<dialog_history_t> dialog_history;
// Shopping vars
// 1 - 499 ... regular items
@@ -193,7 +209,9 @@ bool start_shop_mode(short which,short cost_adj,std::string store_name, bool can
return true;
}
// This is for the legacy "Go Back" button behavior, and for some reason doesn't always work:
static void update_last_talk(int new_node) {
if(has_feature_flag("talk-go-back", "StackV1")) return;
// Store last node in the Go Back button
for(word_rect_t& word : talk_words) {
if(word.word != "Go Back") continue;
@@ -222,6 +240,7 @@ void end_shop_mode() {
if(store_pre_shop_mode == MODE_TALKING) {
save_talk_str1 = "You conclude your business.";
save_talk_str2 = "";
can_save_talk = false;
place_talk_str(save_talk_str1, save_talk_str2, 0, dummy_rect);
update_last_talk(TALK_BUSINESS);
}else{
@@ -633,6 +652,13 @@ void set_up_shop_array() {
}
}
std::vector<std::string> preset_words = {
"Look", "Name", "Job",
"Buy", "Sell", "Record",
"Done", "Go Back",
"Ask About...",
};
static void reset_talk_words() {
// first initialise talk_words here
talk_words.clear();
@@ -642,12 +668,6 @@ static void reset_talk_words() {
{210, 389}, {190, 366},
{4, 343}
};
static const std::vector<std::string> preset_words = {
"Look", "Name", "Job",
"Buy", "Sell", "Record",
"Done", "Go Back",
"Ask About...",
};
TextStyle style;
style.font = FONT_DUNGEON;
style.pointSize = TALK_WORD_SIZE;
@@ -655,6 +675,8 @@ static void reset_talk_words() {
// Place buttons at bottom.
for(short i = 0; i < preset_words.size(); i++) {
if(talk_end_forced && i != 6 && i != 5) continue;
if(!can_save_talk && i == 5) continue;
if(has_feature_flag("talk-go-back", "StackV1") && dialog_history.size() < 2 && i == 7) continue;
std::string word = preset_words[i];
location tl = preset_word_locs[i];
@@ -682,6 +704,7 @@ static void reset_talk_words() {
}
void start_talk_mode(short m_num,short personality,mon_num_t monst_type,short store_face_pic) {
dialog_history.clear();
rectangle area_rect;
store_personality = personality;
@@ -705,6 +728,7 @@ void start_talk_mode(short m_num,short personality,mon_num_t monst_type,short st
overall_mode = MODE_TALKING;
talk_end_forced = false;
can_save_talk = true;
reset_talk_words();
stat_screen_mode = MODE_SHOP;
@@ -713,9 +737,9 @@ void start_talk_mode(short m_num,short personality,mon_num_t monst_type,short st
// Bring up and place first strings.
save_talk_str1 = univ.town.cur_talk().people[personality % 10].look;
save_talk_str2 = "";
can_save_talk = true;
place_talk_str(save_talk_str1, "", 0, dummy_rect);
dialog_history.push_back({true, false, -1, {save_talk_str1, save_talk_str2}});
put_item_screen(stat_window);
give_help(5,6);
@@ -799,12 +823,10 @@ static void show_job_bank(int which_bank, std::string title) {
job_dlg.run();
}
void handle_talk_node(int which_talk_entry) {
void handle_talk_node(int which_talk_entry, bool is_redo) {
if(which_talk_entry == TALK_DUNNO)
return;
reset_talk_words();
short get_pc,s1 = -1,s2 = -1;
char asked[4];
@@ -817,35 +839,32 @@ void handle_talk_node(int which_talk_entry) {
save_talk_str1 = univ.town.cur_talk().people[store_personality % 10].dunno;
save_talk_str2 = "";
if(save_talk_str1.length() < 2) save_talk_str1 = "You get no response.";
place_talk_str(save_talk_str1, "", 0, dummy_rect);
update_last_talk(TALK_DUNNO);
return;
goto FINISH_TALK_NODE;
case TALK_BUSINESS: // This one only reachable via "go back".
place_talk_str("You conclude your business.", "", 0, dummy_rect);
can_save_talk = false;
save_talk_str1 = "You conclude your business.";
save_talk_str2 = "";
update_last_talk(TALK_BUSINESS);
return;
goto FINISH_TALK_NODE;
case TALK_LOOK:
SPECIAL_LOOK:
save_talk_str1 = univ.town.cur_talk().people[store_personality % 10].look;
save_talk_str2 = "";
place_talk_str(save_talk_str1, "", 0, dummy_rect);
update_last_talk(TALK_LOOK);
return;
goto FINISH_TALK_NODE;
case TALK_NAME:
SPECIAL_NAME:
save_talk_str1 = univ.town.cur_talk().people[store_personality % 10].name;
save_talk_str2 = "";
place_talk_str(save_talk_str1, "", 0, dummy_rect);
update_last_talk(TALK_NAME);
return;
goto FINISH_TALK_NODE;
case TALK_JOB:
SPECIAL_JOB:
save_talk_str1 = univ.town.cur_talk().people[store_personality % 10].job;
save_talk_str2 = "";
place_talk_str(save_talk_str1, "", 0, dummy_rect);
update_last_talk(TALK_JOB);
return;
goto FINISH_TALK_NODE;
case TALK_BUY:
SPECIAL_BUY:
which_talk_entry = scan_for_response("purc");
@@ -862,11 +881,6 @@ void handle_talk_node(int which_talk_entry) {
if(which_talk_entry == -1) goto SPECIAL_DUNNO;
break;
case TALK_RECORD:
if(!can_save_talk) {
// TODO the button shouldn't be shown if it won't work, no?
play_sound(1);
return;
}
if(univ.party.save_talk(univ.town->talking.people[store_personality%10].title, univ.town->name, save_talk_str1, save_talk_str2)) {
give_help(57,0);
play_sound(0);
@@ -878,8 +892,27 @@ void handle_talk_node(int which_talk_entry) {
SPECIAL_DONE:
end_talk_mode();
return;
case TALK_BACK: // only if there's nothing to go back to
return; // so, there's nothing to do here
case TALK_BACK:
// In legacy behavior, this should only happen if there's nothing to go back to. In that case, do nothing.
if(has_feature_flag("talk-go-back", "StackV1")){
if(!dialog_history.empty()){
dialog_history_t last_state;
do{
last_state = dialog_history.back();
dialog_history.pop_back();
if(last_state.special_node){
handle_talk_node(last_state.node, true);
return;
}
// If they ended up in the same node, don't treat it as a step back until we get to different text.
}while(!dialog_history.empty() && save_talk_str1 == last_state.text.first && save_talk_str2 == last_state.text.second);
can_save_talk = last_state.can_record;
save_talk_str1 = last_state.text.first;
save_talk_str2 = last_state.text.second;
goto FINISH_TALK_NODE;
}
}
return;
case TALK_ASK: // ask about
save_talk_str1 = get_text_response("Ask about what?", 8);
strncpy(asked, save_talk_str1.c_str(), 4);
@@ -953,13 +986,13 @@ void handle_talk_node(int which_talk_entry) {
save_talk_str2 = "";
break;
case eTalkNode::TRAINING:
save_talk_str1 = "You conclude your training.";
save_talk_str2 = "";
can_save_talk = false;
if((get_pc = select_pc(eSelectPC::ONLY_LIVING,"Train who?")) < 6) {
can_save_talk = false;
spend_xp(get_pc,1, nullptr);
}
save_talk_str1 = "You conclude your training.";
return;
goto RECORD_WHICH_NODE;
case eTalkNode::SHOP:
if(!start_shop_mode(b,a,save_talk_str1,true)){
if(!start_shop_mode_other_pc()){
@@ -976,7 +1009,7 @@ void handle_talk_node(int which_talk_entry) {
}else{
can_save_talk = false;
}
return;
goto RECORD_WHICH_NODE;
case eTalkNode::JOB_BANK:
if(a < univ.party.job_banks.size() && univ.party.job_banks[a].anger >= 50) {
save_talk_str1 = save_talk_str2;
@@ -984,26 +1017,26 @@ void handle_talk_node(int which_talk_entry) {
break;
} else {
show_job_bank(a, save_talk_str1.c_str());
return;
goto RECORD_WHICH_NODE;
}
case eTalkNode::SELL_WEAPONS:
can_save_talk = false;
stat_screen_mode = MODE_SELL_WEAP;
put_item_screen(stat_window);
give_help(42,43);
break;
goto RECORD_WHICH_NODE;
case eTalkNode::SELL_ARMOR:
can_save_talk = false;
stat_screen_mode = MODE_SELL_ARMOR;
put_item_screen(stat_window);
give_help(42,43);
break;
goto RECORD_WHICH_NODE;
case eTalkNode::SELL_ITEMS:
can_save_talk = false;
stat_screen_mode = MODE_SELL_ANY;
put_item_screen(stat_window);
give_help(42,43);
break;
goto RECORD_WHICH_NODE;
case eTalkNode::IDENTIFY:
stat_screen_mode = MODE_IDENTIFY,
give_help(44, 0);
@@ -1020,7 +1053,7 @@ void handle_talk_node(int which_talk_entry) {
shop_recharge_limit = b;
shop_recharge_amount = c;
put_item_screen(stat_window);
break;
goto RECORD_WHICH_NODE;
case eTalkNode::BUY_INFO:
if(univ.party.gold < a) {
save_talk_str1 = save_talk_str2;
@@ -1087,12 +1120,11 @@ void handle_talk_node(int which_talk_entry) {
univ.party.gold -= a;
put_pc_screen();
iter->property = false;
save_talk_str2 = "";
} else {
save_talk_str1 = "There are no horses left.";
save_talk_str2 = "";
can_save_talk = false;
}
save_talk_str2 = "";
}
break;
case eTalkNode::BUY_SPEC_ITEM:
@@ -1132,7 +1164,7 @@ void handle_talk_node(int which_talk_entry) {
break;
case eTalkNode::BUY_TOWN_LOC:
if(univ.scenario.towns[b]->can_find) {
// TODO: Uh, is something supposed to happen here?
save_talk_str1 = "You've already learned that.";
}
else if(univ.party.gold < a) {
save_talk_str1 = save_talk_str2;
@@ -1192,9 +1224,20 @@ void handle_talk_node(int which_talk_entry) {
put_item_screen(stat_window);
break;
}
if(false){
RECORD_WHICH_NODE:
if(!is_redo) dialog_history.push_back({false, true, which_talk_entry, {"", ""}});
place_talk_str(save_talk_str1,save_talk_str2,0,dummy_rect);
reset_talk_words();
return;
}
FINISH_TALK_NODE:
dialog_history.push_back({can_save_talk, false, -1, {save_talk_str1, save_talk_str2}});
place_talk_str(save_talk_str1,save_talk_str2,0,dummy_rect);
reset_talk_words();
return;
}
bool handle_talk_event(location p, cFramerateLimiter& fps_limiter) {

View File

@@ -13,7 +13,7 @@ void handle_info_request(int what_picked);
void set_up_shop_array();
void start_talk_mode(short m_num,short personality,mon_num_t monst_type,short store_face_pic);
void end_talk_mode();
void handle_talk_node(int which_talk_entry);
void handle_talk_node(int which_talk_entry, bool is_redo = false);
bool handle_talk_event(location p, cFramerateLimiter& fps_limiter);
void handle_talk_spec(short ttype,char* place_string1,char* place_string2);
void store_responses();

View File

@@ -114,6 +114,8 @@ std::map<std::string,std::vector<std::string>> feature_flags = {
// New in-game save file picker
{"file-picker-dialog", {"V1"}},
{"scenario-meta-format", {"V2"}},
// Talk mode
{"talk-go-back", {"StackV1"}},
// Bugs required for several VoDT test replays to run faithfully
{"empty-wandering-monster-bug", {"fixed"}},
{"too-many-extra-wandering-monsters-bug", {"fixed"}}

View File

@@ -941,6 +941,8 @@ void click_talk_rect(word_rect_t word) {
place_talk_face();
}
extern std::vector<std::string> preset_words;
// color 0 - regular 1 - darker
void place_talk_str(std::string str_to_place,std::string str_to_place2,short color,rectangle c_rect) {
rectangle area_rect;
@@ -981,9 +983,12 @@ void place_talk_str(std::string str_to_place,std::string str_to_place2,short col
style.colour = PRESET_WORD_OFF;
else
style.colour = PRESET_WORD_ON;
for(short i = 0; i < 9; i++) {
if(!talk_end_forced || i == 6 || i == 5)
int i = 0;
for(std::string preset : preset_words) {
if(talk_words[i].word == preset){
win_draw_string(talk_gworld(),talk_words[i].rect,talk_words[i].word,eTextMode::LEFT_TOP,style);
i++;
}
}
style.colour = Colours::NAVY;