From d4dd94f2ff61d06846500e0616c3e0f9413adf0d Mon Sep 17 00:00:00 2001 From: Ivan Mogilko Date: Tue, 6 Feb 2024 20:48:22 +0300 Subject: [PATCH] Engine: implemented GameLoopUntilState This state runs while the game is in waiting loop, replaces RestrictUntil struct. --- Engine/ac/runtime_defines.h | 4 + Engine/main/game_run.cpp | 201 ++++++++++++++++++++---------------- 2 files changed, 115 insertions(+), 90 deletions(-) diff --git a/Engine/ac/runtime_defines.h b/Engine/ac/runtime_defines.h index cfafd35be70..4eaecaf7ff5 100644 --- a/Engine/ac/runtime_defines.h +++ b/Engine/ac/runtime_defines.h @@ -114,6 +114,10 @@ const int LegacyRoomVolumeFactor = 30; // Overlay parameters #define OVR_AUTOPLACE 30000 +// These are possibly actions scheduled to run after "wait"; +// but only FOR_EXITLOOP is used currently; +// other FOR_* types are deprecated since at least v2.5. +// Judging by the old comment in code, FOR_SCRIPT is for v2.1 and earlier. #define FOR_ANIMATION 1 #define FOR_SCRIPT 2 #define FOR_EXITLOOP 3 diff --git a/Engine/main/game_run.cpp b/Engine/main/game_run.cpp index 4a5d0154d0b..734c71f497a 100644 --- a/Engine/main/game_run.cpp +++ b/Engine/main/game_run.cpp @@ -99,6 +99,7 @@ float fps = std::numeric_limits::quiet_NaN(); static auto t1 = AGS_Clock::now(); // timer for FPS // ... 't1'... how very appropriate.. :) unsigned int loopcounter=0; static unsigned int lastcounter=0; +static size_t numEventsAtStartOfFunction; // CHECKME: research and document this #define UNTIL_ANIMEND 1 #define UNTIL_MOVEEND 2 @@ -110,20 +111,94 @@ static unsigned int lastcounter=0; #define UNTIL_INTISNEG 8 #define UNTIL_ANIMBTNEND 9 -// Following struct instructs the engine to run game loops until +static void GameTick(); + +// Game state instructs the engine to run game loops until // certain condition is not fullfilled. -struct RestrictUntil +class GameLoopUntilState : public GameState { - int type = 0; // type of condition, UNTIL_* constant - int disabled_for = 0; // FOR_* constant +public: + GameLoopUntilState(int untilwhat, const void* data_ptr = nullptr, int data1 = 0, int data2 = 0) + : _untilType(untilwhat) + , _disabledFor(FOR_EXITLOOP) + , _dataPtr(data_ptr) + , _data1(data1) + , _data2(data2) + { + } + + int GetUntilType() const { return _untilType; } + int GetDisabledFor() const { return _disabledFor; } + const void *GetDataPtr() const { return _dataPtr; } + int GetData1() const { return _data1; } + int GetData2() const { return _data2; } + + // Begin the state, initialize and prepare any resources + void Begin() override + { + assert(_disabledFor == FOR_EXITLOOP); + play.disabled_user_interface++; + // If GUI looks change when disabled, then mark all of them for redraw + GUI::MarkAllGUIForUpdate(GUI::Options.DisabledStyle != kGuiDis_Unchanged, true); + + // Only change the mouse cursor if it hasn't been specifically changed first + // (or if it's speech, always change it) + if (((cur_cursor == cur_mode) || (_untilType == UNTIL_NOOVERLAY)) && + (cur_mode != CURS_WAIT)) + { + set_mouse_cursor(CURS_WAIT); + } + } + // End the state, release all resources + void End() override + { + set_our_eip(77); + set_default_cursor(); + // If GUI looks change when disabled, then mark all of them for redraw + GUI::MarkAllGUIForUpdate(GUI::Options.DisabledStyle != kGuiDis_Unchanged, true); + play.disabled_user_interface--; + + switch (_disabledFor) + { + case FOR_EXITLOOP: + break; + // These other types are obsolete since at least v2.5 + // FOR_SCRIPT is for v2.1 and earlier. + // case FOR_ANIMATION: + // run_animation((FullAnimation*)user_disabled_data2,user_disabled_data3); + // break; + // case FOR_SCRIPT: + // break; + default: + quit("Unknown reason to disable user input in the Wait state."); + break; + } + } + // Draw the state + void Draw() override + { + } + // Update the state during a game tick + bool Run() override + { + GameTick(); + return ShouldStayInWaitMode(); + } + +private: + int _untilType = 0; // type of condition, UNTIL_* constant + int _disabledFor = 0; // FOR_* constant // pointer to the test variable - const void *data_ptr = nullptr; + const void *_dataPtr = nullptr; // other values used for a test, depend on type - int data1 = 0; - int data2 = 0; -} restrict_until; + int _data1 = 0; + int _data2 = 0; +}; + +// TODO: this is a global variable, because this state is checked during update; +// find a way to refactor this and not have it here. +std::unique_ptr restrict_until; -static size_t numEventsAtStartOfFunction; static void ProperExit() { @@ -201,7 +276,7 @@ static bool game_loop_check_ground_level_interactions() // if in a Wait loop which is no longer valid (probably // because the Region interaction did a NewRoom), abort // the rest of the loop - if ((restrict_until.type > 0) && (!ShouldStayInWaitMode())) { + if ((restrict_until) && (!ShouldStayInWaitMode())) { // cancel the Rep Exec and Stands on Hotspot events that // we just added -- otherwise the event queue gets huge events.resize(numEventsAtStartOfFunction); @@ -454,7 +529,7 @@ bool run_service_key_controls(KeyInput &out_key) } if (((agskey == eAGSKeyCodeCtrlV) && (ki.Mod & eAGSModAlt) != 0) - && (play.wait_counter < 1) && (play.text_overlay_on == 0) && (restrict_until.type == 0)) { + && (play.wait_counter < 1) && (play.text_overlay_on == 0) && (!restrict_until)) { // make sure we can't interrupt a Wait() // and desync the music to cutscene play.debug_mode++; @@ -999,30 +1074,33 @@ static void UpdateMouseOverLocation() } // Checks if user interface should remain disabled for now +// FIXME: should be a private method of GameLoopUntilState, +// but is called elsewhere for some strange reason; +// investigate and move to GameLoopUntilState. static bool ShouldStayInWaitMode() { - if (restrict_until.type == 0) + if (!restrict_until) quit("end_wait_loop called but game not in loop_until state"); - switch (restrict_until.type) + switch (restrict_until->GetUntilType()) { case UNTIL_MOVEEND: { - short*wkptr = (short*)restrict_until.data_ptr; + short*wkptr = (short*)restrict_until->GetDataPtr(); return !(wkptr[0] < 1); } case UNTIL_CHARIS0: { - char*chptr = (char*)restrict_until.data_ptr; + char*chptr = (char*)restrict_until->GetDataPtr(); return !(chptr[0] == 0); } case UNTIL_NEGATIVE: { - short*wkptr = (short*)restrict_until.data_ptr; + short*wkptr = (short*)restrict_until->GetDataPtr(); return !(wkptr[0] < 0); } case UNTIL_INTISNEG: { - int*wkptr = (int*)restrict_until.data_ptr; + int*wkptr = (int*)restrict_until->GetDataPtr(); return !(wkptr[0] < 0); } case UNTIL_NOOVERLAY: @@ -1031,17 +1109,17 @@ static bool ShouldStayInWaitMode() { } case UNTIL_INTIS0: { - int*wkptr = (int*)restrict_until.data_ptr; + int*wkptr = (int*)restrict_until->GetDataPtr(); return !(wkptr[0] == 0); } case UNTIL_SHORTIS0: { - short*wkptr = (short*)restrict_until.data_ptr; + short*wkptr = (short*)restrict_until->GetDataPtr(); return !(wkptr[0] == 0); } case UNTIL_ANIMBTNEND: { // still animating? - return FindButtonAnimation(restrict_until.data1, restrict_until.data2) >= 0; + return FindButtonAnimation(restrict_until->GetData1(), restrict_until->GetData2()) >= 0; } default: quit("loop_until: unknown until event"); @@ -1050,92 +1128,35 @@ static bool ShouldStayInWaitMode() { return true; // should stay in wait } -static bool UpdateWaitMode() -{ - if (restrict_until.type == 0) { - return true; - } - - if (!ShouldStayInWaitMode()) - restrict_until.type = 0; - set_our_eip(77); - - if (restrict_until.type > 0) { - return true; - } - - auto was_disabled_for = restrict_until.disabled_for; - - set_default_cursor(); - // If GUI looks change when disabled, then mark all of them for redraw - GUI::MarkAllGUIForUpdate(GUI::Options.DisabledStyle != kGuiDis_Unchanged, true); - play.disabled_user_interface--; - restrict_until.disabled_for = 0; - - switch (was_disabled_for) { - // case FOR_ANIMATION: - // run_animation((FullAnimation*)user_disabled_data2,user_disabled_data3); - // break; - case FOR_EXITLOOP: - return false; - case FOR_SCRIPT: - quit("err: for_script obsolete (v2.1 and earlier only)"); - return false; - default: - quit("Unknown user_disabled_for in end restrict_until"); - return false; - } -} - // Run single game iteration; calls UpdateGameOnce() internally -static bool GameTick() +static void GameTick() { if (displayed_room < 0) quit("!A blocking function was called before the first room has been loaded"); UpdateGameOnce(true); UpdateMouseOverLocation(); - - set_our_eip(76); - - return UpdateWaitMode(); -} - -static void SetupLoopParameters(int untilwhat, const void* data_ptr = nullptr, int data1 = 0, int data2 = 0) { - play.disabled_user_interface++; - // If GUI looks change when disabled, then mark all of them for redraw - GUI::MarkAllGUIForUpdate(GUI::Options.DisabledStyle != kGuiDis_Unchanged, true); - - // Only change the mouse cursor if it hasn't been specifically changed first - // (or if it's speech, always change it) - if (((cur_cursor == cur_mode) || (untilwhat == UNTIL_NOOVERLAY)) && - (cur_mode != CURS_WAIT)) - set_mouse_cursor(CURS_WAIT); - - restrict_until.type = untilwhat; - restrict_until.data_ptr = data_ptr; - restrict_until.data1 = data1; - restrict_until.data2 = data2; - restrict_until.disabled_for = FOR_EXITLOOP; } // This function is called from lot of various functions // in the game core, character, room object etc static void GameLoopUntilEvent(int untilwhat, const void* data_ptr = nullptr, int data1 = 0, int data2 = 0) { - // blocking cutscene - end skipping - EndSkippingUntilCharStops(); + // blocking cutscene - end skipping + EndSkippingUntilCharStops(); - // this function can get called in a nested context, so - // remember the state of these vars in case a higher level - // call needs them - auto cached_restrict_until = restrict_until; + // this function can get called in a nested context, so + // remember the state of these vars in case a higher level + // call needs them + std::unique_ptr cached_restrict_until = std::move(restrict_until); - SetupLoopParameters(untilwhat, data_ptr, data1, data2); - while (GameTick()); + restrict_until.reset(new GameLoopUntilState(untilwhat, data_ptr, data1, data2)); + restrict_until->Begin(); + while (restrict_until->Run()); + restrict_until->End(); - set_our_eip(78); + set_our_eip(78); - restrict_until = cached_restrict_until; + restrict_until = std::move(cached_restrict_until); } void GameLoopUntilValueIsZero(const char *value)