Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
7199e96
Cherry Pick commit #ec84539 from experimental branch "florianessl:Int…
florianessl Jun 5, 2024
9e36d1d
Cleanup/Fix warning: Remove unused function "CreateEmptyLines" from w…
florianessl Jan 20, 2025
755ecfc
Refactor: Allow outside unsafe access to internal interpreter state o…
florianessl Feb 5, 2025
3180a52
Refactored Game_Intepreter::Push & implemented Maniac´s special field…
florianessl Feb 5, 2025
7e4b420
Implemented missing Maniac subcommand "GetGameInfo" -> "Get Command I…
florianessl Feb 6, 2025
d3cb130
Refactored "Interpreter.Push" some more, so that we could potentially…
florianessl Feb 6, 2025
fd6d1a9
Fix: Interpreter debug window was broken after refactor, Rewrite & si…
florianessl Feb 6, 2025
1280ace
Interpreter debug window: Extended view to display the initial event …
florianessl Feb 6, 2025
16c6902
Minor: Defined "ExecutionType::ManiacHook" & clarified Push instances…
florianessl Feb 7, 2025
bdc21d9
Amended missing tag "ManiacHook" to Debug::kExecutionType & added som…
florianessl Feb 11, 2025
b299dc0
Debug scene: Fix a crash when foreground interpreter stack is empty
florianessl May 8, 2025
1d2c644
Fix warnings
florianessl May 9, 2025
b67f507
Refactor: Removed "CheckOrMakeWayEx" from header file & renamed it to…
florianessl Jun 2, 2025
799df1d
First "AssertBlockedMoves" draft
florianessl Jun 5, 2024
c8add1b
- Fix: Split AssertWay checks into separate variants for Foreground &…
florianessl Jun 7, 2024
9cd4607
Added new game config for enabling MoveRoute debugging
florianessl Jan 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -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 \
Expand Down
6 changes: 4 additions & 2 deletions src/filefinder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -62,7 +62,8 @@ namespace FileFinder {
WolfRpgEditor,
Encrypted2k3Maniacs,
RpgMaker95,
SimRpgMaker95
SimRpgMaker95,
LAST
};

constexpr auto kProjectType = lcf::makeEnumTags<ProjectType>(
Expand All @@ -77,6 +78,7 @@ namespace FileFinder {
"RPG Maker 95",
"Sim RPG Maker 95"
);
static_assert(kProjectType.size() == static_cast<size_t>(ProjectType::LAST));

/**
* Helper struct combining the project's directory and its type (used by Game Browser)
Expand Down
21 changes: 21 additions & 0 deletions src/game_character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
}
Expand Down
2 changes: 2 additions & 0 deletions src/game_character.h
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,8 @@ class Game_Character {
*/
virtual bool Move(int dir);

virtual bool CheckMove(int dir);

/**
* Jump to (x, y)
*
Expand Down
2 changes: 1 addition & 1 deletion src/game_commonevent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<InterpreterExecutionType::Parallel>(this);
}


Expand Down
3 changes: 2 additions & 1 deletion src/game_commonevent.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
// Headers
#include <string>
#include <vector>
#include "game_interpreter_debug.h"
#include "game_interpreter_map.h"
#include <lcf/rpg/commonevent.h>
#include <lcf/rpg/saveeventexecstate.h>
Expand Down Expand Up @@ -120,7 +121,7 @@ class Game_CommonEvent {
/** Interpreter for parallel common events. */
std::unique_ptr<Game_Interpreter_Map> interpreter;

friend class Scene_Debug;
friend class Game_Interpreter_Inspector;
};

#endif
6 changes: 6 additions & 0 deletions src/game_config_game.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
Expand Down Expand Up @@ -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() {
Expand Down
1 change: 1 addition & 0 deletions src/game_config_game.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ struct Game_ConfigGame {
BoolConfigParam patch_rpg2k3_commands{ "RPG2k3 Event Commands", "Enable support for RPG2k3 event commands", "Patch", "RPG2k3Commands", false };
ConfigParam<int> patch_anti_lag_switch{ "Anti-Lag Switch", "Disable event page refreshes when switch is set", "Patch", "AntiLagSwitch", 0 };
ConfigParam<int> 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 };
Expand Down
2 changes: 1 addition & 1 deletion src/game_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<InterpreterExecutionType::Parallel>(this);
}
interpreter->Update(!resume_async);

Expand Down
3 changes: 2 additions & 1 deletion src/game_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include "game_character.h"
#include <lcf/rpg/event.h>
#include <lcf/rpg/savemapevent.h>
#include "game_interpreter_debug.h"
#include "game_interpreter_map.h"
#include "async_op.h"

Expand Down Expand Up @@ -218,7 +219,7 @@ class Game_Event : public Game_EventBase {
const lcf::rpg::EventPage* page = nullptr;
std::unique_ptr<Game_Interpreter_Map> interpreter;

friend class Scene_Debug;
friend class Game_Interpreter_Inspector;
};

inline int Game_Event::GetNumPages() const {
Expand Down
139 changes: 121 additions & 18 deletions src/game_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ bool Game_Interpreter::IsRunning() const {
}

// Setup.
void Game_Interpreter::Push(
void Game_Interpreter::PushInternal(
InterpreterPush push_info,
std::vector<lcf::rpg::EventCommand> _list,
int event_id,
bool started_by_decision_key,
int event_page_id
) {
if (_list.empty()) {
Expand All @@ -114,15 +114,28 @@ void Game_Interpreter::Push(
Output::Error("Call Event limit ({}) has been exceeded", call_stack_limit);
}

auto type_ex = std::get<ExecutionType>(push_info);
auto type_src = std::get<EventType>(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<int>(type_ex);
}

if (type_src <= EventType::BattleEvent) {
frame.maniac_event_info |= (static_cast<int>(type_src) << 4);
}

if (_state.stack.empty() && main_flag && !Game_Battle::IsBattleRunning()) {
Main_Data::game_system->ClearMessageFace();
Main_Data::game_player->SetMenuCalling(false);
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -2025,13 +2047,13 @@ std::optional<bool> 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 {};
}
}

Expand All @@ -2054,7 +2076,6 @@ std::optional<bool> Game_Interpreter::HandleDynRpgScript(const lcf::rpg::EventCo

return Main_Data::game_dynrpg->Invoke(command, this);
}

return {};
}

Expand Down Expand Up @@ -3984,7 +4005,7 @@ bool Game_Interpreter::CommandCallEvent(lcf::rpg::EventCommand const& com) { //
return true;
}

Push(common_event);
Push<ExecutionType::Call>(common_event);

return true;
}
Expand Down Expand Up @@ -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<ExecutionType::Call, EventType::MapEvent>(page->event_commands, event->GetId(), page->ID);

return true;
}
Expand Down Expand Up @@ -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<int>(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<int>(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());
Expand Down Expand Up @@ -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<ExecutionType::Eval, EventType::None>({ cmd }, GetCurrentEventId(), 0);

return true;
}
Expand Down Expand Up @@ -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();
}
Loading
Loading