From 1fce7fd6ffccb361cb7cf9a3b889db1e3046f869 Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Sun, 27 Oct 2024 20:43:30 +0300 Subject: [PATCH] Engine: fix RunDialog command spawning a nested dialog state This fixes RunDialog script command (or Dialog.Start) spawning a nested dialog state if called within a dialog option script. Something that it should not be doing. Instead, check if we are inside the dialog, and override a option script's return value, which dialog executor will handle upon receiving control back. --- Engine/ac/dialog.cpp | 22 ++++++++++++++++++++++ Engine/ac/dialog.h | 4 ++++ Engine/script/script.cpp | 13 ++++++++++--- Engine/script/script.h | 4 ++-- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/Engine/ac/dialog.cpp b/Engine/ac/dialog.cpp index 00b970307d3..73c63dbd772 100644 --- a/Engine/ac/dialog.cpp +++ b/Engine/ac/dialog.cpp @@ -1425,6 +1425,13 @@ void DialogExec::Run() void do_conversation(int dlgnum) { + assert(dialogExec == nullptr); + if (dialogExec) + { + Debug::Printf(kDbgMsg_Error, "ERROR: tried to start a new dialog state while a dialog state is running."); + return; + } + EndSkippingUntilCharStops(); // AGS 2.x always makes the mouse cursor visible when displaying a dialog. @@ -1449,6 +1456,21 @@ void do_conversation(int dlgnum) dialogExec = {}; } +bool is_in_dialog() +{ + return dialogExec != nullptr; +} + +// NOTE: this is ugly, but I could not come to a better solution at the time... +void set_dialog_option_result(int dlgopt_result) +{ + assert(dialogExec && dialogScriptsInst); + if (!dialogExec || !dialogScriptsInst) + return; + + dialogScriptsInst->returnValue = dlgopt_result; +} + bool handle_state_change_in_dialog_request(const char *apiname, int dlgreq_retval, bool expect_dialog_request) { // Test if we are inside a dialog state AND dialog_request callback diff --git a/Engine/ac/dialog.h b/Engine/ac/dialog.h index 0788d3e0547..7e48ca0d3f1 100644 --- a/Engine/ac/dialog.h +++ b/Engine/ac/dialog.h @@ -30,6 +30,10 @@ void Dialog_Start(ScriptDialog *sd); // Starts a dialog void do_conversation(int dlgnum); +// Tells if the game is currently running a dialog +bool is_in_dialog(); +// Assigns a return value to pass to the dialog state after current option's script have finished executing +void set_dialog_option_result(int dlgopt_result); // Displays dialog options, and returns the chosen number, or CHOSE_TEXTPARSER if parser input was activated int show_dialog_options(int dlgnum, bool runGameLoopsInBackground); // Handles a dialog option, optionally "sais" its text, optionally run corresponding dialog script's entry diff --git a/Engine/script/script.cpp b/Engine/script/script.cpp index 587588a3c57..64a362d7a25 100644 --- a/Engine/script/script.cpp +++ b/Engine/script/script.cpp @@ -626,7 +626,8 @@ String make_interact_func_name(const String &base, int param, int subd) return fname; } -void post_script_cleanup() { +void post_script_cleanup() +{ // should do any post-script stuff here, like go to new room if (cc_has_error()) quit(cc_get_error().ErrorString); @@ -645,7 +646,6 @@ void post_script_cleanup() { else { curscript = nullptr; } - // if (abort_executor) user_disabled_data2=aborted_ip; int old_room_number = displayed_room; @@ -687,7 +687,14 @@ void post_script_cleanup() { load_new_game = thisData; return; case ePSARunDialog: - do_conversation(thisData); + if (is_in_dialog()) + { + set_dialog_option_result(thisData); + } + else + { + do_conversation(thisData); + } break; case ePSARestartGame: cancel_all_scripts(); diff --git a/Engine/script/script.h b/Engine/script/script.h index a88296b82f3..f32df75c2c2 100644 --- a/Engine/script/script.h +++ b/Engine/script/script.h @@ -170,8 +170,8 @@ String GetScriptName(ccInstance *sci); // Makes a old-style interaction function name (for interaction list "run script" command) String make_interact_func_name(const String &base, int param, int subd); // Performs various updates to the game after script interpreter returns control to the engine. -// Executes actions and does changes that are not executed immediately at script command, for -// optimisation and other reasons. +// Executes scheduled commands and does changes that are not executed immediately during script +// (either for logical reasons, and for optimization). void post_script_cleanup(); void quit_with_script_error(const String &fn_name); int get_nivalue (InteractionCommandList *nic, int idx, int parm);