Skip to content

Commit

Permalink
Refactor MakeWay
Browse files Browse the repository at this point in the history
* Refactor makeway logic to follow Cherry's description
  and trigger event updates at the right time
* Correct ordering of actions for diagonal movement
* Jump uses same MakeWay function to simplify logic and
  fix EasyRPG#1612
* Event layer=below tile graphic behavior
    * Highest event ID wins
    * If winning event tile is not above
         * Events and player - overrides chipset
         * Boat or Ship - always blocks
* Refactor IsPassableTile()
    * Better interface taking x,y
    * Support vehicles in one piece of code.
    * Handle tile event logic
* Vehicle cases
    * If player is boarded, calls makeway as vehicle (uses vehicle's
      through flag)
    * If player is boarded, other events collide against vehicle, not
      player
  • Loading branch information
fmatthew5876 committed Apr 6, 2019
1 parent bf9852a commit 4d57305
Show file tree
Hide file tree
Showing 8 changed files with 238 additions and 102 deletions.
24 changes: 4 additions & 20 deletions src/game_character.cpp
Expand Up @@ -53,12 +53,8 @@ bool Game_Character::IsStopping() const {
return !(IsMoving() || IsJumping());
}

bool Game_Character::MakeWay(int x, int y, int d) const {
if (d > 3) {
return MakeWayDiagonal(x, y, d);
}

return Game_Map::MakeWay(x, y, d, *this);
bool Game_Character::MakeWay(int x, int y) const {
return Game_Map::MakeWay(*this, x, y);
}

bool Game_Character::IsLandable(int x, int y) const
Expand Down Expand Up @@ -456,7 +452,7 @@ void Game_Character::Move(int dir, MoveOption option) {
return;
}

move_failed = !MakeWay(GetX(), GetY(), dir);
move_failed = !MakeWay(GetX() + dx, GetY() + dy);

if (!move_failed || option == MoveOption::Normal) {
SetDirection(dir);
Expand Down Expand Up @@ -677,11 +673,7 @@ void Game_Character::BeginJump(int32_t& current_index, const RPG::MoveRoute& cur
}
}

if (
// A character can always land on a tile they were already standing on
!(jump_plus_x == 0 && jump_plus_y == 0) &&
!IsLandable(new_x, new_y)
) {
if (!MakeWay(new_x, new_y)) {
move_failed = true;
}

Expand Down Expand Up @@ -925,14 +917,6 @@ int Game_Character::ReverseDir(int dir) {
return reversed[dir];
}


bool Game_Character::MakeWayDiagonal(int x, int y, int d) const {
int dx = (d == UpRight || d == DownRight) - (d == DownLeft || d == UpLeft);
int dy = (d == DownRight || d == DownLeft) - (d == UpRight || d == UpLeft);
return ((MakeWay(x, y, dy + 1) && MakeWay(x, y + dy, -dx + 2)) ||
(MakeWay(x, y, -dx + 2) && MakeWay(x + dx, y, dy + 1)));
}

void Game_Character::SetMaxStopCountForStep() {
const auto freq = GetMoveFrequency();
SetMaxStopCount(freq >= 8 ? 0 : 1 << (9 - freq));
Expand Down
17 changes: 11 additions & 6 deletions src/game_character.h
Expand Up @@ -257,6 +257,9 @@ class Game_Character {
*/
const std::string& GetSpriteName() const;

/** @return true if this has a tile sprite */
bool HasTileSprite() const;

/**
* Sets sprite name. Usually the name of the graphic file.
*
Expand Down Expand Up @@ -509,16 +512,16 @@ class Game_Character {
*/
virtual bool IsStopping() const;


/**
* Makes way for the character to move from (x,y) in the direction d. Returns
* Makes way for the character to move to (x,y). Returns
* true if the move can be completed.
*
* @param x tile x.
* @param y tile y.
* @param d character direction.
* @param x new x position.
* @param y new y position.
* @return whether the character can walk through.
*/
virtual bool MakeWay(int x, int y, int d) const;
virtual bool MakeWay(int x, int y) const;

/**
* Gets if the character can jump to a tile.
Expand Down Expand Up @@ -851,7 +854,6 @@ class Game_Character {

protected:
explicit Game_Character(Type type, RPG::SaveMapEventBase* d);
bool MakeWayDiagonal(int x, int y, int d) const;
virtual void UpdateSelfMovement() {}
void UpdateJump();
void SetMaxStopCountForStep();
Expand Down Expand Up @@ -1188,5 +1190,8 @@ inline bool Game_Character::IsActive() const {
return data()->active;
}

inline bool Game_Character::HasTileSprite() const {
return GetSpriteName().empty();
}

#endif
237 changes: 198 additions & 39 deletions src/game_map.cpp
Expand Up @@ -572,56 +572,153 @@ static CollisionResult TestCollisionDuringMove(
return NoCollision;
}

bool Game_Map::MakeWay(int x, int y, int d, const Game_Character& self) {
int new_x = RoundX(x + (d == Game_Character::Right ? 1 : d == Game_Character::Left ? -1 : 0));
int new_y = RoundY(y + (d == Game_Character::Down ? 1 : d == Game_Character::Up ? -1 : 0));
static int GetPassableMask(int old_x, int old_y, int new_x, int new_y) {
int bit = 0;
if (new_x > old_x) { bit |= Passable::Right; }
if (new_x < old_x) { bit |= Passable::Left; }
if (new_y > old_y) { bit |= Passable::Down; }
if (new_y < old_y) { bit |= Passable::Up; }
return bit;
}

static bool WouldCollide(const Game_Character& self, const Game_Character& other, bool self_conflict) {
if (self.GetThrough() || other.GetThrough()) {
return false;
}

if (self.IsFlying() || other.IsFlying()) {
return false;
}

if (!self.IsActive() || !other.IsActive()) {
return false;
}

if (self.GetType() == Game_Character::Event
&& other.GetType() == Game_Character::Event
&& (self.IsOverlapForbidden() || other.IsOverlapForbidden())) {
return true;
}

if (other.GetLayer() == RPG::EventPage::Layers_same && self_conflict) {
return true;
}

if (self.GetLayer() == other.GetLayer()) {
return true;
}

return false;
}

template <typename T>
static bool MakeWayCollideEvent(int x, int y, const Game_Character& self, T& other, bool self_conflict) {
if (&self == &other) {
return false;
}

if (!other.IsInPosition(x, y)) {
return false;
}

if (!Game_Map::IsValid(new_x, new_y))
// Force the other event to update, allowing them to possibly move out of the way.
other.Update();

if (!other.IsInPosition(x, y)) {
return false;
}

return WouldCollide(self, other, self_conflict);
}

if (self.GetThrough()) return true;

// A character can move to a position with an impassable tile by
// standing on top of an event below it. These flags track whether
// we stepped off an event and therefore don't need to check the
// passability of the tile layer below.
bool stepped_off_event = false;
bool stepped_onto_event = false;

for (Game_Event& other : GetEvents()) {
CollisionResult result = TestCollisionDuringMove(x, y, new_x, new_y, d, self, other);
if (result == Collision) {
// Try updating the offending event to give it a chance to move out of the
// way and recheck.
other.Update();
if (TestCollisionDuringMove(x, y, new_x, new_y, d, self, other) == Collision) {
bool Game_Map::MakeWay(const Game_Character& self, int x, int y) {
// Moving to same tile (used for jumps) always succeeds
if (x == self.GetX() && y == self.GetY()) {
return true;
}
if (!self.IsJumping() && x != self.GetX() && y != self.GetY()) {
// Handle diagonal stepping.
// Must be able to step on at least one of the 2 adjacent tiles and also the target tile.
// Verified behavior: Always checks vertical first, only checks horizontal if vertical fails.
bool vertical_ok = MakeWay(self, self.GetX(), y);
if (!vertical_ok) {
bool horizontal_ok = MakeWay(self, x, self.GetY());
if (!horizontal_ok) {
return false;
}
}
else if (result == CanStepOffCurrentTile) {
stepped_off_event = true;
} else if (result == CanStepOntoNewTile) {
stepped_onto_event = true;
}
}

if (vehicles[0]->IsInPosition(new_x, new_y) || vehicles[1]->IsInPosition(new_x, new_y)) {
// Note, even for diagonal, if the tile is invalid we still check vertical/horizontal first!
if (!Game_Map::IsValid(x, y)) {
return false;
}

if (Main_Data::game_player->IsInPosition(new_x, new_y)
&& !Main_Data::game_player->GetThrough()
&& self.GetLayer() == RPG::EventPage::Layers_same) {
// Update the Player to see if they'll move and recheck.
Main_Data::game_player->Update();
if (Main_Data::game_player->IsInPosition(new_x, new_y)) {
return false;
if (self.GetThrough()) {
return true;
}

const auto vehicle_type = static_cast<Game_Vehicle::Type>(self.GetVehicleType());

bool self_conflict = false;
if (!self.IsJumping()) {
auto bit = GetPassableMask(self.GetX(), self.GetY(), x, y);
// Check for self conflict.
// If this event has a tile graphic and the tile itself has passage blocked in the direction
// we want to move, flag it as "self conflicting" for use later.
if (self.GetLayer() == RPG::EventPage::Layers_below && self.GetTileId() != 0) {
int tile_id = self.GetTileId();
if ((passages_up[tile_id] & bit) == 0) {
self_conflict = true;
}
}

if (vehicle_type == Game_Vehicle::None) {
// Check that we are allowed to step off of the current tile.
// Note: Vehicles can always step off a tile.
if (!IsPassableTile(&self, bit, self.GetX(), self.GetY())) {
return false;
}
}
}

return
(stepped_off_event || IsPassableTile(DirToMask(d), x + y * GetWidth()))
&& (stepped_onto_event || IsPassableTile(DirToMask(Game_Character::ReverseDir(d)), new_x + new_y * GetWidth()));
if (vehicle_type != Game_Vehicle::Airship) {
// Check for collision with events on the target tile.
for (auto& other: GetEvents()) {
if (MakeWayCollideEvent(x, y, self, other, self_conflict)) {
return false;
}
}
auto& player = Main_Data::game_player;
if (player->GetVehicleType() == Game_Vehicle::None) {
if (MakeWayCollideEvent(x, y, self, *Main_Data::game_player, self_conflict)) {
return false;
}
}
for (auto vid: { Game_Vehicle::Boat, Game_Vehicle::Ship}) {
auto& other = vehicles[vid - 1];
if (other->IsInCurrentMap()) {
if (MakeWayCollideEvent(x, y, self, *other, self_conflict)) {
return false;
}
}
}
auto& airship = vehicles[Game_Vehicle::Airship - 1];
if (airship->IsInCurrentMap() && self.GetType() != Game_Character::Player) {
if (MakeWayCollideEvent(x, y, self, *airship, self_conflict)) {
return false;
}
}
}

int bit = 0;
if (self.IsJumping()) {
bit = Passable::Down | Passable::Up | Passable::Left | Passable::Right;
} else {
bit = GetPassableMask(x, y, self.GetX(), self.GetY());
}

return IsPassableTile(&self, bit, x, y);
}

bool Game_Map::IsPassable(int x, int y, int d, const Game_Character* self_event) {
Expand Down Expand Up @@ -673,7 +770,7 @@ bool Game_Map::IsPassable(int x, int y, int d, const Game_Character* self_event)
return true;
}

return IsPassableTile(bit, x + y * GetWidth());
return IsPassableTile(nullptr, bit, x, y);
}

bool Game_Map::IsPassableVehicle(int x, int y, Game_Vehicle::Type vehicle_type) {
Expand Down Expand Up @@ -729,6 +826,7 @@ bool Game_Map::IsPassableVehicle(int x, int y, Game_Vehicle::Type vehicle_type)
return true;
}


bool Game_Map::IsLandable(int x, int y, const Game_Character *self_event) {
if (!Game_Map::IsValid(x, y)) return false;

Expand All @@ -754,13 +852,74 @@ bool Game_Map::IsLandable(int x, int y, const Game_Character *self_event) {
}
}

return IsPassableTile(bit, x + y * GetWidth());
return IsPassableTile(nullptr, bit, x, y);
}

bool Game_Map::IsPassableTile(int bit, int tile_index) {
bool Game_Map::IsPassableTile(const Game_Character* self, int bit, int x, int y) {
if (!IsValid(x, y)) return false;

auto vehicle_type = (self != nullptr)
? self->GetVehicleType() : Game_Vehicle::None;

if (vehicle_type != Game_Vehicle::None) {
const auto* terrain = ReaderUtil::GetElement(Data::terrains, GetTerrainTag(x, y));
if (!terrain) {
Output::Warning("MakeWay: Invalid terrain at (%d, %d)", x, y);
return false;
}
if (vehicle_type == Game_Vehicle::Boat && !terrain->boat_pass) {
return false;
}
if (vehicle_type == Game_Vehicle::Ship && !terrain->ship_pass) {
return false;
}
if (vehicle_type == Game_Vehicle::Airship) {
return terrain->airship_pass;
}
}

// Highest ID event with layer=below, not through, and a tile graphic wins.
int event_tile_id = 0;
for (auto& ev: events) {
if (self == &ev) {
continue;
}
if (!ev.IsActive() || ev.GetThrough()) {
continue;
}
if (ev.IsInPosition(x, y) && ev.GetLayer() == RPG::EventPage::Layers_below) {
int tile_id = ev.GetTileId();
if (tile_id > 0) {
event_tile_id = tile_id;
}
}
}

// If there was a below tile event, and the tile is not above
// Override the chipset with event tile behavior.
if (event_tile_id > 0
&& ((passages_up[event_tile_id] & Passable::Above) == 0)) {
switch (vehicle_type) {
case Game_Vehicle::None:
return ((passages_up[event_tile_id] & bit) != 0);
case Game_Vehicle::Boat:
case Game_Vehicle::Ship:
return false;
case Game_Vehicle::Airship:
break;
};
}

int tile_index = x + y * GetWidth();
int tile_id = map->upper_layer[tile_index] - BLOCK_F;
tile_id = map_info.upper_tiles[tile_id];

if (vehicle_type == Game_Vehicle::Boat || vehicle_type == Game_Vehicle::Ship) {
if ((passages_up[tile_id] & Passable::Above) == 0)
return false;
return true;
}

if ((passages_up[tile_id] & bit) == 0)
return false;

Expand Down

0 comments on commit 4d57305

Please sign in to comment.