Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New debug option: Generate interpreter warnings for blocked movement #3336

Draft
wants to merge 7 commits into
base: master
Choose a base branch
from
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,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 @@ -160,6 +160,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
21 changes: 21 additions & 0 deletions src/game_character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -515,6 +515,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: 2 additions & 0 deletions 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 @@ -121,6 +122,7 @@ class Game_CommonEvent {
std::unique_ptr<Game_Interpreter_Map> interpreter;

friend class Scene_Debug;
friend class Debug::ParallelInterpreterStates;
};

#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: 2 additions & 0 deletions 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 @@ -219,6 +220,7 @@ class Game_Event : public Game_EventBase {
std::unique_ptr<Game_Interpreter_Map> interpreter;

friend class Scene_Debug;
friend class Debug::ParallelInterpreterStates;
};

inline int Game_Event::GetNumPages() const {
Expand Down
5 changes: 5 additions & 0 deletions src/game_interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,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 @@ -5341,6 +5344,8 @@ bool Game_Interpreter::CommandEasyRpgSetInterpreterFlag(lcf::rpg::EventCommand c
Player::game_config.patch_key_patch.Set(flag_value);
if (flag_name == "rpg2k3-cmds" || flag_name == "rpg2k3-commands")
Player::game_config.patch_rpg2k3_commands.Set(flag_value);
if (flag_name == "debug-routes" || flag_name == "debug-move-routes")
Player::game_config.debug_moveroutes.Set(flag_value);
if (flag_name == "rpg2k-battle")
lcf::Data::system.easyrpg_use_rpg2k_battle_system = flag_value;

Expand Down
188 changes: 188 additions & 0 deletions src/game_interpreter_debug.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/

#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 <lcf/reader_util.h>

Debug::ParallelInterpreterStates Debug::ParallelInterpreterStates::GetCachedStates() {
std::vector<int> ev_ids;
std::vector<int> ce_ids;

std::vector<lcf::rpg::SaveEventExecState> state_ev;
std::vector<lcf::rpg::SaveEventExecState> state_ce;

if (Game_Map::GetMapId() > 0) {
for (auto& ev : Game_Map::GetEvents()) {
if (ev.GetTrigger() != lcf::rpg::EventPage::Trigger_parallel || !ev.interpreter)
continue;
ev_ids.emplace_back(ev.GetId());
state_ev.emplace_back(ev.interpreter->GetState());
}
for (auto& ce : Game_Map::GetCommonEvents()) {
if (ce.IsWaitingBackgroundExecution(false)) {
ce_ids.emplace_back(ce.common_event_id);
state_ce.emplace_back(ce.interpreter->GetState());
}
}
} 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::CallStackItem> Debug::CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state) {
std::vector<CallStackItem> items;

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 = 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]);
}
}
}

auto item = Debug::CallStackItem();
item.stack_item_no = i + 1;
item.is_ce = is_calling_ev_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());
}
}

items.push_back(item);
}

return items;
}

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<Game_Vehicle const&>(ch).GetVehicleType();
assert(type > Game_Vehicle::None && type <= Game_Vehicle::Airship);
return Game_Vehicle::TypeNames[type];
}
case Game_Character::Event:
{
auto& ev = static_cast<Game_Event const&>(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 "";
}

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<int>(Code::move_up)
&& move_command.command_id <= static_cast<int>(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);
}
}
}
73 changes: 73 additions & 0 deletions src/game_interpreter_debug.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/


#ifndef EP_GAME_INTERPRETER_DEBUG
#define EP_GAME_INTERPRETER_DEBUG

#include "game_interpreter_shared.h"
#include "game_character.h"
#include <lcf/rpg/saveeventexecstate.h>
#include "player.h"

class Game_CommonEvent;

namespace Debug {
class ParallelInterpreterStates {
private:
std::vector<int> ev_ids;
std::vector<int> ce_ids;

std::vector<lcf::rpg::SaveEventExecState> state_ev;
std::vector<lcf::rpg::SaveEventExecState> state_ce;

ParallelInterpreterStates(std::vector<int> ev_ids, std::vector<int> ce_ids,
std::vector<lcf::rpg::SaveEventExecState> state_ev, std::vector<lcf::rpg::SaveEventExecState> 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() { return ev_ids.size() + ce_ids.size(); }

inline std::tuple<const int&, const lcf::rpg::SaveEventExecState&> GetEventInterpreter(int i) const {
return std::tie(ev_ids[i], state_ev[i]);
}
inline std::tuple<const int&, const lcf::rpg::SaveEventExecState&> GetCommonEventInterpreter(int i) const {
return std::tie(ce_ids[i], state_ce[i]);
}

static ParallelInterpreterStates GetCachedStates();
};

struct CallStackItem {
bool is_ce;
int evt_id, page_id;
std::string name;
int stack_item_no, cmd_current, cmd_count;
};

std::vector<CallStackItem> CreateCallStack(const int owner_evt_id, const lcf::rpg::SaveEventExecState& state);

std::string FormatEventName(Game_Character const& ev);

void AssertBlockedMoves(bool main_flag);
}

#endif
Loading
Loading