+11
-1
.gitignore
+11
-1
.gitignore
+8
compile_flags.txt
+8
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)
+12
include/keraforge/_header.h
+12
include/keraforge/_header.h
···
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
+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
+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
+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
+13
include/keraforge/error.h
+8
-1
include/keraforge/fs.h
+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
+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
+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
+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
+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
+21
include/keraforge/math.h
···
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
+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
+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
+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
+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
+54
include/keraforge/ui.h
+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
+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
+18
-1
include/keraforge.h
···
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
+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
+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
+9
run.sh
+14
scripts/_config.sh
+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
+12
scripts/build-prod.sh
+27
scripts/build-tools.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
+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
+7
scripts/clean.sh
+7
scripts/clean.sh
+5
scripts/functions/cleanup.sh
+5
scripts/functions/cleanup.sh
+10
scripts/functions/compile.sh
+10
scripts/functions/compile.sh
+9
scripts/functions/init.sh
+9
scripts/functions/init.sh
+5
scripts/functions/link.sh
+5
scripts/functions/link.sh
+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
+10
scripts/run.sh
+10
scripts/run.sh
+208
-12
src/actor.c
+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
+4
src/bini.c
+143
src/editor.c
+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
+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
+129
-1
src/fs.c
···
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
+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
+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
+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
+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
+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
+33
src/math.c
···
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
+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
+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
+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
+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
+
}
+89
src/ui.c
+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
+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
+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
+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
+
}