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/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_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_commonevent.cpp b/src/game_commonevent.cpp index d15ab4dc59..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); + interpreter->Push(this); } diff --git a/src/game_commonevent.h b/src/game_commonevent.h index ae70179b0b..d9bb817fa7 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 @@ -120,7 +121,7 @@ class Game_CommonEvent { /** Interpreter for parallel common events. */ std::unique_ptr interpreter; - friend class Scene_Debug; + friend class Game_Interpreter_Inspector; }; #endif 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_event.cpp b/src/game_event.cpp index a409e6aece..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); + interpreter->Push(this); } interpreter->Update(!resume_async); diff --git a/src/game_event.h b/src/game_event.h index 225cc4e4a4..b2f3dfd07f 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" @@ -218,7 +219,7 @@ class Game_Event : public Game_EventBase { const lcf::rpg::EventPage* page = nullptr; std::unique_ptr interpreter; - friend class Scene_Debug; + 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..702773a922 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -100,10 +100,10 @@ bool Game_Interpreter::IsRunning() const { } // Setup. -void Game_Interpreter::Push( +void Game_Interpreter::PushInternal( + 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) << 4); + } + if (_state.stack.empty() && main_flag && !Game_Battle::IsBattleRunning()) { Main_Data::game_system->ClearMessageFace(); Main_Data::game_player->SetMenuCalling(false); @@ -443,6 +456,9 @@ void Game_Interpreter::Update(bool reset_loop_count) { } if (_state.wait_movement) { + if (Player::game_config.debug_moveroutes.Get()) { + Debug::AssertBlockedMoves(main_flag); + } if (Game_Map::IsAnyMovePending()) { break; } @@ -536,16 +552,22 @@ 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::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, bool triggered_by_decision_key) { - Push(page->event_commands, ev->GetId(), triggered_by_decision_key, page->ID); +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) { - Push(ev->GetList(), 0, false); +void Game_Interpreter::PushInternal(Game_CommonEvent* ev, ExecutionType ex_type) { + PushInternal({ ex_type, EventType::CommonEvent }, ev->GetList(), ev->GetId()); } bool Game_Interpreter::CheckGameOver() { @@ -2025,13 +2047,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 {}; } } @@ -2054,7 +2076,6 @@ std::optional Game_Interpreter::HandleDynRpgScript(const lcf::rpg::EventCo return Main_Data::game_dynrpg->Invoke(command, this); } - return {}; } @@ -3984,7 +4005,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { // return true; } - Push(common_event); + Push(common_event); return true; } @@ -4012,7 +4033,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { // return true; } - Push(page->event_commands, event->GetId(), false, page->ID); + Push(page->event_commands, event->GetId(), page->ID); return true; } @@ -4180,8 +4201,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()); @@ -5382,7 +5425,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({ cmd }, GetCurrentEventId(), 0); return true; } @@ -5784,3 +5827,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..e42c44462f 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 */ @@ -64,14 +67,20 @@ class Game_Interpreter : public Game_BaseInterpreterContext void Update(bool reset_loop_count=true); + template void Push( - std::vector _list, - int _event_id, - bool started_by_decision_key = false, - int event_page_id = 0 + std::vector _list, + int _event_id, + int event_page_id = 0 ); + + template void Push(Game_Event* ev); - void Push(Game_Event* ev, const lcf::rpg::EventPage* page, bool triggered_by_decision_key); + + template + void Push(Game_Event* ev, const lcf::rpg::EventPage* page); + + template void Push(Game_CommonEvent* ev); void InputButton(); @@ -351,9 +360,63 @@ class Game_Interpreter : public Game_BaseInterpreterContext KeyInputState _keyinput; AsyncOp _async_op = {}; - friend class Scene_Debug; + 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; }; +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); +}; + +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 || type_ex == InterpreterExecutionType::ManiacHook, "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 ba807ef747..5dc4ab1d50 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(page.event_commands, 0); 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); 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); // 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(pre_commands, 0); // Necessary to start the sub-event. maniac_interpreter->Update(); diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp new file mode 100644 index 0000000000..f76bccd48f --- /dev/null +++ b/src/game_interpreter_debug.cpp @@ -0,0 +1,186 @@ +/* + * 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() { + Game_Interpreter_Inspector inspector; + + 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 (!inspector.IsInActiveExcecution(ev, true)) { + continue; + } + + ev_ids.emplace_back(ev.GetId()); + state_ev.emplace_back(inspector.GetExecState(ev)); + } + for (auto& ce : Game_Map::GetCommonEvents()) { + 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 + } + + return { ev_ids, ce_ids, state_ev, state_ce }; +} + +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--) { + 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); + } + + 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: + 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 ""; +} + +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()); +} + +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 = [&main_flag](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, main_flag); + if (valid) + 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, main_flag); + if (valid) + 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, main_flag); + } + } + }; + 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 new file mode 100644 index 0000000000..167f1a977c --- /dev/null +++ b/src/game_interpreter_debug.h @@ -0,0 +1,80 @@ +/* + * 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 { + InterpreterExecutionType type_ex; + InterpreterEventType type_ev; + int evt_id, page_id; + std::string name; + int stack_item_no, cmd_current; + size_t cmd_count; + bool map_has_changed; + }; + + std::vector CreateCallStack(const lcf::rpg::SaveEventExecState& state); + + std::string GetEventName(const lcf::rpg::SaveEventExecFrame& frame); + + std::string FormatEventName(Game_Character const& ev); + + std::string FormatEventName(Game_CommonEvent const& ce); + + void AssertBlockedMoves(bool main_flag); +} + +#endif diff --git a/src/game_interpreter_shared.h b/src/game_interpreter_shared.h index 15bad41e5d..a033aaad18 100644 --- a/src/game_interpreter_shared.h +++ b/src/game_interpreter_shared.h @@ -34,6 +34,64 @@ class Game_BaseInterpreterContext; namespace Game_Interpreter_Shared { + enum class EventType { + None = 0, + MapEvent, + CommonEvent, + BattleEvent, + LAST + }; + static constexpr auto kEventType = lcf::makeEnumTags( + "None", + "MapEvent", + "CommonEvent", + "BattleEvent" + ); + static_assert(kEventType.size() == static_cast(EventType::LAST)); + + 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, + ManiacHook, + LAST + }; + static constexpr auto kExecutionType = lcf::makeEnumTags( + "Action", + "Touch", + "Collision", + "AutoStart", + "Parallel", + "Call", + "BattleStart", + "BattleParallel", + "---", + "---", + "DeathHandler", + "Eval", + "DebugCall", + "ManiacHook" + ); + static_assert(kExecutionType.size() == static_cast(ExecutionType::LAST)); + /* * Indicates how the target of an interpreter operation (lvalue) should be evaluated. */ @@ -115,6 +173,12 @@ 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); + + 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) { @@ -153,6 +217,36 @@ 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; +} + +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/game_map.cpp b/src/game_map.cpp index 780d26aaf5..450496bca9 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -674,7 +674,40 @@ 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) { +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_AssertWayForeground, + /* 'ProcessWayEx' will generate warnings + for blocked movement. */ + eProcessWayImpl_AssertWayBackground +}; + +template +static bool WouldCollide(const Game_Character& self, const T& other, bool self_conflict) { if (self.GetThrough() || other.GetThrough()) { return false; } @@ -690,14 +723,131 @@ 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_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { + 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_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_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; } @@ -713,7 +863,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 +873,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 +893,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) { @@ -753,36 +903,30 @@ 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, - 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 - ) -{ +/** + * 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 & 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 ignore_some_events_by_id A set of + * specific event IDs to ignore. + * @return See CheckWay or MakeWay. + */ +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); @@ -793,6 +937,9 @@ bool Game_Map::CheckOrMakeWayEx(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_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { + Output::Warning("MoveRoute: {} can't move out-of-bounds (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); + } return false; } @@ -805,14 +952,13 @@ bool Game_Map::CheckOrMakeWayEx(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()) { @@ -837,7 +983,10 @@ 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)) { + 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; } } @@ -845,13 +994,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,9 +1035,56 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, bit = Passable::Down | Passable::Up | Passable::Left | Passable::Right; } - return 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_AssertWayForeground || impl == eProcessWayImpl_AssertWayBackground) { + 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, {} + ); +} + +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) { + 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, + 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, bool main_flag +) { + 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, @@ -896,9 +1092,8 @@ bool Game_Map::MakeWay(const Game_Character& self, int to_x, int to_y ) { - return CheckOrMakeWayEx( - self, from_x, from_y, to_x, to_y, true, {}, true - ); + return ProcessWay( + self, from_x, from_y, to_x, to_y, {}); } @@ -995,22 +1190,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) { @@ -1060,7 +1247,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]; @@ -1393,7 +1580,7 @@ bool Game_Map::UpdateForegroundEvents(MapUpdateAsyncContext& actx) { } } if (run_ce) { - interp.Push(run_ce); + interp.Push(run_ce); } Game_Event* run_ev = nullptr; @@ -1408,7 +1595,25 @@ bool Game_Map::UpdateForegroundEvents(MapUpdateAsyncContext& actx) { } } if (run_ev) { - interp.Push(run_ev); + if (run_ev->WasStartedByDecisionKey()) { + interp.Push(run_ev); + } else { + switch (run_ev->GetTrigger()) { + case lcf::rpg::EventPage::Trigger_touched: + interp.Push(run_ev); + break; + case lcf::rpg::EventPage::Trigger_collision: + interp.Push(run_ev); + break; + case lcf::rpg::EventPage::Trigger_auto_start: + interp.Push(run_ev); + break; + case lcf::rpg::EventPage::Trigger_action: + default: + interp.Push(run_ev); + break; + } + } run_ev->ClearWaitingForegroundExecution(); } @@ -1558,7 +1763,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); } auto tt = Game_Battle::GetDeathHandlerTeleport(); diff --git a/src/game_map.h b/src/game_map.h index d7cb29126a..07e8b5fb37 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -258,31 +258,9 @@ 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); + bool AssertWay(const Game_Character& self, + int from_x, int from_y, + int to_x, int to_y, bool main_flag); /** * Gets if possible to land the airship at (x,y) @@ -652,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); /** 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/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: 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/scene_debug.cpp b/src/scene_debug.cpp index 7a6dd70cde..9206a73af0 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++; + 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++; + } + for (int i = 0; i < bg_states.CountCommonEventInterpreters() && count_items < 10; i++) { + if (skip_items > 0) { + skip_items--; + continue; } + 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++; + } } 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; @@ -1127,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); 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); Scene::PopUntil(Scene::Map); Output::Debug("Debug Scene Forced execution of common event {} on the map foreground interpreter.", ce.GetIndex()); } @@ -1156,11 +1157,11 @@ void Scene_Debug::DoCallMapEvent() { } if (Game_Battle::IsBattleRunning()) { - Game_Battle::GetInterpreter().Push(me, page, false); + 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, false); + 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); } @@ -1184,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(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() { @@ -1217,52 +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; + lcf::rpg::SaveEventExecState state_display; 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(); + state_display = 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]; - first_line = fmt::format("EV{:04d}: {}", evt_id, Game_Map::GetEvent(evt_id)->GetName()); + } 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 - 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()) { + const auto& [ce_id, state] = bg_states.GetCommonEventInterpreter(index - bg_states.CountEventInterpreters() - 1); + state_display = state; for (auto& ce : Game_Map::GetCommonEvents()) { - if (ce.common_event_id == ce_id) { - first_line = fmt::format("CE{:04d}: {}", ce_id, ce.GetName()); - evt_id = ce_id; + if (ce.GetId() == ce_id) { + first_line = Debug::FormatEventName(ce); valid = true; break; } @@ -1271,36 +1248,39 @@ 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(first_line, state_display); } else { state_interpreter.selected_state = -1; - interpreter_window->SetStackState(0, 0, "", {}); + interpreter_window->SetStackState("", {}); } } -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 <= 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; + auto const& state = inspector.GetExecState(*ev); 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; + 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 a41fe4a3ef..a3efc68e6e 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" @@ -190,13 +191,9 @@ class Scene_Debug : public Scene { bool interpreter_states_cached = false; void UpdateInterpreterWindow(int index); - lcf::rpg::SaveEventExecFrame& GetSelectedInterpreterFrameFromUiState() const; - void CacheBackgroundInterpreterStates(); + lcf::rpg::SaveEventExecFrame const& GetSelectedInterpreterFrameFromUiState() const; 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_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; diff --git a/src/window_interpreter.cpp b/src/window_interpreter.cpp index 8070a5cf88..ea2d462fcb 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,66 +99,17 @@ 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(state); - 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 (static_cast(item.cmd_count) > max_cmd_count) + max_cmd_count = item.cmd_count; } item_max = stack_display_items.size() + lines_without_stack; @@ -248,7 +199,14 @@ void Window_Interpreter::DrawDescriptionLines() { rect = GetItemRect(i++); contents->ClearRect(rect); - contents->TextDraw(rect.x, rect.y, Font::ColorDefault, "Stack Size: "); + + 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); } @@ -257,16 +215,36 @@ 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) { - 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 0d0244e803..6586c3170b 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" @@ -72,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(); @@ -89,17 +90,9 @@ class Window_Interpreter : public Window_Selectable { #endif private: struct InterpDisplayItem { - bool is_ce = false; - int owner_evt_id = 0; 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 +102,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