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..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" @@ -3174,29 +3177,166 @@ 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) { + 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 force_route = lcf::makeScopeGuard([&]() { + // Set the move route when the command ends event->ForceMoveRoute(route, move_freq); + }); + + if (!Player::IsPatchManiac()) { + return true; + } + + // 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 7ddf61ee4d..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); diff --git a/src/game_interpreter_map.cpp b/src/game_interpreter_map.cpp index d22c68003e..c7efa977fe 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" diff --git a/src/game_interpreter_map.h b/src/game_interpreter_map.h index 9faf600a36..7da1917571 100644 --- a/src/game_interpreter_map.h +++ b/src/game_interpreter_map.h @@ -88,12 +88,6 @@ class Game_Interpreter_Map : public Game_Interpreter bool CommandEasyRpgWaitForSingleMovement(lcf::rpg::EventCommand const& com); AsyncOp ContinuationShowInnStart(int indent, int choice_result, int price); - bool CommandSmartMoveRoute( - lcf::rpg::EventCommand const& com, - int maxRouteStepsDefault, int maxSearchStepsDefault, - int abortIfAlreadyMovingDefault - ); // Internal generic path finder function. - static std::vector pending; }; diff --git a/src/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);