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

Compare changes

Choose any two refs to compare.

+11 -1
.gitignore
··· 1 - /build/ 2 /data/
··· 1 + # Data 2 /data/ 3 + 4 + # Built files 5 + /build/ 6 + 7 + # Backups 8 + *.bak 9 + 10 + # Raylib 11 + /raylib.tar.gz 12 + /raylib/
+8
compile_flags.txt
··· 1 -Wall 2 -Wextra 3 -Werror 4 -std=c99 5 -Iinclude/
··· 1 -Wall 2 -Wextra 3 -Werror 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)
+12
include/keraforge/_header.h
··· 1 #ifndef __kf__header__ 2 #define __kf__header__ 3 4 #include <stdint.h> 5 #include <stddef.h> 6 #include <stdbool.h> 7 8 typedef int8_t i8; 9 typedef int16_t i16; 10 typedef int32_t i32; ··· 17 18 typedef float f32; 19 typedef double f64; 20 21 #endif
··· 1 #ifndef __kf__header__ 2 #define __kf__header__ 3 4 + 5 #include <stdint.h> 6 #include <stddef.h> 7 #include <stdbool.h> 8 9 + 10 + #ifndef __FUNCTION_NAME__ 11 + # ifdef WIN32 12 + # define __FUNCTION_NAME__ __FUNCTION__ 13 + # else 14 + # define __FUNCTION_NAME__ __func__ 15 + # endif 16 + #endif 17 + 18 + 19 typedef int8_t i8; 20 typedef int16_t i16; 21 typedef int32_t i32; ··· 28 29 typedef float f32; 30 typedef double f64; 31 + 32 33 #endif
+83 -5
include/keraforge/actor.h
··· 1 #ifndef __kf_actor__ 2 #define __kf_actor__ 3 4 #include <keraforge/_header.h> 5 #include <keraforge/math.h> 6 #include <keraforge/world.h> 7 #include <raylib.h> 8 9 struct kf_actor 10 { 11 - struct kf_vec2(f32) pos, vel, size; 12 - f32 speed, speedmod, friction; 13 bool collide; 14 - void (*tick)(struct kf_world *world, struct kf_actor *self); 15 - void (*draw)(struct kf_world *world, struct kf_actor *self); 16 }; 17 18 - struct kf_actor *kf_actor_new(void); 19 20 /* Check if the actor can move in the given direction. */ 21 int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir); 22 void kf_actor_addforce(struct kf_actor *self, struct kf_vec2(f32) force); 23 /* Move the actor according to their velocity. */ 24 void kf_actor_move(struct kf_world *world, struct kf_actor *actor, f32 deltatime); 25 26 #endif
··· 1 #ifndef __kf_actor__ 2 #define __kf_actor__ 3 4 + 5 #include <keraforge/_header.h> 6 #include <keraforge/math.h> 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. */ 49 + struct kf_vec2(f32) vel; 50 + /* The actor's size. Default origin is 0,0. */ 51 + struct kf_vec2(f32) size; 52 + /* An offset to apply to the actor's collision rect. */ 53 + struct kf_vec2(f32) sizeoffset; 54 + /* Actor's speed. */ 55 + f32 speed; 56 + /* Speed modifier for the actor. Gets multiplied by the actor's speed when moving. */ 57 + f32 speedmod; 58 + /* Friction to apply to the actor while moving. */ 59 + f32 friction; 60 + /* Indicates if collision processing should be applied to this actor. */ 61 bool collide; 62 + /* When true, the actor's `pointing` field is expected to be updated manually. */ 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); 84 85 /* Check if the actor can move in the given direction. */ 86 int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir); 87 + /* Add the given force to the actor's velocity. */ 88 void kf_actor_addforce(struct kf_actor *self, struct kf_vec2(f32) force); 89 /* Move the actor according to their velocity. */ 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
+13
include/keraforge/error.h
···
··· 1 + #ifndef __kf_error__ 2 + #define __kf_error__ 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 + 13 + #endif
+8 -1
include/keraforge/fs.h
··· 1 #ifndef __kf_fs__ 2 #define __kf_fs__ 3 4 #include <keraforge/_header.h> 5 6 /* Check if a file exists. */ 7 int kf_exists(char *filename); 8 9 /* Read binary file contents. */ 10 u8 *kf_readbin(char *filename, size_t *plen); 11 - 12 /* Write binary file contents. */ 13 int kf_writebin(char *filename, u8 *data, size_t len); 14 15 #endif
··· 1 #ifndef __kf_fs__ 2 #define __kf_fs__ 3 4 + 5 #include <keraforge/_header.h> 6 + 7 8 /* Check if a file exists. */ 9 int kf_exists(char *filename); 10 11 /* Read binary file contents. */ 12 u8 *kf_readbin(char *filename, size_t *plen); 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
+99
include/keraforge/graphics.h
···
··· 1 + #ifndef __kf_graphics__ 2 + #define __kf_graphics__ 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; 66 + /* Number of seconds since the game opened. */ 67 + extern f64 kf_s; 68 + /* Deltatime in milliseconds */ 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
+83
include/keraforge/input.h
···
··· 1 + #ifndef __kf_input__ 2 + #define __kf_input__ 3 + 4 + 5 + #include <keraforge/_header.h> 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 15 + typedef u8 kf_inputbind_t; 16 + #define KF_INPUTBIND_NONE ((kf_inputbind_t)0) 17 + 18 + 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]; 29 + MouseButton mouse[KF_INPUTBIND_MAX]; 30 + GamepadButton gamepad[KF_INPUTBIND_MAX]; 31 + GamepadAxis axis[KF_INPUTBIND_MAX]; 32 + }; 33 + 34 + 35 + /* Deadzone for gamepads. Default is 0.2f. */ 36 + extern f32 kf_deadzone; 37 + /* Input binding struct-of-arrays. You should not mutate this directly (use kf_addinput). */ 38 + extern struct _kf_inputbinds kf_inputbinds; 39 + 40 + 41 + /* Add a new input binding. */ 42 + kf_inputbind_t kf_addinput(char *id, KeyboardKey key, KeyboardKey alt, MouseButton mouse, GamepadButton gamepad, GamepadAxis axis); 43 + /* Get an input's index by it's translation key. */ 44 + kf_inputbind_t kf_getinput(char *id); 45 + 46 + /* Check if the given key was pressed this frame. */ 47 + int kf_checkkeypress(kf_inputbind_t id); 48 + /* Check if the given key was released this frame. */ 49 + int kf_checkkeyrelease(kf_inputbind_t id); 50 + /* Check if the given key is down. */ 51 + int kf_checkkeydown(kf_inputbind_t id); 52 + /* Check if the given key is up. */ 53 + int kf_checkkeyup(kf_inputbind_t id); 54 + /* Check if the given mouse button was pressed this frame. */ 55 + int kf_checkmousepress(kf_inputbind_t id); 56 + /* Check if the given mouse button was released this frame. */ 57 + int kf_checkmouserelease(kf_inputbind_t id); 58 + /* Check if the given mouse button is down. */ 59 + int kf_checkmousedown(kf_inputbind_t id); 60 + /* Check if the given mouse button is up. */ 61 + int kf_checkmouseup(kf_inputbind_t id); 62 + /* Check if the given gamepad button was pressed this frame. */ 63 + int kf_checkgamepadpress(kf_inputbind_t id); 64 + /* Check if the given gamepad button was released this frame. */ 65 + int kf_checkgamepadrelease(kf_inputbind_t id); 66 + /* Check if the given gamepad button is down. */ 67 + int kf_checkgamepaddown(kf_inputbind_t id); 68 + /* Check if the given gamepad button is up. */ 69 + int kf_checkgamepadup(kf_inputbind_t id); 70 + /* Get the given gamepad axis' value (-1 to +1). */ 71 + float kf_getgamepadaxis(kf_inputbind_t id); 72 + 73 + /* Check if the given input binding was pressed this frame. */ 74 + int kf_checkinputpress(kf_inputbind_t id); 75 + /* Check if the given input binding was released this frame. */ 76 + int kf_checkinputrelease(kf_inputbind_t id); 77 + /* Check if the given input binding is down. */ 78 + int kf_checkinputdown(kf_inputbind_t id); 79 + /* Check if the given input binding is up. */ 80 + int kf_checkinputup(kf_inputbind_t id); 81 + 82 + 83 + #endif
+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
+21
include/keraforge/math.h
··· 1 #ifndef __kf_math__ 2 #define __kf_math__ 3 4 #include <keraforge/_header.h> 5 #include <raylib.h> 6 #include <raymath.h> 7 8 #define _kf_mathdef(x) \ 9 x(i8); \ ··· 50 struct kf_vec2(T) kf_vec2##T##_y(struct kf_vec2(T) a); 51 _kf_mathdef(x) 52 #undef x 53 54 #endif
··· 1 #ifndef __kf_math__ 2 #define __kf_math__ 3 4 + 5 #include <keraforge/_header.h> 6 #include <raylib.h> 7 #include <raymath.h> 8 + 9 10 #define _kf_mathdef(x) \ 11 x(i8); \ ··· 52 struct kf_vec2(T) kf_vec2##T##_y(struct kf_vec2(T) a); 53 _kf_mathdef(x) 54 #undef x 55 + 56 + 57 + /* Represents a NESW direction. */ 58 + enum kf_direction 59 + { 60 + kf_north, 61 + kf_east, 62 + kf_south, 63 + kf_west, 64 + }; 65 + 66 + 67 + /* Rotate a direction clockwise. */ 68 + enum kf_direction kf_rotatecw(enum kf_direction dir); 69 + /* Rotate a direction counterclockwise. */ 70 + enum kf_direction kf_rotateccw(enum kf_direction dir); 71 + /* Get a vec2(f32) representing the direction. */ 72 + struct kf_vec2(f32) kf_dtov2(enum kf_direction dir); 73 + 74 75 #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
+29
include/keraforge/sprites.h
···
··· 1 + #ifndef __kf_sprites__ 2 + #define __kf_sprites__ 3 + 4 + 5 + #include <keraforge/_header.h> 6 + #include <raylib.h> 7 + 8 + 9 + /* Represents a single texture containing multiple sprites. 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 + 21 + 22 + /* Load a sprite sheet with the given sprite width/height. */ 23 + struct kf_spritesheet kf_loadspritesheet(char *filename, int spritewidth, int spriteheight); 24 + /* Draw a single sprite from the sheet with a provided width and height. */ 25 + void kf_drawsprite_wh(struct kf_spritesheet *sheet, f32 x, f32 y, f32 w, f32 h, int spritex, int spritey); 26 + /* Draw a single sprite from the sheet at the given coordinates. */ 27 + void kf_drawsprite(struct kf_spritesheet *sheet, f32 x, f32 y, int spritex, int spritey); 28 + 29 + #endif
+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
+54
include/keraforge/ui.h
···
··· 1 + #ifndef __kf_ui__ 2 + #define __kf_ui__ 3 + 4 + 5 + #include <keraforge/input.h> 6 + 7 + 8 + /* Holds global config settings for UIs. */ 9 + struct kf_uiconfig 10 + { 11 + /* Keybindings to navigate UIs. */ 12 + kf_inputbind_t select, cancel, up, down; 13 + /* Format string for choices. */ 14 + char *fmt; 15 + /* Format string for selected choices. */ 16 + char *selectfmt; 17 + int fontsize; 18 + Color bg, fg; 19 + /* Height of the UI panel. */ 20 + int panelheight; 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. */ 33 + int kf_ui_panel(char *title); 34 + 35 + /* Present the player with a choice menu. 36 + title: Panel title. Must be null-terminated. 37 + choices: Array of possible choices. Must be null-terminated. 38 + nchoices: Length of the array. 39 + choice: Pointer to the selected choice. This will be mutated. 40 + Returns: 1 if the user has made a choice, otherwise 0. */ 41 + int kf_ui_choice(char *title, char **choices, int nchoices, int *choice); 42 + /* Present the player with a yes/no menu. 43 + title: Panel title. Must be null-terminated. 44 + choice: Pointer to the selected choice. This will be mutated. 1 is yes, 0 is no. 45 + Returns: 1 if the user has made a choice, otherwise 0. */ 46 + int kf_ui_yesno(char *title, int *choice); 47 + /* Present the player with a text input menu. 48 + title: Panel title. Must be null-terminated. 49 + text: The inputted text. If NULL, it will be malloc'd automatically. 50 + If it needs to be grown, it will be realloc'd. 51 + Returns: 1 if the user has made a choice, otherwise 0. */ 52 + int kf_ui_textinput(char *title, char *text); 53 + 54 + #endif
+87 -6
include/keraforge/world.h
··· 1 #ifndef __kf_world__ 2 #define __kf_world__ 3 4 #include <keraforge/_header.h> 5 #include <raylib.h> 6 7 - #define KF_TILEID_MAX UINT16_MAX 8 typedef u16 kf_tileid_t; 9 - #define KF_TILE_SIZE_PX 16 10 11 struct kf_world 12 { 13 - /* Never ever reorder `revision` to be after anything. 14 If you add something before it or move it then the 15 version checker will compare the first u32 in the 16 map's binary to the expected revision, which will 17 almost always be wrong. */ 18 u32 revision; 19 u32 width; 20 u32 height; 21 - kf_tileid_t map[]; 22 }; 23 24 struct _kf_tiles 25 { 26 char *key[KF_TILEID_MAX]; 27 - Color color[KF_TILEID_MAX]; 28 bool collide[KF_TILEID_MAX]; 29 }; 30 extern struct _kf_tiles kf_tiles; 31 32 /* Create a world using the given width and height. 33 Fills the map with the given `fill` tile. */ 34 struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill); ··· 36 /* Get the size of the world in bytes. */ 37 size_t kf_world_getsize(struct kf_world *world); 38 39 /* Get a pointer to the tile ID at a given position. */ 40 - kf_tileid_t *kf_world_gettile(struct kf_world *world, u32 x, u32 y); 41 42 /* Draw the part of the world visible to the given camera. */ 43 void kf_world_draw(struct kf_world *world, Camera2D camera); 44 45 #endif
··· 1 #ifndef __kf_world__ 2 #define __kf_world__ 3 4 + 5 #include <keraforge/_header.h> 6 + #include <keraforge/math.h> 7 + #include <keraforge/sprites.h> 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 47 { 48 + /* Marks the version of this map, used for migrating from one engine version to another. 49 + Never ever reorder `revision` to be after anything. 50 If you add something before it or move it then the 51 version checker will compare the first u32 in the 52 map's binary to the expected revision, which will 53 almost always be wrong. */ 54 u32 revision; 55 + /* Width of the map. */ 56 u32 width; 57 + /* Height of the map. */ 58 u32 height; 59 + /* Array of tiles in the map. Use kf_gettile to get a tile using an X/Y position. */ 60 + struct kf_tile map[]; 61 }; 62 63 + /* Struct-of-arrays for tiles. See: kf_tiles */ 64 struct _kf_tiles 65 { 66 + kf_tileid_t count; 67 + /* Translation key of the tile. */ 68 char *key[KF_TILEID_MAX]; 69 + /* The tile's colour on a world map. */ 70 + Color mapcol[KF_TILEID_MAX]; 71 + /* The spritesheet this tile's sprite belongs to. */ 72 + struct kf_spritesheet *sheet[KF_TILEID_MAX]; 73 + /* Spritesheet coords. */ 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; 82 83 + /* Options for creating tiles using addtile and addtiles. */ 84 + struct kf_tile_opts 85 + { 86 + char *key; 87 + Color mapcol; 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. 100 Fills the map with the given `fill` tile. */ 101 struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill); ··· 103 /* Get the size of the world in bytes. */ 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); 111 + 112 /* Get a pointer to the tile ID at a given position. */ 113 + struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y); 114 + 115 + /* Draw visible collision rectangles. */ 116 + void kf_world_drawcolliders(struct kf_world *world, struct kf_actor *player, Camera2D camera); 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
+18 -1
include/keraforge.h
··· 1 #ifndef __kf__ 2 #define __kf__ 3 4 #include <keraforge/_header.h> 5 #include <keraforge/actor.h> 6 #include <keraforge/fs.h> 7 #include <keraforge/world.h> 8 - #include <keraforge/math.h> 9 10 #endif
··· 1 #ifndef __kf__ 2 #define __kf__ 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 + 26 27 #endif
+47
license
···
··· 1 + === Keraforge - BSD 3-Clause === 2 + 3 + Copyright 2025 Emmeline Coats 4 + 5 + Redistribution and use in source and binary forms, with or without 6 + modification, are permitted provided that the following conditions are met: 7 + 8 + 1. Redistributions of source code must retain the above copyright notice, this 9 + list of conditions and the following disclaimer. 10 + 11 + 2. Redistributions in binary form must reproduce the above copyright notice, 12 + this list of conditions and the following disclaimer in the documentation 13 + and/or other materials provided with the distribution. 14 + 15 + 3. Neither the name of the copyright holder nor the names of its contributors 16 + may be used to endorse or promote products derived from this software 17 + without specific prior written permission. 18 + 19 + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS โ€œAS ISโ€ AND 20 + ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 + DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 + FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 + DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 + CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 + OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 + OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 + 30 + === Raylib - zlib === 31 + 32 + Copyright (c) 2013-2025 Ramon Santamaria (@raysan5) 33 + 34 + This software is provided "as-is", without any express or implied warranty. In no event 35 + will the authors be held liable for any damages arising from the use of this software. 36 + 37 + Permission is granted to anyone to use this software for any purpose, including commercial 38 + applications, and to alter it and redistribute it freely, subject to the following restrictions: 39 + 40 + 1. The origin of this software must not be misrepresented; you must not claim that you 41 + wrote the original software. If you use this software in a product, an acknowledgment 42 + in the product documentation would be appreciated but is not required. 43 + 44 + 2. Altered source versions must be plainly marked as such, and must not be misrepresented 45 + as being the original software. 46 + 47 + 3. This notice may not be removed or altered from any source distribution.
+89
readme
···
··· 1 + 2 + Keraforge 3 + ========= 4 + 5 + A game engine for top-down 2D RPG games. 6 + 7 + [Warning] 8 + Keraforge is still a work-in-progress. Expect breaking 9 + changes! 10 + 11 + Motive 12 + ------ 13 + 14 + There's already a large number of quality game engines 15 + and frameworks in the wild, so what does Keraforge do 16 + differently? 17 + 18 + Design: 19 + Keraforge is designed with a specific kind of game in 20 + mind: top-down, story-driven, handcrafted RPGs. If your 21 + dream game fits in this category, then Keraforge aims to 22 + help make it a reality. 23 + 24 + Simplicity: 25 + Game engines and frameworks always have a learning 26 + curve. Keraforge is no exception. What I can aim for, 27 + though, is keep the learning curve from being exponential 28 + and overwhelming users. I want Keraforge to allow anyone 29 + to share their story with an engine that gives them the 30 + ability to pour love into their work. 31 + 32 + Cost: 33 + Keraforge is 100% free (BSD 3-Clause), zero royalties, 34 + no up-front costs, and no paywalls. I want to give people 35 + the chance to create something beautiful, not to take 36 + their money. 37 + 38 + It's also important to discuss the cons of Keraforge. 39 + It's going to be fundamentally different from any other 40 + engine since it's made for a very specific style of game. 41 + This means that if your game does not fit this style, you 42 + might have more trouble. 43 + 44 + Usage 45 + ----- 46 + 47 + Pre-built binaries are not *yet* distributed. I'll start 48 + publishing binaries once the engine reaches a stable state. 49 + For now, you can compile it yourself. See the section on 50 + development below. 51 + 52 + If you want to see my development progress, see <todo>. 53 + 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 + ------- 88 + 89 + BSD 3-Clause, see <license>.
+9
run.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + for s in "$@" 5 + do 6 + echo "-> $s" 7 + sh scripts/$s.sh 8 + echo "-> done" 9 + done
+14
scripts/_config.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + export CC="gcc" 5 + if [ $(command -v "ccache" &> /dev/null) ] 6 + then 7 + export CC="ccache $CC" 8 + fi 9 + 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"
+12
scripts/build-prod.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + . scripts/_config.sh 5 + 6 + export CFLAGS="$CFLAGS -O3" 7 + export LFLAGS="$LFLAGS -O3" 8 + 9 + sh scripts/functions/init.sh 10 + sh scripts/functions/compile.sh 11 + sh scripts/functions/link.sh 12 + sh scripts/functions/cleanup.sh
+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
+5 -18
scripts/build.sh
··· 1 #!/usr/bin/env sh 2 set -e 3 - . scripts/_config.sh 4 5 - mkdir -p build/ 6 - 7 - if [ -e "build/keraforge" ] 8 - then 9 - rm build/keraforge 10 - fi 11 12 - echo ": building" 13 - for f in `find src/ -name '*.c'` 14 - do 15 - ff=$(echo "$f" | tr '/' '_') 16 - gcc -Wall -Wextra -Werror -std=c99 -Iinclude/ -c $f -o build/${ff%.c}.o 17 - done 18 - 19 - echo ": linking" 20 - gcc -lraylib -lm -lGL -lpthread -ldl -lrt -lX11 build/*.o -o build/keraforge 21 - 22 - rm build/*.o
··· 1 #!/usr/bin/env sh 2 set -e 3 4 + . scripts/_config.sh 5 6 + sh scripts/functions/init.sh 7 + sh scripts/functions/compile.sh 8 + sh scripts/functions/link.sh 9 + sh scripts/functions/cleanup.sh
+7
scripts/clean.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + if [ -e "build" ] 5 + then 6 + rm -rf build 7 + fi
+5
scripts/functions/cleanup.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + echo ": cleaning up" 5 + rm build/*.o
+10
scripts/functions/compile.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + echo ": compiling" 5 + for f in `find src/ -name '*.c'` 6 + do 7 + ff=$(echo "$f" | tr '/' '_') 8 + gcc $CFLAGS $f -o build/${ff%.c}.o 9 + done 10 +
+9
scripts/functions/init.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + mkdir -p build/ 5 + 6 + if [ -e "build/keraforge" ] 7 + then 8 + rm build/keraforge 9 + fi
+5
scripts/functions/link.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + echo ": linking" 5 + gcc -o build/keraforge build/*.o $LFLAGS
+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
+10
scripts/run.sh
···
··· 1 + #!/usr/bin/env sh 2 + set -e 3 + 4 + if [ -e "./build/keraforge" ] 5 + then 6 + gdb -q -ex "set debuginfod enabled off" -ex "file ./build/keraforge" -ex "run" 7 + else 8 + echo ": error: no build available" 9 + exit 1 10 + fi
+208 -12
src/actor.c
··· 1 - #include "keraforge/math.h" 2 - #include "keraforge/world.h" 3 #include <keraforge.h> 4 #include <stdlib.h> 5 #include <raymath.h> 6 7 - struct kf_actor *kf_actor_new(void) 8 { 9 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor)); 10 return actor; 11 } 12 13 int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir) 14 { 15 - Rectangle r = {actor->pos.x - actor->size.x / 2, actor->pos.y - actor->size.y / 2, actor->size.x, actor->size.y}; 16 r.x += dir.x; 17 r.y += dir.y; 18 ··· 28 Rectangle tr = { 29 trx, 30 sy * KF_TILE_SIZE_PX, 31 - KF_TILE_SIZE_PX + 1, 32 - KF_TILE_SIZE_PX + 1, 33 }; /* tile rect */ 34 u32 x; 35 - kf_tileid_t *tile = kf_world_gettile(world, sx, sy); 36 37 for (u32 y = sy ; y <= ey ; y++) 38 { 39 for (x = sx ; x <= ex ; x++) 40 { 41 - if (kf_tiles.collide[*tile] && CheckCollisionRecs(r, tr)) 42 return 0; 43 tile++; /* shift tile pointer to the right */ 44 tr.x += KF_TILE_SIZE_PX; ··· 69 delta.y = 0; 70 } 71 72 self->pos = kf_add_vec2(f32)(self->pos, delta); 73 74 static const f32 speed_deadzone = 0.1f; ··· 83 else if (self->vel.y) 84 self->vel.y /= self->friction; 85 86 - if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone) 87 - self->speedmod = 1; 88 - else if (self->speedmod) 89 - self->speedmod /= self->friction; 90 }
··· 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; 37 + actor->friction = 1.5f; 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) 51 + { 52 + return kf_loadspritesheet(filename, 20, 20); 53 + } 54 + 55 int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir) 56 { 57 + Rectangle r = { 58 + actor->pos.x + (actor->size.x / 2) + actor->sizeoffset.x, 59 + actor->pos.y + (actor->size.y / 2) + actor->sizeoffset.y, 60 + actor->size.x, 61 + actor->size.y 62 + }; 63 r.x += dir.x; 64 r.y += dir.y; 65 ··· 75 Rectangle tr = { 76 trx, 77 sy * KF_TILE_SIZE_PX, 78 + /* TODO: 79 + Subtracting 1 as a bandaid fix to high velocities causing the player to be stopped early in collisions. 80 + This is a very notorious problem in 3D collision and fwik there are plenty of 2D solutions. 81 + I'll research and implement one eventually:tm: */ 82 + KF_TILE_SIZE_PX - 1, 83 + KF_TILE_SIZE_PX - 1, 84 }; /* tile rect */ 85 u32 x; 86 + struct kf_tile *tile = kf_world_gettile(world, sx, sy); 87 88 for (u32 y = sy ; y <= ey ; y++) 89 { 90 for (x = sx ; x <= ex ; x++) 91 { 92 + if (kf_tiles.collide[tile->id] && CheckCollisionRecs(r, tr)) 93 return 0; 94 tile++; /* shift tile pointer to the right */ 95 tr.x += KF_TILE_SIZE_PX; ··· 120 delta.y = 0; 121 } 122 123 + if (!self->controlled) 124 + { 125 + if (delta.y > 0) 126 + self->pointing = kf_south; 127 + else if (delta.y < 0) 128 + self->pointing = kf_north; 129 + else if (delta.x < 0) 130 + self->pointing = kf_west; 131 + else if (delta.x > 0) 132 + self->pointing = kf_east; 133 + } 134 + 135 self->pos = kf_add_vec2(f32)(self->pos, delta); 136 137 static const f32 speed_deadzone = 0.1f; ··· 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) 161 + { 162 + case kf_south: y = 0; break; 163 + case kf_east: y = 1; break; 164 + case kf_west: y = 2; break; 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 + };
+31
src/error.c
···
··· 1 + #include <keraforge.h> 2 + 3 + #if KF_GNU 4 + #include <execinfo.h> 5 + #endif 6 + 7 + 8 + void kf_printbacktrace(FILE *file) 9 + { 10 + #if KF_GNU 11 + static const int maxtracelen = 128; 12 + void *trace[maxtracelen]; 13 + 14 + int size = backtrace(trace, maxtracelen); 15 + char **strings = backtrace_symbols(trace, size); 16 + if (strings) 17 + { 18 + fprintf(file, "backtrace: (%d frames)\n", size); 19 + for (int i = 0 ; i < size ; i++) 20 + fprintf(file, " %d: %s\n", size-i, strings[i]); 21 + } 22 + else 23 + { 24 + fprintf(file, "failed to obtain backtrace\n"); 25 + } 26 + 27 + free(strings); 28 + #else 29 + fprintf(file, "kf_printbacktrace requires GNU extensions (execinfo.h)\n"); 30 + #endif 31 + }
+129 -1
src/fs.c
··· 1 - #include <keraforge/fs.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 5 int kf_exists(char *filename) 6 { ··· 48 49 return 1; 50 }
··· 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) 10 { ··· 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 + }
+268
src/graphics.c
···
··· 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 + };
+140
src/input.c
···
··· 1 + #include <keraforge.h> 2 + #include <raylib.h> 3 + #include <string.h> 4 + 5 + 6 + f32 kf_deadzone = 0.2f; 7 + 8 + struct _kf_inputbinds kf_inputbinds = { 9 + .count = 1, 10 + }; 11 + 12 + 13 + kf_inputbind_t kf_addinput(char *id, KeyboardKey key, KeyboardKey alt, MouseButton mouse, GamepadButton gamepad, GamepadAxis axis) 14 + { 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; 27 + kf_inputbinds.mouse[i] = mouse; 28 + kf_inputbinds.gamepad[i] = gamepad; 29 + kf_inputbinds.axis[i] = axis; 30 + 31 + return i; 32 + } 33 + 34 + kf_inputbind_t kf_getinput(char *id) 35 + { 36 + for (int i = 0 ; i < KF_INPUTBIND_MAX ; i++) 37 + { 38 + if (strcmp(id, kf_inputbinds.id[i]) == 0) 39 + { 40 + return i; 41 + } 42 + } 43 + 44 + return KF_INPUTBIND_NONE; 45 + } 46 + 47 + 48 + int kf_checkkeypress(kf_inputbind_t id) 49 + { 50 + return 51 + (kf_inputbinds.key[id] != KEY_NULL && IsKeyPressed(kf_inputbinds.key[id])) || 52 + (kf_inputbinds.alt[id] != KEY_NULL && IsKeyPressed(kf_inputbinds.alt[id])); 53 + } 54 + 55 + int kf_checkkeyrelease(kf_inputbind_t id) 56 + { 57 + return 58 + (kf_inputbinds.key[id] != KEY_NULL && IsKeyReleased(kf_inputbinds.key[id])) || 59 + (kf_inputbinds.alt[id] != KEY_NULL && IsKeyReleased(kf_inputbinds.alt[id])); 60 + } 61 + 62 + int kf_checkkeydown(kf_inputbind_t id) 63 + { 64 + return 65 + (kf_inputbinds.key[id] != KEY_NULL && IsKeyDown(kf_inputbinds.key[id])) || 66 + (kf_inputbinds.alt[id] != KEY_NULL && IsKeyDown(kf_inputbinds.alt[id])); 67 + } 68 + 69 + int kf_checkkeyup(kf_inputbind_t id) 70 + { 71 + return 72 + (kf_inputbinds.key[id] != KEY_NULL && IsKeyUp(kf_inputbinds.key[id])) || 73 + (kf_inputbinds.alt[id] != KEY_NULL && IsKeyUp(kf_inputbinds.alt[id])); 74 + } 75 + 76 + int kf_checkmousepress(kf_inputbind_t id) 77 + { 78 + return kf_inputbinds.mouse[id] != MOUSE_BUTTON_UNKNOWN && IsMouseButtonPressed(kf_inputbinds.mouse[id]); 79 + } 80 + 81 + int kf_checkmouserelease(kf_inputbind_t id) 82 + { 83 + return kf_inputbinds.mouse[id] != MOUSE_BUTTON_UNKNOWN && IsMouseButtonReleased(kf_inputbinds.mouse[id]); 84 + } 85 + 86 + int kf_checkmousedown(kf_inputbind_t id) 87 + { 88 + return kf_inputbinds.mouse[id] != MOUSE_BUTTON_UNKNOWN && IsMouseButtonDown(kf_inputbinds.mouse[id]); 89 + } 90 + 91 + int kf_checkmouseup(kf_inputbind_t id) 92 + { 93 + return kf_inputbinds.mouse[id] != MOUSE_BUTTON_UNKNOWN && IsMouseButtonUp(kf_inputbinds.mouse[id]); 94 + } 95 + 96 + int kf_checkgamepadpress(kf_inputbind_t id) 97 + { 98 + return kf_inputbinds.gamepad[id] != GAMEPAD_BUTTON_UNKNOWN && IsGamepadButtonPressed(0, kf_inputbinds.gamepad[id]); 99 + } 100 + 101 + int kf_checkgamepadrelease(kf_inputbind_t id) 102 + { 103 + return kf_inputbinds.gamepad[id] != GAMEPAD_BUTTON_UNKNOWN && IsGamepadButtonReleased(0, kf_inputbinds.gamepad[id]); 104 + } 105 + 106 + int kf_checkgamepaddown(kf_inputbind_t id) 107 + { 108 + return kf_inputbinds.gamepad[id] != GAMEPAD_BUTTON_UNKNOWN && IsGamepadButtonDown(0, kf_inputbinds.gamepad[id]); 109 + } 110 + 111 + int kf_checkgamepadup(kf_inputbind_t id) 112 + { 113 + return kf_inputbinds.gamepad[id] != GAMEPAD_BUTTON_UNKNOWN && IsGamepadButtonUp(0, kf_inputbinds.gamepad[id]); 114 + } 115 + 116 + float kf_getgamepadaxis(kf_inputbind_t id) 117 + { 118 + return kf_inputbinds.axis[id] != GAMEPAD_AXIS_UNKNOWN ? GetGamepadAxisMovement(0, kf_inputbinds.gamepad[id]) : 0; 119 + } 120 + 121 + 122 + int kf_checkinputpress(kf_inputbind_t id) 123 + { 124 + return kf_checkkeypress(id) || kf_checkmousepress(id) || kf_checkgamepadpress(id); 125 + } 126 + 127 + int kf_checkinputrelease(kf_inputbind_t id) 128 + { 129 + return kf_checkkeyrelease(id) || kf_checkmouserelease(id) || kf_checkgamepadrelease(id); 130 + } 131 + 132 + int kf_checkinputdown(kf_inputbind_t id) 133 + { 134 + return kf_checkkeydown(id) || kf_checkmousedown(id) || kf_checkgamepaddown(id); 135 + } 136 + 137 + int kf_checkinputup(kf_inputbind_t id) 138 + { 139 + return kf_checkkeyup(id) || kf_checkmouseup(id) || kf_checkgamepadup(id); 140 + }
+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 +
+61 -123
src/main.c
··· 1 - #include "keraforge/actor.h" 2 - #include "keraforge/math.h" 3 - #include "keraforge/world.h" 4 #include <raylib.h> 5 #include <raymath.h> 6 - #include <keraforge.h> 7 - #include <stdio.h> 8 - #include <stdlib.h> 9 10 - static Camera2D cam; 11 - static f32 dt = 0; 12 13 - static void _player_tick(struct kf_world *world, struct kf_actor *self) 14 { 15 - bool w = IsKeyDown(KEY_W), s = IsKeyDown(KEY_S); 16 - bool a = IsKeyDown(KEY_A), d = IsKeyDown(KEY_D); 17 - struct kf_vec2(f32) v = {0, 0}; 18 - 19 - if (w && s) 20 - v.y = 0; 21 - else if (w) 22 - v.y = -1; 23 - else if (s) 24 - v.y = 1; 25 - 26 - if (a && d) 27 - v.x = 0; 28 - else if (a) 29 - v.x = -1; 30 - else if (d) 31 - v.x = 1; 32 - 33 - if (v.x || v.y) 34 - kf_actor_addforce(self, kf_normalize_vec2(f32)(v)); 35 - 36 - if (IsKeyPressed(KEY_SPACE) && self->speedmod <= 1.0f) 37 - self->speedmod = 3.0f; 38 - 39 - kf_actor_move(world, self, dt); 40 } 41 42 - static void _player_draw(struct kf_world *world, struct kf_actor *self) 43 - { 44 - (void)world; 45 - DrawCircle(self->pos.x, self->pos.y, 4, RED); 46 - 47 - cam.target.x = self->pos.x; 48 - cam.target.y = self->pos.y; 49 - } 50 51 int main(int argc, const char *argv[]) 52 { 53 (void)argc; 54 (void)argv; 55 56 - SetTraceLogLevel(LOG_WARNING); 57 - InitWindow(800, 600, "Keraforge"); 58 - SetTargetFPS(60); 59 - 60 - kf_tiles.color[0] = GREEN; 61 - kf_tiles.color[1] = BROWN; 62 - kf_tiles.color[2] = GRAY; 63 - kf_tiles.collide[2] = true; 64 - 65 - if (!DirectoryExists("data")) 66 - MakeDirectory("data"); 67 - 68 - struct kf_world *world = NULL; 69 - if (!kf_exists("data/map.bin")) 70 - { 71 - printf("-> creating world\n"); 72 - world = kf_world_new(128, 128, 0); 73 - for (size_t i = 0 ; i < world->width*world->height ; i += (size_t)((float)(rand())/RAND_MAX*10)) 74 - world->map[i] = (rand()/(float)RAND_MAX*3); 75 - printf("-> saving world\n"); 76 - size_t len = kf_world_getsize(world); 77 - printf("-> writing of %lu bytes\n", len); 78 - if (!kf_writebin("data/map.bin", (u8 *)world, len)) 79 - { 80 - fprintf(stderr, "error creating map: failed to save map.bin\n"); 81 - free(world); 82 - exit(1); 83 - } 84 - } 85 - else 86 - { 87 - printf("-> loading world\n"); 88 - size_t len = 0; 89 - world = (struct kf_world *)kf_readbin("data/map.bin", &len); 90 - printf("-> world is %lu bytes\n", len); 91 - } 92 - if (!world) 93 - { 94 - fprintf(stderr, "error: failed to load world\n"); 95 - exit(1); 96 - } 97 - 98 - struct kf_actor *player = kf_actor_new(); 99 - player->pos.x = world->width / 4.0f * KF_TILE_SIZE_PX; 100 - player->pos.y = world->height / 4.0f * KF_TILE_SIZE_PX; 101 - player->vel.x = 0; 102 - player->vel.y = 0; 103 - player->size.x = 6; 104 - player->size.y = 6; 105 - player->speed = 25; 106 - player->speedmod = 1; 107 - player->friction = 1.25f; 108 - player->collide = true; 109 - player->tick = _player_tick; 110 - player->draw = _player_draw; 111 - 112 - cam = (Camera2D){{GetScreenWidth() / 2.0f, GetScreenHeight() / 2.0f}, {0, 0}, 0, 2}; 113 - while (!WindowShouldClose()) 114 - { 115 - player->tick(world, player); 116 - 117 - BeginDrawing(); 118 - ClearBackground(WHITE); 119 - 120 - BeginMode2D(cam); 121 - kf_world_draw(world, cam); 122 - player->draw(world, player); 123 - EndMode2D(); 124 125 - DrawFPS(0, 0); 126 - DrawText(TextFormat("%f", dt), 0, 20, 20, RED); 127 128 - EndDrawing(); 129 130 - dt = GetFrameTime(); 131 - } 132 - 133 - if (world) 134 - { 135 - if (!kf_writebin("data/map.bin", (u8 *)world, kf_world_getsize(world))) 136 - fprintf(stderr, "error: failed to save map.bin\n"); 137 - free(world); 138 - } 139 - CloseWindow(); 140 141 return 0; 142 }
··· 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 }
+33
src/math.c
··· 2 #include <raylib.h> 3 #include <raymath.h> 4 5 #define x(T) \ 6 struct kf_vec2(T) kf_normalize_vec2##T(struct kf_vec2(T) v) \ 7 { \ ··· 24 struct kf_vec2(T) kf_vec2##T##_y(struct kf_vec2(T) a) { return (struct kf_vec2(T)){0, a.y}; } 25 _kf_mathdef(x) 26 #undef x
··· 2 #include <raylib.h> 3 #include <raymath.h> 4 5 + 6 #define x(T) \ 7 struct kf_vec2(T) kf_normalize_vec2##T(struct kf_vec2(T) v) \ 8 { \ ··· 25 struct kf_vec2(T) kf_vec2##T##_y(struct kf_vec2(T) a) { return (struct kf_vec2(T)){0, a.y}; } 26 _kf_mathdef(x) 27 #undef x 28 + 29 + 30 + static 31 + int _kf_isdir(enum kf_direction dir) 32 + { 33 + return dir >= kf_north && dir <= kf_west; 34 + } 35 + 36 + enum kf_direction kf_rotatecw(enum kf_direction dir) 37 + { 38 + return _kf_isdir(dir+1) ? kf_north : dir+1; 39 + } 40 + 41 + enum kf_direction kf_rotateccw(enum kf_direction dir) 42 + { 43 + return _kf_isdir(dir-1) ? kf_west : dir-1; 44 + } 45 + 46 + struct kf_vec2(f32) kf_dtov2f(enum kf_direction dir) 47 + { 48 + switch (dir) { 49 + case kf_north: 50 + return (struct kf_vec2(f32)){ -1, 0 }; 51 + case kf_east: 52 + return (struct kf_vec2(f32)){ 0, 1 }; 53 + case kf_south: 54 + return (struct kf_vec2(f32)){ 1, 0 }; 55 + case kf_west: 56 + return (struct kf_vec2(f32)){ 0, -1 }; 57 + } 58 + return (struct kf_vec2(f32)){ 0, 0 }; 59 + }
+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 + }
+39
src/sprites.c
···
··· 1 + #include <keraforge.h> 2 + #include <raylib.h> 3 + 4 + 5 + struct kf_spritesheet kf_loadspritesheet(char *filename, int spritewidth, int spriteheight) 6 + { 7 + Texture2D tex = LoadTexture(filename); 8 + return (struct kf_spritesheet){ 9 + .texture = tex, 10 + .spritewidth = spritewidth, 11 + .spriteheight = spriteheight, 12 + .nsprites = (tex.width / spritewidth) * (tex.height / spriteheight), 13 + }; 14 + } 15 + 16 + inline 17 + void kf_drawsprite_wh(struct kf_spritesheet *sheet, f32 x, f32 y, f32 w, f32 h, int spritex, int spritey) 18 + { 19 + KF_SANITY_CHECK(sheet != NULL, "spritesheet is null"); 20 + 21 + DrawTexturePro( 22 + sheet->texture, 23 + (Rectangle){ 24 + spritex * sheet->spritewidth, 25 + spritey * sheet->spriteheight, 26 + sheet->spritewidth, 27 + sheet->spriteheight }, 28 + (Rectangle){ x, y, w, h }, 29 + (Vector2){ 0, 0 }, 30 + 0, 31 + WHITE 32 + ); 33 + } 34 + 35 + inline 36 + void kf_drawsprite(struct kf_spritesheet *sheet, f32 x, f32 y, int spritex, int spritey) 37 + { 38 + kf_drawsprite_wh(sheet, x, y, sheet->spritewidth, sheet->spriteheight, spritex, spritey); 39 + }
+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 + }
+89
src/ui.c
···
··· 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, 10 + .down = KF_INPUTBIND_NONE, 11 + .fmt = " %s ", 12 + .selectfmt = "> %s <", 13 + .fontsize = 20, 14 + .bg = BLACK, 15 + .fg = WHITE, 16 + .panelheight = 200, 17 + .xpadding = 8, 18 + .ypadding = 16, 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; 44 + } 45 + 46 + int kf_ui_choice(char *title, char **choices, int nchoices, int *choice) 47 + { 48 + int y = kf_ui_panel(title); 49 + 50 + if (*choice >= nchoices) 51 + goto skip_text; 52 + 53 + for (int i = 0 ; i < nchoices ; i++) 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; 80 + } 81 + 82 + int kf_ui_yesno(char *title, int *choice) 83 + { 84 + static char *yesno[] = { "yes", "no" }; 85 + return !kf_ui_choice(title, yesno, 2, choice); 86 + } 87 + 88 + int kf_ui_textinput(char *title, char *text); 89 +
+295 -16
src/world.c
··· 1 - #include <keraforge/world.h> 2 - #include <keraforge/actor.h> 3 #include <raylib.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <math.h> 7 8 - struct _kf_tiles kf_tiles; 9 10 struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill) 11 { 12 - const size_t len = sizeof(kf_tileid_t) * width * height; 13 - struct kf_world *world = malloc(sizeof(struct kf_world) + len); 14 world->revision = 0; 15 world->width = width; 16 world->height = height; 17 - memset(world->map, fill, len); 18 return world; 19 } 20 21 size_t kf_world_getsize(struct kf_world *world) 22 { 23 - return sizeof(struct kf_world) + sizeof(kf_tileid_t)*world->width*world->height; 24 } 25 26 - kf_tileid_t *kf_world_gettile(struct kf_world *world, u32 x, u32 y) 27 { 28 return &world->map[y*world->width + x]; 29 } 30 31 void kf_world_draw(struct kf_world *world, Camera2D camera) 32 { 33 const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); ··· 37 const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 38 const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 39 const size_t down = world->width - ex + sx; /* number of indexes to add to reach the next tile down */ 40 - kf_tileid_t *tile = kf_world_gettile(world, sx, sy); 41 u32 x; 42 for (u32 y = sy ; y < ey ; y++) 43 { 44 for (x = sx ; x < ex ; x++) 45 { 46 - DrawRectangle( 47 - (int)x * KF_TILE_SIZE_PX, 48 - (int)y * KF_TILE_SIZE_PX, 49 - KF_TILE_SIZE_PX, 50 - KF_TILE_SIZE_PX, 51 - kf_tiles.color[*tile] 52 - ); 53 tile++; /* shift tile pointer to the right */ 54 } 55 tile += down; /* shift tile pointer down */ 56 } 57 }
··· 1 + #include "keraforge/bini.h" 2 + #include <keraforge.h> 3 #include <raylib.h> 4 + #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <math.h> 8 9 + 10 + struct _kf_tiles kf_tiles = {0}; 11 + 12 + 13 + static inline 14 + void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y); 15 + 16 + 17 + kf_tileid_t kf_addtile(struct kf_tile_opts opts) 18 + { 19 + KF_SANITY_CHECK(opts.key != NULL, "tile added without key"); 20 + KF_SANITY_CHECK(opts.sheet != NULL, "tile added without sheet"); 21 + 22 + kf_tileid_t id = ++kf_tiles.count; 23 + 24 + kf_tiles.key[id] = opts.key; 25 + kf_tiles.mapcol[id] = opts.mapcol; 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 + } 33 34 struct kf_world *kf_world_new(u32 width, u32 height, kf_tileid_t fill) 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; 42 + memset(world->map, 0, sizeof(struct kf_tile)*width*height); 43 + for (size_t i = 0 ; i < width*height ; i++) 44 + { 45 + world->map[i].subid = fill; 46 + world->map[i].id = fill; 47 + } 48 + 49 + u32 x; 50 + for (u32 y = 0 ; y < height ; y++) 51 + { 52 + for (x = 0 ; x < width ; x++) 53 + { 54 + _kf_updatetilebitmask(world, x, y); 55 + } 56 + } 57 + 58 return world; 59 } 60 61 size_t kf_world_getsize(struct kf_world *world) 62 { 63 + return sizeof(struct kf_world) + sizeof(struct kf_tile)*world->width*world->height; 64 + } 65 + 66 + static inline 67 + void _kf_updatetilebitmask(struct kf_world *world, u32 x, u32 y) 68 + { 69 + struct kf_tile *t = kf_world_gettile(world, x, y); 70 + kf_tileid_t 71 + n = y-1 >= world->height ? 0 : kf_world_gettile(world, x, y-1)->id, 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; 79 + if (t->id == s) t->data |= KF_TILEMASK_SOUTH; 80 } 81 82 + void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours) 83 + { 84 + _kf_updatetilebitmask(world, x, y); 85 + if (update_neighbours) 86 + { 87 + if (y-1 < world->height) kf_world_updatetile(world, x, y-1, false); 88 + if (x-1 < world->width) kf_world_updatetile(world, x-1, y, false); 89 + if (x+1 < world->width) kf_world_updatetile(world, x+1, y, false); 90 + if (y+1 < world->height) kf_world_updatetile(world, x, y+1, false); 91 + } 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) 120 { 121 return &world->map[y*world->width + x]; 122 } 123 124 + void kf_world_drawcolliders(struct kf_world *world, struct kf_actor *player, Camera2D camera) 125 + { 126 + Rectangle r = { 127 + player->pos.x + (player->size.x / 2) + player->sizeoffset.x, 128 + player->pos.y + (player->size.y / 2) + player->sizeoffset.y, 129 + player->size.x, 130 + player->size.y 131 + }; 132 + 133 + DrawRectangleLinesEx(r, 1, BLUE); 134 + 135 + const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); 136 + const Vector2 end = GetScreenToWorld2D((Vector2){GetScreenWidth(), GetScreenHeight()}, camera); 137 + const u32 sx = fmax(0, floorf(start.x / KF_TILE_SIZE_PX)); 138 + const u32 sy = fmax(0, floorf(start.y / KF_TILE_SIZE_PX)); 139 + const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 140 + const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 141 + const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */ 142 + struct kf_tile *tile = kf_world_gettile(world, sx, sy); 143 + 144 + /* check if any tiles will collide with the actor's rect */ 145 + const f32 trx = sx * KF_TILE_SIZE_PX; 146 + Rectangle tr = { 147 + trx, 148 + sy * KF_TILE_SIZE_PX, 149 + KF_TILE_SIZE_PX, 150 + KF_TILE_SIZE_PX, 151 + }; /* tile rect */ 152 + u32 x; 153 + 154 + for (u32 y = sy ; y <= ey ; y++) 155 + { 156 + for (x = sx ; x <= ex ; x++) 157 + { 158 + if (kf_tiles.collide[tile->id]) 159 + DrawRectangleLinesEx(tr, 1, CheckCollisionRecs(r, tr) ? RED : BLACK); 160 + 161 + tile++; /* shift tile pointer to the right */ 162 + tr.x += KF_TILE_SIZE_PX; 163 + } 164 + tile += down; /* shift tile pointer down */ 165 + tr.x = trx; 166 + tr.y += KF_TILE_SIZE_PX; 167 + } 168 + } 169 + 170 void kf_world_draw(struct kf_world *world, Camera2D camera) 171 { 172 const Vector2 start = GetScreenToWorld2D((Vector2){0, 0}, camera); ··· 176 const u32 ex = fmin(world->width, ceilf(end.x / KF_TILE_SIZE_PX)); 177 const u32 ey = fmin(world->height, ceilf(end.y / KF_TILE_SIZE_PX)); 178 const size_t down = world->width - ex + sx; /* number of indexes to add to reach the next tile down */ 179 + struct kf_tile *tile = kf_world_gettile(world, sx, sy); 180 u32 x; 181 for (u32 y = sy ; y < ey ; y++) 182 { 183 for (x = sx ; x < ex ; x++) 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], 193 + x*KF_TILE_SIZE_PX, 194 + y*KF_TILE_SIZE_PX, 195 + KF_TILE_SIZE_PX, 196 + KF_TILE_SIZE_PX, 197 + kf_tiles.sprite[tile->subid].x + 2, /* 2,1 are offsets for the "middle" tile */ 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, 208 + y*KF_TILE_SIZE_PX, 209 + KF_TILE_SIZE_PX, 210 + KF_TILE_SIZE_PX, 211 + kf_tiles.sprite[tile->id].x + s.x, 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 +
+39
todo
···
··· 1 + 2 + Dots (.) are to-do 3 + Slashes (/) are in-progress 4 + Xs (X) are done 5 + Squiggly lines (~) are maybes 6 + 7 + Keraforge 1.0 8 + ------------- 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 33 + . Cutscenes 34 + . Engine 35 + . Map+Room editor 36 + . Character creator 37 + . Dialogue editor 38 + . Quest creator 39 + ~ Scripting
+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 + }