diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 7d53b889af..5f9058ca6d 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -591,7 +591,7 @@ void Game_Interpreter::SkipToNextConditional(std::initializer_list codes, i } for (++index; index < static_cast(list.size()); ++index) { - const auto& com = list[index]; + const auto& com = ResolveEventCommand(list[index]); if (com.indent > indent) { continue; } @@ -877,17 +877,21 @@ std::vector Game_Interpreter::GetChoices(int max_num_choices) { auto& index = frame.current_command; // Let's find the choices - int current_indent = list[index + 1].indent; + if (index + 1 >= static_cast(list.size())) return {}; + + int current_indent = ResolveEventCommand(list[index + 1]).indent; + std::vector s_choices; for (int index_temp = index + 1; index_temp < static_cast(list.size()); ++index_temp) { - const auto& com = list[index_temp]; + const auto& com = ResolveEventCommand(list[index_temp]); + if (com.indent != current_indent) { continue; } if (static_cast(com.code) == Cmd::ShowChoiceOption && com.parameters.size() > 0 && com.parameters[0] < max_num_choices) { // Choice found - s_choices.push_back(ToString(list[index_temp].string)); + s_choices.push_back(ToString(com.string)); } if (static_cast(com.code) == Cmd::ShowChoiceEnd) { @@ -927,29 +931,38 @@ bool Game_Interpreter::CommandShowMessage(lcf::rpg::EventCommand const& com) { / ++index; // Check for continued lines via ShowMessage_2 - while (index < static_cast(list.size()) && static_cast(list[index].code) == Cmd::ShowMessage_2) { - // Add second (another) line - pm.PushLine(ToString(list[index].string)); - ++index; + while (index < static_cast(list.size())) { + const auto& next_cmd = ResolveEventCommand(list[index]); + + if (static_cast(next_cmd.code) == Cmd::ShowMessage_2) { + pm.PushLine(ToString(next_cmd.string)); + ++index; + } + else { + break; + } } // Handle Choices or number if (index < static_cast(list.size())) { // If next event command is show choices - if (static_cast(list[index].code) == Cmd::ShowChoice) { + const auto& next_cmd = ResolveEventCommand(list[index]); + + if (static_cast(next_cmd.code) == Cmd::ShowChoice) { std::vector s_choices = GetChoices(4); // If choices fit on screen if (static_cast(s_choices.size()) <= (4 - pm.NumLines())) { - pm.SetChoiceCancelType(list[index].parameters[0]); - SetupChoices(s_choices, com.indent, pm); + pm.SetChoiceCancelType(next_cmd.parameters[0]); + SetupChoices(s_choices, next_cmd.indent, pm); ++index; } - } else if (static_cast(list[index].code) == Cmd::InputNumber) { - // If next event command is input number - // If input number fits on screen + } + else if (static_cast(next_cmd.code) == Cmd::InputNumber) { + // If next event command is input number and + // input number fits on screen if (pm.NumLines() < 4) { - int digits = list[index].parameters[0]; - int variable_id = list[index].parameters[1]; + int digits = next_cmd.parameters[0]; + int variable_id = next_cmd.parameters[1]; pm.PushNumInput(variable_id, digits); ++index; } @@ -2076,7 +2089,7 @@ std::optional Game_Interpreter::HandleDynRpgScript(const lcf::rpg::EventCo // Concat everything that is not another command or a new comment block for (size_t i = index + 1; i < list.size(); ++i) { - const auto& cmd = list[i]; + const auto& cmd = ResolveEventCommand(list[i]); if (cmd.code == static_cast(Cmd::Comment_2) && !cmd.string.empty() && cmd.string[0] != '@') { command += ToString(cmd.string); @@ -3802,9 +3815,11 @@ bool Game_Interpreter::CommandJumpToLabel(lcf::rpg::EventCommand const& com) { / int label_id = com.parameters[0]; for (int idx = 0; (size_t)idx < list.size(); idx++) { - if (static_cast(list[idx].code) != Cmd::Label) + const auto& next_cmd = ResolveEventCommand(list[idx]); + + if (static_cast(next_cmd.code) != Cmd::Label) continue; - if (list[idx].parameters.empty() || list[idx].parameters[0] != label_id) + if (next_cmd.parameters.empty() || next_cmd.parameters[0] != label_id) continue; index = idx; break; @@ -3884,19 +3899,19 @@ bool Game_Interpreter::CommandBreakLoop(lcf::rpg::EventCommand const& /* com */) bool has_bug = !Player::IsPatchManiac(); if (!has_bug) { - SkipToNextConditional({ Cmd::EndLoop }, list[index].indent - 1); + SkipToNextConditional({ Cmd::EndLoop }, ResolveEventCommand(list[index]).indent - 1); ++index; return true; } // This emulates an RPG_RT bug where break loop ignores scopes and // unconditionally jumps to the next EndLoop command. - auto pcode = static_cast(list[index].code); + auto pcode = static_cast(ResolveEventCommand(list[index]).code); for (++index; index < (int)list.size(); ++index) { if (pcode == Cmd::EndLoop) { break; } - pcode = static_cast(list[index].code); + pcode = static_cast(ResolveEventCommand(list[index]).code); } return true; @@ -3962,11 +3977,13 @@ bool Game_Interpreter::CommandEndLoop(lcf::rpg::EventCommand const& com) { // co // Restart the loop for (int idx = index; idx >= 0; idx--) { - if (list[idx].indent > indent) + const auto& next_cmd = ResolveEventCommand(list[idx]); + + if (next_cmd.indent > indent) continue; - if (list[idx].indent < indent) + if (next_cmd.indent < indent) return false; - if (static_cast(list[idx].code) != Cmd::Loop) + if (static_cast(next_cmd.code) != Cmd::Loop) continue; index = idx; break; @@ -5461,9 +5478,9 @@ bool Game_Interpreter::CommandManiacWritePicture(lcf::rpg::EventCommand const& c return true; } -bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& com) { - if (!Player::IsPatchManiac()) { - return true; +const lcf::rpg::EventCommand& Game_Interpreter::ResolveEventCommand(const lcf::rpg::EventCommand& com) { + if (static_cast(com.code) != Cmd::Maniac_CallCommand || !Player::IsPatchManiac()) { + return com; } enum class ProcessingMode { @@ -5477,11 +5494,14 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co std::vector values; // Create command with basic parameters - lcf::rpg::EventCommand cmd; + lcf::rpg::EventCommand& cmd = resolved_cmd; cmd.code = ValueOrVariableBitfield(com.parameters[0], 0, com.parameters[1]); cmd.string = lcf::DBString(CommandStringOrVariableBitfield(com, 0, 3, 4)); + // Preserve the indentation level so loops and branches can find their pairs + cmd.indent = com.indent; + // Determine processing mode auto processing_mode = static_cast((com.parameters[0] >> 4) & 0b1111); @@ -5519,14 +5539,15 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co } default: Output::Warning("Call Command: Unsupported Processing Mode: {}", static_cast(processing_mode)); - return true; + return com; } // Finalize command parameters cmd.parameters = lcf::DBArray(values.begin(), values.end()); // Debug output - /*Output::Warning("Processing mode: {}", static_cast(processing_mode)); + /* + Output::Warning("Processing mode: {}", static_cast(processing_mode)); Output::Warning("Command code: {}", cmd.code); Output::Warning("Command string: {}", cmd.string); std::string params_str; @@ -5534,13 +5555,40 @@ bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& co params_str += " " + std::to_string(param); } Output::Warning("Command parameters:{}", params_str); - Output::Info("--------------------\n");*/ + Output::Info("--------------------\n"); + */ - // 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(), 0); + return cmd; +} - return true; +bool Game_Interpreter::CommandManiacCallCommand(lcf::rpg::EventCommand const& com) { + if (!Player::IsPatchManiac()) { + return true; + } + + const auto& cmd = ResolveEventCommand(com); + + switch (static_cast(cmd.code)) { + case Cmd::JumpToLabel: + case Cmd::Label: + case Cmd::Loop: + case Cmd::BreakLoop: + case Cmd::EndLoop: + case Cmd::EndEventProcessing: + case Cmd::EraseEvent: + // Everything that is flow control must (unfortunately) run inside + // current frame + return ExecuteCommand(cmd); + default: { + // In all other cases our implementation is incompatible to Maniacs + // and pushes a new frame containing the command instead of invoking + // it directly. Is safer to do. + auto new_cmd = cmd; + new_cmd.indent = 0; // reset the indent + Push({ new_cmd }, GetCurrentEventId(), 0); + return true; + } + } } bool Game_Interpreter::CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand const& com) { diff --git a/src/game_interpreter.h b/src/game_interpreter.h index 25f0e006f1..7ddf61ee4d 100644 --- a/src/game_interpreter.h +++ b/src/game_interpreter.h @@ -86,10 +86,19 @@ class Game_Interpreter : public Game_BaseInterpreterContext void InputButton(); void SetupChoices(const std::vector& choices, int indent, PendingMessage& pm); + /** + * Resolves a Maniac Patch @cmd (Call Command) into the actual EventCommand it represents. + * If the input is not a @cmd it returns the command itself. + * The resolved command is shared and overwritten on subsequent calls. + * + * @param com Command to process + * @return com itself or a parsed Call Command + */ + const lcf::rpg::EventCommand& ResolveEventCommand(const lcf::rpg::EventCommand& com); + bool ExecuteCommand(); virtual bool ExecuteCommand(lcf::rpg::EventCommand const& com); - /** * Returns the interpreters current state information. * For saving state into a save file, use GetSaveState instead. @@ -361,17 +370,20 @@ class Game_Interpreter : public Game_BaseInterpreterContext KeyInputState _keyinput; AsyncOp _async_op = {}; - private: - void PushInternal( - InterpreterPush push_info, - std::vector _list, - int _event_id, - int event_page_id = 0 - ); - - void PushInternal(Game_Event* ev, InterpreterExecutionType ex_type); - void PushInternal(Game_Event* ev, const lcf::rpg::EventPage* page, InterpreterExecutionType ex_type); - void PushInternal(Game_CommonEvent* ev, InterpreterExecutionType ex_type); + /** Shared instance for ResolveEventCommand */ + lcf::rpg::EventCommand resolved_cmd; + +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; };