From efb7ca300b136af31f2f600e82afa0a67f250c03 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Sat, 16 May 2026 18:31:24 +0200 Subject: [PATCH 1/2] Implement Maniac Patch Battle Start and Parallel events The execution order appears to be correct for parallel events. They match the examples from #3307 What is not working correctly is showing of message boxes In Maniacs they halt the battle while we continue the battle for one frame everytime it is closed. Co-Authored-By: Dino Suvalic <82914521+MakoInfused@users.noreply.github.com> --- src/game_battle.cpp | 17 ++++++++++ src/game_battle.h | 12 +++++++ src/game_commonevent.cpp | 36 +++++++++++++++++---- src/game_commonevent.h | 16 +++++++-- src/game_interpreter.cpp | 5 +++ src/game_interpreter.h | 14 ++++++-- src/game_interpreter_battle.cpp | 4 +-- src/game_interpreter_battle.h | 2 +- src/game_interpreter_map.cpp | 6 ---- src/game_interpreter_map.h | 14 -------- src/scene_battle.cpp | 57 ++++++++++++++++++++++++++++++--- src/scene_battle.h | 10 +++++- src/scene_battle_rpg2k.cpp | 5 +++ src/scene_battle_rpg2k3.cpp | 5 +++ 14 files changed, 162 insertions(+), 41 deletions(-) diff --git a/src/game_battle.cpp b/src/game_battle.cpp index 8d67afd1a8..00ece930e4 100644 --- a/src/game_battle.cpp +++ b/src/game_battle.cpp @@ -20,6 +20,7 @@ #include #include "player.h" #include "game_actors.h" +#include "game_commonevent.h" #include "game_enemyparty.h" #include "game_message.h" #include "game_party.h" @@ -43,6 +44,8 @@ namespace Game_Battle { std::string background_name; std::unique_ptr interpreter; + std::vector common_events; + /** Contains battle related sprites */ std::unique_ptr spriteset; @@ -76,18 +79,28 @@ void Game_Battle::Init(int troop_id) { animation_actors.reset(); animation_enemies.reset(); + InitCommonEvents(); for (auto* actor: Main_Data::game_party->GetActors()) { actor->ResetEquipmentStates(true); } } +void Game_Battle::InitCommonEvents() { + common_events.clear(); + common_events.reserve(lcf::Data::commonevents.size()); + for (const lcf::rpg::CommonEvent& ev : lcf::Data::commonevents) { + common_events.emplace_back(ev.ID, false); + } +} + void Game_Battle::Quit() { if (!IsBattleRunning()) { return; } interpreter.reset(); + common_events.clear(); spriteset.reset(); animation_actors.reset(); animation_enemies.reset(); @@ -276,6 +289,10 @@ Game_Interpreter_Battle& Game_Battle::GetInterpreterBattle() { return *interpreter; } +std::vector& Game_Battle::GetCommonEvents() { + return common_events; +} + void Game_Battle::SetTerrainId(int id) { terrain_id = id; } diff --git a/src/game_battle.h b/src/game_battle.h index a800d7c177..355a8f2bf4 100644 --- a/src/game_battle.h +++ b/src/game_battle.h @@ -48,6 +48,11 @@ namespace Game_Battle { */ void Init(int troop_id); + /** + * Initializes Common Events. + */ + void InitCommonEvents(); + /** @return true if a battle is currently running */ bool IsBattleRunning(); @@ -141,6 +146,13 @@ namespace Game_Battle { */ Game_Interpreter_Battle& GetInterpreterBattle(); + /** + * Gets common events list. + * + * @return common events list. + */ + std::vector& GetCommonEvents(); + void SetTerrainId(int id); int GetTerrainId(); diff --git a/src/game_commonevent.cpp b/src/game_commonevent.cpp index 5f24e46b26..a70cd1c269 100644 --- a/src/game_commonevent.cpp +++ b/src/game_commonevent.cpp @@ -17,28 +17,37 @@ // Headers #include "game_commonevent.h" +#include "game_interpreter_battle.h" #include "game_map.h" #include "game_switches.h" #include "game_interpreter_map.h" #include "main_data.h" #include #include +#include +#include -Game_CommonEvent::Game_CommonEvent(int common_event_id) : - common_event_id(common_event_id) +Game_CommonEvent::Game_CommonEvent(int common_event_id, bool on_map) : + common_event_id(common_event_id), on_map(on_map) { auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, common_event_id); - if (ce->trigger == lcf::rpg::EventPage::Trigger_parallel + if (on_map && ce->trigger == lcf::rpg::CommonEvent::Trigger_parallel && !ce->event_commands.empty()) { interpreter.reset(new Game_Interpreter_Map()); interpreter->Push(this); } - + if (!on_map && ce->trigger == lcf::rpg::CommonEvent::Trigger_maniac_battle_parallel + && !ce->event_commands.empty()) { + interpreter.reset(new Game_Interpreter_Battle(false)); + interpreter->Push(this); + } } void Game_CommonEvent::SetSaveData(const lcf::rpg::SaveEventExecState& data) { + assert(on_map); + // RPG_RT Savegames have empty stacks for parallel events. // We are LSD compatible but don't load these into interpreter. if (!data.stack.empty() && !data.stack.front().commands.empty()) { @@ -94,11 +103,13 @@ std::vector& Game_CommonEvent::GetList() { } lcf::rpg::SaveEventExecState Game_CommonEvent::GetSaveData() { + assert(on_map); + lcf::rpg::SaveEventExecState state; if (interpreter) { state = interpreter->GetSaveState(); } - if (GetTrigger() == lcf::rpg::EventPage::Trigger_parallel && state.stack.empty()) { + if (GetTrigger() == lcf::rpg::CommonEvent::Trigger_parallel && state.stack.empty()) { // RPG_RT always stores an empty stack frame for parallel events. state.stack.push_back({}); } @@ -107,13 +118,24 @@ lcf::rpg::SaveEventExecState Game_CommonEvent::GetSaveData() { bool Game_CommonEvent::IsWaitingForegroundExecution() const { auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, common_event_id); - return ce->trigger == lcf::rpg::EventPage::Trigger_auto_start && + return ce->trigger == lcf::rpg::CommonEvent::Trigger_automatic && + (!ce->switch_flag || Main_Data::game_switches->Get(ce->switch_id)) + && !ce->event_commands.empty(); +} + +bool Game_CommonEvent::IsWaitingBattleStartExecution() const { + auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, common_event_id); + return ce->trigger == lcf::rpg::CommonEvent::Trigger_maniac_battle_start && (!ce->switch_flag || Main_Data::game_switches->Get(ce->switch_id)) && !ce->event_commands.empty(); } bool Game_CommonEvent::IsWaitingBackgroundExecution(bool force_run) const { auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, common_event_id); - return ce->trigger == lcf::rpg::EventPage::Trigger_parallel && + const auto trigger = on_map ? + lcf::rpg::CommonEvent::Trigger_parallel : + lcf::rpg::CommonEvent::Trigger_maniac_battle_parallel; + + return ce->trigger == trigger && (force_run || !ce->switch_flag || Main_Data::game_switches->Get(ce->switch_id)); } diff --git a/src/game_commonevent.h b/src/game_commonevent.h index d9bb817fa7..ec9fd789d0 100644 --- a/src/game_commonevent.h +++ b/src/game_commonevent.h @@ -38,7 +38,7 @@ class Game_CommonEvent { * * @param common_event_id database common event ID. */ - explicit Game_CommonEvent(int common_event_id); + explicit Game_CommonEvent(int common_event_id, bool on_map = true); /** * Set savegame data. @@ -109,19 +109,31 @@ class Game_CommonEvent { /** @return true if waiting for foreground execution */ bool IsWaitingForegroundExecution() const; + /** @return true if waiting for execution when battle starts */ + bool IsWaitingBattleStartExecution() const; + /** * @param force_run force the event to execute even if conditions not met. * @return true if waiting for background execution */ bool IsWaitingBackgroundExecution(bool force_run) const; + const Game_Interpreter* GetInterpreter() const; + private: int common_event_id; + /** Indicates whether these common events run on the map (false = on battle) */ + bool on_map; + /** Interpreter for parallel common events. */ - std::unique_ptr interpreter; + std::unique_ptr interpreter; friend class Game_Interpreter_Inspector; }; +inline const Game_Interpreter* Game_CommonEvent::GetInterpreter() const { + return interpreter.get(); +} + #endif diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index ac1285ba67..ad3661c2f5 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -144,6 +144,11 @@ void Game_Interpreter::PushInternal( _state.stack.push_back(std::move(frame)); } +void Game_Interpreter::SetState(const lcf::rpg::SaveEventExecState& save) { + Clear(); + _state = save; + _keyinput.fromSave(save); +} void Game_Interpreter::KeyInputState::fromSave(const lcf::rpg::SaveEventExecState& save) { *this = {}; diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 8aba716292..1b7fe16abd 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -89,13 +89,20 @@ class Game_Interpreter : public Game_BaseInterpreterContext bool ExecuteCommand(); virtual bool ExecuteCommand(lcf::rpg::EventCommand const& com); - /** * Returns the interpreters current state information. * For saving state into a save file, use GetSaveState instead. */ const lcf::rpg::SaveEventExecState& GetState() const override; + /** + * Sets up the interpreter with given state. + * + * @param save event to load. + * + */ + void SetState(const lcf::rpg::SaveEventExecState& save); + /** * Returns a SaveEventExecState needed for the savefile. * @@ -122,7 +129,7 @@ class Game_Interpreter : public Game_BaseInterpreterContext void ClearOriginalEventId(); /** Return true if the interpreter is waiting for an async operation and needs to be resumed */ - bool IsAsyncPending(); + bool IsAsyncPending() const; /** Return true if the interpreter is waiting for an async operation and needs to be resumed */ AsyncOp GetAsyncOp() const; @@ -412,6 +419,7 @@ inline void Game_Interpreter::Push(Game_Event* ev, const lcf::rpg::EventPage* pa template inline void Game_Interpreter::Push(Game_CommonEvent* ev) { static_assert(type_ex == InterpreterExecutionType::AutoStart || type_ex == InterpreterExecutionType::Parallel + || type_ex == InterpreterExecutionType::BattleStart || type_ex == InterpreterExecutionType::BattleParallel || type_ex == InterpreterExecutionType::Call || type_ex == InterpreterExecutionType::DeathHandler || type_ex == InterpreterExecutionType::DebugCall || type_ex == InterpreterExecutionType::ManiacHook, "Unexpected ExecutionType for CommonEvent" ); @@ -457,7 +465,7 @@ inline int Game_Interpreter::GetLoopCount() const { return loop_count; } -inline bool Game_Interpreter::IsAsyncPending() { +inline bool Game_Interpreter::IsAsyncPending() const { return GetAsyncOp().IsActive(); } diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index db96f52346..d814cc9f42 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -77,8 +77,8 @@ Game_Interpreter_Battle::Game_Interpreter_Battle(Span maniac_interpreter.reset(new Game_Interpreter_Battle()); } -Game_Interpreter_Battle::Game_Interpreter_Battle() - : Game_Interpreter(true) +Game_Interpreter_Battle::Game_Interpreter_Battle(bool main_flag) + : Game_Interpreter(main_flag) { } diff --git a/src/game_interpreter_battle.h b/src/game_interpreter_battle.h index 4b83790eca..bb7fd74497 100644 --- a/src/game_interpreter_battle.h +++ b/src/game_interpreter_battle.h @@ -40,7 +40,7 @@ class Game_Interpreter_Battle : public Game_Interpreter { public: explicit Game_Interpreter_Battle(Span pages); - explicit Game_Interpreter_Battle(); + explicit Game_Interpreter_Battle(bool main_flag = true); int GetNumPages() const; diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index d22c68003e..9288baf55f 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -83,12 +83,6 @@ enum InnSubcommand { using namespace Game_Interpreter_Shared; -void Game_Interpreter_Map::SetState(const lcf::rpg::SaveEventExecState& save) { - Clear(); - _state = save; - _keyinput.fromSave(save); -} - void Game_Interpreter_Map::OnMapChange() { // When we change the map, we reset all event id's to 0. for (auto& frame: _state.stack) { diff --git a/src/game_interpreter_map.h b/src/game_interpreter_map.h index 9faf600a36..c44af7a415 100644 --- a/src/game_interpreter_map.h +++ b/src/game_interpreter_map.h @@ -38,14 +38,6 @@ class Game_Interpreter_Map : public Game_Interpreter public: using Game_Interpreter::Game_Interpreter; - /** - * Sets up the interpreter with given state. - * - * @param save event to load. - * - */ - void SetState(const lcf::rpg::SaveEventExecState& save); - /** * Called when we change maps. */ @@ -88,12 +80,6 @@ class Game_Interpreter_Map : public Game_Interpreter bool CommandEasyRpgWaitForSingleMovement(lcf::rpg::EventCommand const& com); AsyncOp ContinuationShowInnStart(int indent, int choice_result, int price); - bool CommandSmartMoveRoute( - lcf::rpg::EventCommand const& com, - int maxRouteStepsDefault, int maxSearchStepsDefault, - int abortIfAlreadyMovingDefault - ); // Internal generic path finder function. - static std::vector pending; }; diff --git a/src/scene_battle.cpp b/src/scene_battle.cpp index a5f34080ac..c8a59af2ab 100644 --- a/src/scene_battle.cpp +++ b/src/scene_battle.cpp @@ -25,6 +25,7 @@ #include "player.h" #include "transition.h" #include "game_battlealgorithm.h" +#include "game_commonevent.h" #include "game_interpreter_battle.h" #include "game_message.h" #include "game_system.h" @@ -270,11 +271,7 @@ void Scene_Battle::UpdateUi() { Game_Message::Update(); } -bool Scene_Battle::UpdateEvents() { - auto& interp = Game_Battle::GetInterpreterBattle(); - interp.Update(); - status_window->Refresh(); - +bool Scene_Battle::CheckInterpreter(const Game_Interpreter_Battle& interp) { if (interp.IsForceFleeEnabled()) { if (state != State_Escape) { SetState(State_Escape); @@ -302,6 +299,37 @@ bool Scene_Battle::UpdateEvents() { return true; } +bool Scene_Battle::UpdateEvents() { + auto& interp = Game_Battle::GetInterpreterBattle(); + interp.Update(); + status_window->Refresh(); + + return CheckInterpreter(Game_Battle::GetInterpreterBattle()); +} + +bool Scene_Battle::UpdateCommonEvents() { + if (!Player::IsPatchManiac()) { + return true; + } + + auto& common_events = Game_Battle::GetCommonEvents(); + + // TODO: Async handling not properly implemented + // Async requests that yield the Web Player will not work correctly + for (auto& ce: common_events) { + ce.Update(false); + const auto* interp = static_cast(ce.GetInterpreter()); + if (interp && !CheckInterpreter(*interp)) { + status_window->Refresh(); + return false; + } + } + + status_window->Refresh(); + + return true; +} + bool Scene_Battle::UpdateTimers() { const int timer1 = Main_Data::game_party->GetTimerSeconds(Game_Party::Timer1); const int timer2 = Main_Data::game_party->GetTimerSeconds(Game_Party::Timer2); @@ -323,6 +351,25 @@ void Scene_Battle::UpdateGraphics() { Game_Battle::UpdateGraphics(); } +bool Scene_Battle::ScheduleNextBattleBeginCommonEvent() { + if (!Player::IsPatchManiac()) { + return false; + } + + auto& common_events = Game_Battle::GetCommonEvents(); + + for (size_t i = last_scheduled_start_battle_event + 1; i < common_events.size(); ++i) { + auto& ce = common_events[i]; + last_scheduled_start_battle_event = i; + if (ce.IsWaitingBattleStartExecution()) { + Game_Battle::GetInterpreterBattle().Push(&ce); + return true; + } + } + + return false; +} + bool Scene_Battle::IsWindowMoving() { return options_window->IsMovementActive() || status_window->IsMovementActive() || command_window->IsMovementActive(); } diff --git a/src/scene_battle.h b/src/scene_battle.h index 7e41c05cf2..1e0936306a 100644 --- a/src/scene_battle.h +++ b/src/scene_battle.h @@ -28,6 +28,7 @@ #include "drawable.h" #include "game_actor.h" #include "game_enemy.h" +#include "game_interpreter_battle.h" #include "scene.h" #include "spriteset_battle.h" #include "window_help.h" @@ -84,10 +85,14 @@ class Scene_Battle : public Scene { void UpdateScreen(); void UpdateBattlers(); void UpdateUi(); + bool CheckInterpreter(const Game_Interpreter_Battle& interp); bool UpdateEvents(); + bool UpdateCommonEvents(); bool UpdateTimers(); void UpdateGraphics() override; + bool ScheduleNextBattleBeginCommonEvent(); + void Continue(SceneType prev_scene) override; void TransitionIn(SceneType prev_scene) override; void TransitionOut(SceneType next_scene) override; @@ -154,7 +159,7 @@ class Scene_Battle : public Scene { /** * Executed when selection an action (normal, skill, item, ...) and - * (if needed) choosing an attack target was finished. + * (if needed) choosing an attack target was finished. * * @param for_battler Battler whose action was selected. */ @@ -205,6 +210,9 @@ class Scene_Battle : public Scene { /** Options available in the menu. */ std::vector battle_options; + + /** ID of last invoked start battle event */ + int last_scheduled_start_battle_event = -1; }; inline bool Scene_Battle::IsEscapeAllowed() const { diff --git a/src/scene_battle_rpg2k.cpp b/src/scene_battle_rpg2k.cpp index d9b235c8c7..16c2ec1b49 100644 --- a/src/scene_battle_rpg2k.cpp +++ b/src/scene_battle_rpg2k.cpp @@ -220,6 +220,7 @@ void Scene_Battle_Rpg2k::vUpdate() { } } + UpdateCommonEvents(); Game_Battle::UpdateGraphics(); } @@ -237,6 +238,10 @@ bool Scene_Battle_Rpg2k::CheckBattleEndAndScheduleEvents() { return false; } + if (ScheduleNextBattleBeginCommonEvent()) { + return false; + } + auto& interp = Game_Battle::GetInterpreterBattle(); int page = interp.ScheduleNextPage(nullptr); diff --git a/src/scene_battle_rpg2k3.cpp b/src/scene_battle_rpg2k3.cpp index 32c2d0fee5..7927654021 100644 --- a/src/scene_battle_rpg2k3.cpp +++ b/src/scene_battle_rpg2k3.cpp @@ -987,6 +987,7 @@ void Scene_Battle_Rpg2k3::vUpdate() { } } + UpdateCommonEvents(); UpdateAnimations(); UpdateGraphics(); } @@ -1032,6 +1033,10 @@ bool Scene_Battle_Rpg2k3::CheckBattleEndAndScheduleEvents(EventTriggerType tt, G return false; } + if (ScheduleNextBattleBeginCommonEvent()) { + return false; + } + lcf::rpg::TroopPageCondition::Flags flags; switch (tt) { case EventTriggerType::eBeforeBattleAction: From 6069d0f1b3b9797ba25867d686729f09dfce420f Mon Sep 17 00:00:00 2001 From: Ghabry Date: Sat, 16 May 2026 18:31:44 +0200 Subject: [PATCH 2/2] Control Variables ATB returns the ATB gauge in percent --- src/game_interpreter_control_variables.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game_interpreter_control_variables.cpp b/src/game_interpreter_control_variables.cpp index e459695109..3a55494e01 100644 --- a/src/game_interpreter_control_variables.cpp +++ b/src/game_interpreter_control_variables.cpp @@ -135,7 +135,7 @@ int ControlVariables::Actor(int op, int actor_id) { case 16: // ATB if (Player::IsPatchManiac()) { - return actor->GetAtbGauge(); + return actor->GetAtbGauge() * 100 / actor->GetMaxAtbGauge(); } break; }