From 07b032a64ef996530f909a870b2ebc4e9eb12619 Mon Sep 17 00:00:00 2001 From: Mauro Junior <45118493+jetrotal@users.noreply.github.com> Date: Sat, 1 Nov 2025 16:13:29 -0300 Subject: [PATCH 1/2] Maniacs Patch - AddMoveRoute Command Introduces the ability to chain multiple move route commands using a new AddMoveRoute (3027) event command, allowing for more flexible and scriptable character movement. Adds Game_Character::GetId and AppendMoveRoute methods, custom Maniac Patch move commands, and internal buffering logic in Game_Interpreter to support move route chaining. Also implements CommandAddMoveRoute in Game_Interpreter_Map and provides GetId overrides for player and vehicle classes. --- src/game_character.cpp | 57 ++++++++++++ src/game_character.h | 5 + src/game_interpreter.cpp | 54 +++++++---- src/game_interpreter.h | 4 + src/game_interpreter_map.cpp | 172 +++++++++++++++++++++++++++++++++++ src/game_interpreter_map.h | 1 + src/game_player.h | 2 + src/game_vehicle.h | 2 + 8 files changed, 281 insertions(+), 16 deletions(-) diff --git a/src/game_character.cpp b/src/game_character.cpp index b9ade33e44..4be4a6be3a 100644 --- a/src/game_character.cpp +++ b/src/game_character.cpp @@ -41,6 +41,12 @@ Game_Character::Game_Character(Type type, lcf::rpg::SaveMapEventBase* d) : { } +int Game_Character::GetId() const { + // This should be overridden by subclasses that have a meaningful ID. + // Returning 0 for the base class is a safe default. + return 0; +} + void Game_Character::SanitizeData(std::string_view name) { SanitizeMoveRoute(name, data()->move_route, data()->move_route_index, "move_route_index"); } @@ -445,6 +451,49 @@ void Game_Character::UpdateMoveRoute(int32_t& current_index, const lcf::rpg::Mov case Code::decrease_transp: SetTransparency(GetTransparency() - 1); break; + // START of Maniac Patch custom commands + case static_cast(100): // Custom: Set Speed + SetMoveSpeed(move_command.parameter_a); + break; + case static_cast(101): // Custom: Set Freq + SetMoveFrequency(move_command.parameter_a); + SetMaxStopCountForStep(); + break; + case static_cast(102): // Custom: Set Transparency + SetTransparency(move_command.parameter_a); + break; + // END of Maniac Patch custom commands + + // START of new custom "off-script" commands + case static_cast(110): // SetAnimationType + SetAnimationType(static_cast(move_command.parameter_a)); + break; + case static_cast(111): { // Event2Event + Game_Character* target = GetCharacter(move_command.parameter_a, GetId()); + if (target) { + SetFacing(target->GetFacing()); + SetDirection(target->GetDirection()); + SetX(target->GetX()); + SetY(target->GetY()); + } + break; + } + case static_cast(112): { // FaceTowards + Game_Character* target = GetCharacter(move_command.parameter_a, GetId()); + if (target && !IsMoving()) { + TurnTowardCharacter(*target); + UpdateFacing(); + } + break; + } + case static_cast(113): { // FaceAway + Game_Character* target = GetCharacter(move_command.parameter_a, GetId()); + if (target && !IsMoving()) { + TurnAwayFromCharacter(*target); + UpdateFacing(); + } + break; + } default: break; } @@ -791,6 +840,14 @@ void Game_Character::CancelMoveRoute() { SetMoveRouteFinished(false); } +void Game_Character::AppendMoveRoute(const lcf::rpg::MoveRoute& new_route) { + auto& current_commands = data()->move_route.move_commands; + const auto& new_commands = new_route.move_commands; + current_commands.insert(current_commands.end(), new_commands.begin(), new_commands.end()); + // Do NOT overwrite repeat/skippable flags. They belong to the whole route. + SetMoveRouteFinished(false); +} + struct SearchNode { int x = 0; int y = 0; diff --git a/src/game_character.h b/src/game_character.h index 2549fe516d..6d97e0df98 100644 --- a/src/game_character.h +++ b/src/game_character.h @@ -45,6 +45,8 @@ class Game_Character { static std::string_view TypeToStr(Type t); + virtual int GetId() const; + virtual ~Game_Character() = default; Game_Character(Game_Character&&) = default; Game_Character& operator=(const Game_Character&) = default; @@ -700,6 +702,9 @@ class Game_Character { */ void ForceMoveRoute(const lcf::rpg::MoveRoute& new_route, int frequency); + /** Appends a move route to the current one */ + void AppendMoveRoute(const lcf::rpg::MoveRoute & new_route); + /** * Cancels a previous forced move route. */ diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index deb29dd6d8..44294fa777 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -609,6 +609,10 @@ bool Game_Interpreter::ExecuteCommand() { } bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { + if (com.code != static_cast(Cmd::MoveEvent) && com.code != 3027 /* AddMoveRoute */) { + _move_route_chain_active = false; + } + switch (static_cast(com.code)) { case Cmd::ShowMessage: return CmdSetup<&Game_Interpreter::CommandShowMessage, 0>(com); @@ -3174,28 +3178,46 @@ bool Game_Interpreter::CommandMoveEvent(lcf::rpg::EventCommand const& com) { // int repeat = ManiacBitmask(com.parameters[2], 0x1); Game_Character* event = GetCharacter(event_id, "MoveEvent"); - if (event != NULL) { - // If the event is a vehicle in use, push the commands to the player instead - if (event_id >= Game_Character::CharBoat && event_id <= Game_Character::CharAirship) - if (static_cast(event)->IsInUse()) - event = Main_Data::game_player.get(); + if (event == nullptr) { + _move_route_chain_active = false; + return true; + } - lcf::rpg::MoveRoute route; - int move_freq = com.parameters[1]; + // If the event is a vehicle in use, push the commands to the player instead + if (event_id >= Game_Character::CharBoat && event_id <= Game_Character::CharAirship) + if (static_cast(event)->IsInUse()) + event = Main_Data::game_player.get(); - if (move_freq <= 0 || move_freq > 8) { - // Invalid values - move_freq = 6; - } + lcf::rpg::MoveRoute route; + int move_freq = com.parameters[1]; - route.repeat = repeat != 0; - route.skippable = com.parameters[3] != 0; + if (move_freq <= 0 || move_freq > 8) { + // Invalid values + move_freq = 6; + } - for (auto it = com.parameters.begin() + 4; it < com.parameters.end(); ) { - route.move_commands.push_back(DecodeMove(it)); - } + route.repeat = repeat != 0; + route.skippable = com.parameters[3] != 0; + for (auto it = com.parameters.begin() + 4; it < com.parameters.end(); ) { + route.move_commands.push_back(DecodeMove(it)); + } + + auto& frame = GetFrame(); + bool is_chain_start = (frame.current_command + 1 < static_cast(frame.commands.size()) && + frame.commands[frame.current_command + 1].code == 3027 /* AddMoveRoute */); + + if (is_chain_start) { + // This is the start of a chain. Buffer the data and don't execute yet. + _move_route_event_id = event->GetId(); + _move_route_chain_active = true; + _move_route_buffer = std::move(route); + _move_route_frequency = move_freq; + } + else { + // This is a standalone command. Execute immediately. event->ForceMoveRoute(route, move_freq); + _move_route_chain_active = false; } return true; } diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 7ddf61ee4d..dde9c457c9 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -372,6 +372,10 @@ class Game_Interpreter : public Game_BaseInterpreterContext /** Shared instance for ResolveEventCommand */ lcf::rpg::EventCommand resolved_cmd; + int _move_route_event_id = 0; + bool _move_route_chain_active = false; + lcf::rpg::MoveRoute _move_route_buffer; + int _move_route_frequency = 0; private: void PushInternal( diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index d22c68003e..f0dc556634 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -25,6 +25,7 @@ #include #include "audio.h" #include "feature.h" +#include "game_strings.h" #include "game_character.h" #include "game_map.h" #include "game_battle.h" @@ -242,6 +243,8 @@ bool Game_Interpreter_Map::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CmdSetup<&Game_Interpreter_Map::CommandOpenLoadMenu, 0>(com); case Cmd::ToggleAtbMode: return CmdSetup<&Game_Interpreter_Map::CommandToggleAtbMode, 0>(com); + case static_cast(3027): // CommandAddMoveRoute + return CmdSetup<&Game_Interpreter_Map::CommandAddMoveRoute, 0>(com); case Cmd::EasyRpg_TriggerEventAt: return CmdSetup<&Game_Interpreter_Map::CommandEasyRpgTriggerEventAt, 4>(com); case Cmd::EasyRpg_WaitForSingleMovement: @@ -873,6 +876,175 @@ bool Game_Interpreter_Map::CommandToggleAtbMode(lcf::rpg::EventCommand const& /* return true; } +bool Game_Interpreter_Map::CommandAddMoveRoute(lcf::rpg::EventCommand const& com) { // code 3027 + if (!Player::IsPatchManiac()) { + return true; + } + + if (!_move_route_chain_active) { + Output::Warning("AddMoveRoute: This command must be used after a MoveEvent command."); + return true; + } + + // Helper to safely decode parameters. It checks if the parameter exists before access. + auto get_p = [&](int mode_bit, int val_idx, int default_val = 0) { + if (com.parameters.size() <= static_cast(val_idx)) { + return default_val; + } + return ValueOrVariableBitfield(com, 0, mode_bit, val_idx); + }; + + // Helper to add a command to the buffer, optionally repeated. + auto add_command_to_buffer = [&](const lcf::rpg::MoveCommand& cmd, int repeat = 1) { + for (int i = 0; i < repeat; ++i) { + _move_route_buffer.move_commands.push_back(cmd); + } + }; + + if (com.parameters.size() < 2) { + Output::Warning("AddMoveRoute: Command is missing action_type parameter."); + _move_route_chain_active = false; // Break chain on error + return true; + } + + int action_type = com.parameters[1]; + lcf::rpg::MoveCommand cmd; + + switch (action_type) { + case 0: { // .move(direction, distance) and specific moves like .moveUp(n) + cmd.command_id = get_p(1, 2); + int distance = get_p(2, 3, 1); + if (cmd.command_id >= 0 && cmd.command_id <= 11) { + add_command_to_buffer(cmd, distance); + } + break; + } + case 1: { // .face(direction) and specific facings like .faceUp + int direction = get_p(1, 2); + if (direction >= 0 && direction <= 10) { + cmd.command_id = direction + 12; // Offset to LCF MoveCommand IDs 12-22 + add_command_to_buffer(cmd); + } + break; + } + case 2: { // .pause + cmd.command_id = 23; // Wait + add_command_to_buffer(cmd); + break; + } + case 3: { // .jump(x_offset, y_offset) + int x_offset = get_p(1, 2); + int y_offset = get_p(2, 3); + + cmd.command_id = 24; add_command_to_buffer(cmd); + cmd.command_id = (int32_t)((x_offset >= 0) ? lcf::rpg::MoveCommand::Code::move_right : lcf::rpg::MoveCommand::Code::move_left); + add_command_to_buffer(cmd, std::abs(x_offset)); + cmd.command_id = (int32_t)((y_offset >= 0) ? lcf::rpg::MoveCommand::Code::move_down : lcf::rpg::MoveCommand::Code::move_up); + add_command_to_buffer(cmd, std::abs(y_offset)); + cmd.command_id = 25; add_command_to_buffer(cmd); + break; + } + case 4: { // Toggles like .fixDir/[on|off], .beginThrough/[on|off] + int toggle_type = get_p(1, 2); + bool is_on = get_p(2, 3) != 0; + int base_cmd_id = 0; + switch (toggle_type) { + case 0: base_cmd_id = 24; break; // Begin/End Jump + case 1: base_cmd_id = 26; break; // Lock/Unlock Facing + case 2: base_cmd_id = 36; break; // Through ON/OFF + case 3: base_cmd_id = 38; break; // Stop/Start Animation + default: return true; + } + cmd.command_id = base_cmd_id + (is_on ? 0 : 1); + add_command_to_buffer(cmd); + break; + } + case 5: { // Direct setters like .speed(n), .freq(n), .trans(n) + int type = get_p(1, 2); + int value = get_p(2, 3); + switch (type) { + case 0: cmd.command_id = 100; value += 4; break; // Custom: Set Speed + case 1: cmd.command_id = 101; break; // Custom: Set Freq + case 2: cmd.command_id = 102; break; // Custom: Set Transparency + default: return true; + } + cmd.parameter_a = value; + add_command_to_buffer(cmd); + break; + } + case 6: { // .switch(id, val) + cmd.parameter_a = get_p(1, 2); + cmd.command_id = get_p(2, 3) != 0 ? 32 : 33; // Switch ON / OFF + add_command_to_buffer(cmd); + break; + } + case 7: { // .setBody("[name]", [index]) + cmd.command_id = 34; + int string_mode = (com.parameters[0] >> 16) & 0xF; // logical arg 4 is at bit 16 + if (string_mode == 0) { // literal + cmd.parameter_string = com.string; + } + else if (string_mode == 1) { // direct t[id] + cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(com.parameters[5])); + } + else if (string_mode == 2) { // indirect t[v[id]] + int var_id = Main_Data::game_variables->Get(com.parameters[5]); + cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(var_id)); + } + cmd.parameter_a = get_p(1, 2); + add_command_to_buffer(cmd); + break; + } + case 8: { // .se("[name]", vol, pitch, balance) + cmd.command_id = 35; + int string_mode = (com.parameters[0] >> 20) & 0xF; // logical arg 5 is at bit 20 + if (string_mode == 0) { // literal + cmd.parameter_string = com.string; + } + else if (string_mode == 1) { // direct t[id] + cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(com.parameters[5])); + } + else if (string_mode == 2) { // indirect t[v[id]] + int var_id = Main_Data::game_variables->Get(com.parameters[5]); + cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(var_id)); + } + cmd.parameter_a = get_p(1, 2, 100); + cmd.parameter_b = get_p(2, 3, 100); + cmd.parameter_c = get_p(3, 4, 50); + add_command_to_buffer(cmd); + break; + } + default: + Output::Warning("AddMoveRoute: Unknown action type {}", action_type); + break; + } + + _move_route_buffer.repeat = (com.parameters[0] & (1 << 24)) != 0; + _move_route_buffer.skippable = (com.parameters[0] & (1 << 25)) != 0; + + auto& frame = GetFrame(); + bool is_chain_end = (frame.current_command + 1 >= static_cast(frame.commands.size()) || + frame.commands[frame.current_command + 1].code != 3027 /* AddMoveRoute */); + + if (is_chain_end) { + Game_Character* event = GetCharacter(_move_route_event_id, "AddMoveRoute (exec)"); + if (event != nullptr) { + if (_move_route_event_id >= Game_Character::CharBoat && _move_route_event_id <= Game_Character::CharAirship) { + if (static_cast(event)->IsInUse()) { + event = Main_Data::game_player.get(); + } + } + event->ForceMoveRoute(_move_route_buffer, _move_route_frequency); + } + else { + Output::Warning("AddMoveRoute: Could not find cached event with id {} to execute route.", _move_route_event_id); + } + _move_route_chain_active = false; + } + + return true; +} + bool Game_Interpreter_Map::CommandEasyRpgTriggerEventAt(lcf::rpg::EventCommand const& com) { if (!Player::HasEasyRpgExtensions()) { return true; diff --git a/src/game_interpreter_map.h b/src/game_interpreter_map.h index 9faf600a36..aaab6ffde3 100644 --- a/src/game_interpreter_map.h +++ b/src/game_interpreter_map.h @@ -83,6 +83,7 @@ class Game_Interpreter_Map : public Game_Interpreter bool CommandOpenMainMenu(lcf::rpg::EventCommand const& com); bool CommandOpenLoadMenu(lcf::rpg::EventCommand const& com); bool CommandToggleAtbMode(lcf::rpg::EventCommand const& com); + bool CommandAddMoveRoute(lcf::rpg::EventCommand const& com); bool CommandEasyRpgTriggerEventAt(lcf::rpg::EventCommand const& com); bool CommandEasyRpgPathfinder(lcf::rpg::EventCommand const& com); bool CommandEasyRpgWaitForSingleMovement(lcf::rpg::EventCommand const& com); diff --git a/src/game_player.h b/src/game_player.h index 9174e1f231..3a78c18911 100644 --- a/src/game_player.h +++ b/src/game_player.h @@ -37,6 +37,8 @@ class Game_Player : public Game_PlayerBase { public: Game_Player(); + int GetId() const override { return CharPlayer; } + /** Load from saved game */ void SetSaveData(lcf::rpg::SavePartyLocation data); diff --git a/src/game_vehicle.h b/src/game_vehicle.h index f5b85c1602..99ab34b544 100644 --- a/src/game_vehicle.h +++ b/src/game_vehicle.h @@ -42,6 +42,8 @@ class Game_Vehicle : public Game_VehicleBase { explicit Game_Vehicle(Type type); + int GetId() const override { return -data()->vehicle - 1; } // Boat:-2, Ship:-3, Airship:-4 + /** Load from saved game */ void SetSaveData(lcf::rpg::SaveVehicleLocation save); From fbee7c337de5d86c832278e29c000ab8ea00db13 Mon Sep 17 00:00:00 2001 From: Ghabry Date: Tue, 4 Aug 2026 11:28:13 +0200 Subject: [PATCH 2/2] (WIP) --- src/game_interpreter.cpp | 156 ++++++++++++++++++++++++++++---- src/game_interpreter.h | 7 +- src/game_interpreter_map.cpp | 171 ----------------------------------- src/game_interpreter_map.h | 7 -- 4 files changed, 139 insertions(+), 202 deletions(-) diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 44294fa777..0da4afe481 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -47,6 +48,8 @@ #include "game_interpreter_control_variables.h" #include "game_windows.h" #include "json_helper.h" +#include "lcf/rpg/eventcommand.h" +#include "lcf/rpg/movecommand.h" #include "maniac_patch.h" #include "spriteset_map.h" #include "sprite_character.h" @@ -609,10 +612,6 @@ bool Game_Interpreter::ExecuteCommand() { } bool Game_Interpreter::ExecuteCommand(lcf::rpg::EventCommand const& com) { - if (com.code != static_cast(Cmd::MoveEvent) && com.code != 3027 /* AddMoveRoute */) { - _move_route_chain_active = false; - } - switch (static_cast(com.code)) { case Cmd::ShowMessage: return CmdSetup<&Game_Interpreter::CommandShowMessage, 0>(com); @@ -3178,8 +3177,7 @@ bool Game_Interpreter::CommandMoveEvent(lcf::rpg::EventCommand const& com) { // int repeat = ManiacBitmask(com.parameters[2], 0x1); Game_Character* event = GetCharacter(event_id, "MoveEvent"); - if (event == nullptr) { - _move_route_chain_active = false; + if (!event) { return true; } @@ -3203,22 +3201,142 @@ bool Game_Interpreter::CommandMoveEvent(lcf::rpg::EventCommand const& com) { // route.move_commands.push_back(DecodeMove(it)); } - auto& frame = GetFrame(); - bool is_chain_start = (frame.current_command + 1 < static_cast(frame.commands.size()) && - frame.commands[frame.current_command + 1].code == 3027 /* AddMoveRoute */); + auto force_route = lcf::makeScopeGuard([&]() { + // Set the move route when the command ends + event->ForceMoveRoute(route, move_freq); + }); - if (is_chain_start) { - // This is the start of a chain. Buffer the data and don't execute yet. - _move_route_event_id = event->GetId(); - _move_route_chain_active = true; - _move_route_buffer = std::move(route); - _move_route_frequency = move_freq; + if (!Player::IsPatchManiac()) { + return true; } - else { - // This is a standalone command. Execute immediately. - event->ForceMoveRoute(route, move_freq); - _move_route_chain_active = false; + + // Check for Add Move Route commands + auto add_command = [&](const lcf::rpg::MoveCommand& cmd, int repeat = 1) { + for (int i = 0; i < repeat; ++i) { + route.move_commands.push_back(cmd); + } + }; + using Code = lcf::rpg::MoveCommand::Code; + + auto& frame = GetFrame(); + const auto& list = frame.commands; + auto& index = frame.current_command; + + ++index; + while (index < static_cast(list.size())) { + const auto& next_cmd = ResolveEventCommand(list[index]); + + if (static_cast(next_cmd.code) == Cmd::Maniac_AddMoveRoute) { + if (next_cmd.parameters.size() < 2) { + Output::Warning("AddMoveRoute: Command is missing action_type parameter."); + break; + } + + int action_type = next_cmd.parameters[1]; + lcf::rpg::MoveCommand cmd = {}; + + switch (action_type) { + case 0: { // .move(direction, distance) and specific moves like .moveUp(n) + cmd.command_id = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + int repeat = ValueOrVariableBitfield(next_cmd, 0, 2, 3); + if (next_cmd.parameters.size() <= 3) { + repeat = 1; + } + if (cmd.command_id >= static_cast(Code::move_up) && cmd.command_id <= static_cast(Code::move_forward)) { + add_command(cmd, repeat); + } + break; + } + case 1: { // .face(direction) and specific facings like .faceUp + int direction = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + cmd.command_id = direction + static_cast(Code::face_up); // Apply Offset + + if (cmd.command_id >= static_cast(Code::face_up) && cmd.command_id <= static_cast(Code::face_away_from_hero)) { + add_command(cmd); + } + break; + } + case 2: // .pause + cmd.command_id = static_cast(Code::wait); // Wait + add_command(cmd); + break; + case 3: { // .jump(x_offset, y_offset) + int x_offset = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + int y_offset = ValueOrVariableBitfield(next_cmd, 0, 2, 3); + + // TODO: Check this + cmd.command_id = static_cast(Code::begin_jump); + add_command(cmd); + cmd.command_id = (int32_t)((x_offset >= 0) ? lcf::rpg::MoveCommand::Code::move_right : lcf::rpg::MoveCommand::Code::move_left); + add_command(cmd, std::abs(x_offset)); + cmd.command_id = (int32_t)((y_offset >= 0) ? lcf::rpg::MoveCommand::Code::move_down : lcf::rpg::MoveCommand::Code::move_up); + add_command(cmd, std::abs(y_offset)); + cmd.command_id = static_cast(Code::end_jump); + add_command(cmd); + break; + } + case 4: { // Toggles like .fixDir/[on|off], .beginThrough/[on|off] + int toggle_type = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + bool is_on = ValueOrVariableBitfield(next_cmd, 0, 2, 3) != 0; + int base_cmd_id = 0; + switch (toggle_type) { + case 0: base_cmd_id = static_cast(Code::begin_jump); break; // Begin/End Jump + case 1: base_cmd_id = static_cast(Code::lock_facing); break; // Lock/Unlock Facing + case 2: base_cmd_id = static_cast(Code::walk_everywhere_on); break; // Through ON/OFF + case 3: base_cmd_id = static_cast(Code::stop_animation); break; // Stop/Start Animation + default: return true; + } + cmd.command_id = base_cmd_id + (is_on ? 0 : 1); + add_command(cmd); + break; + } + case 5: { // Direct setters like .speed(n), .freq(n), .trans(n) + int type = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + int value = ValueOrVariableBitfield(next_cmd, 0, 2, 3); + switch (type) { + case 0: cmd.command_id = 100; value += 4; break; // Custom: Set Speed + case 1: cmd.command_id = 101; break; // Custom: Set Freq + case 2: cmd.command_id = 102; break; // Custom: Set Transparency + default: return true; + } + cmd.parameter_a = value; + add_command(cmd); + break; + } + case 6: { // .switch(id, val) + cmd.parameter_a = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + cmd.command_id = ValueOrVariableBitfield(next_cmd, 0, 2, 3) != 0 ? static_cast(Code::switch_on) : static_cast(Code::switch_off); // Switch ON / OFF + add_command(cmd); + break; + } + case 7: { // .setBody("[name]", [index]) + cmd.command_id = static_cast(Code::change_graphic); + cmd.parameter_string = lcf::DBString(CommandStringOrVariableBitfield(next_cmd, 0, 4, 5)); + cmd.parameter_a = ValueOrVariableBitfield(next_cmd, 0, 1, 2); + add_command(cmd); + break; + } + case 8: { // .se("[name]", vol, pitch, balance) + cmd.command_id = static_cast(Code::play_sound_effect); + cmd.parameter_string = lcf::DBString(CommandStringOrVariableBitfield(next_cmd, 0, 5, 5)); + cmd.parameter_a = next_cmd.parameters.size() > 2 ? ValueOrVariableBitfield(next_cmd, 0, 1, 2) : 100; + cmd.parameter_b = next_cmd.parameters.size() > 3 ? ValueOrVariableBitfield(next_cmd, 0, 2, 3) : 100; + cmd.parameter_c = next_cmd.parameters.size() > 4 ? ValueOrVariableBitfield(next_cmd, 0, 3, 4) : 50; + add_command(cmd); + break; + } + default: + Output::Warning("AddMoveRoute: Unknown action type {}", action_type); + break; + } + + ++index; + } + else { + break; + } } + return true; } diff --git a/src/game_interpreter.h b/src/game_interpreter.h index dde9c457c9..8b6bca69c8 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -316,11 +316,12 @@ class Game_Interpreter : public Game_BaseInterpreterContext bool CommandManiacControlStrings(lcf::rpg::EventCommand const& com); bool CommandManiacWritePicture(lcf::rpg::EventCommand const& com); bool CommandManiacCallCommand(lcf::rpg::EventCommand const& com); + bool CommandManiacGetGameInfo(lcf::rpg::EventCommand const& com); + bool CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com); bool CommandEasyRpgProcessJson(lcf::rpg::EventCommand const& com); bool CommandEasyRpgCloneMapEvent(lcf::rpg::EventCommand const& com); bool CommandEasyRpgDestroyMapEvent(lcf::rpg::EventCommand const& com); - bool CommandManiacGetGameInfo(lcf::rpg::EventCommand const& com); void SetSubcommandIndex(int indent, int idx); uint8_t& ReserveSubcommandIndex(int indent); @@ -372,10 +373,6 @@ class Game_Interpreter : public Game_BaseInterpreterContext /** Shared instance for ResolveEventCommand */ lcf::rpg::EventCommand resolved_cmd; - int _move_route_event_id = 0; - bool _move_route_chain_active = false; - lcf::rpg::MoveRoute _move_route_buffer; - int _move_route_frequency = 0; private: void PushInternal( diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index f0dc556634..c7efa977fe 100644 --- a/src/game_interpreter_map.cpp +++ b/src/game_interpreter_map.cpp @@ -243,8 +243,6 @@ bool Game_Interpreter_Map::ExecuteCommand(lcf::rpg::EventCommand const& com) { return CmdSetup<&Game_Interpreter_Map::CommandOpenLoadMenu, 0>(com); case Cmd::ToggleAtbMode: return CmdSetup<&Game_Interpreter_Map::CommandToggleAtbMode, 0>(com); - case static_cast(3027): // CommandAddMoveRoute - return CmdSetup<&Game_Interpreter_Map::CommandAddMoveRoute, 0>(com); case Cmd::EasyRpg_TriggerEventAt: return CmdSetup<&Game_Interpreter_Map::CommandEasyRpgTriggerEventAt, 4>(com); case Cmd::EasyRpg_WaitForSingleMovement: @@ -876,175 +874,6 @@ bool Game_Interpreter_Map::CommandToggleAtbMode(lcf::rpg::EventCommand const& /* return true; } -bool Game_Interpreter_Map::CommandAddMoveRoute(lcf::rpg::EventCommand const& com) { // code 3027 - if (!Player::IsPatchManiac()) { - return true; - } - - if (!_move_route_chain_active) { - Output::Warning("AddMoveRoute: This command must be used after a MoveEvent command."); - return true; - } - - // Helper to safely decode parameters. It checks if the parameter exists before access. - auto get_p = [&](int mode_bit, int val_idx, int default_val = 0) { - if (com.parameters.size() <= static_cast(val_idx)) { - return default_val; - } - return ValueOrVariableBitfield(com, 0, mode_bit, val_idx); - }; - - // Helper to add a command to the buffer, optionally repeated. - auto add_command_to_buffer = [&](const lcf::rpg::MoveCommand& cmd, int repeat = 1) { - for (int i = 0; i < repeat; ++i) { - _move_route_buffer.move_commands.push_back(cmd); - } - }; - - if (com.parameters.size() < 2) { - Output::Warning("AddMoveRoute: Command is missing action_type parameter."); - _move_route_chain_active = false; // Break chain on error - return true; - } - - int action_type = com.parameters[1]; - lcf::rpg::MoveCommand cmd; - - switch (action_type) { - case 0: { // .move(direction, distance) and specific moves like .moveUp(n) - cmd.command_id = get_p(1, 2); - int distance = get_p(2, 3, 1); - if (cmd.command_id >= 0 && cmd.command_id <= 11) { - add_command_to_buffer(cmd, distance); - } - break; - } - case 1: { // .face(direction) and specific facings like .faceUp - int direction = get_p(1, 2); - if (direction >= 0 && direction <= 10) { - cmd.command_id = direction + 12; // Offset to LCF MoveCommand IDs 12-22 - add_command_to_buffer(cmd); - } - break; - } - case 2: { // .pause - cmd.command_id = 23; // Wait - add_command_to_buffer(cmd); - break; - } - case 3: { // .jump(x_offset, y_offset) - int x_offset = get_p(1, 2); - int y_offset = get_p(2, 3); - - cmd.command_id = 24; add_command_to_buffer(cmd); - cmd.command_id = (int32_t)((x_offset >= 0) ? lcf::rpg::MoveCommand::Code::move_right : lcf::rpg::MoveCommand::Code::move_left); - add_command_to_buffer(cmd, std::abs(x_offset)); - cmd.command_id = (int32_t)((y_offset >= 0) ? lcf::rpg::MoveCommand::Code::move_down : lcf::rpg::MoveCommand::Code::move_up); - add_command_to_buffer(cmd, std::abs(y_offset)); - cmd.command_id = 25; add_command_to_buffer(cmd); - break; - } - case 4: { // Toggles like .fixDir/[on|off], .beginThrough/[on|off] - int toggle_type = get_p(1, 2); - bool is_on = get_p(2, 3) != 0; - int base_cmd_id = 0; - switch (toggle_type) { - case 0: base_cmd_id = 24; break; // Begin/End Jump - case 1: base_cmd_id = 26; break; // Lock/Unlock Facing - case 2: base_cmd_id = 36; break; // Through ON/OFF - case 3: base_cmd_id = 38; break; // Stop/Start Animation - default: return true; - } - cmd.command_id = base_cmd_id + (is_on ? 0 : 1); - add_command_to_buffer(cmd); - break; - } - case 5: { // Direct setters like .speed(n), .freq(n), .trans(n) - int type = get_p(1, 2); - int value = get_p(2, 3); - switch (type) { - case 0: cmd.command_id = 100; value += 4; break; // Custom: Set Speed - case 1: cmd.command_id = 101; break; // Custom: Set Freq - case 2: cmd.command_id = 102; break; // Custom: Set Transparency - default: return true; - } - cmd.parameter_a = value; - add_command_to_buffer(cmd); - break; - } - case 6: { // .switch(id, val) - cmd.parameter_a = get_p(1, 2); - cmd.command_id = get_p(2, 3) != 0 ? 32 : 33; // Switch ON / OFF - add_command_to_buffer(cmd); - break; - } - case 7: { // .setBody("[name]", [index]) - cmd.command_id = 34; - int string_mode = (com.parameters[0] >> 16) & 0xF; // logical arg 4 is at bit 16 - if (string_mode == 0) { // literal - cmd.parameter_string = com.string; - } - else if (string_mode == 1) { // direct t[id] - cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(com.parameters[5])); - } - else if (string_mode == 2) { // indirect t[v[id]] - int var_id = Main_Data::game_variables->Get(com.parameters[5]); - cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(var_id)); - } - cmd.parameter_a = get_p(1, 2); - add_command_to_buffer(cmd); - break; - } - case 8: { // .se("[name]", vol, pitch, balance) - cmd.command_id = 35; - int string_mode = (com.parameters[0] >> 20) & 0xF; // logical arg 5 is at bit 20 - if (string_mode == 0) { // literal - cmd.parameter_string = com.string; - } - else if (string_mode == 1) { // direct t[id] - cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(com.parameters[5])); - } - else if (string_mode == 2) { // indirect t[v[id]] - int var_id = Main_Data::game_variables->Get(com.parameters[5]); - cmd.parameter_string = lcf::DBString(Main_Data::game_strings->Get(var_id)); - } - cmd.parameter_a = get_p(1, 2, 100); - cmd.parameter_b = get_p(2, 3, 100); - cmd.parameter_c = get_p(3, 4, 50); - add_command_to_buffer(cmd); - break; - } - default: - Output::Warning("AddMoveRoute: Unknown action type {}", action_type); - break; - } - - _move_route_buffer.repeat = (com.parameters[0] & (1 << 24)) != 0; - _move_route_buffer.skippable = (com.parameters[0] & (1 << 25)) != 0; - - auto& frame = GetFrame(); - bool is_chain_end = (frame.current_command + 1 >= static_cast(frame.commands.size()) || - frame.commands[frame.current_command + 1].code != 3027 /* AddMoveRoute */); - - if (is_chain_end) { - Game_Character* event = GetCharacter(_move_route_event_id, "AddMoveRoute (exec)"); - if (event != nullptr) { - if (_move_route_event_id >= Game_Character::CharBoat && _move_route_event_id <= Game_Character::CharAirship) { - if (static_cast(event)->IsInUse()) { - event = Main_Data::game_player.get(); - } - } - event->ForceMoveRoute(_move_route_buffer, _move_route_frequency); - } - else { - Output::Warning("AddMoveRoute: Could not find cached event with id {} to execute route.", _move_route_event_id); - } - _move_route_chain_active = false; - } - - return true; -} - bool Game_Interpreter_Map::CommandEasyRpgTriggerEventAt(lcf::rpg::EventCommand const& com) { if (!Player::HasEasyRpgExtensions()) { return true; diff --git a/src/game_interpreter_map.h b/src/game_interpreter_map.h index aaab6ffde3..7da1917571 100644 --- a/src/game_interpreter_map.h +++ b/src/game_interpreter_map.h @@ -83,18 +83,11 @@ class Game_Interpreter_Map : public Game_Interpreter bool CommandOpenMainMenu(lcf::rpg::EventCommand const& com); bool CommandOpenLoadMenu(lcf::rpg::EventCommand const& com); bool CommandToggleAtbMode(lcf::rpg::EventCommand const& com); - bool CommandAddMoveRoute(lcf::rpg::EventCommand const& com); bool CommandEasyRpgTriggerEventAt(lcf::rpg::EventCommand const& com); bool CommandEasyRpgPathfinder(lcf::rpg::EventCommand const& com); bool CommandEasyRpgWaitForSingleMovement(lcf::rpg::EventCommand const& com); AsyncOp ContinuationShowInnStart(int indent, int choice_result, int price); - bool CommandSmartMoveRoute( - lcf::rpg::EventCommand const& com, - int maxRouteStepsDefault, int maxSearchStepsDefault, - int abortIfAlreadyMovingDefault - ); // Internal generic path finder function. - static std::vector pending; };