add cartridge system
This commit is contained in:
parent
ff698730f1
commit
98ca54e920
25 changed files with 968 additions and 315 deletions
|
|
@ -9,7 +9,7 @@
|
||||||
(var snow-init false)
|
(var snow-init false)
|
||||||
|
|
||||||
(global init (fn []
|
(global init (fn []
|
||||||
(pxl8.load_palette "res/palettes/gruvbox.ase")
|
(pxl8.load_palette "palettes/gruvbox.ase")
|
||||||
(set particles (pxl8.particles_new 1000))))
|
(set particles (pxl8.particles_new 1000))))
|
||||||
|
|
||||||
(global update (fn [dt]
|
(global update (fn [dt]
|
||||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
pxl8.sh
1
pxl8.sh
|
|
@ -345,6 +345,7 @@ case "$COMMAND" in
|
||||||
src/pxl8.c
|
src/pxl8.c
|
||||||
src/pxl8_ase.c
|
src/pxl8_ase.c
|
||||||
src/pxl8_blit.c
|
src/pxl8_blit.c
|
||||||
|
src/pxl8_cart.c
|
||||||
src/pxl8_font.c
|
src/pxl8_font.c
|
||||||
src/pxl8_gfx.c
|
src/pxl8_gfx.c
|
||||||
src/pxl8_io.c
|
src/pxl8_io.c
|
||||||
|
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
(local pxl8 (require :pxl8))
|
|
||||||
|
|
||||||
(var pxl8-sprite-id nil)
|
|
||||||
|
|
||||||
(global init (fn []
|
|
||||||
(pxl8.load_palette "./res/palettes/gruvbox.ase")
|
|
||||||
(set pxl8-sprite-id (pxl8.load_sprite "./res/sprites/pxl8.ase"))
|
|
||||||
(when (not pxl8-sprite-id)
|
|
||||||
(pxl8.error "Failed to load pxl8 sprite"))))
|
|
||||||
|
|
||||||
(global update (fn [_dt]))
|
|
||||||
|
|
||||||
(global draw (fn []
|
|
||||||
(pxl8.clr 1)
|
|
||||||
(local cols 5)
|
|
||||||
(local sprite-w 128)
|
|
||||||
(local sprite-h 64)
|
|
||||||
|
|
||||||
(for [i 0 8192]
|
|
||||||
(local col (% i cols))
|
|
||||||
(local row (math.floor (/ i cols)))
|
|
||||||
(local x (* col (+ sprite-w 4)))
|
|
||||||
(local y (* row (+ sprite-h 2)))
|
|
||||||
(pxl8.sprite pxl8-sprite-id x y sprite-w sprite-h))))
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
(local pxl8 (require :pxl8))
|
|
||||||
|
|
||||||
(local TILE_SIZE 16)
|
|
||||||
(local SCREEN_WIDTH 320)
|
|
||||||
(local SCREEN_HEIGHT 240)
|
|
||||||
|
|
||||||
(var game-state {
|
|
||||||
:tilesheet nil
|
|
||||||
:tilemap nil
|
|
||||||
:camera {:x 0 :y 0}
|
|
||||||
:player {:x 5 :y 5
|
|
||||||
:screen-x 80 :screen-y 80
|
|
||||||
:facing :down
|
|
||||||
:moving false
|
|
||||||
:move-timer 0}
|
|
||||||
})
|
|
||||||
|
|
||||||
(local tile-types {
|
|
||||||
:empty 0
|
|
||||||
:floor 1
|
|
||||||
:wall-top 2
|
|
||||||
:wall-front 3
|
|
||||||
:wall-left 4
|
|
||||||
:wall-right 5
|
|
||||||
:wall-corner-tl 6
|
|
||||||
:wall-corner-tr 7
|
|
||||||
:wall-corner-bl 8
|
|
||||||
:wall-corner-br 9
|
|
||||||
:door 10
|
|
||||||
:chest 11
|
|
||||||
:stairs-up 12
|
|
||||||
:stairs-down 13
|
|
||||||
})
|
|
||||||
|
|
||||||
(fn generate-room [map x y width height]
|
|
||||||
(for [ry y (+ y height -1)]
|
|
||||||
(for [rx x (+ x width -1)]
|
|
||||||
(when (and (< rx 40) (< ry 30))
|
|
||||||
(let [is-wall (or (= rx x)
|
|
||||||
(= rx (+ x width -1))
|
|
||||||
(= ry y)
|
|
||||||
(= ry (+ y height -1)))]
|
|
||||||
(if is-wall
|
|
||||||
(tset map ry rx :wall)
|
|
||||||
(tset map ry rx :floor)))))))
|
|
||||||
|
|
||||||
(fn generate-dungeon []
|
|
||||||
(local map {})
|
|
||||||
|
|
||||||
(for [y 0 29]
|
|
||||||
(tset map y {})
|
|
||||||
(for [x 0 39]
|
|
||||||
(tset map y x :empty)))
|
|
||||||
|
|
||||||
(local rooms [])
|
|
||||||
(local room-count (+ 5 (math.random 5)))
|
|
||||||
|
|
||||||
(for [i 1 room-count]
|
|
||||||
(local room-w (+ 4 (math.random 6)))
|
|
||||||
(local room-h (+ 4 (math.random 6)))
|
|
||||||
(local room-x (math.random 0 (- 39 room-w)))
|
|
||||||
(local room-y (math.random 0 (- 29 room-h)))
|
|
||||||
|
|
||||||
(generate-room map room-x room-y room-w room-h)
|
|
||||||
(table.insert rooms {:x (+ room-x (math.floor (/ room-w 2)))
|
|
||||||
:y (+ room-y (math.floor (/ room-h 2)))
|
|
||||||
:w room-w
|
|
||||||
:h room-h}))
|
|
||||||
|
|
||||||
(for [i 1 (- (length rooms) 1)]
|
|
||||||
(let [room1 (. rooms i)
|
|
||||||
room2 (. rooms (+ i 1))
|
|
||||||
start-x room1.x
|
|
||||||
start-y room1.y
|
|
||||||
end-x room2.x
|
|
||||||
end-y room2.y]
|
|
||||||
|
|
||||||
(var cx start-x)
|
|
||||||
(var cy start-y)
|
|
||||||
|
|
||||||
(while (not= cx end-x)
|
|
||||||
(set cx (+ cx (if (< cx end-x) 1 -1)))
|
|
||||||
(when (= (. map cy cx) :empty)
|
|
||||||
(tset map cy cx :floor)))
|
|
||||||
|
|
||||||
(while (not= cy end-y)
|
|
||||||
(set cy (+ cy (if (< cy end-y) 1 -1)))
|
|
||||||
(when (= (. map cy cx) :empty)
|
|
||||||
(tset map cy cx :floor)))))
|
|
||||||
|
|
||||||
(let [first-room (. rooms 1)]
|
|
||||||
(set game-state.player.x first-room.x)
|
|
||||||
(set game-state.player.y first-room.y)
|
|
||||||
(set game-state.player.screen-x (* first-room.x TILE_SIZE))
|
|
||||||
(set game-state.player.screen-y (* first-room.y TILE_SIZE)))
|
|
||||||
|
|
||||||
map)
|
|
||||||
|
|
||||||
(fn map-to-tilemap [map tilemap]
|
|
||||||
(for [y 0 29]
|
|
||||||
(for [x 0 39]
|
|
||||||
(let [tile (. map y x)
|
|
||||||
above (and (> y 0) (. map (- y 1) x))
|
|
||||||
below (and (< y 29) (. map (+ y 1) x))
|
|
||||||
left (and (> x 0) (. map y (- x 1)))
|
|
||||||
right (and (< x 39) (. map y (+ x 1)))]
|
|
||||||
|
|
||||||
(if (= tile :floor)
|
|
||||||
(pxl8.tilemap_set_tile tilemap 0 x y tile-types.floor 0)
|
|
||||||
|
|
||||||
(= tile :wall)
|
|
||||||
(let [is-top-wall (and below (= below :floor))
|
|
||||||
is-corner-tl (and (= above :empty) (= left :empty))
|
|
||||||
is-corner-tr (and (= above :empty) (= right :empty))
|
|
||||||
is-corner-bl (and (= below :floor) (= left :empty))
|
|
||||||
is-corner-br (and (= below :floor) (= right :empty))]
|
|
||||||
|
|
||||||
(if is-corner-tl (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-tl pxl8.TILE_SOLID)
|
|
||||||
is-corner-tr (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-tr pxl8.TILE_SOLID)
|
|
||||||
is-corner-bl (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-bl pxl8.TILE_SOLID)
|
|
||||||
is-corner-br (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-corner-br pxl8.TILE_SOLID)
|
|
||||||
is-top-wall (pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-top pxl8.TILE_SOLID)
|
|
||||||
(pxl8.tilemap_set_tile tilemap 0 x y tile-types.wall-front pxl8.TILE_SOLID)))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(pxl8.tilemap_set_tile tilemap 0 x y 0 0))))))
|
|
||||||
|
|
||||||
(fn move-player [dx dy]
|
|
||||||
(let [p game-state.player
|
|
||||||
new-x (+ p.x dx)
|
|
||||||
new-y (+ p.y dy)]
|
|
||||||
|
|
||||||
(when (and (>= new-x 0) (< new-x 40)
|
|
||||||
(>= new-y 0) (< new-y 30)
|
|
||||||
(not (pxl8.tilemap_is_solid game-state.tilemap
|
|
||||||
(* new-x TILE_SIZE)
|
|
||||||
(* new-y TILE_SIZE))))
|
|
||||||
(set p.x new-x)
|
|
||||||
(set p.y new-y)
|
|
||||||
(set p.screen-x (* new-x TILE_SIZE))
|
|
||||||
(set p.screen-y (* new-y TILE_SIZE))
|
|
||||||
(set p.moving true)
|
|
||||||
(set p.move-timer 0.2))))
|
|
||||||
|
|
||||||
(global init (fn []
|
|
||||||
(pxl8.info "Prydain RPG initializing...")
|
|
||||||
|
|
||||||
(pxl8.load_palette "./res/palettes/gruvbox.ase")
|
|
||||||
|
|
||||||
(set game-state.tilesheet (pxl8.tilesheet_new TILE_SIZE))
|
|
||||||
(set game-state.tilemap (pxl8.tilemap_new 40 30 TILE_SIZE))
|
|
||||||
|
|
||||||
(if (and game-state.tilesheet game-state.tilemap)
|
|
||||||
(do
|
|
||||||
(let [load-result (pxl8.tilesheet_load game-state.tilesheet "./res/tiles/tilesheet-dungeon.ase")]
|
|
||||||
(if (= load-result 0)
|
|
||||||
(pxl8.info "Loaded dungeon tilesheet")
|
|
||||||
(do
|
|
||||||
(pxl8.warn "Failed to load dungeon tilesheet, using placeholder tiles")
|
|
||||||
(pxl8.info (.. "Error code: " load-result)))))
|
|
||||||
|
|
||||||
(pxl8.tilemap_set_tilesheet game-state.tilemap game-state.tilesheet)
|
|
||||||
(pxl8.info "Created tilemap 40x30 and tilesheet")
|
|
||||||
|
|
||||||
(local dungeon-map (generate-dungeon))
|
|
||||||
(map-to-tilemap dungeon-map game-state.tilemap)
|
|
||||||
|
|
||||||
(pxl8.info "Random dungeon generated"))
|
|
||||||
(pxl8.error "Failed to create tilemap or tilesheet"))))
|
|
||||||
|
|
||||||
(global update (fn [dt]
|
|
||||||
(let [p game-state.player]
|
|
||||||
|
|
||||||
(when (> p.move-timer 0)
|
|
||||||
(set p.move-timer (- p.move-timer dt))
|
|
||||||
(when (<= p.move-timer 0)
|
|
||||||
(set p.moving false)))
|
|
||||||
|
|
||||||
(when (not p.moving)
|
|
||||||
(when (pxl8.key_pressed "w")
|
|
||||||
(set p.facing :up)
|
|
||||||
(move-player 0 -1))
|
|
||||||
(when (pxl8.key_pressed "s")
|
|
||||||
(set p.facing :down)
|
|
||||||
(move-player 0 1))
|
|
||||||
(when (pxl8.key_pressed "a")
|
|
||||||
(set p.facing :left)
|
|
||||||
(move-player -1 0))
|
|
||||||
(when (pxl8.key_pressed "d")
|
|
||||||
(set p.facing :right)
|
|
||||||
(move-player 1 0))
|
|
||||||
(when (pxl8.key_pressed "r")
|
|
||||||
(pxl8.info "Regenerating dungeon...")
|
|
||||||
(local new-map (generate-dungeon))
|
|
||||||
(map-to-tilemap new-map game-state.tilemap)))
|
|
||||||
|
|
||||||
(let [target-cam-x (- p.screen-x (/ SCREEN_WIDTH 2))
|
|
||||||
target-cam-y (- p.screen-y (/ SCREEN_HEIGHT 2))]
|
|
||||||
(set game-state.camera.x (math.max 0 (math.min target-cam-x (* (- 40 20) TILE_SIZE))))
|
|
||||||
(set game-state.camera.y (math.max 0 (math.min target-cam-y (* (- 30 15) TILE_SIZE)))))
|
|
||||||
|
|
||||||
(when game-state.tilemap
|
|
||||||
(pxl8.tilemap_set_camera game-state.tilemap
|
|
||||||
game-state.camera.x
|
|
||||||
game-state.camera.y)))))
|
|
||||||
|
|
||||||
(global draw (fn []
|
|
||||||
(pxl8.clr 0)
|
|
||||||
|
|
||||||
(when game-state.tilemap
|
|
||||||
(pxl8.tilemap_render game-state.tilemap))
|
|
||||||
|
|
||||||
(let [p game-state.player
|
|
||||||
px (- p.screen-x game-state.camera.x)
|
|
||||||
py (- p.screen-y game-state.camera.y)]
|
|
||||||
|
|
||||||
(pxl8.rect_fill px py TILE_SIZE TILE_SIZE 14)
|
|
||||||
|
|
||||||
(case p.facing
|
|
||||||
:up (pxl8.rect_fill (+ px 6) py 4 4 15)
|
|
||||||
:down (pxl8.rect_fill (+ px 6) (+ py 12) 4 4 15)
|
|
||||||
:left (pxl8.rect_fill px (+ py 6) 4 4 15)
|
|
||||||
:right (pxl8.rect_fill (+ px 12) (+ py 6) 4 4 15)))
|
|
||||||
|
|
||||||
(pxl8.text "WASD: Move | R: New Dungeon" 5 5 15)
|
|
||||||
(pxl8.text (.. "Pos: " game-state.player.x "," game-state.player.y) 5 15 15)))
|
|
||||||
53
src/pxl8.c
53
src/pxl8.c
|
|
@ -12,6 +12,7 @@
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <SDL3/SDL_main.h>
|
#include <SDL3/SDL_main.h>
|
||||||
|
|
||||||
|
#include "pxl8_cart.h"
|
||||||
#include "pxl8_lua.h"
|
#include "pxl8_lua.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
|
|
@ -37,6 +38,7 @@ typedef struct pxl8_app_state {
|
||||||
lua_State* lua;
|
lua_State* lua;
|
||||||
pxl8_repl_state repl;
|
pxl8_repl_state repl;
|
||||||
pxl8_resolution resolution;
|
pxl8_resolution resolution;
|
||||||
|
pxl8_cart* cart;
|
||||||
|
|
||||||
f32 fps_timer;
|
f32 fps_timer;
|
||||||
i32 frame_count;
|
i32 frame_count;
|
||||||
|
|
@ -242,19 +244,30 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||||
app.resolution = PXL8_RESOLUTION_640x360;
|
app.resolution = PXL8_RESOLUTION_640x360;
|
||||||
|
|
||||||
const char* script_arg = NULL;
|
const char* script_arg = NULL;
|
||||||
|
bool pack_mode = false;
|
||||||
|
const char* pack_input = NULL;
|
||||||
|
const char* pack_output = NULL;
|
||||||
|
|
||||||
for (int i = 1; i < argc; i++) {
|
for (int i = 1; i < argc; i++) {
|
||||||
if (strcmp(argv[i], "--repl") == 0) {
|
if (strcmp(argv[i], "--repl") == 0) {
|
||||||
app.repl_mode = true;
|
app.repl_mode = true;
|
||||||
|
} else if (strcmp(argv[i], "--pack") == 0) {
|
||||||
|
pack_mode = true;
|
||||||
|
if (i + 2 < argc) {
|
||||||
|
pack_input = argv[++i];
|
||||||
|
pack_output = argv[++i];
|
||||||
|
} else {
|
||||||
|
pxl8_error("--pack requires <folder> <output.pxc>");
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
} else if (!script_arg) {
|
} else if (!script_arg) {
|
||||||
script_arg = argv[i];
|
script_arg = argv[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (script_arg) {
|
if (pack_mode) {
|
||||||
strncpy(app.script_path, script_arg, sizeof(app.script_path) - 1);
|
pxl8_result result = pxl8_cart_pack(pack_input, pack_output);
|
||||||
app.script_path[sizeof(app.script_path) - 1] = '\0';
|
return (result == PXL8_OK) ? SDL_APP_SUCCESS : SDL_APP_FAILURE;
|
||||||
} else {
|
|
||||||
strcpy(app.script_path, "src/fnl/demo.fnl");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -286,6 +299,32 @@ SDL_AppResult SDL_AppInit(void** appstate, int argc, char* argv[]) {
|
||||||
return SDL_APP_FAILURE;
|
return SDL_APP_FAILURE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (script_arg) {
|
||||||
|
struct stat st;
|
||||||
|
bool is_cart = (stat(script_arg, &st) == 0 && S_ISDIR(st.st_mode)) ||
|
||||||
|
strstr(script_arg, ".pxc");
|
||||||
|
|
||||||
|
if (is_cart) {
|
||||||
|
char* original_cwd = getcwd(NULL, 0);
|
||||||
|
app.cart = pxl8_cart_new();
|
||||||
|
if (pxl8_cart_load(app.cart, script_arg) == PXL8_OK) {
|
||||||
|
pxl8_lua_setup_cart_path(app.lua, app.cart->base_path, original_cwd);
|
||||||
|
pxl8_cart_mount(app.cart);
|
||||||
|
strcpy(app.script_path, "main.fnl");
|
||||||
|
pxl8_info("Loaded cart: %s", app.cart->name);
|
||||||
|
} else {
|
||||||
|
pxl8_error("Failed to load cart: %s", script_arg);
|
||||||
|
return SDL_APP_FAILURE;
|
||||||
|
}
|
||||||
|
free(original_cwd);
|
||||||
|
} else {
|
||||||
|
strncpy(app.script_path, script_arg, sizeof(app.script_path) - 1);
|
||||||
|
app.script_path[sizeof(app.script_path) - 1] = '\0';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
strcpy(app.script_path, "src/fnl/demo.fnl");
|
||||||
|
}
|
||||||
|
|
||||||
pxl8_lua_setup_contexts(app.lua, &app.gfx, &app.input);
|
pxl8_lua_setup_contexts(app.lua, &app.gfx, &app.input);
|
||||||
|
|
||||||
app.script_mod_time = get_file_mod_time(app.script_path);
|
app.script_mod_time = get_file_mod_time(app.script_path);
|
||||||
|
|
@ -471,6 +510,10 @@ void SDL_AppQuit(void* appstate, SDL_AppResult result) {
|
||||||
if (app->repl_mode) {
|
if (app->repl_mode) {
|
||||||
pxl8_repl_shutdown(&app->repl);
|
pxl8_repl_shutdown(&app->repl);
|
||||||
}
|
}
|
||||||
|
if (app->cart) {
|
||||||
|
pxl8_cart_destroy(app->cart);
|
||||||
|
app->cart = NULL;
|
||||||
|
}
|
||||||
pxl8_lua_shutdown(app->lua);
|
pxl8_lua_shutdown(app->lua);
|
||||||
pxl8_gfx_shutdown(&app->gfx);
|
pxl8_gfx_shutdown(&app->gfx);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
326
src/pxl8_cart.c
Normal file
326
src/pxl8_cart.c
Normal file
|
|
@ -0,0 +1,326 @@
|
||||||
|
#include "pxl8_cart.h"
|
||||||
|
#include "pxl8_macros.h"
|
||||||
|
#include <dirent.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include "../lib/miniz/miniz.h"
|
||||||
|
|
||||||
|
static pxl8_cart* s_current_cart = NULL;
|
||||||
|
static char* s_original_cwd = NULL;
|
||||||
|
|
||||||
|
static bool is_directory(const char* path) {
|
||||||
|
struct stat st;
|
||||||
|
return stat(path, &st) == 0 && S_ISDIR(st.st_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_pxc_file(const char* path) {
|
||||||
|
size_t len = strlen(path);
|
||||||
|
return len > 4 && strcmp(path + len - 4, ".pxc") == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static char* get_cart_name(const char* path) {
|
||||||
|
char* name = strdup(path);
|
||||||
|
size_t len = strlen(name);
|
||||||
|
|
||||||
|
if (len > 4 && strcmp(name + len - 4, ".pxc") == 0) {
|
||||||
|
name[len - 4] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
char* last_slash = strrchr(name, '/');
|
||||||
|
if (last_slash) {
|
||||||
|
char* result = strdup(last_slash + 1);
|
||||||
|
free(name);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_cart* pxl8_cart_new(void) {
|
||||||
|
pxl8_cart* cart = calloc(1, sizeof(pxl8_cart));
|
||||||
|
if (!cart) {
|
||||||
|
pxl8_error("Failed to allocate cart");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return cart;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_destroy(pxl8_cart* cart) {
|
||||||
|
if (!cart) return;
|
||||||
|
pxl8_cart_unload(cart);
|
||||||
|
free(cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path) {
|
||||||
|
if (!cart || !path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
pxl8_cart_unload(cart);
|
||||||
|
|
||||||
|
cart->name = get_cart_name(path);
|
||||||
|
|
||||||
|
if (is_directory(path)) {
|
||||||
|
cart->base_path = realpath(path, NULL);
|
||||||
|
if (!cart->base_path) {
|
||||||
|
pxl8_error("Failed to resolve cart path: %s", path);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
cart->is_folder = true;
|
||||||
|
|
||||||
|
char main_path[512];
|
||||||
|
snprintf(main_path, sizeof(main_path), "%s/main.fnl", cart->base_path);
|
||||||
|
if (access(main_path, F_OK) != 0) {
|
||||||
|
snprintf(main_path, sizeof(main_path), "%s/main.lua", cart->base_path);
|
||||||
|
if (access(main_path, F_OK) != 0) {
|
||||||
|
pxl8_error("No main.fnl or main.lua found in cart: %s", path);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_info("Loaded cart folder: %s", cart->name);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_pxc_file(path)) {
|
||||||
|
FILE* file = fopen(path, "rb");
|
||||||
|
if (!file) {
|
||||||
|
pxl8_error("Failed to open cart archive: %s", path);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
fseek(file, 0, SEEK_END);
|
||||||
|
cart->archive_size = ftell(file);
|
||||||
|
fseek(file, 0, SEEK_SET);
|
||||||
|
|
||||||
|
cart->archive_data = malloc(cart->archive_size);
|
||||||
|
if (!cart->archive_data) {
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fread(cart->archive_data, 1, cart->archive_size, file) != cart->archive_size) {
|
||||||
|
free(cart->archive_data);
|
||||||
|
cart->archive_data = NULL;
|
||||||
|
fclose(file);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(file);
|
||||||
|
|
||||||
|
char temp_dir[256];
|
||||||
|
snprintf(temp_dir, sizeof(temp_dir), "/tmp/pxl8_%s_%d", cart->name, getpid());
|
||||||
|
|
||||||
|
if (mkdir(temp_dir, 0755) != 0) {
|
||||||
|
pxl8_error("Failed to create temp directory: %s", temp_dir);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_archive zip = {0};
|
||||||
|
if (!mz_zip_reader_init_mem(&zip, cart->archive_data, cart->archive_size, 0)) {
|
||||||
|
pxl8_error("Failed to open cart archive as zip");
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
int num_files = mz_zip_reader_get_num_files(&zip);
|
||||||
|
for (int i = 0; i < num_files; i++) {
|
||||||
|
mz_zip_archive_file_stat file_stat;
|
||||||
|
if (!mz_zip_reader_file_stat(&zip, i, &file_stat)) continue;
|
||||||
|
|
||||||
|
char extract_path[512];
|
||||||
|
snprintf(extract_path, sizeof(extract_path), "%s/%s", temp_dir, file_stat.m_filename);
|
||||||
|
|
||||||
|
if (file_stat.m_is_directory) {
|
||||||
|
mkdir(extract_path, 0755);
|
||||||
|
} else {
|
||||||
|
char* last_slash = strrchr(extract_path, '/');
|
||||||
|
if (last_slash) {
|
||||||
|
*last_slash = '\0';
|
||||||
|
mkdir(extract_path, 0755);
|
||||||
|
*last_slash = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mz_zip_reader_extract_to_file(&zip, i, extract_path, 0)) {
|
||||||
|
pxl8_warn("Failed to extract: %s", file_stat.m_filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_reader_end(&zip);
|
||||||
|
|
||||||
|
cart->base_path = strdup(temp_dir);
|
||||||
|
cart->is_folder = false;
|
||||||
|
|
||||||
|
pxl8_info("Loaded cart archive: %s", cart->name);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_error("Unknown cart format: %s", path);
|
||||||
|
return PXL8_ERROR_INVALID_FORMAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_unload(pxl8_cart* cart) {
|
||||||
|
if (!cart) return;
|
||||||
|
|
||||||
|
pxl8_cart_unmount(cart);
|
||||||
|
|
||||||
|
if (!cart->is_folder && cart->base_path) {
|
||||||
|
char cmd[512];
|
||||||
|
snprintf(cmd, sizeof(cmd), "rm -rf %s", cart->base_path);
|
||||||
|
system(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cart->base_path) {
|
||||||
|
free(cart->base_path);
|
||||||
|
cart->base_path = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cart->name) {
|
||||||
|
free(cart->name);
|
||||||
|
cart->name = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cart->archive_data) {
|
||||||
|
free(cart->archive_data);
|
||||||
|
cart->archive_data = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cart->archive_size = 0;
|
||||||
|
cart->is_folder = false;
|
||||||
|
cart->is_mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_mount(pxl8_cart* cart) {
|
||||||
|
if (!cart || !cart->base_path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
if (cart->is_mounted) return PXL8_OK;
|
||||||
|
|
||||||
|
if (s_current_cart) {
|
||||||
|
pxl8_cart_unmount(s_current_cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_original_cwd = getcwd(NULL, 0);
|
||||||
|
if (chdir(cart->base_path) != 0) {
|
||||||
|
pxl8_error("Failed to change to cart directory: %s", cart->base_path);
|
||||||
|
free(s_original_cwd);
|
||||||
|
s_original_cwd = NULL;
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
cart->is_mounted = true;
|
||||||
|
s_current_cart = cart;
|
||||||
|
|
||||||
|
pxl8_info("Mounted cart: %s", cart->name);
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_cart_unmount(pxl8_cart* cart) {
|
||||||
|
if (!cart || !cart->is_mounted) return;
|
||||||
|
|
||||||
|
if (s_original_cwd) {
|
||||||
|
chdir(s_original_cwd);
|
||||||
|
free(s_original_cwd);
|
||||||
|
s_original_cwd = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cart->is_mounted = false;
|
||||||
|
if (s_current_cart == cart) {
|
||||||
|
s_current_cart = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_info("Unmounted cart: %s", cart->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
char* pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path) {
|
||||||
|
if (!cart || !cart->base_path || !relative_path) return NULL;
|
||||||
|
|
||||||
|
char* full_path = malloc(512);
|
||||||
|
if (!full_path) return NULL;
|
||||||
|
|
||||||
|
snprintf(full_path, 512, "%s/%s", cart->base_path, relative_path);
|
||||||
|
return full_path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path) {
|
||||||
|
if (!cart || !cart->base_path || !path) return false;
|
||||||
|
|
||||||
|
char* full_path = pxl8_cart_resolve_path(cart, path);
|
||||||
|
if (!full_path) return false;
|
||||||
|
|
||||||
|
bool exists = access(full_path, F_OK) == 0;
|
||||||
|
free(full_path);
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pxl8_add_files_recursive(mz_zip_archive* zip, const char* dir_path, const char* prefix) {
|
||||||
|
DIR* dir = opendir(dir_path);
|
||||||
|
if (!dir) return;
|
||||||
|
|
||||||
|
struct dirent* entry;
|
||||||
|
while ((entry = readdir(dir)) != NULL) {
|
||||||
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
|
||||||
|
|
||||||
|
char full_path[512];
|
||||||
|
char zip_path[512];
|
||||||
|
snprintf(full_path, sizeof(full_path), "%s/%s", dir_path, entry->d_name);
|
||||||
|
snprintf(zip_path, sizeof(zip_path), "%s%s", prefix, entry->d_name);
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (stat(full_path, &st) == 0) {
|
||||||
|
if (S_ISDIR(st.st_mode)) {
|
||||||
|
char new_prefix[512];
|
||||||
|
snprintf(new_prefix, sizeof(new_prefix), "%s/", zip_path);
|
||||||
|
pxl8_add_files_recursive(zip, full_path, new_prefix);
|
||||||
|
} else {
|
||||||
|
pxl8_info("Adding: %s", zip_path);
|
||||||
|
if (!mz_zip_writer_add_file(zip, zip_path, full_path, NULL, 0, MZ_BEST_COMPRESSION)) {
|
||||||
|
pxl8_warn("Failed to add file: %s", zip_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path) {
|
||||||
|
if (!folder_path || !output_path) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
if (!is_directory(folder_path)) {
|
||||||
|
pxl8_error("Cart folder not found: %s", folder_path);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
char main_path[512];
|
||||||
|
snprintf(main_path, sizeof(main_path), "%s/main.fnl", folder_path);
|
||||||
|
if (access(main_path, F_OK) != 0) {
|
||||||
|
snprintf(main_path, sizeof(main_path), "%s/main.lua", folder_path);
|
||||||
|
if (access(main_path, F_OK) != 0) {
|
||||||
|
pxl8_error("No main.fnl or main.lua found in cart: %s", folder_path);
|
||||||
|
return PXL8_ERROR_FILE_NOT_FOUND;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_archive zip = {0};
|
||||||
|
if (!mz_zip_writer_init_file(&zip, output_path, 0)) {
|
||||||
|
pxl8_error("Failed to create archive: %s", output_path);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_info("Packing cart: %s -> %s", folder_path, output_path);
|
||||||
|
pxl8_add_files_recursive(&zip, folder_path, "");
|
||||||
|
|
||||||
|
if (!mz_zip_writer_finalize_archive(&zip)) {
|
||||||
|
pxl8_error("Failed to finalize archive");
|
||||||
|
mz_zip_writer_end(&zip);
|
||||||
|
return PXL8_ERROR_SYSTEM_FAILURE;
|
||||||
|
}
|
||||||
|
|
||||||
|
mz_zip_writer_end(&zip);
|
||||||
|
pxl8_info("Cart packed successfully!");
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_cart* pxl8_cart_current(void) {
|
||||||
|
return s_current_cart;
|
||||||
|
}
|
||||||
36
src/pxl8_cart.h
Normal file
36
src/pxl8_cart.h
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "pxl8_types.h"
|
||||||
|
|
||||||
|
typedef struct pxl8_cart {
|
||||||
|
char* base_path;
|
||||||
|
char* name;
|
||||||
|
void* archive_data;
|
||||||
|
size_t archive_size;
|
||||||
|
bool is_folder;
|
||||||
|
bool is_mounted;
|
||||||
|
} pxl8_cart;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_load(pxl8_cart* cart, const char* path);
|
||||||
|
void pxl8_cart_unload(pxl8_cart* cart);
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_mount(pxl8_cart* cart);
|
||||||
|
void pxl8_cart_unmount(pxl8_cart* cart);
|
||||||
|
|
||||||
|
char* pxl8_cart_resolve_path(const pxl8_cart* cart, const char* relative_path);
|
||||||
|
bool pxl8_cart_file_exists(const pxl8_cart* cart, const char* path);
|
||||||
|
|
||||||
|
pxl8_cart* pxl8_cart_new(void);
|
||||||
|
void pxl8_cart_destroy(pxl8_cart* cart);
|
||||||
|
|
||||||
|
pxl8_result pxl8_cart_pack(const char* folder_path, const char* output_path);
|
||||||
|
|
||||||
|
pxl8_cart* pxl8_cart_current(void);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#include "pxl8_lua.h"
|
#include "pxl8_lua.h"
|
||||||
#include "pxl8_vfx.h"
|
#include "pxl8_vfx.h"
|
||||||
#include "pxl8_macros.h"
|
#include "pxl8_macros.h"
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
static const char* pxl8_ffi_cdefs =
|
static const char* pxl8_ffi_cdefs =
|
||||||
"typedef uint8_t u8;\n"
|
"typedef uint8_t u8;\n"
|
||||||
|
|
@ -250,6 +251,40 @@ pxl8_result pxl8_lua_run_string(lua_State* lua_state, const char* code) {
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pxl8_lua_setup_cart_path(lua_State* lua_state, const char* cart_path, const char* original_cwd) {
|
||||||
|
if (!lua_state || !cart_path || !original_cwd) return;
|
||||||
|
|
||||||
|
lua_getglobal(lua_state, "package");
|
||||||
|
lua_getfield(lua_state, -1, "path");
|
||||||
|
const char* current_path = lua_tostring(lua_state, -1);
|
||||||
|
|
||||||
|
char new_path[2048];
|
||||||
|
snprintf(new_path, sizeof(new_path),
|
||||||
|
"%s/?.lua;%s/?/init.lua;%s/src/?.lua;%s/src/?/init.lua;%s/src/lua/?.lua;%s",
|
||||||
|
cart_path, cart_path, cart_path, cart_path, original_cwd,
|
||||||
|
current_path ? current_path : "");
|
||||||
|
|
||||||
|
lua_pushstring(lua_state, new_path);
|
||||||
|
lua_setfield(lua_state, -3, "path");
|
||||||
|
lua_pop(lua_state, 2);
|
||||||
|
|
||||||
|
lua_getglobal(lua_state, "fennel");
|
||||||
|
if (!lua_isnil(lua_state, -1)) {
|
||||||
|
lua_getfield(lua_state, -1, "path");
|
||||||
|
const char* fennel_path = lua_tostring(lua_state, -1);
|
||||||
|
|
||||||
|
snprintf(new_path, sizeof(new_path),
|
||||||
|
"%s/?.fnl;%s/?/init.fnl;%s/src/?.fnl;%s/src/?/init.fnl;%s",
|
||||||
|
cart_path, cart_path, cart_path, cart_path,
|
||||||
|
fennel_path ? fennel_path : "");
|
||||||
|
|
||||||
|
lua_pushstring(lua_state, new_path);
|
||||||
|
lua_setfield(lua_state, -3, "path");
|
||||||
|
lua_pop(lua_state, 1);
|
||||||
|
}
|
||||||
|
lua_pop(lua_state, 1);
|
||||||
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_lua_run_fennel_file(lua_State* lua_state, const char* filename) {
|
pxl8_result pxl8_lua_run_fennel_file(lua_State* lua_state, const char* filename) {
|
||||||
if (!lua_state || !filename) return PXL8_ERROR_NULL_POINTER;
|
if (!lua_state || !filename) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ pxl8_result pxl8_lua_run_fennel_file(lua_State* lua_state, const char* filename)
|
||||||
pxl8_result pxl8_lua_run_file(lua_State* lua_state, const char* filename);
|
pxl8_result pxl8_lua_run_file(lua_State* lua_state, const char* filename);
|
||||||
pxl8_result pxl8_lua_run_string(lua_State* lua_state, const char* code);
|
pxl8_result pxl8_lua_run_string(lua_State* lua_state, const char* code);
|
||||||
pxl8_result pxl8_lua_setup_contexts(lua_State* lua_state, pxl8_gfx_ctx* gfx_ctx, pxl8_input_state* input);
|
pxl8_result pxl8_lua_setup_contexts(lua_State* lua_state, pxl8_gfx_ctx* gfx_ctx, pxl8_input_state* input);
|
||||||
|
void pxl8_lua_setup_cart_path(lua_State* lua_state, const char* cart_path, const char* original_cwd);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,30 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.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) {
|
pxl8_result pxl8_tilemap_init(pxl8_tilemap* tilemap, u32 width, u32 height, u32 tile_size) {
|
||||||
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
|
if (!tilemap) return PXL8_ERROR_NULL_POINTER;
|
||||||
if (width > PXL8_MAX_TILEMAP_WIDTH || height > PXL8_MAX_TILEMAP_HEIGHT) {
|
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->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
|
||||||
tilemap->active_layers = 1;
|
tilemap->active_layers = 1;
|
||||||
|
|
||||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
u32 chunks_wide = (width + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
||||||
tilemap->layers[i].width = width;
|
u32 chunks_high = (height + PXL8_CHUNK_SIZE - 1) / PXL8_CHUNK_SIZE;
|
||||||
tilemap->layers[i].height = height;
|
|
||||||
tilemap->layers[i].visible = (i == 0);
|
|
||||||
tilemap->layers[i].opacity = 255;
|
|
||||||
|
|
||||||
size_t tiles_size = width * height * sizeof(pxl8_tile);
|
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||||
tilemap->layers[i].tiles = calloc(1, tiles_size);
|
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
||||||
if (!tilemap->layers[i].tiles) {
|
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++) {
|
for (u32 j = 0; j < i; j++) {
|
||||||
free(tilemap->layers[j].tiles);
|
free(tilemap->layers[j].chunks);
|
||||||
}
|
}
|
||||||
return PXL8_ERROR_OUT_OF_MEMORY;
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
}
|
}
|
||||||
|
|
@ -39,17 +67,33 @@ void pxl8_tilemap_free(pxl8_tilemap* tilemap) {
|
||||||
if (!tilemap) return;
|
if (!tilemap) return;
|
||||||
|
|
||||||
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
for (u32 i = 0; i < PXL8_MAX_TILE_LAYERS; i++) {
|
||||||
if (tilemap->layers[i].tiles) {
|
pxl8_tilemap_layer* layer = &tilemap->layers[i];
|
||||||
free(tilemap->layers[i].tiles);
|
if (layer->chunks) {
|
||||||
tilemap->layers[i].tiles = NULL;
|
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) {
|
pxl8_result pxl8_tilemap_set_tilesheet(pxl8_tilemap* tilemap, pxl8_tilesheet* tilesheet) {
|
||||||
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
|
if (!tilemap || !tilesheet) return PXL8_ERROR_NULL_POINTER;
|
||||||
|
|
||||||
|
if (tilemap->tilesheet) {
|
||||||
|
pxl8_tilesheet_unref(tilemap->tilesheet);
|
||||||
|
}
|
||||||
|
|
||||||
tilemap->tilesheet = tilesheet;
|
tilemap->tilesheet = tilesheet;
|
||||||
|
pxl8_tilesheet_ref(tilesheet);
|
||||||
|
|
||||||
if (tilesheet->tile_size != tilemap->tile_size) {
|
if (tilesheet->tile_size != tilemap->tile_size) {
|
||||||
pxl8_warn("Tilesheet tile size (%d) differs from tilemap tile size (%d)",
|
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;
|
if (x >= tilemap->width || y >= tilemap->height) return PXL8_ERROR_INVALID_COORDINATE;
|
||||||
|
|
||||||
pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||||
u32 idx = y * tilemap->width + x;
|
pxl8_tile_chunk* chunk = pxl8_get_or_create_chunk(l, x, y);
|
||||||
l->tiles[idx].id = tile_id;
|
if (!chunk) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
l->tiles[idx].flags = flags;
|
|
||||||
|
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) {
|
if (layer >= tilemap->active_layers) {
|
||||||
tilemap->active_layers = layer + 1;
|
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 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;
|
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||||
if (x >= tilemap->width || y >= tilemap->height) return empty_tile;
|
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) {
|
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];
|
const pxl8_tilemap_layer* l = &tilemap->layers[layer];
|
||||||
if (!l->visible) return;
|
if (!l->visible) return;
|
||||||
|
|
||||||
pxl8_tilemap_view view;
|
i32 view_left = tilemap->camera_x / tilemap->tile_size;
|
||||||
pxl8_tilemap_get_view(tilemap, gfx, &view);
|
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++) {
|
u32 chunk_left = pxl8_max(0, view_left >> 4);
|
||||||
for (i32 tx = view.tile_start_x; tx < view.tile_end_x; tx++) {
|
u32 chunk_top = pxl8_max(0, view_top >> 4);
|
||||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
|
u32 chunk_right = pxl8_min((tilemap->width + 15) >> 4, (view_right >> 4) + 1);
|
||||||
if (tile.id == 0) continue;
|
u32 chunk_bottom = pxl8_min((tilemap->height + 15) >> 4, (view_bottom >> 4) + 1);
|
||||||
|
|
||||||
i32 screen_x = tx * tilemap->tile_size - tilemap->camera_x;
|
for (u32 cy = chunk_top; cy < chunk_bottom; cy++) {
|
||||||
i32 screen_y = ty * tilemap->tile_size - tilemap->camera_y;
|
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++) {
|
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
||||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tile_x, tile_y);
|
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;
|
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++) {
|
for (u32 layer = 0; layer < tilemap->active_layers; layer++) {
|
||||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, tx, ty);
|
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) {
|
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y) {
|
||||||
if (!tilemap) return 0;
|
if (!tilemap) return 0;
|
||||||
pxl8_tile tile = pxl8_tilemap_get_tile(tilemap, layer, x, y);
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,24 +8,78 @@
|
||||||
#define PXL8_MAX_TILEMAP_WIDTH 256
|
#define PXL8_MAX_TILEMAP_WIDTH 256
|
||||||
#define PXL8_MAX_TILEMAP_HEIGHT 256
|
#define PXL8_MAX_TILEMAP_HEIGHT 256
|
||||||
#define PXL8_MAX_TILE_LAYERS 4
|
#define PXL8_MAX_TILE_LAYERS 4
|
||||||
|
#define PXL8_CHUNK_SIZE 16
|
||||||
|
#define PXL8_CHUNK_MASK 15
|
||||||
|
|
||||||
typedef enum pxl8_tile_flags {
|
typedef enum pxl8_tile_flags {
|
||||||
PXL8_TILE_FLIP_X = 1 << 0,
|
PXL8_TILE_FLIP_X = 1 << 0,
|
||||||
PXL8_TILE_FLIP_Y = 1 << 1,
|
PXL8_TILE_FLIP_Y = 1 << 1,
|
||||||
PXL8_TILE_SOLID = 1 << 2,
|
PXL8_TILE_SOLID = 1 << 2,
|
||||||
PXL8_TILE_TRIGGER = 1 << 3,
|
PXL8_TILE_TRIGGER = 1 << 3,
|
||||||
|
PXL8_TILE_ANIMATED = 1 << 4,
|
||||||
|
PXL8_TILE_AUTOTILE = 1 << 5,
|
||||||
} pxl8_tile_flags;
|
} pxl8_tile_flags;
|
||||||
|
|
||||||
typedef struct pxl8_tile {
|
#define PXL8_TILE_ID_MASK 0x0000FFFF
|
||||||
u16 id;
|
#define PXL8_TILE_FLAGS_MASK 0x00FF0000
|
||||||
u8 flags;
|
#define PXL8_TILE_PAL_MASK 0xFF000000
|
||||||
u8 palette_offset;
|
#define PXL8_TILE_ID_SHIFT 0
|
||||||
} pxl8_tile;
|
#define PXL8_TILE_FLAGS_SHIFT 16
|
||||||
|
#define PXL8_TILE_PAL_SHIFT 24
|
||||||
|
|
||||||
|
typedef u32 pxl8_tile;
|
||||||
|
|
||||||
|
static inline pxl8_tile pxl8_tile_pack(u16 id, u8 flags, u8 palette_offset) {
|
||||||
|
return (u32)id | ((u32)flags << 16) | ((u32)palette_offset << 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u16 pxl8_tile_get_id(pxl8_tile tile) {
|
||||||
|
return tile & PXL8_TILE_ID_MASK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 pxl8_tile_get_flags(pxl8_tile tile) {
|
||||||
|
return (tile & PXL8_TILE_FLAGS_MASK) >> PXL8_TILE_FLAGS_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 pxl8_tile_get_palette(pxl8_tile tile) {
|
||||||
|
return (tile & PXL8_TILE_PAL_MASK) >> PXL8_TILE_PAL_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct pxl8_tile_animation {
|
||||||
|
u16* frames;
|
||||||
|
u16 frame_count;
|
||||||
|
u16 current_frame;
|
||||||
|
f32 frame_duration;
|
||||||
|
f32 time_accumulator;
|
||||||
|
} pxl8_tile_animation;
|
||||||
|
|
||||||
|
typedef struct pxl8_tile_properties {
|
||||||
|
void* user_data;
|
||||||
|
u32 property_flags;
|
||||||
|
i16 collision_offset_x;
|
||||||
|
i16 collision_offset_y;
|
||||||
|
u16 collision_width;
|
||||||
|
u16 collision_height;
|
||||||
|
} pxl8_tile_properties;
|
||||||
|
|
||||||
|
typedef struct pxl8_autotile_rule {
|
||||||
|
u8 neighbor_mask;
|
||||||
|
u16 tile_id;
|
||||||
|
} pxl8_autotile_rule;
|
||||||
|
|
||||||
|
typedef struct pxl8_tile_chunk {
|
||||||
|
pxl8_tile tiles[PXL8_CHUNK_SIZE * PXL8_CHUNK_SIZE];
|
||||||
|
u32 chunk_x;
|
||||||
|
u32 chunk_y;
|
||||||
|
bool empty;
|
||||||
|
} pxl8_tile_chunk;
|
||||||
|
|
||||||
typedef struct pxl8_tilemap_layer {
|
typedef struct pxl8_tilemap_layer {
|
||||||
pxl8_tile* tiles;
|
pxl8_tile_chunk** chunks;
|
||||||
u32 width;
|
u32 chunks_wide;
|
||||||
u32 height;
|
u32 chunks_high;
|
||||||
|
u32 chunk_count;
|
||||||
|
u32 allocated_chunks;
|
||||||
bool visible;
|
bool visible;
|
||||||
u8 opacity;
|
u8 opacity;
|
||||||
} pxl8_tilemap_layer;
|
} pxl8_tilemap_layer;
|
||||||
|
|
@ -75,6 +129,15 @@ pxl8_tilemap* pxl8_tilemap_new(u32 width, u32 height, u32 tile_size);
|
||||||
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);
|
void pxl8_tilemap_destroy(pxl8_tilemap* tilemap);
|
||||||
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
|
u16 pxl8_tilemap_get_tile_id(const pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y);
|
||||||
|
|
||||||
|
void pxl8_tilemap_update(pxl8_tilemap* tilemap, f32 delta_time);
|
||||||
|
|
||||||
|
pxl8_result pxl8_tilemap_set_tile_auto(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y,
|
||||||
|
u16 base_tile_id, u8 flags);
|
||||||
|
void pxl8_tilemap_update_autotiles(pxl8_tilemap* tilemap, u32 layer, u32 x, u32 y, u32 w, u32 h);
|
||||||
|
|
||||||
|
u32 pxl8_tilemap_get_memory_usage(const pxl8_tilemap* tilemap);
|
||||||
|
void pxl8_tilemap_compress(pxl8_tilemap* tilemap);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -10,6 +10,7 @@ pxl8_result pxl8_tilesheet_init(pxl8_tilesheet* tilesheet, u32 tile_size) {
|
||||||
|
|
||||||
memset(tilesheet, 0, sizeof(pxl8_tilesheet));
|
memset(tilesheet, 0, sizeof(pxl8_tilesheet));
|
||||||
tilesheet->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
|
tilesheet->tile_size = tile_size ? tile_size : PXL8_TILE_SIZE;
|
||||||
|
tilesheet->ref_count = 1;
|
||||||
|
|
||||||
return PXL8_OK;
|
return PXL8_OK;
|
||||||
}
|
}
|
||||||
|
|
@ -26,6 +27,33 @@ void pxl8_tilesheet_free(pxl8_tilesheet* tilesheet) {
|
||||||
free(tilesheet->tile_valid);
|
free(tilesheet->tile_valid);
|
||||||
tilesheet->tile_valid = NULL;
|
tilesheet->tile_valid = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tilesheet->animations) {
|
||||||
|
for (u32 i = 0; i < tilesheet->animation_count; i++) {
|
||||||
|
if (tilesheet->animations[i].frames) {
|
||||||
|
free(tilesheet->animations[i].frames);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(tilesheet->animations);
|
||||||
|
tilesheet->animations = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tilesheet->properties) {
|
||||||
|
free(tilesheet->properties);
|
||||||
|
tilesheet->properties = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tilesheet->autotile_rules) {
|
||||||
|
for (u32 i = 0; i <= tilesheet->total_tiles; i++) {
|
||||||
|
if (tilesheet->autotile_rules[i]) {
|
||||||
|
free(tilesheet->autotile_rules[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(tilesheet->autotile_rules);
|
||||||
|
free(tilesheet->autotile_rule_counts);
|
||||||
|
tilesheet->autotile_rules = NULL;
|
||||||
|
tilesheet->autotile_rule_counts = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx) {
|
pxl8_result pxl8_tilesheet_load(pxl8_tilesheet* tilesheet, const char* filepath, pxl8_gfx_ctx* gfx) {
|
||||||
|
|
@ -177,6 +205,136 @@ pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size) {
|
||||||
|
|
||||||
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
|
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet) {
|
||||||
if (!tilesheet) return;
|
if (!tilesheet) return;
|
||||||
|
pxl8_tilesheet_unref(tilesheet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet) {
|
||||||
|
if (!tilesheet) return;
|
||||||
|
tilesheet->ref_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet) {
|
||||||
|
if (!tilesheet) return;
|
||||||
|
|
||||||
|
if (--tilesheet->ref_count == 0) {
|
||||||
pxl8_tilesheet_free(tilesheet);
|
pxl8_tilesheet_free(tilesheet);
|
||||||
free(tilesheet);
|
free(tilesheet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||||
|
const u16* frames, u16 frame_count, f32 frame_duration) {
|
||||||
|
if (!tilesheet || !frames || frame_count == 0) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
if (base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
|
||||||
|
if (!tilesheet->animations) {
|
||||||
|
tilesheet->animations = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_animation));
|
||||||
|
if (!tilesheet->animations) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_tile_animation* anim = &tilesheet->animations[base_tile_id];
|
||||||
|
if (anim->frames) free(anim->frames);
|
||||||
|
|
||||||
|
anim->frames = malloc(frame_count * sizeof(u16));
|
||||||
|
if (!anim->frames) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
memcpy(anim->frames, frames, frame_count * sizeof(u16));
|
||||||
|
anim->frame_count = frame_count;
|
||||||
|
anim->current_frame = 0;
|
||||||
|
anim->frame_duration = frame_duration;
|
||||||
|
anim->time_accumulator = 0;
|
||||||
|
|
||||||
|
tilesheet->animation_count++;
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_tilesheet_update_animations(pxl8_tilesheet* tilesheet, f32 delta_time) {
|
||||||
|
if (!tilesheet || !tilesheet->animations) return;
|
||||||
|
|
||||||
|
for (u32 i = 1; i <= tilesheet->total_tiles; i++) {
|
||||||
|
pxl8_tile_animation* anim = &tilesheet->animations[i];
|
||||||
|
if (!anim->frames || anim->frame_count == 0) continue;
|
||||||
|
|
||||||
|
anim->time_accumulator += delta_time;
|
||||||
|
while (anim->time_accumulator >= anim->frame_duration) {
|
||||||
|
anim->time_accumulator -= anim->frame_duration;
|
||||||
|
anim->current_frame = (anim->current_frame + 1) % anim->frame_count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||||
|
if (!tilesheet || !tilesheet->animations || tile_id == 0 || tile_id > tilesheet->total_tiles) {
|
||||||
|
return tile_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pxl8_tile_animation* anim = &tilesheet->animations[tile_id];
|
||||||
|
if (!anim->frames || anim->frame_count == 0) return tile_id;
|
||||||
|
|
||||||
|
return anim->frames[anim->current_frame];
|
||||||
|
}
|
||||||
|
|
||||||
|
void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id,
|
||||||
|
const pxl8_tile_properties* props) {
|
||||||
|
if (!tilesheet || !props || tile_id == 0 || tile_id > tilesheet->total_tiles) return;
|
||||||
|
|
||||||
|
if (!tilesheet->properties) {
|
||||||
|
tilesheet->properties = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_tile_properties));
|
||||||
|
if (!tilesheet->properties) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
tilesheet->properties[tile_id] = *props;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tilesheet* tilesheet, u16 tile_id) {
|
||||||
|
if (!tilesheet || !tilesheet->properties || tile_id == 0 || tile_id > tilesheet->total_tiles) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return &tilesheet->properties[tile_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||||
|
u8 neighbor_mask, u16 result_tile_id) {
|
||||||
|
if (!tilesheet || base_tile_id == 0 || base_tile_id > tilesheet->total_tiles) {
|
||||||
|
return PXL8_ERROR_INVALID_ARGUMENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tilesheet->autotile_rules) {
|
||||||
|
tilesheet->autotile_rules = calloc(tilesheet->total_tiles + 1, sizeof(pxl8_autotile_rule*));
|
||||||
|
tilesheet->autotile_rule_counts = calloc(tilesheet->total_tiles + 1, sizeof(u32));
|
||||||
|
if (!tilesheet->autotile_rules || !tilesheet->autotile_rule_counts) {
|
||||||
|
return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 count = tilesheet->autotile_rule_counts[base_tile_id];
|
||||||
|
pxl8_autotile_rule* new_rules = realloc(tilesheet->autotile_rules[base_tile_id],
|
||||||
|
(count + 1) * sizeof(pxl8_autotile_rule));
|
||||||
|
if (!new_rules) return PXL8_ERROR_OUT_OF_MEMORY;
|
||||||
|
|
||||||
|
new_rules[count].neighbor_mask = neighbor_mask;
|
||||||
|
new_rules[count].tile_id = result_tile_id;
|
||||||
|
|
||||||
|
tilesheet->autotile_rules[base_tile_id] = new_rules;
|
||||||
|
tilesheet->autotile_rule_counts[base_tile_id]++;
|
||||||
|
|
||||||
|
return PXL8_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors) {
|
||||||
|
if (!tilesheet || !tilesheet->autotile_rules || base_tile_id == 0 ||
|
||||||
|
base_tile_id > tilesheet->total_tiles) {
|
||||||
|
return base_tile_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
pxl8_autotile_rule* rules = tilesheet->autotile_rules[base_tile_id];
|
||||||
|
u32 rule_count = tilesheet->autotile_rule_counts[base_tile_id];
|
||||||
|
|
||||||
|
for (u32 i = 0; i < rule_count; i++) {
|
||||||
|
if (rules[i].neighbor_mask == neighbors) {
|
||||||
|
return rules[i].tile_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return base_tile_id;
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
#include "pxl8_types.h"
|
#include "pxl8_types.h"
|
||||||
#include "pxl8_gfx.h"
|
#include "pxl8_gfx.h"
|
||||||
|
|
||||||
|
typedef struct pxl8_tile_animation pxl8_tile_animation;
|
||||||
|
typedef struct pxl8_tile_properties pxl8_tile_properties;
|
||||||
|
typedef struct pxl8_autotile_rule pxl8_autotile_rule;
|
||||||
|
|
||||||
typedef struct pxl8_tilesheet {
|
typedef struct pxl8_tilesheet {
|
||||||
u8* data;
|
u8* data;
|
||||||
bool* tile_valid;
|
bool* tile_valid;
|
||||||
|
|
@ -12,6 +16,15 @@ typedef struct pxl8_tilesheet {
|
||||||
u32 tiles_per_row;
|
u32 tiles_per_row;
|
||||||
u32 total_tiles;
|
u32 total_tiles;
|
||||||
pxl8_color_mode color_mode;
|
pxl8_color_mode color_mode;
|
||||||
|
u32 ref_count;
|
||||||
|
|
||||||
|
pxl8_tile_animation* animations;
|
||||||
|
u32 animation_count;
|
||||||
|
|
||||||
|
pxl8_tile_properties* properties;
|
||||||
|
|
||||||
|
pxl8_autotile_rule** autotile_rules;
|
||||||
|
u32* autotile_rule_counts;
|
||||||
} pxl8_tilesheet;
|
} pxl8_tilesheet;
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|
@ -30,6 +43,22 @@ bool pxl8_tilesheet_is_tile_valid(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||||
pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size);
|
pxl8_tilesheet* pxl8_tilesheet_new(u32 tile_size);
|
||||||
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);
|
void pxl8_tilesheet_destroy(pxl8_tilesheet* tilesheet);
|
||||||
|
|
||||||
|
void pxl8_tilesheet_ref(pxl8_tilesheet* tilesheet);
|
||||||
|
void pxl8_tilesheet_unref(pxl8_tilesheet* tilesheet);
|
||||||
|
|
||||||
|
pxl8_result pxl8_tilesheet_add_animation(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||||
|
const u16* frames, u16 frame_count, f32 frame_duration);
|
||||||
|
void pxl8_tilesheet_update_animations(pxl8_tilesheet* tilesheet, f32 delta_time);
|
||||||
|
u16 pxl8_tilesheet_get_animated_frame(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||||
|
|
||||||
|
void pxl8_tilesheet_set_tile_property(pxl8_tilesheet* tilesheet, u16 tile_id,
|
||||||
|
const pxl8_tile_properties* props);
|
||||||
|
const pxl8_tile_properties* pxl8_tilesheet_get_tile_property(const pxl8_tilesheet* tilesheet, u16 tile_id);
|
||||||
|
|
||||||
|
pxl8_result pxl8_tilesheet_add_autotile_rule(pxl8_tilesheet* tilesheet, u16 base_tile_id,
|
||||||
|
u8 neighbor_mask, u16 result_tile_id);
|
||||||
|
u16 pxl8_tilesheet_apply_autotile(const pxl8_tilesheet* tilesheet, u16 base_tile_id, u8 neighbors);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -344,7 +344,9 @@ void pxl8_vfx_fire(pxl8_particle_system* sys, i32 x, i32 y, i32 width, u8 palett
|
||||||
|
|
||||||
static void rain_spawn(pxl8_particle* p, void* userdata) {
|
static void rain_spawn(pxl8_particle* p, void* userdata) {
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
p->color = 27 + (rand() % 3);
|
p->start_color = 27 + (rand() % 3);
|
||||||
|
p->end_color = 29;
|
||||||
|
p->color = p->start_color;
|
||||||
p->max_life = 2.0f;
|
p->max_life = 2.0f;
|
||||||
p->vy = 200.0f + ((f32)rand() / RAND_MAX) * 100.0f;
|
p->vy = 200.0f + ((f32)rand() / RAND_MAX) * 100.0f;
|
||||||
}
|
}
|
||||||
|
|
@ -391,7 +393,9 @@ void pxl8_vfx_smoke(pxl8_particle_system* sys, i32 x, i32 y, u8 color) {
|
||||||
|
|
||||||
static void snow_spawn(pxl8_particle* p, void* userdata) {
|
static void snow_spawn(pxl8_particle* p, void* userdata) {
|
||||||
(void)userdata;
|
(void)userdata;
|
||||||
p->color = 10 + (rand() % 2);
|
p->start_color = 15 + (rand() % 2);
|
||||||
|
p->end_color = 10;
|
||||||
|
p->color = p->start_color;
|
||||||
p->max_life = 4.0f;
|
p->max_life = 4.0f;
|
||||||
p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 20.0f;
|
p->vx = (((f32)rand() / RAND_MAX) - 0.5f) * 20.0f;
|
||||||
p->vy = 30.0f + ((f32)rand() / RAND_MAX) * 20.0f;
|
p->vy = 30.0f + ((f32)rand() / RAND_MAX) * 20.0f;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue