diff options
author | kdx <kikoodx@paranoici.org> | 2023-05-03 01:17:25 +0200 |
---|---|---|
committer | kdx <kikoodx@paranoici.org> | 2023-05-03 01:17:25 +0200 |
commit | ca2f55ac494245422e27fe272dddc5092ce0afc6 (patch) | |
tree | 6c7cd09707ff3b66c8fcb6d545ff6c8a8b87838a | |
download | study-sttky-ca2f55ac494245422e27fe272dddc5092ce0afc6.tar.gz |
base
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Tupfile | 16 | ||||
-rw-r--r-- | Tupfile.ini | 0 | ||||
-rwxr-xr-x | buildwin.sh | 13 | ||||
-rw-r--r-- | compile_flags.txt | 4 | ||||
-rwxr-xr-x | emcc.sh | 7 | ||||
-rw-r--r-- | map/game.tmj | 67 | ||||
-rw-r--r-- | map/tset.tsj | 23 | ||||
-rw-r--r-- | res/tset.png | bin | 0 -> 98 bytes | |||
-rwxr-xr-x | run.sh | 2 | ||||
-rw-r--r-- | src/TZR.c | 992 | ||||
-rw-r--r-- | src/TZR.h | 417 | ||||
-rw-r--r-- | src/camera.c | 48 | ||||
-rw-r--r-- | src/camera.h | 7 | ||||
-rw-r--r-- | src/cfg.h | 7 | ||||
-rw-r--r-- | src/clamp.h | 19 | ||||
-rw-r--r-- | src/entity.c | 174 | ||||
-rw-r--r-- | src/entity.h | 69 | ||||
-rw-r--r-- | src/entityimpl.h | 72 | ||||
-rw-r--r-- | src/entitytag.c | 4 | ||||
-rw-r--r-- | src/entitytag.h | 11 | ||||
-rw-r--r-- | src/game.c | 182 | ||||
-rw-r--r-- | src/game.h | 36 | ||||
-rw-r--r-- | src/main.c | 88 | ||||
-rw-r--r-- | src/map.c | 130 | ||||
-rw-r--r-- | src/map.h | 21 | ||||
-rw-r--r-- | src/player.c | 29 | ||||
-rw-r--r-- | src/px.c | 260 | ||||
-rw-r--r-- | src/px.h | 132 | ||||
-rw-r--r-- | src/tiled2c.h | 61 | ||||
-rw-r--r-- | src/tileset.c | 95 | ||||
-rw-r--r-- | src/tileset.h | 12 |
32 files changed, 3000 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c03b489 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.tup +/build @@ -0,0 +1,16 @@ +CC = gcc +LD = $(CC) -fuse-ld=mold +CFLAGS = -O3 -std=c99 -Wall -Wextra -Wno-override-init \ + -DPX_WIDTH=400 -DPX_HEIGHT=224 +LDFLAGS = -lm -lSDL2 -lSDL2_image -lSDL2_mixer + +# codebase +: foreach map/*.tmj |> tiled2c %f embed_%B_tmj >%o |> build/embed_%B_tmj.c +: foreach map/*.tsj |> tiled2c %f embed_%B_tsj >%o |> build/embed_%B_tsj.c +: foreach src/*.c |> $(CC) $(CFLAGS) -c -o %o %f |> build/%B.o +: foreach build/*.c |> $(CC) $(CFLAGS) -Isrc -c -o %o %f |> build/%B.o +: build/*.o |> $(LD) -o %o %f $(LDFLAGS) |> build/study-sttky + +# assets +: foreach res/*.wav |> cp %f %o |> build/%f +: foreach res/*.png |> convert %f %o |> build/res/%B.bmp diff --git a/Tupfile.ini b/Tupfile.ini new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/Tupfile.ini diff --git a/buildwin.sh b/buildwin.sh new file mode 100755 index 0000000..f0ea51a --- /dev/null +++ b/buildwin.sh @@ -0,0 +1,13 @@ +#!/bin/sh +tup || exit 1 +cd build || exit 1 +rm -rf ld53_win +mkdir -p ld53_win || exit 1 +x86_64-w64-mingw32-gcc \ + -o ld53_win/ld53.exe -I../src -Dmain=SDL_main \ + *.c ../src/*.c \ + -lmingw32 -lSDL2main -lSDL2 -lSDL2_mixer -lSDL2_image -mwindows \ + || exit 1 +cp ~/dll/SDL2*.dll ld53_win || exit 1 +cp -r res ld53_win || exit 1 +zip -9r ld53.zip ld53_win || exit 1 diff --git a/compile_flags.txt b/compile_flags.txt new file mode 100644 index 0000000..f45cb29 --- /dev/null +++ b/compile_flags.txt @@ -0,0 +1,4 @@ +-Wall +-Wextra +-Wno-initializer-overrides +-std=c99 @@ -0,0 +1,7 @@ +#!/bin/sh +tup +cp src/index.html build +cd build +emcc -sUSE_SDL=2 -sUSE_SDL_IMAGE=2 -sUSE_SDL_MIXER=2 -sALLOW_MEMORY_GROWTH \ + -Wno-initializer-overrides -std=c2x -O3 \ + *.c ../src/*.c -I../src -o index.js --preload-file res diff --git a/map/game.tmj b/map/game.tmj new file mode 100644 index 0000000..f4c7dde --- /dev/null +++ b/map/game.tmj @@ -0,0 +1,67 @@ +{ "compressionlevel":-1, + "height":14, + "infinite":false, + "layers":[ + { + "data":[2, 0, 0, 1, 1, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 1, + 2, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 0, 2, 0, 0, 2, 0, 0, 0, 0, 0, 0, 1, 1, 0, + 2, 0, 1, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, + 2, 0, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 2, 2, 0, 2, 0, 0, 1, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 2, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, + 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 1, 2, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 1, 0, 0, 0, 1, 0, 0, 2, 0, 1, 1, 1, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, + 2, 0, 1, 0, 0, 0, 1, 2, 2, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, + 2, 0, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 2, 0, 0, 0, 0, + 2, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 2, 0, 0, 0, + 2, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2, 2, 0, + 1, 1, 1, 2, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 2, + 1, 2, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, + 2, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + "height":14, + "id":1, + "name":"Tile Layer 1", + "opacity":1, + "type":"tilelayer", + "visible":true, + "width":25, + "x":0, + "y":0 + }, + { + "draworder":"topdown", + "id":2, + "name":"Object Layer 1", + "objects":[ + { + "height":16, + "id":1, + "name":"player", + "rotation":0, + "type":"", + "visible":true, + "width":16, + "x":176, + "y":144 + }], + "opacity":1, + "type":"objectgroup", + "visible":true, + "x":0, + "y":0 + }], + "nextlayerid":3, + "nextobjectid":2, + "orientation":"orthogonal", + "renderorder":"right-down", + "tiledversion":"1.10.1", + "tileheight":16, + "tilesets":[ + { + "firstgid":1, + "source":"tset.tsj" + }], + "tilewidth":16, + "type":"map", + "version":"1.10", + "width":25 +}
\ No newline at end of file diff --git a/map/tset.tsj b/map/tset.tsj new file mode 100644 index 0000000..54a7ecf --- /dev/null +++ b/map/tset.tsj @@ -0,0 +1,23 @@ +{ "columns":2, + "image":"..\/res\/tset.png", + "imageheight":16, + "imagewidth":32, + "margin":0, + "name":"tset", + "spacing":0, + "tilecount":2, + "tiledversion":"1.10.1", + "tileheight":16, + "tiles":[ + { + "id":0, + "type":"solid" + }, + { + "id":1, + "type":"spike" + }], + "tilewidth":16, + "type":"tileset", + "version":"1.10" +}
\ No newline at end of file diff --git a/res/tset.png b/res/tset.png Binary files differnew file mode 100644 index 0000000..3cc3164 --- /dev/null +++ b/res/tset.png @@ -0,0 +1,2 @@ +#!/bin/sh +tup && build/study-sttky diff --git a/src/TZR.c b/src/TZR.c new file mode 100644 index 0000000..386e765 --- /dev/null +++ b/src/TZR.c @@ -0,0 +1,992 @@ +/* Licensing information can be found at the end of the file. */ +#include <math.h> +#include <SDL2/SDL_error.h> +#include <SDL2/SDL_events.h> +#include <SDL2/SDL.h> +#include <SDL2/SDL_image.h> +#include <SDL2/SDL_log.h> +#include <SDL2/SDL_mixer.h> +#include <SDL2/SDL_mouse.h> +#include <SDL2/SDL_render.h> +#include <SDL2/SDL_rwops.h> +#include <SDL2/SDL_scancode.h> +#include <SDL2/SDL_timer.h> +#include <stddef.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <sys/stat.h> +#include "TZR.h" +#include <unistd.h> + +/* sources/drain.h */ + +static void *drain_error(void) +{ + perror("TZR_LoadResourceTyped:drain"); + return NULL; +} + +static char *drain(FILE *fp, size_t *size) +{ + if (fseek(fp, 0, SEEK_END) < 0) + return drain_error(); + *size = ftell(fp); + if (fseek(fp, 0, SEEK_SET) < 0) + return drain_error(); + char *data = malloc(*size); + if (data == NULL) + return drain_error(); + if (fread(data, 1, *size, fp) != *size) + return free(data), drain_error(); + return data; +} + +/* sources/sdl_error.h */ + +static int sdl_error(int rv) +{ + SDL_Log("%s\n", SDL_GetError()); + return rv; +} +/* sources/sdl_rect_to_rect.h */ + +static void TZR_RectFromSDLRect(TZR_Rect *dest, const SDL_Rect *src) +{ + dest->from.x = src->x; + dest->from.y = src->y; + dest->to.x = src->x + src->w - 1; + dest->to.y = src->y + src->h - 1; +} +/* sources/globals.c */ + +TZR_Config ___tzr_config = {0}; +TZR_Color ___tzr_color = {0.0f, 0.0f, 0.0f, 0.0f, 1.0f}; +TZR_Resource *___tzr_resources = NULL; +size_t ___tzr_resources_capacity = 0; +size_t ___tzr_resources_size = 0; +SDL_Window *___tzr_window = NULL; +SDL_Renderer *___tzr_renderer = NULL; +SDL_Texture *___tzr_target = NULL; +SDL_Texture *___tzr_target_light = NULL; +unsigned long ___tzr_tick = 0; +unsigned long ___tzr_next_time = 0; +unsigned long ___tzr_min_dt = 0; +int ___tzr_should_quit = 0; +int ___tzr_mouse_x = 0; +int ___tzr_mouse_y = 0; +TZR_KeyState ___tzr_keystates[SDL_NUM_SCANCODES] = {0}; +TZR_KeyState ___tzr_mousestates[256] = {0}; //doc says than mouse button is u8 +float ___tzr_scale = 1.0; +int ___tzr_off_x = 1.0; +int ___tzr_off_y = 1.0; +Mix_Music *___tzr_music = NULL; +/* sources/TZR_CycleEvents.c */ + +void +next_state(TZR_KeyState *keystate) +{ + switch (*keystate) { + case TZR_KEYSTATE_RELEASE: + case TZR_KEYSTATE_UP: + *keystate = TZR_KEYSTATE_UP; + break; + case TZR_KEYSTATE_PRESS: + case TZR_KEYSTATE_DOWN: + *keystate = TZR_KEYSTATE_DOWN; + break; + default: + break; + } +} + +void +TZR_CycleEvents(void) +{ + TZR_ResourcesWatch(); + for (int i = 0; i < SDL_NUM_SCANCODES; i++) + next_state(&___tzr_keystates[i]); + for (int i = 0; i < 256; i++) + next_state(&___tzr_mousestates[i]); + TZR_Event e; + while (TZR_PollEvent(&e)) + ; +} +/* sources/TZR_DestroyResource.c */ + +void +TZR_DestroyResource(TZR_Resource *res, int free_path) +{ + if (res->path != NULL && free_path) + free(res->path); + switch (res->type) { + case TZR_RES_RAW: + if (res->raw.data != NULL) + free(res->raw.data); + break; + case TZR_RES_IMAGE: + if (res->image.ptr != NULL) + SDL_DestroyTexture(res->image.ptr); + break; + case TZR_RES_SOUND: + if (res->sound.ptr != NULL) + Mix_FreeChunk(res->sound.ptr); + break; + default: + fprintf(stderr, "unknown resource type %u\n", res->type); + break; + } +} + +/* sources/TZR_DirectResourceLoad.c */ + +int +TZR_DirectResourceLoad(TZR_Resource *res, const void *data, int size) +{ + switch (res->type) { + case TZR_RES_RAW: + res->raw.size = size; + res->raw.data = malloc(size); + if (res->raw.data == NULL) { + perror("TZR_DirectResourceLoad"); + return -1; + } + memcpy(res->raw.data, data, size); + break; + case TZR_RES_IMAGE: { + SDL_RWops *const rw = SDL_RWFromConstMem(data, size); + if (rw == NULL) + return sdl_error(-1); + SDL_Surface *surf = IMG_Load_RW(rw, 1); + if (surf == NULL) { + SDL_RWops *const rw = SDL_RWFromConstMem(data, size); + if (rw == NULL) + return sdl_error(-1); + surf = SDL_LoadBMP_RW(rw, 1); + } + if (surf == NULL) + return sdl_error(-1); + SDL_Texture *const tex = + SDL_CreateTextureFromSurface(___tzr_renderer, surf); + SDL_FreeSurface(surf); + if (tex == NULL) + return sdl_error(-1); + if (SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_BLEND) < 0) { + SDL_DestroyTexture(tex); + return sdl_error(-1); + } + if (SDL_QueryTexture(tex, NULL, NULL, + &res->image.width, &res->image.height)) { + SDL_DestroyTexture(tex); + return sdl_error(-1); + } + res->image.ptr = tex; + } break; + case TZR_RES_SOUND: { + if (!___tzr_config.mixer) { + SDL_Log("audio mixer disabled, skip loading"); + return -1; + } + SDL_RWops *const rw = SDL_RWFromConstMem(data, size); + if (rw == NULL) + return sdl_error(-1); + Mix_Chunk *const chunk = Mix_LoadWAV_RW(rw, 0); + SDL_RWclose(rw); + if (chunk == NULL) + return sdl_error(-1); + res->sound.ptr = chunk; + } break; + default: + fprintf(stderr, "invalid type\n"); + return -1; + } + return 0; +} +/* sources/TZR_DrawBegin.c */ + +int +TZR_DrawBegin(void) +{ + if (SDL_SetRenderTarget(___tzr_renderer, ___tzr_target) < 0) + return sdl_error(-1); + if (SDL_SetRenderDrawBlendMode(___tzr_renderer, SDL_BLENDMODE_BLEND)) + return sdl_error(-1); + return 0; +} +/* sources/TZR_DrawClear.c */ + +int +TZR_DrawClear(void) +{ + if (SDL_RenderClear(___tzr_renderer) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_DrawEnd.c */ + +int +TZR_DrawEnd(void) +{ + if (SDL_SetRenderTarget(___tzr_renderer, NULL) < 0) + return sdl_error(-1); + if (SDL_SetRenderDrawColor(___tzr_renderer, 0, 0, 0, 255) < 0) + return sdl_error(-1); + if (SDL_RenderClear(___tzr_renderer) < 0) + return sdl_error(-1); + + int win_w, win_h; + SDL_GetWindowSize(___tzr_window, &win_w, &win_h); + win_w = win_w ? win_w : 1; + win_h = win_h ? win_h : 1; + + const float ratio_w = win_w / (float)___tzr_config.width; + const float ratio_h = win_h / (float)___tzr_config.height; + ___tzr_scale = (ratio_w < ratio_h) ? ratio_w : ratio_h; + if (___tzr_scale > 1.0f && ___tzr_config.pixel_perfect) + ___tzr_scale = (int)___tzr_scale; + ___tzr_off_x = (win_w - ___tzr_config.width * ___tzr_scale) / 2; + ___tzr_off_y = (win_h - ___tzr_config.height * ___tzr_scale) / 2; + const SDL_Rect dest = { + ___tzr_off_x, + ___tzr_off_y, + ___tzr_config.width * ___tzr_scale, + ___tzr_config.height * ___tzr_scale + }; + + if (SDL_RenderCopy(___tzr_renderer, ___tzr_target, NULL, &dest) < 0) + return sdl_error(-1); + if (___tzr_config.light_system) { + if (SDL_SetTextureBlendMode(___tzr_target_light, + SDL_BLENDMODE_MOD) < 0) + return sdl_error(-1); + if (SDL_RenderCopy(___tzr_renderer, ___tzr_target_light, NULL, + &dest) < 0) + return sdl_error(-1); + } + + if (___tzr_config.target_fps > 0) { + ___tzr_next_time += ___tzr_min_dt; + const unsigned long cur_time = SDL_GetTicks64(); + if (___tzr_next_time <= cur_time) + ___tzr_next_time = cur_time; + else + SDL_Delay(___tzr_next_time - cur_time); + } + SDL_RenderPresent(___tzr_renderer); + + ___tzr_tick += 1; + return 0; +} +/* sources/TZR_DrawImage.c */ + +int +_TZR_DrawImage(const TZR_DrawImageArgs *args) +{ + if (args->id == 0) { + fprintf(stderr, "args->id is 0\n"); + return -1; + } + + if (TZR_GetResourceType(args->id) != TZR_RES_IMAGE) { + fprintf(stderr, "%u isn't an image\n", args->id); + return -1; + } + TZR_Resource *const res = TZR_GetResourcePointer(args->id); + const TZR_Image *const img = &res->image; + + if (SDL_SetTextureColorMod(img->ptr, ___tzr_color.r * 255, + ___tzr_color.g * 255, ___tzr_color.b * 255)) + return sdl_error(-1); + if (SDL_SetTextureAlphaMod(img->ptr, ___tzr_color.a * 255)) + return sdl_error(-1); + + float scale_x = args->sx; + float scale_y = args->sy; + + int simg_width = (args->w == INT_MIN) ? img->width : args->w; + if (simg_width < 0) { + simg_width *= -1; + scale_x *= -1; + } + if (simg_width + args->ix > img->width) + simg_width = img->width - args->ix; + + int simg_height = (args->h == INT_MIN) ? img->height : args->h; + if (simg_height < 0) { + simg_height *= -1; + scale_y *= -1; + } + if (simg_height + args->iy > img->height) + simg_height = img->height - args->iy; + + int flip = 0; + flip |= SDL_FLIP_HORIZONTAL * ((scale_x < 0.0f) ^ args->flip_x); + flip |= SDL_FLIP_VERTICAL * ((scale_y < 0.0f) ^ args->flip_y); + const SDL_Rect src = { + args->ix, + args->iy, + simg_width, + simg_height + }; + + const int width = simg_width * fabs(scale_x); + const int height = simg_height * fabs(scale_y); + const int x = args->x - (scale_x < 0.0f ? width : 0); + const int y = args->y - (scale_y < 0.0f ? height : 0); + + const SDL_Rect dest = { + x - (args->center ? (simg_width * scale_x / 2) : 0), + y - (args->center ? (simg_height * scale_y / 2) : 0), + width, + height + }; + if (SDL_RenderCopyEx(___tzr_renderer, img->ptr, &src, &dest, + args->r * 360, NULL, flip) < 0) + return sdl_error(-1); + TZR_Rect area; + TZR_RectFromSDLRect(&area, &dest); + return 0; +} +/* sources/TZR_DrawLine.c */ + +int +TZR_DrawLine(int x0, int y0, int x1, int y1) +{ + if (SDL_RenderDrawLine(___tzr_renderer, x0, y0, x1, y1) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_DrawPoint.c */ + +int +TZR_DrawPoint(int x, int y) +{ + if (SDL_RenderDrawPoint(___tzr_renderer, x, y) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_DrawRectangle.c */ + +int +_TZR_DrawRectangle(const TZR_DrawRectangleArgs *args) +{ + const SDL_Rect rect = { + args->x - args->center * (args->w / 2), + args->y - args->center * (args->h / 2), + args->w, + args->h + }; + if (args->fill) { + if (SDL_RenderFillRect(___tzr_renderer, &rect) < 0) + return sdl_error(-1); + } else { + if (SDL_RenderDrawRect(___tzr_renderer, &rect) < 0) + return sdl_error(-1); + } + return 0; +} +/* sources/TZR_DrawSetColor8.c */ + +int +TZR_DrawSetColor8(uint8_t r, uint8_t g, uint8_t b, uint8_t a) +{ + ___tzr_color.r = (float)r / 255.0f; + ___tzr_color.g = (float)g / 255.0f; + ___tzr_color.b = (float)b / 255.0f; + ___tzr_color.a = (float)a / 255.0f; + if (SDL_SetRenderDrawColor(___tzr_renderer, r, g, b, a) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_DrawSetColor.c */ + +int +_TZR_DrawSetColor(const TZR_Color *color) +{ + ___tzr_color.r = (color->r < 0.0f) ? ___tzr_color.r : color->r; + ___tzr_color.g = (color->g < 0.0f) ? ___tzr_color.g : color->g; + ___tzr_color.b = (color->b < 0.0f) ? ___tzr_color.b : color->b; + ___tzr_color.a = (color->a < 0.0f) ? ___tzr_color.a : color->a; + const uint8_t r = ___tzr_color.r * 255; + const uint8_t g = ___tzr_color.g * 255; + const uint8_t b = ___tzr_color.b * 255; + const uint8_t a = ___tzr_color.a * 255; + if (SDL_SetRenderDrawColor(___tzr_renderer, r, g, b, a) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_GetImageHeight.c */ + +int +TZR_GetImageHeight(TZR_Uint id) +{ + return TZR_GetResourcePointer(id)->image.height; +} +/* sources/TZR_GetImageWidth.c */ + +int +TZR_GetImageWidth(TZR_Uint id) +{ + return TZR_GetResourcePointer(id)->image.width; +} +/* sources/TZR_GetKeyState.c */ + +TZR_KeyState +TZR_GetKeyState(int scancode) +{ + return ___tzr_keystates[scancode]; +} +/* sources/TZR_GetMousePosition.c */ + +void +TZR_GetMousePosition(int *x, int *y) +{ + if (x != NULL) + *x = ___tzr_mouse_x; + if (y != NULL) + *y = ___tzr_mouse_y; + TZR_ScreenTransform(x, y); +} +/* sources/TZR_GetRawResource.c */ + +TZR_Raw * +TZR_GetRawResource(TZR_Uint id) +{ + return &TZR_GetResourcePointer(id)->raw; +} +/* sources/TZR_GetResourcePath.c */ + +const char * +TZR_GetResourcePath(TZR_Uint id) +{ + return TZR_GetResourcePointer(id)->path; +} +/* sources/TZR_GetResourcePointer.c */ + +TZR_Resource * +TZR_GetResourcePointer(TZR_Uint id) +{ + return &___tzr_resources[id - 1]; +} +/* sources/TZR_GetResourceType.c */ + +TZR_ResourceType +TZR_GetResourceType(TZR_Uint id) +{ + return TZR_GetResourcePointer(id)->type; +} +/* sources/TZR_GetTick.c */ + +unsigned long +TZR_GetTick(void) +{ + return ___tzr_tick; +} +/* sources/TZR_Init.c */ + +static int +_sdl_error(void) +{ + sdl_error(-1); + TZR_Quit(); + return -1; +} + +int +_TZR_Init(const TZR_Config *config) +{ + memcpy(&___tzr_config, config, sizeof(TZR_Config)); + + if (SDL_Init(SDL_INIT_VIDEO) < 0) + return _sdl_error(); + if (IMG_Init(IMG_INIT_PNG * ___tzr_config.png_loading) < 0) + return _sdl_error(); + if (___tzr_config.mixer == TZR_MIXER_FLAC && + Mix_Init(MIX_INIT_FLAC) != MIX_INIT_FLAC) + { + SDL_Log("%s", Mix_GetError()); + ___tzr_config.mixer = TZR_MIXER_OFF; + } + + char *const basepath = SDL_GetBasePath(); + if (basepath == NULL) + return _sdl_error(); + const int chdir_rv = chdir(basepath); + SDL_free(basepath); + if (chdir_rv < 0) + return perror("TZR_Init"), -1; + + ___tzr_window = + SDL_CreateWindow("TZR", + SDL_WINDOWPOS_UNDEFINED, + SDL_WINDOWPOS_UNDEFINED, + ___tzr_config.width * ___tzr_config.scale, + ___tzr_config.height * ___tzr_config.scale, + SDL_WINDOW_RESIZABLE); + if (___tzr_window == NULL) + return _sdl_error(); + +#ifdef __EMSCRIPTEN__ + ___tzr_renderer = SDL_CreateRenderer(___tzr_window, -1, + SDL_RENDERER_SOFTWARE); +#else + ___tzr_renderer = SDL_CreateRenderer(___tzr_window, -1, + SDL_RENDERER_ACCELERATED); +#endif + if (___tzr_renderer == NULL) + return _sdl_error(); + + ___tzr_target = SDL_CreateTexture(___tzr_renderer, + SDL_PIXELFORMAT_RGB888, + SDL_TEXTUREACCESS_TARGET, + ___tzr_config.width, + ___tzr_config.height); + if (___tzr_target == NULL) + return _sdl_error(); + + if (___tzr_config.light_system) { + ___tzr_target_light = SDL_CreateTexture(___tzr_renderer, + SDL_PIXELFORMAT_RGB888, + SDL_TEXTUREACCESS_TARGET, + ___tzr_config.width, + ___tzr_config.height); + if (___tzr_target_light == NULL) + return _sdl_error(); + } + + SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0); + ___tzr_tick = 0; + + if (___tzr_config.target_fps > 0) { + ___tzr_min_dt = 1000 / ___tzr_config.target_fps; + ___tzr_next_time = SDL_GetTicks64(); + } + + if (!___tzr_config.show_cursor) + SDL_ShowCursor(SDL_FALSE); + ___tzr_mouse_x = ___tzr_config.width / 2; + ___tzr_mouse_y = ___tzr_config.height / 2; + + /* Setup audio. */ + if (___tzr_config.mixer) { + if (Mix_OpenAudio(48000, MIX_DEFAULT_FORMAT, 8, 1024) < 0) { + sdl_error(0); + Mix_Quit(); + ___tzr_config.mixer = false; + } + } + return 0; +} +/* sources/TZR_IsKeyDown.c */ + +bool +TZR_IsKeyDown(int scancode) +{ + const TZR_KeyState state = TZR_GetKeyState(scancode); + return (state == TZR_KEYSTATE_DOWN || state == TZR_KEYSTATE_PRESS); +} +/* sources/TZR_IsKeyPressed.c */ + +bool +TZR_IsKeyPressed(int scancode) +{ + return (TZR_GetKeyState(scancode) == TZR_KEYSTATE_PRESS); +} +/* sources/TZR_IsKeyReleased.c */ + +bool +TZR_IsKeyReleased(int scancode) +{ + if (TZR_GetKeyState(scancode) == TZR_KEYSTATE_RELEASE) + return true; + return false; +} +/* sources/TZR_IsKeyUp.c */ + +bool +TZR_IsKeyUp(int scancode) +{ + const TZR_KeyState state = TZR_GetKeyState(scancode); + return (state == TZR_KEYSTATE_UP || state == TZR_KEYSTATE_RELEASE); +} +/* sources/TZR_LightBegin.c */ + +int +TZR_LightBegin(void) +{ + if (!___tzr_config.light_system) { + fprintf(stderr, "light system disabled\n"); + return -1; + } + if (SDL_SetRenderDrawBlendMode(___tzr_renderer, SDL_BLENDMODE_ADD) < 0) + return sdl_error(-1); + if (SDL_SetRenderTarget(___tzr_renderer, ___tzr_target_light) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_LightEnd.c */ + +int +TZR_LightEnd(void) +{ + return ___tzr_config.light_system ? 0 : -1; +} +/* sources/TZR_LoadResource.c */ + +static TZR_ResourceType +deduce_type(const char *path) +{ + const char *const path_extension = strrchr(path, '.'); + if (path_extension == NULL) + return TZR_RES_RAW; + if (strcasecmp(path_extension, ".bmp") == 0 || + strcasecmp(path_extension, ".png") == 0 || + strcasecmp(path_extension, ".qoi") == 0) + return TZR_RES_IMAGE; + if (strcasecmp(path_extension, ".wav") == 0) + return TZR_RES_SOUND; + return TZR_RES_RAW; +} + +TZR_Uint +TZR_LoadResource(const char *path) +{ + return TZR_LoadResourceTyped(deduce_type(path), path); +} +/* sources/TZR_LoadResourceFromMemory.c */ + +static int +reserve_ressources(size_t size) +{ + if (size <= ___tzr_resources_capacity) + return 0; + size_t target = 16; + while (target < size * sizeof(TZR_Resource)) + target *= 2; + TZR_Resource *new_resources; + if (___tzr_resources == NULL) + new_resources = malloc(target); + else + new_resources = realloc(___tzr_resources, target); + if (new_resources == NULL) { + perror("TZR_LoadResourceFromMemory:reserve_ressources"); + return 1; + } + ___tzr_resources = new_resources; + return 0; +} + +TZR_Uint +TZR_LoadResourceFromMemory(TZR_ResourceType type, const void *data, int size) +{ + if (reserve_ressources(___tzr_resources_size + 1)) { + fprintf(stderr, "failed to reserve for new ressource\n"); + return 0; + } + TZR_Resource *const res = &___tzr_resources[___tzr_resources_size]; + res->type = type; + res->path = NULL; + if (TZR_DirectResourceLoad(res, data, size)) + return 0; + ___tzr_resources_size += 1; + return ___tzr_resources_size; +} +/* sources/TZR_LoadResourceTyped.c */ + +TZR_Uint +TZR_LoadResourceTyped(TZR_ResourceType type, const char *path) +{ + for (TZR_Uint i = 0; i < ___tzr_resources_size; i++) + if (___tzr_resources[i].type == type && + strcmp(___tzr_resources[i].path, path) == 0) + return i + 1; + FILE *const fp = fopen(path, "rb"); + if (fp == NULL) { + perror(path); + return 0; + } + size_t size; + char *const data = drain(fp, &size); + fclose(fp); + if (data == NULL) + return 0; + const TZR_Uint id = TZR_LoadResourceFromMemory(type, data, size); + free(data); + if (id == 0) + return 0; + TZR_Resource *const res = TZR_GetResourcePointer(id); + + const size_t path_len = strlen(path); + struct stat st = {0}; + (void)stat(path, &st); + res->path = malloc(path_len + 1); + res->mtime = 0; + if (res->path == NULL) + perror("TZR_LoadResourceTyped"); + else + strcpy(res->path, path); + return id; +} +/* sources/TZR_MainLoop.c */ + +#ifdef __EMSCRIPTEN__ + +static int (*_main_loop)(void *udata); +static void *_udata; +static void +_em_loop(void) +{ + TZR_CycleEvents(); + _main_loop(_udata); +} + +int +TZR_MainLoop(int (*main_loop)(void *udata), void *udata) +{ + _main_loop = main_loop; + _udata = udata; + emscripten_set_main_loop(_em_loop, 0, 1); + return 0; +} + +#else + +int +TZR_MainLoop(int (*main_loop)(void *udata), void *udata) +{ + while (!TZR_ShouldQuit()) { + TZR_CycleEvents(); + if (main_loop(udata)) + return 1; + } + return 0; +} + +#endif +/* sources/TZR_PlayMusic.c */ + +int +TZR_PlayMusic(const char *path, int loop) +{ + if (!___tzr_config.mixer) { + fprintf(stderr, "mixer is disabled\n"); + return -1; + } + TZR_StopMusic(); + ___tzr_music = Mix_LoadMUS(path); + if (___tzr_music == NULL) + return sdl_error(-1); + if (Mix_PlayMusic(___tzr_music, loop) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_PlaySound.c */ + +int +_TZR_PlaySound(const TZR_PlaySoundArgs *args) +{ + if (args->id == 0) { + fprintf(stderr, "args->id is 0\n"); + return -1; + } + + if (TZR_GetResourceType(args->id) != TZR_RES_SOUND) { + fprintf(stderr, "%u isn't a sound\n", args->id); + return -1; + } + + TZR_Sound *const sound = &TZR_GetResourcePointer(args->id)->sound; + Mix_VolumeChunk(sound->ptr, args->volume * MIX_MAX_VOLUME); + if (Mix_PlayChannel(-1, sound->ptr, args->loop) < 0) + return sdl_error(-1); + return 0; +} +/* sources/TZR_PollEvent.c */ + +int +TZR_PollEvent(TZR_Event *e) +{ + TZR_Event discard; + if (e == NULL) + e = &discard; + + SDL_Event se; + while (SDL_PollEvent(&se)) switch (se.type) { + case SDL_QUIT: + e->type = TZR_EV_QUIT; + ___tzr_should_quit = 1; + return 1; + case SDL_KEYDOWN: + if (se.key.repeat) + break; + e->type = TZR_EV_KEYDOWN; + e->button = se.key.keysym.scancode; + ___tzr_keystates[e->button] = TZR_KEYSTATE_PRESS; + break; + case SDL_KEYUP: + e->type = TZR_EV_KEYUP; + e->button = se.key.keysym.scancode; + ___tzr_keystates[e->button] = TZR_KEYSTATE_RELEASE; + break; + case SDL_MOUSEBUTTONDOWN: + ___tzr_mouse_x = se.button.x; + ___tzr_mouse_y = se.button.y; + ___tzr_mousestates[se.button.button] = TZR_KEYSTATE_PRESS; + break; + case SDL_MOUSEBUTTONUP: + ___tzr_mouse_x = se.button.x; + ___tzr_mouse_y = se.button.y; + ___tzr_mousestates[se.button.button] = TZR_KEYSTATE_RELEASE; + break; + case SDL_MOUSEMOTION: + ___tzr_mouse_x = se.motion.x; + ___tzr_mouse_y = se.motion.y; + break; + default: + break; + } + return 0; +} +/* sources/TZR_Quit.c */ + +void +TZR_Quit(void) +{ + for (size_t i = 0; i < ___tzr_resources_size; i++) + TZR_DestroyResource(&___tzr_resources[i], 1); + if (___tzr_resources != NULL) + free(___tzr_resources); + ___tzr_resources = NULL; + ___tzr_resources_size = 0; + ___tzr_resources_capacity = 0; + if (___tzr_target_light != NULL) { + SDL_DestroyTexture(___tzr_target_light); + ___tzr_target_light = NULL; + } + if (___tzr_target != NULL) { + SDL_DestroyTexture(___tzr_target); + ___tzr_target = NULL; + } + if (___tzr_renderer != NULL) { + SDL_DestroyRenderer(___tzr_renderer); + ___tzr_renderer = NULL; + } + if (___tzr_window != NULL) { + SDL_DestroyWindow(___tzr_window); + ___tzr_window = NULL; + } + if (___tzr_config.mixer) { + Mix_CloseAudio(); + Mix_Quit(); + } + IMG_Quit(); + SDL_Quit(); +} +/* sources/TZR_ReloadResource.c */ + +int +TZR_ReloadResource(TZR_Uint id) +{ + TZR_Resource *const res = TZR_GetResourcePointer(id); + if (res->path == NULL) { + fprintf(stderr, "resource path of %u is NULL\n", id); + return -1; + } + FILE *const fp = fopen(res->path, "rb"); + if (fp == NULL) { + perror(res->path); + return -1; + } + size_t size; + char *const data = drain(fp, &size); + fclose(fp); + if (data == NULL) + return -1; + TZR_Resource new_res; + memcpy(&new_res, res, sizeof(TZR_Resource)); + const int load_rv = TZR_DirectResourceLoad(&new_res, data, size); + free(data); + if (load_rv) + return -1; + TZR_DestroyResource(res, 0); + memcpy(res, &new_res, sizeof(TZR_Resource)); + return 0; +} +/* sources/TZR_ResourcesWatch.c */ + +void +TZR_ResourcesWatch(void) +{ + for (TZR_Uint i = 0; i < ___tzr_resources_size; i++) { + TZR_Resource *const res = TZR_GetResourcePointer(i + 1); + if (res->path == NULL) + continue; + struct stat st = {0}; + (void)stat(res->path, &st); + if (st.st_mtime != res->mtime) { + if (res->mtime != 0) + TZR_ReloadResource(i + 1); + res->mtime = st.st_mtime; + } + } +} +/* sources/TZR_ScreenTransform.c */ + +void +TZR_ScreenTransform(int *x, int *y) +{ + if (___tzr_scale == 0.0) + return; + if (x != NULL) { + *x -= ___tzr_off_x; + *x /= ___tzr_scale; + } + if (y != NULL) { + *y -= ___tzr_off_y; + *y /= ___tzr_scale; + } +} +/* sources/TZR_ShouldQuit.c */ + +int +TZR_ShouldQuit(void) +{ + return ___tzr_should_quit; +} +/* sources/TZR_StopMusic.c */ + +void +TZR_StopMusic(void) +{ + if (!___tzr_config.mixer) + return; + if (Mix_PlayingMusic()) + Mix_HaltMusic(); + if (___tzr_music != NULL) + Mix_FreeMusic(___tzr_music); + ___tzr_music = NULL; +} + +/* +** Copyright (c) 2023 kdx +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to +** deal in the Software without restriction, including without limitation the +** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +** sell copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +** IN THE SOFTWARE. +*/ +/* commit hash: a4112e4311eb226c4f43cb697aa7f6c34bd7b9a5 */ diff --git a/src/TZR.h b/src/TZR.h new file mode 100644 index 0000000..9fcf126 --- /dev/null +++ b/src/TZR.h @@ -0,0 +1,417 @@ +/* Licensing information can be found at the end of the file. */ +#pragma once +#include <limits.h> +#include <SDL2/SDL_main.h> +#include <SDL2/SDL_mixer.h> +#include <SDL2/SDL_render.h> +#include <SDL2/SDL_scancode.h> +#include <stdbool.h> +#include <stddef.h> +#ifdef __EMSCRIPTEN__ +#include <emscripten.h> +#endif + +/* headers/TZR_types.h */ + +enum TZR_ResourceType { + TZR_RES_RAW, + TZR_RES_IMAGE, + TZR_RES_SOUND, + ___TZR_RES_COUNT +}; + +enum TZR_EventType { + TZR_EV_QUIT, + TZR_EV_KEYDOWN, + TZR_EV_KEYUP +}; + +enum TZR_KeyState { + TZR_KEYSTATE_UP, + TZR_KEYSTATE_DOWN, + TZR_KEYSTATE_RELEASE, + TZR_KEYSTATE_PRESS +}; + +enum TZR_MixerFlags { + TZR_MIXER_OFF, + TZR_MIXER_ON, + TZR_MIXER_FLAC +}; + +typedef unsigned int TZR_Uint; +typedef struct TZR_Config TZR_Config; +typedef struct TZR_Point TZR_Point; +typedef struct TZR_Color TZR_Color; +typedef struct TZR_Rect TZR_Rect; +typedef enum TZR_ResourceType TZR_ResourceType; +typedef struct TZR_Raw TZR_Raw; +typedef struct TZR_Image TZR_Image; +typedef struct TZR_Sound TZR_Sound; +typedef struct TZR_Resource TZR_Resource; +typedef enum TZR_EventType TZR_EventType; +typedef struct TZR_Event TZR_Event; +typedef struct TZR_Music TZR_Music; +typedef struct TZR_DrawImageArgs TZR_DrawImageArgs; +typedef struct TZR_DrawRectangleArgs TZR_DrawRectangleArgs; +typedef struct TZR_PlaySoundArgs TZR_PlaySoundArgs; +typedef enum TZR_KeyState TZR_KeyState; + +struct TZR_Config { + int _; + int width; + int height; + int scale; + int target_fps; + bool pixel_perfect; + bool show_cursor; + unsigned int mixer; + bool png_loading; + bool light_system; + const char *title; +}; + +struct TZR_Point { + int x; + int y; +}; + +struct TZR_Color { + int _; + float r; + float g; + float b; + float a; +}; + +struct TZR_Rect { + TZR_Point from; + TZR_Point to; +}; + +struct TZR_Raw { + void *data; + size_t size; +}; + +struct TZR_Image { + int width; + int height; + SDL_Texture *ptr; +}; + +struct TZR_Sound { + Mix_Chunk *ptr; +}; + +struct TZR_Resource { + TZR_ResourceType type; + char *path; /* allocated and freed by TZR; can be NULL */ + long mtime; + union { + TZR_Raw raw; /* raw file data */ + TZR_Image image; + TZR_Sound sound; + }; +}; + +struct TZR_Event { + TZR_EventType type; + int button; +}; + +struct TZR_DrawImageArgs { + int _; + TZR_Uint id; + int x; + int y; + int ix; + int iy; + int w; + int h; + float r; + float sx; + float sy; + bool center; + bool flip_x; + bool flip_y; +}; + +struct TZR_DrawRectangleArgs { + int _; + int x; + int y; + int w; + int h; + bool fill; + bool center; +}; + +struct TZR_PlaySoundArgs { + int _; + int id; + int loop; + float volume; +}; +/* headers/TZR_globals.h */ + +extern TZR_Config ___tzr_config; +extern TZR_Color ___tzr_color; +extern TZR_Resource *___tzr_resources; +extern size_t ___tzr_resources_capacity; +extern size_t ___tzr_resources_size; +extern SDL_Window *___tzr_window; +extern SDL_Renderer *___tzr_renderer; +extern SDL_Texture *___tzr_target; +extern SDL_Texture *___tzr_target_light; +extern unsigned long ___tzr_tick; +extern unsigned long ___tzr_next_time; +extern unsigned long ___tzr_min_dt; +extern int ___tzr_should_quit; +extern int ___tzr_mouse_x; +extern int ___tzr_mouse_y; +extern TZR_KeyState ___tzr_keystates[SDL_NUM_SCANCODES]; +extern TZR_KeyState ___tzr_mousestates[256]; +extern float ___tzr_scale; +extern int ___tzr_off_x; +extern int ___tzr_off_y; +extern Mix_Music *___tzr_music; +/* headers/TZR_resource.h */ + +#define TZR_RES TZR_LoadResource + +/* Return 0 on error. */ +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +TZR_Uint TZR_LoadResourceFromMemory(TZR_ResourceType type, const void *data, int size); +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +TZR_Uint TZR_LoadResourceTyped(TZR_ResourceType type, const char *path); + +/* Return 0 on error; try to autodetect resource type from file extension. */ +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +TZR_Uint TZR_LoadResource(const char *path); + +/* Doesn't handle invalid ID. Pointer might get invalidated by resource + * creation calls, use with caution. */ +TZR_Resource *TZR_GetResourcePointer(TZR_Uint id); + +/* Doesn't handle invalid ID. Pointer might get invalidated by resource + * creation calls, use with caution. */ +TZR_Raw *TZR_GetRawResource(TZR_Uint id); + +/* Doesn't handle invalid ID. */ +TZR_ResourceType TZR_GetResourceType(TZR_Uint id); + +/* Doesn't handle invalid ID. */ +const char *TZR_GetResourcePath(TZR_Uint id); + +/* Doesn't handle invalid ID. */ +int TZR_GetImageWidth(TZR_Uint id); + +/* Doesn't handle invalid ID. */ +int TZR_GetImageHeight(TZR_Uint id); + +/* Reload ressource. Returns -1 on error and doesn't apply changes. */ +int TZR_ReloadResource(TZR_Uint id); + +/* Watch for changes on managed resources and hotreload if appropriate. */ +void TZR_ResourcesWatch(void); + +/* Used internaly. */ +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int TZR_DirectResourceLoad(TZR_Resource *res, const void *data, int size); +void TZR_DestroyResource(TZR_Resource *res, int free_path); +/* headers/TZR_sound.h */ + +#define TZR_PlaySound(...) _TZR_PlaySound(&(const TZR_PlaySoundArgs){ \ + .id=0, .loop=0, .volume=1.0f, ._=0, __VA_ARGS__ }) +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int _TZR_PlaySound(const TZR_PlaySoundArgs *args); + +/* Return -1 on error. */ +int TZR_PlayMusic(const char *path, int loop); + +void TZR_StopMusic(void); +/* headers/TZR_events.h */ + +/* Write event data to `e`. Return 0 when event queue is empty. */ +int TZR_PollEvent(TZR_Event *e); + +/* Drain queued events with TZR_PollEvent and call TZR_ResourcesWatch. */ +void TZR_CycleEvents(void); + +void TZR_GetMousePosition(int *x, int *y); +/* headers/TZR_render.h */ + +/* All draw calls should happen between TZR_DrawBegin and TZR_DrawEnd. + * Return -1 on error. */ +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int TZR_DrawBegin(void); + +/* Return -1 on error. */ +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int TZR_DrawEnd(void); + +/* Return -1 on error. */ +#define TZR_DrawSetColor(...) _TZR_DrawSetColor(&(const TZR_Color){ \ + .r=-1.0f, .g=-1.0f, .b=-1.0f, .a=-1.0f, ._=0, __VA_ARGS__ }) +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int _TZR_DrawSetColor(const TZR_Color *color); + +/* Return -1 on error. */ +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int TZR_DrawSetColor8(uint8_t r, uint8_t g, uint8_t b, uint8_t a); + +/* Return -1 on error. */ +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int TZR_DrawClear(void); + +/* Return -1 on error. Draw point on `x`;`y` in the framebuffer. */ +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int TZR_DrawPoint(int x, int y); + +/* Return -1 on error. Draw line between `x0`;`y0` and `x1`;`y1` in the + * framebuffer. */ +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int TZR_DrawLine(int x0, int y0, int x1, int y1); + +/* Return -1 on error. Draw rectangle at `x`;`y` position of size `w`x`h` in the + * framebuffer. */ +#define TZR_DrawRectangle(...) _TZR_DrawRectangle( \ + &(const TZR_DrawRectangleArgs){ \ + .x=0, .y=0, .w=0, .h=0, .fill=false, .center=false, ._=0, __VA_ARGS__ }) +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int _TZR_DrawRectangle(const TZR_DrawRectangleArgs *args); + +/* Return -1 on error. Draw texture ressource `id` at `x`;`y` position of + * the framebuffer. */ +#define TZR_DrawImage(...) _TZR_DrawImage(&(const TZR_DrawImageArgs){ \ + .x=0, .y=0, .ix=0, .iy=0, .w=INT_MIN, .h=INT_MIN, .r=0.0f, .sx=1.0f, \ + .sy=1.0f, .center=false, .flip_x=false, .flip_y=false, ._=0, \ + __VA_ARGS__ }) +#ifdef TZR_PARANOID +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +#endif +int _TZR_DrawImage(const TZR_DrawImageArgs *args); + +void TZR_ScreenTransform(int *x, int *y); +/* headers/TZR_light.h */ + +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int TZR_LightBegin(void); + +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int TZR_LightEnd(void); +/* headers/TZR_keystate.h */ + +TZR_KeyState TZR_GetKeyState(int scancode); +bool TZR_IsKeyDown(int scancode); +bool TZR_IsKeyUp(int scancode); +bool TZR_IsKeyReleased(int scancode); +bool TZR_IsKeyPressed(int scancode); +/* headers/TZR.h */ + +/* TZR manages all loaded resources internally and tries to avoid duplicate. + * A resource can be loaded multiple times if asked, identification doesn't rely + * on filepath. In doubt, use TZR_LoadResource ONCE per asset and store the ID. + * Resources are exposed as ID instead of pointer, as a future proof measure in + * case TZR needs to rearrange memory at runtime in the future. */ + +#define TZR_Init(...) _TZR_Init(&(const TZR_Config){ \ + .width=256, \ + .height=224, \ + .scale=2, \ + .target_fps=60, \ + .pixel_perfect=true, \ + .show_cursor=false, \ + .mixer=TZR_MIXER_ON, \ + .png_loading=true, \ + .light_system=false, \ + .title="TZR", \ + ._=0, __VA_ARGS__ }) + +/* Should be called only once before usage of any other function unless + * otherwise specified. On error this calls TZR_Quit and returns -1. + * `config` will be duplicated internally and can be safely discarded. */ +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int _TZR_Init(const TZR_Config *config); + +/* Destroy and free everything created by TZR. */ +void TZR_Quit(void); + +/* Return non-zero value when a quit event has been received. */ +int TZR_ShouldQuit(void); + +int TZR_MainLoop(int (*main_loop)(void *udata), void *udata); + +unsigned long TZR_GetTick(void); + +/* +** Copyright (c) 2023 kdx +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to +** deal in the Software without restriction, including without limitation the +** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +** sell copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +** IN THE SOFTWARE. +*/ +/* commit hash: a4112e4311eb226c4f43cb697aa7f6c34bd7b9a5 */ diff --git a/src/camera.c b/src/camera.c new file mode 100644 index 0000000..9dd95da --- /dev/null +++ b/src/camera.c @@ -0,0 +1,48 @@ +#include "camera.h" +#include "cfg.h" +#include "clamp.h" +#include "map.h" +#include <stdlib.h> + +static double cam[2] = {0, 0}; +static double off[2] = {0, 0}; + +int shake = 0; + +void +camera_init(void) +{ + cam[0] = 0.0; + cam[1] = 0.0; +} + +void +camera_teleport(int x, int y) +{ + cam[0] = x; + cam[1] = y; +} + +void +camera_update(double dest[2]) +{ + for (int i = 0; i < 2; i++) + cam[i] += 0.1 * (dest[i] - cam[i]); + cam[0] = clamp(DWIDTH/2.0, map_width() * TSIZE - DWIDTH/2.0, cam[0]); + cam[1] = clamp(DHEIGHT/2.0, map_height() * TSIZE - DHEIGHT/2.0, cam[1]); + off[0] = shake ? (2 - rand() % 5) : 0; + off[1] = shake ? (2 - rand() % 5) : 0; + shake -= (shake > 0); +} + +int +camera_x(double scale) +{ + return -cam[0] * scale + DWIDTH / 2.0 + off[0]; +} + +int +camera_y(double scale) +{ + return -cam[1] * scale + DHEIGHT / 2.0 + off[1]; +} diff --git a/src/camera.h b/src/camera.h new file mode 100644 index 0000000..8c572af --- /dev/null +++ b/src/camera.h @@ -0,0 +1,7 @@ +#pragma once + +void camera_init(void); +void camera_teleport(int x, int y); +void camera_update(double dest[2]); +int camera_x(double scale); +int camera_y(double scale); diff --git a/src/cfg.h b/src/cfg.h new file mode 100644 index 0000000..97d457b --- /dev/null +++ b/src/cfg.h @@ -0,0 +1,7 @@ +#pragma once +#include "px.h" + +#define TARGET_FPS 30 +#define TSIZE 16 +#define DWIDTH PX_WIDTH +#define DHEIGHT PX_HEIGHT diff --git a/src/clamp.h b/src/clamp.h new file mode 100644 index 0000000..518a237 --- /dev/null +++ b/src/clamp.h @@ -0,0 +1,19 @@ +#pragma once + +static double +dmin(double a, double b) +{ + return (a < b) ? a : b; +} + +static double +dmax(double a, double b) +{ + return (a > b) ? a : b; +} + +static double +clamp(double min, double max, double v) +{ + return dmax(min, dmin(max, v)); +} diff --git a/src/entity.c b/src/entity.c new file mode 100644 index 0000000..c3d65b7 --- /dev/null +++ b/src/entity.c @@ -0,0 +1,174 @@ +#include "entity.h" +#include "entitytag.h" +#include "map.h" +#include "game.h" +#include "tileset.h" +#include "cfg.h" +#include <stdio.h> +#include <strings.h> + +static void +_points(Entity *this, int ox, int oy, int *x0, int *x1, int *y0, int *y1) +{ + *x0 = this->pos[0] - this->width / 2 + ox; + *y0 = this->pos[1] - this->height / 2 + oy; + *x1 = *x0 + this->width - 1; + *y1 = *y0 + this->height - 1; +} + +unsigned int +entity_type(const char *typename) +{ + if (typename == NULL) + return 0; + for (unsigned i = 0; i < num_entitytags; i++) + if (strcasecmp(typename, entitytags[i].name) == 0) + return i + 1; + printf("unknown type '%s'\n", typename); + return 0; +} + +unsigned int +entity_type_parent(unsigned int type) +{ + if (type == 0) + return 0; + if (entitytags[type - 1].parent != NULL) + return entity_type(entitytags[type - 1].parent); + return 0; +} + +bool +entity_is_type(unsigned int type, unsigned int search) +{ + do { + if (type == search) + return true; + type = entity_type_parent(type); + } while (type != 0); + return false; +} + +static bool +_entity_collide(Entity *this, int ox, int oy, unsigned int type) +{ + int x0, y0, x1, y1; + _points(this, ox, oy, &x0, &x1, &y0, &y1); + for (int y = y0; y <= y1; y++) + for (int x = x0; x <= x1; x++) + if (map_get_px(x, y, type)) + return true; + return false; +} + +bool +entity_collide(Entity *this, int ox, int oy) +{ + return _entity_collide(this, ox, oy, TILE_SOLID); +} + +static bool +collide_smol_spike(Entity *this, int ox, int oy) +{ + const int width = this->width; + const int height = this->height; + this->width = 2; + this->height = 2; + const bool collided = _entity_collide(this, ox, oy, TILE_SMOLSPIKE); + this->width = width; + this->height = height; + return collided; +} + +bool +entity_collide_spike(Entity *this, int ox, int oy) +{ + return (_entity_collide(this, ox, oy, TILE_SPIKE) || + collide_smol_spike(this, ox, oy)); +} + +bool +entity_oob(Entity *this) +{ + return (this->pos[0] + this->width / 2 <= 0 || + this->pos[1] + this->height / 2 <= 0 || + this->pos[0] - this->width / 2 >= map_width() * TSIZE || + this->pos[1] - this->height / 2 >= map_height() * TSIZE); +} + +bool +entity_meet(Entity *this, Entity *other) +{ + int tx0, ty0, tx1, ty1; + int ox0, oy0, ox1, oy1; + _points(this, 0, 0, &tx0, &tx1, &ty0, &ty1); + _points(other, 0, 0, &ox0, &ox1, &oy0, &oy1); + return (((tx0 < ox1 && tx1 > ox0) || (tx0 > ox1 && tx1 < ox0)) && + ((ty0 < oy1 && ty1 > oy0) || (ty0 > oy1 && ty1 < oy0))); +} + +Entity * +entity_place_meeting(Entity *this, struct Game *g, unsigned int type) +{ + for (int i = MAX_ENTITIES - 1; i >= 0; i--) + if (this != &g->entities[i] && + entity_is_type(g->entities[i].type, type) && + entity_meet(this, &g->entities[i])) + return &g->entities[i]; + return NULL; +} + +#define MOVE_COLLIDE (entity_collide(this, 0, 0) || \ + (this->solid_spikes && entity_collide_spike(this, 0, 0))) + +void +entity_move(Entity *this, struct Game *g) +{ + (void)g; + + if (this->ignore_solids) { + for (int a = 0; a < 2; a++) { + const double sum = this->vel[a] + this->rem[a]; + int spd = (int)sum; + this->rem[a] = sum - spd; + this->pos[a] += spd; + } + return; + } + if (MOVE_COLLIDE) { + this->vel[0] = 0.0; + this->vel[1] = 0.0; + this->rem[0] = 0.0; + this->rem[1] = 0.0; + return; + } + for (int a = 0; a < 2; a++) { + const double sum = this->vel[a] + this->rem[a]; + int spd = (int)sum; + this->rem[a] = sum - spd; + const int sign = (spd > 0) - (spd < 0); + if (sign == 0) + continue; + while (spd != 0) { + this->pos[a] += sign; + if (MOVE_COLLIDE) { + this->pos[a] -= sign; + this->rem[a] = 0.0; + if (a == 0 && this->type == entity_type("player")) + this->vel[a] *= 0.9; + else + this->vel[a] = 0.0; + break; + } + spd -= sign; + } + } +} + +Entity * +entity_init(Entity *this, Game *g, unsigned int type, const char *name, int x, + int y, int w, int h) +{ + entitytags[type - 1].init(this, g, name, x, y, w, h); + return this; +} diff --git a/src/entity.h b/src/entity.h new file mode 100644 index 0000000..b41b560 --- /dev/null +++ b/src/entity.h @@ -0,0 +1,69 @@ +#pragma once +#include <stdbool.h> +#include "TZR.h" + +struct Game; + +typedef struct Entity Entity; +struct Entity { + unsigned long uuid; + void (*update_begin)(Entity *this, struct Game *g); + void (*update)(Entity *this, struct Game *g); + void (*update_end)(Entity *this, struct Game *g); + void (*draw_begin)(Entity *this, struct Game *g); + void (*draw)(Entity *this, struct Game *g); + void (*draw_end)(Entity *this, struct Game *g); + void (*draw_ui)(Entity *this, struct Game *g); + void (*draw_last)(Entity *this, struct Game *g); + void (*light)(Entity *this, struct Game *g); + unsigned int type; + char name[256]; + int pos[2]; + int prev_pos[2]; + double vel[2]; + double rem[2]; + int gravity[2]; + int width; + int height; + bool ignore_solids; + bool solid_spikes; + bool pusher; + bool solid; + float angle; + float target_angle; + float a; + int life; + bool hold; + int hold_duration; + float frame; + bool was_on_ground; + int spr; + unsigned long next; + int hurt; + char time[16]; + int dirx; + bool on_ground; + bool air_dash; + int air_state; + int anim_state; + int anim_life; + int dead; + unsigned long attached; + int grace; + int jump_buffer; + int max_pizza; + union { + }; +}; + +unsigned int entity_type(const char *typename); +unsigned int entity_type_parent(unsigned int type); +bool entity_is_type(unsigned int type, unsigned int search); +bool entity_collide(Entity *this, int ox, int oy); +bool entity_collide_spike(Entity *this, int ox, int oy); +bool entity_oob(Entity *this); +bool entity_meet(Entity *this, Entity *other); +Entity *entity_place_meeting(Entity *this, struct Game *g, unsigned int type); +void entity_move(Entity *this, struct Game *g); +Entity *entity_init(Entity *this, struct Game *g, unsigned int type, + const char *name, int x, int y, int w, int h); diff --git a/src/entityimpl.h b/src/entityimpl.h new file mode 100644 index 0000000..a8b0f75 --- /dev/null +++ b/src/entityimpl.h @@ -0,0 +1,72 @@ +#pragma once +#include "game.h" +#include "cfg.h" +#include "TZR.h" +#include "entitytag.h" +#include "camera.h" +#include <string.h> + +#define IMPL_UNUSED (void)this, (void)g +#define UNPACK_POS this->pos[0], this->pos[1] +#define UNPACK_SIZE this->width, this->height + +static void *_draw_begin; +static void *_draw; +static void *_draw_end; +static void *_draw_ui; +static void *_draw_last; +static void *_update_begin; +static void *_update; +static void *_update_end; +static void *_light; + +__attribute__((constructor)) static void silence_unused(void) { + (void)_draw_begin; + (void)_draw; + (void)_draw_end; + (void)_draw_ui; + (void)_draw_last; + (void)_update_begin; + (void)_update; + (void)_update_end; + (void)_light; +} + +#define IMPL(X) static void X(Entity *this, Game *g); \ +__attribute__((constructor)) static void init_##X() { _##X = X; } \ +static void X(Entity *this, Game *g) + +#define IMPL_INIT(X,P) static void init(Entity *, Game *, const char *, \ + int, int, int, int); \ +__attribute__((constructor)) static void init_tag(void) { \ + entitytags[num_entitytags].init = init; \ + entitytags[num_entitytags].parent = P; \ + entitytags[num_entitytags++].name = #X; \ +} \ +static void _init(Entity *this, Game *g); \ +static void init(Entity *this, Game *g, const char *name, int x, int y, int w, int h) { \ + { \ + const __auto_type uuid = this->uuid; \ + memset(this, 0, sizeof(*this)); \ + this->uuid = uuid; \ + } \ + this->update_begin = _update_begin; \ + this->update = _update; \ + this->update_end = _update_end; \ + this->draw_begin = _draw_begin; \ + this->draw = _draw; \ + this->draw_end = _draw_end; \ + this->draw_ui = _draw_ui; \ + this->draw_last = _draw_last; \ + this->light = _light; \ + if (name != NULL) strncpy(this->name, name, sizeof(this->name) - 1); \ + this->pos[0] = x; \ + this->pos[1] = y; \ + this->width = w; \ + this->height = h; \ + this->type = entity_type(#X); \ + this->solid = false; \ + this->pusher = false; \ + _init(this, g); \ +} \ +static void _init(Entity *this, Game *g) diff --git a/src/entitytag.c b/src/entitytag.c new file mode 100644 index 0000000..de56455 --- /dev/null +++ b/src/entitytag.c @@ -0,0 +1,4 @@ +#include "entitytag.h" + +unsigned int num_entitytags = 0; +EntityTag entitytags[256] = {}; diff --git a/src/entitytag.h b/src/entitytag.h new file mode 100644 index 0000000..7c9a972 --- /dev/null +++ b/src/entitytag.h @@ -0,0 +1,11 @@ +#pragma once +#include "game.h" + +typedef struct { + const char *name; + const char *parent; + void (*init)(Entity*, Game*, const char*, int x, int y, int w, int h); +} EntityTag; + +extern unsigned int num_entitytags; +extern EntityTag entitytags[256]; diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..1a8a246 --- /dev/null +++ b/src/game.c @@ -0,0 +1,182 @@ +#include "game.h" +#include "cfg.h" +#include "entity.h" +#include "map.h" +#include "camera.h" +#include <stdio.h> +#include <string.h> + +int +game_init(Game *this) +{ + map_reset(); + memset(this, 0, sizeof(*this)); + camera_init(); + game_restart_scene(this); + return 0; +} + +void +game_update(Game *this) +{ + this->timer += 1; + if (this->queue_next_scene) { + if (--this->queue_next_scene == 0) { + this->queue_restart_scene = 0; + this->queue_previous_scene = 0; + this->checkpoint = false; + map_next(); + this->missed_bois += + game_entity_count(this, entity_type("boi")); + game_restart_scene(this); + this->total_bois += + game_entity_count(this, entity_type("boi")); + return; + } + } + if (this->queue_previous_scene) { + if (--this->queue_next_scene == 0) { + this->queue_restart_scene = 0; + this->queue_next_scene = 0; + map_previous(); + game_restart_scene(this); + return; + } + } + if (this->queue_restart_scene) { + if (--this->queue_restart_scene == 0) + game_restart_scene(this); + } + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->update_begin != NULL) + e->update_begin(e, this); + } + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->update != NULL) + e->update(e, this); + } + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->update_end != NULL) + e->update_end(e, this); + } + + // tidy + for (int i = 1; i < MAX_ENTITIES; i++) { + Entity *const e_prev = &this->entities[i - 1]; + Entity *const e = &this->entities[i]; + if (e->type && !e_prev->type) { + memcpy(e_prev, e, sizeof(*e)); + e->type = 0; + } + } +} + +void +game_light(Game *this) +{ + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->light != NULL) + e->light(e, this); + } +} + +void +game_draw(Game *this) +{ + map_draw(); + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->draw_begin != NULL) + e->draw_begin(e, this); + } + for (int i = MAX_ENTITIES - 1; i >= 0; i--) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->draw != NULL) + e->draw(e, this); + } + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->draw_end != NULL) + e->draw_end(e, this); + } + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->draw_last != NULL) + e->draw_last(e, this); + } + map_draw_foreground(); + if (this->show_ui <= 0) + return; + this->show_ui -= 1; + for (int i = 0; i < MAX_ENTITIES; i++) { + Entity *const e = &this->entities[i]; + if (e->type != 0 && e->draw_ui != NULL) + e->draw_ui(e, this); + } +} + +void +game_restart_scene(Game *this) +{ + memset(this->entities, 0, sizeof(this->entities)); + this->icheckpoint = 0; + unsigned int size; + const Tiled2cObject *const objects = map_objects(&size); + for (unsigned i = 0u; i < size; i++) { + const Tiled2cObject *const object = &objects[i]; + const unsigned int type = (object->type && object->type[0]) + ? entity_type(object->type) + : entity_type(object->name); + if (type == 0) + continue; + const double x = object->x + object->width / 2; + const double y = object->y + object->height / 2; + if (y < 0 || y >= map_height() * TSIZE) + continue; + entity_init(game_create_entity(this), this, type, object->name, + x, y, object->width, object->height); + } +} + +Entity * +game_create_entity(Game *this) +{ + Entity *e = &this->entities[MAX_ENTITIES - 1]; + for (int i = 0; i < MAX_ENTITIES; i++) + if (this->entities[i].type == 0) { + e = &this->entities[i]; + break; + } + e->uuid = this->uuid++; + return e; +} + +int +game_entity_count(Game *this, unsigned int type) +{ + int count = 0; + for (int i = 0; i < MAX_ENTITIES; i++) + count += (this->entities[i].type == type); + return count; +} + +Entity * +game_get_entity(Game *this, unsigned int type) +{ + for (int i = 0; i < MAX_ENTITIES; i++) + if (this->entities[i].type == type) + return &this->entities[i]; + return NULL; +} + +Entity * +game_get_entity_by_uuid(Game *this, unsigned long uuid) { + for (int i = 0; i < MAX_ENTITIES; i++) + if (this->entities[i].uuid == uuid) + return &this->entities[i]; + return NULL; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..f2d1928 --- /dev/null +++ b/src/game.h @@ -0,0 +1,36 @@ +#pragma once +#include "entity.h" + +enum { MAX_ENTITIES = 1024 }; + +typedef struct Game { + unsigned long uuid; + unsigned long timer; + int queue_next_scene; + int queue_previous_scene; + int queue_restart_scene; + int show_ui; + int missed_bois; + int total_bois; + int deaths; + int dashes; + int icheckpoint; + int checkpoint; + int checkpoint_pos[2]; + int bois[3]; + int bois_count; + Entity entities[MAX_ENTITIES]; +} Game; + +#if __STDC_VERSION__ > 201710L +[[nodiscard]] +#endif +int game_init(Game *this); +void game_update(Game *this); +void game_light(Game *this); +void game_draw(Game *this); +void game_restart_scene(Game *this); +Entity *game_create_entity(Game *this); +int game_entity_count(Game *this, unsigned int type); +Entity *game_get_entity(Game *this, unsigned int type); +Entity *game_get_entity_by_uuid(Game *this, unsigned long type); diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..388fb02 --- /dev/null +++ b/src/main.c @@ -0,0 +1,88 @@ +#include "TZR.h" +#include "cfg.h" +#include "game.h" +#include "map.h" +#include "tileset.h" +#include <SDL2/SDL_video.h> +#include <stdio.h> +#include <stdlib.h> +#include <SDL2/SDL_main.h> +#include <time.h> + +static Game *game = NULL; + +static void deinit(void); +static int main_loop(void *udata); + +int +main(int argc, char **argv) +{ + (void)argc, (void)argv; + + if (TZR_Init(.width=DWIDTH, + .height=DHEIGHT, + .scale=2, + .target_fps=TARGET_FPS, + .pixel_perfect=true, + .mixer=false, + .png_loading=false, + .light_system=false, + .title="goty of the day")) + return 1; + srand(time(NULL)); + + Mix_AllocateChannels(32); + + if (atexit(deinit)) { + perror("main:atexit"); + deinit(); + return 1; + } + + if ((game = malloc(sizeof(*game))) == NULL) { + perror("main:malloc"); + return 1; + } + if (game_init(game)) + return 1; + + return TZR_MainLoop(main_loop, game); +} + +static void +deinit(void) +{ + if (game != NULL) + free(game); + TZR_Quit(); +} + +static int +main_loop(void *udata) +{ + Game *const game = udata; + + game_update(game); + tileset_update(); + + if (TZR_IsKeyPressed(SDL_SCANCODE_R)) { + if (game_init(game)) + return 1; + } + if (TZR_IsKeyPressed(SDL_SCANCODE_F)) { + static bool fullscreen = false; + fullscreen = !fullscreen; + SDL_SetWindowFullscreen(___tzr_window, fullscreen); + } + if (TZR_IsKeyPressed(SDL_SCANCODE_N)) + game->queue_next_scene = 1; + + if (TZR_DrawBegin()) + return 1; + TZR_DrawSetColor8(0x03, 0x06, 0x16, 0xff); + TZR_DrawClear(); + game_draw(game); + if (TZR_DrawEnd()) + return 1; + return 0; +} diff --git a/src/map.c b/src/map.c new file mode 100644 index 0000000..311db7f --- /dev/null +++ b/src/map.c @@ -0,0 +1,130 @@ +#include "map.h" +#include "cfg.h" +#include "TZR.h" +#include "tiled2c.h" +#include "tileset.h" +#include "camera.h" +#include <stddef.h> + +extern const Tiled2cMap embed_game_tmj; +extern const Tiled2cMap embed_grablmao_tmj; +extern const Tiled2cMap embed_pizzeria_tmj; +extern const Tiled2cMap embed_banger_tmj; +extern const Tiled2cMap embed_end_tmj; + +static const Tiled2cMap *const maps[] = { + &embed_game_tmj, + NULL +}; +static unsigned int map_id; + +void +map_reset(void) +{ + map_id = 0; +} + +void +map_next(void) +{ + map_id += (maps[map_id + 1] != NULL); +} + +void +map_previous(void) +{ + if (map_id > 0) + map_id -= 1; +} + +int +map_width(void) +{ + return maps[map_id]->width; +} + +int +map_height(void) +{ + return maps[map_id]->height; +} + +bool +map_get(int x, int y, unsigned int type) +{ + type &= ~TILED_TRANSFORMS; + const Tiled2cMap *const map = maps[map_id]; + if (x >= map_width() || y < 0 || y >= map_height()) + return 0; + if (x < 0) + return (type != TILE_SPIKE); + if (map->numlayers < 1) + return (type != TILE_SPIKE); + for (unsigned int i = 0; i < map->numlayers; i++) { + const unsigned int tile = map->layers[i] + .data[x + y * map_width()]; + if (tile_type(tile) == type) + return true; + } + return false; +} + +bool +map_get_px(int x, int y, unsigned int type) +{ + if (y < 0) + return 0; + if (x < 0) + return (type != TILE_SPIKE); + return map_get(x / TSIZE, y / TSIZE, type); +} + +static void +draw_layer(const Tiled2cLayer *layer) +{ + const int tset = TZR_RES("res/tset.bmp"); + const int tset_w = TZR_GetImageWidth(tset); + const int ox = camera_x(layer->parallaxx); + const int oy = camera_y(layer->parallaxy); + for (int y = 0; y < map_height(); y++) + for (int x = 0; x < map_width(); x++) { + const unsigned tile = + layer->data[x + y * map_width()]; + if (tile == 0) + continue; + const unsigned vtile = tile_visual(tile); + const int dx = ox + x * TSIZE; + const int dy = oy + y * TSIZE; + const int ix = vtile % (tset_w / TSIZE) * TSIZE; + const int iy = vtile / (tset_w / TSIZE) * TSIZE; + TZR_DrawImage(tset, dx, dy, ix, iy, TSIZE, TSIZE); + } +} + +void +map_draw(void) +{ + const Tiled2cMap *const map = maps[map_id]; + TZR_DrawSetColor(1, 1, 1); + for (unsigned int i = 0; i < map->numlayers; i++) + if (map->layers[i].visible && map->layers[i].parallaxx <= 1.0) + draw_layer(&map->layers[i]); +} + +void +map_draw_foreground(void) +{ + const Tiled2cMap *const map = maps[map_id]; + TZR_DrawSetColor(1, 1, 1); + for (unsigned int i = 0; i < map->numlayers; i++) + if (map->layers[i].visible && map->layers[i].parallaxx > 1.0) + draw_layer(&map->layers[i]); +} + +const Tiled2cObject * +map_objects(unsigned int *size) +{ + if (size != NULL) + *size = maps[map_id]->numobjects; + return maps[map_id]->objects; +} diff --git a/src/map.h b/src/map.h new file mode 100644 index 0000000..a76930c --- /dev/null +++ b/src/map.h @@ -0,0 +1,21 @@ +#pragma once +#include "tiled2c.h" +#include <stdbool.h> + +enum { + TILED_FLIP_X = 1 << 31, + TILED_FLIP_Y = 1 << 30, + TILED_TRANSFORMS = TILED_FLIP_X | TILED_FLIP_Y, +}; + +void map_reset(void); +void map_next(void); +void map_previous(void); +int map_width(void); +int map_height(void); +int map_room_y(void); +bool map_get(int x, int y, unsigned int type); +bool map_get_px(int x, int y, unsigned int type); +void map_draw(void); +void map_draw_foreground(void); +const Tiled2cObject *map_objects(unsigned int *size); diff --git a/src/player.c b/src/player.c new file mode 100644 index 0000000..d98bdee --- /dev/null +++ b/src/player.c @@ -0,0 +1,29 @@ +#include "entityimpl.h" + +IMPL_INIT(player, 0) { + IMPL_UNUSED; +} + +IMPL(update) { + IMPL_UNUSED; + const int kleft = TZR_IsKeyDown(SDL_SCANCODE_A) || + TZR_IsKeyDown(SDL_SCANCODE_LEFT); + const int kright = TZR_IsKeyDown(SDL_SCANCODE_D) || + TZR_IsKeyDown(SDL_SCANCODE_RIGHT); + const int kpjump = TZR_IsKeyPressed(SDL_SCANCODE_Z) || + TZR_IsKeyPressed(SDL_SCANCODE_UP) || + TZR_IsKeyPressed(SDL_SCANCODE_SPACE); + const int dirx = kright - kleft; + + this->vel[0] = dirx * 2; + + entity_move(this, g); + camera_update(this->vel); +} + +IMPL(draw) { + IMPL_UNUSED; + TZR_DrawSetColor(1, 1, 1); + TZR_DrawRectangle(this->pos[0], this->pos[1], this->width, this->height, + .center=true); +} diff --git a/src/px.c b/src/px.c new file mode 100644 index 0000000..5c1af9b --- /dev/null +++ b/src/px.c @@ -0,0 +1,260 @@ +/* Licensing information can be found at the end of the file. */ +#include "px.h" +#include "TZR.h" +#include <assert.h> + +PxCol pxbuf[PX_WIDTH * PX_HEIGHT] = {0}; +PxPal pxpal[256] = { + /* PICO-8 palette */ + {0x00, 0x00, 0x00, false, 0, 0}, + {0x1d, 0x2b, 0x53, false, 0, 0}, + {0x7e, 0x25, 0x53, false, 0, 0}, + {0x00, 0x87, 0x51, false, 0, 0}, + {0xab, 0x52, 0x36, false, 0, 0}, + {0x5f, 0x57, 0x4f, false, 0, 0}, + {0xc2, 0xc3, 0xc7, false, 0, 0}, + {0xff, 0xf1, 0xe8, false, 0, 0}, + {0xff, 0x00, 0x4d, false, 0, 0}, + {0xff, 0xa3, 0x00, false, 0, 0}, + {0xff, 0xec, 0x27, false, 0, 0}, + {0x00, 0xe4, 0x36, false, 0, 0}, + {0x29, 0xad, 0xff, false, 0, 0}, + {0x83, 0x76, 0x9c, false, 0, 0}, + {0xff, 0x77, 0xa8, false, 0, 0}, + {0xff, 0xcc, 0xaa, false, 0, 0}, +}; +static struct { + int x; + int y; + int w; + int h; +} pxclip = {0, 0, PX_WIDTH, PX_HEIGHT}; +static SDL_Texture *pxtexture = NULL; + +int +pxInit(void) +{ + pxtexture = SDL_CreateTexture(___tzr_renderer, + SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + PX_WIDTH, PX_HEIGHT); + if (pxtexture == NULL) { + fprintf(stderr, "%s\n", SDL_GetError()); + return 1; + } + return 0; +} + +void +pxDeinit(void) +{ + if (pxtexture != NULL) { + SDL_DestroyTexture(pxtexture); + pxtexture = NULL; + } +} + +void +pxFlip(void) +{ + uint8_t *pixels = NULL; + int pitch = 0; + SDL_LockTexture(pxtexture, NULL, (void **)&pixels, &pitch); + + for (int y = 0; y < PX_HEIGHT; y++) { + for (int x = 0; x < PX_WIDTH; x++) { + const int ix = x << 2; + PxCol col = pxbuf[x + y * PX_WIDTH]; + if (pxpal[col].spal) + col = pxpal[col].spal - 1; + pixels[ix + y * pitch + 0] = pxpal[col].b; + pixels[ix + y * pitch + 1] = pxpal[col].g; + pixels[ix + y * pitch + 2] = pxpal[col].r; + pixels[ix + y * pitch + 3] = 255; + } + } + + SDL_UnlockTexture(pxtexture); + SDL_RenderCopy(___tzr_renderer, pxtexture, NULL, NULL); +} + +void +pxCls(PxCol c) +{ + for (int y = 0; y < PX_HEIGHT; y++) + for (int x = 0; x < PX_WIDTH; x++) + pxPset(x, y, c); +} + +void +pxClip(int x, int y, int w, int h) +{ + pxclip.x = pxClamp(0, PX_WIDTH - 1, x); + pxclip.y = pxClamp(0, PX_HEIGHT - 1, y); + pxclip.w = pxClamp(0, PX_WIDTH - pxclip.x - 1, w); + pxclip.h = pxClamp(0, PX_HEIGHT - pxclip.y - 1, h); +} + +void +pxClipReset(void) +{ + pxClip(0, 0, PX_WIDTH, PX_HEIGHT); +} + +void +pxPset(int x, int y, PxCol c) +{ + if (x < pxclip.x || x >= pxclip.x + pxclip.w || + y < pxclip.y || y >= pxclip.y + pxclip.h) + return; + if (pxpal[c].pal) + c = pxpal[c].pal - 1; + pxbuf[x + y * PX_WIDTH] = c; +} + +uint8_t +pxPget(int x, int y) +{ + if (x < 0 || y < 0 || x >= PX_WIDTH || y >= PX_HEIGHT) + return 0; + return pxbuf[x + y * PX_WIDTH]; +} + +void +pxRect(int x0, int y0, int x1, int y1, PxCol col) +{ + if (x0 > x1) { + x0 ^= x1; + x1 ^= x0; + x0 ^= x1; + } + if (y0 > y1) { + y0 ^= y1; + y1 ^= y0; + y0 ^= y1; + } + for (int x = x0; x <= x1; x++) { + pxPset(x, y0, col); + pxPset(x, y1, col); + } + for (int y = y0; y <= y1; y++) { + pxPset(x0, y, col); + pxPset(x1, y, col); + } +} + +void +pxRectfill(int x0, int y0, int x1, int y1, PxCol col) +{ + if (x0 > x1) { + x0 ^= x1; + x1 ^= x0; + x0 ^= x1; + } + if (y0 > y1) { + y0 ^= y1; + y1 ^= y0; + y0 ^= y1; + } + for (int y = y0; y <= y1; y++) + for (int x = x0; x <= x1; x++) + pxPset(x, y, col); +} + +void +_pxSpr(const PxSprArgs *args) +{ + int w = (args->w < 0) ? (int)(args->spr->w) : (args->w); + w = pxMin(w, args->spr->w - args->ix); + + int h = (args->h < 0) ? (int)(args->spr->h) : (args->h); + h = pxMin(h, args->spr->h - args->iy); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + const int dx = x + args->x; + const int dy = y + args->y; + int ix = args->ix + ((args->flip_x) + ? (w - x - 1) + : (x)); + int iy = args->iy + ((args->flip_y) + ? (h - y - 1) + : (y)); + const PxCol c = args->spr->data[ix + iy * args->spr->w]; + if (!pxpal[c].t) + pxPset(dx, dy, c); + } + } +} + +void +_pxPal(const PxPalArgs *args) +{ + assert((args->c0 == -1) ^ (args->c1 != -1)); + if (args->c0 == -1) + for (int i = 0; i < 256; i++) + pxpal[i].pal = 0; + else + pxpal[args->c0].pal = args->c1 + 1; +} + +void +_pxSpal(const PxSpalArgs *args) +{ + assert((args->c0 == -1) ^ (args->c1 != -1)); + if (args->c0 == -1) + for (int i = 0; i < 256; i++) + pxpal[i].spal = 0; + else + pxpal[args->c0].spal = args->c1 + 1; +} + +void +_pxPalt(const PxPaltArgs *args) +{ + if (args->col < 0 || args->col > 255) + for (int i = 0; i < 256; i++) + pxpal[i].t = false; + else + pxpal[args->col].t = args->t; +} + +int +pxMin(int a, int b) +{ + return (a < b) ? a : b; +} + +int +pxMax(int a, int b) +{ + return (a > b) ? a : b; +} + +int +pxClamp(int min, int max, int v) +{ + return pxMax(min, pxMin(max, v)); +} + +/* +** Copyright (c) 2023 kdx +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to +** deal in the Software without restriction, including without limitation the +** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +** sell copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +** IN THE SOFTWARE. +*/ diff --git a/src/px.h b/src/px.h new file mode 100644 index 0000000..f413bdc --- /dev/null +++ b/src/px.h @@ -0,0 +1,132 @@ +/* Licensing information can be found at the end of the file. */ +#pragma once +#include <stdint.h> +#include <stdbool.h> + +#ifndef PX_WIDTH +#define PX_WIDTH 128 +#endif +#ifndef PX_HEIGHT +#define PX_HEIGHT 128 +#endif + +typedef uint8_t PxCol; + +typedef struct { + uint8_t r, g, b; + bool t; /* tranparency */ + int pal, spal; +} PxPal; + +typedef struct { + unsigned int w, h; + PxCol *data; +} PxSpr; + +typedef struct { + int _; + const PxSpr *spr; + int x; + int y; + int ix; + int iy; + int w; + int h; + bool flip_x; + bool flip_y; +} PxSprArgs; + +typedef struct { + int _; + int c0; + int c1; +} PxPalArgs; +typedef PxPalArgs PxSpalArgs; + +typedef struct { + int _; + int col; + bool t; +} PxPaltArgs; + +extern PxCol pxbuf[PX_WIDTH * PX_HEIGHT]; +extern PxPal pxpal[256]; + +/* Return -1 on error. Should be called after TZR_Init. */ +int pxInit(void); + +/* Should be called before TZR_Quit. */ +void pxDeinit(void); + +/* Should be called between TZR_DrawBegin and TZR_DrawEnd. */ +void pxFlip(void); + +/*** DRAW ***/ +/* Fill the clipping region with color 'c'. */ +void pxCls(PxCol c); + +/* Set the clipping region. */ +void pxClip(int x, int y, int w, int h); + +/* Reset the clipping region. */ +void pxClipReset(void); + +/* Put a pixel. */ +void pxPset(int x, int y, PxCol c); + +/* Get a pixel. */ +PxCol pxPget(int x, int y); + +/* Draw empty rectangle. */ +void pxRect(int x0, int y0, int x1, int y1, PxCol col); + +/* Draw filled rectangle. */ +void pxRectfill(int x0, int y0, int x1, int y1, PxCol col); + +/* Draw sprite. */ +#define pxSpr(...) _pxSpr(&(const PxSprArgs){ \ + .x=0, .y=0, .ix=0, .iy=0, .w=-1, .h=-1, .flip_x=false, .flip_x=false, \ + ._=0, __VA_ARGS__}) +void _pxSpr(const PxSprArgs *args); + +/* Bind palette index to new color. */ +#define pxPal(...) _pxPal(&(const PxPalArgs){ \ + .c0=-1, .c1=-1, ._=0, __VA_ARGS__}) +void _pxPal(const PxPalArgs *args); + +/* Set screen palette. */ +#define pxSpal(...) _pxSpal(&(const PxSpalArgs){ \ + .c0=-1, .c1=-1, ._=0, __VA_ARGS__}) +void _pxSpal(const PxSpalArgs *args); + +/* Set palette tranparency. */ +#define pxPalt(...) _pxPalt(&(const PxPaltArgs){ \ + .col=-1, .t=true, ._=0, __VA_ARGS__}) +void _pxPalt(const PxPaltArgs *args); + +/*** MATH ***/ +int pxMin(int a, int b); +int pxMax(int a, int b); +int pxClamp(int min, int max, int v); + +/* +** Copyright (c) 2023 kdx +** +** Permission is hereby granted, free of charge, to any person obtaining a copy +** of this software and associated documentation files (the "Software"), to +** deal in the Software without restriction, including without limitation the +** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +** sell copies of the Software, and to permit persons to whom the Software is +** furnished to do so, subject to the following conditions: +** +** The above copyright notice and this permission notice shall be included in +** all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +** IN THE SOFTWARE. +*/ diff --git a/src/tiled2c.h b/src/tiled2c.h new file mode 100644 index 0000000..f57c83a --- /dev/null +++ b/src/tiled2c.h @@ -0,0 +1,61 @@ +#pragma once + +typedef struct { + double duration; + double tileid; +} Tiled2cFrame; + +typedef struct { + unsigned int id; + const char *type; + double probability; + const int numframes; + const Tiled2cFrame *frames; +} Tiled2cTile; + +typedef struct { + const char *name; + const char *path; + unsigned int imagewidth; + unsigned int imageheight; + unsigned int tilewidth; + unsigned int tileheight; + unsigned int margin; + unsigned int columns; + unsigned int tilecount; + unsigned int numtiles; + const Tiled2cTile *tiles; +} Tiled2cSet; + +typedef struct { + const char *name; + double opacity; + unsigned int visible; + double parallaxx; + double parallaxy; + const unsigned int *data; +} Tiled2cLayer; + +typedef struct { + const char *name; + const char *type; + unsigned int id; + double x; + double y; + double width; + double height; + double rotation; + unsigned int visible; +} Tiled2cObject; + +typedef struct { + const char *path; + unsigned int width; + unsigned int height; + unsigned int tilewidth; + unsigned int tileheight; + unsigned int numlayers; + const Tiled2cLayer *layers; + const Tiled2cObject *objects; + unsigned int numobjects; +} Tiled2cMap; diff --git a/src/tileset.c b/src/tileset.c new file mode 100644 index 0000000..b4dc7c6 --- /dev/null +++ b/src/tileset.c @@ -0,0 +1,95 @@ +#include "cfg.h" +#include "tileset.h" +#include "TZR.h" +#include "map.h" +#include "tiled2c.h" +#include <assert.h> +#include <stddef.h> +#include <stdio.h> +#include <strings.h> + +#define MAX_TILE_ID 8192 + +extern const Tiled2cSet embed_tset_tsj; + +static unsigned int tile_types[MAX_TILE_ID] = {}; +static unsigned int tile_visuals[MAX_TILE_ID] = {}; +static unsigned int tile_numframes[MAX_TILE_ID] = {}; +static const Tiled2cFrame *tile_frames[MAX_TILE_ID] = {}; +static unsigned int tile_frame_id[MAX_TILE_ID] = {}; +static long tile_next_frame[MAX_TILE_ID] = {}; +static unsigned long last_tick = 0; + +__attribute__((constructor)) static void +match_tile_types(void) +{ + const Tiled2cSet *tset = &embed_tset_tsj; + assert(MAX_TILE_ID > tset->tilecount); + for (unsigned int i = 0; i < tset->numtiles; i++) { + const Tiled2cTile *const tile = &tset->tiles[i]; + if (tile->type == NULL) + ; + else if (strcasecmp(tile->type, "solid") == 0) + tile_types[tile->id] = TILE_SOLID; + else if (strcasecmp(tile->type, "spike") == 0) + tile_types[tile->id] = TILE_SPIKE; + else if (strcasecmp(tile->type, "smolspike") == 0) + tile_types[tile->id] = TILE_SMOLSPIKE; + else + fprintf(stderr, "unknown tile type '%s'\n", tile->type); + if (tile->numframes) { + tile_numframes[tile->id] = tile->numframes; + tile_frames[tile->id] = tile->frames; + } + } + for (int i = 0; i < MAX_TILE_ID; i++) { + tile_frame_id[i] = 0; + if (tile_numframes[i]) { + tile_visuals[i] = tile_frames[i]->tileid; + tile_next_frame[i] = tile_frames[i]->duration * + TARGET_FPS / 1000; + } else + tile_visuals[i] = i; + } +} + +unsigned int +tile_type(unsigned int tile) +{ + tile &= ~TILED_TRANSFORMS; + if (tile == 0) + return TILE_NONE; + return tile_types[(tile - 1) & ~TILED_TRANSFORMS]; +} + +void +tileset_update(void) +{ + const unsigned long tick = TZR_GetTick(); + const unsigned long elapsed = tick - last_tick; + last_tick = tick; + + for (int i = 0; i < MAX_TILE_ID; i++) { + if (!tile_numframes[i]) + continue; + tile_next_frame[i] -= elapsed; + while (tile_next_frame[i] <= 0) { + if (tile_frame_id[i] == tile_numframes[i] - 1) + tile_frame_id[i] = 0; + else + tile_frame_id[i] += 1; + const unsigned int frame_id = tile_frame_id[i]; + tile_next_frame[i] = tile_frames[i][frame_id] + .duration * TARGET_FPS / 1000; + tile_visuals[i] = tile_frames[i][frame_id].tileid; + } + } +} + +unsigned int +tile_visual(unsigned int tile) +{ + if (tile == 0) + return 0; + return tile_visuals[(tile - 1) & ~TILED_TRANSFORMS]; +} diff --git a/src/tileset.h b/src/tileset.h new file mode 100644 index 0000000..6684874 --- /dev/null +++ b/src/tileset.h @@ -0,0 +1,12 @@ +#pragma once + +enum { + TILE_NONE, + TILE_SOLID, + TILE_SPIKE, + TILE_SMOLSPIKE, +}; + +unsigned int tile_type(unsigned int tile); +void tileset_update(void); +unsigned int tile_visual(unsigned int tile); |