From 7199e96aa7c183a36bc636854f8bbb1ab5d67787 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 5 Jun 2024 16:06:00 +0200 Subject: [PATCH 01/16] Cherry Pick commit #ec84539 from experimental branch "florianessl:InterpreterDebug": - Added new file "game_interpreter_debug.cpp" & moved/refactored some of the logic from the interpreter debug scene there --- CMakeLists.txt | 2 + Makefile.am | 2 + src/game_commonevent.h | 2 + src/game_event.h | 2 + src/game_interpreter_debug.cpp | 137 +++++++++++++++++++++++++++++++++ src/game_interpreter_debug.h | 71 +++++++++++++++++ src/scene_debug.cpp | 104 +++++++++++-------------- src/scene_debug.h | 7 +- src/window_interpreter.cpp | 81 ++++++------------- src/window_interpreter.h | 11 +-- 10 files changed, 286 insertions(+), 133 deletions(-) create mode 100644 src/game_interpreter_debug.cpp create mode 100644 src/game_interpreter_debug.h diff --git a/CMakeLists.txt b/CMakeLists.txt index f58dd9e6bf..3080dfbe61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,8 @@ add_library(${PROJECT_NAME} OBJECT src/game_interpreter_battle.h src/game_interpreter_control_variables.cpp src/game_interpreter_control_variables.h + src/game_interpreter_debug.cpp + src/game_interpreter_debug.h src/game_interpreter.cpp src/game_interpreter.h src/game_interpreter_map.cpp diff --git a/Makefile.am b/Makefile.am index da211087c6..15ddf3ca63 100644 --- a/Makefile.am +++ b/Makefile.am @@ -158,6 +158,8 @@ libeasyrpg_player_a_SOURCES = \ src/game_interpreter_battle.h \ src/game_interpreter_control_variables.cpp \ src/game_interpreter_control_variables.h \ + src/game_interpreter_debug.cpp \ + src/game_interpreter_debug.h \ src/game_interpreter_map.cpp \ src/game_interpreter_map.h \ src/game_interpreter_shared.cpp \ diff --git a/src/game_commonevent.h b/src/game_commonevent.h index ae70179b0b..14ec9b1e57 100644 --- a/src/game_commonevent.h +++ b/src/game_commonevent.h @@ -21,6 +21,7 @@ // Headers #include #include +#include "game_interpreter_debug.h" #include "game_interpreter_map.h" #include #include @@ -121,6 +122,7 @@ class Game_CommonEvent { std::unique_ptr interpreter; friend class Scene_Debug; + friend class Debug::ParallelInterpreterStates; }; #endif diff --git a/src/game_event.h b/src/game_event.h index 225cc4e4a4..b079faddfc 100644 --- a/src/game_event.h +++ b/src/game_event.h @@ -24,6 +24,7 @@ #include "game_character.h" #include #include +#include "game_interpreter_debug.h" #include "game_interpreter_map.h" #include "async_op.h" @@ -219,6 +220,7 @@ class Game_Event : public Game_EventBase { std::unique_ptr interpreter; friend class Scene_Debug; + friend class Debug::ParallelInterpreterStates; }; inline int Game_Event::GetNumPages() const { diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp new file mode 100644 index 0000000000..39dd528cfe --- /dev/null +++ b/src/game_interpreter_debug.cpp @@ -0,0 +1,137 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "game_interpreter_debug.h" +#include "game_interpreter.h" +#include "game_battle.h" +#include "game_map.h" +#include "main_data.h" +#include "game_variables.h" +#include "output.h" +#include + +Debug::ParallelInterpreterStates Debug::ParallelInterpreterStates::GetCachedStates() { + std::vector ev_ids; + std::vector ce_ids; + + std::vector state_ev; + std::vector state_ce; + + if (Game_Map::GetMapId() > 0) { + for (auto& ev : Game_Map::GetEvents()) { + if (ev.GetTrigger() != lcf::rpg::EventPage::Trigger_parallel || !ev.interpreter) + continue; + ev_ids.emplace_back(ev.GetId()); + state_ev.emplace_back(ev.interpreter->GetState()); + } + for (auto& ce : Game_Map::GetCommonEvents()) { + if (ce.IsWaitingBackgroundExecution(false)) { + ce_ids.emplace_back(ce.common_event_id); + state_ce.emplace_back(ce.interpreter->GetState()); + } + } + } else if (Game_Battle::IsBattleRunning() && Player::IsPatchManiac()) { + //FIXME: Not implemented: battle common events + } + + return { ev_ids, ce_ids, state_ev, state_ce }; +} + +std::vector Debug::CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state) { + std::vector items(state.stack.size()); + + for (int i = state.stack.size() - 1; i >= 0; i--) { + int evt_id = state.stack[i].event_id; + int page_id = 0; + if (state.stack[i].maniac_event_id > 0) { + evt_id = state.stack[i].maniac_event_id; + page_id = state.stack[i].maniac_event_page_id; + } + if (evt_id == 0 && i == 0) + evt_id = owner_evt_id; + + bool is_calling_ev_ce = false; + + //FIXME: There are some currently unimplemented SaveEventExecFrame fields introduced via the ManiacPatch which should be used to properly get event state information + if (evt_id == 0 && i > 0) { + auto& prev_frame = state.stack[i - 1]; + auto& com = prev_frame.commands[prev_frame.current_command - 1]; + if (com.code == 12330) { // CallEvent + if (com.parameters[0] == 0) { + is_calling_ev_ce = true; + evt_id = com.parameters[1]; + } else if (com.parameters[0] == 3 && Player::IsPatchManiac()) { + is_calling_ev_ce = true; + evt_id = Main_Data::game_variables->Get(com.parameters[1]); + } else if (com.parameters[0] == 4 && Player::IsPatchManiac()) { + is_calling_ev_ce = true; + evt_id = Main_Data::game_variables->GetIndirect(com.parameters[1]); + } + } + } + + auto item = Debug::CallStackItem(); + item.stack_item_no = i + 1; + item.is_ce = is_calling_ev_ce; + item.evt_id = evt_id; + item.page_id = page_id; + item.name = ""; + item.cmd_current = state.stack[i].current_command; + item.cmd_count = state.stack[i].commands.size(); + + if (item.is_ce) { + auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, item.evt_id); + if (ce) { + item.name = ToString(ce->name); + } + } else { + auto* ev = Game_Map::GetEvent(evt_id); + if (ev) { + //FIXME: map could have changed, but map_id isn't available + item.name = ToString(ev->GetName()); + } + } + + items.push_back(item); + } + + return items; +} + +std::string Debug::FormatEventName(Game_Character const& ch) { + switch (ch.GetType()) { + case Game_Character::Player: + return "Player"; + case Game_Character::Vehicle: + { + int type = static_cast(ch).GetVehicleType(); + assert(type > Game_Vehicle::None && type <= Game_Vehicle::Airship); + return Game_Vehicle::TypeNames[type]; + } + case Game_Character::Event: + { + auto& ev = static_cast(ch); + if (ev.GetName().empty()) { + return fmt::format("EV{:04d}", ev.GetId()); + } + return fmt::format("EV{:04d} '{}'", ev.GetId(), ev.GetName()); + } + default: + assert(false); + } + return ""; +} diff --git a/src/game_interpreter_debug.h b/src/game_interpreter_debug.h new file mode 100644 index 0000000000..7e3bd2de48 --- /dev/null +++ b/src/game_interpreter_debug.h @@ -0,0 +1,71 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + + +#ifndef EP_GAME_INTERPRETER_DEBUG +#define EP_GAME_INTERPRETER_DEBUG + +#include "game_interpreter_shared.h" +#include "game_character.h" +#include +#include "player.h" + +class Game_CommonEvent; + +namespace Debug { + class ParallelInterpreterStates { + private: + std::vector ev_ids; + std::vector ce_ids; + + std::vector state_ev; + std::vector state_ce; + + ParallelInterpreterStates(std::vector ev_ids, std::vector ce_ids, + std::vector state_ev, std::vector state_ce) + : ev_ids(ev_ids), ce_ids(ce_ids), state_ev(state_ev), state_ce(state_ce) { } + public: + ParallelInterpreterStates() = default; + + inline int CountEventInterpreters() const { return ev_ids.size(); } + inline int CountCommonEventInterpreters() const { return ce_ids.size(); } + + inline int Count() const { return ev_ids.size() + ce_ids.size(); } + + inline std::tuple GetEventInterpreter(int i) const { + return std::tie(ev_ids[i], state_ev[i]); + } + inline std::tuple GetCommonEventInterpreter(int i) const { + return std::tie(ce_ids[i], state_ce[i]); + } + + static ParallelInterpreterStates GetCachedStates(); + }; + + struct CallStackItem { + bool is_ce; + int evt_id, page_id; + std::string name; + int stack_item_no, cmd_current, cmd_count; + }; + + std::vector CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state); + + std::string FormatEventName(Game_Character const& ev); +} + +#endif diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 7a6dd70cde..38e926bd48 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -631,7 +631,7 @@ void Scene_Debug::vUpdate() { PushUiInterpreterView(); } else if (sz == 1) { if (!interpreter_states_cached) { - CacheBackgroundInterpreterStates(); + state_interpreter.background_states = Debug::ParallelInterpreterStates::GetCachedStates(); interpreter_states_cached = true; } PushUiRangeList(); @@ -850,32 +850,33 @@ void Scene_Debug::UpdateRangeListWindow() { break; case eInterpreter: { - int skip_items = range_page * 10; - int count_items = 0; - if (range_page == 0) { - addItem(fmt::format("{}Main", Game_Interpreter::GetForegroundInterpreter().GetState().wait_movement ? "(W) " : "")); - skip_items = 1; - count_items = 1; - } - for (int i = 0; i < static_cast(state_interpreter.ev.size()) && count_items < 10; i++) { - if (skip_items > 0) { - skip_items--; - continue; - } - int evt_id = state_interpreter.ev[i]; - addItem(fmt::format("{}EV{:04d}: {}", state_interpreter.state_ev[i].wait_movement ? "(W) " : "", evt_id, Game_Map::GetEvent(evt_id)->GetName())); - count_items++; + auto& bg_states = state_interpreter.background_states; + int skip_items = range_page * 10; + int count_items = 0; + if (range_page == 0) { + addItem(fmt::format("{}Main", Game_Interpreter::GetForegroundInterpreter().GetState().wait_movement ? "(W) " : "")); + skip_items = 1; + count_items = 1; + } + for (int i = 0; i < bg_states.CountEventInterpreters() && count_items < 10; i++) { + if (skip_items > 0) { + skip_items--; + continue; } - for (int i = 0; i < static_cast(state_interpreter.ce.size()) && count_items < 10; i++) { - if (skip_items > 0) { - skip_items--; - continue; - } - int ce_id = state_interpreter.ce[i]; - auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, ce_id); - addItem(fmt::format("{}CE{:04d}: {}", state_interpreter.state_ce[i].wait_movement ? "(W) " : "", ce_id, ce->name)); - count_items++; + auto& [evt_id, state] = bg_states.GetEventInterpreter(i); + addItem(fmt::format("{}EV{:04d}: {}", state.wait_movement ? "(W) " : "", evt_id, Game_Map::GetEvent(evt_id)->GetName())); + count_items++; + } + for (int i = 0; i < bg_states.CountCommonEventInterpreters() && count_items < 10; i++) { + if (skip_items > 0) { + skip_items--; + continue; } + auto& [ce_id, state] = bg_states.GetCommonEventInterpreter(i); + auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, ce_id); + addItem(fmt::format("{}CE{:04d}: {}", state.wait_movement ? "(W) " : "", ce_id, ce->name)); + count_items++; + } } break; default: @@ -986,7 +987,7 @@ int Scene_Debug::GetLastPage() const { num_elements = Main_Data::game_strings->GetSizeWithLimit(); break; case eInterpreter: - num_elements = 1 + state_interpreter.ev.size() + state_interpreter.ce.size(); + num_elements = 1 + state_interpreter.background_states.Count(); return (static_cast(num_elements) - 1) / 10; default: break; @@ -1217,48 +1218,28 @@ void Scene_Debug::UpdateArrows() { range_window->SetRightArrow(show_right_arrow && arrow_visible); } -void Scene_Debug::CacheBackgroundInterpreterStates() { - state_interpreter.ev.clear(); - state_interpreter.ce.clear(); - state_interpreter.state_ev.clear(); - state_interpreter.state_ce.clear(); - - if (Game_Map::GetMapId() > 0) { - for (auto& ev : Game_Map::GetEvents()) { - if (ev.GetTrigger() != lcf::rpg::EventPage::Trigger_parallel || !ev.interpreter) - continue; - state_interpreter.ev.emplace_back(ev.GetId()); - state_interpreter.state_ev.emplace_back(ev.interpreter->GetState()); - } - for (auto& ce : Game_Map::GetCommonEvents()) { - if (ce.IsWaitingBackgroundExecution(false)) { - state_interpreter.ce.emplace_back(ce.common_event_id); - state_interpreter.state_ce.emplace_back(ce.interpreter->GetState()); - } - } - } else if (Game_Battle::IsBattleRunning() && Player::IsPatchManiac()) { - //Not implemented: battle common events - } -} - void Scene_Debug::UpdateInterpreterWindow(int index) { lcf::rpg::SaveEventExecState state; std::string first_line = ""; bool valid = false; int evt_id = 0; + auto& bg_states = state_interpreter.background_states; + if (index == 1) { state = Game_Interpreter::GetForegroundInterpreter().GetState(); first_line = Game_Battle::IsBattleRunning() ? "Foreground (Battle)" : "Foreground (Map)"; valid = true; - } else if (index <= static_cast(state_interpreter.ev.size())) { - evt_id = state_interpreter.ev[index - 1]; - state = state_interpreter.state_ev[index - 1]; + } else if (index <= bg_states.CountEventInterpreters()) { + auto tuple = bg_states.GetEventInterpreter(index - 1); + evt_id = std::get<0>(tuple); + state = std::get<1>(tuple); first_line = fmt::format("EV{:04d}: {}", evt_id, Game_Map::GetEvent(evt_id)->GetName()); valid = true; - } else if ((index - state_interpreter.ev.size()) <= state_interpreter.ce.size()) { - int ce_id = state_interpreter.ce[index - state_interpreter.ev.size() - 1]; - state = state_interpreter.state_ce[index - state_interpreter.ev.size() - 1]; + } else if ((index - bg_states.CountEventInterpreters()) <= bg_states.CountCommonEventInterpreters()) { + auto tuple = bg_states.GetCommonEventInterpreter(index - bg_states.CountEventInterpreters() - 1); + int ce_id = std::get<0>(tuple); + state = std::get<1>(tuple); for (auto& ce : Game_Map::GetCommonEvents()) { if (ce.common_event_id == ce_id) { first_line = fmt::format("CE{:04d}: {}", ce_id, ce.GetName()); @@ -1271,7 +1252,7 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { if (valid) { state_interpreter.selected_state = index; - interpreter_window->SetStackState(index > static_cast(state_interpreter.ev.size()), evt_id, first_line, state); + interpreter_window->SetStackState(index > bg_states.CountEventInterpreters(), evt_id, first_line, state); } else { state_interpreter.selected_state = -1; interpreter_window->SetStackState(0, 0, "", {}); @@ -1286,18 +1267,19 @@ lcf::rpg::SaveEventExecFrame& Scene_Debug::GetSelectedInterpreterFrameFromUiStat } int index = state_interpreter.selected_state; + const auto& bg_states = state_interpreter.background_states; if (index == 1) { auto& state = Game_Interpreter::GetForegroundInterpreter()._state; return state.stack[state_interpreter.selected_frame]; - } else if (index <= static_cast(state_interpreter.ev.size())) { - int evt_id = state_interpreter.ev[index - 1]; + } else if (index <= bg_states.CountEventInterpreters()) { + int evt_id = std::get<0>(bg_states.GetEventInterpreter(index - 1)); auto ev = Game_Map::GetEvent(evt_id); auto& state = ev->interpreter->_state; return state.stack[state_interpreter.selected_frame]; - } else if ((index - state_interpreter.ev.size()) <= state_interpreter.ce.size()) { - int ce_id = state_interpreter.ce[index - state_interpreter.ev.size() - 1]; + } else if ((index - bg_states.CountEventInterpreters()) <= bg_states.CountCommonEventInterpreters()) { + int ce_id = std::get<0>(bg_states.GetEventInterpreter(index - bg_states.CountEventInterpreters() - 1)); for (auto& ce : Game_Map::GetCommonEvents()) { if (ce.common_event_id == ce_id) { auto& state = ce.interpreter->_state; diff --git a/src/scene_debug.h b/src/scene_debug.h index a41fe4a3ef..c7c726059e 100644 --- a/src/scene_debug.h +++ b/src/scene_debug.h @@ -20,6 +20,7 @@ // Headers #include +#include "game_interpreter_debug.h" #include "scene.h" #include "window_command.h" #include "window_numberinput.h" @@ -191,12 +192,8 @@ class Scene_Debug : public Scene { void UpdateInterpreterWindow(int index); lcf::rpg::SaveEventExecFrame& GetSelectedInterpreterFrameFromUiState() const; - void CacheBackgroundInterpreterStates(); struct { - std::vector ev; - std::vector ce; - std::vector state_ev; - std::vector state_ce; + Debug::ParallelInterpreterStates background_states; // Frame-scoped data types introduced in 'ScopedVars' branch // bool show_frame_switches = false; diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index 8070a5cf88..ddb1d6ebec 100644 --- a/src/window_interpreter.cpp +++ b/src/window_interpreter.cpp @@ -60,6 +60,15 @@ constexpr std::array runtime_flags = { { } }; #endif +namespace { + std::vector CreateEmptyLines(int c) { + std::vector vars; + for (int i = 0; i < c; i++) + vars.push_back(""); + return vars; + } +} + Window_Interpreter::Window_Interpreter(int ix, int iy, int iwidth, int iheight) : Window_Selectable(ix, iy, iwidth, iheight) { column_max = 1; @@ -99,66 +108,20 @@ void Window_Interpreter::Refresh() { int max_cmd_count = 0, max_evt_id = 10, max_page_id = 0; - for (int i = state.stack.size() - 1; i >= 0; i--) { - int evt_id = state.stack[i].event_id; - int page_id = 0; - if (state.stack[i].maniac_event_id > 0) { - evt_id = state.stack[i].maniac_event_id; - page_id = state.stack[i].maniac_event_page_id; - } - if (evt_id == 0 && i == 0) - evt_id = display_item.owner_evt_id; - - bool is_calling_ev_ce = false; - - //FIXME: There are some currently unimplemented SaveEventExecFrame fields introduced via the ManiacPatch which should be used to properly get event state information - if (evt_id == 0 && i > 0) { - auto& prev_frame = state.stack[i - 1]; - auto& com = prev_frame.commands[prev_frame.current_command - 1]; - if (com.code == 12330) { // CallEvent - if (com.parameters[0] == 0) { - is_calling_ev_ce = true; - evt_id = com.parameters[1]; - } else if (com.parameters[0] == 3 && Player::IsPatchManiac()) { - is_calling_ev_ce = true; - evt_id = Main_Data::game_variables->Get(com.parameters[1]); - } else if (com.parameters[0] == 4 && Player::IsPatchManiac()) { - is_calling_ev_ce = true; - evt_id = Main_Data::game_variables->GetIndirect(com.parameters[1]); - } - } - } - if (evt_id > max_evt_id) - max_evt_id = evt_id; - - if (page_id > max_page_id) - max_page_id = page_id; - - StackItem item = StackItem(); - item.is_ce = is_calling_ev_ce || (i == 0 && this->display_item.is_ce); - item.evt_id = evt_id; - item.page_id = page_id; - item.name = ""; - item.cmd_current = state.stack[i].current_command; - item.cmd_count = state.stack[i].commands.size(); - - if (item.is_ce) { - auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, item.evt_id); - if (ce) { - item.name = ToString(ce->name); - } - } else { - auto* ev = Game_Map::GetEvent(evt_id); - if (ev) { - //FIXME: map could have changed, but map_id isn't available - item.name = ToString(ev->GetName()); - } - } + stack_display_items = Debug::CreateCallStack(display_item.owner_evt_id, state); + if (stack_display_items.size() > 0 && this->display_item.is_ce) { + stack_display_items[0].is_ce = true; + } - if (static_cast(state.stack[i].commands.size()) > max_cmd_count) - max_cmd_count = state.stack[i].commands.size(); + for (auto it = stack_display_items.begin(); it < stack_display_items.end(); ++it) { + auto& item = *it; - stack_display_items.push_back(item); + if (item.evt_id > max_evt_id) + max_evt_id = item.evt_id; + if (item.page_id > max_page_id) + max_page_id = item.page_id; + if (item.cmd_count > max_cmd_count) + max_cmd_count = item.cmd_count; } item_max = stack_display_items.size() + lines_without_stack; @@ -257,7 +220,7 @@ void Window_Interpreter::DrawStackLine(int index) { Rect rect = GetItemRect(index + lines_without_stack); contents->ClearRect(rect); - StackItem& item = stack_display_items[index]; + Debug::CallStackItem& item = stack_display_items[index]; contents->TextDraw(rect.x, rect.y, Font::ColorDisabled, fmt::format("[{:0" + std::to_string(digits_stackitemno) + "d}]", state.stack.size() - index)); if (item.is_ce) { diff --git a/src/window_interpreter.h b/src/window_interpreter.h index 0d0244e803..2504783f65 100644 --- a/src/window_interpreter.h +++ b/src/window_interpreter.h @@ -18,7 +18,8 @@ #ifndef EP_WINDOW_INTERPRETER_H #define EP_WINDOW_INTERPRETER_H - // Headers +// Headers +#include "game_interpreter_debug.h" #include "window_command.h" #include "game_interpreter_shared.h" #include "lcf/rpg/saveeventexecstate.h" @@ -94,12 +95,6 @@ class Window_Interpreter : public Window_Selectable { std::string desc; }; - struct StackItem { - bool is_ce; - int evt_id, page_id; - std::string name; - int cmd_current, cmd_count; - }; const int lines_without_stack_fixed = 3; @@ -109,10 +104,10 @@ class Window_Interpreter : public Window_Selectable { int digits_stackitemno = 0, digits_evt_id = 0, digits_page_id = 0, digits_evt_combined_id = 0, digits_cmdcount = 0; InterpDisplayItem display_item; - std::vector stack_display_items; UiSubActionLine sub_actions; std::unique_ptr sub_window_flags; + std::vector stack_display_items; }; #endif From 9e36d1d44e89f90f90a7b01e42d4e608e6e27650 Mon Sep 17 00:00:00 2001 From: florianessl Date: Mon, 20 Jan 2025 21:12:47 +0100 Subject: [PATCH 02/16] Cleanup/Fix warning: Remove unused function "CreateEmptyLines" from window_interpreter --- src/window_interpreter.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index ddb1d6ebec..02cc7d54e7 100644 --- a/src/window_interpreter.cpp +++ b/src/window_interpreter.cpp @@ -60,15 +60,6 @@ constexpr std::array runtime_flags = { { } }; #endif -namespace { - std::vector CreateEmptyLines(int c) { - std::vector vars; - for (int i = 0; i < c; i++) - vars.push_back(""); - return vars; - } -} - Window_Interpreter::Window_Interpreter(int ix, int iy, int iwidth, int iheight) : Window_Selectable(ix, iy, iwidth, iheight) { column_max = 1; From 755ecfc029bb25e52f5dbfc104dd23eeb2191293 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 5 Feb 2025 07:25:11 +0100 Subject: [PATCH 03/16] Refactor: Allow outside unsafe access to internal interpreter state only when using a wrapper type of 'Game_Interpreter_Inspector' --- src/game_commonevent.h | 3 +- src/game_event.h | 3 +- src/game_interpreter.cpp | 60 ++++++++++++++++++++++++++++++++++ src/game_interpreter.h | 18 +++++++++- src/game_interpreter_debug.cpp | 22 ++++++++++--- src/game_interpreter_debug.h | 2 ++ src/scene_debug.cpp | 30 ++++++++--------- src/scene_debug.h | 2 +- 8 files changed, 113 insertions(+), 27 deletions(-) diff --git a/src/game_commonevent.h b/src/game_commonevent.h index 14ec9b1e57..d9bb817fa7 100644 --- a/src/game_commonevent.h +++ b/src/game_commonevent.h @@ -121,8 +121,7 @@ class Game_CommonEvent { /** Interpreter for parallel common events. */ std::unique_ptr interpreter; - friend class Scene_Debug; - friend class Debug::ParallelInterpreterStates; + friend class Game_Interpreter_Inspector; }; #endif diff --git a/src/game_event.h b/src/game_event.h index b079faddfc..b2f3dfd07f 100644 --- a/src/game_event.h +++ b/src/game_event.h @@ -219,8 +219,7 @@ class Game_Event : public Game_EventBase { const lcf::rpg::EventPage* page = nullptr; std::unique_ptr interpreter; - friend class Scene_Debug; - friend class Debug::ParallelInterpreterStates; + friend class Game_Interpreter_Inspector; }; inline int Game_Event::GetNumPages() const { diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 0594e5b596..0946f802fe 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -5784,3 +5784,63 @@ int Game_Interpreter::ManiacBitmask(int value, int mask) const { return value; } + +namespace { + lcf::rpg::SaveEventExecState const& empty_state = {}; +} + + +lcf::rpg::SaveEventExecState const& Game_Interpreter_Inspector::GetForegroundExecState() { + return Game_Interpreter::GetForegroundInterpreter()._state; +} + +lcf::rpg::SaveEventExecState& Game_Interpreter_Inspector::GetForegroundExecStateUnsafe() { + return Game_Interpreter::GetForegroundInterpreter()._state; +} + +lcf::rpg::SaveEventExecState const& Game_Interpreter_Inspector::GetExecState(Game_Event const& ev) { + if (!ev.interpreter) { + return empty_state; + } + return ev.interpreter->GetState(); +} + +lcf::rpg::SaveEventExecState const& Game_Interpreter_Inspector::GetExecState(Game_CommonEvent const& ce) { + if (!ce.interpreter) { + return empty_state; + } + return ce.interpreter->GetState(); +} + +lcf::rpg::SaveEventExecState& Game_Interpreter_Inspector::GetExecStateUnsafe(Game_Event& ev) { + assert(ev.interpreter); + return ev.interpreter->_state; +} + +lcf::rpg::SaveEventExecState& Game_Interpreter_Inspector::GetExecStateUnsafe(Game_CommonEvent& ce) { + assert(ce.interpreter); + return ce.interpreter->_state; +} + +bool Game_Interpreter_Inspector::IsInActiveExcecution(Game_Event const& ev, bool background_only) { + if (!background_only) { + //TODO + } + if (!ev.IsActive() || ev.GetTrigger() != lcf::rpg::EventPage::Trigger_parallel) { + return false; + } + auto pg = ev.GetActivePage(); + if (pg == nullptr || pg->event_commands.empty()) + return false; + return ev.interpreter && ev.interpreter->IsRunning(); +} + +bool Game_Interpreter_Inspector::IsInActiveExcecution(Game_CommonEvent const& ce, bool background_only) { + if (!background_only) { + //TODO + } + if (!ce.IsWaitingBackgroundExecution(false)) { + return false; + } + return ce.interpreter && ce.interpreter->IsRunning(); +} diff --git a/src/game_interpreter.h b/src/game_interpreter.h index f7572b92f0..524695ac5c 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -351,7 +351,23 @@ class Game_Interpreter : public Game_BaseInterpreterContext KeyInputState _keyinput; AsyncOp _async_op = {}; - friend class Scene_Debug; + friend class Game_Interpreter_Inspector; +}; + +class Game_Interpreter_Inspector { +public: + bool IsInActiveExcecution(Game_Event const& ev, bool background_only); + + bool IsInActiveExcecution(Game_CommonEvent const& ce, bool background_only); + + lcf::rpg::SaveEventExecState const& GetForegroundExecState(); + lcf::rpg::SaveEventExecState& GetForegroundExecStateUnsafe(); + + lcf::rpg::SaveEventExecState const& GetExecState(Game_Event const& ev); + lcf::rpg::SaveEventExecState const& GetExecState(Game_CommonEvent const& ce); + + lcf::rpg::SaveEventExecState& GetExecStateUnsafe(Game_Event& ev); + lcf::rpg::SaveEventExecState& GetExecStateUnsafe(Game_CommonEvent& ce); }; inline const lcf::rpg::SaveEventExecFrame* Game_Interpreter::GetFramePtr() const { diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp index 39dd528cfe..7659af118d 100644 --- a/src/game_interpreter_debug.cpp +++ b/src/game_interpreter_debug.cpp @@ -25,6 +25,8 @@ #include Debug::ParallelInterpreterStates Debug::ParallelInterpreterStates::GetCachedStates() { + Game_Interpreter_Inspector inspector; + std::vector ev_ids; std::vector ce_ids; @@ -33,16 +35,19 @@ Debug::ParallelInterpreterStates Debug::ParallelInterpreterStates::GetCachedStat if (Game_Map::GetMapId() > 0) { for (auto& ev : Game_Map::GetEvents()) { - if (ev.GetTrigger() != lcf::rpg::EventPage::Trigger_parallel || !ev.interpreter) + if (!inspector.IsInActiveExcecution(ev, true)) { continue; + } + ev_ids.emplace_back(ev.GetId()); - state_ev.emplace_back(ev.interpreter->GetState()); + state_ev.emplace_back(inspector.GetExecState(ev)); } for (auto& ce : Game_Map::GetCommonEvents()) { - if (ce.IsWaitingBackgroundExecution(false)) { - ce_ids.emplace_back(ce.common_event_id); - state_ce.emplace_back(ce.interpreter->GetState()); + if (!inspector.IsInActiveExcecution(ce, true)) { + continue; } + ce_ids.emplace_back(ce.GetId()); + state_ce.emplace_back(inspector.GetExecState(ce)); } } else if (Game_Battle::IsBattleRunning() && Player::IsPatchManiac()) { //FIXME: Not implemented: battle common events @@ -135,3 +140,10 @@ std::string Debug::FormatEventName(Game_Character const& ch) { } return ""; } + +std::string Debug::FormatEventName(Game_CommonEvent const& ce) { + if (ce.GetName().empty()) { + return fmt::format("CE{:04d}", ce.GetIndex()); + } + return fmt::format("CE{:04d}: '{}'", ce.GetIndex(), ce.GetName()); +} diff --git a/src/game_interpreter_debug.h b/src/game_interpreter_debug.h index 7e3bd2de48..ac4e3332c8 100644 --- a/src/game_interpreter_debug.h +++ b/src/game_interpreter_debug.h @@ -66,6 +66,8 @@ namespace Debug { std::vector CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state); std::string FormatEventName(Game_Character const& ev); + + std::string FormatEventName(Game_CommonEvent const& ce); } #endif diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 38e926bd48..2f10d01a27 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -863,7 +863,7 @@ void Scene_Debug::UpdateRangeListWindow() { skip_items--; continue; } - auto& [evt_id, state] = bg_states.GetEventInterpreter(i); + const auto& [evt_id, state] = bg_states.GetEventInterpreter(i); addItem(fmt::format("{}EV{:04d}: {}", state.wait_movement ? "(W) " : "", evt_id, Game_Map::GetEvent(evt_id)->GetName())); count_items++; } @@ -872,7 +872,7 @@ void Scene_Debug::UpdateRangeListWindow() { skip_items--; continue; } - auto& [ce_id, state] = bg_states.GetCommonEventInterpreter(i); + const auto& [ce_id, state] = bg_states.GetCommonEventInterpreter(i); auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, ce_id); addItem(fmt::format("{}CE{:04d}: {}", state.wait_movement ? "(W) " : "", ce_id, ce->name)); count_items++; @@ -1231,18 +1231,14 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { first_line = Game_Battle::IsBattleRunning() ? "Foreground (Battle)" : "Foreground (Map)"; valid = true; } else if (index <= bg_states.CountEventInterpreters()) { - auto tuple = bg_states.GetEventInterpreter(index - 1); - evt_id = std::get<0>(tuple); - state = std::get<1>(tuple); - first_line = fmt::format("EV{:04d}: {}", evt_id, Game_Map::GetEvent(evt_id)->GetName()); + const auto& [evt_id, state] = bg_states.GetEventInterpreter(index - 1); + first_line = Debug::FormatEventName(*Game_Map::GetEvent(evt_id)); valid = true; } else if ((index - bg_states.CountEventInterpreters()) <= bg_states.CountCommonEventInterpreters()) { - auto tuple = bg_states.GetCommonEventInterpreter(index - bg_states.CountEventInterpreters() - 1); - int ce_id = std::get<0>(tuple); - state = std::get<1>(tuple); + const auto& [ce_id, state] = bg_states.GetCommonEventInterpreter(index - bg_states.CountEventInterpreters() - 1); for (auto& ce : Game_Map::GetCommonEvents()) { - if (ce.common_event_id == ce_id) { - first_line = fmt::format("CE{:04d}: {}", ce_id, ce.GetName()); + if (ce.GetId() == ce_id) { + first_line = Debug::FormatEventName(ce); evt_id = ce_id; valid = true; break; @@ -1259,30 +1255,32 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { } } -lcf::rpg::SaveEventExecFrame& Scene_Debug::GetSelectedInterpreterFrameFromUiState() const { +lcf::rpg::SaveEventExecFrame const& Scene_Debug::GetSelectedInterpreterFrameFromUiState() const { static lcf::rpg::SaveEventExecFrame empty; if (state_interpreter.selected_state <= 0 || state_interpreter.selected_frame < 0) { return empty; } + Game_Interpreter_Inspector inspector; + int index = state_interpreter.selected_state; const auto& bg_states = state_interpreter.background_states; if (index == 1) { - auto& state = Game_Interpreter::GetForegroundInterpreter()._state; + auto const& state = inspector.GetForegroundExecState(); return state.stack[state_interpreter.selected_frame]; } else if (index <= bg_states.CountEventInterpreters()) { int evt_id = std::get<0>(bg_states.GetEventInterpreter(index - 1)); auto ev = Game_Map::GetEvent(evt_id); - auto& state = ev->interpreter->_state; + auto const& state = inspector.GetExecState(*ev); return state.stack[state_interpreter.selected_frame]; } else if ((index - bg_states.CountEventInterpreters()) <= bg_states.CountCommonEventInterpreters()) { int ce_id = std::get<0>(bg_states.GetEventInterpreter(index - bg_states.CountEventInterpreters() - 1)); for (auto& ce : Game_Map::GetCommonEvents()) { - if (ce.common_event_id == ce_id) { - auto& state = ce.interpreter->_state; + if (ce.GetId() == ce_id) { + auto const& state = inspector.GetExecState(ce); return state.stack[state_interpreter.selected_frame]; } } diff --git a/src/scene_debug.h b/src/scene_debug.h index c7c726059e..a3efc68e6e 100644 --- a/src/scene_debug.h +++ b/src/scene_debug.h @@ -191,7 +191,7 @@ class Scene_Debug : public Scene { bool interpreter_states_cached = false; void UpdateInterpreterWindow(int index); - lcf::rpg::SaveEventExecFrame& GetSelectedInterpreterFrameFromUiState() const; + lcf::rpg::SaveEventExecFrame const& GetSelectedInterpreterFrameFromUiState() const; struct { Debug::ParallelInterpreterStates background_states; From 3180a524cce3312204c661c84d79b715d470e8d3 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 5 Feb 2025 20:46:22 +0100 Subject: [PATCH 04/16] =?UTF-8?q?Refactored=20Game=5FIntepreter::Push=20&?= =?UTF-8?q?=20implemented=20Maniac=C2=B4s=20special=20field=20"maniac=5Fev?= =?UTF-8?q?ent=5Finfo"=20which=20holds=20info=20about=20the=20current=20in?= =?UTF-8?q?terpreter=20frame.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/game_commonevent.cpp | 2 +- src/game_event.cpp | 2 +- src/game_interpreter.cpp | 52 +++++++++++++++++++++++++-------- src/game_interpreter.h | 18 +++++++----- src/game_interpreter_battle.cpp | 12 ++++---- src/game_interpreter_shared.h | 34 +++++++++++++++++++++ src/game_map.cpp | 24 +++++++++++++-- src/scene_debug.cpp | 12 ++++---- 8 files changed, 121 insertions(+), 35 deletions(-) diff --git a/src/game_commonevent.cpp b/src/game_commonevent.cpp index d15ab4dc59..3ebf9a2b46 100644 --- a/src/game_commonevent.cpp +++ b/src/game_commonevent.cpp @@ -32,7 +32,7 @@ Game_CommonEvent::Game_CommonEvent(int common_event_id) : if (ce->trigger == lcf::rpg::EventPage::Trigger_parallel && !ce->event_commands.empty()) { interpreter.reset(new Game_Interpreter_Map()); - interpreter->Push(this); + interpreter->Push(this, InterpreterExecutionType::Parallel); } diff --git a/src/game_event.cpp b/src/game_event.cpp index a409e6aece..1028827d2d 100644 --- a/src/game_event.cpp +++ b/src/game_event.cpp @@ -587,7 +587,7 @@ AsyncOp Game_Event::Update(bool resume_async) { // the wait will tick by 1 each time the interpreter is invoked. if ((resume_async || GetTrigger() == lcf::rpg::EventPage::Trigger_parallel) && interpreter) { if (!interpreter->IsRunning() && page && !page->event_commands.empty()) { - interpreter->Push(this); + interpreter->Push(this, InterpreterExecutionType::Parallel); } interpreter->Update(!resume_async); diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 0946f802fe..4cc8917aad 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -101,9 +101,9 @@ bool Game_Interpreter::IsRunning() const { // Setup. void Game_Interpreter::Push( + InterpreterPush push_info, std::vector _list, int event_id, - bool started_by_decision_key, int event_page_id ) { if (_list.empty()) { @@ -114,15 +114,28 @@ void Game_Interpreter::Push( Output::Error("Call Event limit ({}) has been exceeded", call_stack_limit); } + auto type_ex = std::get(push_info); + auto type_src = std::get(push_info); + lcf::rpg::SaveEventExecFrame frame; frame.ID = _state.stack.size() + 1; frame.commands = std::move(_list); frame.current_command = 0; - frame.triggered_by_decision_key = started_by_decision_key; - frame.event_id = event_id; + frame.triggered_by_decision_key = type_ex == ExecutionType::Action; + if (type_src == EventType::MapEvent) { + frame.event_id = event_id; + } frame.maniac_event_id = event_id; frame.maniac_event_page_id = event_page_id; + if (type_ex <= ExecutionType::BattleParallel) { + frame.maniac_event_info = static_cast(type_ex); + } + + if (type_src <= EventType::BattleEvent) { + frame.maniac_event_info |= static_cast(type_src); + } + if (_state.stack.empty() && main_flag && !Game_Battle::IsBattleRunning()) { Main_Data::game_system->ClearMessageFace(); Main_Data::game_player->SetMenuCalling(false); @@ -536,16 +549,31 @@ void Game_Interpreter::Update(bool reset_loop_count) { } // Setup Starting Event -void Game_Interpreter::Push(Game_Event* ev) { - Push(ev->GetList(), ev->GetId(), ev->WasStartedByDecisionKey(), ev->GetActivePage() ? ev->GetActivePage()->ID : 0); +void Game_Interpreter::Push(Game_Event* ev, ExecutionType ex_type) { + assert(ex_type <= ExecutionType::Call || ex_type == ExecutionType::DebugCall); + + Push( + { ex_type, EventType::MapEvent }, + ev->GetList(), ev->GetId(), ev->GetActivePage() ? ev->GetActivePage()->ID : 0 + ); } -void Game_Interpreter::Push(Game_Event* ev, const lcf::rpg::EventPage* page, bool triggered_by_decision_key) { - Push(page->event_commands, ev->GetId(), triggered_by_decision_key, page->ID); +void Game_Interpreter::Push(Game_Event* ev, const lcf::rpg::EventPage* page, ExecutionType ex_type) { + assert(ex_type <= ExecutionType::Call || ex_type == ExecutionType::DebugCall); + + Push( + { ex_type, EventType::MapEvent }, + page->event_commands, ev->GetId(), page->ID + ); } -void Game_Interpreter::Push(Game_CommonEvent* ev) { - Push(ev->GetList(), 0, false); +void Game_Interpreter::Push(Game_CommonEvent* ev, ExecutionType ex_type) { + assert(ex_type == ExecutionType::AutoStart || ex_type == ExecutionType::Parallel + || ex_type == ExecutionType::Call || ex_type == ExecutionType::DeathHandler + || ex_type == ExecutionType::DebugCall + ); + + Push({ ex_type, EventType::CommonEvent }, ev->GetList(), ev->GetId()); } bool Game_Interpreter::CheckGameOver() { @@ -3984,7 +4012,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { // return true; } - Push(common_event); + Push(common_event, ExecutionType::Call); return true; } @@ -4012,7 +4040,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { // return true; } - Push(page->event_commands, event->GetId(), false, page->ID); + Push({ ExecutionType::Call, EventType::MapEvent }, page->event_commands, event->GetId(), page->ID); return true; } @@ -5382,7 +5410,7 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co // Our implementation pushes a new frame containing the command instead of invoking it directly. // This is incompatible to Maniacs but has a better compatibility with our code. - Push({ cmd }, GetCurrentEventId(), false); //FIXME: add some new flag, so the interpreter debug view (window_interpreter) can differentiate this frame from normal ones + Push({ ExecutionType::Eval, EventType::None }, { cmd }, GetCurrentEventId(), 0); return true; } diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 524695ac5c..9c0832de07 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -38,6 +38,9 @@ class Game_Event; class Game_CommonEvent; class PendingMessage; + +using InterpreterPush = std::tuple; + /** * Game_Interpreter class */ @@ -65,14 +68,15 @@ class Game_Interpreter : public Game_BaseInterpreterContext void Update(bool reset_loop_count=true); void Push( - std::vector _list, - int _event_id, - bool started_by_decision_key = false, - int event_page_id = 0 + InterpreterPush push_info, + std::vector _list, + int _event_id, + int event_page_id = 0 ); - void Push(Game_Event* ev); - void Push(Game_Event* ev, const lcf::rpg::EventPage* page, bool triggered_by_decision_key); - void Push(Game_CommonEvent* ev); + + void Push(Game_Event* ev, InterpreterExecutionType ex_type); + void Push(Game_Event* ev, const lcf::rpg::EventPage* page, InterpreterExecutionType ex_type); + void Push(Game_CommonEvent* ev, InterpreterExecutionType ex_type); void InputButton(); void SetupChoices(const std::vector& choices, int indent, PendingMessage& pm); diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index ba807ef747..f89d330c65 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -33,6 +33,8 @@ #include #include "scene_battle.h" +using namespace Game_Interpreter_Shared; + enum BranchBattleSubcommand { eOptionBranchBattleElse = 1 }; @@ -211,7 +213,7 @@ int Game_Interpreter_Battle::ScheduleNextPage(lcf::rpg::TroopPageCondition::Flag continue; } Clear(); - Push(page.event_commands, 0); + Push({ ExecutionType::Eval, EventType::None }, page.event_commands, 0); // FIXME: clarify type_src & type_ex for battle events executed[i] = true; return i + 1; } @@ -275,7 +277,7 @@ bool Game_Interpreter_Battle::CommandCallCommonEvent(lcf::rpg::EventCommand cons return true; } - Push(common_event); + Push(common_event, ExecutionType::Call); return true; } @@ -643,9 +645,9 @@ bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, i Output::Warning("CommandManiacControlBattle: Can't call invalid common event {}", common_event_id); return false; } - + // pushes the common event to be run into the queue of events. - maniac_interpreter->Push(common_event); + maniac_interpreter->Push(common_event, ExecutionType::Call); // FIXME: clarify type_src & type_ex for battle events // pushes the change variable events into the interpreters // event queue, so we don't run into a race condition. @@ -683,7 +685,7 @@ bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, i } // Push is actually "push_back", so this gets added before other events. - maniac_interpreter->Push(pre_commands, 0); + maniac_interpreter->Push({ ExecutionType::Eval, EventType::None }, pre_commands, 0); // FIXME: clarify type_src & type_ex for battle events // Necessary to start the sub-event. maniac_interpreter->Update(); diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index 15bad41e5d..9d82cec953 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -34,6 +34,37 @@ class Game_BaseInterpreterContext; namespace Game_Interpreter_Shared { + enum class EventType { + None = 0, + MapEvent, + CommonEvent, + BattleEvent + }; + + enum class ExecutionType { + /* + * MapEvent Triggered by decision key + * (or via custom command EasyRpg_TriggerEventAt) + */ + Action = 0, + Touch, + Collision, + AutoStart, + Parallel, + /* Frame was pushed via "CallCommand" */ + Call, + /* Maniac's special CE type "Battle start" */ + BattleStart, + /* Maniac's special CE type "Battle Parallel" */ + BattleParallel, + + /* 2k3 Death Handler */ + DeathHandler = 10, + /* Event code was dynamically evaluated. (ManiacCallCommand) */ + Eval, + DebugCall + }; + /* * Indicates how the target of an interpreter operation (lvalue) should be evaluated. */ @@ -213,4 +244,7 @@ class Game_BaseInterpreterContext { } }; +using InterpreterExecutionType = Game_Interpreter_Shared::ExecutionType; +using InterpreterEventType = Game_Interpreter_Shared::EventType; + #endif diff --git a/src/game_map.cpp b/src/game_map.cpp index 780d26aaf5..3950eefd79 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -1393,7 +1393,7 @@ bool Game_Map::UpdateForegroundEvents(MapUpdateAsyncContext& actx) { } } if (run_ce) { - interp.Push(run_ce); + interp.Push(run_ce, InterpreterExecutionType::AutoStart); } Game_Event* run_ev = nullptr; @@ -1408,7 +1408,25 @@ bool Game_Map::UpdateForegroundEvents(MapUpdateAsyncContext& actx) { } } if (run_ev) { - interp.Push(run_ev); + if (run_ev->WasStartedByDecisionKey()) { + interp.Push(run_ev, InterpreterExecutionType::Action); + } else { + switch (run_ev->GetTrigger()) { + case lcf::rpg::EventPage::Trigger_touched: + interp.Push(run_ev, InterpreterExecutionType::Touch); + break; + case lcf::rpg::EventPage::Trigger_collision: + interp.Push(run_ev, InterpreterExecutionType::Collision); + break; + case lcf::rpg::EventPage::Trigger_auto_start: + interp.Push(run_ev, InterpreterExecutionType::AutoStart); + break; + case lcf::rpg::EventPage::Trigger_action: + default: + interp.Push(run_ev, InterpreterExecutionType::Action); + break; + } + } run_ev->ClearWaitingForegroundExecution(); } @@ -1558,7 +1576,7 @@ static void OnEncounterEnd(BattleResult result) { auto* ce = lcf::ReaderUtil::GetElement(common_events, Game_Battle::GetDeathHandlerCommonEvent()); if (ce) { auto& interp = Game_Map::GetInterpreter(); - interp.Push(ce); + interp.Push(ce, InterpreterExecutionType::DeathHandler); } auto tt = Game_Battle::GetDeathHandlerTeleport(); diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 2f10d01a27..a897f7ee37 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -1128,11 +1128,11 @@ void Scene_Debug::DoCallCommonEvent() { auto& ce = Game_Map::GetCommonEvents()[ceid - 1]; if (Game_Battle::IsBattleRunning()) { - Game_Battle::GetInterpreter().Push(&ce); + Game_Battle::GetInterpreter().Push(&ce, InterpreterExecutionType::DebugCall); Scene::PopUntil(Scene::Battle); Output::Debug("Debug Scene Forced execution of common event {} on the battle foreground interpreter.", ce.GetIndex()); } else { - Game_Map::GetInterpreter().Push(&ce); + Game_Map::GetInterpreter().Push(&ce, InterpreterExecutionType::DebugCall); Scene::PopUntil(Scene::Map); Output::Debug("Debug Scene Forced execution of common event {} on the map foreground interpreter.", ce.GetIndex()); } @@ -1157,11 +1157,11 @@ void Scene_Debug::DoCallMapEvent() { } if (Game_Battle::IsBattleRunning()) { - Game_Battle::GetInterpreter().Push(me, page, false); + Game_Battle::GetInterpreter().Push(me, page, InterpreterExecutionType::DebugCall); Scene::PopUntil(Scene::Battle); Output::Debug("Debug Scene Forced execution of map event {} page {} on the battle foreground interpreter.", me->GetId(), page->ID); } else { - Game_Map::GetInterpreter().Push(me, page, false); + Game_Map::GetInterpreter().Push(me, page, InterpreterExecutionType::DebugCall); Scene::PopUntil(Scene::Map); Output::Debug("Debug Scene Forced execution of map event {} page {} on the map foreground interpreter.", me->GetId(), page->ID); } @@ -1185,9 +1185,9 @@ void Scene_Debug::DoCallBattleEvent() { auto& page = troop->pages[page_idx]; - Game_Battle::GetInterpreter().Push(page.event_commands, 0, false); + Game_Battle::GetInterpreter().Push({ InterpreterExecutionType::DebugCall, InterpreterEventType::BattleEvent }, page.event_commands, 0, false); Scene::PopUntil(Scene::Battle); - Output::Debug("Debug Scene Forced execution of battle troop {} event page {} on the map foreground interpreter.", troop->ID, page.ID); + Output::Debug("Debug Scene Forced execution of battle troop {} event page {} on the battle foreground interpreter.", troop->ID, page.ID); } void Scene_Debug::DoOpenMenu() { From 7e4b42076e47fe441fd6d5f965c1f21435b79760 Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 6 Feb 2025 17:14:24 +0100 Subject: [PATCH 05/16] Implemented missing Maniac subcommand "GetGameInfo" -> "Get Command Interpreter State" --- src/game_interpreter.cpp | 28 +++++++++++++++++++++++++--- src/game_interpreter_shared.h | 27 ++++++++++++++++++++++++--- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 4cc8917aad..adce1d9857 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -133,7 +133,7 @@ void Game_Interpreter::Push( } if (type_src <= EventType::BattleEvent) { - frame.maniac_event_info |= static_cast(type_src); + frame.maniac_event_info |= (static_cast(type_src) << 4); } if (_state.stack.empty() && main_flag && !Game_Battle::IsBattleRunning()) { @@ -4208,8 +4208,30 @@ bool Game_Interpreter::CommandManiacGetGameInfo(lcf::rpg::EventCommand const& co Output::Warning("GetGameInfo: Option 'Pixel Info' not implemented."); break; case 4: // Get command interpreter state - // FIXME: figure out how 'command interpreter state' works - Output::Warning("GetGameInfo: Option 'Command Interpreter State' not implemented."); + { + // Parameter "Nest" in the English version of Maniacs + // This value specifies how far you'd want to go back the stack + int peek = ValueOrVariableBitfield(com.parameters[0], 2, com.parameters[4]); + + //First set everything to '0' + Main_Data::game_variables->SetRange(var, var + 4, 0); + + int stack_no = _state.stack.size() - peek; + if (stack_no > 0) { + auto frame = &_state.stack[stack_no - 1]; + + // Note: It looks like for Battles, Maniacs doesn't give out any detailed interpreter + // information via this command (only the current command line: frame->current_command) + // The others are implemented here nonetheless for consistency. + // (This is true for both the normal "Troop" events & the new "Battle Start"/"Battle Parallel" execution types) + + Main_Data::game_variables->Set(var, static_cast(ManiacEventType(*frame))); + Main_Data::game_variables->Set(var + 1, frame->maniac_event_id); + Main_Data::game_variables->Set(var + 2, frame->maniac_event_page_id); + Main_Data::game_variables->Set(var + 3, static_cast(ManiacExecutionType(*frame))); + Main_Data::game_variables->Set(var + 4, frame->current_command + 1); + } + } break; case 5: // Get tileset ID Main_Data::game_variables->Set(var, Game_Map::GetChipset()); diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index 9d82cec953..d0b3cac5ad 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -146,6 +146,9 @@ namespace Game_Interpreter_Shared { std::optional GetRuntimeFlag(lcf::rpg::SaveEventExecState::EasyRpgStateRuntime_Flags const& state_runtime_flags, StateRuntimeFlagRef const field_on, StateRuntimeFlagRef const field_off); #endif + + ExecutionType ManiacExecutionType(lcf::rpg::SaveEventExecFrame const& frame); + EventType ManiacEventType(lcf::rpg::SaveEventExecFrame const& frame); } inline bool Game_Interpreter_Shared::CheckOperator(int val, int val2, int op) { @@ -184,6 +187,27 @@ inline bool Game_Interpreter_Shared::ManiacCheckContinueLoop(int val, int val2, } } +using InterpreterExecutionType = Game_Interpreter_Shared::ExecutionType; +using InterpreterEventType = Game_Interpreter_Shared::EventType; + +inline InterpreterExecutionType Game_Interpreter_Shared::ManiacExecutionType(lcf::rpg::SaveEventExecFrame const& frame) { + if (int type_ex = (frame.maniac_event_info & 0xF); type_ex <= static_cast(ExecutionType::BattleParallel)) { + return static_cast(type_ex); + } + return InterpreterExecutionType::Action; +} + +inline InterpreterEventType Game_Interpreter_Shared::ManiacEventType(lcf::rpg::SaveEventExecFrame const& frame) { + if ((frame.maniac_event_info & 0x10) > 0) { + return InterpreterEventType::MapEvent; + } else if ((frame.maniac_event_info & 0x20) > 0) { + return InterpreterEventType::CommonEvent; + } else if ((frame.maniac_event_info & 0x40) > 0) { + return InterpreterEventType::BattleEvent; + } + return InterpreterEventType::None; +} + class Game_BaseInterpreterContext { public: virtual ~Game_BaseInterpreterContext() {} @@ -244,7 +268,4 @@ class Game_BaseInterpreterContext { } }; -using InterpreterExecutionType = Game_Interpreter_Shared::ExecutionType; -using InterpreterEventType = Game_Interpreter_Shared::EventType; - #endif From d3cb1309b680dfcf429a4ca8e540d43cffb054c3 Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 6 Feb 2025 17:37:36 +0100 Subject: [PATCH 06/16] Refactored "Interpreter.Push" some more, so that we could potentially catch some undefined or incorrect types of interpreter pushes at compile time. --- src/game_commonevent.cpp | 2 +- src/game_event.cpp | 2 +- src/game_interpreter.cpp | 29 +++++++------------ src/game_interpreter.h | 51 ++++++++++++++++++++++++++++++--- src/game_interpreter_battle.cpp | 8 +++--- src/game_map.cpp | 14 ++++----- src/scene_debug.cpp | 10 +++---- 7 files changed, 75 insertions(+), 41 deletions(-) diff --git a/src/game_commonevent.cpp b/src/game_commonevent.cpp index 3ebf9a2b46..5f24e46b26 100644 --- a/src/game_commonevent.cpp +++ b/src/game_commonevent.cpp @@ -32,7 +32,7 @@ Game_CommonEvent::Game_CommonEvent(int common_event_id) : if (ce->trigger == lcf::rpg::EventPage::Trigger_parallel && !ce->event_commands.empty()) { interpreter.reset(new Game_Interpreter_Map()); - interpreter->Push(this, InterpreterExecutionType::Parallel); + interpreter->Push(this); } diff --git a/src/game_event.cpp b/src/game_event.cpp index 1028827d2d..039e232104 100644 --- a/src/game_event.cpp +++ b/src/game_event.cpp @@ -587,7 +587,7 @@ AsyncOp Game_Event::Update(bool resume_async) { // the wait will tick by 1 each time the interpreter is invoked. if ((resume_async || GetTrigger() == lcf::rpg::EventPage::Trigger_parallel) && interpreter) { if (!interpreter->IsRunning() && page && !page->event_commands.empty()) { - interpreter->Push(this, InterpreterExecutionType::Parallel); + interpreter->Push(this); } interpreter->Update(!resume_async); diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index adce1d9857..5b9aca117b 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -100,7 +100,7 @@ bool Game_Interpreter::IsRunning() const { } // Setup. -void Game_Interpreter::Push( +void Game_Interpreter::PushInternal( InterpreterPush push_info, std::vector _list, int event_id, @@ -549,31 +549,22 @@ void Game_Interpreter::Update(bool reset_loop_count) { } // Setup Starting Event -void Game_Interpreter::Push(Game_Event* ev, ExecutionType ex_type) { - assert(ex_type <= ExecutionType::Call || ex_type == ExecutionType::DebugCall); - - Push( +void Game_Interpreter::PushInternal(Game_Event* ev, ExecutionType ex_type) { + PushInternal( { ex_type, EventType::MapEvent }, ev->GetList(), ev->GetId(), ev->GetActivePage() ? ev->GetActivePage()->ID : 0 ); } -void Game_Interpreter::Push(Game_Event* ev, const lcf::rpg::EventPage* page, ExecutionType ex_type) { - assert(ex_type <= ExecutionType::Call || ex_type == ExecutionType::DebugCall); - - Push( +void Game_Interpreter::PushInternal(Game_Event* ev, const lcf::rpg::EventPage* page, ExecutionType ex_type) { + PushInternal( { ex_type, EventType::MapEvent }, page->event_commands, ev->GetId(), page->ID ); } -void Game_Interpreter::Push(Game_CommonEvent* ev, ExecutionType ex_type) { - assert(ex_type == ExecutionType::AutoStart || ex_type == ExecutionType::Parallel - || ex_type == ExecutionType::Call || ex_type == ExecutionType::DeathHandler - || ex_type == ExecutionType::DebugCall - ); - - Push({ ex_type, EventType::CommonEvent }, ev->GetList(), ev->GetId()); +void Game_Interpreter::PushInternal(Game_CommonEvent* ev, ExecutionType ex_type) { + PushInternal({ ex_type, EventType::CommonEvent }, ev->GetList(), ev->GetId()); } bool Game_Interpreter::CheckGameOver() { @@ -4012,7 +4003,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { // return true; } - Push(common_event, ExecutionType::Call); + Push(common_event); return true; } @@ -4040,7 +4031,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { // return true; } - Push({ ExecutionType::Call, EventType::MapEvent }, page->event_commands, event->GetId(), page->ID); + Push(page->event_commands, event->GetId(), page->ID); return true; } @@ -5432,7 +5423,7 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co // Our implementation pushes a new frame containing the command instead of invoking it directly. // This is incompatible to Maniacs but has a better compatibility with our code. - Push({ ExecutionType::Eval, EventType::None }, { cmd }, GetCurrentEventId(), 0); + Push({ cmd }, GetCurrentEventId(), 0); return true; } diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 9c0832de07..4e14d1a7bf 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -67,16 +67,21 @@ class Game_Interpreter : public Game_BaseInterpreterContext void Update(bool reset_loop_count=true); + template void Push( - InterpreterPush push_info, std::vector _list, int _event_id, int event_page_id = 0 ); - void Push(Game_Event* ev, InterpreterExecutionType ex_type); - void Push(Game_Event* ev, const lcf::rpg::EventPage* page, InterpreterExecutionType ex_type); - void Push(Game_CommonEvent* ev, InterpreterExecutionType ex_type); + template + void Push(Game_Event* ev); + + template + void Push(Game_Event* ev, const lcf::rpg::EventPage* page); + + template + void Push(Game_CommonEvent* ev); void InputButton(); void SetupChoices(const std::vector& choices, int indent, PendingMessage& pm); @@ -355,6 +360,18 @@ class Game_Interpreter : public Game_BaseInterpreterContext KeyInputState _keyinput; AsyncOp _async_op = {}; + private: + void PushInternal( + InterpreterPush push_info, + std::vector _list, + int _event_id, + int event_page_id = 0 + ); + + void PushInternal(Game_Event* ev, InterpreterExecutionType ex_type); + void PushInternal(Game_Event* ev, const lcf::rpg::EventPage* page, InterpreterExecutionType ex_type); + void PushInternal(Game_CommonEvent* ev, InterpreterExecutionType ex_type); + friend class Game_Interpreter_Inspector; }; @@ -374,6 +391,32 @@ class Game_Interpreter_Inspector { lcf::rpg::SaveEventExecState& GetExecStateUnsafe(Game_CommonEvent& ce); }; +template +inline void Game_Interpreter::Push(std::vector _list, int _event_id, int event_page_id) { + PushInternal({ type_ex, type_ev }, _list, _event_id, event_page_id); +} + +template +inline void Game_Interpreter::Push(Game_Event* ev) { + static_assert(type_ex <= InterpreterExecutionType::Call || type_ex == InterpreterExecutionType::DebugCall, "Unexpected ExecutionType for MapEvent"); + PushInternal(ev, type_ex); +} + +template +inline void Game_Interpreter::Push(Game_Event* ev, const lcf::rpg::EventPage* page) { + static_assert(type_ex <= InterpreterExecutionType::Call || type_ex == InterpreterExecutionType::DebugCall, "Unexpected ExecutionType for MapEvent"); + PushInternal(ev, page, type_ex); +} + +template +inline void Game_Interpreter::Push(Game_CommonEvent* ev) { + static_assert(type_ex == InterpreterExecutionType::AutoStart || type_ex == InterpreterExecutionType::Parallel + || type_ex == InterpreterExecutionType::Call || type_ex == InterpreterExecutionType::DeathHandler + || type_ex == InterpreterExecutionType::DebugCall, "Unexpected ExecutionType for CommonEvent" + ); + PushInternal(ev, type_ex); +} + inline const lcf::rpg::SaveEventExecFrame* Game_Interpreter::GetFramePtr() const { return !_state.stack.empty() ? &_state.stack.back() : nullptr; } diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index f89d330c65..00eb66be89 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -213,7 +213,7 @@ int Game_Interpreter_Battle::ScheduleNextPage(lcf::rpg::TroopPageCondition::Flag continue; } Clear(); - Push({ ExecutionType::Eval, EventType::None }, page.event_commands, 0); // FIXME: clarify type_src & type_ex for battle events + Push(page.event_commands, 0); // FIXME: clarify type_src & type_ex for battle events executed[i] = true; return i + 1; } @@ -277,7 +277,7 @@ bool Game_Interpreter_Battle::CommandCallCommonEvent(lcf::rpg::EventCommand cons return true; } - Push(common_event, ExecutionType::Call); + Push(common_event); return true; } @@ -647,7 +647,7 @@ bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, i } // pushes the common event to be run into the queue of events. - maniac_interpreter->Push(common_event, ExecutionType::Call); // FIXME: clarify type_src & type_ex for battle events + maniac_interpreter->Push(common_event); // FIXME: clarify type_src & type_ex for battle events // pushes the change variable events into the interpreters // event queue, so we don't run into a race condition. @@ -685,7 +685,7 @@ bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, i } // Push is actually "push_back", so this gets added before other events. - maniac_interpreter->Push({ ExecutionType::Eval, EventType::None }, pre_commands, 0); // FIXME: clarify type_src & type_ex for battle events + maniac_interpreter->Push(pre_commands, 0); // FIXME: clarify type_src & type_ex for battle events // Necessary to start the sub-event. maniac_interpreter->Update(); diff --git a/src/game_map.cpp b/src/game_map.cpp index 3950eefd79..4f7ad5f82c 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -1393,7 +1393,7 @@ bool Game_Map::UpdateForegroundEvents(MapUpdateAsyncContext& actx) { } } if (run_ce) { - interp.Push(run_ce, InterpreterExecutionType::AutoStart); + interp.Push(run_ce); } Game_Event* run_ev = nullptr; @@ -1409,21 +1409,21 @@ bool Game_Map::UpdateForegroundEvents(MapUpdateAsyncContext& actx) { } if (run_ev) { if (run_ev->WasStartedByDecisionKey()) { - interp.Push(run_ev, InterpreterExecutionType::Action); + interp.Push(run_ev); } else { switch (run_ev->GetTrigger()) { case lcf::rpg::EventPage::Trigger_touched: - interp.Push(run_ev, InterpreterExecutionType::Touch); + interp.Push(run_ev); break; case lcf::rpg::EventPage::Trigger_collision: - interp.Push(run_ev, InterpreterExecutionType::Collision); + interp.Push(run_ev); break; case lcf::rpg::EventPage::Trigger_auto_start: - interp.Push(run_ev, InterpreterExecutionType::AutoStart); + interp.Push(run_ev); break; case lcf::rpg::EventPage::Trigger_action: default: - interp.Push(run_ev, InterpreterExecutionType::Action); + interp.Push(run_ev); break; } } @@ -1576,7 +1576,7 @@ static void OnEncounterEnd(BattleResult result) { auto* ce = lcf::ReaderUtil::GetElement(common_events, Game_Battle::GetDeathHandlerCommonEvent()); if (ce) { auto& interp = Game_Map::GetInterpreter(); - interp.Push(ce, InterpreterExecutionType::DeathHandler); + interp.Push(ce); } auto tt = Game_Battle::GetDeathHandlerTeleport(); diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index a897f7ee37..cd8fcd7576 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -1128,11 +1128,11 @@ void Scene_Debug::DoCallCommonEvent() { auto& ce = Game_Map::GetCommonEvents()[ceid - 1]; if (Game_Battle::IsBattleRunning()) { - Game_Battle::GetInterpreter().Push(&ce, InterpreterExecutionType::DebugCall); + Game_Battle::GetInterpreter().Push(&ce); Scene::PopUntil(Scene::Battle); Output::Debug("Debug Scene Forced execution of common event {} on the battle foreground interpreter.", ce.GetIndex()); } else { - Game_Map::GetInterpreter().Push(&ce, InterpreterExecutionType::DebugCall); + Game_Map::GetInterpreter().Push(&ce); Scene::PopUntil(Scene::Map); Output::Debug("Debug Scene Forced execution of common event {} on the map foreground interpreter.", ce.GetIndex()); } @@ -1157,11 +1157,11 @@ void Scene_Debug::DoCallMapEvent() { } if (Game_Battle::IsBattleRunning()) { - Game_Battle::GetInterpreter().Push(me, page, InterpreterExecutionType::DebugCall); + Game_Battle::GetInterpreter().Push(me, page); Scene::PopUntil(Scene::Battle); Output::Debug("Debug Scene Forced execution of map event {} page {} on the battle foreground interpreter.", me->GetId(), page->ID); } else { - Game_Map::GetInterpreter().Push(me, page, InterpreterExecutionType::DebugCall); + Game_Map::GetInterpreter().Push(me, page); Scene::PopUntil(Scene::Map); Output::Debug("Debug Scene Forced execution of map event {} page {} on the map foreground interpreter.", me->GetId(), page->ID); } @@ -1185,7 +1185,7 @@ void Scene_Debug::DoCallBattleEvent() { auto& page = troop->pages[page_idx]; - Game_Battle::GetInterpreter().Push({ InterpreterExecutionType::DebugCall, InterpreterEventType::BattleEvent }, page.event_commands, 0, false); + Game_Battle::GetInterpreter().Push(page.event_commands, 0, false); Scene::PopUntil(Scene::Battle); Output::Debug("Debug Scene Forced execution of battle troop {} event page {} on the battle foreground interpreter.", troop->ID, page.ID); } From fd6d1a9033165f3896da1a2db77ea25b8f1962f5 Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 6 Feb 2025 18:25:17 +0100 Subject: [PATCH 07/16] Fix: Interpreter debug window was broken after refactor, Rewrite & simplify the whole thing by using the recently implemented event info fields --- src/game_interpreter_debug.cpp | 90 ++++++++++++++-------------------- src/game_interpreter_debug.h | 11 +++-- src/game_interpreter_shared.h | 12 +++++ src/scene_debug.cpp | 10 ++-- src/window_interpreter.cpp | 41 +++++++++++----- src/window_interpreter.h | 4 +- 6 files changed, 94 insertions(+), 74 deletions(-) diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp index 7659af118d..0be5d2872c 100644 --- a/src/game_interpreter_debug.cpp +++ b/src/game_interpreter_debug.cpp @@ -56,60 +56,26 @@ Debug::ParallelInterpreterStates Debug::ParallelInterpreterStates::GetCachedStat return { ev_ids, ce_ids, state_ev, state_ce }; } -std::vector Debug::CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state) { - std::vector items(state.stack.size()); +std::vector Debug::CreateCallStack(const lcf::rpg::SaveEventExecState& state) { + std::vector items; + items.reserve(state.stack.size()); for (int i = state.stack.size() - 1; i >= 0; i--) { - int evt_id = state.stack[i].event_id; - int page_id = 0; - if (state.stack[i].maniac_event_id > 0) { - evt_id = state.stack[i].maniac_event_id; - page_id = state.stack[i].maniac_event_page_id; - } - if (evt_id == 0 && i == 0) - evt_id = owner_evt_id; - - bool is_calling_ev_ce = false; - - //FIXME: There are some currently unimplemented SaveEventExecFrame fields introduced via the ManiacPatch which should be used to properly get event state information - if (evt_id == 0 && i > 0) { - auto& prev_frame = state.stack[i - 1]; - auto& com = prev_frame.commands[prev_frame.current_command - 1]; - if (com.code == 12330) { // CallEvent - if (com.parameters[0] == 0) { - is_calling_ev_ce = true; - evt_id = com.parameters[1]; - } else if (com.parameters[0] == 3 && Player::IsPatchManiac()) { - is_calling_ev_ce = true; - evt_id = Main_Data::game_variables->Get(com.parameters[1]); - } else if (com.parameters[0] == 4 && Player::IsPatchManiac()) { - is_calling_ev_ce = true; - evt_id = Main_Data::game_variables->GetIndirect(com.parameters[1]); - } - } - } - - auto item = Debug::CallStackItem(); - item.stack_item_no = i + 1; - item.is_ce = is_calling_ev_ce; - item.evt_id = evt_id; - item.page_id = page_id; - item.name = ""; - item.cmd_current = state.stack[i].current_command; - item.cmd_count = state.stack[i].commands.size(); - - if (item.is_ce) { - auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, item.evt_id); - if (ce) { - item.name = ToString(ce->name); - } - } else { - auto* ev = Game_Map::GetEvent(evt_id); - if (ev) { - //FIXME: map could have changed, but map_id isn't available - item.name = ToString(ev->GetName()); - } - } + auto& frame = state.stack[i]; + + bool map_has_changed = (frame.event_id == 0 && frame.maniac_event_id > 0); + + Debug::CallStackItem item = { + Game_Interpreter_Shared::EasyRpgExecutionType(frame), + Game_Interpreter_Shared::EasyRpgEventType(frame), + frame.maniac_event_id, + frame.maniac_event_page_id, + GetEventName(frame), + i + 1, //stack_item_no + frame.current_command, // cmd_current + frame.commands.size(), // cmd_count + map_has_changed + }; items.push_back(item); } @@ -117,6 +83,26 @@ std::vector Debug::CreateCallStack(const int owner_evt_id, return items; } +std::string Debug::GetEventName(const lcf::rpg::SaveEventExecFrame& frame) { + switch (Game_Interpreter_Shared::EasyRpgEventType(frame)) { + case InterpreterEventType::MapEvent: + if (auto* ev = Game_Map::GetEvent(frame.event_id)) { + return ToString(ev->GetName()); + } else if (frame.maniac_event_id > 0) { + return fmt::format("[(EV{:04d}) from another map..]", frame.maniac_event_id); + } + break; + case InterpreterEventType::CommonEvent: + if (auto* ce = lcf::ReaderUtil::GetElement(lcf::Data::commonevents, frame.maniac_event_id)) { + return ToString(ce->name); + } + break; + default: + break; + } + return ""; +} + std::string Debug::FormatEventName(Game_Character const& ch) { switch (ch.GetType()) { case Game_Character::Player: diff --git a/src/game_interpreter_debug.h b/src/game_interpreter_debug.h index ac4e3332c8..1f8674f349 100644 --- a/src/game_interpreter_debug.h +++ b/src/game_interpreter_debug.h @@ -57,13 +57,18 @@ namespace Debug { }; struct CallStackItem { - bool is_ce; + InterpreterExecutionType type_ex; + InterpreterEventType type_ev; int evt_id, page_id; std::string name; - int stack_item_no, cmd_current, cmd_count; + int stack_item_no, cmd_current; + size_t cmd_count; + bool map_has_changed; }; - std::vector CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state); + std::vector CreateCallStack(const lcf::rpg::SaveEventExecState& state); + + std::string GetEventName(const lcf::rpg::SaveEventExecFrame& frame); std::string FormatEventName(Game_Character const& ev); diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index d0b3cac5ad..21941f91a3 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -149,6 +149,9 @@ namespace Game_Interpreter_Shared { ExecutionType ManiacExecutionType(lcf::rpg::SaveEventExecFrame const& frame); EventType ManiacEventType(lcf::rpg::SaveEventExecFrame const& frame); + + ExecutionType EasyRpgExecutionType(lcf::rpg::SaveEventExecFrame const& frame); + EventType EasyRpgEventType(lcf::rpg::SaveEventExecFrame const& frame); } inline bool Game_Interpreter_Shared::CheckOperator(int val, int val2, int op) { @@ -208,6 +211,15 @@ inline InterpreterEventType Game_Interpreter_Shared::ManiacEventType(lcf::rpg::S return InterpreterEventType::None; } +inline InterpreterExecutionType Game_Interpreter_Shared::EasyRpgExecutionType(lcf::rpg::SaveEventExecFrame const& frame) { + return static_cast(frame.maniac_event_info & 0xF); +} + +inline InterpreterEventType Game_Interpreter_Shared::EasyRpgEventType(lcf::rpg::SaveEventExecFrame const& frame) { + // Same as ManiacEventType, because no special new event types exist at the moment + return ManiacEventType(frame); +} + class Game_BaseInterpreterContext { public: virtual ~Game_BaseInterpreterContext() {} diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index cd8fcd7576..6e9150a772 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -1219,7 +1219,7 @@ void Scene_Debug::UpdateArrows() { } void Scene_Debug::UpdateInterpreterWindow(int index) { - lcf::rpg::SaveEventExecState state; + lcf::rpg::SaveEventExecState state_display; std::string first_line = ""; bool valid = false; int evt_id = 0; @@ -1227,15 +1227,17 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { auto& bg_states = state_interpreter.background_states; if (index == 1) { - state = Game_Interpreter::GetForegroundInterpreter().GetState(); + state_display = Game_Interpreter::GetForegroundInterpreter().GetState(); first_line = Game_Battle::IsBattleRunning() ? "Foreground (Battle)" : "Foreground (Map)"; valid = true; } else if (index <= bg_states.CountEventInterpreters()) { const auto& [evt_id, state] = bg_states.GetEventInterpreter(index - 1); first_line = Debug::FormatEventName(*Game_Map::GetEvent(evt_id)); + state_display = state; valid = true; } else if ((index - bg_states.CountEventInterpreters()) <= bg_states.CountCommonEventInterpreters()) { const auto& [ce_id, state] = bg_states.GetCommonEventInterpreter(index - bg_states.CountEventInterpreters() - 1); + state_display = state; for (auto& ce : Game_Map::GetCommonEvents()) { if (ce.GetId() == ce_id) { first_line = Debug::FormatEventName(ce); @@ -1248,10 +1250,10 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { if (valid) { state_interpreter.selected_state = index; - interpreter_window->SetStackState(index > bg_states.CountEventInterpreters(), evt_id, first_line, state); + interpreter_window->SetStackState(first_line, state_display); } else { state_interpreter.selected_state = -1; - interpreter_window->SetStackState(0, 0, "", {}); + interpreter_window->SetStackState("", {}); } } diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index 02cc7d54e7..5c4e62a832 100644 --- a/src/window_interpreter.cpp +++ b/src/window_interpreter.cpp @@ -81,8 +81,8 @@ Window_Interpreter::~Window_Interpreter() { } -void Window_Interpreter::SetStackState(bool is_ce, int owner_evt_id, std::string interpreter_desc, lcf::rpg::SaveEventExecState state) { - this->display_item = { is_ce, owner_evt_id, interpreter_desc }; +void Window_Interpreter::SetStackState(std::string interpreter_desc, lcf::rpg::SaveEventExecState state) { + this->display_item = { interpreter_desc }; this->state = state; } @@ -99,10 +99,7 @@ void Window_Interpreter::Refresh() { int max_cmd_count = 0, max_evt_id = 10, max_page_id = 0; - stack_display_items = Debug::CreateCallStack(display_item.owner_evt_id, state); - if (stack_display_items.size() > 0 && this->display_item.is_ce) { - stack_display_items[0].is_ce = true; - } + stack_display_items = Debug::CreateCallStack(state); for (auto it = stack_display_items.begin(); it < stack_display_items.end(); ++it) { auto& item = *it; @@ -214,13 +211,33 @@ void Window_Interpreter::DrawStackLine(int index) { Debug::CallStackItem& item = stack_display_items[index]; contents->TextDraw(rect.x, rect.y, Font::ColorDisabled, fmt::format("[{:0" + std::to_string(digits_stackitemno) + "d}]", state.stack.size() - index)); - if (item.is_ce) { - contents->TextDraw(rect.x + (digits_stackitemno * 6) + 16, rect.y, Font::ColorDefault, fmt::format("CE{:0" + std::to_string(digits_evt_id) + "d}", item.evt_id)); - } else if (item.page_id > 0) { - contents->TextDraw(rect.x + (digits_stackitemno * 6) + 16, rect.y, Font::ColorDefault, fmt::format("EV{:0" + std::to_string(digits_evt_id) + "d}[{:0" + std::to_string(digits_page_id) + "d}]", item.evt_id, item.page_id)); - } else { - contents->TextDraw(rect.x + (digits_stackitemno * 6) + 16, rect.y, Font::ColorDefault, fmt::format("EV{:0" + std::to_string(digits_evt_id) + "d}", item.evt_id)); + + std::string formatted_id; + Font::SystemColor color = Font::ColorDefault; + + switch (item.type_ev) { + case InterpreterEventType::MapEvent: + if (item.page_id > 0) { + formatted_id = fmt::format("EV{:0" + std::to_string(digits_evt_id) + "d}[{:0" + std::to_string(digits_page_id) + "d}]", item.evt_id, item.page_id); + } else { + formatted_id = fmt::format("EV{:0" + std::to_string(digits_evt_id) + "d}", item.evt_id); + } + if (item.map_has_changed) { + color = Font::ColorKnockout; + } + break; + case InterpreterEventType::CommonEvent: + formatted_id = fmt::format("CE{:0" + std::to_string(digits_evt_id) + "d}", item.evt_id); + break; + case InterpreterEventType::BattleEvent: + formatted_id = fmt::format("BE{:0" + std::to_string(digits_evt_id) + "d}", item.evt_id); + break; + default: + formatted_id = fmt::format("{:0" + std::to_string(digits_evt_id + 2) + "d}", 0); + color = Font::ColorKnockout; + break; } + contents->TextDraw(rect.x + (digits_stackitemno * 6) + 16, rect.y, color, formatted_id); std::string name = item.name; int max_length = 28; diff --git a/src/window_interpreter.h b/src/window_interpreter.h index 2504783f65..6586c3170b 100644 --- a/src/window_interpreter.h +++ b/src/window_interpreter.h @@ -73,7 +73,7 @@ class Window_Interpreter : public Window_Selectable { void Update() override; - void SetStackState(bool is_ce, int owner_evt_id, std::string interpreter_desc, lcf::rpg::SaveEventExecState state); + void SetStackState(std::string interpreter_desc, lcf::rpg::SaveEventExecState state); void Refresh(); bool IsValid(); @@ -90,8 +90,6 @@ class Window_Interpreter : public Window_Selectable { #endif private: struct InterpDisplayItem { - bool is_ce = false; - int owner_evt_id = 0; std::string desc; }; From 1280acedfe28525f9944d34ed42790fa9d440d88 Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 6 Feb 2025 21:36:35 +0100 Subject: [PATCH 08/16] Interpreter debug window: Extended view to display the initial event execution type of an interpreter stack (Action, Touch, Parallel, etc...) --- src/game_interpreter_shared.h | 21 +++++++++++++++++++++ src/window_interpreter.cpp | 6 +++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index 21941f91a3..5f0c93e8bd 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -40,6 +40,12 @@ namespace Game_Interpreter_Shared { CommonEvent, BattleEvent }; + static constexpr auto kEventType = lcf::makeEnumTags( + "None", + "MapEvent", + "CommonEvent", + "BattleEvent" + ); enum class ExecutionType { /* @@ -64,6 +70,21 @@ namespace Game_Interpreter_Shared { Eval, DebugCall }; + static constexpr auto kExecutionType = lcf::makeEnumTags( + "Action", + "Touch", + "Collision", + "AutoStart", + "Parallel", + "Call", + "BattleStart", + "BattleParallel", + "---", + "---", + "DeathHandler", + "Eval", + "DebugCall" + ); /* * Indicates how the target of an interpreter operation (lvalue) should be evaluated. diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index 5c4e62a832..1ba0ea6641 100644 --- a/src/window_interpreter.cpp +++ b/src/window_interpreter.cpp @@ -199,7 +199,11 @@ void Window_Interpreter::DrawDescriptionLines() { rect = GetItemRect(i++); contents->ClearRect(rect); - contents->TextDraw(rect.x, rect.y, Font::ColorDefault, "Stack Size: "); + auto str_ex_type = std::string(Game_Interpreter_Shared::kExecutionType.tag(static_cast(stack_display_items[0].type_ex))); + contents->TextDraw(rect.x, rect.y, Font::ColorDefault, "("); + contents->TextDraw(rect.x + 6, rect.y, Font::ColorHeal, str_ex_type); + contents->TextDraw(rect.x + 6 * (str_ex_type.length() + 1), rect.y, Font::ColorDefault, ")"); + contents->TextDraw(rect.x + rect.width / 2, rect.y, Font::ColorDefault, "Stack Size: "); contents->TextDraw(GetWidth() - 16, rect.y, Font::ColorCritical, std::to_string(state.stack.size()), Text::AlignRight); } From 16c69027d4150723b255e3ae42146267366a705b Mon Sep 17 00:00:00 2001 From: florianessl Date: Fri, 7 Feb 2025 17:03:38 +0100 Subject: [PATCH 09/16] Minor: Defined "ExecutionType::ManiacHook" & clarified Push instances inside battle interpreter --- src/game_interpreter.h | 2 +- src/game_interpreter_battle.cpp | 6 +++--- src/game_interpreter_shared.h | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 4e14d1a7bf..e42c44462f 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -412,7 +412,7 @@ template inline void Game_Interpreter::Push(Game_CommonEvent* ev) { static_assert(type_ex == InterpreterExecutionType::AutoStart || type_ex == InterpreterExecutionType::Parallel || type_ex == InterpreterExecutionType::Call || type_ex == InterpreterExecutionType::DeathHandler - || type_ex == InterpreterExecutionType::DebugCall, "Unexpected ExecutionType for CommonEvent" + || type_ex == InterpreterExecutionType::DebugCall || type_ex == InterpreterExecutionType::ManiacHook, "Unexpected ExecutionType for CommonEvent" ); PushInternal(ev, type_ex); } diff --git a/src/game_interpreter_battle.cpp b/src/game_interpreter_battle.cpp index 00eb66be89..5dc4ab1d50 100644 --- a/src/game_interpreter_battle.cpp +++ b/src/game_interpreter_battle.cpp @@ -213,7 +213,7 @@ int Game_Interpreter_Battle::ScheduleNextPage(lcf::rpg::TroopPageCondition::Flag continue; } Clear(); - Push(page.event_commands, 0); // FIXME: clarify type_src & type_ex for battle events + Push(page.event_commands, 0); executed[i] = true; return i + 1; } @@ -647,7 +647,7 @@ bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, i } // pushes the common event to be run into the queue of events. - maniac_interpreter->Push(common_event); // FIXME: clarify type_src & type_ex for battle events + maniac_interpreter->Push(common_event); // pushes the change variable events into the interpreters // event queue, so we don't run into a race condition. @@ -685,7 +685,7 @@ bool Game_Interpreter_Battle::ManiacBattleHook(ManiacBattleHookType hook_type, i } // Push is actually "push_back", so this gets added before other events. - maniac_interpreter->Push(pre_commands, 0); // FIXME: clarify type_src & type_ex for battle events + maniac_interpreter->Push(pre_commands, 0); // Necessary to start the sub-event. maniac_interpreter->Update(); diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index 5f0c93e8bd..39bf55497e 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -68,7 +68,8 @@ namespace Game_Interpreter_Shared { DeathHandler = 10, /* Event code was dynamically evaluated. (ManiacCallCommand) */ Eval, - DebugCall + DebugCall, + ManiacHook }; static constexpr auto kExecutionType = lcf::makeEnumTags( "Action", From bdc21d9cdb7c10372df7235fac991338b8581ad6 Mon Sep 17 00:00:00 2001 From: florianessl Date: Tue, 11 Feb 2025 15:31:12 +0100 Subject: [PATCH 10/16] Amended missing tag "ManiacHook" to Debug::kExecutionType & added some static_asserts so it don't happen again --- src/filefinder.h | 6 ++++-- src/game_interpreter_shared.h | 11 ++++++++--- src/input_buttons.h | 12 ++++++------ src/rtp.h | 4 +++- src/window_gamelist.cpp | 2 +- 5 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/filefinder.h b/src/filefinder.h index f9b371abca..a112074e0d 100644 --- a/src/filefinder.h +++ b/src/filefinder.h @@ -50,7 +50,7 @@ namespace FileFinder { * Type of the project. Used to differentiate between supported games (2kX or EasyRPG) * and known but unsupported (i.e. newer RPG Makers). */ - enum ProjectType { + enum class ProjectType { Unknown, // 2kX or EasyRPG Supported, @@ -62,7 +62,8 @@ namespace FileFinder { WolfRpgEditor, Encrypted2k3Maniacs, RpgMaker95, - SimRpgMaker95 + SimRpgMaker95, + LAST }; constexpr auto kProjectType = lcf::makeEnumTags( @@ -77,6 +78,7 @@ namespace FileFinder { "RPG Maker 95", "Sim RPG Maker 95" ); + static_assert(kProjectType.size() == static_cast(ProjectType::LAST)); /** * Helper struct combining the project's directory and its type (used by Game Browser) diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index 39bf55497e..a033aaad18 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -38,7 +38,8 @@ namespace Game_Interpreter_Shared { None = 0, MapEvent, CommonEvent, - BattleEvent + BattleEvent, + LAST }; static constexpr auto kEventType = lcf::makeEnumTags( "None", @@ -46,6 +47,7 @@ namespace Game_Interpreter_Shared { "CommonEvent", "BattleEvent" ); + static_assert(kEventType.size() == static_cast(EventType::LAST)); enum class ExecutionType { /* @@ -69,7 +71,8 @@ namespace Game_Interpreter_Shared { /* Event code was dynamically evaluated. (ManiacCallCommand) */ Eval, DebugCall, - ManiacHook + ManiacHook, + LAST }; static constexpr auto kExecutionType = lcf::makeEnumTags( "Action", @@ -84,8 +87,10 @@ namespace Game_Interpreter_Shared { "---", "DeathHandler", "Eval", - "DebugCall" + "DebugCall", + "ManiacHook" ); + static_assert(kExecutionType.size() == static_cast(ExecutionType::LAST)); /* * Indicates how the target of an interpreter operation (lvalue) should be evaluated. diff --git a/src/input_buttons.h b/src/input_buttons.h index e9dce4cb2d..6524062927 100644 --- a/src/input_buttons.h +++ b/src/input_buttons.h @@ -130,8 +130,8 @@ namespace Input { "FAST_FORWARD_A", "FAST_FORWARD_B", "TOGGLE_FULLSCREEN", - "TOGGLE_ZOOM", - "BUTTON_COUNT"); + "TOGGLE_ZOOM"); + static_assert(kInputButtonNames.size() == static_cast(BUTTON_COUNT)); constexpr auto kInputButtonHelp = lcf::makeEnumTags( "Up Direction", @@ -175,8 +175,8 @@ namespace Input { "Run the game at x{} speed", "Run the game at x{} speed", "Toggle Fullscreen mode", - "Toggle Window Zoom level", - "Total Button Count"); + "Toggle Window Zoom level"); + static_assert(kInputButtonHelp.size() == static_cast(BUTTON_COUNT)); /** * Return true if the given button is a system button. @@ -241,8 +241,8 @@ namespace Input { "RIGHT", "UPLEFT", "UP", - "UPRIGHT", - "NUM_DIRECTIONS"); + "UPRIGHT"); + static_assert(kInputDirectionNames.size() == static_cast(NUM_DIRECTIONS)); }; using ButtonMappingArray = FlatUniqueMultiMap; diff --git a/src/rtp.h b/src/rtp.h index 05f402d8d0..4212cbf4c3 100644 --- a/src/rtp.h +++ b/src/rtp.h @@ -50,7 +50,8 @@ namespace RTP { RPG2003_VladRussian, RPG2003_RpgUniverseSpanishPortuguese, RPG2003_Korean, - RPG2003_OfficialTraditionalChinese + RPG2003_OfficialTraditionalChinese, + LAST }; constexpr auto kTypes = lcf::makeEnumTags( @@ -66,6 +67,7 @@ namespace RTP { "Korean Translation", "Official Traditional Chinese" ); + static_assert(kTypes.size() == static_cast(Type::LAST)); struct RtpHitInfo { RTP::Type type; diff --git a/src/window_gamelist.cpp b/src/window_gamelist.cpp index 7b3257b61a..12e185dfb2 100644 --- a/src/window_gamelist.cpp +++ b/src/window_gamelist.cpp @@ -129,7 +129,7 @@ void Window_GameList::DrawItem(int index) { #ifndef USE_CUSTOM_FILEBUF auto color = Font::ColorDefault; - if (ge.type == FileFinder::Unknown) { + if (ge.type == FileFinder::ProjectType::Unknown) { color = Font::ColorHeal; } else if (ge.type > FileFinder::ProjectType::Supported) { color = Font::ColorKnockout; From b299dc0c3f16de0632fa47576453aa7d44a027ee Mon Sep 17 00:00:00 2001 From: florianessl Date: Thu, 8 May 2025 21:01:54 +0200 Subject: [PATCH 11/16] Debug scene: Fix a crash when foreground interpreter stack is empty --- src/game_interpreter.cpp | 5 ++--- src/window_interpreter.cpp | 11 +++++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 5b9aca117b..5816779dfa 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -2044,13 +2044,13 @@ std::optional Game_Interpreter::HandleDynRpgScript(const lcf::rpg::EventCo if (Player::IsPatchDynRpg() || Player::HasEasyRpgExtensions()) { if (com.string.empty() || com.string[0] != '@') { // Not a DynRPG command - return std::nullopt; + return {}; } if (!Player::IsPatchDynRpg() && Player::HasEasyRpgExtensions()) { // Only accept commands starting with @easyrpg_ if (!StartsWith(com.string, "@easyrpg_")) { - return std::nullopt; + return {}; } } @@ -2073,7 +2073,6 @@ std::optional Game_Interpreter::HandleDynRpgScript(const lcf::rpg::EventCo return Main_Data::game_dynrpg->Invoke(command, this); } - return {}; } diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index 1ba0ea6641..d5973396d7 100644 --- a/src/window_interpreter.cpp +++ b/src/window_interpreter.cpp @@ -199,10 +199,13 @@ void Window_Interpreter::DrawDescriptionLines() { rect = GetItemRect(i++); contents->ClearRect(rect); - auto str_ex_type = std::string(Game_Interpreter_Shared::kExecutionType.tag(static_cast(stack_display_items[0].type_ex))); - contents->TextDraw(rect.x, rect.y, Font::ColorDefault, "("); - contents->TextDraw(rect.x + 6, rect.y, Font::ColorHeal, str_ex_type); - contents->TextDraw(rect.x + 6 * (str_ex_type.length() + 1), rect.y, Font::ColorDefault, ")"); + + if (stack_display_items.size() > 0) { + auto str_ex_type = std::string(Game_Interpreter_Shared::kExecutionType.tag(static_cast(stack_display_items[0].type_ex))); + contents->TextDraw(rect.x, rect.y, Font::ColorDefault, "("); + contents->TextDraw(rect.x + 6, rect.y, Font::ColorHeal, str_ex_type); + contents->TextDraw(rect.x + 6 * (str_ex_type.length() + 1), rect.y, Font::ColorDefault, ")"); + } contents->TextDraw(rect.x + rect.width / 2, rect.y, Font::ColorDefault, "Stack Size: "); contents->TextDraw(GetWidth() - 16, rect.y, Font::ColorCritical, std::to_string(state.stack.size()), Text::AlignRight); } From 1d2c644fd6e9f1fa41b1899114e8fe88f7b421d9 Mon Sep 17 00:00:00 2001 From: florianessl Date: Fri, 9 May 2025 11:25:46 +0200 Subject: [PATCH 12/16] Fix warnings --- src/scene_debug.cpp | 2 -- src/window_interpreter.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/scene_debug.cpp b/src/scene_debug.cpp index 6e9150a772..9206a73af0 100644 --- a/src/scene_debug.cpp +++ b/src/scene_debug.cpp @@ -1222,7 +1222,6 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { lcf::rpg::SaveEventExecState state_display; std::string first_line = ""; bool valid = false; - int evt_id = 0; auto& bg_states = state_interpreter.background_states; @@ -1241,7 +1240,6 @@ void Scene_Debug::UpdateInterpreterWindow(int index) { for (auto& ce : Game_Map::GetCommonEvents()) { if (ce.GetId() == ce_id) { first_line = Debug::FormatEventName(ce); - evt_id = ce_id; valid = true; break; } diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index d5973396d7..ea2d462fcb 100644 --- a/src/window_interpreter.cpp +++ b/src/window_interpreter.cpp @@ -108,7 +108,7 @@ void Window_Interpreter::Refresh() { max_evt_id = item.evt_id; if (item.page_id > max_page_id) max_page_id = item.page_id; - if (item.cmd_count > max_cmd_count) + if (static_cast(item.cmd_count) > max_cmd_count) max_cmd_count = item.cmd_count; } From b67f5071aee4369eea742481afba90a3d499dc77 Mon Sep 17 00:00:00 2001 From: florianessl Date: Mon, 2 Jun 2025 19:03:23 +0200 Subject: [PATCH 13/16] Refactor: Removed "CheckOrMakeWayEx" from header file & renamed it to "ProcessWay" --- src/game_map.cpp | 75 ++++++++++++++++++++++++++++++------------------ src/game_map.h | 26 ----------------- 2 files changed, 47 insertions(+), 54 deletions(-) diff --git a/src/game_map.cpp b/src/game_map.cpp index 4f7ad5f82c..9a11c23899 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -753,29 +753,26 @@ static Game_Vehicle::Type GetCollisionVehicleType(const Game_Character* ch) { return Game_Vehicle::None; } -bool Game_Map::CheckWay(const Game_Character& self, - int from_x, int from_y, - int to_x, int to_y - ) -{ - return CheckOrMakeWayEx( - self, from_x, from_y, to_x, to_y, true, {}, false - ); -} - -bool Game_Map::CheckWay(const Game_Character& self, - int from_x, int from_y, - int to_x, int to_y, - bool check_events_and_vehicles, - Span ignore_some_events_by_id) { - return CheckOrMakeWayEx( - self, from_x, from_y, to_x, to_y, - check_events_and_vehicles, - ignore_some_events_by_id, false - ); -} - -bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, +/** + * Extended function behind MakeWay and CheckWay + * that allows controlling exactly which events are + * ignored in the collision, and whether events should + * be prompted to make way with side effects (for MakeWay) + * or not (for CheckWay). + * + * @param self See CheckWay or MakeWay. + * @param from_x See CheckWay or MakeWay. + * @param from_y See CheckWay or MakeWay. + * @param to_x See CheckWay or MakeWay. + * @param to_y See CheckWay or MakeWay. + * @param check_events_and_vehicles whether to check + * events, or only consider map collision. + * @param make_way Whether to cause side effects. + * @param ignore_some_events_by_id A set of + * specific event IDs to ignore. + * @return See CheckWay or MakeWay. + */ +static bool ProcessWay(const Game_Character& self, int from_x, int from_y, int to_x, int to_y, bool check_events_and_vehicles, @@ -837,7 +834,7 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, // inbounds after the first move. from_x = Game_Map::RoundX(from_x); from_y = Game_Map::RoundY(from_y); - if (!IsPassableTile(&self, bit_from, from_x, from_y)) { + if (!Game_Map::IsPassableTile(&self, bit_from, from_x, from_y)) { return false; } } @@ -845,13 +842,13 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, if (vehicle_type != Game_Vehicle::Airship && check_events_and_vehicles) { // Check for collision with events on the target tile. if (ignore_some_events_by_id.empty()) { - for (auto& other: GetEvents()) { + for (auto& other: Game_Map::GetEvents()) { if (CheckOrMakeCollideEvent(other)) { return false; } } } else { - for (auto& other: GetEvents()) { + for (auto& other: Game_Map::GetEvents()) { if (std::find(ignore_some_events_by_id.begin(), ignore_some_events_by_id.end(), other.GetId()) != ignore_some_events_by_id.end()) continue; if (CheckOrMakeCollideEvent(other)) { @@ -886,17 +883,39 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, bit = Passable::Down | Passable::Up | Passable::Left | Passable::Right; } - return IsPassableTile( + return Game_Map::IsPassableTile( &self, bit, to_x, to_y, check_events_and_vehicles, true ); } +bool Game_Map::CheckWay(const Game_Character& self, + int from_x, int from_y, + int to_x, int to_y +) +{ + return ProcessWay( + self, from_x, from_y, to_x, to_y, true, {}, false + ); +} + +bool Game_Map::CheckWay(const Game_Character& self, + int from_x, int from_y, + int to_x, int to_y, + bool check_events_and_vehicles, + Span ignore_some_events_by_id) { + return ProcessWay( + self, from_x, from_y, to_x, to_y, + check_events_and_vehicles, + ignore_some_events_by_id, false + ); +} + bool Game_Map::MakeWay(const Game_Character& self, int from_x, int from_y, int to_x, int to_y ) { - return CheckOrMakeWayEx( + return ProcessWay( self, from_x, from_y, to_x, to_y, true, {}, true ); } diff --git a/src/game_map.h b/src/game_map.h index d7cb29126a..4fa44d4463 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -258,32 +258,6 @@ namespace Game_Map { int from_x, int from_y, int to_x, int to_y); - /** - * Extended function behind MakeWay and CheckWay - * that allows controlling exactly which events are - * ignored in the collision, and whether events should - * be prompted to make way with side effects (for MakeWay) - * or not (for CheckWay). - * - * @param self See CheckWay or MakeWay. - * @param from_x See CheckWay or MakeWay. - * @param from_y See CheckWay or MakeWay. - * @param to_x See CheckWay or MakeWay. - * @param to_y See CheckWay or MakeWay. - * @param check_events_and_vehicles whether to check - * events, or only consider map collision. - * @param make_way Whether to cause side effects. - * @param ignore_some_events_by_id A set of - * specific event IDs to ignore. - * @return See CheckWay or MakeWay. - */ - bool CheckOrMakeWayEx(const Game_Character& self, - int from_x, int from_y, - int to_x, int to_y, - bool check_events_and_vehicles, - Span ignore_some_events_by_id, - bool make_way); - /** * Gets if possible to land the airship at (x,y) * From 799df1d5fbadd3afce517bc30fbbdcb7009a8015 Mon Sep 17 00:00:00 2001 From: florianessl Date: Wed, 5 Jun 2024 16:26:00 +0200 Subject: [PATCH 14/16] First "AssertBlockedMoves" draft --- src/game_interpreter.cpp | 10 +++ src/game_interpreter.h | 1 + src/game_interpreter_debug.cpp | 51 +++++++++++++ src/game_interpreter_debug.h | 2 + src/game_map.cpp | 132 +++++++++++++++++++++------------ src/game_map.h | 13 ++-- 6 files changed, 156 insertions(+), 53 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 5816779dfa..5054c4cd30 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -456,6 +456,9 @@ void Game_Interpreter::Update(bool reset_loop_count) { } if (_state.wait_movement) { + if (ShouldAssertMoveRoutes()) { + Debug::AssertBlockedMoves(); + } if (Game_Map::IsAnyMovePending()) { break; } @@ -5884,3 +5887,10 @@ bool Game_Interpreter_Inspector::IsInActiveExcecution(Game_CommonEvent const& ce } return ce.interpreter && ce.interpreter->IsRunning(); } + +bool Game_Interpreter::ShouldAssertMoveRoutes() const { + //FIXME: Needs liblcf update + //return (main_flag && (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_blocked_movement_main) > 0) + // || (!main_flag && (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_blocked_movement_parallel) > 0); + return true; +} diff --git a/src/game_interpreter.h b/src/game_interpreter.h index e42c44462f..74c7439c54 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -130,6 +130,7 @@ class Game_Interpreter : public Game_BaseInterpreterContext /** @return true if wait command (time or key) is active. Used by 2k3 battle system */ bool IsWaitingForWaitCommand() const; + bool ShouldAssertMoveRoutes() const; protected: static constexpr int loop_limit = 10000; static constexpr int call_stack_limit = 1000; diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp index 0be5d2872c..f47568fe07 100644 --- a/src/game_interpreter_debug.cpp +++ b/src/game_interpreter_debug.cpp @@ -133,3 +133,54 @@ std::string Debug::FormatEventName(Game_CommonEvent const& ce) { } return fmt::format("CE{:04d}: '{}'", ce.GetIndex(), ce.GetName()); } + +void Debug::AssertBlockedMoves() { + auto check = [](Game_Character& ev) { + return ev.IsMoveRouteOverwritten() && !ev.IsMoveRouteFinished() + && ev.GetStopCount() != 0xFFFF && ev.GetStopCount() > ev.GetMaxStopCount(); + }; + auto assert_way = [](Game_Character& ev) { + using Code = lcf::rpg::MoveCommand::Code; + auto& move_command = ev.GetMoveRoute().move_commands[ev.GetMoveRouteIndex()]; + + if (move_command.command_id >= static_cast(Code::move_up) + && move_command.command_id <= static_cast(Code::move_forward)) { + + const int dir = ev.GetDirection(); + const int from_x = ev.GetX(), + from_y = ev.GetY(), + to_x = from_x + ev.GetDxFromDirection(dir), + to_y = from_y + ev.GetDyFromDirection(dir); + + if (from_x != to_x && from_y != to_y) { + bool valid = Game_Map::AssertWay(ev, from_x, from_y, from_x, to_y); + if (valid) + valid = Game_Map::AssertWay(ev, from_x, to_y, to_x, to_y); + if (valid) + valid = Game_Map::AssertWay(ev, from_x, from_y, to_x, from_y); + if (valid) + valid = Game_Map::AssertWay(ev, to_x, from_y, to_x, to_y); + } else { + Game_Map::AssertWay(ev, from_x, from_y, to_x, to_y); + } + } + }; + const auto map_id = Game_Map::GetMapId(); + if (auto& ch = *Main_Data::game_player; check(ch)) { + assert_way(ch); + } + if (auto& vh = *Game_Map::GetVehicle(Game_Vehicle::Boat); vh.GetMapId() == map_id && check(vh)) { + assert_way(vh); + } + if (auto& vh = *Game_Map::GetVehicle(Game_Vehicle::Ship); vh.GetMapId() == map_id && check(vh)) { + assert_way(vh); + } + if (auto& vh = *Game_Map::GetVehicle(Game_Vehicle::Airship); vh.GetMapId() == map_id && check(vh)) { + assert_way(vh); + } + for (auto& ev : Game_Map::GetEvents()) { + if (check(ev)) { + assert_way(ev); + } + } +} diff --git a/src/game_interpreter_debug.h b/src/game_interpreter_debug.h index 1f8674f349..bd66663d76 100644 --- a/src/game_interpreter_debug.h +++ b/src/game_interpreter_debug.h @@ -73,6 +73,8 @@ namespace Debug { std::string FormatEventName(Game_Character const& ev); std::string FormatEventName(Game_CommonEvent const& ce); + + void AssertBlockedMoves(); } #endif diff --git a/src/game_map.cpp b/src/game_map.cpp index 9a11c23899..c84dc0cfdf 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -674,7 +674,17 @@ static int GetPassableMask(int old_x, int old_y, int new_x, int new_y) { return bit; } -static bool WouldCollide(const Game_Character& self, const Game_Character& other, bool self_conflict) { +enum ProcessWayImpl { + eProcessWayImpl_CheckWay, + /* 'ProcessWay' causes side effects. */ + eProcessWayImpl_MakeWay, + /* 'ProcessWay' will generate warnings + for blocked movement. */ + eProcessWayImpl_AssertWay +}; + +template +static bool WouldCollide(const Game_Character& self, const T& other, bool self_conflict) { if (self.GetThrough() || other.GetThrough()) { return false; } @@ -690,14 +700,28 @@ static bool WouldCollide(const Game_Character& self, const Game_Character& other if (self.GetType() == Game_Character::Event && other.GetType() == Game_Character::Event && (self.IsOverlapForbidden() || other.IsOverlapForbidden())) { + if constexpr (impl == eProcessWayImpl_AssertWay) { + if (self.IsOverlapForbidden()) { + Output::Warning("MoveRoute: {} is not allowed to overlap with other events!", Debug::FormatEventName(self)); + } else { + Output::Warning("MoveRoute: {} is not allowed to overlap with event {}!", Debug::FormatEventName(self), Debug::FormatEventName(other)); + } + } return true; } if (other.GetLayer() == lcf::rpg::EventPage::Layers_same && self_conflict) { + if constexpr (impl == eProcessWayImpl_AssertWay) { + Output::Warning("MoveRoute: {} can't move (self collision)!", Debug::FormatEventName(self)); + } return true; } if (self.GetLayer() == other.GetLayer()) { + if constexpr (impl == eProcessWayImpl_AssertWay) { + //TODO: check if 'other' could still move away due to some routing behavior... + Output::Warning("MoveRoute: {} would overlap with {}!", Debug::FormatEventName(self), Debug::FormatEventName(other)); + } return true; } @@ -713,7 +737,7 @@ static void MakeWayUpdate(Game_Event& other) { other.Update(false); } -template +template static bool CheckWayTestCollideEvent(int x, int y, const Game_Character& self, T& other, bool self_conflict) { if (&self == &other) { return false; @@ -723,7 +747,7 @@ static bool CheckWayTestCollideEvent(int x, int y, const Game_Character& self, T return false; } - return WouldCollide(self, other, self_conflict); + return WouldCollide(self, other, self_conflict); } template @@ -743,7 +767,7 @@ static bool MakeWayCollideEvent(int x, int y, const Game_Character& self, T& oth return false; } - return WouldCollide(self, other, self_conflict); + return WouldCollide(self, other, self_conflict); } static Game_Vehicle::Type GetCollisionVehicleType(const Game_Character* ch) { @@ -754,32 +778,29 @@ static Game_Vehicle::Type GetCollisionVehicleType(const Game_Character* ch) { } /** - * Extended function behind MakeWay and CheckWay + * Extended function behind MakeWay, CheckWay & AssertWay * that allows controlling exactly which events are * ignored in the collision, and whether events should * be prompted to make way with side effects (for MakeWay) - * or not (for CheckWay). + * or not (for CheckWay & AssertWay). * + * @tparam check_events_and_vehicles whether to check + * events, or only consider map collision + * @tparam impl * @param self See CheckWay or MakeWay. * @param from_x See CheckWay or MakeWay. * @param from_y See CheckWay or MakeWay. * @param to_x See CheckWay or MakeWay. * @param to_y See CheckWay or MakeWay. - * @param check_events_and_vehicles whether to check - * events, or only consider map collision. - * @param make_way Whether to cause side effects. * @param ignore_some_events_by_id A set of * specific event IDs to ignore. * @return See CheckWay or MakeWay. */ -static bool ProcessWay(const Game_Character& self, - int from_x, int from_y, - int to_x, int to_y, - bool check_events_and_vehicles, - Span ignore_some_events_by_id, - bool make_way - ) -{ +template +static bool ProcessWay(const Game_Character & self, + int from_x, int from_y, + int to_x, int to_y, + Span ignore_some_events_by_id) { // Infer directions before we do any rounding. const int bit_from = GetPassableMask(from_x, from_y, to_x, to_y); const int bit_to = GetPassableMask(to_x, to_y, from_x, from_y); @@ -790,6 +811,9 @@ static bool ProcessWay(const Game_Character& self, // Note, even for diagonal, if the tile is invalid we still check vertical/horizontal first! if (!Game_Map::IsValid(to_x, to_y)) { + if constexpr (impl == eProcessWayImpl_AssertWay) { + Output::Warning("MoveRoute: {} can't move out-of-bounds (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); + } return false; } @@ -802,14 +826,13 @@ static bool ProcessWay(const Game_Character& self, // Depending on whether we're supposed to call MakeWayCollideEvent // (which might change the map) or not, choose what to call: - auto CheckOrMakeCollideEvent = [&](auto& other) { - if (make_way) { + auto CheckOrMakeCollideEvent = [&](auto& other) -> bool { + if constexpr (impl == eProcessWayImpl_MakeWay) { return MakeWayCollideEvent(to_x, to_y, self, other, self_conflict); - } else { - return CheckWayTestCollideEvent( - to_x, to_y, self, other, self_conflict - ); } + return CheckWayTestCollideEvent( + to_x, to_y, self, other, self_conflict + ); }; if (!self.IsJumping()) { @@ -835,6 +858,9 @@ static bool ProcessWay(const Game_Character& self, from_x = Game_Map::RoundX(from_x); from_y = Game_Map::RoundY(from_y); if (!Game_Map::IsPassableTile(&self, bit_from, from_x, from_y)) { + if constexpr (impl == eProcessWayImpl_AssertWay) { + Output::Warning("MoveRoute: {} can't step of current tile (x:{}, y:{})!", Debug::FormatEventName(self), from_x, from_y); + } return false; } } @@ -883,18 +909,24 @@ static bool ProcessWay(const Game_Character& self, bit = Passable::Down | Passable::Up | Passable::Left | Passable::Right; } - return Game_Map::IsPassableTile( - &self, bit, to_x, to_y, check_events_and_vehicles, true - ); + bool result = Game_Map::IsPassableTile( + &self, bit, to_x, to_y + ); + if constexpr (impl == eProcessWayImpl_AssertWay) { + if (!result) { + Output::Warning("MoveRoute: {} can't pass target tile (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); + } + } + return result; } + bool Game_Map::CheckWay(const Game_Character& self, int from_x, int from_y, int to_x, int to_y -) -{ - return ProcessWay( - self, from_x, from_y, to_x, to_y, true, {}, false +) { + return ProcessWay( + self, from_x, from_y, to_x, to_y, {} ); } @@ -903,21 +935,35 @@ bool Game_Map::CheckWay(const Game_Character& self, int to_x, int to_y, bool check_events_and_vehicles, Span ignore_some_events_by_id) { - return ProcessWay( + if (check_events_and_vehicles) { + return ProcessWay( + self, from_x, from_y, to_x, to_y, + ignore_some_events_by_id + ); + } + return ProcessWay( self, from_x, from_y, to_x, to_y, - check_events_and_vehicles, - ignore_some_events_by_id, false + ignore_some_events_by_id ); } +bool Game_Map::AssertWay(const Game_Character& self, + int from_x, int from_y, + int to_x, int to_y +) { + return ProcessWay( + self, from_x, from_y, to_x, to_y, {} + ); +} + + bool Game_Map::MakeWay(const Game_Character& self, int from_x, int from_y, int to_x, int to_y ) { - return ProcessWay( - self, from_x, from_y, to_x, to_y, true, {}, true - ); + return ProcessWay( + self, from_x, from_y, to_x, to_y, {}); } @@ -1014,22 +1060,14 @@ bool Game_Map::IsPassableLowerTile(int bit, int tile_index) { return (passages_down[tile_id] & bit) != 0; } +template bool Game_Map::IsPassableTile( const Game_Character* self, int bit, int x, int y ) { - return IsPassableTile( - self, bit, x, y, true, true - ); -} - -bool Game_Map::IsPassableTile( - const Game_Character* self, int bit, int x, int y, - bool check_events_and_vehicles, bool check_map_geometry - ) { if (!IsValid(x, y)) return false; const auto vehicle_type = GetCollisionVehicleType(self); - if (check_events_and_vehicles) { + if constexpr(check_events_and_vehicles) { if (vehicle_type != Game_Vehicle::None) { const auto* terrain = lcf::ReaderUtil::GetElement(lcf::Data::terrains, GetTerrainTag(x, y)); if (!terrain) { @@ -1079,7 +1117,7 @@ bool Game_Map::IsPassableTile( } } - if (check_map_geometry) { + if constexpr(check_map_geometry) { int tile_index = x + y * GetTilesX(); int tile_id = map->upper_layer[tile_index] - BLOCK_F; tile_id = map_info.upper_tiles[tile_id]; diff --git a/src/game_map.h b/src/game_map.h index 4fa44d4463..dda4aef1bf 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -258,6 +258,10 @@ namespace Game_Map { int from_x, int from_y, int to_x, int to_y); + bool AssertWay(const Game_Character& self, + int from_x, int from_y, + int to_x, int to_y); + /** * Gets if possible to land the airship at (x,y) * @@ -626,19 +630,16 @@ namespace Game_Map { * * Returns true if move is possible. * + * @tparam check_events_and_vehicles Whether to consider events and vehicles. + * @tparam check_map_geometry Whether to take map collision into account. * @param self Character to move. If not nullptr, checks the vehicle type and performs vehicle specific checks if is vehicle. * Also ignores self in the event tile graphic checks if self is not nullptr. * @param bit which direction bits to check * @param x target tile x. * @param y target tile y. - * @param check_events_and_vehicles Whether to consider events and vehicles. - * @param check_map_geometry Whether to take map collision into account. * @return whether is passable. */ - bool IsPassableTile( - const Game_Character* self, int bit, int x, int y, - bool check_events_and_vehicles, bool check_map_geometry - ); + template bool IsPassableTile(const Game_Character* self, int bit, int x, int y); /** From c8add1bed0f6918250008d76fa54f97b3b3a6ac3 Mon Sep 17 00:00:00 2001 From: florianessl Date: Fri, 7 Jun 2024 14:24:00 +0200 Subject: [PATCH 15/16] - Fix: Split AssertWay checks into separate variants for Foreground & Background interpreter instances - MoveRoute asserts: Implemented some basic routing checks before considering an event as being 'blocked' by another game character --- src/game_character.cpp | 21 +++++ src/game_character.h | 2 + src/game_interpreter.cpp | 2 +- src/game_interpreter_debug.cpp | 14 +-- src/game_interpreter_debug.h | 2 +- src/game_map.cpp | 152 ++++++++++++++++++++++++++++++--- src/game_map.h | 2 +- 7 files changed, 174 insertions(+), 21 deletions(-) diff --git a/src/game_character.cpp b/src/game_character.cpp index caf7ad8542..7ecd8834fb 100644 --- a/src/game_character.cpp +++ b/src/game_character.cpp @@ -516,6 +516,27 @@ bool Game_Character::Move(int dir) { return true; } +bool Game_Character::CheckMove(int dir) { + bool move_success = false; + + const auto x = GetX(); + const auto y = GetY(); + const auto dx = GetDxFromDirection(dir); + const auto dy = GetDyFromDirection(dir); + + if (dx && dy) { + // For diagonal movement, RPG_RT trys vert -> horiz and if that fails, then horiz -> vert. + move_success = (CheckWay(x, y, x, y + dy) && CheckWay(x, y + dy, x + dx, y + dy)) + || (CheckWay(x, y, x + dx, y) && CheckWay(x + dx, y, x + dx, y + dy)); + } else if (dx) { + move_success = CheckWay(x, y, x + dx, y); + } else if (dy) { + move_success = CheckWay(x, y, x, y + dy); + } + + return move_success; +} + void Game_Character::Turn90DegreeLeft() { SetDirection(GetDirection90DegreeLeft(GetDirection())); } diff --git a/src/game_character.h b/src/game_character.h index 2549fe516d..ef75c5e5b6 100644 --- a/src/game_character.h +++ b/src/game_character.h @@ -575,6 +575,8 @@ class Game_Character { */ virtual bool Move(int dir); + virtual bool CheckMove(int dir); + /** * Jump to (x, y) * diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 5054c4cd30..fd99130288 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -457,7 +457,7 @@ void Game_Interpreter::Update(bool reset_loop_count) { if (_state.wait_movement) { if (ShouldAssertMoveRoutes()) { - Debug::AssertBlockedMoves(); + Debug::AssertBlockedMoves(main_flag); } if (Game_Map::IsAnyMovePending()) { break; diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp index f47568fe07..f76bccd48f 100644 --- a/src/game_interpreter_debug.cpp +++ b/src/game_interpreter_debug.cpp @@ -134,12 +134,12 @@ std::string Debug::FormatEventName(Game_CommonEvent const& ce) { return fmt::format("CE{:04d}: '{}'", ce.GetIndex(), ce.GetName()); } -void Debug::AssertBlockedMoves() { +void Debug::AssertBlockedMoves(bool main_flag) { auto check = [](Game_Character& ev) { return ev.IsMoveRouteOverwritten() && !ev.IsMoveRouteFinished() && ev.GetStopCount() != 0xFFFF && ev.GetStopCount() > ev.GetMaxStopCount(); }; - auto assert_way = [](Game_Character& ev) { + auto assert_way = [&main_flag](Game_Character& ev) { using Code = lcf::rpg::MoveCommand::Code; auto& move_command = ev.GetMoveRoute().move_commands[ev.GetMoveRouteIndex()]; @@ -153,15 +153,15 @@ void Debug::AssertBlockedMoves() { to_y = from_y + ev.GetDyFromDirection(dir); if (from_x != to_x && from_y != to_y) { - bool valid = Game_Map::AssertWay(ev, from_x, from_y, from_x, to_y); + bool valid = Game_Map::AssertWay(ev, from_x, from_y, from_x, to_y, main_flag); if (valid) - valid = Game_Map::AssertWay(ev, from_x, to_y, to_x, to_y); + valid = Game_Map::AssertWay(ev, from_x, to_y, to_x, to_y, main_flag); if (valid) - valid = Game_Map::AssertWay(ev, from_x, from_y, to_x, from_y); + valid = Game_Map::AssertWay(ev, from_x, from_y, to_x, from_y, main_flag); if (valid) - valid = Game_Map::AssertWay(ev, to_x, from_y, to_x, to_y); + valid = Game_Map::AssertWay(ev, to_x, from_y, to_x, to_y, main_flag); } else { - Game_Map::AssertWay(ev, from_x, from_y, to_x, to_y); + Game_Map::AssertWay(ev, from_x, from_y, to_x, to_y, main_flag); } } }; diff --git a/src/game_interpreter_debug.h b/src/game_interpreter_debug.h index bd66663d76..167f1a977c 100644 --- a/src/game_interpreter_debug.h +++ b/src/game_interpreter_debug.h @@ -74,7 +74,7 @@ namespace Debug { std::string FormatEventName(Game_CommonEvent const& ce); - void AssertBlockedMoves(); + void AssertBlockedMoves(bool main_flag); } #endif diff --git a/src/game_map.cpp b/src/game_map.cpp index c84dc0cfdf..450496bca9 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -674,13 +674,36 @@ static int GetPassableMask(int old_x, int old_y, int new_x, int new_y) { return bit; } +static int GetEventId(Game_Character const& ch) { + switch (ch.GetType()) { + case Game_Character::Event: + return static_cast(ch).GetId(); + case Game_Character::Player: + return Game_Character::CharPlayer; + case Game_Character::Vehicle: + switch (static_cast(ch).GetVehicleType()) { + case Game_Vehicle::Boat: + return Game_Character::CharBoat; + case Game_Vehicle::Ship: + return Game_Character::CharShip; + case Game_Vehicle::Airship: + return Game_Character::CharAirship; + } + } + assert(false); + return 0; +} + enum ProcessWayImpl { eProcessWayImpl_CheckWay, /* 'ProcessWay' causes side effects. */ eProcessWayImpl_MakeWay, /* 'ProcessWay' will generate warnings for blocked movement. */ - eProcessWayImpl_AssertWay + eProcessWayImpl_AssertWayForeground, + /* 'ProcessWayEx' will generate warnings + for blocked movement. */ + eProcessWayImpl_AssertWayBackground }; template @@ -700,7 +723,7 @@ static bool WouldCollide(const Game_Character& self, const T& other, bool self_c if (self.GetType() == Game_Character::Event && other.GetType() == Game_Character::Event && (self.IsOverlapForbidden() || other.IsOverlapForbidden())) { - if constexpr (impl == eProcessWayImpl_AssertWay) { + if constexpr (impl == eProcessWayImpl_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { if (self.IsOverlapForbidden()) { Output::Warning("MoveRoute: {} is not allowed to overlap with other events!", Debug::FormatEventName(self)); } else { @@ -711,15 +734,118 @@ static bool WouldCollide(const Game_Character& self, const T& other, bool self_c } if (other.GetLayer() == lcf::rpg::EventPage::Layers_same && self_conflict) { - if constexpr (impl == eProcessWayImpl_AssertWay) { + if constexpr (impl == eProcessWayImpl_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { Output::Warning("MoveRoute: {} can't move (self collision)!", Debug::FormatEventName(self)); } return true; } if (self.GetLayer() == other.GetLayer()) { - if constexpr (impl == eProcessWayImpl_AssertWay) { - //TODO: check if 'other' could still move away due to some routing behavior... + if constexpr (impl == eProcessWayImpl_AssertWayBackground) { + // check if 'self' is blocked by player + // if the blocked movement doesn't occur in foreground + // context, then they could just walk away + + if (other.GetType() == Game_Character::Player) { + return false; + } + //TODO: should maybe check if offscreen + if (other.GetType() == Game_Character::Vehicle) { + return false; + } + } + if constexpr (impl == eProcessWayImpl_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { + const auto rng_moves = { + lcf::rpg::EventPage::MoveType_random, + lcf::rpg::EventPage::MoveType_toward, + lcf::rpg::EventPage::MoveType_away + }; + + // check if 'other' could still move away due to some routing behavior... + bool do_check_next_move_command = false; + auto check_rng_move = [&](Game_Character const& ch, lcf::Span ignore_list) { + return Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX(), ch.GetY() + ch.GetDyFromDirection(Game_Character::Up), true, ignore_list) + || Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX(), ch.GetY() + ch.GetDyFromDirection(Game_Character::Down), true, ignore_list) + || Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX() + ch.GetDxFromDirection(Game_Character::Left), ch.GetY(), true, ignore_list) + || Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX() + ch.GetDxFromDirection(Game_Character::Right), ch.GetY(), true, ignore_list); + }; + std::vector ignore_list; + ignore_list.emplace_back(GetEventId(self)); + + if (other.IsMoveRouteOverwritten() && !other.IsMoveRouteFinished()) { + do_check_next_move_command = true; + } else if (other.GetType() == Game_Character::Event) { + if constexpr (impl == eProcessWayImpl_AssertWayForeground) { + auto* page = reinterpret_cast(other).GetActivePage(); + auto move_type = page ? page->move_type : 0; + + //TODO: only when !main_flag + if (move_type == lcf::rpg::EventPage::MoveType_vertical) { + //check if other event culd still move up/down... + if (Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX(), other.GetY() + other.GetDyFromDirection(Game_Character::Up), true, ignore_list) + || Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX(), other.GetY() + other.GetDyFromDirection(Game_Character::Down), true, ignore_list)) { + return false; + } + } else if (move_type == lcf::rpg::EventPage::MoveType_horizontal) { + //check if other event culd still move left/right... + if (Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX() + other.GetDxFromDirection(Game_Character::Left), other.GetY(), true, ignore_list) + || Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX() + other.GetDxFromDirection(Game_Character::Right), other.GetY(), true, ignore_list)) { + return false; + } + } else if (std::any_of(rng_moves.begin(), rng_moves.end(), [&move_type](auto mt) { return move_type == mt; })) { + //check if other event culd still move in any direction... + if (check_rng_move(other, ignore_list)) { + return false; + } + } else if (move_type == lcf::rpg::EventPage::MoveType_custom) { + //check if other events custom route would make it possible for this event to move... + do_check_next_move_command = true; + } + } + } + + if (do_check_next_move_command) { + auto& move_command = other.GetMoveRoute().move_commands[other.GetMoveRouteIndex()]; + bool move_success = false; + + using Code = lcf::rpg::MoveCommand::Code; + switch (static_cast(move_command.command_id)) { + case Code::move_up: + case Code::move_right: + case Code::move_down: + case Code::move_left: + case Code::move_upright: + case Code::move_downright: + case Code::move_downleft: + case Code::move_upleft: + move_success = other.CheckMove(static_cast(move_command.command_id)); + break; + case Code::move_forward: + move_success = other.CheckMove(other.GetDirection()); + break; + case Code::move_random: + move_success = check_rng_move(other, ignore_list); + break; + case Code::move_towards_hero: + if constexpr (impl == eProcessWayImpl_AssertWayForeground) { + move_success = check_rng_move(other, ignore_list); + } else { + move_success = other.CheckMove(other.GetDirectionToCharacter(*Main_Data::game_player)); + } + break; + case Code::move_away_from_hero: + if constexpr (impl == eProcessWayImpl_AssertWayForeground) { + move_success = check_rng_move(other, ignore_list); + } else { + move_success = other.CheckMove(other.GetDirectionAwayCharacter(*Main_Data::game_player)); + } + break; + } + if (move_success) { + return false; + } + } + Output::Warning("MoveRoute: {} would overlap with {}!", Debug::FormatEventName(self), Debug::FormatEventName(other)); } return true; @@ -811,7 +937,7 @@ static bool ProcessWay(const Game_Character & self, // Note, even for diagonal, if the tile is invalid we still check vertical/horizontal first! if (!Game_Map::IsValid(to_x, to_y)) { - if constexpr (impl == eProcessWayImpl_AssertWay) { + if constexpr (impl == eProcessWayImpl_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { Output::Warning("MoveRoute: {} can't move out-of-bounds (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); } return false; @@ -858,7 +984,7 @@ static bool ProcessWay(const Game_Character & self, from_x = Game_Map::RoundX(from_x); from_y = Game_Map::RoundY(from_y); if (!Game_Map::IsPassableTile(&self, bit_from, from_x, from_y)) { - if constexpr (impl == eProcessWayImpl_AssertWay) { + if constexpr (impl == eProcessWayImpl_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { Output::Warning("MoveRoute: {} can't step of current tile (x:{}, y:{})!", Debug::FormatEventName(self), from_x, from_y); } return false; @@ -912,7 +1038,7 @@ static bool ProcessWay(const Game_Character & self, bool result = Game_Map::IsPassableTile( &self, bit, to_x, to_y ); - if constexpr (impl == eProcessWayImpl_AssertWay) { + if constexpr (impl == eProcessWayImpl_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { if (!result) { Output::Warning("MoveRoute: {} can't pass target tile (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); } @@ -949,14 +1075,18 @@ bool Game_Map::CheckWay(const Game_Character& self, bool Game_Map::AssertWay(const Game_Character& self, int from_x, int from_y, - int to_x, int to_y + int to_x, int to_y, bool main_flag ) { - return ProcessWay( + if (main_flag) { + return ProcessWay( + self, from_x, from_y, to_x, to_y, {} + ); + } + return ProcessWay( self, from_x, from_y, to_x, to_y, {} ); } - bool Game_Map::MakeWay(const Game_Character& self, int from_x, int from_y, int to_x, int to_y diff --git a/src/game_map.h b/src/game_map.h index dda4aef1bf..07e8b5fb37 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -260,7 +260,7 @@ namespace Game_Map { bool AssertWay(const Game_Character& self, int from_x, int from_y, - int to_x, int to_y); + int to_x, int to_y, bool main_flag); /** * Gets if possible to land the airship at (x,y) From 9cd4607e22d83a3614303d6624388d23ec3b783a Mon Sep 17 00:00:00 2001 From: florianessl Date: Sun, 19 Jan 2025 16:34:22 +0100 Subject: [PATCH 16/16] Added new game config for enabling MoveRoute debugging --- src/game_config_game.cpp | 6 ++++++ src/game_config_game.h | 1 + src/game_interpreter.cpp | 9 +-------- src/game_interpreter.h | 1 - src/player.cpp | 5 +++++ 5 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/game_config_game.cpp b/src/game_config_game.cpp index 4a719b5556..c350daf216 100644 --- a/src/game_config_game.cpp +++ b/src/game_config_game.cpp @@ -177,6 +177,10 @@ void Game_ConfigGame::LoadFromArgs(CmdlineParser& cp) { continue; } + if (cp.ParseNext(arg, 0, { "--debug-routes", "--debug-move-routes" })) { + debug_moveroutes.Set(true); + continue; + } cp.SkipNext(); } @@ -229,6 +233,8 @@ void Game_ConfigGame::LoadFromStream(Filesystem_Stream::InputStream& is) { if (patch_direct_menu.FromIni(ini)) { patch_override = true; } + + debug_moveroutes.FromIni(ini); } void Game_ConfigGame::PrintActivePatches() { diff --git a/src/game_config_game.h b/src/game_config_game.h index fe9e3ec0c0..acb4e05b5b 100644 --- a/src/game_config_game.h +++ b/src/game_config_game.h @@ -48,6 +48,7 @@ struct Game_ConfigGame { BoolConfigParam patch_rpg2k3_commands{ "RPG2k3 Event Commands", "Enable support for RPG2k3 event commands", "Patch", "RPG2k3Commands", false }; ConfigParam patch_anti_lag_switch{ "Anti-Lag Switch", "Disable event page refreshes when switch is set", "Patch", "AntiLagSwitch", 0 }; ConfigParam patch_direct_menu{ "Direct Menu", " Allows direct access to subscreens of the default menu", "Patch", "DirectMenu", 0 }; + BoolConfigParam debug_moveroutes{ "Debug MoveRoutes", "Generate Interpreter warnings whenever \"MoveRoute\" commands are blocked", "Game", "DebugMoveRoutes", false }; // Command line only BoolConfigParam patch_support{ "Support patches", "When OFF all patch support is disabled", "", "", true }; diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index fd99130288..702773a922 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -456,7 +456,7 @@ void Game_Interpreter::Update(bool reset_loop_count) { } if (_state.wait_movement) { - if (ShouldAssertMoveRoutes()) { + if (Player::game_config.debug_moveroutes.Get()) { Debug::AssertBlockedMoves(main_flag); } if (Game_Map::IsAnyMovePending()) { @@ -5887,10 +5887,3 @@ bool Game_Interpreter_Inspector::IsInActiveExcecution(Game_CommonEvent const& ce } return ce.interpreter && ce.interpreter->IsRunning(); } - -bool Game_Interpreter::ShouldAssertMoveRoutes() const { - //FIXME: Needs liblcf update - //return (main_flag && (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_blocked_movement_main) > 0) - // || (!main_flag && (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_blocked_movement_parallel) > 0); - return true; -} diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 74c7439c54..e42c44462f 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -130,7 +130,6 @@ class Game_Interpreter : public Game_BaseInterpreterContext /** @return true if wait command (time or key) is active. Used by 2k3 battle system */ bool IsWaitingForWaitCommand() const; - bool ShouldAssertMoveRoutes() const; protected: static constexpr int loop_limit = 10000; static constexpr int call_stack_limit = 1000; diff --git a/src/player.cpp b/src/player.cpp index 43425216e5..34a62dfabb 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -1549,6 +1549,11 @@ Debug options: --start-position X Y Overwrite the party start position and move the party to position (X, Y). Incompatible with --load-game-id. + --debug-routes This option enables a range of asserts whenever a + scripting stack is halted due to a WaitForAllMovement + command. If the Interpreter is not able to resume operation, + due to a blocked route, detailed warnings will be printed to + the output. --test-play Enable TestPlay (Debug) mode. Other options: