+10
-1
.gitignore
+10
-1
.gitignore
+3
compile_flags.txt
+3
compile_flags.txt
+56
etc/style.c
+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
+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)
+48
-7
include/keraforge/actor.h
+48
-7
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
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
+
13
40
/* Represents any kinematic body in the world (players, NPCs, etc). */
14
41
struct kf_actor
15
42
{
16
-
/* Spritesheet for the actor. */
17
-
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;
18
46
/* The actor's position. */
19
47
struct kf_vec2(f32) pos;
20
48
/* The actor's velocity. */
···
37
65
enum kf_direction pointing;
38
66
/* If the actor is running. This will not increase their speed, you are expected to yourself. */
39
67
bool running;
40
-
/* Called every frame to update the actor. Don't forget about deltatime (kf_dts, kf_dtms)! */
41
-
void (*tick)(struct kf_actor *self);
42
-
/* Called every frame to render the actor. */
43
-
void (*draw)(struct kf_actor *self);
68
+
/* Doubly-linked list of actors. */
69
+
struct kf_actor *prev, *next;
44
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;
45
75
46
76
47
77
/* Create a new actor. */
48
-
struct kf_actor *kf_actor_new(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);
49
81
50
82
/* Load a spritesheet, filling in the details for loading character spritesheets. */
51
83
struct kf_spritesheet kf_actor_loadspritesheet(char *filename);
···
58
90
void kf_actor_move(struct kf_world *world, struct kf_actor *actor, f32 deltatime);
59
91
/* Draw the actor. */
60
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);
61
102
62
103
63
104
#endif
+500
include/keraforge/bini.h
+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__ */
+2
include/keraforge/error.h
+2
include/keraforge/error.h
+11
include/keraforge/graphics.h
+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
-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
+1
include/keraforge/inputbinds.h
+8
-2
include/keraforge/log.h
+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
include/keraforge/player.h
+2
include/keraforge/player.h
+3
include/keraforge/sprites.h
+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
+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
+12
include/keraforge/string.h
+78
include/keraforge/time.h
+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
+11
-5
include/keraforge/world.h
+11
-5
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;
···
87
91
bool transparent;
88
92
};
89
93
94
+
/* Register a tile. */
90
95
kf_tileid_t kf_addtile(struct kf_tile_opts opts);
96
+
/* Register a tile. */
91
97
#define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ }))
92
98
93
99
/* Create a world using the given width and height.
···
112
118
/* Draw the part of the world visible to the given camera. */
113
119
void kf_world_draw(struct kf_world *world, Camera2D camera);
114
120
115
-
/* Save a world to map.bin(.xz). */
116
-
int kf_world_save(struct kf_world *world, bool compress);
117
-
/* Load a world from a map.bin(.xz). */
118
-
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);
119
125
120
126
#endif
+3
include/keraforge.h
+3
include/keraforge.h
···
7
7
#include <keraforge/log.h>
8
8
9
9
#include <keraforge/actor.h>
10
+
#include <keraforge/bini.h>
10
11
#include <keraforge/editor.h>
11
12
#include <keraforge/error.h>
12
13
#include <keraforge/fs.h>
···
17
18
#include <keraforge/player.h>
18
19
#include <keraforge/sprites.h>
19
20
#include <keraforge/state.h>
21
+
#include <keraforge/string.h>
22
+
#include <keraforge/time.h>
20
23
#include <keraforge/ui.h>
21
24
#include <keraforge/world.h>
22
25
+23
readme
+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
+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
+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
+10
scripts/run-valgrind.sh
+134
-6
src/actor.c
+134
-6
src/actor.c
···
1
+
#include "keraforge/actor.h"
2
+
#include "keraforge/fs.h"
1
3
#include <keraforge.h>
2
4
#include <stdlib.h>
3
5
#include <raymath.h>
6
+
#include <string.h>
4
7
5
8
6
-
struct kf_actor *kf_actor_new(struct kf_spritesheet sprites, f32 width, f32 height, bool collides)
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)
7
15
{
8
16
struct kf_actor *actor = calloc(1, sizeof(struct kf_actor));
17
+
kf_actor_count++;
9
18
10
-
actor->sprites = sprites;
11
-
actor->size.x = width;
12
-
actor->size.y = height;
13
-
actor->collide = collides;
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;
14
34
15
35
actor->speed = 25;
16
36
actor->speedmod = 1;
···
18
38
actor->pointing = kf_north;
19
39
20
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;
21
48
}
22
49
23
50
struct kf_spritesheet kf_actor_loadspritesheet(char *filename)
···
154
181
155
182
x += (int)(kf_s * (frames+1)) % frames;
156
183
157
-
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);
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;
158
286
}
+4
src/bini.c
+4
src/bini.c
+1
-1
src/editor.c
+1
-1
src/editor.c
+45
-17
src/graphics.c
+45
-17
src/graphics.c
···
1
+
#include "keraforge/actor.h"
1
2
#include <keraforge.h>
2
3
#include <raylib.h>
3
4
···
43
44
44
45
struct kf_state *state = NULL;
45
46
int is_new_state = kf_state_load(&state);
47
+
(void)is_new_state;
46
48
kf_window.state = state;
47
49
48
50
struct kf_world *world = NULL;
49
-
kf_world_load(&world, true);
51
+
kf_timeit("load world", kf_world_load(&world, true, NULL));
50
52
kf_window.room = world;
51
53
52
-
struct kf_actor *player = kf_actor_new(kf_actor_loadspritesheet("data/res/img/char/template.png"), 10, 10, true);
53
-
player->sizeoffset.y = 6;
54
-
player->tick = kf_player_tick;
55
-
player->draw = kf_player_draw;
56
-
player->controlled = true;
57
-
if (is_new_state) /* place the player in the centre of the room */
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"))
58
69
{
59
-
state->player.pos.x = world->width * KF_TILE_SIZE_PX / 2.0f;
60
-
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());
61
80
}
62
-
player->pos.x = state->player.pos.x;
63
-
player->pos.y = state->player.pos.y;
64
-
kf_window.player = player;
81
+
else
82
+
{
83
+
kf_timeit("load actors", kf_loadactors());
84
+
}
65
85
66
-
kf_window.cam.target.x = player->pos.x + (player->size.x / 2);
67
-
kf_window.cam.target.y = player->pos.y + (player->size.y / 2);
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);
68
90
kf_window.cam.zoom = 2;
69
91
70
92
kf_setmodal(&kf_modal_play);
···
75
97
kf_window.running = 1;
76
98
kf_window.cam.offset.x = GetScreenWidth() / 2.0f;
77
99
kf_window.cam.offset.y = GetScreenHeight() / 2.0f;
100
+
78
101
while (!WindowShouldClose() && kf_window.running)
79
102
{
80
103
if (IsWindowResized())
···
116
139
kf_setmenu(NULL);
117
140
kf_setmodal(NULL);
118
141
119
-
kf_window.state->player.pos = kf_window.player->pos;
142
+
kf_saveactors();
143
+
120
144
kf_state_save(kf_window.state);
121
145
122
146
free(kf_window.player);
···
163
187
static
164
188
void _kf_modal_play_update(void)
165
189
{
166
-
kf_window.player->tick(kf_window.player);
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);
167
193
168
194
Vector2 v = GetScreenToWorld2D(GetMousePosition(), kf_window.cam);
169
195
kf_window.select.x = v.x / KF_TILE_SIZE_PX;
···
193
219
{
194
220
kf_world_draw(kf_window.room, kf_window.cam);
195
221
// kf_world_drawcolliders(world, player, cam);
196
-
kf_window.player->draw(kf_window.player);
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);
197
225
}
198
226
199
227
static
+21
-3
src/player.c
+21
-3
src/player.c
···
1
+
#include "keraforge/bini.h"
1
2
#include <keraforge.h>
2
3
3
4
···
51
52
52
53
void kf_player_tick(struct kf_actor *self)
53
54
{
54
-
if (!kf_window.menu)
55
+
if (!kf_window.menu && self->controlled)
55
56
{
56
57
_player_tick_move(self);
57
58
···
74
75
{
75
76
kf_actor_draw(self);
76
77
77
-
kf_window.cam.target.x = self->pos.x + (self->size.x / 2);
78
-
kf_window.cam.target.y = self->pos.y + (self->size.y / 2);
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);
79
97
}
+13
src/string.c
+13
src/string.c
+77
src/time.c
+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
+
}
+56
-9
src/world.c
+56
-9
src/world.c
···
1
-
#include "keraforge/fs.h"
1
+
#include "keraforge/bini.h"
2
2
#include <keraforge.h>
3
3
#include <raylib.h>
4
4
#include <stdio.h>
···
224
224
#define _KF_MAPFILE_XZ "data/map.bin.xz"
225
225
#define _KF_MAPFILE "data/map.bin"
226
226
227
-
int kf_world_save(struct kf_world *world, bool compress)
227
+
static
228
+
void _kf_world_save_bs(struct kf_world *world, struct bini_stream *bs)
228
229
{
229
-
char *outfile = compress ? _KF_MAPFILE_TMP : _KF_MAPFILE;
230
-
if (!kf_writebin(outfile, (u8 *)world, kf_world_getsize(world)))
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++)
231
235
{
232
-
KF_THROW("failed to write to %s", outfile);
233
-
return 0; /* unreachable */
236
+
bini_wsu(bs, t->subid);
237
+
bini_wsu(bs, t->id);
238
+
bini_wcu(bs, t->data);
239
+
t++;
234
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);
235
251
236
252
if (compress)
237
253
{
···
247
263
return 1;
248
264
}
249
265
250
-
int kf_world_load(struct kf_world **pworld, bool compressed)
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)
251
288
{
252
289
if (compressed) /* decompress before loading */
253
290
{
···
265
302
}
266
303
}
267
304
268
-
char *infile = compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE;
305
+
infile = infile ? infile : (compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE);
269
306
kf_logdbg("loading world: %s", infile);
270
307
271
308
size_t len = 0;
272
-
*pworld = (struct kf_world *)kf_readbin(infile, &len);
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);
273
320
kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len);
274
321
275
322
if (compressed)
+13
-2
todo
+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
-14
tools/newgame.c
+5
-14
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
-
if (!kf_writebin(worldpath, (u8 *)world, len))
87
-
KF_THROW("failed to save %s", worldpath);
88
-
89
-
if (compress)
90
-
{
91
-
char worldxzpath[4096] = {0};
92
-
strcpy(worldxzpath, path);
93
-
strcpy(&worldxzpath[0] + strlen(path), "/map.bin.xz");
94
-
if (!kf_compress(worldpath, worldxzpath))
95
-
KF_THROW("failed to compress %s", worldpath);
96
-
remove(worldpath); /* no longer needed */
97
-
}
87
+
kf_loginfo("saving world to %s (%lu bytes uncompressed)", worldpath, len);
88
+
kf_timeit("save world", kf_world_save(world, compress, worldpath));
98
89
99
90
free(world);
100
91
}