add cartridge system

This commit is contained in:
asrael 2025-09-27 11:03:36 -05:00
parent ff698730f1
commit 98ca54e920
25 changed files with 968 additions and 315 deletions

View file

@ -4,6 +4,30 @@
#include <stdlib.h>
#include <string.h>
static inline u32 pxl8_chunk_index(u32 x, u32 y, u32 chunks_wide) {
return (y >> 4) * chunks_wide + (x >> 4);
}
static pxl8_tile_chunk* pxl8_get_or_create_chunk(pxl8_tilemap_layer* layer, u32 x, u32 y) {
u32 chunk_x = x >> 4;
u32 chunk_y = y >> 4;
u32 idx = chunk_y * layer->chunks_wide + chunk_x;
if (idx >= layer->chunks_wide * layer->chunks_high) return NULL;
if (!layer->chunks[idx]) {
layer->chunks[idx] = calloc(1, sizeof(pxl8_tile_chunk));
if (!layer->chunks[idx]) return NULL;
layer->chunks[idx]->chunk_x = chunk_x;
layer->chunks[idx]->chunk_y = chunk_y;
layer->chunks[idx]->empty = true;
layer->allocated_chunks++;
}
return layer->chunks[idx];
}
pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size) {
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) {
@ -16,17 +40,21 @@ pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32
tilemap->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
tilemap->active_layers = 1;
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
tilemap->layers[i].width = width;
tilemap->layers[i].height = height;
tilemap->layers[i].visible = (i == 0);
tilemap->layers[i].opacity = 255;
u32 chunks_wide = (width + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
u32 chunks_high = (height + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
size_t tiles_size = width * height * sizeof(pxl8_tile);
tilemap->layers[i].tiles = calloc(1, tiles_size);
if (!tilemap->layers[i].tiles) {
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
pxl8_tilemap_layer* layer = &tilemap->layers[i];
layer->chunks_wide = chunks_wide;
layer->chunks_high = chunks_high;
layer->chunk_count = chunks_wide * chunks_high;
layer->visible = (i == 0);
layer->opacity = 255;
layer->chunks = calloc(layer->chunk_count, sizeof(pxl8_tile_chunk*));
if (!layer->chunks) {
for (u32 j = 0; j < i; j++) {
free(tilemap->layers[j].tiles);
free(tilemap->layers[j].chunks);
}
return PXL8_ERROR_OUT_OF_MEMORY;
}
@ -39,17 +67,33 @@ void pxl8_tilemap_free(pxl8_tilemap* tilemap) {
if (!tilemap) return;
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
if (tilemap->layers[i].tiles) {
free(tilemap->layers[i].tiles);
tilemap->layers[i].tiles = NULL;
pxl8_tilemap_layer* layer = &tilemap->layers[i];
if (layer->chunks) {
for (u32 j = 0; j < layer->chunk_count; j++) {
if (layer->chunks[j]) {
free(layer->chunks[j]);
}
}
free(layer->chunks);
layer->chunks = NULL;
}
}
if (tilemap->tilesheet) {
pxl8_tilesheet_unref(tilemap->tilesheet);
tilemap->tilesheet = NULL;
}
}
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) {
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
if (tilemap->tilesheet) {
pxl8_tilesheet_unref(tilemap->tilesheet);
}
tilemap->tilesheet = tilesheet;
pxl8_tilesheet_ref(tilesheet);
if (tilesheet->tile_size != tilemap->tile_size) {
pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
@ -66,9 +110,18 @@ pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y
if (x >= tilemap->width || y >= tilemap->height) return PXL8_ERROR_INVALID_COORDINATE;
pxl8_tilemap_layer* l = &tilemap->layers[layer];
u32 idx = y * tilemap->width + x;
l->tiles[idx].id = tile_id;
l->tiles[idx].flags = flags;
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, x, y);
if (!chunk) return PXL8_ERROR_OUT_OF_MEMORY;
u32 local_x = x & PXL8_CHUNK_MASK;
u32 local_y = y & PXL8_CHUNK_MASK;
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
chunk->tiles[idx] = pxl8_tile_pack(tile_id, flags, 0);
if (tile_id != 0) {
chunk->empty = false;
}
if (layer >= tilemap->active_layers) {
tilemap->active_layers = layer + 1;
@ -79,12 +132,20 @@ pxl8_result pxl8_tilemap_set_tile(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y
}
pxl8_tile pxl8_tilemap_get_tile(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
pxl8_tile empty_tile = {0, 0, 0};
if (!tilemap || layer >= PXL8_MAX_TILE_LAYERS) return 0;
if (x >= tilemap->width || y >= tilemap->height) return 0;
if (!tilemap || layer >= PXL8_MAX_TILE_LAYERS) return empty_tile;
if (x >= tilemap->width || y >= tilemap->height) return empty_tile;
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
u32 chunk_idx = pxl8_chunk_index(x, y, l->chunks_wide);
return tilemap->layers[layer].tiles[y * tilemap->width + x];
if (chunk_idx >= l->chunk_count || !l->chunks[chunk_idx]) return 0;
const pxl8_tile_chunk* chunk = l->chunks[chunk_idx];
u32 local_x = x & PXL8_CHUNK_MASK;
u32 local_y = y & PXL8_CHUNK_MASK;
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
return chunk->tiles[idx];
}
void pxl8_tilemap_set_camera(pxl8_tilemap* tilemap, i32 x, i32 y) {
@ -124,18 +185,49 @@ void pxl8_tilemap_render_layer(const pxl8_tilemap* tilemap, pxl8_gfx_ctx* gfx, u
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
if (!l->visible) return;
pxl8_tilemap_view view;
pxl8_tilemap_get_view(tilemap, gfx, &view);
i32 view_left = tilemap->camera_x / tilemap->tile_size;
i32 view_top = tilemap->camera_y / tilemap->tile_size;
i32 view_right = (tilemap->camera_x + gfx->framebuffer_width) / tilemap->tile_size + 1;
i32 view_bottom = (tilemap->camera_y + gfx->framebuffer_height) / tilemap->tile_size + 1;
for (i32 ty = view.tile_start_y; ty < view.tile_end_y; ty++) {
for (i32 tx = view.tile_start_x; tx < view.tile_end_x; tx++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
if (tile.id == 0) continue;
u32 chunk_left = pxl8_max(0, view_left >> 4);
u32 chunk_top = pxl8_max(0, view_top >> 4);
u32 chunk_right = pxl8_min((tilemap->width + 15) >> 4, (view_right >> 4) + 1);
u32 chunk_bottom = pxl8_min((tilemap->height + 15) >> 4, (view_bottom >> 4) + 1);
i32 screen_x = tx * tilemap->tile_size - tilemap->camera_x;
i32 screen_y = ty * tilemap->tile_size - tilemap->camera_y;
for (u32 cy = chunk_top; cy < chunk_bottom; cy++) {
for (u32 cx = chunk_left; cx < chunk_right; cx++) {
u32 chunk_idx = cy * l->chunks_wide + cx;
if (chunk_idx >= l->chunk_count || !l->chunks[chunk_idx]) continue;
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile.id, screen_x, screen_y, tile.flags);
const pxl8_tile_chunk* chunk = l->chunks[chunk_idx];
if (chunk->empty) continue;
u32 tile_start_x = pxl8_max(0, view_left - (i32)(cx << 4));
u32 tile_end_x = pxl8_min(PXL8_CHUNK_SIZE, view_right - (i32)(cx << 4));
u32 tile_start_y = pxl8_max(0, view_top - (i32)(cy << 4));
u32 tile_end_y = pxl8_min(PXL8_CHUNK_SIZE, view_bottom - (i32)(cy << 4));
for (u32 ty = tile_start_y; ty < tile_end_y; ty++) {
for (u32 tx = tile_start_x; tx < tile_end_x; tx++) {
u32 idx = ty * PXL8_CHUNK_SIZE + tx;
pxl8_tile tile = chunk->tiles[idx];
u16 tile_id = pxl8_tile_get_id(tile);
if (tile_id == 0) continue;
i32 world_x = (cx << 4) + tx;
i32 world_y = (cy << 4) + ty;
i32 screen_x = world_x * tilemap->tile_size - tilemap->camera_x;
i32 screen_y = world_y * tilemap->tile_size - tilemap->camera_y;
u8 flags = pxl8_tile_get_flags(tile);
if (flags & PXL8_TILE_ANIMATED && tilemap->tilesheet) {
tile_id = pxl8_tilesheet_get_animated_frame(tilemap->tilesheet, tile_id);
}
pxl8_tilesheet_render_tile(tilemap->tilesheet, gfx, tile_id, screen_x, screen_y, flags);
}
}
}
}
}
@ -156,7 +248,7 @@ bool pxl8_tilemap_is_solid(const pxl8_tilemap* tilemap, u32 x, u32 y) {
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tile_x, tile_y);
if (tile.flags & PXL8_TILE_SOLID) return true;
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
}
return false;
@ -177,7 +269,7 @@ bool pxl8_tilemap_check_collision(const pxl8_tilemap* tilemap, i32 x, i32 y, i32
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
if (tile.flags & PXL8_TILE_SOLID) return true;
if (pxl8_tile_get_flags(tile) & PXL8_TILE_SOLID) return true;
}
}
}
@ -211,5 +303,120 @@ void pxl8_tilemap_destroy(pxl8_tilemap* tilemap) {
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
if (!tilemap) return 0;
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, x, y);
return tile.id;
}
return pxl8_tile_get_id(tile);
}
void pxl8_tilemap_update(pxl8_tilemap* tilemap, f32 delta_time) {
if (!tilemap || !tilemap->tilesheet) return;
pxl8_tilesheet_update_animations(tilemap->tilesheet, delta_time);
}
static u8 pxl8_tilemap_get_neighbors(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u16 match_id) {
u8 neighbors = 0;
if (y > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x, y - 1) == match_id)
neighbors |= 1 << 0;
if (x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y) == match_id)
neighbors |= 1 << 1;
if (y < tilemap->height - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x, y + 1) == match_id)
neighbors |= 1 << 2;
if (x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y) == match_id)
neighbors |= 1 << 3;
if (y > 0 && x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y - 1) == match_id)
neighbors |= 1 << 4;
if (y < tilemap->height - 1 && x < tilemap->width - 1 && pxl8_tilemap_get_tile_id(tilemap, layer, x + 1, y + 1) == match_id)
neighbors |= 1 << 5;
if (y < tilemap->height - 1 && x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y + 1) == match_id)
neighbors |= 1 << 6;
if (y > 0 && x > 0 && pxl8_tilemap_get_tile_id(tilemap, layer, x - 1, y - 1) == match_id)
neighbors |= 1 << 7;
return neighbors;
}
pxl8_result pxl8_tilemap_set_tile_auto(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y,
u16 base_tile_id, u8 flags) {
if (!tilemap || !tilemap->tilesheet) return PXL8_ERROR_NULL_POINTER;
pxl8_result result = pxl8_tilemap_set_tile(tilemap, layer, x, y, base_tile_id, flags | PXL8_TILE_AUTOTILE);
if (result != PXL8_OK) return result;
pxl8_tilemap_update_autotiles(tilemap, layer,
x > 0 ? x - 1 : x, y > 0 ? y - 1 : y,
x < tilemap->width - 1 ? 3 : 2,
y < tilemap->height - 1 ? 3 : 2);
return PXL8_OK;
}
void pxl8_tilemap_update_autotiles(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u32 w, u32 h) {
if (!tilemap || !tilemap->tilesheet) return;
for (u32 ty = y; ty < y + h && ty < tilemap->height; ty++) {
for (u32 tx = x; tx < x + w && tx < tilemap->width; tx++) {
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
u8 flags = pxl8_tile_get_flags(tile);
if (flags & PXL8_TILE_AUTOTILE) {
u16 base_id = pxl8_tile_get_id(tile);
u8 neighbors = pxl8_tilemap_get_neighbors(tilemap, layer, tx, ty, base_id);
u16 new_id = pxl8_tilesheet_apply_autotile(tilemap->tilesheet, base_id, neighbors);
if (new_id != base_id) {
pxl8_tilemap_layer* l = &tilemap->layers[layer];
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, tx, ty);
if (chunk) {
u32 local_x = tx & PXL8_CHUNK_MASK;
u32 local_y = ty & PXL8_CHUNK_MASK;
u32 idx = local_y * PXL8_CHUNK_SIZE + local_x;
chunk->tiles[idx] = pxl8_tile_pack(new_id, flags, pxl8_tile_get_palette(tile));
}
}
}
}
}
}
u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap) {
if (!tilemap) return 0;
u32 total = sizeof(pxl8_tilemap);
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
const pxl8_tilemap_layer* layer = &tilemap->layers[i];
total += layer->chunk_count * sizeof(pxl8_tile_chunk*);
total += layer->allocated_chunks * sizeof(pxl8_tile_chunk);
}
return total;
}
void pxl8_tilemap_compress(pxl8_tilemap* tilemap) {
if (!tilemap) return;
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
pxl8_tilemap_layer* layer = &tilemap->layers[i];
for (u32 j = 0; j < layer->chunk_count; j++) {
pxl8_tile_chunk* chunk = layer->chunks[j];
if (!chunk) continue;
bool has_tiles = false;
for (u32 k = 0; k < PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE; k++) {
if (pxl8_tile_get_id(chunk->tiles[k]) != 0) {
has_tiles = true;
break;
}
}
if (!has_tiles) {
free(chunk);
layer->chunks[j] = NULL;
layer->allocated_chunks--;
} else {
chunk->empty = false;
}
}
}
}