Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
162 changes: 161 additions & 1 deletion src/game_character.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1123,7 +1123,32 @@ int Game_Character::GetSpriteY() const {
}

bool Game_Character::IsInPosition(int x, int y) const {
return ((GetX() == x) && (GetY() == y));
int x1, x2, y1, y2;
GetTileOffsets(x1, x2, y1, y2);
bool is_expanded = (x1 != 0 || x2 != 0 || y1 != 0 || y2 != 0);

if (!is_expanded) {
// Standard Branch: Exact coordinate match
return GetX() == x && GetY() == y;
}

// Extended Branch: Rectangle footprint match
if (GetY() + y1 > y || GetY() + y2 < y) {
return false;
}

int left_edge = GetX() + x1;
int right_edge = GetX() + x2;

if (Game_Map::LoopHorizontal()) {
int map_w = Game_Map::GetTilesX();
for (int i = left_edge; i <= right_edge; ++i) {
if (Utils::PositiveModulo(i, map_w) == x) return true;
}
return false;
}

return x >= left_edge && x <= right_edge;
}

int Game_Character::GetOpacity() const {
Expand Down Expand Up @@ -1231,3 +1256,138 @@ void Game_Character::UpdateFacing() {
SetFacing(dir);
}
}

void Game_Character::GetTileOffsets(int& min_dx, int& max_dx, int& min_dy, int& max_dy) const {
min_dx = 0;
max_dx = 0;
min_dy = 0;
max_dy = 0;

if (!_data) {
return;
}

std::string_view name = _data->sprite_name;
if (name.empty()) {
return;
}

// --- X-Axis Parsing: {_#ModeBias} ---
size_t x_bracket = name.find("{_");
if (x_bracket != std::string_view::npos) {
size_t close = name.find("}", x_bracket);
if (close != std::string_view::npos) {
int width = 0;
char bias = '\0';
char mode = '\0';
std::string_view tag = name.substr(x_bracket + 2, close - (x_bracket + 2));
for (size_t i = 0; i < tag.length(); ++i) {
char c = tag[i];
if (c >= '0' && c <= '9') {
width = width * 10 + (c - '0');
} else if (c == 'L' || c == 'l') {
bias = 'L';
} else if (c == 'R' || c == 'r') {
bias = 'R';
} else if (c == '-' || c == '+') {
mode = c;
}
}
if (width > 0) {
if (mode == '-') {
min_dx = -(width - 1);
max_dx = 0;
} else if (mode == '+') {
min_dx = 0;
max_dx = (width - 1);
} else {
if (width > 1 && width % 2 == 0 && bias == '\0') {
width--;
}
if (width > 1 && width % 2 == 0) {
int r = (width - 1) / 2;
if (bias == 'L') {
min_dx = -(r + 1);
max_dx = r;
} else {
min_dx = -r;
max_dx = r + 1;
}
} else {
int r = width / 2;
min_dx = -r;
max_dx = r;
}
}
}
}
}

// --- Y-Axis Parsing: {!#ModeBias} ---
size_t y_bracket = name.find("{!");
if (y_bracket != std::string_view::npos) {
size_t close = name.find("}", y_bracket);
if (close != std::string_view::npos) {
int height = 0;
char bias = '\0';
char mode = '\0';
std::string_view tag = name.substr(y_bracket + 2, close - (y_bracket + 2));
for (size_t i = 0; i < tag.length(); ++i) {
char c = tag[i];
if (c >= '0' && c <= '9') {
height = height * 10 + (c - '0');
} else if (c == 'U' || c == 'u') {
bias = 'U';
} else if (c == 'D' || c == 'd') {
bias = 'D';
} else if (c == '^' || c == 'v' || c == '=') {
mode = c;
}
}

if (mode == '\0') {
mode = '^';
}

if (height > 0) {
if (mode == '^') {
min_dy = -(height - 1);
max_dy = 0;
} else if (mode == 'v') {
min_dy = 0;
max_dy = (height - 1);
} else {
if (height > 1 && height % 2 == 0 && bias == '\0') {
height--;
}
if (height > 1 && height % 2 == 0) {
int r = (height - 1) / 2;
if (bias == 'U') {
min_dy = -(r + 1);
max_dy = r;
} else {
min_dy = -r;
max_dy = r + 1;
}
} else {
int r = height / 2;
min_dy = -r;
max_dy = r;
}
}
}
}
}
}

int Game_Character::GetTileWidth() const {
int x1, x2, y1, y2;
GetTileOffsets(x1, x2, y1, y2);
return (x2 - x1) + 1;
}

