2021-05-30 21:59:09 +02:00
|
|
|
#include "map.h"
|
2021-06-01 17:49:29 +02:00
|
|
|
#include <stdlib.h>
|
2022-03-11 00:31:31 +01:00
|
|
|
#include <gint/defs/util.h>
|
2021-05-30 21:59:09 +02:00
|
|
|
|
2022-02-11 20:42:20 +01:00
|
|
|
rect tile_shape(tileset_t const *tileset, int id)
|
2021-05-30 21:59:09 +02:00
|
|
|
{
|
2022-02-11 20:42:20 +01:00
|
|
|
if(!tileset->tiles[id].solid)
|
|
|
|
return (rect){ 0 };
|
2021-06-10 22:48:27 +02:00
|
|
|
|
2021-10-24 21:54:37 +02:00
|
|
|
return (rect){
|
2021-07-16 18:49:58 +02:00
|
|
|
.l = -fix(0.5),
|
|
|
|
.r = fix(0.5),
|
|
|
|
.t = -fix(0.5),
|
|
|
|
.b = fix(0.5),
|
2021-06-10 22:48:27 +02:00
|
|
|
};
|
2021-06-01 17:49:29 +02:00
|
|
|
}
|
|
|
|
|
2022-02-11 20:42:20 +01:00
|
|
|
map_cell_t *map_cell(map_t const *m, int x, int y)
|
2021-06-01 17:49:29 +02:00
|
|
|
{
|
2021-12-27 21:54:55 +01:00
|
|
|
if((unsigned)x >= m->width || (unsigned)y >= m->height)
|
2021-05-30 21:59:09 +02:00
|
|
|
return NULL;
|
|
|
|
|
2022-02-11 20:42:20 +01:00
|
|
|
return &m->cells[y * m->width + x];
|
2021-06-01 17:49:29 +02:00
|
|
|
}
|
|
|
|
|
2022-03-11 00:31:31 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2021-10-24 21:54:37 +02:00
|
|
|
bool map_collides(map_t const *m, rect hitbox)
|
2021-06-01 17:49:29 +02:00
|
|
|
{
|
2021-06-15 17:27:30 +02:00
|
|
|
int y_min = ffloor(hitbox.t);
|
|
|
|
int y_max = fceil(hitbox.b);
|
|
|
|
int x_min = ffloor(hitbox.l);
|
|
|
|
int x_max = fceil(hitbox.r);
|
|
|
|
|
2021-06-01 22:06:58 +02:00
|
|
|
/* Collisions against walls and static objects */
|
2021-06-15 17:27:30 +02:00
|
|
|
for(int y = y_min; y < y_max; y++)
|
|
|
|
for(int x = x_min; x < x_max; x++) {
|
2022-02-11 20:42:20 +01:00
|
|
|
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;
|
|
|
|
}
|
2021-06-01 22:06:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
2022-03-11 00:31:31 +01:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|