#include "keraforge/bini.h" #include #include #include #include #include #include struct _kf_tiles kf_tiles = {0}; static inline void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y); kf_tileid_t kf_addtile(struct kf_tile_opts opts) { KF_SANITY_CHECK(opts.key != NULL, "tile added without key"); KF_SANITY_CHECK(opts.sheet != NULL, "tile added without sheet"); kf_tileid_t id = ++kf_tiles.count; kf_tiles.key[id] = opts.key; kf_tiles.mapcol[id] = opts.mapcol; kf_tiles.sheet[id] = opts.sheet; kf_tiles.sprite[id] = opts.sprite; kf_tiles.collide[id] = opts.collide; kf_tiles.transparent[id] = opts.transparent; return id; } struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill) { const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height; struct kf_world *world = malloc(len); kf_loginfo("creating world: %lu bytes: %p", len, world); world->revision = 0; world->width = width; world->height = height; memset(world->map, 0, sizeof(struct kf_tile)*width*height); for (size_t i = 0 ; i < width*height ; i++) { world->map[i].subid = fill; world->map[i].id = fill; } u32 x; for (u32 y = 0 ; y < height ; y++) { for (x = 0 ; x < width ; x++) { _kf_updatetilebitmask(world, x, y); } } return world; } size_t kf_world_getsize(struct kf_world *world) { return sizeof(struct kf_world) + sizeof(struct kf_tile)*world->width*world->height; } static inline void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y) { struct kf_tile *t = kf_world_gettile(world, x, y); kf_tileid_t n = y-1 >= world->height ? 0 : kf_world_gettile(world, x, y-1)->id, w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id, e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id, s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id; t->data = 0b0000; if (t->id == n) t->data |= KF_TILEMASK_NORTH; if (t->id == w) t->data |= KF_TILEMASK_WEST; if (t->id == e) t->data |= KF_TILEMASK_EAST; if (t->id == s) t->data |= KF_TILEMASK_SOUTH; } void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours) { _kf_updatetilebitmask(world, x, y); if (update_neighbours) { if (y-1 < world->height) kf_world_updatetile(world, x, y-1, false); if (x-1 < world->width) kf_world_updatetile(world, x-1, y, false); if (x+1 < world->width) kf_world_updatetile(world, x+1, y, false); if (y+1 < world->height) kf_world_updatetile(world, x, y+1, false); } } inline struct kf_vec2(u32) kf_getspritefordatum(kf_tiledatum_t t) { static const struct kf_vec2(u32) v[] = { {0, 3}, /* 0b0000: */ {0, 2}, /* 0b0001: N */ {3, 3}, /* 0b0010: W */ {3, 2}, /* 0b0011: NW */ {1, 3}, /* 0b0100: E */ {1, 2}, /* 0b0101: EN */ {2, 3}, /* 0b0110: EW */ {2, 2}, /* 0b0111: EWN */ {0, 0}, /* 0b1000: S */ {0, 1}, /* 0b1001: SN */ {3, 0}, /* 0b1010: SW */ {3, 1}, /* 0b1011: SWN */ {1, 0}, /* 0b1100: SE */ {1, 1}, /* 0b1101: SEN */ {2, 0}, /* 0b1110: SEW */ {2, 1}, /* 0b1111: NESW */ }; return v[t]; } struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y) { return &world->map[y*world->width + x]; } void kf_world_drawcolliders(struct kf_world *world, struct kf_actor *player, Camera2D camera) { Rectangle r = { player->pos.x + (player->size.x / 2) + player->sizeoffset.x, player->pos.y + (player->size.y / 2) + player->sizeoffset.y, player->size.x, player->size.y }; DrawRectangleLinesEx(r, 1, BLUE); const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); const Vector2 end = GetScreenToWorld2D((Vector2){GetScreenWidth(), GetScreenHeight()}, camera); const u32 sx = fmax(0, floorf(start.x / KF_TILE_SIZE_PX)); const u32 sy = fmax(0, floorf(start.y / KF_TILE_SIZE_PX)); const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */ struct kf_tile *tile = kf_world_gettile(world, sx, sy); /* check if any tiles will collide with the actor's rect */ const f32 trx = sx * KF_TILE_SIZE_PX; Rectangle tr = { trx, sy * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, }; /* tile rect */ u32 x; for (u32 y = sy ; y <= ey ; y++) { for (x = sx ; x <= ex ; x++) { if (kf_tiles.collide[tile->id]) DrawRectangleLinesEx(tr, 1, CheckCollisionRecs(r, tr) ? RED : BLACK); tile++; /* shift tile pointer to the right */ tr.x += KF_TILE_SIZE_PX; } tile += down; /* shift tile pointer down */ tr.x = trx; tr.y += KF_TILE_SIZE_PX; } } void kf_world_draw(struct kf_world *world, Camera2D camera) { const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); const Vector2 end = GetScreenToWorld2D((Vector2){GetScreenWidth(), GetScreenHeight()}, camera); const u32 sx = fmax(0, floorf(start.x / KF_TILE_SIZE_PX)); const u32 sy = fmax(0, floorf(start.y / KF_TILE_SIZE_PX)); const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); const size_t down = world->width - ex + sx; /* number of indexes to add to reach the next tile down */ struct kf_tile *tile = kf_world_gettile(world, sx, sy); u32 x; for (u32 y = sy ; y < ey ; y++) { for (x = sx ; x < ex ; x++) { KF_SANITY_CHECK(tile->subid <= kf_tiles.count, "erroneous subtile on map at %u,%u: %u (count=%u)", x, y, tile->subid, kf_tiles.count); KF_SANITY_CHECK(tile->id <= kf_tiles.count, "erroneous tile on map at %u,%u: %u (count=%u)", x, y, tile->id, kf_tiles.count); /* 15: full tile, no subtile rendering needed (unless transparent) */ if ((tile->data != 15 || kf_tiles.transparent[tile->id]) && tile->subid && tile->id) { kf_drawsprite_wh( kf_tiles.sheet[tile->subid], x*KF_TILE_SIZE_PX, y*KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, kf_tiles.sprite[tile->subid].x + 2, /* 2,1 are offsets for the "middle" tile */ kf_tiles.sprite[tile->subid].y + 1 ); } if (tile->id) { struct kf_vec2(u32) s = kf_getspritefordatum(tile->data); kf_drawsprite_wh( kf_tiles.sheet[tile->id], x*KF_TILE_SIZE_PX, y*KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, kf_tiles.sprite[tile->id].x + s.x, kf_tiles.sprite[tile->id].y + s.y ); } tile++; /* shift tile pointer to the right */ } tile += down; /* shift tile pointer down */ } } #define _KF_MAPFILE_TMP "data/tmp/map.bin" #define _KF_MAPFILE_XZ "data/map.bin.xz" #define _KF_MAPFILE "data/map.bin" static void _kf_world_save_bs(struct kf_world *world, struct bini_stream *bs) { bini_wiu(bs, world->revision); bini_wiu(bs, world->width); bini_wiu(bs, world->height); struct kf_tile *t = &world->map[0]; for (size_t i = 0 ; i < world->width*world->height ; i++) { bini_wsu(bs, t->subid); bini_wsu(bs, t->id); bini_wcu(bs, t->data); t++; } } int kf_world_save(struct kf_world *world, bool compress, char *outfile) { outfile = outfile ? outfile : (compress ? _KF_MAPFILE_TMP : _KF_MAPFILE); struct bini_stream *bs = bini_new(); _kf_world_save_bs(world, bs); if (!kf_writebin(outfile, bs->buffer, bs->len)) KF_THROW("failed to open %s", outfile); bini_close(bs); if (compress) { if (!kf_compress(_KF_MAPFILE_TMP, _KF_MAPFILE_XZ)) { KF_THROW("failed to compress %s", _KF_MAPFILE_XZ); return 0; /* unreachable */ } /* we don't need this anymore, might as well toss it to save file storage. */ remove(_KF_MAPFILE_TMP); } return 1; } static void _kf_world_load_bs(struct kf_world **pworld, struct bini_stream *bs) { u32 r = bini_riu(bs); u32 w = bini_riu(bs); u32 h = bini_riu(bs); struct kf_world *world = malloc(sizeof(*world) + sizeof(struct kf_tile)*w*h); world->revision = r; world->width = w; world->height = h; struct kf_tile *t = &world->map[0]; for (size_t i = 0 ; i < world->width*world->height ; i++) { t->subid = bini_rsu(bs); t->id = bini_rsu(bs); t->data = bini_rcu(bs); t++; } *pworld = world; } int kf_world_load(struct kf_world **pworld, bool compressed, char *infile) { if (compressed) /* decompress before loading */ { if (!kf_exists(_KF_MAPFILE_XZ)) { KF_THROW("no such file: %s", _KF_MAPFILE_XZ); return 0; /* unreachable */ } kf_logdbg("decompressing %s to %s", _KF_MAPFILE_XZ, _KF_MAPFILE_TMP); if (!kf_decompress(_KF_MAPFILE_XZ, _KF_MAPFILE_TMP)) { KF_THROW("failed to decompress %s", _KF_MAPFILE_XZ); return 0; /* unreachable */ } } infile = infile ? infile : (compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE); kf_logdbg("loading world: %s", infile); size_t len = 0; struct bini_stream bs = { .mode = BINI_STREAM, .buffer = kf_readbin(infile, &len), }; if (!bs.buffer) KF_THROW("failed to read/open %s", infile); bs.cap = len; bs.len = len; kf_logdbg("loaded world into binary stream: len=%lu", len); _kf_world_load_bs(pworld, &bs); free(bs.buffer); kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len); if (compressed) { /* we don't need this anymore, might as well toss it to save file storage. */ remove(_KF_MAPFILE_TMP); } if (!*pworld) { KF_THROW("failed to load world"); return 0; /* unreachable */ } return 1; }