Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Refactor MakeWay
* 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 IsTilePassable()
    * Better interface taking x,y
    * Support vehicles in one piece of code.
  • Loading branch information
fmatthew5876 committed Mar 8, 2019
1 parent 33dbccd commit 71e9f11
Show file tree
Hide file tree
Showing 8 changed files with 200 additions and 99 deletions.
20 changes: 4 additions & 16 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, false);
bool Game_Character::MakeWay(int x, int y) const {
return Game_Map::MakeWay(x, y, *this, false);
}

bool Game_Character::IsLandable(int x, int y) const
Expand Down Expand Up @@ -451,7 +447,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 @@ -710,7 +706,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)
!MakeWay(new_x, new_y)
) {
move_failed = true;
}
Expand Down Expand Up @@ -954,14 +950,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 - GetMoveFrequency()));
Expand Down
16 changes: 10 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 @@ -510,15 +513,14 @@ 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 tile x.
* @param y new tile y.
* @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 @@ -861,7 +863,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 @@ -1198,5 +1199,8 @@ inline bool Game_Character::IsActive() const {
return data()->active;
}

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

#endif
214 changes: 176 additions & 38 deletions src/game_map.cpp
Expand Up @@ -571,56 +571,140 @@ static CollisionResult TestCollisionDuringMove(
return NoCollision;
}

bool Game_Map::MakeWay(int x, int y, int d, const Game_Character& self, bool force_through) {
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));

if (!Game_Map::IsValid(new_x, new_y))
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::Up; }
if (new_y < old_y) { bit |= Passable::Down; }
return bit;
}

static bool WouldCollide(const Game_Character& self, const Game_Character& other, bool ev1_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 && ev1_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;
}

// Force the other event to update, allowing them to possibly move out of the way.
other.Update();

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

if (self.GetThrough() || force_through) 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) {
return WouldCollide(self, other, self_conflict);
}

bool Game_Map::MakeWay(int x, int y, const Game_Character& self, bool force_through) {
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.GetX(), y, self, force_through);
if (!vertical_ok) {
bool horizontal_ok = MakeWay(x, self.GetY(), self, force_through);
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)) {
if (self.GetThrough() || force_through) {
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(bit, self.GetX(), self.GetY(), vehicle_type)) {
return false;
}
}
}

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;
}
}
if (MakeWayCollideEvent(x, y, self, *Main_Data::game_player, self_conflict)) {
return false;
}
for (auto& other: vehicles) {
if (other->IsInCurrentMap()) {
if (MakeWayCollideEvent(x, y, self, *other, 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
(stepped_off_event || IsPassableTile(DirToMask(d), x + y * GetWidth()))
&& (stepped_onto_event || IsPassableTile(DirToMask(Game_Character::ReverseDir(d)), new_x + new_y * GetWidth()));
return IsPassableTile(bit, x, y, vehicle_type);
}

bool Game_Map::IsPassable(int x, int y, int d, const Game_Character* self_event) {
Expand Down Expand Up @@ -672,7 +756,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(bit, x, y, Game_Vehicle::None);
}

bool Game_Map::IsPassableVehicle(int x, int y, Game_Vehicle::Type vehicle_type) {
Expand Down Expand Up @@ -728,6 +812,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 @@ -753,13 +838,66 @@ bool Game_Map::IsLandable(int x, int y, const Game_Character *self_event) {
}
}

return IsPassableTile(bit, x + y * GetWidth());
return IsPassableTile(bit, x, y, Game_Vehicle::None);
}

bool Game_Map::IsPassableTile(int bit, int tile_index) {
bool Game_Map::IsPassableTile(int bit, int x, int y, Game_Vehicle::Type vehicle_type) {
if (!IsValid(x, y)) return false;

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 and a tile graphic wins.
int event_tile_id = 0;
for (auto& ev: events) {
// FIXME: don't tile check self.
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
15 changes: 7 additions & 8 deletions src/game_map.h
Expand Up @@ -138,20 +138,19 @@ namespace Game_Map {
bool IsValid(int x, int y);

/**
* Clears the way for a move by self from (x,y) in the direction d. Any events
* that block the way are updated early (by UpdateParallel) to give them a
* Clears the way for a move by self to (x,y). Any events
* that block the way are updated early to give them a
* chance to move out of the way.
*
* Returns true if everything is clear to make the move.
* Returns true if move is possible.
*
* @param x tile x.
* @param y tile y.
* @param d direction
* @param x new tile x.
* @param y new tile y.
* @param self Character to move.
* @param force_through act as if self.SetThrough() == true
* @return whether is passable.
*/
bool MakeWay(int x, int y, int d, const Game_Character& self, bool force_through);
bool MakeWay(int x, int y, const Game_Character& self, bool force_through);

/**
* Gets if a tile coordinate is passable in a direction.
Expand Down Expand Up @@ -566,7 +565,7 @@ namespace Game_Map {
int SubstituteDown(int old_id, int new_id);
int SubstituteUp(int old_id, int new_id);

bool IsPassableTile(int bit, int tile_index);
bool IsPassableTile(int bit, int x, int y, Game_Vehicle::Type vehicle);

enum PanDirection {
PanUp,
Expand Down

0 comments on commit 71e9f11

Please sign in to comment.