From e6fcffb5ac2b2775cd5c35cb3db9b21e0c265003 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Thu, 14 May 2026 13:58:00 +0200 Subject: [PATCH] Implement Partial Maniac Patch game option commands Added support for various Maniac Patch runtime engine settings via a new command handler in Game_Interpreter. Introduced new Game_System methods for pause screen when focus lost, fatal mix settings, fast-forward text, frame skip mode and message mouse disabling. Updated Player and Window_Message to respect these options, including frame skipping and fast-forwarding message text. missing features are: battle origin, and message face size. --- src/game_interpreter.cpp | 82 ++++++++++++++++++++++++++++++++++++---- src/game_system.cpp | 1 - src/game_system.h | 58 ++++++++++++++++++++++++++++ src/player.cpp | 23 ++++++++++- src/player.h | 6 +++ src/window_message.cpp | 21 +++++++++- 6 files changed, 181 insertions(+), 10 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index ac1285ba67..dd2a7c9c88 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5007,17 +5007,85 @@ bool Game_Interpreter::CommandManiacSetGameOption(lcf::rpg::EventCommand const& return true; } - int operation = com.parameters[1]; - //int value = ValueOrVariable(com.parameters[0], com.parameters[2]); + const auto option_type = com.parameters[1]; - switch (operation) { - case 2: // Change Picture Limit (noop, we support arbitrary amount of pictures) + switch (option_type) { + case 0: { // .runWhenInactive or .pauseWhenInactive + const bool run_when_inactive = (ValueOrVariable(com.parameters[0], com.parameters[2]) != 0); + + if (DisplayUi) { + DisplayUi->SetPauseWhenFocusLost(!run_when_inactive); + } + return true; + } + case 1: { // FatalMix: .fatal fps, testPlayMode, fastForwardText + const int fps = ValueOrVariableBitfield(com, 0, 0, 2); + const int test_play_mode = com.parameters[3] & 3; // 0: Keep, 1: Off, 2: On + const bool enable_fast_forward_text = (com.parameters[3] & 16) != 0; + + if (fps > 0) { + Game_Clock::SetGameSpeedFactor(static_cast(fps) / 60.0f); + } + + switch (test_play_mode) { + case 1: + Player::debug_flag = false; break; - default: - Output::Warning("Maniac SetGameOption: Operation {} not supported", operation); + case 2: + Player::debug_flag = true; + break; + } + + Main_Data::game_system->SetFastForwardText(enable_fast_forward_text); + return true; + } + case 2: { // .picLimit limit + // Change Picture Limit (noop, we support arbitrary amount of pictures) + return true; + } + case 3: { // .fullFrame, .oneFifth, .oneThird, .oneHalf + // Frame Skip + const int skip_mode = com.parameters[2]; + Player::SetFrameSkip(skip_mode); + return true; + } + case 4: { // .mouse.disableMsgProcession value + // Left Mouse button can continue messages + const bool disable_mouse_for_messages = (ValueOrVariableBitfield(com, 0, 0, 2) != 0); + Main_Data::game_system->SetMessageMouseDisabled(disable_mouse_for_messages); + return true; } + case 5: { // .btlOrigin position + // Battle UI position + // 0: center, 1: topLeft, 2: bottomLeft, 3: topRight, 4: bottomRight, + // 5: top, 6: bottom, 7: left, 8: right + const int battle_origin = com.parameters[2]; - return true; + // TODO: Store this value, likely in `Game_System`, and have `Scene_Battle` use it + // to adjust the layout of its windows and sprites during initialization. + Output::Warning("Maniac SetGameOption: Reposition Battle UI (position: {}) not implemented.", battle_origin); + Main_Data::game_system->SetBattleOrigin(battle_origin); + return true; + } + case 6: { // .animLimit limit + // Battle Animation Limit + Output::Warning("Maniac SetGameOption: Battle Animation limit not implemented."); + return true; + } + case 7: { // .winFaceSize width, height + const int width = ValueOrVariableBitfield(com, 0, 0, 2); + const int height = ValueOrVariableBitfield(com, 0, 1, 3); + + // TODO: Store these values in Game_System (e.g., `message_face_width`/`height`). + // `Window_Message::Refresh` must then use these values when drawing the face graphic. + Output::Warning("Maniac SetGameOption: Custom WinFaceSize ({}x{}) not implemented.", width, height); + Main_Data::game_system->SetMessageFaceSize(width, height); + return true; + } + default: + Output::Warning("Maniac SetGameOption: Unknown Operation {}", static_cast(option_type)); + return true; + } } bool Game_Interpreter::CommandManiacControlStrings(lcf::rpg::EventCommand const& com) { diff --git a/src/game_system.cpp b/src/game_system.cpp index 3482f7f07c..eb1fc0f777 100644 --- a/src/game_system.cpp +++ b/src/game_system.cpp @@ -648,4 +648,3 @@ bool Game_System::IsMessageTransparent() { return data.message_transparent != 0; } - diff --git a/src/game_system.h b/src/game_system.h index a15e006c83..1bf97e23af 100644 --- a/src/game_system.h +++ b/src/game_system.h @@ -436,6 +436,19 @@ class Game_System { /** @return Whether the game was loaded from a savegame in the current frame */ bool IsLoadedThisFrame() const; + void SetFastForwardText(bool enabled); + bool GetFastForwardText() const; + + void SetMessageMouseDisabled(bool disabled); + bool IsMessageMouseDisabled() const; + + void SetBattleOrigin(int origin); + int GetBattleOrigin() const; + + void SetMessageFaceSize(int width, int height); + int GetMessageFaceWidth() const; + int GetMessageFaceHeight() const; + private: std::string InelukiReadLink(Filesystem_Stream::InputStream& stream); @@ -453,6 +466,10 @@ class Game_System { Color bg_color = Color{ 0, 0, 0, 255 }; bool bgm_pending = false; int loaded_frame_count = 0; + + // Game options that are not saved + bool maniac_fast_forward_text = false; + bool message_mouse_disabled = false; }; inline bool Game_System::HasSystemGraphic() { @@ -662,5 +679,46 @@ inline bool Game_System::GetAllowMenu() { return data.menu_allowed; } +inline void Game_System::SetFastForwardText(bool enabled) { + maniac_fast_forward_text = enabled; +} + +inline bool Game_System::GetFastForwardText() const { + return maniac_fast_forward_text; +} + +inline void Game_System::SetMessageMouseDisabled(bool disabled) { + message_mouse_disabled = disabled; +} + +inline bool Game_System::IsMessageMouseDisabled() const { + return message_mouse_disabled; +} + +inline void Game_System::SetBattleOrigin(int origin) { + //data.maniac_battle_position = origin; +} + +inline int Game_System::GetBattleOrigin() const { + //return data.maniac_battle_position; + return 0; +} + +inline void Game_System::SetMessageFaceSize(int width, int height) { + //data.maniac_message_face_width = width; + //data.maniac_message_face_height = height; +} + +inline int Game_System::GetMessageFaceWidth() const { + //return data.maniac_message_face_width; + return 0; +} + +inline int Game_System::GetMessageFaceHeight() const { + //return data.maniac_message_face_height; + return 0; +} + + #endif diff --git a/src/player.cpp b/src/player.cpp index 3b414d41b9..ee525dacb9 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -135,6 +135,12 @@ namespace Player { int rng_seed = -1; Game_ConfigPlayer player_config; Game_ConfigGame game_config; + + namespace { + int frame_skip_counter = 0; + int frame_skip_rate = 1; + } + #ifdef __EMSCRIPTEN__ std::string emscripten_game_name; #endif @@ -265,7 +271,11 @@ void Player::MainLoop() { Input::UpdateSystem(); } - Player::Draw(); + frame_skip_counter = (frame_skip_counter + 1) % frame_skip_rate; + if (frame_skip_counter == 0) { + Player::Draw(); + } + Scene::old_instances.clear(); @@ -296,6 +306,17 @@ void Player::MainLoop() { } } +void Player::SetFrameSkip(int mode) { + switch (mode) { + case 0: frame_skip_rate = 1; break; // Full + case 1: frame_skip_rate = 5; break; // 1/5 + case 2: frame_skip_rate = 3; break; // 1/3 + case 3: frame_skip_rate = 2; break; // 1/2 + default: frame_skip_rate = 1; break; + } + frame_skip_counter = 0; +} + void Player::Pause() { Audio().BGM_Pause(); } diff --git a/src/player.h b/src/player.h index 0e0680167e..2d8c5361c6 100644 --- a/src/player.h +++ b/src/player.h @@ -431,6 +431,12 @@ namespace Player { /** game specific configuration */ extern Game_ConfigGame game_config; + /** + * Sets the frame skipping rate for the main loop. + * @param mode 0: Full, 1: 1/5, 2: 1/3, 3: 1/2 + */ + void SetFrameSkip(int mode); + #ifdef __EMSCRIPTEN__ /** Name of game emscripten uses */ extern std::string emscripten_game_name; diff --git a/src/window_message.cpp b/src/window_message.cpp index cfd2139cb4..39827c89c4 100644 --- a/src/window_message.cpp +++ b/src/window_message.cpp @@ -201,6 +201,15 @@ void Window_Message::StartMessageProcessing(PendingMessage pm) { void Window_Message::OnFinishPage() { DebugLog("{}: FINISH PAGE"); + if (Player::IsPatchManiac() && Main_Data::game_system->GetFastForwardText()) { + if ((text.data() + text.size() - text_index) < 3 && !pending_message.HasNumberInput() && pending_message.GetNumChoices() <= 0) { + line_char_counter = 0; + SetWait(5); + FinishMessageProcessing(); + return; + } + } + if (pending_message.GetNumChoices() > 0) { StartChoiceProcessing(); } else if (pending_message.HasNumberInput()) { @@ -313,6 +322,7 @@ void Window_Message::InsertNewPage() { } if (IsFaceEnabled()) { + int face_width = Main_Data::game_system->GetMessageFaceWidth(); if (!Main_Data::game_system->IsMessageFaceRightPosition()) { contents_x = LeftMargin + FaceSize + RightFaceMargin; DrawFace(Main_Data::game_system->GetMessageFaceName(), Main_Data::game_system->GetMessageFaceIndex(), LeftMargin, TopMargin, Main_Data::game_system->IsMessageFaceFlipped()); @@ -479,7 +489,8 @@ void Window_Message::UpdateMessage() { // Message Box Show Message rendering loop bool instant_speed_forced = false; - if (Player::debug_flag && Input::IsPressed(Input::SHIFT)) { + if ((Player::debug_flag && Input::IsPressed(Input::SHIFT)) || + (Main_Data::game_system->GetFastForwardText() && Input::IsRawKeyPressed(Input::Keys::RSHIFT))) { instant_speed = true; instant_speed_forced = true; } @@ -845,6 +856,14 @@ void Window_Message::InputChoice() { } void Window_Message::InputNumber() { + + if (Main_Data::game_system->IsMessageMouseDisabled()) { + // When mouse is disabled, it shouldn't trigger number input either + if (Input::IsRawKeyTriggered(Input::Keys::MOUSE_LEFT) || Input::IsRawKeyTriggered(Input::Keys::MOUSE_RIGHT)) { + return; + } + } + number_input_window->SetVisible(true); if (Input::IsTriggered(Input::DECISION)) { Main_Data::game_system->SePlay(Main_Data::game_system->GetSystemSE(Main_Data::game_system->SFX_Decision));