A game engine for top-down 2D RPG games.
rpg game-engine raylib c99

Compare changes

Choose any two refs to compare.

+11
.gitignore
··· 1 /build/
··· 1 + # Data 2 + /data/ 3 + 4 + # Built files 5 /build/ 6 + 7 + # Backups 8 + *.bak 9 + 10 + # Raylib 11 + /raylib.tar.gz 12 + /raylib/
+3
compile_flags.txt
··· 4 5 -std=c99 6 -Iinclude/ 7 -xc-header 8 9 -DKF_SANITY_CHECKS 10 -DKF_GNU
··· 4 5 -std=c99 6 -Iinclude/ 7 + -Iraylib/src/ 8 -xc-header 9 10 -DKF_SANITY_CHECKS 11 -DKF_GNU 12 + -Dbini_impl 13 + -Dbini_dbg
+56
etc/style.c
···
··· 1 + #ifndef __main__ 2 + #define __main__ 3 + 4 + 5 + /* 6 + Include order doesn't really matter to me, however I do like to keep them reasonably organized: 7 + 1. "Header" includes (i.e, keraforge/_header.h). 8 + 2. Macro/common headers (i.e, keraforge/log.h). 9 + 3. Internal library includes (i.e, keraforge/*.h). 10 + 4. External library includes (i.e, raylib.h, raymath.h, etc). 11 + 5. C standard library includes. 12 + */ 13 + #include <keraforge.h> 14 + #include <stdio.h> 15 + 16 + 17 + /* 18 + Please follow good practice when defining macros: 19 + - #undef after usage. 20 + - Use do/while blocks for parameterized macros. 21 + - Wrap constant macros in parenthesis, unless its a string that can be safely concatenated. 22 + */ 23 + 24 + 25 + /* Additional keywords (excl. const) should be on their own line. */ 26 + static 27 + /* All public items should be prefixed with kf_. All static items should use _kf_. */ 28 + int _kf_parse(int argc, /* Attach pointers with the identifier, not the type. */ char *argv[]) 29 + /* Allman braces */ 30 + { 31 + /* Tabs for indentation, spaces for alignment. */ 32 + 33 + /* Space out your semicolons in for loops. */ 34 + for (int i = 0 ; i < argc ; i++) 35 + fprintf(stderr, "%s", argv[i]); 36 + 37 + if (true) 38 + { 39 + /* ... */ 40 + } 41 + else 42 + { 43 + /* ... */ 44 + } 45 + 46 + return 1; 47 + } 48 + 49 + int main(void) 50 + { 51 + (void)_kf_parse(0, NULL); 52 + return 0; 53 + } 54 + 55 + 56 + #endif
+73
etc/style.txt
···
··· 1 + 2 + Style Guidelines 3 + ================ 4 + 5 + As a preface: I break these guidelines occasionally. It is 6 + important to note that I often find myself up into the 7 + early morning hours (sometimes even as early as 03:00) 8 + developing Keraforge. Late-night coding sessions tend to 9 + lead to mistakes that I accidentally push to prod. Feel 10 + free to point these mistakes out to me! 11 + 12 + Zen 13 + --- 14 + 15 + 1. Keep types simple. 16 + - If your type is complex enough to be unreadable, maybe 17 + you should change your type. 18 + - Function pointers may be disgraceful to write, but if 19 + you write your code in such a way that avoids writing 20 + the type more than once then you'll be fine. 21 + - See: `struct kf_actorregistry`. I use some quirky 22 + types there, but nothing too unreadable. Notice how I 23 + never rewrite those types again and notice that I am not 24 + using a typedef either. 25 + - Additionally, if the user is never going to use that 26 + type then it almost definitely does not need to be 27 + typedef'd. 28 + 2. Documentation is important. 29 + - "Self-documenting code" is not sufficient. If the item 30 + is public then it should have an explanation. 31 + - The ONLY exceptions to this rule are blatantly obvious 32 + struct fields, such as `position`, `velocity`, etc. 33 + - A few lines of documentation will save you a massive 34 + headache in the future. 35 + 3. Macro magic is garbage, but unmaintainable code is worse. 36 + - Using macro magic sparingly can help prevent you from 37 + being smitten by the DRY deities. Save yourself today! 38 + - See: math.h, log.h, and bini.h for cases where macro 39 + magic is acceptable. 40 + 4. Keep names terse but readable. 41 + - I don't want to see stdlib-style function names, but I 42 + would much rather that over a name ripped from an 43 + object-oriented C. 44 + - If you must use a stdlib-style function name to remain 45 + terse, then please provide a sentence in its docs to 46 + explain the name. 47 + 5. Most importantly: Be simple. 48 + - This often means sitting at your screen pondering the 49 + best implementation for something rather than simply 50 + using the first one that comes to mind. 51 + - It's okay to sacrifice a bit of performance for more 52 + maintainable code. 53 + - If you must write complex code, please explain why via 54 + a brief comment. 55 + 56 + Basics 57 + ------ 58 + 59 + See <etc/style.c> for a sample of the style with comments 60 + explaining each main point. 61 + 62 + For more specifics, please give a skim throughout the 63 + codebase itself. 64 + 65 + - Prefer [iuf](8|16|32|64) over (float|double|(u?int(8|16|32|64)_t)). 66 + - Do not typedef function pointers (see Zen #1), structs, enums, or unions. 67 + - Use global variables instead of requiring the user to maintain state. 68 + See: kf_uiconfig, kf_actors, kf_actorregistry, kf_state, etc. 69 + - Use `struct kf_vec2(type)` instead of `struct kf_vec2f32`. 70 + - Do not modify libraries included in the source. 71 + Those are: bini.h. 72 + - I'm not afraid to postfix _t to typedef'd types. The standard is useless here since we prefix kf_ anyway. 73 + (I think this standard is useless *to begin with*, but I digress)
-32
include/keraforge/_header.h
··· 16 #endif 17 18 19 - #ifdef KF_SANITY_CHECKS 20 - # include <stdio.h> /* fprintf, stderr */ 21 - # include <stdlib.h> /* exit */ 22 - # define KF_SANITY_CHECK(EXPR, ...) \ 23 - do \ 24 - { \ 25 - if (!(EXPR)) \ 26 - { \ 27 - fprintf(stderr, "\x1b[1;31msanity check failed: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m", __FILE__, __LINE__, __FUNCTION_NAME__); \ 28 - fprintf(stderr, __VA_ARGS__); \ 29 - fprintf(stderr, "\n"); \ 30 - kf_printbacktrace(stderr); \ 31 - exit(1); \ 32 - } \ 33 - } \ 34 - while (0) 35 - # define KF_UNREACHABLE(...) \ 36 - do \ 37 - { \ 38 - fprintf(stderr, "\x1b[1;31munreachable: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m", __FILE__, __LINE__, __FUNCTION_NAME__); \ 39 - fprintf(stderr, __VA_ARGS__); \ 40 - fprintf(stderr, "\n"); \ 41 - kf_printbacktrace(stderr); \ 42 - exit(1); \ 43 - } \ 44 - while (0) 45 - #else 46 - # define KF_SANITY_CHECK(EXPR, ...) do { ; } while (0) 47 - # define KF_UNREACHABLE(...) do { ; } while (0) 48 - #endif 49 - 50 - 51 typedef int8_t i8; 52 typedef int16_t i16; 53 typedef int32_t i32;
··· 16 #endif 17 18 19 typedef int8_t i8; 20 typedef int16_t i16; 21 typedef int32_t i32;
+50 -7
include/keraforge/actor.h
··· 7 #include <keraforge/world.h> 8 #include <keraforge/math.h> 9 #include <keraforge/sprites.h> 10 #include <raylib.h> 11 12 13 /* Represents any kinematic body in the world (players, NPCs, etc). */ 14 struct kf_actor 15 { 16 - /* Spritesheet for the actor. */ 17 - struct kf_spritesheet sprites; 18 /* The actor's position. */ 19 struct kf_vec2(f32) pos; 20 /* The actor's velocity. */ ··· 35 bool controlled; 36 /* The direction that the actor is pointing. */ 37 enum kf_direction pointing; 38 - /* Called every frame to update the actor. Don't forget about deltatime (kf_dts, kf_dtms)! */ 39 - void (*tick)(struct kf_world *world, struct kf_actor *self); 40 - /* Called every frame to render the actor. */ 41 - void (*draw)(struct kf_world *world, struct kf_actor *self); 42 }; 43 44 45 /* Create a new actor. */ 46 - struct kf_actor *kf_actor_new(struct kf_spritesheet sprites, f32 width, f32 height, bool collides); 47 48 /* Load a spritesheet, filling in the details for loading character spritesheets. */ 49 struct kf_spritesheet kf_actor_loadspritesheet(char *filename); ··· 56 void kf_actor_move(struct kf_world *world, struct kf_actor *actor, f32 deltatime); 57 /* Draw the actor. */ 58 void kf_actor_draw(struct kf_actor *actor); 59 60 61 #endif
··· 7 #include <keraforge/world.h> 8 #include <keraforge/math.h> 9 #include <keraforge/sprites.h> 10 + #include <keraforge/bini.h> 11 #include <raylib.h> 12 13 14 + /* The maximum number of registered actors. */ 15 + #define KF_MAX_ACTOR_TYPES 1024 16 + 17 + 18 + /* Stores serializers and deserializers for actors. Any actor you plan to save MUST be registered here! 19 + TODO: This will probably be better as a hash table (especially for kf_actor_getregistryid). */ 20 + struct kf_actorregistry 21 + { 22 + /* Amount of actor types currently registered. */ 23 + int count; 24 + /* Keys for each actor. */ 25 + char *key[KF_MAX_ACTOR_TYPES]; 26 + /* Save actor to a binary stream. */ 27 + void (*serialize[KF_MAX_ACTOR_TYPES])(struct kf_actor *self, struct bini_stream *bs); 28 + /* Load actor from a binary stream. */ 29 + void (*deserialize[KF_MAX_ACTOR_TYPES])(struct kf_actor *self, struct bini_stream *bs); 30 + /* Called every frame to update actor. Don't forget about deltatime (kf_dts, kf_dtms)! */ 31 + void (*tick[KF_MAX_ACTOR_TYPES])(struct kf_actor *self); 32 + /* Called every frame to render actor. */ 33 + void (*draw[KF_MAX_ACTOR_TYPES])(struct kf_actor *self); 34 + /* Spritesheet for actor. */ 35 + struct kf_spritesheet sprite[KF_MAX_ACTOR_TYPES]; 36 + }; 37 + /* Global actor registry instance. */ 38 + extern struct kf_actorregistry kf_actorregistry; 39 + 40 /* Represents any kinematic body in the world (players, NPCs, etc). */ 41 struct kf_actor 42 { 43 + /* Integer identifier for this actor, comes from kf_actorregistry. 44 + This is unique per-actor-type, not per-actor. */ 45 + int id; 46 /* The actor's position. */ 47 struct kf_vec2(f32) pos; 48 /* The actor's velocity. */ ··· 63 bool controlled; 64 /* The direction that the actor is pointing. */ 65 enum kf_direction pointing; 66 + /* If the actor is running. This will not increase their speed, you are expected to yourself. */ 67 + bool running; 68 + /* Doubly-linked list of actors. */ 69 + struct kf_actor *prev, *next; 70 }; 71 + /* Linked list of actors. */ 72 + extern struct kf_actor *kf_actors, *kf_actors_last; 73 + /* Number of actors currently in the world. */ 74 + extern u32 kf_actor_count; 75 76 77 /* Create a new actor. */ 78 + struct kf_actor *kf_actor_new(char *key); 79 + /* Register a new actor and return its integer ID. */ 80 + int kf_actor_register(char *key); 81 82 /* Load a spritesheet, filling in the details for loading character spritesheets. */ 83 struct kf_spritesheet kf_actor_loadspritesheet(char *filename); ··· 90 void kf_actor_move(struct kf_world *world, struct kf_actor *actor, f32 deltatime); 91 /* Draw the actor. */ 92 void kf_actor_draw(struct kf_actor *actor); 93 + 94 + /* Get the registry ID of a given actor ID. 95 + This will perform a lot of string comparisons, use sparingly! */ 96 + int kf_actor_getregistryid(char *key); 97 + 98 + /* Save actor data. */ 99 + int kf_saveactors(void); 100 + /* Load actor data. */ 101 + int kf_loadactors(void); 102 103 104 #endif
+500
include/keraforge/bini.h
···
··· 1 + #ifndef __kf_bini__ 2 + #define __kf_bini__ 3 + 4 + /* 5 + bini.h: endian-independent binary IO. 6 + License: BSD 3-Clause. See EOF for license text. 7 + 8 + Version: 1.1 9 + 10 + Changelog: 11 + 1.0 (Dec 28, 2025): Initial version. 12 + 1.1 (Dec 30, 2025): Use an offset instead of shifting the 13 + byte buffer so carelessly. _bini_grow will shift the 14 + buffer on-demand instead so as to conserve CPU time. 15 + */ 16 + 17 + /* Used to add functions for version-specific types. */ 18 + #define bini_c 1989 19 + #ifdef __STDC_VERSION__ 20 + # undef bini_c 21 + # if __STDC_VERSION__ == 199901 22 + # define bini_c 1999 23 + # elif __STDC_VERSION__ == 201112 24 + # define bini_c 2011 25 + # elif __STDC_VERSION__ == 201710 26 + # define bini_c 2017 27 + # elif __STDC_VERSION__ == 202000 || __STDC_VERSION__ == 202311 28 + # define bini_c 2023 29 + # else 30 + # warning "bini.h: unknown C version. Falling back to C89." 31 + # define bini_c 1989 32 + # endif 33 + #endif 34 + 35 + 36 + #include <stdint.h> 37 + #include <stddef.h> 38 + #include <stdarg.h> 39 + #include <stdio.h> 40 + #if bini_c >= 1999 41 + # include <stdbool.h> 42 + #endif 43 + 44 + 45 + #ifndef BINI_BUFSIZ 46 + /* Starting size for bini_stream buffers. */ 47 + # define BINI_BUFSIZ 8192 48 + #endif 49 + 50 + #ifndef BINI_STRBUFSIZ 51 + /* Starting size for bini_*print buffers. */ 52 + # define BINI_STRBUFSIZ 1024 53 + #endif 54 + 55 + 56 + /* Execute the `x` macro for each type that bini uses. 57 + Excludes string types (char *). */ 58 + #ifndef BINI_X 59 + # if bini_c >= 1999 60 + # define BINI_X(x) \ 61 + x(char, c) \ 62 + x(short, s) \ 63 + x(int, i) \ 64 + x(long, l) \ 65 + x(long long, ll) \ 66 + x(unsigned char, cu) \ 67 + x(unsigned short, su) \ 68 + x(unsigned int, iu) \ 69 + x(unsigned long, lu) \ 70 + x(unsigned long long, llu) \ 71 + x(float, f) \ 72 + x(double, d) \ 73 + x(bool, b) 74 + # else 75 + # define BINI_X(x) \ 76 + x(char, c) \ 77 + x(short, s) \ 78 + x(int, i) \ 79 + x(long, l) \ 80 + x(unsigned char, cu) \ 81 + x(unsigned short, su) \ 82 + x(unsigned int, iu) \ 83 + x(unsigned long, lu) \ 84 + x(float, f) \ 85 + x(double, d) 86 + # endif 87 + #endif 88 + 89 + 90 + enum { BINI_ERR, BINI_OK }; 91 + enum { BINI_BIGENDIAN, BINI_LITTLEENDIAN }; 92 + enum 93 + { 94 + /* Read values from the end of the buffer. */ 95 + BINI_STACK, 96 + /* Read values from the start of the buffer. */ 97 + BINI_STREAM 98 + }; 99 + 100 + 101 + /* Represents a binary IO stream. */ 102 + struct bini_stream 103 + { 104 + /* Read mode for the stream. Should be either BINI_STACK or BINI_STREAM. */ 105 + int mode; 106 + /* Offset from the start of the buffer in bytes. Only used for BINI_STREAM. */ 107 + size_t off; 108 + size_t cap; 109 + size_t len; 110 + uint8_t *buffer; 111 + }; 112 + 113 + 114 + int bini_getendian(void); 115 + 116 + 117 + /* Initialize a bini_stream, allocating a buffer and 118 + setting each field to its respective defaults. Also 119 + see bini_new(). Free data using bini_end(). */ 120 + int bini_init(struct bini_stream *); 121 + /* Create a new bini_stream, allocating a starting buffer 122 + of BINI_BUFSIZ bytes. To use your own buffer, initialize 123 + your own bini_stream and call bini_init() to fill in the 124 + necessary fields. Free data using bini_close(). */ 125 + struct bini_stream *bini_new(void); 126 + /* End a bini_stream, flushing it if need be and freeing 127 + its buffer. */ 128 + void bini_end(struct bini_stream *); 129 + /* End a bini_stream, flushing it if need be, freeing its 130 + buffer, and frees the provided variable as well. */ 131 + void bini_close(struct bini_stream *bs); 132 + 133 + /* Shift a binary stream, resetting the offset and shifting the array. */ 134 + void bini_shift(struct bini_stream *); 135 + /* Flush a binary stream, clearing its buffer. */ 136 + int bini_flush(struct bini_stream *); 137 + /* Dump the binary stream's buffer to fp. */ 138 + void bini_dump(FILE *fp, struct bini_stream *bs); 139 + 140 + /* Read data from a FILE into a binary stream. */ 141 + size_t bini_fread(struct bini_stream *bs, size_t size, size_t n, FILE *fp); 142 + /* Write data to a FILE into a binary stream. This will 143 + NOT consume data from the stream! Stack/stream mode does 144 + not affect this function. 145 + To consume data, use `bs->len -= bini_fwrite(bs, ...);` */ 146 + size_t bini_fwrite(struct bini_stream *bs, size_t size, size_t n, FILE *fp); 147 + 148 + 149 + #define _bini_rwx(_type, _name) \ 150 + /* Write a `typeof(v)` to the stream. */ \ 151 + void bini_w##_name(struct bini_stream *bs, _type v); \ 152 + /* Read a `typeof(v)` from the stream. */ \ 153 + _type bini_r##_name(struct bini_stream *bs); 154 + BINI_X(_bini_rwx) 155 + #undef _bini_rwx 156 + 157 + void bini_wstrn(struct bini_stream *, const char *, size_t); 158 + void bini_wstr(struct bini_stream *, const char *); 159 + 160 + size_t bini_rstr(struct bini_stream *, char *); 161 + 162 + #if 0 /* unimplemented */ 163 + /* Write to a binary stream. */ 164 + int bini_vprint(struct bini_stream *bs, char *fmt, va_list va); 165 + /* Write to a binary stream. */ 166 + int bini_print(struct bini_stream *bs, char *fmt, ...); 167 + 168 + /* Read from a binary stream. */ 169 + int bini_vscan(struct bini_stream *bs, char *fmt, va_list va); 170 + /* Read from a binary stream. */ 171 + int bini_scan(struct bini_stream *bs, char *fmt, ...); 172 + #endif 173 + 174 + 175 + /* Aliased names that are more akin to libc names. */ 176 + #if defined(bini_libc_names) 177 + 178 + typedef struct bini_stream bstream; 179 + 180 + # define bnew bini_new 181 + # define binit bini_init 182 + # define bend bini_end 183 + # define bclose bini_close 184 + 185 + # define bflush bini_flush 186 + # define bdump bini_dump 187 + 188 + # define bputc bini_wc 189 + # define bputs bini_ws 190 + # define bputi bini_wi 191 + # define bputl bini_wl 192 + # define bputll bini_wll 193 + # define bputcu bini_wcu 194 + # define bputsu bini_wsu 195 + # define bputiu bini_wiu 196 + # define bputlu bini_wlu 197 + # define bputllu bini_wllu 198 + # define bputf bini_wf 199 + # define bputd bini_wd 200 + # define bputb bini_wb 201 + # define bputstr bini_wstr 202 + # define bputstrn bini_wstrn 203 + 204 + # define breadc bini_rc 205 + # define breads bini_rs 206 + # define breadi bini_ri 207 + # define breadl bini_rl 208 + # define breadll bini_rll 209 + # define breadcu bini_rcu 210 + # define breadsu bini_rsu 211 + # define breadiu bini_riu 212 + # define breadlu bini_rlu 213 + # define breadllu bini_rllu 214 + # define breadf bini_rf 215 + # define breadd bini_rd 216 + # define breadb bini_rb 217 + # define breadstr bini_rstr 218 + # define breadstrn bini_rstrn 219 + 220 + #if 0 221 + # define bfprint bini_vprint 222 + # define bprint bini_print 223 + # define bvscan bini_vscan 224 + # define bscan bini_scan 225 + #endif 226 + 227 + #endif 228 + 229 + 230 + #if defined(bini_impl) 231 + 232 + 233 + #include <stdlib.h> 234 + #include <string.h> 235 + 236 + 237 + static 238 + void _bini_dbg(char *fmt, ...) 239 + { 240 + # if bini_dbg 241 + va_list ap; 242 + va_start(ap, fmt); 243 + vfprintf(stderr, fmt, ap); 244 + va_end(ap); 245 + # else 246 + (void)fmt; 247 + # endif 248 + } 249 + 250 + 251 + int bini_getendian(void) 252 + { 253 + const int n = 1; 254 + if (*(char *)&n == 1) 255 + return BINI_LITTLEENDIAN; 256 + return BINI_BIGENDIAN; 257 + } 258 + 259 + 260 + int bini_init(struct bini_stream *bs) 261 + { 262 + bs->buffer = calloc(BINI_BUFSIZ, 1); 263 + if (!bs->buffer) 264 + return BINI_ERR; 265 + bs->off = 0; 266 + bs->len = 0; 267 + bs->cap = BINI_BUFSIZ; 268 + bs->mode = BINI_STREAM; 269 + return BINI_OK; 270 + } 271 + 272 + struct bini_stream *bini_new(void) 273 + { 274 + struct bini_stream *bs = malloc(sizeof(struct bini_stream)); 275 + bini_init(bs); 276 + return bs; 277 + } 278 + 279 + void bini_end(struct bini_stream *bs) 280 + { 281 + bini_flush(bs); 282 + free(bs->buffer); 283 + } 284 + 285 + void bini_close(struct bini_stream *bs) 286 + { 287 + bini_end(bs); 288 + free(bs); 289 + } 290 + 291 + static 292 + #if bini_c >= 1999 293 + inline 294 + #endif 295 + void _bini_shiftleft(struct bini_stream *bs, size_t n) 296 + { 297 + size_t i; 298 + _bini_dbg("shift %d: (len=%lu)\n", n, bs->len); 299 + for (i = 0 ; i < bs->len ; i++) 300 + { 301 + if (i+n >= bs->len) 302 + { 303 + _bini_dbg(" %d: [%d] (out of bounds) to [%d]\n", n, i+n, i); 304 + bs->buffer[i] = 0; 305 + continue; 306 + } 307 + _bini_dbg(" %d: [%d] (%02x '%c') to [%d]\n", 308 + n, 309 + i+n, 310 + bs->buffer[i+n], 311 + bs->buffer[i+n], 312 + i); 313 + bs->buffer[i] = bs->buffer[i+n]; 314 + } 315 + } 316 + 317 + void bini_shift(struct bini_stream *bs) 318 + { 319 + _bini_shiftleft(bs, bs->off); 320 + bs->off = 0; 321 + } 322 + 323 + int bini_flush(struct bini_stream *bs) 324 + { 325 + size_t i; 326 + for (i = 0 ; i < bs->len ; i++) 327 + bs->buffer[i] = 0; 328 + bs->len = 0; 329 + return BINI_OK; 330 + } 331 + 332 + void bini_dump(FILE *fp, struct bini_stream *bs) 333 + { 334 + size_t i; 335 + fprintf(fp, "bini buffer dump:\n"); 336 + fprintf(fp, "buffer: %lu/%lu bytes\n", bs->len, bs->cap); 337 + fprintf(fp, "---\n"); 338 + for (i = 0 ; i < bs->len ; i++) 339 + fprintf(fp, ". %02x '%c'\n", bs->buffer[i], bs->buffer[i]); 340 + fprintf(fp, "---\n"); 341 + } 342 + 343 + static 344 + #if bini_c >= 1999 345 + inline 346 + #endif 347 + int _bini_grow(struct bini_stream *bs, size_t n) 348 + { 349 + uint8_t *newbuf; 350 + 351 + if (bs->off + bs->len + n < bs->cap) 352 + return BINI_OK; 353 + 354 + /* Shifting the array could give enough space to 355 + fit `n`, avoiding the need to realloc. */ 356 + bini_shift(bs); 357 + 358 + if (bs->len + n < bs->cap) 359 + return BINI_OK; 360 + 361 + while (bs->len + n >= bs->cap) 362 + { 363 + newbuf = realloc(bs->buffer, bs->cap * 2); 364 + if (!newbuf) 365 + return BINI_ERR; 366 + bs->buffer = newbuf; 367 + bs->cap *= 2; 368 + } 369 + 370 + return BINI_OK; 371 + } 372 + 373 + size_t bini_fread(struct bini_stream *bs, size_t size, size_t n, FILE *fp) 374 + { 375 + size_t nread; 376 + _bini_grow(bs, size*n); 377 + nread = fread(bs->buffer, size, n, fp); 378 + bs->len += nread; 379 + return nread; 380 + } 381 + 382 + size_t bini_fwrite(struct bini_stream *bs, size_t size, size_t n, FILE *fp) 383 + { 384 + return fwrite(bs->buffer, size, n, fp); 385 + } 386 + 387 + #define _bini_rwx(_type, _name) \ 388 + void bini_w##_name(struct bini_stream *bs, _type v) \ 389 + { \ 390 + size_t i; \ 391 + uint8_t *bytes; \ 392 + int l = bini_getendian() == BINI_LITTLEENDIAN; \ 393 + _bini_dbg("%s: %d (%lu bytes)\n", __FUNCTION__, *(int *)&v, sizeof(v)); \ 394 + _bini_grow(bs, sizeof(v)); \ 395 + bytes = (uint8_t *)&v; \ 396 + for (i = 0 ; i < sizeof(v) ; i++) \ 397 + { \ 398 + _bini_dbg("%s: byte index [%lu] (= %02x '%c') to buffer index %lu\n", \ 399 + __FUNCTION__, \ 400 + l ? sizeof(v)-i-1 : i, \ 401 + bytes[l ? sizeof(v)-i-1 : i], \ 402 + bytes[l ? sizeof(v)-i-1 : i], \ 403 + bs->off+bs->len+i); \ 404 + bs->buffer[bs->off+bs->len+i] = bytes[l ? sizeof(v)-i-1 : i]; \ 405 + } \ 406 + bs->len += i; \ 407 + _bini_dbg("-> %s: %d (%lu bytes)\n", __FUNCTION__, *(int *)bytes, sizeof(v)); \ 408 + } \ 409 + _type bini_r##_name(struct bini_stream *bs) \ 410 + { \ 411 + size_t i; \ 412 + _type v; \ 413 + uint8_t *bytes = (uint8_t *)&v; \ 414 + int l = bini_getendian() == BINI_LITTLEENDIAN; \ 415 + int m = bs->mode == BINI_STREAM; \ 416 + for (i = 0 ; i < sizeof(v) ; i++) \ 417 + { \ 418 + const size_t p = bs->off + (m ? i : (bs->len-sizeof(v)+i)); \ 419 + _bini_dbg("%s: byte index [%lu] from buffer index %lu (= %02x '%c')\n", \ 420 + __FUNCTION__, \ 421 + l ? sizeof(v)-i-1 : i, \ 422 + p, \ 423 + bs->buffer[p], \ 424 + bs->buffer[p]); \ 425 + bytes[l ? sizeof(v)-i-1 : i] = bs->buffer[p]; \ 426 + } \ 427 + if (m) \ 428 + { \ 429 + bs->off += i; \ 430 + /* _bini_shiftleft(bs, i); */ \ 431 + } \ 432 + bs->len -= i; \ 433 + return v; \ 434 + } 435 + BINI_X(_bini_rwx) 436 + #undef _bini_rwx 437 + 438 + 439 + void bini_wstrn(struct bini_stream *bs, const char *str, size_t n) 440 + { 441 + bini_wlu(bs, n); 442 + _bini_grow(bs, n); 443 + while (str) 444 + { 445 + bini_wc(bs, *str); 446 + str++; 447 + } 448 + } 449 + 450 + void bini_wstr(struct bini_stream *bs, const char *str) 451 + { 452 + size_t i, n = strlen(str); 453 + char *s = (char *)str; 454 + bini_wlu(bs, n); 455 + _bini_grow(bs, n); 456 + for (i = 0 ; i < n ; i++) 457 + bini_wc(bs, s[i]); 458 + } 459 + 460 + size_t bini_rstr(struct bini_stream *bs, char *str) 461 + { 462 + size_t i, n = bini_rlu(bs); 463 + for (i = 0 ; i < n ; i++) 464 + str[i] = bini_rc(bs); 465 + return n; 466 + } 467 + 468 + /* 469 + Bini license: BSD 3-Clause. 470 + 471 + Copyright 2025 Emmeline Coats 472 + 473 + Redistribution and use in source and binary forms, with or without 474 + modification, are permitted provided that the following conditions are met: 475 + 476 + 1. Redistributions of source code must retain the above copyright notice, this 477 + list of conditions and the following disclaimer. 478 + 479 + 2. Redistributions in binary form must reproduce the above copyright notice, 480 + this list of conditions and the following disclaimer in the documentation 481 + and/or other materials provided with the distribution. 482 + 483 + 3. Neither the name of the copyright holder nor the names of its contributors 484 + may be used to endorse or promote products derived from this software 485 + without specific prior written permission. 486 + 487 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS โ€œAS ISโ€ AND 488 + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 489 + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 490 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 491 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 492 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 493 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 494 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 495 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 496 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 497 + */ 498 + 499 + #endif /* bini_impl */ 500 + #endif /* __bini__ */
+22
include/keraforge/editor.h
···
··· 1 + #ifndef __kf_editor__ 2 + #define __kf_editor__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + #include <keraforge/world.h> 7 + 8 + 9 + /* State for the world editor */ 10 + struct kf_editor 11 + { 12 + /* The tile that the player is "holding." */ 13 + kf_tileid_t selected_tile; 14 + /* If the world has been changed or not. */ 15 + bool dirty; 16 + }; 17 + 18 + extern struct kf_modal kf_modal_edit; 19 + extern struct kf_modal kf_menu_palette; 20 + 21 + 22 + #endif
+2
include/keraforge/error.h
··· 3 4 5 #include <keraforge/_header.h> 6 7 8 void kf_printbacktrace(FILE *file); 9 10
··· 3 4 5 #include <keraforge/_header.h> 6 + #include <stdio.h> 7 8 9 + /* Print a traceback. Requires GNU extensions to work properly. */ 10 void kf_printbacktrace(FILE *file); 11 12
+5
include/keraforge/fs.h
··· 13 /* Write binary file contents. */ 14 int kf_writebin(char *filename, u8 *data, size_t len); 15 16 17 #endif
··· 13 /* Write binary file contents. */ 14 int kf_writebin(char *filename, u8 *data, size_t len); 15 16 + /* Compress a file using LZMA (XZ). */ 17 + int kf_compress(char *infile, char *outfile); 18 + /* Decompress a file using LZMA (XZ). */ 19 + int kf_decompress(char *infile, char *outfile); 20 + 21 22 #endif
+80
include/keraforge/graphics.h
··· 3 4 5 #include <keraforge/_header.h> 6 7 8 /* Number of frames since the game opened. 9 Not consistent enough for precise timing! Use deltatime (kf_dts/kf_dtms) instead. */ 10 extern u64 kf_frame; ··· 14 extern f32 kf_dtms; 15 /* Deltatime in seconds. */ 16 extern f32 kf_dts; 17 18 19 #endif
··· 3 4 5 #include <keraforge/_header.h> 6 + #include <keraforge/math.h> 7 + #include <raylib.h> 8 9 10 + /* Represents either a model or a menu. */ 11 + struct kf_modal 12 + { 13 + /* The title of this modal. */ 14 + char *name; 15 + /* Called when this modal is closed. */ 16 + void (*exit)(void); 17 + /* Called when this modal is opened/ */ 18 + void (*init)(void); 19 + /* Called every frame before rendering anything. */ 20 + void (*update)(void); 21 + /* Called every frame to render the world. */ 22 + void (*render_world)(void); 23 + /* Called every frame to render the UI. */ 24 + void (*render_ui)(void); 25 + /* Any extra data this needed. 26 + You are expected to allocate and free this yourself in init/exit respectively. */ 27 + void *data; 28 + }; 29 + 30 + /* Stores data about the window and graphics. None of this data is stored between saves/loads. */ 31 + struct kf_window 32 + { 33 + /* World camera. */ 34 + Camera2D cam; 35 + /* Target FPS. Do not mutate this yourself, use kf_settargetfps(). */ 36 + int target_fps; 37 + /* Indicates whether or not a window is open. */ 38 + bool running; 39 + 40 + /* Pointer to the current state. */ 41 + struct kf_state *state; 42 + /* Pointer to the current room. */ 43 + struct kf_world *room; 44 + /* Pointer to the current player. */ 45 + struct kf_actor *player; 46 + 47 + /* X,Y coords of the selected tile */ 48 + struct kf_vec2(u32) select; 49 + 50 + /* Current modal. Do not mutate this yourself, use kf_setmodal(). */ 51 + struct kf_modal *modal; 52 + /* Current menu. Do not mutate this yourself, use kf_setmenu(). */ 53 + struct kf_modal *menu; 54 + 55 + /* Font used for kf_drawtext functions. */ 56 + Font font; 57 + /* Base font size to use. */ 58 + int fontsize; 59 + }; 60 + 61 + /* Global window instance. */ 62 + extern struct kf_window kf_window; 63 /* Number of frames since the game opened. 64 Not consistent enough for precise timing! Use deltatime (kf_dts/kf_dtms) instead. */ 65 extern u64 kf_frame; ··· 69 extern f32 kf_dtms; 70 /* Deltatime in seconds. */ 71 extern f32 kf_dts; 72 + 73 + 74 + /* Measure text using the default font. */ 75 + int kf_measuretext(int size, char *text); 76 + /* Draw text using the default font. */ 77 + void kf_drawtext(Color c, int x, int y, int size, char *text); 78 + /* Draw text with a shadow using the default font. */ 79 + void kf_drawtextshadowed(Color c, int x, int y, int size, char *text); 80 + 81 + 82 + /* Open a window via Raylib. This should only be called once! */ 83 + void kf_openwindow(char *title); 84 + /* Start the render loop. */ 85 + void kf_startwindow(void); 86 + /* Close the window. */ 87 + void kf_closewindow(void); 88 + /* Set the target FPS. Prefer this over manually setting kf_window.target_fps. */ 89 + void kf_settargetfps(int fps); 90 + /* Set the current modal. */ 91 + void kf_setmodal(struct kf_modal *modal); 92 + /* Set the current menu. */ 93 + void kf_setmenu(struct kf_modal *menu); 94 + 95 + /* Default modal. */ 96 + extern struct kf_modal kf_modal_play; 97 98 99 #endif
+6 -1
include/keraforge/input.h
··· 6 #include <raylib.h> 7 8 9 #define MOUSE_BUTTON_UNKNOWN ((MouseButton)-1) 10 #define GAMEPAD_AXIS_UNKNOWN ((GamepadAxis)-1) 11 12 #define KF_INPUTBIND_MAX UINT8_MAX ··· 17 /* Struct-of-Arrays for keybindings. */ 18 struct _kf_inputbinds 19 { 20 - kf_inputbind_t count; /* must start at 1. 0 is the `none` keybind. */ 21 char *id[KF_INPUTBIND_MAX]; 22 KeyboardKey key[KF_INPUTBIND_MAX]; 23 KeyboardKey alt[KF_INPUTBIND_MAX];
··· 6 #include <raylib.h> 7 8 9 + /* Represents an unknown mouse button. Provided by Keraforge, not by Raylib. */ 10 #define MOUSE_BUTTON_UNKNOWN ((MouseButton)-1) 11 + /* Represents an unknown gamepad axis. Provided by Keraforge, not by Raylib. */ 12 #define GAMEPAD_AXIS_UNKNOWN ((GamepadAxis)-1) 13 14 #define KF_INPUTBIND_MAX UINT8_MAX ··· 19 /* Struct-of-Arrays for keybindings. */ 20 struct _kf_inputbinds 21 { 22 + /* Amount of input bindings registered. 23 + Must start at 1. 0 is the `none` keybind. */ 24 + kf_inputbind_t count; 25 + /* String IDs of each binding. */ 26 char *id[KF_INPUTBIND_MAX]; 27 KeyboardKey key[KF_INPUTBIND_MAX]; 28 KeyboardKey alt[KF_INPUTBIND_MAX];
+40
include/keraforge/inputbinds.h
···
··· 1 + #ifndef __kf_inputbinds__ 2 + #define __kf_inputbinds__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + #include <keraforge/input.h> 7 + 8 + 9 + /* Keraforge default keybinds. */ 10 + extern kf_inputbind_t 11 + kf_inputbind_move_up, 12 + kf_inputbind_move_down, 13 + kf_inputbind_move_left, 14 + kf_inputbind_move_right, 15 + kf_inputbind_run, 16 + 17 + kf_inputbind_ui_up, 18 + kf_inputbind_ui_down, 19 + kf_inputbind_ui_left, 20 + kf_inputbind_ui_right, 21 + kf_inputbind_select, 22 + kf_inputbind_cancel, 23 + 24 + kf_inputbind_pause, 25 + kf_inputbind_palette, 26 + 27 + kf_inputbind_zoom_reset, 28 + kf_inputbind_zoom_in, 29 + kf_inputbind_zoom_out, 30 + 31 + kf_inputbind_toggle_fps_limit, 32 + kf_inputbind_toggle_editor 33 + ; 34 + 35 + 36 + /* Load default input bindings. */ 37 + void kf_loaddefaultbinds(void); 38 + 39 + 40 + #endif
+71
include/keraforge/log.h
···
··· 1 + #ifndef __kf_log__ 2 + #define __kf_log__ 3 + 4 + 5 + #include <stdio.h> /* fprintf, stderr */ 6 + #include <stdlib.h> /* exit */ 7 + #include <stdarg.h> 8 + 9 + 10 + /* Log a message. */ 11 + void kf_vlog(char *level, char *fmt, va_list va); 12 + /* Log a message. */ 13 + void kf_log(char *level, char *fmt, ...); 14 + /* Log a debug message. */ 15 + void kf_logdbg(char *fmt, ...); 16 + /* Log an info message. */ 17 + void kf_loginfo(char *fmt, ...); 18 + /* Log a error message. */ 19 + void kf_logerr(char *fmt, ...); 20 + 21 + 22 + /* Throw a formatted error message without printing a traceback or exiting. */ 23 + #define KF_THROWSOFTER(MSG, ...) \ 24 + kf_logerr("\x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m" MSG, __FILE__, __LINE__, __FUNCTION_NAME__, __VA_ARGS__) 25 + 26 + /* Throw a formatted error message and print a traceback if available. */ 27 + #define KF_THROWSOFT(MSG, ...) \ 28 + do \ 29 + { \ 30 + kf_logerr("\x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m" MSG, __FILE__, __LINE__, __FUNCTION_NAME__, __VA_ARGS__); \ 31 + kf_printbacktrace(stderr); \ 32 + } \ 33 + while (0) 34 + 35 + /* Throw a formatted error message, print a traceback if available, and aborts. */ 36 + #define KF_THROW(MSG, ...) \ 37 + do \ 38 + { \ 39 + kf_logerr("\x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m" MSG, __FILE__, __LINE__, __FUNCTION_NAME__ __VA_OPT__(,) __VA_ARGS__); \ 40 + kf_printbacktrace(stderr); \ 41 + abort(); \ 42 + } \ 43 + while (0) 44 + 45 + 46 + #ifdef KF_SANITY_CHECKS 47 + /* Indicate that the given expression should never resolve to false and throw an error if it manages to. */ 48 + # define KF_SANITY_CHECK(EXPR, MSG, ...) \ 49 + do \ 50 + { \ 51 + if (!(EXPR)) \ 52 + { \ 53 + KF_THROW("sanity check failed: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s:\x1b[0m " MSG, __FILE__, __LINE__, __FUNCTION_NAME__ __VA_OPT__(,) __VA_ARGS__); \ 54 + } \ 55 + } \ 56 + while (0) 57 + 58 + /* Indicate that this branch of code should never be reached. Throws an error if it ever manages to. */ 59 + # define KF_UNREACHABLE(MSG, ...) \ 60 + do \ 61 + { \ 62 + KF_THROW("unreachable: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s:\x1b[0m " MSG, __FILE__, __LINE__, __FUNCTION_NAME__ __VA_OPT__(,) __VA_ARGS__); \ 63 + } \ 64 + while (0) 65 + #else 66 + # define KF_SANITY_CHECK(EXPR, MSG, ...) do { ; } while (0) 67 + # define KF_UNREACHABLE(MSG, ...) do { ; } while (0) 68 + #endif 69 + 70 + 71 + #endif
+15
include/keraforge/player.h
···
··· 1 + #ifndef __kf_player__ 2 + #define __kf_player__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + #include <keraforge/actor.h> 7 + 8 + 9 + void kf_player_tick(struct kf_actor *self); 10 + void kf_player_draw(struct kf_actor *self); 11 + 12 + void kf_player_serialize(struct kf_actor *self, struct bini_stream *bs); 13 + void kf_player_deserialize(struct kf_actor *self, struct bini_stream *bs); 14 + 15 + #endif
+3
include/keraforge/sprites.h
··· 10 Can be used for animations or texture atlases. */ 11 struct kf_spritesheet 12 { 13 Texture2D texture; 14 u16 spritewidth, spriteheight; 15 u32 nsprites; 16 }; 17
··· 10 Can be used for animations or texture atlases. */ 11 struct kf_spritesheet 12 { 13 + /* The texture to pull sprites from. */ 14 Texture2D texture; 15 + /* Width and height of each sprite in this sheet. */ 16 u16 spritewidth, spriteheight; 17 + /* Number of sprites in this sheet. */ 18 u32 nsprites; 19 }; 20
+25
include/keraforge/state.h
···
··· 1 + #ifndef __kf_state__ 2 + #define __kf_state__ 3 + 4 + 5 + #include <keraforge/actor.h> 6 + 7 + 8 + #define KF_NPCPOOL_SIZE 1024 9 + 10 + 11 + /* Stores global variables to be saved alongside the world. Use this for save data. */ 12 + struct kf_state 13 + { 14 + /* Do not modify this and do not relocate it. See kf_world for an explanation. */ 15 + u16 revision; 16 + }; 17 + 18 + 19 + /* Save a state. */ 20 + int kf_state_save(struct kf_state *state); 21 + /* Load a state. Returns 1 if the state was freshly created and 0 if an existing state was loaded. */ 22 + int kf_state_load(struct kf_state **pstate); 23 + 24 + 25 + #endif
+12
include/keraforge/string.h
···
··· 1 + #ifndef __kf_string__ 2 + #define __kf_string__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + 7 + 8 + /* Duplicate a string. Owner is expected to free this. */ 9 + char *kf_strdup(char *str); 10 + 11 + 12 + #endif
+78
include/keraforge/time.h
···
··· 1 + #ifndef __kf_time__ 2 + #define __kf_time__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + #include <time.h> 7 + 8 + 9 + /* Execute the given statement(s) and time them, printing 10 + the amount of time it took with loginfo using this format: 11 + "took [<duration>] to <_msg>" */ 12 + #define kf_timeit(_msg, ...) \ 13 + do \ 14 + { \ 15 + struct kf_timer __timer = { \ 16 + .mode = kf_timermode_stopwatch, \ 17 + }; \ 18 + kf_timer_start(&__timer); \ 19 + __VA_ARGS__; \ 20 + kf_timer_stop(&__timer); \ 21 + char __timerstr[BUFSIZ] = {0}; \ 22 + kf_timer_snprint(&__timer, __timerstr, sizeof(__timerstr)); \ 23 + kf_loginfo("took [%s] to %s", __timerstr, _msg); \ 24 + } \ 25 + while (0) 26 + 27 + 28 + enum kf_timermode 29 + { 30 + /* Manually stopped timer. Upon stopping, age is 31 + updated to contain stop-start, then the callback 32 + is invoked if present. Ticking is not necessary. */ 33 + kf_timermode_stopwatch, 34 + /* Repeating timer. Every time length is reached, 35 + the callback will be invoked if present. stop is 36 + unused until kf_timer_stop() is called, which 37 + will prevent further repeats of this timer. 38 + Ticking is necessary. */ 39 + kf_timermode_repeat, 40 + /* Timer that stops after reaching length. After 41 + reaching length, the callback will be invoked 42 + if present. Ticking is necessary. */ 43 + kf_timermode_oneshot, 44 + }; 45 + 46 + struct kf_timer 47 + { 48 + /* The mode for this timer. */ 49 + enum kf_timermode mode; 50 + /* The time when this timer was last ticked. */ 51 + time_t now; 52 + /* When this timer started. */ 53 + time_t start; 54 + /* When this timer stopped. Will default to 0, 55 + indicating that the timer is still running. */ 56 + time_t stop; 57 + /* How long this timer has lasted for. 58 + Stopwatch: This will be stop-start, updated 59 + once kf_timer_stop() is called. 60 + Repeat: This will be reset to 0 every iteration. 61 + One Shot: This will be reset to 0 with 62 + kf_timer_start(). 63 + */ 64 + time_t age; 65 + /* How long this timer lasts for. */ 66 + time_t length; 67 + /* Executed when this timer finishes. */ 68 + void (*callback)(struct kf_timer *timer); 69 + }; 70 + 71 + 72 + void kf_timer_start(struct kf_timer *timer); 73 + void kf_timer_tick(struct kf_timer *timer); 74 + void kf_timer_stop(struct kf_timer *timer); 75 + size_t kf_timer_snprint(struct kf_timer *timer, char *buf, size_t bufsiz); 76 + 77 + 78 + #endif
+5 -2
include/keraforge/ui.h
··· 21 /* Padding for the panel. */ 22 int xpadding, ypadding; 23 }; 24 25 - /* Get a pointer to the global UI config. */ 26 - struct kf_uiconfig *kf_ui_getconfig(void); 27 28 /* Draw a panel with the given title. Returns the Y position for the next line of text. 29 title: Title of the panel, drawn at the top of the panel. Must be null-terminated. */
··· 21 /* Padding for the panel. */ 22 int xpadding, ypadding; 23 }; 24 + /* Global UI config. */ 25 + extern struct kf_uiconfig kf_uiconfig; 26 27 + /* Performs non-compile-time initialization for kf_uiconfig. 28 + This is called automatically by kf_openwindow. */ 29 + void kf_ui_init(void); 30 31 /* Draw a panel with the given title. Returns the Y position for the next line of text. 32 title: Title of the panel, drawn at the top of the panel. Must be null-terminated. */
+31 -12
include/keraforge/world.h
··· 8 #include <raylib.h> 9 10 11 #define KF_TILE_SIZE_PX 16 12 13 - #define KF_TILEID_MAX UINT16_MAX 14 typedef u16 kf_tileid_t; 15 - /* Used to store connectivity variant. */ 16 - typedef u16 kf_tiledatum_t; 17 18 19 struct kf_actor; /* Forward declaration */ 20 21 22 - #define KF_TILEMASK_NORTH (0x0001) 23 - #define KF_TILEMASK_WEST (0x0010) 24 - #define KF_TILEMASK_EAST (0x0100) 25 - #define KF_TILEMASK_SOUTH (0x1000) 26 27 28 /* Represents a singular tile in the world. */ 29 struct kf_tile 30 { 31 - kf_tileid_t subid; 32 - kf_tileid_t id; 33 - kf_tiledatum_t data; 34 - }; 35 36 /* Represents a world (or a subworld, often called "rooms"). */ 37 struct kf_world ··· 65 struct kf_vec2(u32) sprite[KF_TILEID_MAX]; 66 /* Whether or not this tile has collision. */ 67 bool collide[KF_TILEID_MAX]; 68 }; 69 /* Struct-of-arrays for tiles. */ 70 extern struct _kf_tiles kf_tiles; ··· 77 struct kf_spritesheet *sheet; 78 struct kf_vec2(u32) sprite; 79 bool collide; 80 }; 81 82 kf_tileid_t kf_addtile(struct kf_tile_opts opts); 83 #define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ })) 84 85 /* Create a world using the given width and height. ··· 90 size_t kf_world_getsize(struct kf_world *world); 91 92 /* Get the sprite offset for a given tile datum. */ 93 - struct kf_vec2(u32) kf_getspritefortilebitmask(kf_tiledatum_t t); 94 95 /* Update a tile and optionally its neighbours. */ 96 void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours); ··· 103 104 /* Draw the part of the world visible to the given camera. */ 105 void kf_world_draw(struct kf_world *world, Camera2D camera); 106 107 #endif
··· 8 #include <raylib.h> 9 10 11 + /* Size of tiles in pixels. */ 12 #define KF_TILE_SIZE_PX 16 13 14 + /* Number of bits to use for storing tile IDs. See: struct kf_tile. */ 15 + #define KF_TILE_IDWIDTH 10 16 + /* Number of bits to use for storing tile datum. See: struct kf_tile. */ 17 + #define KF_TILE_DATAWIDTH 4 18 + 19 + /* Number of max tiles. This should be (2^KF_TILE_IDWIDTH-1). */ 20 + #define KF_TILEID_MAX (1023) 21 + 22 + /* Used to store tile IDs. Stored in 10 bits; max value is 1023! */ 23 typedef u16 kf_tileid_t; 24 + /* Used to store connectivity variant. Stored in 4 bits; max value is 15! */ 25 + typedef u8 kf_tiledatum_t; 26 27 28 struct kf_actor; /* Forward declaration */ 29 30 31 + #define KF_TILEMASK_NORTH (0b0001) 32 + #define KF_TILEMASK_WEST (0b0010) 33 + #define KF_TILEMASK_EAST (0b0100) 34 + #define KF_TILEMASK_SOUTH (0b1000) 35 36 37 /* Represents a singular tile in the world. */ 38 struct kf_tile 39 { 40 + kf_tileid_t subid : KF_TILE_IDWIDTH; 41 + kf_tileid_t id : KF_TILE_IDWIDTH; 42 + kf_tiledatum_t data : KF_TILE_DATAWIDTH; 43 + } __attribute__((packed)); 44 45 /* Represents a world (or a subworld, often called "rooms"). */ 46 struct kf_world ··· 74 struct kf_vec2(u32) sprite[KF_TILEID_MAX]; 75 /* Whether or not this tile has collision. */ 76 bool collide[KF_TILEID_MAX]; 77 + /* Whether or not this tile has a transparent or transluscent texture. */ 78 + bool transparent[KF_TILEID_MAX]; 79 }; 80 /* Struct-of-arrays for tiles. */ 81 extern struct _kf_tiles kf_tiles; ··· 88 struct kf_spritesheet *sheet; 89 struct kf_vec2(u32) sprite; 90 bool collide; 91 + bool transparent; 92 }; 93 94 + /* Register a tile. */ 95 kf_tileid_t kf_addtile(struct kf_tile_opts opts); 96 + /* Register a tile. */ 97 #define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ })) 98 99 /* Create a world using the given width and height. ··· 104 size_t kf_world_getsize(struct kf_world *world); 105 106 /* Get the sprite offset for a given tile datum. */ 107 + struct kf_vec2(u32) kf_getspritefordatum(kf_tiledatum_t t); 108 109 /* Update a tile and optionally its neighbours. */ 110 void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours); ··· 117 118 /* Draw the part of the world visible to the given camera. */ 119 void kf_world_draw(struct kf_world *world, Camera2D camera); 120 + 121 + /* Save a world to map.bin(.xz). Set outfile to NULL to use the default. */ 122 + int kf_world_save(struct kf_world *world, bool compress, char *outfile); 123 + /* Load a world from a map.bin(.xz). Set infile to NULL to use the default. */ 124 + int kf_world_load(struct kf_world **world, bool compressed, char *infile); 125 126 #endif
+10
include/keraforge.h
··· 3 4 5 #include <keraforge/_header.h> 6 #include <keraforge/actor.h> 7 #include <keraforge/error.h> 8 #include <keraforge/fs.h> 9 #include <keraforge/graphics.h> 10 #include <keraforge/input.h> 11 #include <keraforge/math.h> 12 #include <keraforge/sprites.h> 13 #include <keraforge/ui.h> 14 #include <keraforge/world.h> 15
··· 3 4 5 #include <keraforge/_header.h> 6 + 7 + #include <keraforge/log.h> 8 + 9 #include <keraforge/actor.h> 10 + #include <keraforge/bini.h> 11 + #include <keraforge/editor.h> 12 #include <keraforge/error.h> 13 #include <keraforge/fs.h> 14 #include <keraforge/graphics.h> 15 #include <keraforge/input.h> 16 + #include <keraforge/inputbinds.h> 17 #include <keraforge/math.h> 18 + #include <keraforge/player.h> 19 #include <keraforge/sprites.h> 20 + #include <keraforge/state.h> 21 + #include <keraforge/string.h> 22 + #include <keraforge/time.h> 23 #include <keraforge/ui.h> 24 #include <keraforge/world.h> 25
+23
readme
··· 54 Develop 55 ------- 56 57 Build "system" is contained in a folder of shell scripts, 58 `scripts`. You can run these with the `run.sh` script to 59 run multiple tasks in order. Generally: 60 `sh run.sh build run` 61 is all you'll need to run while developing. 62 63 License 64 -------
··· 54 Develop 55 ------- 56 57 + Libraries (Debian): 58 + Raylib: 59 + libasound2-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxcursor-dev libxinerama-dev libwayland-dev libxkbcommon-dev 60 + Keraforge: 61 + liblzma-dev 62 + 63 + Initialise a development environment: 64 + `sh run.sh init` 65 + 66 Build "system" is contained in a folder of shell scripts, 67 `scripts`. You can run these with the `run.sh` script to 68 run multiple tasks in order. Generally: 69 `sh run.sh build run` 70 is all you'll need to run while developing. 71 + 72 + Please note that I will be hesitant to accept PRs to this 73 + codebase. I am very nitpicky with my code style and if your 74 + PR does not match it (or if it goes out-of-scope, adds 75 + useless/redundant functions, further pollute global scope, 76 + etc), then I will likely decline the PR or request a number 77 + of changes. Ideally, please communicate with me before 78 + submitting a PR just for it to get closed. You can find my 79 + contact information on my website. I don't want you wasting 80 + your time or effort! 81 + 82 + Additionally, please read through <etc/style.txt> before 83 + attempting to contribute. It'll make your life much easier 84 + if you understand my style *before* attempting a PR. 85 86 License 87 -------
+2 -2
scripts/_config.sh
··· 10 export KF_DEBUG_CFLAGS="-g -DKF_SANITY_CHECKS" 11 export KF_DEBUG_LFLAGS="-g -rdynamic" 12 13 - export CFLAGS="-Wall -Wextra -Werror -std=c99 -Iinclude/ -c -DKF_GNU $KF_DEBUG_CFLAGS" 14 - export LFLAGS="-lraylib -lm -lGL -lpthread -ldl -lrt -lX11 $KF_DEBUG_LFLAGS"
··· 10 export KF_DEBUG_CFLAGS="-g -DKF_SANITY_CHECKS" 11 export KF_DEBUG_LFLAGS="-g -rdynamic" 12 13 + export CFLAGS="-Wall -Wextra -Werror -std=c99 -Iinclude/ -Iraylib/src/ -c -DKF_GNU $KF_DEBUG_CFLAGS" 14 + export LFLAGS="raylib/src/libraylib.a -lm -lGL -lpthread -ldl -lrt -lX11 -llzma $KF_DEBUG_LFLAGS"
+27
scripts/build-tools.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + . scripts/_config.sh 5 + 6 + mkdir -p build/tools/ 7 + 8 + echo ": compiling keraforge" 9 + for f in `find src/ -name '*.c'` 10 + do 11 + ff=$(echo "$f" | tr '/' '_') 12 + gcc $CFLAGS $f -o build/${ff%.c}.o 13 + done 14 + 15 + echo ": compiling tools" 16 + for f in `find tools/ -name '*.c'` 17 + do 18 + ff=$(echo "$f" | tr '/' '_') 19 + o=build/tools/${ff%.c}.o 20 + echo ": tool: $f->build/tools/${ff%.c}" 21 + gcc $CFLAGS $f -o $o 22 + gcc \ 23 + -o build/tools/${ff%.c} \ 24 + `find build -maxdepth 1 -name '*.o' ! -name '*src_main.o'` \ 25 + $o \ 26 + $LFLAGS 27 + done
+20
scripts/init.sh
···
··· 1 + #!/usr/bin/env sh 2 + 3 + set -e 4 + 5 + RAYLIB_VERSION=5.5 6 + 7 + if [ ! -e "raylib.tar.gz" ] 8 + then 9 + curl -Lo raylib.tar.gz https://github.com/raysan5/raylib/archive/refs/tags/$RAYLIB_VERSION.tar.gz 10 + fi 11 + 12 + if [ ! -e "raylib/" ] 13 + then 14 + tar -xkf raylib.tar.gz && mv -v raylib-$RAYLIB_VERSION raylib 15 + fi 16 + 17 + cd raylib/src 18 + make 19 + cd ../.. 20 +
+10
scripts/run-valgrind.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + if [ -e "./build/keraforge" ] 5 + then 6 + valgrind --track-origins=yes --leak-check=full --show-leak-kinds=all -s ./build/keraforge 7 + else 8 + echo ": error: no build available" 9 + exit 1 10 + fi
+151 -13
src/actor.c
··· 1 #include <keraforge.h> 2 #include <stdlib.h> 3 #include <raymath.h> 4 5 6 - struct kf_actor *kf_actor_new(struct kf_spritesheet sprites, f32 width, f32 height, bool collides) 7 { 8 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor)); 9 10 - actor->sprites = sprites; 11 - actor->size.x = width; 12 - actor->size.y = height; 13 - actor->collide = collides; 14 15 actor->speed = 25; 16 actor->speedmod = 1; ··· 18 actor->pointing = kf_north; 19 20 return actor; 21 } 22 23 struct kf_spritesheet kf_actor_loadspritesheet(char *filename) ··· 119 else if (self->vel.y) 120 self->vel.y /= self->friction; 121 122 - if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone) 123 - self->speedmod = 1; 124 - else if (self->speedmod) 125 - self->speedmod -= self->friction * self->friction * dt; 126 } 127 128 void kf_actor_draw(struct kf_actor *actor) 129 { 130 int x = 0, y = 0; 131 132 switch (actor->pointing) ··· 137 case kf_north: y = 3; break; 138 } 139 140 - if (actor->vel.x != 0 || actor->vel.y != 0) 141 x += 7; /* walk sprites */ 142 143 - x += (int)(kf_s * 5) % 4; 144 145 - /* todo: run and jump */ 146 147 - kf_drawsprite(&actor->sprites, actor->pos.x, actor->pos.y, x, y); 148 }
··· 1 + #include "keraforge/actor.h" 2 + #include "keraforge/fs.h" 3 #include <keraforge.h> 4 #include <stdlib.h> 5 #include <raymath.h> 6 + #include <string.h> 7 8 9 + struct kf_actorregistry kf_actorregistry = {0}; 10 + struct kf_actor *kf_actors = NULL, *kf_actors_last = NULL; 11 + u32 kf_actor_count = 0; 12 + 13 + 14 + struct kf_actor *kf_actor_new(char *key) 15 { 16 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor)); 17 + kf_actor_count++; 18 19 + if (!kf_actors) 20 + { 21 + kf_actors = kf_actors_last = actor; 22 + } 23 + else 24 + { 25 + actor->prev = kf_actors_last; 26 + kf_actors_last->next = actor; 27 + kf_actors_last = actor; 28 + } 29 + 30 + actor->id = kf_actor_getregistryid(key); 31 + actor->size.x = 10; 32 + actor->size.y = 10; 33 + actor->collide = true; 34 35 actor->speed = 25; 36 actor->speedmod = 1; ··· 38 actor->pointing = kf_north; 39 40 return actor; 41 + } 42 + 43 + int kf_actor_register(char *key) 44 + { 45 + int id = kf_actorregistry.count++; 46 + kf_actorregistry.key[id] = key; 47 + return id; 48 } 49 50 struct kf_spritesheet kf_actor_loadspritesheet(char *filename) ··· 146 else if (self->vel.y) 147 self->vel.y /= self->friction; 148 149 + // if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone) 150 + // self->speedmod = 1; 151 + // else if (self->speedmod) 152 + // self->speedmod -= self->friction * self->friction * dt; 153 } 154 155 void kf_actor_draw(struct kf_actor *actor) 156 { 157 + int frames = 4; 158 int x = 0, y = 0; 159 160 switch (actor->pointing) ··· 165 case kf_north: y = 3; break; 166 } 167 168 + bool moving = actor->vel.x != 0 || actor->vel.y != 0; 169 + 170 + if (actor->running && moving) 171 + { 172 + y += 5; /* run sprites */ 173 + frames = 6; 174 + } 175 + else if (moving) 176 + { 177 x += 7; /* walk sprites */ 178 + } 179 180 + /* todo: jump */ 181 + 182 + x += (int)(kf_s * (frames+1)) % frames; 183 + 184 + kf_drawsprite(&kf_actorregistry.sprite[actor->id], actor->pos.x, actor->pos.y, x, y); 185 + } 186 187 + int kf_actor_getregistryid(char *key) 188 + { 189 + size_t l = strlen(key); 190 + for (int i = 0 ; i < kf_actorregistry.count ; i++) 191 + { 192 + char *c = kf_actorregistry.key[i]; 193 + size_t cl = strlen(c); 194 + if (strncmp(c, key, l>cl?l:cl) == 0) 195 + { 196 + return i; 197 + } 198 + } 199 + return -1; 200 + } 201 202 + #define _KF_ACTORFILE_TMP "data/tmp/actors.bin" 203 + #define _KF_ACTORFILE_XZ "data/actors.bin.xz" 204 + #define _KF_ACTORFILE "data/actors.bin" 205 + 206 + int kf_saveactors(void) 207 + { 208 + struct bini_stream *bs = bini_new(); 209 + 210 + /* kf_actor_count includes unserialized actors, so we have to manually count. */ 211 + size_t nactors = 0; 212 + for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 213 + { 214 + if (actor->id == -1) 215 + continue; 216 + nactors++; 217 + } 218 + bini_wlu(bs, nactors); 219 + kf_logdbg("expecting to save %lu actors", nactors); 220 + 221 + for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 222 + { 223 + if (actor->id == -1) 224 + continue; 225 + bini_wstr(bs, kf_actorregistry.key[actor->id]); 226 + kf_actorregistry.serialize[actor->id](actor, bs); 227 + } 228 + 229 + kf_logdbg("serialized %d actors (%lu bytes)", nactors, bs->len); 230 + 231 + // char *outfile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE; 232 + char *outfile = _KF_ACTORFILE; 233 + if (!kf_writebin(outfile, bs->buffer, bs->len)) 234 + KF_THROW("failed to write actors to %s", outfile); 235 + 236 + return 1; 237 + } 238 + 239 + int kf_loadactors(void) 240 + { 241 + // char *infile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE; 242 + char *infile = _KF_ACTORFILE; 243 + 244 + if (!kf_exists(infile)) 245 + { 246 + kf_logdbg("actors.bin nonexistent, creating..."); 247 + kf_saveactors(); 248 + return 1; 249 + } 250 + 251 + size_t len = 0; 252 + struct bini_stream bs = { 253 + .mode = BINI_STREAM, 254 + .buffer = kf_readbin(infile, &len), 255 + }; 256 + if (!bs.buffer) 257 + KF_THROW("failed to read/open %s", infile); 258 + bs.cap = len; 259 + bs.len = len; 260 + kf_logdbg("loaded actors into binary stream: len=%lu", len); 261 + 262 + const size_t nactors = bini_rlu(&bs); 263 + kf_logdbg("expecting to load %lu actors", nactors); 264 + 265 + for (size_t i = 0 ; i < nactors ; i++) 266 + { 267 + kf_logdbg("loading actor #%lu", i); 268 + char key[4096]; 269 + size_t n = bini_rstr(&bs, key); 270 + key[n] = '\0'; 271 + char *keycpy = kf_strdup(key); 272 + kf_logdbg(" key: %s", keycpy); 273 + int id = kf_actor_getregistryid(keycpy); 274 + kf_logdbg(" id: %d", id); 275 + if (id == -1) 276 + continue; 277 + struct kf_actor *actor = kf_actor_new(keycpy); 278 + kf_actorregistry.deserialize[id](actor, &bs); 279 + } 280 + 281 + kf_logdbg("loaded %d actors", nactors); 282 + 283 + free(bs.buffer); 284 + 285 + return 1; 286 }
+4
src/bini.c
···
··· 1 + #ifndef bini_impl 2 + #define bini_impl 3 + #endif 4 + #include <keraforge/bini.h>
+143
src/editor.c
···
··· 1 + #include "keraforge/editor.h" 2 + #include "keraforge/graphics.h" 3 + #include "keraforge/world.h" 4 + #include <keraforge.h> 5 + 6 + 7 + static 8 + void _kf_modal_edit_init(void) 9 + { 10 + struct kf_editor *editor = malloc(sizeof(struct kf_editor)); 11 + editor->selected_tile = 0; 12 + editor->dirty = false; 13 + kf_window.modal->data = editor; 14 + } 15 + 16 + static 17 + void _kf_modal_edit_exit(void) 18 + { 19 + bool d = ((struct kf_editor *)kf_window.modal->data)->dirty; 20 + 21 + kf_logdbg("exiting editor, world is %s", d ? "dirty" : "not dirty"); 22 + 23 + if (d) 24 + kf_timeit("save world", kf_world_save(kf_window.room, true, NULL)); 25 + 26 + free(kf_window.modal->data); 27 + kf_window.modal->data = NULL; 28 + } 29 + 30 + static 31 + void _kf_modal_edit_update(void) 32 + { 33 + /* Inherit from kf_modal_play. */ 34 + kf_modal_play.update(); 35 + 36 + if (kf_checkinputpress(kf_inputbind_palette) && kf_window.modal == &kf_modal_edit) 37 + kf_setmenu(&kf_menu_palette); 38 + } 39 + 40 + static 41 + void _kf_modal_edit_render_world(void) 42 + { 43 + /* Inherit from kf_modal_play. */ 44 + kf_modal_play.render_world(); 45 + 46 + if (kf_window.menu) 47 + return; 48 + 49 + struct kf_vec2(u32) select = kf_window.select; 50 + 51 + if (select.x >= kf_window.room->width || select.y >= kf_window.room->height) 52 + return; 53 + 54 + struct kf_editor *data = (struct kf_editor *)kf_window.modal->data; 55 + struct kf_tile *t = kf_world_gettile(kf_window.room, select.x, select.y); 56 + if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) 57 + { 58 + t->id = data->selected_tile; 59 + data->dirty = true; 60 + kf_world_updatetile(kf_window.room, select.x, select.y, true); 61 + } 62 + else if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) 63 + { 64 + t->subid = data->selected_tile; 65 + data->dirty = true; 66 + } 67 + else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) 68 + { 69 + data->selected_tile = t->id; 70 + } 71 + 72 + DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE); 73 + char *text = (char *)TextFormat("%d [%d] (%d,%d)", t->id, t->data, select.x, select.y); 74 + int s = kf_window.fontsize / kf_window.cam.zoom; 75 + kf_drawtext( 76 + BLACK, 77 + select.x * KF_TILE_SIZE_PX, 78 + select.y * KF_TILE_SIZE_PX - s, 79 + s, 80 + text 81 + ); 82 + } 83 + 84 + static 85 + void _kf_modal_edit_render_ui() 86 + { 87 + /* Inherit from kf_modal_play. */ 88 + kf_modal_play.render_ui(); 89 + } 90 + 91 + struct kf_modal kf_modal_edit = { 92 + .name = "map editor", 93 + .init = _kf_modal_edit_init, 94 + .exit = _kf_modal_edit_exit, 95 + .update = _kf_modal_edit_update, 96 + .render_world = _kf_modal_edit_render_world, 97 + .render_ui = _kf_modal_edit_render_ui, 98 + .data = NULL, 99 + }; 100 + 101 + 102 + static 103 + void _kf_menu_palette_render_ui(void) 104 + { 105 + kf_tileid_t *selected = &((struct kf_editor *)kf_window.modal->data)->selected_tile; 106 + 107 + int px = 80, py = 80; 108 + DrawRectangle(px, py, 400, 400, BLACK); 109 + kf_drawtextshadowed(WHITE, px + 10, py + 10, kf_window.fontsize, "tiles :3"); 110 + py += 40; 111 + int x = 0, y = 0; 112 + int s = KF_TILE_SIZE_PX * 2; 113 + for (int i = 1 ; i <= kf_tiles.count ; i++) 114 + { 115 + Rectangle r = {px + x*s, py + y*s, s, s}; 116 + kf_drawsprite_wh(kf_tiles.sheet[i], r.x, r.y, r.width, r.height, kf_tiles.sprite[i].x + 0, kf_tiles.sprite[i].y + 3); 117 + 118 + if (*selected == i) 119 + DrawRectangleLinesEx(r, 1, GOLD); 120 + 121 + if (CheckCollisionPointRec(GetMousePosition(), r)) 122 + { 123 + DrawRectangleLinesEx(r, 1, WHITE); 124 + if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 125 + *selected = i; 126 + } 127 + 128 + x += 1; 129 + if (x >= 8) 130 + { 131 + x = 0; 132 + y++; 133 + } 134 + } 135 + 136 + if (kf_checkinputpress(kf_inputbind_cancel)) 137 + kf_setmenu(NULL); 138 + }; 139 + 140 + struct kf_modal kf_menu_palette = { 141 + .name = "tile palette", 142 + .render_ui = _kf_menu_palette_render_ui, 143 + };
+127
src/fs.c
··· 1 #include <keraforge.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 6 int kf_exists(char *filename) ··· 49 50 return 1; 51 }
··· 1 #include <keraforge.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 + #include <string.h> 5 + #include <errno.h> 6 + #include <lzma.h> 7 8 9 int kf_exists(char *filename) ··· 52 53 return 1; 54 } 55 + 56 + static 57 + int _kf_compress(lzma_stream *s, FILE *ifp, FILE *ofp) 58 + { 59 + lzma_action a = LZMA_RUN; 60 + u8 inbuf[BUFSIZ], outbuf[BUFSIZ]; 61 + 62 + s->next_in = NULL; 63 + s->avail_in = 0; 64 + s->next_out = outbuf; 65 + s->avail_out = sizeof(outbuf); 66 + 67 + for (;;) 68 + { 69 + if (s->avail_in == 0 && !feof(ifp)) 70 + { 71 + s->next_in = inbuf; 72 + s->avail_in = fread(inbuf, 1, sizeof(inbuf), ifp); 73 + 74 + if (ferror(ifp)) 75 + { 76 + KF_THROW("read error: %s", strerror(errno)); 77 + return 0; /* unreachable */ 78 + } 79 + 80 + if (feof(ifp)) 81 + a = LZMA_FINISH; 82 + } 83 + 84 + lzma_ret r = lzma_code(s, a); 85 + 86 + if (s->avail_out == 0 || r == LZMA_STREAM_END) 87 + { 88 + size_t nwrote = sizeof(outbuf) - s->avail_out; 89 + 90 + if (fwrite(outbuf, 1, nwrote, ofp) != nwrote) 91 + { 92 + KF_THROW("write error: %s", strerror(errno)); 93 + return 0; /* unreachable */ 94 + } 95 + 96 + s->next_out = outbuf; 97 + s->avail_out = sizeof(outbuf); 98 + } 99 + 100 + if (r != LZMA_OK) 101 + { 102 + if (r == LZMA_STREAM_END) 103 + return 1; 104 + 105 + KF_THROW("lzma compression error: code=%d", r); 106 + return 0; /* unreachable */ 107 + } 108 + } 109 + 110 + KF_UNREACHABLE("broke out of for(;;) loop"); 111 + return 0; /* unreachable */ 112 + } 113 + 114 + int kf_compress(char *infile, char *outfile) 115 + { 116 + FILE *ifp = fopen(infile, "r"); 117 + if (!ifp) 118 + { 119 + KF_THROW("failed to open input file: %s", infile); 120 + return 0; /* unreachable */ 121 + } 122 + FILE *ofp = fopen(outfile, "w"); 123 + if (!ofp) 124 + { 125 + fclose(ifp); 126 + KF_THROW("failed to open output file: %s", outfile); 127 + return 0; /* unreachable */ 128 + } 129 + 130 + lzma_stream s = LZMA_STREAM_INIT; 131 + lzma_ret r = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64); 132 + if (r != LZMA_OK) 133 + { 134 + KF_THROW("failed to initialize lzma encoder: code=%d\n", r); 135 + return 0; /* unreachable */ 136 + } 137 + 138 + int res = _kf_compress(&s, ifp, ofp); 139 + 140 + lzma_end(&s); 141 + fclose(ifp); 142 + fclose(ofp); 143 + 144 + return res; 145 + } 146 + 147 + int kf_decompress(char *infile, char *outfile) 148 + { 149 + FILE *ifp = fopen(infile, "r"); 150 + if (!ifp) 151 + { 152 + KF_THROW("failed to open input file: %s", infile); 153 + return 0; /* unreachable */ 154 + } 155 + FILE *ofp = fopen(outfile, "w"); 156 + if (!ofp) 157 + { 158 + fclose(ifp); 159 + KF_THROW("failed to open output file: %s", outfile); 160 + return 0; /* unreachable */ 161 + } 162 + 163 + lzma_stream s = LZMA_STREAM_INIT; 164 + lzma_ret r = lzma_stream_decoder(&s, UINT64_MAX, LZMA_CONCATENATED); 165 + if (r != LZMA_OK) 166 + { 167 + KF_THROW("failed to initialize lzma decoder: code=%d\n", r); 168 + return 0; /* unreachable */ 169 + } 170 + 171 + int res = _kf_compress(&s, ifp, ofp); 172 + 173 + lzma_end(&s); 174 + fclose(ifp); 175 + fclose(ofp); 176 + 177 + return res; 178 + }
+261
src/graphics.c
··· 1 #include <keraforge.h> 2 3 4 u64 kf_frame = 0; 5 f64 kf_s = 0; 6 f32 kf_dtms = 0; 7 f32 kf_dts = 0;
··· 1 + #include "keraforge/actor.h" 2 #include <keraforge.h> 3 + #include <raylib.h> 4 5 6 u64 kf_frame = 0; 7 f64 kf_s = 0; 8 f32 kf_dtms = 0; 9 f32 kf_dts = 0; 10 + struct kf_window kf_window = {0}; 11 + 12 + 13 + int kf_measuretext(int size, char *text) 14 + { 15 + return MeasureTextEx(kf_window.font, text, size, 2).x; 16 + } 17 + 18 + void kf_drawtext(Color c, int x, int y, int size, char *text) 19 + { 20 + DrawTextEx(kf_window.font, text, (Vector2){x, y}, size, 2, c); 21 + } 22 + 23 + void kf_drawtextshadowed(Color c, int x, int y, int size, char *text) 24 + { 25 + kf_drawtext(BLACK, x+1, y+1, size, text); 26 + kf_drawtext(c, x, y, size, text); 27 + } 28 + 29 + 30 + void kf_openwindow(char *title) 31 + { 32 + kf_window.target_fps = 60; 33 + 34 + SetTraceLogLevel(LOG_WARNING); 35 + InitWindow(800, 600, title); 36 + SetTargetFPS(kf_window.target_fps); 37 + SetExitKey(KEY_NULL); 38 + 39 + kf_window.font = GetFontDefault(); 40 + kf_window.fontsize = 20; 41 + 42 + kf_loaddefaultbinds(); 43 + kf_ui_init(); 44 + 45 + struct kf_state *state = NULL; 46 + int is_new_state = kf_state_load(&state); 47 + (void)is_new_state; 48 + kf_window.state = state; 49 + 50 + struct kf_world *world = NULL; 51 + kf_timeit("load world", kf_world_load(&world, true, NULL)); 52 + kf_window.room = world; 53 + 54 + int idplayer = kf_actor_register("player"); 55 + kf_actorregistry.serialize[idplayer] = kf_player_serialize; 56 + kf_actorregistry.deserialize[idplayer] = kf_player_deserialize; 57 + kf_actorregistry.sprite[idplayer] = kf_actor_loadspritesheet("data/res/img/char/template.png"); 58 + kf_actorregistry.tick[idplayer] = kf_player_tick; 59 + kf_actorregistry.draw[idplayer] = kf_player_draw; 60 + 61 + int idplayer2 = kf_actor_register("player2"); 62 + kf_actorregistry.serialize[idplayer2] = kf_player_serialize; 63 + kf_actorregistry.deserialize[idplayer2] = kf_player_deserialize; 64 + kf_actorregistry.sprite[idplayer2] = kf_actor_loadspritesheet("data/res/img/char/whom.png"); 65 + kf_actorregistry.tick[idplayer2] = kf_player_tick; 66 + kf_actorregistry.draw[idplayer2] = kf_player_draw; 67 + 68 + if (!kf_exists("data/actors.bin")) 69 + { 70 + struct kf_actor *player = kf_actor_new("player"); 71 + player->controlled = true; 72 + player->pos.x = world->width * KF_TILE_SIZE_PX / 2.0f; 73 + player->pos.y = world->width * KF_TILE_SIZE_PX / 2.0f; 74 + 75 + struct kf_actor *player2 = kf_actor_new("player2"); 76 + player2->pos.x = world->width * KF_TILE_SIZE_PX / 2.0f; 77 + player2->pos.y = (world->width * KF_TILE_SIZE_PX / 2.0f) - 32; 78 + 79 + kf_timeit("save actors", kf_saveactors()); 80 + } 81 + else 82 + { 83 + kf_timeit("load actors", kf_loadactors()); 84 + } 85 + 86 + kf_window.player = kf_actors; /* player should always be the first actor. */ 87 + 88 + kf_window.cam.target.x = kf_window.player->pos.x + (kf_window.player->size.x / 2); 89 + kf_window.cam.target.y = kf_window.player->pos.y + (kf_window.player->size.y / 2); 90 + kf_window.cam.zoom = 2; 91 + 92 + kf_setmodal(&kf_modal_play); 93 + } 94 + 95 + void kf_startwindow(void) 96 + { 97 + kf_window.running = 1; 98 + kf_window.cam.offset.x = GetScreenWidth() / 2.0f; 99 + kf_window.cam.offset.y = GetScreenHeight() / 2.0f; 100 + 101 + while (!WindowShouldClose() && kf_window.running) 102 + { 103 + if (IsWindowResized()) 104 + { 105 + kf_window.cam.offset.x = GetScreenWidth() / 2.0f; 106 + kf_window.cam.offset.y = GetScreenHeight() / 2.0f; 107 + } 108 + 109 + if (kf_window.modal && kf_window.modal->render_world) 110 + kf_window.modal->update(); 111 + if (kf_window.menu && kf_window.menu->render_world) 112 + kf_window.menu->update(); 113 + 114 + BeginDrawing(); 115 + ClearBackground(BLACK); 116 + { 117 + BeginMode2D(kf_window.cam); 118 + { 119 + if (kf_window.modal && kf_window.modal->render_world) 120 + kf_window.modal->render_world(); 121 + if (kf_window.menu && kf_window.menu->render_world) 122 + kf_window.menu->render_world(); 123 + } 124 + EndMode2D(); 125 + 126 + if (kf_window.modal && kf_window.modal->render_ui) 127 + kf_window.modal->render_ui(); 128 + if (kf_window.menu && kf_window.menu->render_ui) 129 + kf_window.menu->render_ui(); 130 + } 131 + EndDrawing(); 132 + 133 + kf_frame++; 134 + kf_dts = GetFrameTime(); 135 + kf_dtms = kf_dts * 1000; 136 + kf_s += kf_dts; 137 + } 138 + 139 + kf_setmenu(NULL); 140 + kf_setmodal(NULL); 141 + 142 + kf_saveactors(); 143 + 144 + kf_state_save(kf_window.state); 145 + 146 + free(kf_window.player); 147 + free(kf_window.state); 148 + free(kf_window.room); 149 + 150 + CloseWindow(); 151 + } 152 + 153 + void kf_closewindow(void) 154 + { 155 + kf_window.running = false; 156 + } 157 + 158 + void kf_settargetfps(int fps) 159 + { 160 + SetTargetFPS(kf_window.target_fps = fps); 161 + } 162 + 163 + void kf_setmodal(struct kf_modal *modal) 164 + { 165 + if (kf_window.modal && kf_window.modal->exit) 166 + { 167 + kf_window.modal->exit(); 168 + kf_window.modal->exit = NULL; 169 + } 170 + kf_window.modal = modal; 171 + if (kf_window.modal && kf_window.modal->init) 172 + kf_window.modal->init(); 173 + } 174 + 175 + void kf_setmenu(struct kf_modal *menu) 176 + { 177 + if (kf_window.menu && kf_window.menu->exit) 178 + { 179 + kf_window.menu->exit(); 180 + kf_window.menu->exit = NULL; 181 + } 182 + kf_window.menu = menu; 183 + if (kf_window.menu && kf_window.menu->init) 184 + kf_window.menu->init(); 185 + } 186 + 187 + static 188 + void _kf_modal_play_update(void) 189 + { 190 + // kf_window.player->tick(kf_window.player); 191 + for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 192 + kf_actorregistry.tick[actor->id](actor); 193 + 194 + Vector2 v = GetScreenToWorld2D(GetMousePosition(), kf_window.cam); 195 + kf_window.select.x = v.x / KF_TILE_SIZE_PX; 196 + kf_window.select.y = v.y / KF_TILE_SIZE_PX; 197 + 198 + if (kf_checkinputpress(kf_inputbind_cancel) && kf_window.menu == NULL) 199 + kf_window.running = 0; 200 + else if (kf_checkinputpress(kf_inputbind_zoom_reset)) 201 + kf_window.cam.zoom = 2; 202 + else if (kf_checkinputpress(kf_inputbind_zoom_in) && kf_window.cam.zoom < 3.50f) 203 + kf_window.cam.zoom += 0.25f; 204 + else if (kf_checkinputpress(kf_inputbind_zoom_out) && kf_window.cam.zoom > 1.00f) 205 + kf_window.cam.zoom -= 0.25f; 206 + else if (kf_checkinputpress(kf_inputbind_toggle_fps_limit)) 207 + kf_settargetfps(kf_window.target_fps != 60 ? 60 : 0); 208 + else if (kf_checkinputpress(kf_inputbind_toggle_editor)) 209 + { 210 + if (kf_window.modal == &kf_modal_edit) 211 + kf_setmodal(&kf_modal_play); 212 + else 213 + kf_setmodal(&kf_modal_edit); 214 + } 215 + } 216 + 217 + static 218 + void _kf_modal_play_render_world(void) 219 + { 220 + kf_world_draw(kf_window.room, kf_window.cam); 221 + // kf_world_drawcolliders(world, player, cam); 222 + // kf_window.player->draw(kf_window.player); 223 + for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 224 + kf_actorregistry.draw[actor->id](actor); 225 + } 226 + 227 + static 228 + void _kf_modal_play_render_ui(void) 229 + { 230 + const int dy = kf_window.fontsize + 4; 231 + int y = -dy + 4; /* start at -dy so that the FPS starts at 0,0. */ 232 + y += 8; /* add a bit of padding */ 233 + int x = 8; 234 + 235 + const Color d = GRAY; /* default colour */ 236 + Color c = d; 237 + 238 + /* "curry" some arguments */ 239 + # define line(FMT, ...) \ 240 + do \ 241 + { \ 242 + char *t = (char *)TextFormat(FMT __VA_OPT__(,) __VA_ARGS__); \ 243 + kf_drawtextshadowed(c, x, y+=dy, kf_window.fontsize, t); \ 244 + } \ 245 + while (0) 246 + 247 + line("--time--"); 248 + c = GetFPS() >= kf_window.target_fps ? GREEN : RED; 249 + line("fps: %d", GetFPS()); 250 + line("dts: %f", kf_dts); 251 + c = d; 252 + line("sec: %f", kf_s); 253 + 254 + line("--modals--"); 255 + c = kf_window.modal ? ORANGE : d; 256 + line("mode: %s", kf_window.modal ? kf_window.modal->name : "n/a"); 257 + c = kf_window.menu ? ORANGE : d; 258 + line("menu: %s", kf_window.menu ? kf_window.menu->name : "n/a"); 259 + 260 + # undef line 261 + } 262 + 263 + struct kf_modal kf_modal_play = { 264 + .name = "play", 265 + .update = _kf_modal_play_update, 266 + .render_world = _kf_modal_play_render_world, 267 + .render_ui = _kf_modal_play_render_ui, 268 + };
+3 -4
src/input.c
··· 1 #include <keraforge.h> 2 #include <raylib.h> 3 - #include <stdio.h> 4 #include <string.h> 5 6 ··· 16 kf_inputbind_t i = kf_inputbinds.count; 17 if (i >= KF_INPUTBIND_MAX) 18 { 19 - fprintf(stderr, "error: max keybind count is 255.\n"); 20 - return -1; 21 } 22 kf_inputbinds.count++; 23 24 - printf("add keybind: %d: %s (k=%d a=%d m=%d g=%d x=%d)\n", i, id, key, alt, mouse, gamepad, axis); 25 kf_inputbinds.id[i] = id; 26 kf_inputbinds.key[i] = key; 27 kf_inputbinds.alt[i] = alt;
··· 1 #include <keraforge.h> 2 #include <raylib.h> 3 #include <string.h> 4 5 ··· 15 kf_inputbind_t i = kf_inputbinds.count; 16 if (i >= KF_INPUTBIND_MAX) 17 { 18 + KF_THROW("max keybind count is 255"); 19 + return -1; /* unreachable */ 20 } 21 kf_inputbinds.count++; 22 23 + // kf_logdbg("add keybind: %d: %s (k=%d a=%d m=%d g=%d x=%d)", i, id, key, alt, mouse, gamepad, axis); 24 kf_inputbinds.id[i] = id; 25 kf_inputbinds.key[i] = key; 26 kf_inputbinds.alt[i] = alt;
+55
src/inputbinds.c
···
··· 1 + #include <keraforge.h> 2 + 3 + 4 + kf_inputbind_t 5 + kf_inputbind_move_up, 6 + kf_inputbind_move_down, 7 + kf_inputbind_move_left, 8 + kf_inputbind_move_right, 9 + kf_inputbind_run, 10 + 11 + kf_inputbind_ui_up, 12 + kf_inputbind_ui_down, 13 + kf_inputbind_ui_left, 14 + kf_inputbind_ui_right, 15 + kf_inputbind_select, 16 + kf_inputbind_cancel, 17 + 18 + kf_inputbind_pause, 19 + kf_inputbind_palette, 20 + 21 + kf_inputbind_zoom_reset, 22 + kf_inputbind_zoom_in, 23 + kf_inputbind_zoom_out, 24 + 25 + kf_inputbind_toggle_fps_limit, 26 + kf_inputbind_toggle_editor 27 + ; 28 + 29 + 30 + void kf_loaddefaultbinds(void) 31 + { 32 + kf_inputbind_move_up = kf_addinput("move_up", KEY_W, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 33 + kf_inputbind_move_down = kf_addinput("move_down", KEY_S, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 34 + kf_inputbind_move_left = kf_addinput("move_left", KEY_A, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X); 35 + kf_inputbind_move_right = kf_addinput("move_right", KEY_D, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X); 36 + kf_inputbind_run = kf_addinput("run", KEY_LEFT_SHIFT, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 37 + 38 + kf_inputbind_ui_up = kf_addinput("ui_up", KEY_W, KEY_UP, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_UP, GAMEPAD_AXIS_UNKNOWN); 39 + kf_inputbind_ui_down = kf_addinput("ui_down", KEY_S, KEY_DOWN, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN); 40 + kf_inputbind_ui_left = kf_addinput("ui_left", KEY_A, KEY_LEFT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_LEFT, GAMEPAD_AXIS_UNKNOWN); 41 + kf_inputbind_ui_right = kf_addinput("ui_right", KEY_D, KEY_RIGHT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 42 + 43 + kf_inputbind_select = kf_addinput("select", KEY_E, KEY_ENTER, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN); 44 + kf_inputbind_cancel = kf_addinput("cancel", KEY_Q, KEY_ESCAPE, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 45 + 46 + kf_inputbind_pause = kf_addinput("pause", KEY_ESCAPE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_MIDDLE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 47 + kf_inputbind_palette = kf_addinput("palette", KEY_TAB, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_UP, GAMEPAD_AXIS_UNKNOWN); 48 + 49 + kf_inputbind_zoom_reset = kf_addinput("zoom_reset", KEY_ZERO, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 50 + kf_inputbind_zoom_in = kf_addinput("zoom_in", KEY_EQUAL, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 51 + kf_inputbind_zoom_out = kf_addinput("zoom_out", KEY_MINUS, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 52 + 53 + kf_inputbind_toggle_fps_limit = kf_addinput("toggle_fps_limit", KEY_NINE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 54 + kf_inputbind_toggle_editor = kf_addinput("toggle_editor", KEY_EIGHT, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 55 + }
+41
src/log.c
···
··· 1 + #include <keraforge.h> 2 + 3 + void kf_vlog(char *level, char *fmt, va_list va) 4 + { 5 + fprintf(stderr, "\x1b[0;1m-> %s\x1b[0;1m:\x1b[0m ", level); 6 + vfprintf(stderr, fmt, va); 7 + fprintf(stderr, "\n"); 8 + } 9 + 10 + void kf_log(char *level, char *fmt, ...) 11 + { 12 + va_list va; 13 + va_start(va, fmt); 14 + kf_vlog(level, fmt, va); 15 + va_end(va); 16 + } 17 + 18 + void kf_logdbg(char *fmt, ...) 19 + { 20 + va_list va; 21 + va_start(va, fmt); 22 + kf_vlog("\x1b[1;33mdbg", fmt, va); 23 + va_end(va); 24 + } 25 + 26 + void kf_loginfo(char *fmt, ...) 27 + { 28 + va_list va; 29 + va_start(va, fmt); 30 + kf_vlog("\x1b[1;34minfo", fmt, va); 31 + va_end(va); 32 + } 33 + 34 + void kf_logerr(char *fmt, ...) 35 + { 36 + va_list va; 37 + va_start(va, fmt); 38 + kf_vlog("\x1b[1;31merr", fmt, va); 39 + va_end(va); 40 + } 41 +
+38 -336
src/main.c
··· 1 - #include "keraforge/input.h" 2 - #include "keraforge/sprites.h" 3 - #include "keraforge/world.h" 4 #include <keraforge.h> 5 #include <raylib.h> 6 #include <raymath.h> 7 - #include <stdio.h> 8 - #include <stdlib.h> 9 - 10 - 11 - static Camera2D cam; 12 - static struct kf_vec2(u32) select = { 0, 0 }; 13 - static int selected_tile = 0; 14 - static enum { 15 - menu_none, 16 - menu_palette, 17 - } menu; 18 - static int target_fps = 60; 19 - static enum { 20 - modal_play, 21 - modal_edit, 22 - } modal; 23 - static char *modals[] = { "play", "edit" }; 24 - static bool dirty = false; 25 - 26 - static kf_inputbind_t 27 - inputbind_move_up, 28 - inputbind_move_down, 29 - inputbind_move_left, 30 - inputbind_move_right, 31 - inputbind_ui_up, 32 - inputbind_ui_down, 33 - inputbind_ui_left, 34 - inputbind_ui_right, 35 - inputbind_select, 36 - inputbind_cancel, 37 - inputbind_pause, 38 - inputbind_palette, 39 - inputbind_zoom_reset, 40 - inputbind_zoom_in, 41 - inputbind_zoom_out, 42 - inputbind_toggle_fps_limit, 43 - inputbind_toggle_editor 44 - ; 45 46 47 static 48 - void loadbinds() 49 - { 50 - inputbind_move_up = kf_addinput("move_up", KEY_W, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 51 - inputbind_move_down = kf_addinput("move_down", KEY_S, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y); 52 - inputbind_move_left = kf_addinput("move_left", KEY_A, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X); 53 - inputbind_move_right = kf_addinput("move_right", KEY_D, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X); 54 - 55 - inputbind_ui_up = kf_addinput("ui_up", KEY_W, KEY_UP, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_UP, GAMEPAD_AXIS_UNKNOWN); 56 - inputbind_ui_down = kf_addinput("ui_down", KEY_S, KEY_DOWN, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN); 57 - inputbind_ui_left = kf_addinput("ui_left", KEY_A, KEY_LEFT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_LEFT, GAMEPAD_AXIS_UNKNOWN); 58 - inputbind_ui_right = kf_addinput("ui_right", KEY_D, KEY_RIGHT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 59 - 60 - inputbind_select = kf_addinput("select", KEY_E, KEY_ENTER, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN); 61 - inputbind_cancel = kf_addinput("cancel", KEY_Q, KEY_ESCAPE, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 62 - 63 - inputbind_pause = kf_addinput("pause", KEY_ESCAPE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_MIDDLE_RIGHT, GAMEPAD_AXIS_UNKNOWN); 64 - inputbind_palette = kf_addinput("palette", KEY_TAB, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_UP, GAMEPAD_AXIS_UNKNOWN); 65 - 66 - inputbind_zoom_reset = kf_addinput("zoom_reset", KEY_ZERO, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 67 - inputbind_zoom_in = kf_addinput("zoom_in", KEY_EQUAL, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 68 - inputbind_zoom_out = kf_addinput("zoom_out", KEY_MINUS, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 69 - 70 - inputbind_toggle_fps_limit = kf_addinput("toggle_fps_limit", KEY_NINE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 71 - inputbind_toggle_editor = kf_addinput("toggle_editor", KEY_EIGHT, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN); 72 - } 73 - 74 - static 75 - void setmenu(int m) 76 { 77 - menu = m; 78 - } 79 - 80 - static 81 - void _player_tick_move(struct kf_actor *self) 82 - { 83 - struct kf_vec2(f32) v = {0, 0}; 84 - 85 - /* gamepad axis movement */ 86 - f32 gpx = kf_getgamepadaxis(inputbind_move_left); 87 - f32 gpy = kf_getgamepadaxis(inputbind_move_up); 88 - if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone) 89 - { 90 - v.y = gpy; 91 - v.x = gpx; 92 - 93 - f32 angle = Vector2LineAngle(Vector2Zero(), (Vector2){gpx, gpy}) * RAD2DEG; 94 - angle /= 90; 95 - switch ((int)roundf(angle)) 96 - { 97 - case 0: self->pointing = kf_east; break; 98 - case 1: self->pointing = kf_north; break; 99 - case -2: 100 - case 2: self->pointing = kf_west; break; 101 - case -1: self->pointing = kf_south; break; 102 - } 103 - 104 - goto done; 105 - } 106 - 107 - /* non-axis movement */ 108 - bool w = kf_checkinputdown(inputbind_move_up); 109 - bool s = kf_checkinputdown(inputbind_move_down); 110 - bool a = kf_checkinputdown(inputbind_move_left); 111 - bool d = kf_checkinputdown(inputbind_move_right); 112 - 113 - if (a && d) { v.x = 0; } 114 - else if (a) { v.x = -1; self->pointing = kf_west; } 115 - else if (d) { v.x = 1; self->pointing = kf_east; } 116 - 117 - if (w && s) { v.y = 0; } 118 - else if (w) { v.y = -1; self->pointing = kf_north; } 119 - else if (s) { v.y = 1; self->pointing = kf_south; } 120 - 121 - v = kf_normalize_vec2(f32)(v); 122 - 123 - done: 124 - if (v.x || v.y) 125 - kf_actor_addforce(self, v); 126 - } 127 - 128 - static 129 - void _player_tick(struct kf_world *world, struct kf_actor *self) 130 - { 131 - if (menu == menu_none) 132 - _player_tick_move(self); 133 - 134 - kf_actor_move(world, self, kf_dts); 135 - } 136 - 137 - static 138 - void _player_draw(struct kf_world *world, struct kf_actor *self) 139 - { 140 - (void)world; 141 - kf_actor_draw(self); 142 - 143 - cam.target.x = self->pos.x + (self->size.x / 2); 144 - cam.target.y = self->pos.y + (self->size.y / 2); 145 - } 146 - 147 - static 148 - void draw_palette(int *selected) 149 - { 150 - int px = 80, py = 80; 151 - DrawRectangle(px, py, 400, 400, BLACK); 152 - DrawText("tiles :3", px + 10, py + 10, 20, WHITE); 153 - py += 40; 154 - int x = 0, y = 0; 155 - int s = KF_TILE_SIZE_PX * 2; 156 - for (int i = 1 ; i <= kf_tiles.count ; i++) 157 - { 158 - Rectangle r = {px + x*s, py + y*s, s, s}; 159 - kf_drawsprite_wh(kf_tiles.sheet[i], r.x, r.y, r.width, r.height, kf_tiles.sprite[i].x + 0, kf_tiles.sprite[i].y + 3); 160 - 161 - if (*selected == i) 162 - DrawRectangleLinesEx(r, 1, GOLD); 163 - 164 - if (CheckCollisionPointRec(GetMousePosition(), r)) 165 - { 166 - DrawRectangleLinesEx(r, 1, WHITE); 167 - if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT)) 168 - *selected = i; 169 - } 170 - 171 - x += 1; 172 - if (x >= 8) 173 - { 174 - x = 0; 175 - y++; 176 - } 177 - } 178 - 179 - if (kf_checkinputpress(inputbind_cancel)) 180 - setmenu(menu_none); 181 - }; 182 - 183 - 184 - int main(int argc, const char *argv[]) 185 - { 186 - (void)argc; 187 - (void)argv; 188 - 189 - // SetTraceLogLevel(LOG_WARNING); 190 - InitWindow(800, 600, "Keraforge"); 191 - SetTargetFPS(target_fps); 192 - SetExitKey(KEY_NULL); 193 - 194 - loadbinds(); 195 - 196 - struct kf_spritesheet terrain = kf_loadspritesheet("data/res/img/tile/terrain.png", 16, 16); 197 KF_ADDTILE( 198 .key = "grass", 199 .mapcol = GREEN, 200 - .sheet = &terrain, 201 .sprite = {0, 0}, 202 ); 203 KF_ADDTILE( 204 .key = "sand", 205 .mapcol = YELLOW, 206 - .sheet = &terrain, 207 .sprite = {4, 0}, 208 ); 209 KF_ADDTILE( 210 .key = "stone", 211 .mapcol = GRAY, 212 - .sheet = &terrain, 213 .sprite = {0, 4}, 214 ); 215 KF_ADDTILE( 216 .key = "debug", 217 .mapcol = BLUE, 218 - .sheet = &terrain, 219 .sprite = {4, 4}, 220 ); 221 KF_ADDTILE( 222 .key = "brick", 223 .mapcol = RED, 224 - .sheet = &terrain, 225 .sprite = {8, 0}, 226 .collide = true, 227 ); 228 - printf("loaded %d tiles\n", kf_tiles.count); 229 230 - struct kf_uiconfig *uiconfig = kf_ui_getconfig(); 231 - uiconfig->select = inputbind_select; 232 - uiconfig->cancel = inputbind_cancel; 233 - uiconfig->up = inputbind_ui_up; 234 - uiconfig->down = inputbind_ui_down; 235 236 - if (!DirectoryExists("data")) 237 - MakeDirectory("data"); 238 239 - struct kf_world *world = NULL; 240 - if (!kf_exists("data/map.bin")) 241 - { 242 - printf("-> creating world\n"); 243 - world = kf_world_new(4096, 4096, 2); 244 - printf("-> saving world\n"); 245 - size_t len = kf_world_getsize(world); 246 - printf("-> writing of %lu bytes\n", len); 247 - if (!kf_writebin("data/map.bin", (u8 *)world, len)) 248 - { 249 - fprintf(stderr, "error creating map: failed to save map.bin\n"); 250 - free(world); 251 - exit(1); 252 - } 253 - } 254 - else 255 - { 256 - printf("-> loading world\n"); 257 - size_t len = 0; 258 - world = (struct kf_world *)kf_readbin("data/map.bin", &len); 259 - printf("-> world is %lu bytes\n", len); 260 - } 261 - if (!world) 262 - { 263 - fprintf(stderr, "error: failed to load world\n"); 264 - exit(1); 265 - } 266 267 - struct kf_actor *player = kf_actor_new(kf_actor_loadspritesheet("data/res/img/char/whom.png"), 10, 10, true); 268 - player->sizeoffset.y = 6; 269 - player->pos.x = world->width / 4.0f * KF_TILE_SIZE_PX; 270 - player->pos.y = world->height / 4.0f * KF_TILE_SIZE_PX; 271 - player->tick = _player_tick; 272 - player->draw = _player_draw; 273 - player->controlled = true; 274 275 - cam = (Camera2D){0}; 276 - cam.offset.x = GetScreenWidth() / 2.0f; 277 - cam.offset.y = GetScreenHeight() / 2.0f; 278 - cam.zoom = 2; 279 280 - int running = 1; 281 - while (!WindowShouldClose() && running) 282 - { 283 - if (IsWindowResized()) 284 - { 285 - cam.offset.x = GetScreenWidth() / 2.0f; 286 - cam.offset.y = GetScreenHeight() / 2.0f; 287 - } 288 - 289 - player->tick(world, player); 290 - 291 - Vector2 v = GetScreenToWorld2D(GetMousePosition(), cam); 292 - select.x = v.x / KF_TILE_SIZE_PX; 293 - select.y = v.y / KF_TILE_SIZE_PX; 294 - 295 - if (kf_checkinputpress(inputbind_palette) && modal == modal_edit) 296 - setmenu(menu_palette); 297 - else if (kf_checkinputpress(inputbind_cancel) && menu == menu_none) 298 - running = 0; 299 - else if (kf_checkinputpress(inputbind_zoom_reset)) 300 - cam.zoom = 2; 301 - else if (kf_checkinputpress(inputbind_zoom_in) && cam.zoom < 3.50f) 302 - cam.zoom += 0.25f; 303 - else if (kf_checkinputpress(inputbind_zoom_out) && cam.zoom > 1.00f) 304 - cam.zoom -= 0.25f; 305 - else if (kf_checkinputpress(inputbind_toggle_fps_limit)) 306 - { 307 - target_fps = target_fps <= 0 ? 60 : 0; 308 - SetTargetFPS(target_fps); 309 - } 310 - else if (kf_checkinputpress(inputbind_toggle_editor)) 311 - { 312 - if (modal == modal_edit) 313 - modal = modal_play; 314 - else 315 - { 316 - modal = modal_edit; 317 - dirty = true; 318 - } 319 - } 320 - 321 - BeginDrawing(); 322 - ClearBackground(BLACK); 323 - 324 - BeginMode2D(cam); 325 - kf_world_draw(world, cam); 326 - // kf_world_drawcolliders(world, player, cam); 327 - if (modal == modal_edit && menu == menu_none && select.x < world->width && select.y < world->height) 328 - { 329 - struct kf_tile *t = kf_world_gettile(world, select.x, select.y); 330 - if (IsMouseButtonDown(MOUSE_BUTTON_LEFT)) 331 - { 332 - t->id = (kf_tileid_t)selected_tile; 333 - kf_world_updatetile(world, select.x, select.y, true); 334 - } 335 - else if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT)) 336 - t->subid = (kf_tileid_t)selected_tile; 337 - else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE)) 338 - selected_tile = t->id; 339 - 340 - DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE); 341 - struct kf_vec2(u32) s = kf_getspritefortilebitmask(t->data); 342 - DrawText(TextFormat("%d [0x%04x] (%d,%d) {%d,%d}", t->id, t->data, select.x, select.y, s.x, s.y), select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX - 10, 10, BLACK); 343 - } 344 - player->draw(world, player); 345 - EndMode2D(); 346 - 347 - switch (menu) 348 - { 349 - case menu_none: 350 - break; 351 - case menu_palette: 352 - draw_palette(&selected_tile); 353 - break; 354 - } 355 - 356 - DrawFPS(0, 0); 357 - DrawText(TextFormat("%f", kf_dts), 0, 20, 20, RED); 358 - DrawText(TextFormat("%f", kf_s), 0, 40, 20, RED); 359 - DrawText(TextFormat("%s", modals[modal]), 0, 60, 20, ORANGE); 360 - 361 - EndDrawing(); 362 - 363 - kf_frame++; 364 - kf_dts = GetFrameTime(); 365 - kf_dtms = kf_dts * 1000; 366 - kf_s += kf_dts; 367 - } 368 - 369 - if (world) 370 - { 371 - if (dirty && !kf_writebin("data/map.bin", (u8 *)world, kf_world_getsize(world))) 372 - fprintf(stderr, "error: failed to save map.bin\n"); 373 - free(world); 374 - } 375 - CloseWindow(); 376 377 return 0; 378 }
··· 1 #include <keraforge.h> 2 #include <raylib.h> 3 #include <raymath.h> 4 5 6 static 7 + void loadtiles(struct kf_spritesheet *terrain) 8 { 9 KF_ADDTILE( 10 .key = "grass", 11 .mapcol = GREEN, 12 + .sheet = terrain, 13 .sprite = {0, 0}, 14 ); 15 KF_ADDTILE( 16 .key = "sand", 17 .mapcol = YELLOW, 18 + .sheet = terrain, 19 .sprite = {4, 0}, 20 ); 21 KF_ADDTILE( 22 .key = "stone", 23 .mapcol = GRAY, 24 + .sheet = terrain, 25 .sprite = {0, 4}, 26 ); 27 KF_ADDTILE( 28 .key = "debug", 29 .mapcol = BLUE, 30 + .sheet = terrain, 31 .sprite = {4, 4}, 32 ); 33 KF_ADDTILE( 34 .key = "brick", 35 .mapcol = RED, 36 + .sheet = terrain, 37 .sprite = {8, 0}, 38 .collide = true, 39 ); 40 + KF_ADDTILE( 41 + .key = "ice", 42 + .mapcol = BLUE, 43 + .sheet = terrain, 44 + .sprite = {8, 4}, 45 + .transparent = true, 46 + ); 47 + KF_ADDTILE( 48 + .key = "dirt", 49 + .mapcol = BROWN, 50 + .sheet = terrain, 51 + .sprite = {12, 0}, 52 + ); 53 + KF_ADDTILE( 54 + .key = "torch", 55 + .mapcol = ORANGE, 56 + .sheet = terrain, 57 + .sprite = {12, 4}, 58 + .transparent = true, 59 + ); 60 + kf_logdbg("loaded %d tiles", kf_tiles.count); 61 + } 62 63 64 + int main(int argc, const char *argv[]) 65 + { 66 + (void)argc; 67 + (void)argv; 68 69 + kf_openwindow("Keraforge"); 70 71 + kf_window.font = LoadFont("data/res/font/MyDearestBit.ttf"); 72 + kf_window.fontsize = 16; 73 74 + struct kf_spritesheet terrain = kf_loadspritesheet("data/res/img/tile/terrain.png", 16, 16); 75 + loadtiles(&terrain); 76 77 + kf_startwindow(); 78 79 return 0; 80 }
+97
src/player.c
···
··· 1 + #include "keraforge/bini.h" 2 + #include <keraforge.h> 3 + 4 + 5 + static 6 + void _player_tick_move(struct kf_actor *self) 7 + { 8 + struct kf_vec2(f32) v = {0, 0}; 9 + 10 + /* gamepad axis movement */ 11 + f32 gpx = kf_getgamepadaxis(kf_inputbind_move_left); 12 + f32 gpy = kf_getgamepadaxis(kf_inputbind_move_up); 13 + if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone) 14 + { 15 + v.y = gpy; 16 + v.x = gpx; 17 + 18 + f32 angle = Vector2LineAngle(Vector2Zero(), (Vector2){gpx, gpy}) * RAD2DEG; 19 + angle /= 90; 20 + switch ((int)roundf(angle)) 21 + { 22 + case 0: self->pointing = kf_east; break; 23 + case 1: self->pointing = kf_north; break; 24 + case -2: /* fallthrough */ 25 + case 2: self->pointing = kf_west; break; 26 + case -1: self->pointing = kf_south; break; 27 + } 28 + 29 + goto done; 30 + } 31 + 32 + /* non-axis movement */ 33 + bool w = kf_checkinputdown(kf_inputbind_move_up); 34 + bool s = kf_checkinputdown(kf_inputbind_move_down); 35 + bool a = kf_checkinputdown(kf_inputbind_move_left); 36 + bool d = kf_checkinputdown(kf_inputbind_move_right); 37 + 38 + if (a && d) { v.x = 0; } 39 + else if (a) { v.x = -1; self->pointing = kf_west; } 40 + else if (d) { v.x = 1; self->pointing = kf_east; } 41 + 42 + if (w && s) { v.y = 0; } 43 + else if (w) { v.y = -1; self->pointing = kf_north; } 44 + else if (s) { v.y = 1; self->pointing = kf_south; } 45 + 46 + v = kf_normalize_vec2(f32)(v); 47 + 48 + done: 49 + if (v.x || v.y) 50 + kf_actor_addforce(self, v); 51 + } 52 + 53 + void kf_player_tick(struct kf_actor *self) 54 + { 55 + if (!kf_window.menu && self->controlled) 56 + { 57 + _player_tick_move(self); 58 + 59 + if (kf_checkinputpress(kf_inputbind_run)) 60 + { 61 + self->running = true; 62 + self->speedmod = 1.5; 63 + } 64 + else if (kf_checkinputrelease(kf_inputbind_run)) 65 + { 66 + self->running = false; 67 + self->speedmod = 1; 68 + } 69 + } 70 + 71 + kf_actor_move(kf_window.room, self, kf_dts); 72 + } 73 + 74 + void kf_player_draw(struct kf_actor *self) 75 + { 76 + kf_actor_draw(self); 77 + 78 + if (self->controlled) 79 + { 80 + kf_window.cam.target.x = self->pos.x + (self->size.x / 2); 81 + kf_window.cam.target.y = self->pos.y + (self->size.y / 2); 82 + } 83 + } 84 + 85 + void kf_player_serialize(struct kf_actor *self, struct bini_stream *bs) 86 + { 87 + bini_wf(bs, self->pos.x); 88 + bini_wf(bs, self->pos.y); 89 + bini_wb(bs, self->controlled); 90 + } 91 + 92 + void kf_player_deserialize(struct kf_actor *self, struct bini_stream *bs) 93 + { 94 + self->pos.x = bini_rf(bs); 95 + self->pos.y = bini_rf(bs); 96 + self->controlled = bini_rb(bs); 97 + }
+43
src/state.c
···
··· 1 + #include <keraforge.h> 2 + 3 + 4 + #define _KF_STATEFILE "data/state.bin" 5 + 6 + int kf_state_save(struct kf_state *state) 7 + { 8 + if (!kf_writebin(_KF_STATEFILE, (u8 *)state, sizeof(*state))) 9 + { 10 + KF_THROW("failed to write to %s", _KF_STATEFILE); 11 + return 0; /* unreachable */ 12 + } 13 + 14 + return 1; 15 + } 16 + 17 + int kf_state_load(struct kf_state **pstate) 18 + { 19 + char *infile = _KF_STATEFILE; 20 + kf_logdbg("loading state: %s", infile); 21 + 22 + int res = 0; 23 + 24 + if (!kf_exists(infile)) 25 + { 26 + kf_logdbg("creating state..."); 27 + struct kf_state s = {0}; 28 + kf_state_save(&s); 29 + res = 1; 30 + } 31 + 32 + size_t len = 0; 33 + *pstate = (struct kf_state *)kf_readbin(infile, &len); 34 + kf_logdbg("loaded state (%p): len=%lu (res=%d)", *pstate, len, res); 35 + 36 + if (!*pstate) 37 + { 38 + KF_THROW("failed to load state"); 39 + return -1; /* unreachable */ 40 + } 41 + 42 + return res; 43 + }
+13
src/string.c
···
··· 1 + #include <keraforge.h> 2 + 3 + #include <string.h> 4 + 5 + 6 + char *kf_strdup(char *str) 7 + { 8 + size_t n = strlen(str); 9 + char *s = malloc(n + 1); 10 + strcpy(s, str); 11 + s[n] = '\0'; 12 + return s; 13 + }
+77
src/time.c
···
··· 1 + #include "keraforge/time.h" 2 + #include <keraforge.h> 3 + #include <time.h> 4 + 5 + 6 + void kf_timer_start(struct kf_timer *timer) 7 + { 8 + time(&timer->start); 9 + time(&timer->now); 10 + timer->stop = 0; 11 + 12 + if (timer->mode == kf_timermode_oneshot) 13 + timer->age = 0; 14 + } 15 + 16 + void kf_timer_tick(struct kf_timer *timer) 17 + { 18 + if (timer->stop) 19 + return; 20 + 21 + time_t now = time(NULL); 22 + time_t dif = now - timer->now; 23 + timer->age += dif; 24 + timer->now = now; 25 + 26 + switch (timer->mode) 27 + { 28 + case kf_timermode_stopwatch: 29 + break; 30 + case kf_timermode_repeat: 31 + if (timer->age >= timer->length) 32 + { 33 + timer->age = 0; 34 + if (timer->callback) 35 + timer->callback(timer); 36 + } 37 + break; 38 + case kf_timermode_oneshot: 39 + if (timer->age >= timer->length) 40 + { 41 + timer->stop = now; 42 + timer->age = 0; 43 + if (timer->callback) 44 + timer->callback(timer); 45 + } 46 + break; 47 + } 48 + 49 + } 50 + 51 + void kf_timer_stop(struct kf_timer *timer) 52 + { 53 + time(&timer->stop); 54 + if (timer->mode == kf_timermode_stopwatch) 55 + { 56 + timer->length = timer->stop - timer->start; 57 + if (timer->callback) 58 + timer->callback(timer); 59 + } 60 + } 61 + 62 + size_t kf_timer_snprint(struct kf_timer *timer, char *buf, size_t bufsiz) 63 + { 64 + size_t n = 0; 65 + switch (timer->mode) 66 + { 67 + case kf_timermode_stopwatch: 68 + n += strftime(buf, bufsiz, "%Hh:%Mm:%Ss", gmtime(&timer->length)); 69 + break; 70 + case kf_timermode_repeat: 71 + case kf_timermode_oneshot: 72 + n += strftime(buf, bufsiz, "%Hh:%Mm:%Ss/", gmtime(&timer->age)); 73 + n += strftime(buf + n, bufsiz - n, "%Hh:%Mm:%Ss", gmtime(&timer->length)); 74 + break; 75 + } 76 + return n; 77 + }
+22 -18
src/ui.c
··· 1 #include <keraforge.h> 2 #include <raylib.h> 3 4 5 - static 6 - struct kf_uiconfig _kf_uiconfig = { 7 .select = KF_INPUTBIND_NONE, 8 .cancel = KF_INPUTBIND_NONE, 9 .up = KF_INPUTBIND_NONE, ··· 19 }; 20 21 22 - struct kf_uiconfig *kf_ui_getconfig(void) 23 { 24 - return &_kf_uiconfig; 25 } 26 27 int kf_ui_panel(char *title) 28 { 29 - int y = GetScreenHeight() - _kf_uiconfig.panelheight; 30 - DrawRectangle(0, y, GetScreenWidth(), _kf_uiconfig.panelheight, _kf_uiconfig.bg); 31 32 if (title) 33 { 34 - y += _kf_uiconfig.ypadding; 35 - DrawText(title, _kf_uiconfig.xpadding, y, _kf_uiconfig.fontsize, _kf_uiconfig.fg); 36 - y += _kf_uiconfig.fontsize; 37 } 38 39 return y; ··· 50 { 51 char *c = choices[i]; 52 53 - DrawText( 54 - TextFormat(i == *choice ? _kf_uiconfig.selectfmt : _kf_uiconfig.fmt, c), 55 - _kf_uiconfig.xpadding, 56 y, 57 - _kf_uiconfig.fontsize, 58 - _kf_uiconfig.fg 59 ); 60 61 - y += _kf_uiconfig.fontsize; 62 if (y >= GetScreenHeight()) 63 break; 64 } 65 66 skip_text: 67 68 - if (kf_checkinputpress(_kf_uiconfig.select) || kf_checkinputpress(_kf_uiconfig.cancel)) 69 return 1; 70 - else if (kf_checkinputpress(_kf_uiconfig.down) && *choice < nchoices - 1) 71 (*choice)++; 72 - else if (kf_checkinputpress(_kf_uiconfig.up) && *choice > 0) 73 (*choice)--; 74 75 return 0;
··· 1 + #include "keraforge/graphics.h" 2 #include <keraforge.h> 3 #include <raylib.h> 4 5 6 + struct kf_uiconfig kf_uiconfig = { 7 .select = KF_INPUTBIND_NONE, 8 .cancel = KF_INPUTBIND_NONE, 9 .up = KF_INPUTBIND_NONE, ··· 19 }; 20 21 22 + void kf_ui_init(void) 23 { 24 + kf_uiconfig.select = kf_inputbind_select; 25 + kf_uiconfig.cancel = kf_inputbind_cancel; 26 + kf_uiconfig.up = kf_inputbind_ui_up; 27 + kf_uiconfig.down = kf_inputbind_ui_down; 28 + kf_uiconfig.fontsize = kf_window.fontsize; 29 } 30 31 int kf_ui_panel(char *title) 32 { 33 + int y = GetScreenHeight() - kf_uiconfig.panelheight; 34 + DrawRectangle(0, y, GetScreenWidth(), kf_uiconfig.panelheight, kf_uiconfig.bg); 35 36 if (title) 37 { 38 + y += kf_uiconfig.ypadding; 39 + kf_drawtextshadowed(kf_uiconfig.fg, kf_uiconfig.xpadding, y, kf_uiconfig.fontsize, title); 40 + y += kf_uiconfig.fontsize; 41 } 42 43 return y; ··· 54 { 55 char *c = choices[i]; 56 57 + kf_drawtextshadowed( 58 + kf_uiconfig.fg, 59 + kf_uiconfig.xpadding, 60 y, 61 + kf_uiconfig.fontsize, 62 + (char *)TextFormat(i == *choice ? kf_uiconfig.selectfmt : kf_uiconfig.fmt, c) 63 ); 64 65 + y += kf_uiconfig.fontsize; 66 if (y >= GetScreenHeight()) 67 break; 68 } 69 70 skip_text: 71 72 + if (kf_checkinputpress(kf_uiconfig.select) || kf_checkinputpress(kf_uiconfig.cancel)) 73 return 1; 74 + else if (kf_checkinputpress(kf_uiconfig.down) && *choice < nchoices - 1) 75 (*choice)++; 76 + else if (kf_checkinputpress(kf_uiconfig.up) && *choice > 0) 77 (*choice)--; 78 79 return 0;
+147 -26
src/world.c
··· 1 #include <keraforge.h> 2 #include <raylib.h> 3 #include <stdio.h> ··· 25 kf_tiles.sheet[id] = opts.sheet; 26 kf_tiles.sprite[id] = opts.sprite; 27 kf_tiles.collide[id] = opts.collide; 28 29 return id; 30 } ··· 33 { 34 const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height; 35 struct kf_world *world = malloc(len); 36 - printf("creating world: %lu bytes: %p\n", len, world); 37 world->revision = 0; 38 world->width = width; 39 world->height = height; ··· 70 w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id, 71 e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id, 72 s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id; 73 - t->data = 0x0000; 74 if (t->id == n) t->data |= KF_TILEMASK_NORTH; 75 if (t->id == w) t->data |= KF_TILEMASK_WEST; 76 if (t->id == e) t->data |= KF_TILEMASK_EAST; ··· 90 } 91 92 inline 93 - struct kf_vec2(u32) kf_getspritefortilebitmask(kf_tiledatum_t t) 94 { 95 - switch (t) 96 - { 97 - case 0x0000: return (struct kf_vec2(u32)){0, 3}; /* 0x0000: */ 98 - case 0x0001: return (struct kf_vec2(u32)){0, 2}; /* 0x0001: N */ 99 - case 0x0010: return (struct kf_vec2(u32)){3, 3}; /* 0x0010: W */ 100 - case 0x0011: return (struct kf_vec2(u32)){3, 2}; /* 0x0011: NW */ 101 - case 0x0100: return (struct kf_vec2(u32)){1, 3}; /* 0x0100: E */ 102 - case 0x0101: return (struct kf_vec2(u32)){1, 2}; /* 0x0101: EN */ 103 - case 0x0110: return (struct kf_vec2(u32)){2, 3}; /* 0x0110: EW */ 104 - case 0x0111: return (struct kf_vec2(u32)){2, 2}; /* 0x0111: EWN */ 105 - case 0x1000: return (struct kf_vec2(u32)){0, 0}; /* 0x1000: S */ 106 - case 0x1001: return (struct kf_vec2(u32)){0, 1}; /* 0x1001: SN */ 107 - case 0x1010: return (struct kf_vec2(u32)){3, 0}; /* 0x1010: SW */ 108 - case 0x1011: return (struct kf_vec2(u32)){3, 1}; /* 0x1011: SWN */ 109 - case 0x1100: return (struct kf_vec2(u32)){1, 0}; /* 0x1100: SE */ 110 - case 0x1101: return (struct kf_vec2(u32)){1, 1}; /* 0x1101: SEN */ 111 - case 0x1110: return (struct kf_vec2(u32)){2, 0}; /* 0x1110: SEW */ 112 - case 0x1111: return (struct kf_vec2(u32)){2, 1}; /* 0x1111: NESW */ 113 - } 114 - KF_UNREACHABLE("invalid bitmask: 0x%x", t); 115 - return (struct kf_vec2(u32)){-1, -1}; 116 } 117 118 struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y) ··· 183 { 184 KF_SANITY_CHECK(tile->subid <= kf_tiles.count, "erroneous subtile on map at %u,%u: %u (count=%u)", x, y, tile->subid, kf_tiles.count); 185 KF_SANITY_CHECK(tile->id <= kf_tiles.count, "erroneous tile on map at %u,%u: %u (count=%u)", x, y, tile->id, kf_tiles.count); 186 - if (tile->data != 0x1111 && tile->subid && tile->id) /* 0x1111: full tile, no subtile rendering needed */ 187 { 188 kf_drawsprite_wh( 189 kf_tiles.sheet[tile->subid], ··· 195 kf_tiles.sprite[tile->subid].y + 1 196 ); 197 } 198 if (tile->id) 199 { 200 - struct kf_vec2(u32) s = kf_getspritefortilebitmask(tile->data); 201 kf_drawsprite_wh( 202 kf_tiles.sheet[tile->id], 203 x*KF_TILE_SIZE_PX, ··· 208 kf_tiles.sprite[tile->id].y + s.y 209 ); 210 } 211 tile++; /* shift tile pointer to the right */ 212 } 213 tile += down; /* shift tile pointer down */ 214 } 215 }
··· 1 + #include "keraforge/bini.h" 2 #include <keraforge.h> 3 #include <raylib.h> 4 #include <stdio.h> ··· 26 kf_tiles.sheet[id] = opts.sheet; 27 kf_tiles.sprite[id] = opts.sprite; 28 kf_tiles.collide[id] = opts.collide; 29 + kf_tiles.transparent[id] = opts.transparent; 30 31 return id; 32 } ··· 35 { 36 const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height; 37 struct kf_world *world = malloc(len); 38 + kf_loginfo("creating world: %lu bytes: %p", len, world); 39 world->revision = 0; 40 world->width = width; 41 world->height = height; ··· 72 w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id, 73 e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id, 74 s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id; 75 + t->data = 0b0000; 76 if (t->id == n) t->data |= KF_TILEMASK_NORTH; 77 if (t->id == w) t->data |= KF_TILEMASK_WEST; 78 if (t->id == e) t->data |= KF_TILEMASK_EAST; ··· 92 } 93 94 inline 95 + struct kf_vec2(u32) kf_getspritefordatum(kf_tiledatum_t t) 96 { 97 + static const 98 + struct kf_vec2(u32) v[] = { 99 + {0, 3}, /* 0b0000: */ 100 + {0, 2}, /* 0b0001: N */ 101 + {3, 3}, /* 0b0010: W */ 102 + {3, 2}, /* 0b0011: NW */ 103 + {1, 3}, /* 0b0100: E */ 104 + {1, 2}, /* 0b0101: EN */ 105 + {2, 3}, /* 0b0110: EW */ 106 + {2, 2}, /* 0b0111: EWN */ 107 + {0, 0}, /* 0b1000: S */ 108 + {0, 1}, /* 0b1001: SN */ 109 + {3, 0}, /* 0b1010: SW */ 110 + {3, 1}, /* 0b1011: SWN */ 111 + {1, 0}, /* 0b1100: SE */ 112 + {1, 1}, /* 0b1101: SEN */ 113 + {2, 0}, /* 0b1110: SEW */ 114 + {2, 1}, /* 0b1111: NESW */ 115 + }; 116 + return v[t]; 117 } 118 119 struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y) ··· 184 { 185 KF_SANITY_CHECK(tile->subid <= kf_tiles.count, "erroneous subtile on map at %u,%u: %u (count=%u)", x, y, tile->subid, kf_tiles.count); 186 KF_SANITY_CHECK(tile->id <= kf_tiles.count, "erroneous tile on map at %u,%u: %u (count=%u)", x, y, tile->id, kf_tiles.count); 187 + 188 + /* 15: full tile, no subtile rendering needed (unless transparent) */ 189 + if ((tile->data != 15 || kf_tiles.transparent[tile->id]) && tile->subid && tile->id) 190 { 191 kf_drawsprite_wh( 192 kf_tiles.sheet[tile->subid], ··· 198 kf_tiles.sprite[tile->subid].y + 1 199 ); 200 } 201 + 202 if (tile->id) 203 { 204 + struct kf_vec2(u32) s = kf_getspritefordatum(tile->data); 205 kf_drawsprite_wh( 206 kf_tiles.sheet[tile->id], 207 x*KF_TILE_SIZE_PX, ··· 212 kf_tiles.sprite[tile->id].y + s.y 213 ); 214 } 215 + 216 tile++; /* shift tile pointer to the right */ 217 } 218 + 219 tile += down; /* shift tile pointer down */ 220 } 221 } 222 + 223 + #define _KF_MAPFILE_TMP "data/tmp/map.bin" 224 + #define _KF_MAPFILE_XZ "data/map.bin.xz" 225 + #define _KF_MAPFILE "data/map.bin" 226 + 227 + static 228 + void _kf_world_save_bs(struct kf_world *world, struct bini_stream *bs) 229 + { 230 + bini_wiu(bs, world->revision); 231 + bini_wiu(bs, world->width); 232 + bini_wiu(bs, world->height); 233 + struct kf_tile *t = &world->map[0]; 234 + for (size_t i = 0 ; i < world->width*world->height ; i++) 235 + { 236 + bini_wsu(bs, t->subid); 237 + bini_wsu(bs, t->id); 238 + bini_wcu(bs, t->data); 239 + t++; 240 + } 241 + } 242 + 243 + int kf_world_save(struct kf_world *world, bool compress, char *outfile) 244 + { 245 + outfile = outfile ? outfile : (compress ? _KF_MAPFILE_TMP : _KF_MAPFILE); 246 + struct bini_stream *bs = bini_new(); 247 + _kf_world_save_bs(world, bs); 248 + if (!kf_writebin(outfile, bs->buffer, bs->len)) 249 + KF_THROW("failed to open %s", outfile); 250 + bini_close(bs); 251 + 252 + if (compress) 253 + { 254 + if (!kf_compress(_KF_MAPFILE_TMP, _KF_MAPFILE_XZ)) 255 + { 256 + KF_THROW("failed to compress %s", _KF_MAPFILE_XZ); 257 + return 0; /* unreachable */ 258 + } 259 + /* we don't need this anymore, might as well toss it to save file storage. */ 260 + remove(_KF_MAPFILE_TMP); 261 + } 262 + 263 + return 1; 264 + } 265 + 266 + static 267 + void _kf_world_load_bs(struct kf_world **pworld, struct bini_stream *bs) 268 + { 269 + u32 r = bini_riu(bs); 270 + u32 w = bini_riu(bs); 271 + u32 h = bini_riu(bs); 272 + struct kf_world *world = malloc(sizeof(*world) + sizeof(struct kf_tile)*w*h); 273 + world->revision = r; 274 + world->width = w; 275 + world->height = h; 276 + struct kf_tile *t = &world->map[0]; 277 + for (size_t i = 0 ; i < world->width*world->height ; i++) 278 + { 279 + t->subid = bini_rsu(bs); 280 + t->id = bini_rsu(bs); 281 + t->data = bini_rcu(bs); 282 + t++; 283 + } 284 + *pworld = world; 285 + } 286 + 287 + int kf_world_load(struct kf_world **pworld, bool compressed, char *infile) 288 + { 289 + if (compressed) /* decompress before loading */ 290 + { 291 + if (!kf_exists(_KF_MAPFILE_XZ)) 292 + { 293 + KF_THROW("no such file: %s", _KF_MAPFILE_XZ); 294 + return 0; /* unreachable */ 295 + } 296 + 297 + kf_logdbg("decompressing %s to %s", _KF_MAPFILE_XZ, _KF_MAPFILE_TMP); 298 + if (!kf_decompress(_KF_MAPFILE_XZ, _KF_MAPFILE_TMP)) 299 + { 300 + KF_THROW("failed to decompress %s", _KF_MAPFILE_XZ); 301 + return 0; /* unreachable */ 302 + } 303 + } 304 + 305 + infile = infile ? infile : (compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE); 306 + kf_logdbg("loading world: %s", infile); 307 + 308 + size_t len = 0; 309 + struct bini_stream bs = { 310 + .mode = BINI_STREAM, 311 + .buffer = kf_readbin(infile, &len), 312 + }; 313 + if (!bs.buffer) 314 + KF_THROW("failed to read/open %s", infile); 315 + bs.cap = len; 316 + bs.len = len; 317 + kf_logdbg("loaded world into binary stream: len=%lu", len); 318 + _kf_world_load_bs(pworld, &bs); 319 + free(bs.buffer); 320 + kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len); 321 + 322 + if (compressed) 323 + { 324 + /* we don't need this anymore, might as well toss it to save file storage. */ 325 + remove(_KF_MAPFILE_TMP); 326 + } 327 + 328 + if (!*pworld) 329 + { 330 + KF_THROW("failed to load world"); 331 + return 0; /* unreachable */ 332 + } 333 + 334 + return 1; 335 + } 336 +
+16 -3
todo
··· 9 10 . Core 11 . World 12 - / Tiles 13 - . Actors 14 - . Compression (perhaps https://github.com/google/brotli/) 15 . Dialogue 16 . Quests 17 . Combat 18 . Character Stats 19 . (De)Buffs
··· 9 10 . Core 11 . World 12 + x Tiles 13 + / Actors 14 + x Rendering 15 + x Serialization 16 + . NPC paths (i.e, walking to/from locations. Stardew Valley style) 17 + x Compression 18 + . Compress without saving the world binary as an intermediate step. 19 + 20 + All I need to do for this is adapt the compression functions to have an 21 + in-memory (`u8 *`) compression equivalent instead of just `FILE *` 22 + compression. 23 + 24 + Another good option would be to implement compression support in Bini, 25 + i.e, a function that compresses a binary stream's buffer. I would 26 + also need to mark the stream as read-only until a flush occurs, though. 27 . Dialogue 28 . Quests 29 + . UI+layout library 30 . Combat 31 . Character Stats 32 . (De)Buffs
+91
tools/newgame.c
···
··· 1 + #include <keraforge.h> 2 + #include <raylib.h> 3 + #include <stdio.h> 4 + #include <stdlib.h> 5 + #include <string.h> 6 + 7 + 8 + static const char *HELP = 9 + "usage: newgame [options...]\n\n" 10 + "options:\n" 11 + "\t-w --width <int> specify width for the world (default: 1024)\n" 12 + "\t-h --height <int> specify height for the world (default: 1024)\n" 13 + "\t-s --size <int> specify width and height for the world\n" 14 + "\t-p --path <str> specify path to save the game in (default: path)\n" 15 + "\t-f --force create the new game even if the directory exists, this will delete data\n" 16 + "\t --no-force opposite of -f (default)\n" 17 + "\t-c --compress compress the world after creating it (recommended)\n" 18 + "\t --no-compress don't compress the world after creating it (default)\n" 19 + "\t --help display this message\n" 20 + ; 21 + 22 + 23 + int main(int argc, char *argv[]) 24 + { 25 + char *path = "data"; 26 + int width = 1024, height = 1024; 27 + bool compress = false; 28 + bool force = false; 29 + 30 + for (int i = 1 ; i < argc ; i++) 31 + { 32 + char *arg = argv[i]; 33 + 34 + # define _checkshort(SHORT) (strncmp(arg, "-" SHORT, strlen("-" SHORT)) == 0) 35 + # define _checklong(LONG) (strncmp(arg, "--" LONG, strlen("--" LONG)) == 0) 36 + # define _check(SHORT, LONG) (_checkshort(SHORT) || _checklong(LONG)) 37 + 38 + if (_check("w", "width")) 39 + width = atoi(argv[++i]); 40 + else if (_check("h", "height")) 41 + height = atoi(argv[++i]); 42 + else if (_check("s", "size")) 43 + width = height = atoi(argv[++i]); 44 + else if (_check("p", "path")) 45 + path = argv[++i]; 46 + else if (_check("c", "compress")) 47 + compress = true; 48 + else if (_checklong("no-compress")) 49 + compress = false; 50 + else if (_check("f", "force")) 51 + force = true; 52 + else if (_checklong("no-force")) 53 + force = false; 54 + else if (_checklong("help")) 55 + { 56 + kf_loginfo("%s", HELP); 57 + exit(0); 58 + } 59 + else 60 + { 61 + kf_logerr("invalid argument: %s", arg); 62 + exit(1); 63 + } 64 + 65 + # undef _checkshort 66 + # undef _checklong 67 + # undef _check 68 + } 69 + 70 + if (!force && DirectoryExists(path)) 71 + KF_THROW("path exists: %s", path); 72 + 73 + struct kf_world *world = NULL; 74 + 75 + kf_loginfo("creating world"); 76 + kf_timeit("create world", { 77 + world = kf_world_new(width, height, 2); 78 + }); 79 + 80 + /* path for our map.bin */ 81 + char worldpath[4096] = {0}; 82 + strcpy(worldpath, path); 83 + strcpy(&worldpath[0] + strlen(path), compress ? "/tmp/map.bin" : "/map.bin"); 84 + MakeDirectory(GetDirectoryPath(worldpath)); 85 + 86 + size_t len = kf_world_getsize(world); 87 + kf_loginfo("saving world to %s (%lu bytes uncompressed)", worldpath, len); 88 + kf_timeit("save world", kf_world_save(world, compress, worldpath)); 89 + 90 + free(world); 91 + }