diff --git a/Engine/ac/dialog.cpp b/Engine/ac/dialog.cpp index 9b9d79f74ed..00b970307d3 100644 --- a/Engine/ac/dialog.cpp +++ b/Engine/ac/dialog.cpp @@ -175,11 +175,39 @@ const char *Dialog_GetScriptName(ScriptDialog *sd) } //============================================================================= +// dialog manager stuff #define RUN_DIALOG_STAY -1 #define RUN_DIALOG_STOP_DIALOG -2 #define RUN_DIALOG_GOTO_PREVIOUS -4 -// dialog manager stuff + +static int run_dialog_request(int parmtr) +{ + play.stop_dialog_at_end = DIALOG_RUNNING; + RuntimeScriptValue params[]{ parmtr }; + RunScriptFunction(gameinst.get(), "dialog_request", 1, params); + + if (play.stop_dialog_at_end == DIALOG_STOP) + { + play.stop_dialog_at_end = DIALOG_NONE; + return -2; + } + if (play.stop_dialog_at_end >= DIALOG_NEWTOPIC) + { + int tval = play.stop_dialog_at_end - DIALOG_NEWTOPIC; + play.stop_dialog_at_end = DIALOG_NONE; + return tval; + } + if (play.stop_dialog_at_end >= DIALOG_NEWROOM) + { + int roomnum = play.stop_dialog_at_end - DIALOG_NEWROOM; + play.stop_dialog_at_end = DIALOG_NONE; + NewRoom(roomnum); + return -2; + } + play.stop_dialog_at_end = DIALOG_NONE; + return -1; +} void get_dialog_script_parameters(unsigned char* &script, unsigned short* param1, unsigned short* param2) { @@ -1292,11 +1320,18 @@ struct DialogExec int DlgWas = -1; // CHECKME: this may be unnecessary, investigate later bool IsFirstEntry = true; - // nested dialogs "stack" + // Dialog topics history, used by "goto-previous" command std::stack TopicHist; int ExecutedOption = -1; // option which is currently run (or -1) bool AreOptionsDisplayed = false; // if dialog options are displayed on screen + // A position in script saved by certain API function calls in "dialog_request" callback; + // used purely for error reporting when the script has 2+ calls to gamestate-changing + // functions such as StartDialog or ChangeRoom. + // FIXME: this is horrible, review this and make consistent with error reporting + // for regular calls in normal script. + ScriptPosition SavedDialogRequestScriptPos; + DialogExec(int start_dlgnum) : DlgNum(start_dlgnum) {} int HandleDialogResult(int res); void Run(); @@ -1414,6 +1449,32 @@ void do_conversation(int dlgnum) dialogExec = {}; } +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 + if ((dialogExec == nullptr) || (play.stop_dialog_at_end == DIALOG_NONE)) + { + // Some command may only work inside dialog_request (?) + if (expect_dialog_request) + debug_script_warn("%s: not in a dialog_request(), ignored", apiname); + return false; // not handled, process command as normal + } + + // Test if dialog result was not set yet + if (play.stop_dialog_at_end == DIALOG_RUNNING) + { + play.stop_dialog_at_end = dlgreq_retval; + get_script_position(dialogExec->SavedDialogRequestScriptPos); + } + else + { + debug_script_warn("!%s: more than one NewRoom/RunDialog/StopDialog requests within a dialog '%s' (%d), following one(s) will be ignored\n\tfirst was made in \"%s\", line %d", + apiname, game.dialogScriptNames[dialogExec->DlgNum].GetCStr(), dialogExec->DlgNum, + dialogExec->SavedDialogRequestScriptPos.Section.GetCStr(), dialogExec->SavedDialogRequestScriptPos.Line); + } + return true; // handled, state change will be taken care of by a dialog script +} + // end dialog manager diff --git a/Engine/ac/dialog.h b/Engine/ac/dialog.h index de66851b1f4..0788d3e0547 100644 --- a/Engine/ac/dialog.h +++ b/Engine/ac/dialog.h @@ -34,6 +34,10 @@ void do_conversation(int dlgnum); int show_dialog_options(int dlgnum, bool runGameLoopsInBackground); // Handles a dialog option, optionally "sais" its text, optionally run corresponding dialog script's entry int run_dialog_option(int dlgnum, int dialog_choice, int sayChosenOption, bool run_script); +// Handles a game-state changing command (such as StartDialog) inside "dialog_request" callback. +// Returns whether the change was handled in "dialog's way", and further processing is not necessary. +// Otherwise should process the command as normal. +bool handle_state_change_in_dialog_request(const char *apiname, int dlgreq_retval, bool expect_dialog_request = false); extern std::vector scrDialog; extern std::vector dialog; diff --git a/Engine/ac/gamestate.cpp b/Engine/ac/gamestate.cpp index 3f328b0c4d3..ba0fa580ed9 100644 --- a/Engine/ac/gamestate.cpp +++ b/Engine/ac/gamestate.cpp @@ -531,7 +531,7 @@ void GamePlayState::ReadFromSavegame(Stream *in, GameDataVersion data_ver, GameS show_single_dialog_option = in->ReadInt32(); keep_screen_during_instant_transition = in->ReadInt32(); read_dialog_option_colour = in->ReadInt32(); - stop_dialog_at_end = in->ReadInt32(); + in->ReadInt32(); // was stop_dialog_at_end, which should not serialize speech_portrait_placement = in->ReadInt32(); speech_portrait_x = in->ReadInt32(); speech_portrait_y = in->ReadInt32(); @@ -726,7 +726,7 @@ void GamePlayState::WriteForSavegame(Stream *out) const out->WriteInt32(show_single_dialog_option); out->WriteInt32(keep_screen_during_instant_transition); out->WriteInt32(read_dialog_option_colour); - out->WriteInt32(stop_dialog_at_end); + out->WriteInt32(0); // was stop_dialog_at_end, which should not serialize out->WriteInt32(speech_portrait_placement); out->WriteInt32(speech_portrait_x); out->WriteInt32(speech_portrait_y); diff --git a/Engine/ac/gamestate.h b/Engine/ac/gamestate.h index e8301f2c308..08ffc3bc30f 100644 --- a/Engine/ac/gamestate.h +++ b/Engine/ac/gamestate.h @@ -173,6 +173,10 @@ struct GamePlayState int show_single_dialog_option = 0; int keep_screen_during_instant_transition = 0; int read_dialog_option_colour = 0; + // stop_dialog_at_end - special value that is not supposed to be used in user script, + // but used in a generated dialogscript (when converted from dialog script slang) + // to tell how to proceed after "run-script" command ("dialog_request" callback). + // if non-0 - means that we are inside "dialog_request" callback. int stop_dialog_at_end = 0; int speech_portrait_placement = 0; // speech portrait placement mode (automatic/custom) int speech_portrait_x = 0; // a speech portrait x offset from corresponding screen side diff --git a/Engine/ac/global_dialog.cpp b/Engine/ac/global_dialog.cpp index d07ca68357d..9d28c9d8098 100644 --- a/Engine/ac/global_dialog.cpp +++ b/Engine/ac/global_dialog.cpp @@ -27,23 +27,15 @@ using namespace AGS::Common; extern GameSetupStruct game; -ScriptPosition last_in_dialog_request_script_pos; -void RunDialog(int tum) { +void RunDialog(int tum) +{ if ((tum<0) | (tum>=game.numdialog)) quit("!RunDialog: invalid topic number specified"); can_run_delayed_command(); - if (play.stop_dialog_at_end != DIALOG_NONE) { - if (play.stop_dialog_at_end == DIALOG_RUNNING) - play.stop_dialog_at_end = DIALOG_NEWTOPIC + tum; - else - quitprintf("!RunDialog: two NewRoom/RunDialog/StopDialog requests within dialog; last was called in \"%s\", line %d", - last_in_dialog_request_script_pos.Section.GetCStr(), last_in_dialog_request_script_pos.Line); - return; - } - - get_script_position(last_in_dialog_request_script_pos); + if (handle_state_change_in_dialog_request("RunDialog", DIALOG_NEWTOPIC + tum)) + return; // handled if (inside_script) get_executingscript()->QueueAction(PostScriptAction(ePSARunDialog, tum, "RunDialog")); @@ -51,15 +43,9 @@ void RunDialog(int tum) { do_conversation(tum); } - -void StopDialog() { - if (play.stop_dialog_at_end == DIALOG_NONE) { - debug_script_warn("StopDialog called, but was not in a dialog"); - debug_script_log("StopDialog called but no dialog"); - return; - } - get_script_position(last_in_dialog_request_script_pos); - play.stop_dialog_at_end = DIALOG_STOP; +void StopDialog() +{ + handle_state_change_in_dialog_request("StopDialog", DIALOG_STOP, true); } void SetDialogOption(int dlg, int opt, int onoroff, bool dlg_script) diff --git a/Engine/ac/global_room.cpp b/Engine/ac/global_room.cpp index caadec7325a..d3e037230c0 100644 --- a/Engine/ac/global_room.cpp +++ b/Engine/ac/global_room.cpp @@ -15,6 +15,7 @@ #include "ac/common.h" #include "ac/character.h" #include "ac/characterinfo.h" +#include "ac/dialog.h" #include "ac/draw.h" #include "ac/event.h" #include "ac/gamesetupstruct.h" @@ -68,7 +69,6 @@ void SetAmbientLightLevel(int light_level) play.rtint_light = light_level; } -extern ScriptPosition last_in_dialog_request_script_pos; void NewRoom(int nrnum) { if (nrnum < 0) quitprintf("!NewRoom: room change requested to invalid room number %d.", nrnum); @@ -85,17 +85,8 @@ void NewRoom(int nrnum) { can_run_delayed_command(); - if (play.stop_dialog_at_end != DIALOG_NONE) { - if (play.stop_dialog_at_end == DIALOG_RUNNING) - play.stop_dialog_at_end = DIALOG_NEWROOM + nrnum; - else { - quitprintf("!NewRoom: two NewRoom/RunDialog/StopDialog requests within dialog; last was called in \"%s\", line %d", - last_in_dialog_request_script_pos.Section.GetCStr(), last_in_dialog_request_script_pos.Line); - } - return; - } - - get_script_position(last_in_dialog_request_script_pos); + if (handle_state_change_in_dialog_request("NewRoom", DIALOG_NEWROOM + nrnum)) + return; // handled if (in_leaves_screen >= 0) { // NewRoom called from the Player Leaves Screen event -- just diff --git a/Engine/ac/runtime_defines.h b/Engine/ac/runtime_defines.h index 986bf54b449..859866ccf29 100644 --- a/Engine/ac/runtime_defines.h +++ b/Engine/ac/runtime_defines.h @@ -30,11 +30,6 @@ #define MAXGSVALUES 500 #define MAXGLOBALSTRINGS 51 #define MAX_INVORDER 500 -#define DIALOG_NONE 0 -#define DIALOG_RUNNING 1 -#define DIALOG_STOP 2 -#define DIALOG_NEWROOM 100 -#define DIALOG_NEWTOPIC 12000 #define MAX_TIMERS 21 #define MAX_PARSED_WORDS 15 @@ -122,10 +117,19 @@ const int LegacyRoomVolumeFactor = 30; // a 1-based movelist index offset for characters #define CHMLSOFFS (1 + MAX_ROOM_OBJECTS) #define MAX_SCRIPT_AT_ONCE 10 + #define EVENT_NONE 0 #define EVENT_INPROGRESS 1 #define EVENT_CLAIMED 2 +// Values for GameState::stop_dialog_at_end; +// tell what to do with the current Dialog after returning from the option script +#define DIALOG_NONE 0 +#define DIALOG_RUNNING 1 +#define DIALOG_STOP 2 +#define DIALOG_NEWROOM 100 +#define DIALOG_NEWTOPIC 12000 + // Internal skip style flags, for speech/display, wait; // theoretically correspond to InputType in script (with a 24-bit shift) #define SKIP_NONE 0x00 diff --git a/Engine/debug/debugger.h b/Engine/debug/debugger.h index f12d06360e1..bb56a75f64d 100644 --- a/Engine/debug/debugger.h +++ b/Engine/debug/debugger.h @@ -29,8 +29,6 @@ extern int break_on_next_script_step; int check_for_messages_from_debugger(); bool send_state_to_debugger(const char *msg); bool send_exception_to_debugger(const char *qmsg); -// Returns current script's location and callstack -AGS::Common::String get_cur_script(int numberOfLinesOfCallStack); bool get_script_position(ScriptPosition &script_pos); void check_debug_keys(); diff --git a/Engine/script/script.cpp b/Engine/script/script.cpp index a452cd031d8..587588a3c57 100644 --- a/Engine/script/script.cpp +++ b/Engine/script/script.cpp @@ -89,30 +89,6 @@ size_t numScriptModules = 0; static bool DoRunScriptFuncCantBlock(ccInstance *sci, NonBlockingScriptFunction* funcToRun, bool hasTheFunc); -int run_dialog_request (int parmtr) { - play.stop_dialog_at_end = DIALOG_RUNNING; - RuntimeScriptValue params[]{ parmtr }; - RunScriptFunction(gameinst.get(), "dialog_request", 1, params); - - if (play.stop_dialog_at_end == DIALOG_STOP) { - play.stop_dialog_at_end = DIALOG_NONE; - return -2; - } - if (play.stop_dialog_at_end >= DIALOG_NEWTOPIC) { - int tval = play.stop_dialog_at_end - DIALOG_NEWTOPIC; - play.stop_dialog_at_end = DIALOG_NONE; - return tval; - } - if (play.stop_dialog_at_end >= DIALOG_NEWROOM) { - int roomnum = play.stop_dialog_at_end - DIALOG_NEWROOM; - play.stop_dialog_at_end = DIALOG_NONE; - NewRoom(roomnum); - return -2; - } - play.stop_dialog_at_end = DIALOG_NONE; - return -1; -} - void run_function_on_non_blocking_thread(NonBlockingScriptFunction* funcToRun) { update_script_mouse_coords(); diff --git a/Engine/script/script.h b/Engine/script/script.h index 9fa085befc4..a88296b82f3 100644 --- a/Engine/script/script.h +++ b/Engine/script/script.h @@ -93,7 +93,6 @@ struct NonBlockingScriptFunction } }; -int run_dialog_request (int parmtr); void run_function_on_non_blocking_thread(NonBlockingScriptFunction* funcToRun); // TODO: run_interaction_event() and run_interaction_script()