int Game_Character::GetTileHeight() const {
int x1, x2, y1, y2;
GetTileOffsets(x1, x2, y1, y2);
return (y2 - y1) + 1;
}
24 changes: 24 additions & 0 deletions src/game_character.h
Original file line number Diff line number Diff line change
Expand Up @@ -808,13 +808,37 @@ class Game_Character {
*/
int GetDistanceYfromCharacter(const Game_Character& target) const;

/**
* Calculates the relative tile offsets based on filename tags.
* min_dx/max_dx handle horizontal ({_#}) and min_dy/max_dy handle vertical ({!#}).
*/
void GetTileOffsets(int& min_dx, int& max_dx, int& min_dy, int& max_dy) const;

/** Returns the total width of the character in 16px tiles. */
int GetTileWidth() const;

/** Returns the total height of the character in 16px tiles. */
int GetTileHeight() const;

/** Returns the collision radius in units (1.0 = 16px) for pixel movement. */
float GetHitboxRadius() const;

/** Returns the absolute X center of the expanded hitbox in logical units. */
float GetHitboxCenterX() const;

/** Returns the absolute Y center of the expanded hitbox in logical units. */
float GetHitboxCenterY() const;

/**
* Tests if the character is currently on the tile at x/y or moving
* towards it.
*
* @param x X tile position
* @param y Y tile position
* @return If on tile or moving towards
* Checks if the character footprint overlaps the given tile coordinates.
* Overrides the standard 1-to-1 coordinate check.

*/
virtual bool IsInPosition(int x, int y) const;

Expand Down
81 changes: 62 additions & 19 deletions src/game_event.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,34 +359,77 @@ bool Game_Event::CheckEventCollision() {
if (GetTrigger() == lcf::rpg::EventPage::Trigger_collision
&& GetLayer() != lcf::rpg::EventPage::Layers_same
&& !Main_Data::game_player->IsMoveRouteOverwritten()
&& !Game_Map::GetInterpreter().IsRunning()
&& Main_Data::game_player->GetX() == GetX()
&& Main_Data::game_player->GetY() == GetY())
&& !Game_Map::GetInterpreter().IsRunning())
{
ScheduleForegroundExecution(false, true);
SetStopCount(0);
return true;
int x1, x2, y1, y2;
GetTileOffsets(x1, x2, y1, y2);
bool is_expanded = (x1 != 0 || x2 != 0 || y1 != 0 || y2 != 0);

if (!is_expanded) {
// Standard logic
if (Main_Data::game_player->GetX() == GetX() && Main_Data::game_player->GetY() == GetY()) {
ScheduleForegroundExecution(false, true);
SetStopCount(0);
return true;
}
return false;
}

for (int dy = y1; dy <= y2; ++dy) {
for (int dx = x1; dx <= x2; ++dx) {
if (Main_Data::game_player->IsInPosition(Game_Map::RoundX(GetX() + dx), Game_Map::RoundY(GetY() + dy))) {
ScheduleForegroundExecution(false, true);
SetStopCount(0);
return true;
}
}
}
}
return false;
}

void Game_Event::CheckCollisonOnMoveFailure() {
if (Game_Map::GetInterpreter().IsRunning()) {
if (Game_Map::GetInterpreter().IsRunning()) return;

int x1, x2, y1, y2;
GetTileOffsets(x1, x2, y1, y2);
bool is_expanded = (x1 != 0 || x2 != 0 || y1 != 0 || y2 != 0);

if (!is_expanded) {
// Standard logic
int fx = Game_Map::XwithDirection(GetX(), GetDirection());
int fy = Game_Map::YwithDirection(GetY(), GetDirection());
if (Main_Data::game_player->GetX() == fx && Main_Data::game_player->GetY() == fy &&
GetLayer() == lcf::rpg::EventPage::Layers_same && GetTrigger() == lcf::rpg::EventPage::Trigger_collision) {
ScheduleForegroundExecution(false, true);
SetStopCount(0);
}
return;
}

const auto front_x = Game_Map::XwithDirection(GetX(), GetDirection());
const auto front_y = Game_Map::YwithDirection(GetY(), GetDirection());

if (Main_Data::game_player->GetX() == front_x
&& Main_Data::game_player->GetY() == front_y
&& GetLayer() == lcf::rpg::EventPage::Layers_same
&& GetTrigger() == lcf::rpg::EventPage::Trigger_collision)
{
ScheduleForegroundExecution(false, true);
// Events with trigger collision and layer same always reset their
// stop_count when they fail movement to a tile that the player inhabits.
SetStopCount(0);
int dir = GetDirection();
if (dir == Up || dir == Down) {
int target_dy = (dir == Up) ? y1 : y2;
for (int dx = x1; dx <= x2; ++dx) {
int fx = Game_Map::RoundX(GetX() + dx);
int fy = Game_Map::RoundY(Game_Map::YwithDirection(GetY() + target_dy, dir));
if (Main_Data::game_player->IsInPosition(fx, fy) && GetLayer() == lcf::rpg::EventPage::Layers_same && GetTrigger() == lcf::rpg::EventPage::Trigger_collision) {
ScheduleForegroundExecution(false, true);
SetStopCount(0);
return;
}
}
} else {
int target_dx = (dir == Left) ? x1 : x2;
for (int dy = y1; dy <= y2; ++dy) {
int fx = Game_Map::RoundX(Game_Map::XwithDirection(GetX() + target_dx, dir));
int fy = Game_Map::RoundY(GetY() + dy);
if (Main_Data::game_player->IsInPosition(fx, fy) && GetLayer() == lcf::rpg::EventPage::Layers_same && GetTrigger() == lcf::rpg::EventPage::Trigger_collision) {
ScheduleForegroundExecution(false, true);
SetStopCount(0);
return;
}
}
}
}

Expand Down
Loading