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

Compare changes

Choose any two refs to compare.

+10 -1
.gitignore
··· 1 + # Data 2 + /data/ 3 + 4 + # Built files 1 5 /build/ 2 6 3 - /data.bak/ 7 + # Backups 8 + *.bak 9 + 10 + # Raylib 11 + /raylib.tar.gz 12 + /raylib/
+1
compile_flags.txt
··· 4 4 5 5 -std=c99 6 6 -Iinclude/ 7 + -Iraylib/src/ 7 8 -xc-header 8 9 9 10 -DKF_SANITY_CHECKS
+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)
+24 -13
include/keraforge/actor.h
··· 7 7 #include <keraforge/world.h> 8 8 #include <keraforge/math.h> 9 9 #include <keraforge/sprites.h> 10 + #include <keraforge/bini.h> 10 11 #include <raylib.h> 11 12 12 13 ··· 18 19 TODO: This will probably be better as a hash table (especially for kf_actor_getregistryid). */ 19 20 struct kf_actorregistry 20 21 { 22 + /* Amount of actor types currently registered. */ 21 23 int count; 22 - char *id[KF_MAX_ACTOR_TYPES]; 23 - u8 *(*serialize[KF_MAX_ACTOR_TYPES])(struct kf_actor *self, size_t *plen); 24 - void (*deserialize[KF_MAX_ACTOR_TYPES])(struct kf_actor *self, u8 *data, size_t len); 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]; 25 36 }; 26 37 /* Global actor registry instance. */ 27 38 extern struct kf_actorregistry kf_actorregistry; ··· 29 40 /* Represents any kinematic body in the world (players, NPCs, etc). */ 30 41 struct kf_actor 31 42 { 32 - /* Unique string identifier for this actor. */ 33 - char *id; 34 - /* Spritesheet for the actor. */ 35 - struct kf_spritesheet sprites; 43 + /* Integer identifier for this actor, comes from kf_actorregistry. 44 + This is unique per-actor-type, not per-actor. */ 45 + int id; 36 46 /* The actor's position. */ 37 47 struct kf_vec2(f32) pos; 38 48 /* The actor's velocity. */ ··· 55 65 enum kf_direction pointing; 56 66 /* If the actor is running. This will not increase their speed, you are expected to yourself. */ 57 67 bool running; 58 - /* Called every frame to update the actor. Don't forget about deltatime (kf_dts, kf_dtms)! */ 59 - void (*tick)(struct kf_actor *self); 60 - /* Called every frame to render the actor. */ 61 - void (*draw)(struct kf_actor *self); 62 68 /* Doubly-linked list of actors. */ 63 69 struct kf_actor *prev, *next; 64 70 }; 65 71 /* Linked list of actors. */ 66 72 extern struct kf_actor *kf_actors, *kf_actors_last; 73 + /* Number of actors currently in the world. */ 67 74 extern u32 kf_actor_count; 68 75 69 76 70 77 /* Create a new actor. */ 71 - struct kf_actor *kf_actor_new(char *id, struct kf_spritesheet sprites, f32 width, f32 height, bool collides); 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); 72 81 73 82 /* Load a spritesheet, filling in the details for loading character spritesheets. */ 74 83 struct kf_spritesheet kf_actor_loadspritesheet(char *filename); ··· 84 93 85 94 /* Get the registry ID of a given actor ID. 86 95 This will perform a lot of string comparisons, use sparingly! */ 87 - int kf_actor_getregistryid(char *id); 96 + int kf_actor_getregistryid(char *key); 88 97 89 98 /* Save actor data. */ 90 99 int kf_saveactors(void); 100 + /* Load actor data. */ 101 + int kf_loadactors(void); 91 102 92 103 93 104 #endif
+2
include/keraforge/error.h
··· 3 3 4 4 5 5 #include <keraforge/_header.h> 6 + #include <stdio.h> 6 7 7 8 9 + /* Print a traceback. Requires GNU extensions to work properly. */ 8 10 void kf_printbacktrace(FILE *file); 9 11 10 12
+11
include/keraforge/graphics.h
··· 10 10 /* Represents either a model or a menu. */ 11 11 struct kf_modal 12 12 { 13 + /* The title of this modal. */ 13 14 char *name; 15 + /* Called when this modal is closed. */ 14 16 void (*exit)(void); 17 + /* Called when this modal is opened/ */ 15 18 void (*init)(void); 19 + /* Called every frame before rendering anything. */ 16 20 void (*update)(void); 21 + /* Called every frame to render the world. */ 17 22 void (*render_world)(void); 23 + /* Called every frame to render the UI. */ 18 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. */ 19 27 void *data; 20 28 }; 21 29 ··· 63 71 extern f32 kf_dts; 64 72 65 73 74 + /* Measure text using the default font. */ 66 75 int kf_measuretext(int size, char *text); 76 + /* Draw text using the default font. */ 67 77 void kf_drawtext(Color c, int x, int y, int size, char *text); 78 + /* Draw text with a shadow using the default font. */ 68 79 void kf_drawtextshadowed(Color c, int x, int y, int size, char *text); 69 80 70 81
+6 -1
include/keraforge/input.h
··· 6 6 #include <raylib.h> 7 7 8 8 9 + /* Represents an unknown mouse button. Provided by Keraforge, not by Raylib. */ 9 10 #define MOUSE_BUTTON_UNKNOWN ((MouseButton)-1) 11 + /* Represents an unknown gamepad axis. Provided by Keraforge, not by Raylib. */ 10 12 #define GAMEPAD_AXIS_UNKNOWN ((GamepadAxis)-1) 11 13 12 14 #define KF_INPUTBIND_MAX UINT8_MAX ··· 17 19 /* Struct-of-Arrays for keybindings. */ 18 20 struct _kf_inputbinds 19 21 { 20 - kf_inputbind_t count; /* must start at 1. 0 is the `none` keybind. */ 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. */ 21 26 char *id[KF_INPUTBIND_MAX]; 22 27 KeyboardKey key[KF_INPUTBIND_MAX]; 23 28 KeyboardKey alt[KF_INPUTBIND_MAX];
+1
include/keraforge/inputbinds.h
··· 33 33 ; 34 34 35 35 36 + /* Load default input bindings. */ 36 37 void kf_loaddefaultbinds(void); 37 38 38 39
+8 -2
include/keraforge/log.h
··· 1 1 #ifndef __kf_log__ 2 2 #define __kf_log__ 3 3 4 + 4 5 #include <stdio.h> /* fprintf, stderr */ 5 6 #include <stdlib.h> /* exit */ 6 7 #include <stdarg.h> 7 8 9 + 10 + /* Log a message. */ 8 11 void kf_vlog(char *level, char *fmt, va_list va); 12 + /* Log a message. */ 9 13 void kf_log(char *level, char *fmt, ...); 14 + /* Log a debug message. */ 10 15 void kf_logdbg(char *fmt, ...); 16 + /* Log an info message. */ 11 17 void kf_loginfo(char *fmt, ...); 18 + /* Log a error message. */ 12 19 void kf_logerr(char *fmt, ...); 13 20 14 - /* Errors */ 15 21 16 22 /* Throw a formatted error message without printing a traceback or exiting. */ 17 23 #define KF_THROWSOFTER(MSG, ...) \ ··· 36 42 } \ 37 43 while (0) 38 44 39 - /* Sanity Checking */ 40 45 41 46 #ifdef KF_SANITY_CHECKS 42 47 /* Indicate that the given expression should never resolve to false and throw an error if it manages to. */ ··· 61 66 # define KF_SANITY_CHECK(EXPR, MSG, ...) do { ; } while (0) 62 67 # define KF_UNREACHABLE(MSG, ...) do { ; } while (0) 63 68 #endif 69 + 64 70 65 71 #endif
+2 -2
include/keraforge/player.h
··· 9 9 void kf_player_tick(struct kf_actor *self); 10 10 void kf_player_draw(struct kf_actor *self); 11 11 12 - u8* kf_player_serialize(struct kf_actor *self, size_t *plen); 13 - void kf_player_deserialize(struct kf_actor *self, u8 *data, size_t len); 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 14 15 15 #endif
+3
include/keraforge/sprites.h
··· 10 10 Can be used for animations or texture atlases. */ 11 11 struct kf_spritesheet 12 12 { 13 + /* The texture to pull sprites from. */ 13 14 Texture2D texture; 15 + /* Width and height of each sprite in this sheet. */ 14 16 u16 spritewidth, spriteheight; 17 + /* Number of sprites in this sheet. */ 15 18 u32 nsprites; 16 19 }; 17 20
+1 -7
include/keraforge/state.h
··· 8 8 #define KF_NPCPOOL_SIZE 1024 9 9 10 10 11 - /* Stores global variables to be saved alongside the world. Use this for save data. 12 - Do not use pointers here! This gets transmuted into a u8 array for serialization. */ 11 + /* Stores global variables to be saved alongside the world. Use this for save data. */ 13 12 struct kf_state 14 13 { 15 14 /* Do not modify this and do not relocate it. See kf_world for an explanation. */ 16 15 u16 revision; 17 - /* Player data */ 18 - struct { 19 - struct kf_vec2(f32) pos; 20 - } player; 21 - // struct kf_actor npc[KF_NPCPOOL_SIZE]; 22 16 }; 23 17 24 18
+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
+15 -15
include/keraforge/world.h
··· 8 8 #include <raylib.h> 9 9 10 10 11 + /* Size of tiles in pixels. */ 11 12 #define KF_TILE_SIZE_PX 16 12 13 14 + /* Number of bits to use for storing tile IDs. See: struct kf_tile. */ 13 15 #define KF_TILE_IDWIDTH 10 16 + /* Number of bits to use for storing tile datum. See: struct kf_tile. */ 14 17 #define KF_TILE_DATAWIDTH 4 15 18 16 - #define KF_TILEID_MAX (1023) /* 2^10-1 */ 19 + /* Number of max tiles. This should be (2^KF_TILE_IDWIDTH-1). */ 20 + #define KF_TILEID_MAX (1023) 17 21 18 22 /* Used to store tile IDs. Stored in 10 bits; max value is 1023! */ 19 23 typedef u16 kf_tileid_t; ··· 31 35 32 36 33 37 /* Represents a singular tile in the world. */ 34 - // struct kf_tile 35 - // { 36 - // kf_tileid_t subid : KF_TILE_IDWIDTH; 37 - // kf_tileid_t id : KF_TILE_IDWIDTH; 38 - // kf_tiledatum_t data : KF_TILE_DATAWIDTH; 39 - // } __attribute__((packed)); 40 38 struct kf_tile 41 39 { 42 - kf_tileid_t subid ;//: KF_TILE_IDWIDTH; 43 - kf_tileid_t id ;//: KF_TILE_IDWIDTH; 44 - kf_tiledatum_t data ;//: KF_TILE_DATAWIDTH; 45 - };// __attribute__((packed)); 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)); 46 44 47 45 /* Represents a world (or a subworld, often called "rooms"). */ 48 46 struct kf_world ··· 93 91 bool transparent; 94 92 }; 95 93 94 + /* Register a tile. */ 96 95 kf_tileid_t kf_addtile(struct kf_tile_opts opts); 96 + /* Register a tile. */ 97 97 #define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ })) 98 98 99 99 /* Create a world using the given width and height. ··· 118 118 /* Draw the part of the world visible to the given camera. */ 119 119 void kf_world_draw(struct kf_world *world, Camera2D camera); 120 120 121 - /* Save a world to map.bin(.xz). */ 122 - int kf_world_save(struct kf_world *world, bool compress); 123 - /* Load a world from a map.bin(.xz). */ 124 - int kf_world_load(struct kf_world **world, bool compressed); 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 125 126 126 #endif
+2
include/keraforge.h
··· 18 18 #include <keraforge/player.h> 19 19 #include <keraforge/sprites.h> 20 20 #include <keraforge/state.h> 21 + #include <keraforge/string.h> 22 + #include <keraforge/time.h> 21 23 #include <keraforge/ui.h> 22 24 #include <keraforge/world.h> 23 25
+23
readme
··· 54 54 Develop 55 55 ------- 56 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 + 57 66 Build "system" is contained in a folder of shell scripts, 58 67 `scripts`. You can run these with the `run.sh` script to 59 68 run multiple tasks in order. Generally: 60 69 `sh run.sh build run` 61 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. 62 85 63 86 License 64 87 -------
+2 -2
scripts/_config.sh
··· 10 10 export KF_DEBUG_CFLAGS="-g -DKF_SANITY_CHECKS" 11 11 export KF_DEBUG_LFLAGS="-g -rdynamic" 12 12 13 - export CFLAGS="-Wall -Wextra -Werror -std=c99 -Iinclude/ -c -DKF_GNU $KF_DEBUG_CFLAGS" 14 - export LFLAGS="-lraylib -lm -lGL -lpthread -ldl -lrt -lX11 -llzma $KF_DEBUG_LFLAGS" 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"
+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 +
+84 -45
src/actor.c
··· 1 + #include "keraforge/actor.h" 1 2 #include "keraforge/fs.h" 2 3 #include <keraforge.h> 3 4 #include <stdlib.h> ··· 10 11 u32 kf_actor_count = 0; 11 12 12 13 13 - struct kf_actor *kf_actor_new(char *id, struct kf_spritesheet sprites, f32 width, f32 height, bool collides) 14 + struct kf_actor *kf_actor_new(char *key) 14 15 { 15 16 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor)); 16 17 kf_actor_count++; ··· 26 27 kf_actors_last = actor; 27 28 } 28 29 29 - actor->id = id; 30 - actor->sprites = sprites; 31 - actor->size.x = width; 32 - actor->size.y = height; 33 - actor->collide = collides; 30 + actor->id = kf_actor_getregistryid(key); 31 + actor->size.x = 10; 32 + actor->size.y = 10; 33 + actor->collide = true; 34 34 35 35 actor->speed = 25; 36 36 actor->speedmod = 1; ··· 38 38 actor->pointing = kf_north; 39 39 40 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; 41 48 } 42 49 43 50 struct kf_spritesheet kf_actor_loadspritesheet(char *filename) ··· 174 181 175 182 x += (int)(kf_s * (frames+1)) % frames; 176 183 177 - kf_drawsprite(&actor->sprites, actor->pos.x, actor->pos.y, x, y); 184 + kf_drawsprite(&kf_actorregistry.sprite[actor->id], actor->pos.x, actor->pos.y, x, y); 178 185 } 179 186 180 - int kf_actor_getregistryid(char *id) 187 + int kf_actor_getregistryid(char *key) 181 188 { 182 - size_t l = strlen(id); 189 + size_t l = strlen(key); 183 190 for (int i = 0 ; i < kf_actorregistry.count ; i++) 184 191 { 185 - char *c = kf_actorregistry.id[i]; 192 + char *c = kf_actorregistry.key[i]; 186 193 size_t cl = strlen(c); 187 - if (strncmp(c, id, l>cl?l:cl) == 0) 194 + if (strncmp(c, key, l>cl?l:cl) == 0) 188 195 { 189 196 return i; 190 197 } ··· 198 205 199 206 int kf_saveactors(void) 200 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 + 201 231 // char *outfile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE; 202 232 char *outfile = _KF_ACTORFILE; 233 + if (!kf_writebin(outfile, bs->buffer, bs->len)) 234 + KF_THROW("failed to write actors to %s", outfile); 203 235 204 - FILE *fp = fopen(outfile, "wb"); 205 - if (!fp) 206 - KF_THROW("failed to open %s", outfile); 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 + } 207 250 208 - size_t nactors = 0; 209 - size_t total_bytes = 0; 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); 210 261 211 - for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 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++) 212 266 { 213 - if (!actor->id) 214 - continue; 215 - int id = kf_actor_getregistryid(actor->id); 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); 216 275 if (id == -1) 217 276 continue; 218 - size_t n = strlen(actor->id); 219 - if (fwrite(actor->id, 1, n, fp) != n) 220 - { 221 - KF_THROWSOFTER("failed to write actor ID: %s", actor->id); 222 - fclose(fp); 223 - return 0; 224 - } 225 - n = 0; 226 - u8 *data = kf_actorregistry.serialize[id](actor, &n); 227 - if (!data) 228 - { 229 - KF_THROWSOFTER("failed to serialize actor of type %s", actor->id); 230 - fclose(fp); 231 - return 0; 232 - } 233 - if (fwrite(data, 1, n, fp) != n) 234 - { 235 - KF_THROWSOFTER("failed to write serialized actor of type %s", actor->id); 236 - fclose(fp); 237 - return 0; 238 - } 239 - nactors++; 240 - total_bytes += n; 277 + struct kf_actor *actor = kf_actor_new(keycpy); 278 + kf_actorregistry.deserialize[id](actor, &bs); 241 279 } 242 280 243 - kf_logdbg("serialized %d actors (%lu bytes)", nactors, total_bytes); 244 - fclose(fp); 281 + kf_logdbg("loaded %d actors", nactors); 282 + 283 + free(bs.buffer); 245 284 246 285 return 1; 247 286 }
+2
src/bini.c
··· 1 + #ifndef bini_impl 1 2 #define bini_impl 3 + #endif 2 4 #include <keraforge/bini.h>
+1 -1
src/editor.c
··· 21 21 kf_logdbg("exiting editor, world is %s", d ? "dirty" : "not dirty"); 22 22 23 23 if (d) 24 - kf_world_save(kf_window.room, false); 24 + kf_timeit("save world", kf_world_save(kf_window.room, true, NULL)); 25 25 26 26 free(kf_window.modal->data); 27 27 kf_window.modal->data = NULL;
+35 -27
src/graphics.c
··· 44 44 45 45 struct kf_state *state = NULL; 46 46 int is_new_state = kf_state_load(&state); 47 + (void)is_new_state; 47 48 kf_window.state = state; 48 49 49 50 struct kf_world *world = NULL; 50 - kf_world_load(&world, false); 51 + kf_timeit("load world", kf_world_load(&world, true, NULL)); 51 52 kf_window.room = world; 52 53 53 - kf_actorregistry.id[0] = "player"; 54 - kf_actorregistry.serialize[0] = kf_player_serialize; 55 - kf_actorregistry.deserialize[0] = kf_player_deserialize; 56 - kf_actorregistry.count++; 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; 57 67 58 - struct kf_actor *player = kf_actor_new("player", kf_actor_loadspritesheet("data/res/img/char/template.png"), 10, 10, true); 59 - player->sizeoffset.y = 6; 60 - player->tick = kf_player_tick; 61 - player->draw = kf_player_draw; 62 - player->controlled = true; 63 - if (is_new_state) /* place the player in the centre of the room */ 68 + if (!kf_exists("data/actors.bin")) 64 69 { 65 - state->player.pos.x = world->width * KF_TILE_SIZE_PX / 2.0f; 66 - state->player.pos.y = world->width * KF_TILE_SIZE_PX / 2.0f; 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()); 67 80 } 68 - player->pos.x = state->player.pos.x; 69 - player->pos.y = state->player.pos.y; 70 - kf_window.player = player; 81 + else 82 + { 83 + kf_timeit("load actors", kf_loadactors()); 84 + } 71 85 72 - struct kf_actor *player2 = kf_actor_new("player", kf_actor_loadspritesheet("data/res/img/char/whom.png"), 10, 10, true); 73 - player2->sizeoffset.y = 6; 74 - player2->tick = kf_player_tick; /* when controlled is false, player_tick won't process keybinds */ 75 - player2->draw = kf_player_draw; 76 - player2->pos.x = player->pos.x; 77 - player2->pos.y = player->pos.y - 24; 86 + kf_window.player = kf_actors; /* player should always be the first actor. */ 78 87 79 - kf_window.cam.target.x = player->pos.x + (player->size.x / 2); 80 - kf_window.cam.target.y = player->pos.y + (player->size.y / 2); 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); 81 90 kf_window.cam.zoom = 2; 82 91 83 92 kf_setmodal(&kf_modal_play); ··· 132 141 133 142 kf_saveactors(); 134 143 135 - kf_window.state->player.pos = kf_window.player->pos; 136 144 kf_state_save(kf_window.state); 137 145 138 146 free(kf_window.player); ··· 181 189 { 182 190 // kf_window.player->tick(kf_window.player); 183 191 for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 184 - actor->tick(actor); 192 + kf_actorregistry.tick[actor->id](actor); 185 193 186 194 Vector2 v = GetScreenToWorld2D(GetMousePosition(), kf_window.cam); 187 195 kf_window.select.x = v.x / KF_TILE_SIZE_PX; ··· 213 221 // kf_world_drawcolliders(world, player, cam); 214 222 // kf_window.player->draw(kf_window.player); 215 223 for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next) 216 - actor->draw(actor); 224 + kf_actorregistry.draw[actor->id](actor); 217 225 } 218 226 219 227 static
+9 -15
src/player.c
··· 1 + #include "keraforge/bini.h" 1 2 #include <keraforge.h> 2 3 3 4 ··· 81 82 } 82 83 } 83 84 84 - 85 - struct _kf_serialized_player 86 - { 87 - struct kf_vec2(f32) pos; 88 - }; 89 - 90 - u8* kf_player_serialize(struct kf_actor *self, size_t *plen) 85 + void kf_player_serialize(struct kf_actor *self, struct bini_stream *bs) 91 86 { 92 - *plen = sizeof(struct _kf_serialized_player); 93 - struct _kf_serialized_player *s = malloc(*plen); 94 - s->pos = self->pos; 95 - return (u8 *)s; 87 + bini_wf(bs, self->pos.x); 88 + bini_wf(bs, self->pos.y); 89 + bini_wb(bs, self->controlled); 96 90 } 97 91 98 - void kf_player_deserialize(struct kf_actor *self, u8 *data, size_t len) 92 + void kf_player_deserialize(struct kf_actor *self, struct bini_stream *bs) 99 93 { 100 - (void)len; 101 - struct _kf_serialized_player *s = (struct _kf_serialized_player *)data; 102 - self->pos = s->pos; 94 + self->pos.x = bini_rf(bs); 95 + self->pos.y = bini_rf(bs); 96 + self->controlled = bini_rb(bs); 103 97 }
+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 + }
+4 -4
src/world.c
··· 240 240 } 241 241 } 242 242 243 - int kf_world_save(struct kf_world *world, bool compress) 243 + int kf_world_save(struct kf_world *world, bool compress, char *outfile) 244 244 { 245 - char *outfile = compress ? _KF_MAPFILE_TMP : _KF_MAPFILE; 245 + outfile = outfile ? outfile : (compress ? _KF_MAPFILE_TMP : _KF_MAPFILE); 246 246 struct bini_stream *bs = bini_new(); 247 247 _kf_world_save_bs(world, bs); 248 248 if (!kf_writebin(outfile, bs->buffer, bs->len)) ··· 284 284 *pworld = world; 285 285 } 286 286 287 - int kf_world_load(struct kf_world **pworld, bool compressed) 287 + int kf_world_load(struct kf_world **pworld, bool compressed, char *infile) 288 288 { 289 289 if (compressed) /* decompress before loading */ 290 290 { ··· 302 302 } 303 303 } 304 304 305 - char *infile = compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE; 305 + infile = infile ? infile : (compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE); 306 306 kf_logdbg("loading world: %s", infile); 307 307 308 308 size_t len = 0;
+13 -2
todo
··· 10 10 . Core 11 11 . World 12 12 x Tiles 13 - . Actors 13 + / Actors 14 + x Rendering 15 + x Serialization 16 + . NPC paths (i.e, walking to/from locations. Stardew Valley style) 14 17 x Compression 15 18 . Compress without saving the world binary as an intermediate step. 16 - All I need to do for this is adapt the compression functions to have an in-memory (`u8 *`) compression equivalent instead of just `FILE *` compression. 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. 17 27 . Dialogue 18 28 . Quests 29 + . UI+layout library 19 30 . Combat 20 31 . Character Stats 21 32 . (De)Buffs
+5 -15
tools/newgame.c
··· 73 73 struct kf_world *world = NULL; 74 74 75 75 kf_loginfo("creating world"); 76 - world = kf_world_new(width, height, 2); 76 + kf_timeit("create world", { 77 + world = kf_world_new(width, height, 2); 78 + }); 77 79 78 80 /* path for our map.bin */ 79 81 char worldpath[4096] = {0}; ··· 82 84 MakeDirectory(GetDirectoryPath(worldpath)); 83 85 84 86 size_t len = kf_world_getsize(world); 85 - kf_loginfo("saving world (%lu bytes uncompressed)", len); 86 - kf_world_save(world, compress); 87 - // if (!kf_writebin(worldpath, (u8 *)world, len)) 88 - // KF_THROW("failed to save %s", worldpath); 89 - 90 - if (compress) 91 - { 92 - char worldxzpath[4096] = {0}; 93 - strcpy(worldxzpath, path); 94 - strcpy(&worldxzpath[0] + strlen(path), "/map.bin.xz"); 95 - if (!kf_compress(worldpath, worldxzpath)) 96 - KF_THROW("failed to compress %s", worldpath); 97 - remove(worldpath); /* no longer needed */ 98 - } 87 + kf_loginfo("saving world to %s (%lu bytes uncompressed)", worldpath, len); 88 + kf_timeit("save world", kf_world_save(world, compress, worldpath)); 99 89 100 90 free(world); 101 91 }