diff --git a/src/game_character.cpp b/src/game_character.cpp index 0619addb88..cea62cce1c 100644 --- a/src/game_character.cpp +++ b/src/game_character.cpp @@ -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())); } diff --git a/src/game_character.h b/src/game_character.h index 152b275bb3..05f41c963d 100644 --- a/src/game_character.h +++ b/src/game_character.h @@ -563,6 +563,8 @@ class Game_Character { */ virtual bool Move(int dir); + virtual bool CheckMove(int dir); + /** * Jump to (x, y) * diff --git a/src/game_interpreter.cpp b/src/game_interpreter.cpp index 984054f982..b4531ca0f2 100644 --- a/src/game_interpreter.cpp +++ b/src/game_interpreter.cpp @@ -496,7 +496,7 @@ void Game_Interpreter::Update(bool reset_loop_count) { if (_state.wait_movement) { if ((main_flag && (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_blocked_movement_main) > 0) || (!main_flag && (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_blocked_movement_parallel) > 0)) { - Debug::AssertBlockedMoves(); + Debug::AssertBlockedMoves(main_flag); } if (Game_Map::IsAnyMovePending()) { break; @@ -5175,7 +5175,7 @@ void Game_Interpreter::AssertMoveRoutePushOnWait() const { if ((_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_skip_asserts_for_curr_command) > 0) { return; } - if (!main_flag || (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_moveroute_while_waiting) == 0) { + if (main_flag || (_state.easyrpg_debug_flags & lcf::rpg::SaveEventExecState::DebugFlags_warn_on_moveroute_while_waiting) == 0) { return; } auto& main_interpr = Game_Map::GetInterpreter(); diff --git a/src/game_interpreter_debug.cpp b/src/game_interpreter_debug.cpp index 5949c1a48c..0af11a06f1 100644 --- a/src/game_interpreter_debug.cpp +++ b/src/game_interpreter_debug.cpp @@ -139,12 +139,12 @@ std::string Debug::FormatEventName(lcf::rpg::SaveEventExecFrame const* frame) { return "Event"; } -void Debug::AssertBlockedMoves() { +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 = [](Game_Character& ev) { + auto assert_way = [&main_flag](Game_Character& ev) { using Code = lcf::rpg::MoveCommand::Code; auto& move_command = ev.GetMoveRoute().move_commands[ev.GetMoveRouteIndex()]; @@ -158,15 +158,15 @@ void Debug::AssertBlockedMoves() { 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); + 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); + 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); + 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); + 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); + Game_Map::AssertWay(ev, from_x, from_y, to_x, to_y, main_flag); } } }; diff --git a/src/game_interpreter_debug.h b/src/game_interpreter_debug.h index 810ea79f33..03278e8e7c 100644 --- a/src/game_interpreter_debug.h +++ b/src/game_interpreter_debug.h @@ -95,7 +95,7 @@ namespace Debug { std::string FormatEventName(Game_CommonEvent const& ev); std::string FormatEventName(lcf::rpg::SaveEventExecFrame const* frame); - void AssertBlockedMoves(); + void AssertBlockedMoves(bool main_flag); } #ifdef INTERPRETER_DEBUGGING diff --git a/src/game_map.cpp b/src/game_map.cpp index 9937821e4c..9498d7868d 100644 --- a/src/game_map.cpp +++ b/src/game_map.cpp @@ -518,6 +518,26 @@ static int GetPassableMask(int old_x, int old_y, int new_x, int new_y) { return bit; } +static int GetEventId(Game_Character const& ch) { + switch (ch.GetType()) { + case Game_Character::Event: + return static_cast(ch).GetId(); + case Game_Character::Player: + return Game_Character::CharPlayer; + case Game_Character::Vehicle: + switch (static_cast(ch).GetVehicleType()) { + case Game_Vehicle::Boat: + return Game_Character::CharBoat; + case Game_Vehicle::Ship: + return Game_Character::CharShip; + case Game_Vehicle::Airship: + return Game_Character::CharAirship; + } + } + assert(false); + return 0; +} + template static bool WouldCollide(const Game_Character& self, const T& other, bool self_conflict) { if (self.GetThrough() || other.GetThrough()) { @@ -535,7 +555,7 @@ static bool WouldCollide(const Game_Character& self, const T& other, bool self_c if (self.GetType() == Game_Character::Event && other.GetType() == Game_Character::Event && (self.IsOverlapForbidden() || other.IsOverlapForbidden())) { - if constexpr (impl == Game_Map::eImpl_AssertWay) { + if constexpr (impl == Game_Map::eImpl_AssertWayForeground || impl == Game_Map::eImpl_AssertWayBackground) { if (self.IsOverlapForbidden()) { Output::Warning("MoveRoute: {} is not allowed to overlap with other events!", Debug::FormatEventName(self)); } else { @@ -546,15 +566,118 @@ static bool WouldCollide(const Game_Character& self, const T& other, bool self_c } if (other.GetLayer() == lcf::rpg::EventPage::Layers_same && self_conflict) { - if constexpr (impl == Game_Map::eImpl_AssertWay) { + if constexpr (impl == Game_Map::eImpl_AssertWayForeground || impl == Game_Map::eImpl_AssertWayBackground) { Output::Warning("MoveRoute: {} can't move (self collision)!", Debug::FormatEventName(self)); } return true; } if (self.GetLayer() == other.GetLayer()) { - if constexpr (impl == Game_Map::eImpl_AssertWay) { - //TODO: check if 'other' could still move away due to some routing behavior... + if constexpr (impl == Game_Map::eImpl_AssertWayBackground) { + // check if 'self' is blocked by player + // if the blocked movement doesn't occur in foreground + // context, then they could just walk away + + if (other.GetType() == Game_Character::Player) { + return false; + } + //TODO: should maybe check if offscreen + if (other.GetType() == Game_Character::Vehicle) { + return false; + } + } + if constexpr (impl == Game_Map::eImpl_AssertWayForeground || impl == Game_Map::eImpl_AssertWayBackground) { + constexpr auto rng_moves = { + lcf::rpg::EventPage::MoveType_random, + lcf::rpg::EventPage::MoveType_toward, + lcf::rpg::EventPage::MoveType_away + }; + + // check if 'other' could still move away due to some routing behavior... + bool do_check_next_move_command = false; + auto check_rng_move = [&](Game_Character const& ch, std::unordered_set* ignore_list) { + return Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX(), ch.GetY() + ch.GetDyFromDirection(Game_Character::Up), true, ignore_list) + || Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX(), ch.GetY() + ch.GetDyFromDirection(Game_Character::Down), true, ignore_list) + || Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX() + ch.GetDxFromDirection(Game_Character::Left), ch.GetY(), true, ignore_list) + || Game_Map::CheckWay(ch, ch.GetX(), ch.GetY(), ch.GetX() + ch.GetDxFromDirection(Game_Character::Right), ch.GetY(), true, ignore_list); + }; + std::unordered_set ignore_list; + ignore_list.emplace(GetEventId(self)); + + if (other.IsMoveRouteOverwritten() && !other.IsMoveRouteFinished()) { + do_check_next_move_command = true; + } else if (other.GetType() == Game_Character::Event) { + if constexpr (impl == Game_Map::eImpl_AssertWayForeground) { + auto* page = reinterpret_cast(other).GetActivePage(); + auto move_type = page ? page->move_type : 0; + + //TODO: only when !main_flag + if (move_type == lcf::rpg::EventPage::MoveType_vertical) { + //check if other event culd still move up/down... + if (Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX(), other.GetY() + other.GetDyFromDirection(Game_Character::Up), true, &ignore_list) + || Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX(), other.GetY() + other.GetDyFromDirection(Game_Character::Down), true, &ignore_list)) { + return false; + } + } else if (move_type == lcf::rpg::EventPage::MoveType_horizontal) { + //check if other event culd still move left/right... + if (Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX() + other.GetDxFromDirection(Game_Character::Left), other.GetY(), true, &ignore_list) + || Game_Map::CheckWay(other, other.GetX(), other.GetY(), other.GetX() + other.GetDxFromDirection(Game_Character::Right), other.GetY(), true, &ignore_list)) { + return false; + } + } else if (std::any_of(rng_moves.begin(), rng_moves.end(), [&move_type](auto mt) { return move_type == mt; })) { + //check if other event culd still move in any direction... + if (check_rng_move(other, &ignore_list)) { + return false; + } + } else if (move_type == lcf::rpg::EventPage::MoveType_custom) { + //check if other events custom route would make it possible for this event to move... + do_check_next_move_command = true; + } + } + } + + if (do_check_next_move_command) { + auto& move_command = other.GetMoveRoute().move_commands[other.GetMoveRouteIndex()]; + bool move_success = false; + + using Code = lcf::rpg::MoveCommand::Code; + switch (static_cast(move_command.command_id)) { + case Code::move_up: + case Code::move_right: + case Code::move_down: + case Code::move_left: + case Code::move_upright: + case Code::move_downright: + case Code::move_downleft: + case Code::move_upleft: + move_success = other.CheckMove(static_cast(move_command.command_id)); + break; + case Code::move_forward: + move_success = other.CheckMove(other.GetDirection()); + break; + case Code::move_random: + move_success = check_rng_move(other, &ignore_list); + break; + case Code::move_towards_hero: + if constexpr (impl == Game_Map::eImpl_AssertWayForeground) { + move_success = check_rng_move(other, &ignore_list); + } else { + move_success = other.CheckMove(other.GetDirectionToHero()); + } + break; + case Code::move_away_from_hero: + if constexpr (impl == Game_Map::eImpl_AssertWayForeground) { + move_success = check_rng_move(other, &ignore_list); + } else { + move_success = other.CheckMove(other.GetDirectionAwayHero()); + } + break; + } + if (move_success) { + return false; + } + } + Output::Warning("MoveRoute: {} would overlap with {}!", Debug::FormatEventName(self), Debug::FormatEventName(other)); } return true; @@ -641,10 +764,15 @@ bool Game_Map::CheckWay(const Game_Character& self, bool Game_Map::AssertWay(const Game_Character& self, int from_x, int from_y, - int to_x, int to_y + int to_x, int to_y, bool main_flag ) { - return CheckOrMakeWayEx( + if (main_flag) { + return CheckOrMakeWayEx( + self, from_x, from_y, to_x, to_y, nullptr + ); + } + return CheckOrMakeWayEx( self, from_x, from_y, to_x, to_y, nullptr ); } @@ -666,7 +794,7 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, // Note, even for diagonal, if the tile is invalid we still check vertical/horizontal first! if (!Game_Map::IsValid(to_x, to_y)) { - if constexpr (impl == eImpl_AssertWay) { + if constexpr (impl == Game_Map::eImpl_AssertWayForeground || impl == Game_Map::eImpl_AssertWayBackground) { Output::Warning("MoveRoute: {} can't move out-of-bounds (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); } return false; @@ -713,7 +841,7 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, from_x = Game_Map::RoundX(from_x); from_y = Game_Map::RoundY(from_y); if (!IsPassableTile(&self, bit_from, from_x, from_y)) { - if constexpr (impl == eImpl_AssertWay) { + if constexpr (impl == Game_Map::eImpl_AssertWayForeground || impl == Game_Map::eImpl_AssertWayBackground) { Output::Warning("MoveRoute: {} can't step of current tile (x:{}, y:{})!", Debug::FormatEventName(self), from_x, from_y); } return false; @@ -760,7 +888,7 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, bool result = IsPassableTile( &self, bit, to_x, to_y ); - if constexpr (impl == eImpl_AssertWay) { + if constexpr (impl == Game_Map::eImpl_AssertWayForeground || impl == Game_Map::eImpl_AssertWayBackground) { if (!result) { Output::Warning("MoveRoute: {} can't pass target tile (x:{}, y:{})!", Debug::FormatEventName(self), to_x, to_y); } @@ -771,10 +899,12 @@ bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, // explicit template declarations template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); -template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); +template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); +template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); -template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); +template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); +template bool Game_Map::CheckOrMakeWayEx(const Game_Character& self, int, int, int, int, std::unordered_set*); bool Game_Map::MakeWay(const Game_Character& self, int from_x, int from_y, diff --git a/src/game_map.h b/src/game_map.h index cb293087c1..eb2cc29b38 100644 --- a/src/game_map.h +++ b/src/game_map.h @@ -230,7 +230,7 @@ namespace Game_Map { bool AssertWay(const Game_Character& self, int from_x, int from_y, - int to_x, int to_y); + int to_x, int to_y, bool main_flag); enum CheckOrMakeWayImpl { eImpl_CheckWay, @@ -238,7 +238,8 @@ namespace Game_Map { eImpl_MakeWay, /* 'CheckOrMakeWayEx' will generate warnings for blocked movement. */ - eImpl_AssertWay + eImpl_AssertWayForeground, + eImpl_AssertWayBackground }; /**