#include "map.h" #include #include rect tile_shape(tileset_t const *tileset, int id) { if(!tileset->tiles[id].solid) return (rect){ 0 }; return (rect){ .l = -fix(0.5), .r = fix(0.5), .t = -fix(0.5), .b = fix(0.5), }; } map_cell_t *map_cell(map_t const *m, int x, int y) { if((unsigned)x >= m->width || (unsigned)y >= m->height) return NULL; return &m->cells[y * m->width + x]; } bool map_cell_walkable(map_t const *m, map_cell_t const *cell) { if(!cell) return false; if(m->tileset->tiles[cell->base].solid) return false; if(cell->decor && m->tileset->tiles[cell->decor].solid) return false; return true; } bool map_collides(map_t const *m, rect hitbox) { int y_min = ffloor(hitbox.t); int y_max = fceil(hitbox.b); int x_min = ffloor(hitbox.l); int x_max = fceil(hitbox.r); /* Collisions against walls and static objects */ for(int y = y_min; y < y_max; y++) for(int x = x_min; x < x_max; x++) { map_cell_t *t = map_cell(m, x, y); if(!t) continue; vec2 c = { fix(x) + fix(0.5), fix(y) + fix(0.5) }; rect tile_hitbox; if(m->tileset->tiles[t->base].solid) { tile_hitbox = rect_translate(tile_shape(m->tileset, t->base), c); if(rect_collide(hitbox, tile_hitbox)) return true; } if(m->tileset->tiles[t->decor].solid) { tile_hitbox = rect_translate(tile_shape(m->tileset, t->decor), c); if(rect_collide(hitbox, tile_hitbox)) return true; } } return false; } vec2 map_move(map_t const *m, vec2 old_pos, vec2 new_pos, rect hitbox) { int x = ffloor(new_pos.x); int y = ffloor(new_pos.y); /* If there is no collision, accept immediately */ if(!map_collides(m, rect_translate(hitbox, new_pos))) return new_pos; /* Find the nearest walkable cell */ fixed_t best_d2 = fix(999); fixed_t best_x = fix(-1), best_y = fix(-1); for(int cy = y-1; cy <= y+1; cy++) for(int cx = x-1; cx <= x+1; cx++) { map_cell_t *cell = map_cell(m, cx, cy); if(!map_cell_walkable(m, cell)) continue; fixed_t closest_x = fclamp(new_pos.x, fix(cx), fix(cx+1)-1); fixed_t closest_y = fclamp(new_pos.y, fix(cy), fix(cy+1)-1); fixed_t d2 = dist2(new_pos, (vec2){ closest_x, closest_y }); if(d2 < best_d2) { best_d2 = d2; best_x = closest_x; best_y = closest_y; } } /* If there is no walkable cell, prevent movement */ if(best_x < 0 || best_y < 0) return old_pos; /* In that cell, clamp to the limits imposed by neighboring cells */ x = ffloor(best_x); y = ffloor(best_y); fixed_t nx = fdec(best_x); fixed_t ny = fdec(best_y); /* Limits at edges */ if(!map_cell_walkable(m, map_cell(m, x-1, y))) nx = max(nx, -hitbox.l); if(!map_cell_walkable(m, map_cell(m, x+1, y))) nx = min(nx, fix(1) - hitbox.r); if(!map_cell_walkable(m, map_cell(m, x, y-1))) ny = max(ny, -hitbox.t); if(!map_cell_walkable(m, map_cell(m, x, y+1))) ny = min(ny, fix(1) - hitbox.b); /* Limits at corners */ if(!map_cell_walkable(m, map_cell(m, x-1, y-1)) && nx < -hitbox.l && ny < -hitbox.t) { nx = -hitbox.l; ny = -hitbox.t; } if(!map_cell_walkable(m, map_cell(m, x+1, y-1)) && nx > fix(1) - hitbox.r && ny < -hitbox.t) { nx = fix(1) - hitbox.r; ny = -hitbox.t; } if(!map_cell_walkable(m, map_cell(m, x-1, y+1)) && nx < -hitbox.l && ny > fix(1) - hitbox.b) { nx = -hitbox.l; ny = fix(1) - hitbox.b; } if(!map_cell_walkable(m, map_cell(m, x+1, y+1)) && nx > fix(1) - hitbox.r && ny > fix(1) - hitbox.b) { nx = fix(1) - hitbox.r; ny = fix(1) - hitbox.b; } /* If the new position is valid, use it, otherwise refuse the movement. We catch here some potential bugs, plus a whole lot of unhandled cases when the hitbox is larger than a tile */ new_pos = (vec2){ fix(x) + nx, fix(y) + ny }; if(!map_collides(m, rect_translate(hitbox, new_pos))) return new_pos; return old_pos; }