+11
.gitignore
+11
.gitignore
+3
compile_flags.txt
+3
compile_flags.txt
+56
etc/style.c
+56
etc/style.c
···
···
1
+
#ifndef __main__
2
+
#define __main__
3
+
4
+
5
+
/*
6
+
Include order doesn't really matter to me, however I do like to keep them reasonably organized:
7
+
1. "Header" includes (i.e, keraforge/_header.h).
8
+
2. Macro/common headers (i.e, keraforge/log.h).
9
+
3. Internal library includes (i.e, keraforge/*.h).
10
+
4. External library includes (i.e, raylib.h, raymath.h, etc).
11
+
5. C standard library includes.
12
+
*/
13
+
#include <keraforge.h>
14
+
#include <stdio.h>
15
+
16
+
17
+
/*
18
+
Please follow good practice when defining macros:
19
+
- #undef after usage.
20
+
- Use do/while blocks for parameterized macros.
21
+
- Wrap constant macros in parenthesis, unless its a string that can be safely concatenated.
22
+
*/
23
+
24
+
25
+
/* Additional keywords (excl. const) should be on their own line. */
26
+
static
27
+
/* All public items should be prefixed with kf_. All static items should use _kf_. */
28
+
int _kf_parse(int argc, /* Attach pointers with the identifier, not the type. */ char *argv[])
29
+
/* Allman braces */
30
+
{
31
+
/* Tabs for indentation, spaces for alignment. */
32
+
33
+
/* Space out your semicolons in for loops. */
34
+
for (int i = 0 ; i < argc ; i++)
35
+
fprintf(stderr, "%s", argv[i]);
36
+
37
+
if (true)
38
+
{
39
+
/* ... */
40
+
}
41
+
else
42
+
{
43
+
/* ... */
44
+
}
45
+
46
+
return 1;
47
+
}
48
+
49
+
int main(void)
50
+
{
51
+
(void)_kf_parse(0, NULL);
52
+
return 0;
53
+
}
54
+
55
+
56
+
#endif
+73
etc/style.txt
+73
etc/style.txt
···
···
1
+
2
+
Style Guidelines
3
+
================
4
+
5
+
As a preface: I break these guidelines occasionally. It is
6
+
important to note that I often find myself up into the
7
+
early morning hours (sometimes even as early as 03:00)
8
+
developing Keraforge. Late-night coding sessions tend to
9
+
lead to mistakes that I accidentally push to prod. Feel
10
+
free to point these mistakes out to me!
11
+
12
+
Zen
13
+
---
14
+
15
+
1. Keep types simple.
16
+
- If your type is complex enough to be unreadable, maybe
17
+
you should change your type.
18
+
- Function pointers may be disgraceful to write, but if
19
+
you write your code in such a way that avoids writing
20
+
the type more than once then you'll be fine.
21
+
- See: `struct kf_actorregistry`. I use some quirky
22
+
types there, but nothing too unreadable. Notice how I
23
+
never rewrite those types again and notice that I am not
24
+
using a typedef either.
25
+
- Additionally, if the user is never going to use that
26
+
type then it almost definitely does not need to be
27
+
typedef'd.
28
+
2. Documentation is important.
29
+
- "Self-documenting code" is not sufficient. If the item
30
+
is public then it should have an explanation.
31
+
- The ONLY exceptions to this rule are blatantly obvious
32
+
struct fields, such as `position`, `velocity`, etc.
33
+
- A few lines of documentation will save you a massive
34
+
headache in the future.
35
+
3. Macro magic is garbage, but unmaintainable code is worse.
36
+
- Using macro magic sparingly can help prevent you from
37
+
being smitten by the DRY deities. Save yourself today!
38
+
- See: math.h, log.h, and bini.h for cases where macro
39
+
magic is acceptable.
40
+
4. Keep names terse but readable.
41
+
- I don't want to see stdlib-style function names, but I
42
+
would much rather that over a name ripped from an
43
+
object-oriented C.
44
+
- If you must use a stdlib-style function name to remain
45
+
terse, then please provide a sentence in its docs to
46
+
explain the name.
47
+
5. Most importantly: Be simple.
48
+
- This often means sitting at your screen pondering the
49
+
best implementation for something rather than simply
50
+
using the first one that comes to mind.
51
+
- It's okay to sacrifice a bit of performance for more
52
+
maintainable code.
53
+
- If you must write complex code, please explain why via
54
+
a brief comment.
55
+
56
+
Basics
57
+
------
58
+
59
+
See <etc/style.c> for a sample of the style with comments
60
+
explaining each main point.
61
+
62
+
For more specifics, please give a skim throughout the
63
+
codebase itself.
64
+
65
+
- Prefer [iuf](8|16|32|64) over (float|double|(u?int(8|16|32|64)_t)).
66
+
- Do not typedef function pointers (see Zen #1), structs, enums, or unions.
67
+
- Use global variables instead of requiring the user to maintain state.
68
+
See: kf_uiconfig, kf_actors, kf_actorregistry, kf_state, etc.
69
+
- Use `struct kf_vec2(type)` instead of `struct kf_vec2f32`.
70
+
- Do not modify libraries included in the source.
71
+
Those are: bini.h.
72
+
- I'm not afraid to postfix _t to typedef'd types. The standard is useless here since we prefix kf_ anyway.
73
+
(I think this standard is useless *to begin with*, but I digress)
-32
include/keraforge/_header.h
-32
include/keraforge/_header.h
···
16
#endif
17
18
19
-
#ifdef KF_SANITY_CHECKS
20
-
# include <stdio.h> /* fprintf, stderr */
21
-
# include <stdlib.h> /* exit */
22
-
# define KF_SANITY_CHECK(EXPR, ...) \
23
-
do \
24
-
{ \
25
-
if (!(EXPR)) \
26
-
{ \
27
-
fprintf(stderr, "\x1b[1;31msanity check failed: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m", __FILE__, __LINE__, __FUNCTION_NAME__); \
28
-
fprintf(stderr, __VA_ARGS__); \
29
-
fprintf(stderr, "\n"); \
30
-
kf_printbacktrace(stderr); \
31
-
exit(1); \
32
-
} \
33
-
} \
34
-
while (0)
35
-
# define KF_UNREACHABLE(...) \
36
-
do \
37
-
{ \
38
-
fprintf(stderr, "\x1b[1;31munreachable: \x1b[0;34m%s:%d\x1b[0m in \x1b[34m%s: \x1b[0m", __FILE__, __LINE__, __FUNCTION_NAME__); \
39
-
fprintf(stderr, __VA_ARGS__); \
40
-
fprintf(stderr, "\n"); \
41
-
kf_printbacktrace(stderr); \
42
-
exit(1); \
43
-
} \
44
-
while (0)
45
-
#else
46
-
# define KF_SANITY_CHECK(EXPR, ...) do { ; } while (0)
47
-
# define KF_UNREACHABLE(...) do { ; } while (0)
48
-
#endif
49
-
50
-
51
typedef int8_t i8;
52
typedef int16_t i16;
53
typedef int32_t i32;
+50
-7
include/keraforge/actor.h
+50
-7
include/keraforge/actor.h
···
7
#include <keraforge/world.h>
8
#include <keraforge/math.h>
9
#include <keraforge/sprites.h>
10
#include <raylib.h>
11
12
13
/* Represents any kinematic body in the world (players, NPCs, etc). */
14
struct kf_actor
15
{
16
-
/* Spritesheet for the actor. */
17
-
struct kf_spritesheet sprites;
18
/* The actor's position. */
19
struct kf_vec2(f32) pos;
20
/* The actor's velocity. */
···
35
bool controlled;
36
/* The direction that the actor is pointing. */
37
enum kf_direction pointing;
38
-
/* Called every frame to update the actor. Don't forget about deltatime (kf_dts, kf_dtms)! */
39
-
void (*tick)(struct kf_world *world, struct kf_actor *self);
40
-
/* Called every frame to render the actor. */
41
-
void (*draw)(struct kf_world *world, struct kf_actor *self);
42
};
43
44
45
/* Create a new actor. */
46
-
struct kf_actor *kf_actor_new(struct kf_spritesheet sprites, f32 width, f32 height, bool collides);
47
48
/* Load a spritesheet, filling in the details for loading character spritesheets. */
49
struct kf_spritesheet kf_actor_loadspritesheet(char *filename);
···
56
void kf_actor_move(struct kf_world *world, struct kf_actor *actor, f32 deltatime);
57
/* Draw the actor. */
58
void kf_actor_draw(struct kf_actor *actor);
59
60
61
#endif
···
7
#include <keraforge/world.h>
8
#include <keraforge/math.h>
9
#include <keraforge/sprites.h>
10
+
#include <keraforge/bini.h>
11
#include <raylib.h>
12
13
14
+
/* The maximum number of registered actors. */
15
+
#define KF_MAX_ACTOR_TYPES 1024
16
+
17
+
18
+
/* Stores serializers and deserializers for actors. Any actor you plan to save MUST be registered here!
19
+
TODO: This will probably be better as a hash table (especially for kf_actor_getregistryid). */
20
+
struct kf_actorregistry
21
+
{
22
+
/* Amount of actor types currently registered. */
23
+
int count;
24
+
/* Keys for each actor. */
25
+
char *key[KF_MAX_ACTOR_TYPES];
26
+
/* Save actor to a binary stream. */
27
+
void (*serialize[KF_MAX_ACTOR_TYPES])(struct kf_actor *self, struct bini_stream *bs);
28
+
/* Load actor from a binary stream. */
29
+
void (*deserialize[KF_MAX_ACTOR_TYPES])(struct kf_actor *self, struct bini_stream *bs);
30
+
/* Called every frame to update actor. Don't forget about deltatime (kf_dts, kf_dtms)! */
31
+
void (*tick[KF_MAX_ACTOR_TYPES])(struct kf_actor *self);
32
+
/* Called every frame to render actor. */
33
+
void (*draw[KF_MAX_ACTOR_TYPES])(struct kf_actor *self);
34
+
/* Spritesheet for actor. */
35
+
struct kf_spritesheet sprite[KF_MAX_ACTOR_TYPES];
36
+
};
37
+
/* Global actor registry instance. */
38
+
extern struct kf_actorregistry kf_actorregistry;
39
+
40
/* Represents any kinematic body in the world (players, NPCs, etc). */
41
struct kf_actor
42
{
43
+
/* Integer identifier for this actor, comes from kf_actorregistry.
44
+
This is unique per-actor-type, not per-actor. */
45
+
int id;
46
/* The actor's position. */
47
struct kf_vec2(f32) pos;
48
/* The actor's velocity. */
···
63
bool controlled;
64
/* The direction that the actor is pointing. */
65
enum kf_direction pointing;
66
+
/* If the actor is running. This will not increase their speed, you are expected to yourself. */
67
+
bool running;
68
+
/* Doubly-linked list of actors. */
69
+
struct kf_actor *prev, *next;
70
};
71
+
/* Linked list of actors. */
72
+
extern struct kf_actor *kf_actors, *kf_actors_last;
73
+
/* Number of actors currently in the world. */
74
+
extern u32 kf_actor_count;
75
76
77
/* Create a new actor. */
78
+
struct kf_actor *kf_actor_new(char *key);
79
+
/* Register a new actor and return its integer ID. */
80
+
int kf_actor_register(char *key);
81
82
/* Load a spritesheet, filling in the details for loading character spritesheets. */
83
struct kf_spritesheet kf_actor_loadspritesheet(char *filename);
···
90
void kf_actor_move(struct kf_world *world, struct kf_actor *actor, f32 deltatime);
91
/* Draw the actor. */
92
void kf_actor_draw(struct kf_actor *actor);
93
+
94
+
/* Get the registry ID of a given actor ID.
95
+
This will perform a lot of string comparisons, use sparingly! */
96
+
int kf_actor_getregistryid(char *key);
97
+
98
+
/* Save actor data. */
99
+
int kf_saveactors(void);
100
+
/* Load actor data. */
101
+
int kf_loadactors(void);
102
103
104
#endif
+500
include/keraforge/bini.h
+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
+2
include/keraforge/error.h
+2
include/keraforge/error.h
+5
include/keraforge/fs.h
+5
include/keraforge/fs.h
···
13
/* Write binary file contents. */
14
int kf_writebin(char *filename, u8 *data, size_t len);
15
16
+
/* Compress a file using LZMA (XZ). */
17
+
int kf_compress(char *infile, char *outfile);
18
+
/* Decompress a file using LZMA (XZ). */
19
+
int kf_decompress(char *infile, char *outfile);
20
+
21
22
#endif
+80
include/keraforge/graphics.h
+80
include/keraforge/graphics.h
···
3
4
5
#include <keraforge/_header.h>
6
+
#include <keraforge/math.h>
7
+
#include <raylib.h>
8
9
10
+
/* Represents either a model or a menu. */
11
+
struct kf_modal
12
+
{
13
+
/* The title of this modal. */
14
+
char *name;
15
+
/* Called when this modal is closed. */
16
+
void (*exit)(void);
17
+
/* Called when this modal is opened/ */
18
+
void (*init)(void);
19
+
/* Called every frame before rendering anything. */
20
+
void (*update)(void);
21
+
/* Called every frame to render the world. */
22
+
void (*render_world)(void);
23
+
/* Called every frame to render the UI. */
24
+
void (*render_ui)(void);
25
+
/* Any extra data this needed.
26
+
You are expected to allocate and free this yourself in init/exit respectively. */
27
+
void *data;
28
+
};
29
+
30
+
/* Stores data about the window and graphics. None of this data is stored between saves/loads. */
31
+
struct kf_window
32
+
{
33
+
/* World camera. */
34
+
Camera2D cam;
35
+
/* Target FPS. Do not mutate this yourself, use kf_settargetfps(). */
36
+
int target_fps;
37
+
/* Indicates whether or not a window is open. */
38
+
bool running;
39
+
40
+
/* Pointer to the current state. */
41
+
struct kf_state *state;
42
+
/* Pointer to the current room. */
43
+
struct kf_world *room;
44
+
/* Pointer to the current player. */
45
+
struct kf_actor *player;
46
+
47
+
/* X,Y coords of the selected tile */
48
+
struct kf_vec2(u32) select;
49
+
50
+
/* Current modal. Do not mutate this yourself, use kf_setmodal(). */
51
+
struct kf_modal *modal;
52
+
/* Current menu. Do not mutate this yourself, use kf_setmenu(). */
53
+
struct kf_modal *menu;
54
+
55
+
/* Font used for kf_drawtext functions. */
56
+
Font font;
57
+
/* Base font size to use. */
58
+
int fontsize;
59
+
};
60
+
61
+
/* Global window instance. */
62
+
extern struct kf_window kf_window;
63
/* Number of frames since the game opened.
64
Not consistent enough for precise timing! Use deltatime (kf_dts/kf_dtms) instead. */
65
extern u64 kf_frame;
···
69
extern f32 kf_dtms;
70
/* Deltatime in seconds. */
71
extern f32 kf_dts;
72
+
73
+
74
+
/* Measure text using the default font. */
75
+
int kf_measuretext(int size, char *text);
76
+
/* Draw text using the default font. */
77
+
void kf_drawtext(Color c, int x, int y, int size, char *text);
78
+
/* Draw text with a shadow using the default font. */
79
+
void kf_drawtextshadowed(Color c, int x, int y, int size, char *text);
80
+
81
+
82
+
/* Open a window via Raylib. This should only be called once! */
83
+
void kf_openwindow(char *title);
84
+
/* Start the render loop. */
85
+
void kf_startwindow(void);
86
+
/* Close the window. */
87
+
void kf_closewindow(void);
88
+
/* Set the target FPS. Prefer this over manually setting kf_window.target_fps. */
89
+
void kf_settargetfps(int fps);
90
+
/* Set the current modal. */
91
+
void kf_setmodal(struct kf_modal *modal);
92
+
/* Set the current menu. */
93
+
void kf_setmenu(struct kf_modal *menu);
94
+
95
+
/* Default modal. */
96
+
extern struct kf_modal kf_modal_play;
97
98
99
#endif
+6
-1
include/keraforge/input.h
+6
-1
include/keraforge/input.h
···
6
#include <raylib.h>
7
8
9
#define MOUSE_BUTTON_UNKNOWN ((MouseButton)-1)
10
#define GAMEPAD_AXIS_UNKNOWN ((GamepadAxis)-1)
11
12
#define KF_INPUTBIND_MAX UINT8_MAX
···
17
/* Struct-of-Arrays for keybindings. */
18
struct _kf_inputbinds
19
{
20
-
kf_inputbind_t count; /* must start at 1. 0 is the `none` keybind. */
21
char *id[KF_INPUTBIND_MAX];
22
KeyboardKey key[KF_INPUTBIND_MAX];
23
KeyboardKey alt[KF_INPUTBIND_MAX];
···
6
#include <raylib.h>
7
8
9
+
/* Represents an unknown mouse button. Provided by Keraforge, not by Raylib. */
10
#define MOUSE_BUTTON_UNKNOWN ((MouseButton)-1)
11
+
/* Represents an unknown gamepad axis. Provided by Keraforge, not by Raylib. */
12
#define GAMEPAD_AXIS_UNKNOWN ((GamepadAxis)-1)
13
14
#define KF_INPUTBIND_MAX UINT8_MAX
···
19
/* Struct-of-Arrays for keybindings. */
20
struct _kf_inputbinds
21
{
22
+
/* Amount of input bindings registered.
23
+
Must start at 1. 0 is the `none` keybind. */
24
+
kf_inputbind_t count;
25
+
/* String IDs of each binding. */
26
char *id[KF_INPUTBIND_MAX];
27
KeyboardKey key[KF_INPUTBIND_MAX];
28
KeyboardKey alt[KF_INPUTBIND_MAX];
+40
include/keraforge/inputbinds.h
+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
+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
+3
include/keraforge/sprites.h
+3
include/keraforge/sprites.h
···
10
Can be used for animations or texture atlases. */
11
struct kf_spritesheet
12
{
13
+
/* The texture to pull sprites from. */
14
Texture2D texture;
15
+
/* Width and height of each sprite in this sheet. */
16
u16 spritewidth, spriteheight;
17
+
/* Number of sprites in this sheet. */
18
u32 nsprites;
19
};
20
+25
include/keraforge/state.h
+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
+5
-2
include/keraforge/ui.h
+5
-2
include/keraforge/ui.h
···
21
/* Padding for the panel. */
22
int xpadding, ypadding;
23
};
24
25
-
/* Get a pointer to the global UI config. */
26
-
struct kf_uiconfig *kf_ui_getconfig(void);
27
28
/* Draw a panel with the given title. Returns the Y position for the next line of text.
29
title: Title of the panel, drawn at the top of the panel. Must be null-terminated. */
···
21
/* Padding for the panel. */
22
int xpadding, ypadding;
23
};
24
+
/* Global UI config. */
25
+
extern struct kf_uiconfig kf_uiconfig;
26
27
+
/* Performs non-compile-time initialization for kf_uiconfig.
28
+
This is called automatically by kf_openwindow. */
29
+
void kf_ui_init(void);
30
31
/* Draw a panel with the given title. Returns the Y position for the next line of text.
32
title: Title of the panel, drawn at the top of the panel. Must be null-terminated. */
+31
-12
include/keraforge/world.h
+31
-12
include/keraforge/world.h
···
8
#include <raylib.h>
9
10
11
#define KF_TILE_SIZE_PX 16
12
13
-
#define KF_TILEID_MAX UINT16_MAX
14
typedef u16 kf_tileid_t;
15
-
/* Used to store connectivity variant. */
16
-
typedef u16 kf_tiledatum_t;
17
18
19
struct kf_actor; /* Forward declaration */
20
21
22
-
#define KF_TILEMASK_NORTH (0x0001)
23
-
#define KF_TILEMASK_WEST (0x0010)
24
-
#define KF_TILEMASK_EAST (0x0100)
25
-
#define KF_TILEMASK_SOUTH (0x1000)
26
27
28
/* Represents a singular tile in the world. */
29
struct kf_tile
30
{
31
-
kf_tileid_t subid;
32
-
kf_tileid_t id;
33
-
kf_tiledatum_t data;
34
-
};
35
36
/* Represents a world (or a subworld, often called "rooms"). */
37
struct kf_world
···
65
struct kf_vec2(u32) sprite[KF_TILEID_MAX];
66
/* Whether or not this tile has collision. */
67
bool collide[KF_TILEID_MAX];
68
};
69
/* Struct-of-arrays for tiles. */
70
extern struct _kf_tiles kf_tiles;
···
77
struct kf_spritesheet *sheet;
78
struct kf_vec2(u32) sprite;
79
bool collide;
80
};
81
82
kf_tileid_t kf_addtile(struct kf_tile_opts opts);
83
#define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ }))
84
85
/* Create a world using the given width and height.
···
90
size_t kf_world_getsize(struct kf_world *world);
91
92
/* Get the sprite offset for a given tile datum. */
93
-
struct kf_vec2(u32) kf_getspritefortilebitmask(kf_tiledatum_t t);
94
95
/* Update a tile and optionally its neighbours. */
96
void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours);
···
103
104
/* Draw the part of the world visible to the given camera. */
105
void kf_world_draw(struct kf_world *world, Camera2D camera);
106
107
#endif
···
8
#include <raylib.h>
9
10
11
+
/* Size of tiles in pixels. */
12
#define KF_TILE_SIZE_PX 16
13
14
+
/* Number of bits to use for storing tile IDs. See: struct kf_tile. */
15
+
#define KF_TILE_IDWIDTH 10
16
+
/* Number of bits to use for storing tile datum. See: struct kf_tile. */
17
+
#define KF_TILE_DATAWIDTH 4
18
+
19
+
/* Number of max tiles. This should be (2^KF_TILE_IDWIDTH-1). */
20
+
#define KF_TILEID_MAX (1023)
21
+
22
+
/* Used to store tile IDs. Stored in 10 bits; max value is 1023! */
23
typedef u16 kf_tileid_t;
24
+
/* Used to store connectivity variant. Stored in 4 bits; max value is 15! */
25
+
typedef u8 kf_tiledatum_t;
26
27
28
struct kf_actor; /* Forward declaration */
29
30
31
+
#define KF_TILEMASK_NORTH (0b0001)
32
+
#define KF_TILEMASK_WEST (0b0010)
33
+
#define KF_TILEMASK_EAST (0b0100)
34
+
#define KF_TILEMASK_SOUTH (0b1000)
35
36
37
/* Represents a singular tile in the world. */
38
struct kf_tile
39
{
40
+
kf_tileid_t subid : KF_TILE_IDWIDTH;
41
+
kf_tileid_t id : KF_TILE_IDWIDTH;
42
+
kf_tiledatum_t data : KF_TILE_DATAWIDTH;
43
+
} __attribute__((packed));
44
45
/* Represents a world (or a subworld, often called "rooms"). */
46
struct kf_world
···
74
struct kf_vec2(u32) sprite[KF_TILEID_MAX];
75
/* Whether or not this tile has collision. */
76
bool collide[KF_TILEID_MAX];
77
+
/* Whether or not this tile has a transparent or transluscent texture. */
78
+
bool transparent[KF_TILEID_MAX];
79
};
80
/* Struct-of-arrays for tiles. */
81
extern struct _kf_tiles kf_tiles;
···
88
struct kf_spritesheet *sheet;
89
struct kf_vec2(u32) sprite;
90
bool collide;
91
+
bool transparent;
92
};
93
94
+
/* Register a tile. */
95
kf_tileid_t kf_addtile(struct kf_tile_opts opts);
96
+
/* Register a tile. */
97
#define KF_ADDTILE(...) (kf_addtile((struct kf_tile_opts){ __VA_ARGS__ }))
98
99
/* Create a world using the given width and height.
···
104
size_t kf_world_getsize(struct kf_world *world);
105
106
/* Get the sprite offset for a given tile datum. */
107
+
struct kf_vec2(u32) kf_getspritefordatum(kf_tiledatum_t t);
108
109
/* Update a tile and optionally its neighbours. */
110
void kf_world_updatetile(struct kf_world *world, u32 x, u32 y, bool update_neighbours);
···
117
118
/* Draw the part of the world visible to the given camera. */
119
void kf_world_draw(struct kf_world *world, Camera2D camera);
120
+
121
+
/* Save a world to map.bin(.xz). Set outfile to NULL to use the default. */
122
+
int kf_world_save(struct kf_world *world, bool compress, char *outfile);
123
+
/* Load a world from a map.bin(.xz). Set infile to NULL to use the default. */
124
+
int kf_world_load(struct kf_world **world, bool compressed, char *infile);
125
126
#endif
+10
include/keraforge.h
+10
include/keraforge.h
···
3
4
5
#include <keraforge/_header.h>
6
#include <keraforge/actor.h>
7
#include <keraforge/error.h>
8
#include <keraforge/fs.h>
9
#include <keraforge/graphics.h>
10
#include <keraforge/input.h>
11
#include <keraforge/math.h>
12
#include <keraforge/sprites.h>
13
#include <keraforge/ui.h>
14
#include <keraforge/world.h>
15
···
3
4
5
#include <keraforge/_header.h>
6
+
7
+
#include <keraforge/log.h>
8
+
9
#include <keraforge/actor.h>
10
+
#include <keraforge/bini.h>
11
+
#include <keraforge/editor.h>
12
#include <keraforge/error.h>
13
#include <keraforge/fs.h>
14
#include <keraforge/graphics.h>
15
#include <keraforge/input.h>
16
+
#include <keraforge/inputbinds.h>
17
#include <keraforge/math.h>
18
+
#include <keraforge/player.h>
19
#include <keraforge/sprites.h>
20
+
#include <keraforge/state.h>
21
+
#include <keraforge/string.h>
22
+
#include <keraforge/time.h>
23
#include <keraforge/ui.h>
24
#include <keraforge/world.h>
25
+23
readme
+23
readme
···
54
Develop
55
-------
56
57
+
Libraries (Debian):
58
+
Raylib:
59
+
libasound2-dev libx11-dev libxrandr-dev libxi-dev libgl1-mesa-dev libglu1-mesa-dev libxcursor-dev libxinerama-dev libwayland-dev libxkbcommon-dev
60
+
Keraforge:
61
+
liblzma-dev
62
+
63
+
Initialise a development environment:
64
+
`sh run.sh init`
65
+
66
Build "system" is contained in a folder of shell scripts,
67
`scripts`. You can run these with the `run.sh` script to
68
run multiple tasks in order. Generally:
69
`sh run.sh build run`
70
is all you'll need to run while developing.
71
+
72
+
Please note that I will be hesitant to accept PRs to this
73
+
codebase. I am very nitpicky with my code style and if your
74
+
PR does not match it (or if it goes out-of-scope, adds
75
+
useless/redundant functions, further pollute global scope,
76
+
etc), then I will likely decline the PR or request a number
77
+
of changes. Ideally, please communicate with me before
78
+
submitting a PR just for it to get closed. You can find my
79
+
contact information on my website. I don't want you wasting
80
+
your time or effort!
81
+
82
+
Additionally, please read through <etc/style.txt> before
83
+
attempting to contribute. It'll make your life much easier
84
+
if you understand my style *before* attempting a PR.
85
86
License
87
-------
+2
-2
scripts/_config.sh
+2
-2
scripts/_config.sh
···
10
export KF_DEBUG_CFLAGS="-g -DKF_SANITY_CHECKS"
11
export KF_DEBUG_LFLAGS="-g -rdynamic"
12
13
+
export CFLAGS="-Wall -Wextra -Werror -std=c99 -Iinclude/ -Iraylib/src/ -c -DKF_GNU $KF_DEBUG_CFLAGS"
14
+
export LFLAGS="raylib/src/libraylib.a -lm -lGL -lpthread -ldl -lrt -lX11 -llzma $KF_DEBUG_LFLAGS"
+27
scripts/build-tools.sh
+27
scripts/build-tools.sh
···
···
1
+
#!/usr/bin/env sh
2
+
set -e
3
+
4
+
. scripts/_config.sh
5
+
6
+
mkdir -p build/tools/
7
+
8
+
echo ": compiling keraforge"
9
+
for f in `find src/ -name '*.c'`
10
+
do
11
+
ff=$(echo "$f" | tr '/' '_')
12
+
gcc $CFLAGS $f -o build/${ff%.c}.o
13
+
done
14
+
15
+
echo ": compiling tools"
16
+
for f in `find tools/ -name '*.c'`
17
+
do
18
+
ff=$(echo "$f" | tr '/' '_')
19
+
o=build/tools/${ff%.c}.o
20
+
echo ": tool: $f->build/tools/${ff%.c}"
21
+
gcc $CFLAGS $f -o $o
22
+
gcc \
23
+
-o build/tools/${ff%.c} \
24
+
`find build -maxdepth 1 -name '*.o' ! -name '*src_main.o'` \
25
+
$o \
26
+
$LFLAGS
27
+
done
+20
scripts/init.sh
+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
+151
-13
src/actor.c
+151
-13
src/actor.c
···
1
#include <keraforge.h>
2
#include <stdlib.h>
3
#include <raymath.h>
4
5
6
-
struct kf_actor *kf_actor_new(struct kf_spritesheet sprites, f32 width, f32 height, bool collides)
7
{
8
struct kf_actor *actor = calloc(1, sizeof(struct kf_actor));
9
10
-
actor->sprites = sprites;
11
-
actor->size.x = width;
12
-
actor->size.y = height;
13
-
actor->collide = collides;
14
15
actor->speed = 25;
16
actor->speedmod = 1;
···
18
actor->pointing = kf_north;
19
20
return actor;
21
}
22
23
struct kf_spritesheet kf_actor_loadspritesheet(char *filename)
···
119
else if (self->vel.y)
120
self->vel.y /= self->friction;
121
122
-
if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone)
123
-
self->speedmod = 1;
124
-
else if (self->speedmod)
125
-
self->speedmod -= self->friction * self->friction * dt;
126
}
127
128
void kf_actor_draw(struct kf_actor *actor)
129
{
130
int x = 0, y = 0;
131
132
switch (actor->pointing)
···
137
case kf_north: y = 3; break;
138
}
139
140
-
if (actor->vel.x != 0 || actor->vel.y != 0)
141
x += 7; /* walk sprites */
142
143
-
x += (int)(kf_s * 5) % 4;
144
145
-
/* todo: run and jump */
146
147
-
kf_drawsprite(&actor->sprites, actor->pos.x, actor->pos.y, x, y);
148
}
···
1
+
#include "keraforge/actor.h"
2
+
#include "keraforge/fs.h"
3
#include <keraforge.h>
4
#include <stdlib.h>
5
#include <raymath.h>
6
+
#include <string.h>
7
8
9
+
struct kf_actorregistry kf_actorregistry = {0};
10
+
struct kf_actor *kf_actors = NULL, *kf_actors_last = NULL;
11
+
u32 kf_actor_count = 0;
12
+
13
+
14
+
struct kf_actor *kf_actor_new(char *key)
15
{
16
struct kf_actor *actor = calloc(1, sizeof(struct kf_actor));
17
+
kf_actor_count++;
18
19
+
if (!kf_actors)
20
+
{
21
+
kf_actors = kf_actors_last = actor;
22
+
}
23
+
else
24
+
{
25
+
actor->prev = kf_actors_last;
26
+
kf_actors_last->next = actor;
27
+
kf_actors_last = actor;
28
+
}
29
+
30
+
actor->id = kf_actor_getregistryid(key);
31
+
actor->size.x = 10;
32
+
actor->size.y = 10;
33
+
actor->collide = true;
34
35
actor->speed = 25;
36
actor->speedmod = 1;
···
38
actor->pointing = kf_north;
39
40
return actor;
41
+
}
42
+
43
+
int kf_actor_register(char *key)
44
+
{
45
+
int id = kf_actorregistry.count++;
46
+
kf_actorregistry.key[id] = key;
47
+
return id;
48
}
49
50
struct kf_spritesheet kf_actor_loadspritesheet(char *filename)
···
146
else if (self->vel.y)
147
self->vel.y /= self->friction;
148
149
+
// if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone)
150
+
// self->speedmod = 1;
151
+
// else if (self->speedmod)
152
+
// self->speedmod -= self->friction * self->friction * dt;
153
}
154
155
void kf_actor_draw(struct kf_actor *actor)
156
{
157
+
int frames = 4;
158
int x = 0, y = 0;
159
160
switch (actor->pointing)
···
165
case kf_north: y = 3; break;
166
}
167
168
+
bool moving = actor->vel.x != 0 || actor->vel.y != 0;
169
+
170
+
if (actor->running && moving)
171
+
{
172
+
y += 5; /* run sprites */
173
+
frames = 6;
174
+
}
175
+
else if (moving)
176
+
{
177
x += 7; /* walk sprites */
178
+
}
179
180
+
/* todo: jump */
181
+
182
+
x += (int)(kf_s * (frames+1)) % frames;
183
+
184
+
kf_drawsprite(&kf_actorregistry.sprite[actor->id], actor->pos.x, actor->pos.y, x, y);
185
+
}
186
187
+
int kf_actor_getregistryid(char *key)
188
+
{
189
+
size_t l = strlen(key);
190
+
for (int i = 0 ; i < kf_actorregistry.count ; i++)
191
+
{
192
+
char *c = kf_actorregistry.key[i];
193
+
size_t cl = strlen(c);
194
+
if (strncmp(c, key, l>cl?l:cl) == 0)
195
+
{
196
+
return i;
197
+
}
198
+
}
199
+
return -1;
200
+
}
201
202
+
#define _KF_ACTORFILE_TMP "data/tmp/actors.bin"
203
+
#define _KF_ACTORFILE_XZ "data/actors.bin.xz"
204
+
#define _KF_ACTORFILE "data/actors.bin"
205
+
206
+
int kf_saveactors(void)
207
+
{
208
+
struct bini_stream *bs = bini_new();
209
+
210
+
/* kf_actor_count includes unserialized actors, so we have to manually count. */
211
+
size_t nactors = 0;
212
+
for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next)
213
+
{
214
+
if (actor->id == -1)
215
+
continue;
216
+
nactors++;
217
+
}
218
+
bini_wlu(bs, nactors);
219
+
kf_logdbg("expecting to save %lu actors", nactors);
220
+
221
+
for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next)
222
+
{
223
+
if (actor->id == -1)
224
+
continue;
225
+
bini_wstr(bs, kf_actorregistry.key[actor->id]);
226
+
kf_actorregistry.serialize[actor->id](actor, bs);
227
+
}
228
+
229
+
kf_logdbg("serialized %d actors (%lu bytes)", nactors, bs->len);
230
+
231
+
// char *outfile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE;
232
+
char *outfile = _KF_ACTORFILE;
233
+
if (!kf_writebin(outfile, bs->buffer, bs->len))
234
+
KF_THROW("failed to write actors to %s", outfile);
235
+
236
+
return 1;
237
+
}
238
+
239
+
int kf_loadactors(void)
240
+
{
241
+
// char *infile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE;
242
+
char *infile = _KF_ACTORFILE;
243
+
244
+
if (!kf_exists(infile))
245
+
{
246
+
kf_logdbg("actors.bin nonexistent, creating...");
247
+
kf_saveactors();
248
+
return 1;
249
+
}
250
+
251
+
size_t len = 0;
252
+
struct bini_stream bs = {
253
+
.mode = BINI_STREAM,
254
+
.buffer = kf_readbin(infile, &len),
255
+
};
256
+
if (!bs.buffer)
257
+
KF_THROW("failed to read/open %s", infile);
258
+
bs.cap = len;
259
+
bs.len = len;
260
+
kf_logdbg("loaded actors into binary stream: len=%lu", len);
261
+
262
+
const size_t nactors = bini_rlu(&bs);
263
+
kf_logdbg("expecting to load %lu actors", nactors);
264
+
265
+
for (size_t i = 0 ; i < nactors ; i++)
266
+
{
267
+
kf_logdbg("loading actor #%lu", i);
268
+
char key[4096];
269
+
size_t n = bini_rstr(&bs, key);
270
+
key[n] = '\0';
271
+
char *keycpy = kf_strdup(key);
272
+
kf_logdbg(" key: %s", keycpy);
273
+
int id = kf_actor_getregistryid(keycpy);
274
+
kf_logdbg(" id: %d", id);
275
+
if (id == -1)
276
+
continue;
277
+
struct kf_actor *actor = kf_actor_new(keycpy);
278
+
kf_actorregistry.deserialize[id](actor, &bs);
279
+
}
280
+
281
+
kf_logdbg("loaded %d actors", nactors);
282
+
283
+
free(bs.buffer);
284
+
285
+
return 1;
286
}
+4
src/bini.c
+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
+
};
+127
src/fs.c
+127
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)
···
52
53
return 1;
54
}
55
+
56
+
static
57
+
int _kf_compress(lzma_stream *s, FILE *ifp, FILE *ofp)
58
+
{
59
+
lzma_action a = LZMA_RUN;
60
+
u8 inbuf[BUFSIZ], outbuf[BUFSIZ];
61
+
62
+
s->next_in = NULL;
63
+
s->avail_in = 0;
64
+
s->next_out = outbuf;
65
+
s->avail_out = sizeof(outbuf);
66
+
67
+
for (;;)
68
+
{
69
+
if (s->avail_in == 0 && !feof(ifp))
70
+
{
71
+
s->next_in = inbuf;
72
+
s->avail_in = fread(inbuf, 1, sizeof(inbuf), ifp);
73
+
74
+
if (ferror(ifp))
75
+
{
76
+
KF_THROW("read error: %s", strerror(errno));
77
+
return 0; /* unreachable */
78
+
}
79
+
80
+
if (feof(ifp))
81
+
a = LZMA_FINISH;
82
+
}
83
+
84
+
lzma_ret r = lzma_code(s, a);
85
+
86
+
if (s->avail_out == 0 || r == LZMA_STREAM_END)
87
+
{
88
+
size_t nwrote = sizeof(outbuf) - s->avail_out;
89
+
90
+
if (fwrite(outbuf, 1, nwrote, ofp) != nwrote)
91
+
{
92
+
KF_THROW("write error: %s", strerror(errno));
93
+
return 0; /* unreachable */
94
+
}
95
+
96
+
s->next_out = outbuf;
97
+
s->avail_out = sizeof(outbuf);
98
+
}
99
+
100
+
if (r != LZMA_OK)
101
+
{
102
+
if (r == LZMA_STREAM_END)
103
+
return 1;
104
+
105
+
KF_THROW("lzma compression error: code=%d", r);
106
+
return 0; /* unreachable */
107
+
}
108
+
}
109
+
110
+
KF_UNREACHABLE("broke out of for(;;) loop");
111
+
return 0; /* unreachable */
112
+
}
113
+
114
+
int kf_compress(char *infile, char *outfile)
115
+
{
116
+
FILE *ifp = fopen(infile, "r");
117
+
if (!ifp)
118
+
{
119
+
KF_THROW("failed to open input file: %s", infile);
120
+
return 0; /* unreachable */
121
+
}
122
+
FILE *ofp = fopen(outfile, "w");
123
+
if (!ofp)
124
+
{
125
+
fclose(ifp);
126
+
KF_THROW("failed to open output file: %s", outfile);
127
+
return 0; /* unreachable */
128
+
}
129
+
130
+
lzma_stream s = LZMA_STREAM_INIT;
131
+
lzma_ret r = lzma_easy_encoder(&s, LZMA_PRESET_DEFAULT, LZMA_CHECK_CRC64);
132
+
if (r != LZMA_OK)
133
+
{
134
+
KF_THROW("failed to initialize lzma encoder: code=%d\n", r);
135
+
return 0; /* unreachable */
136
+
}
137
+
138
+
int res = _kf_compress(&s, ifp, ofp);
139
+
140
+
lzma_end(&s);
141
+
fclose(ifp);
142
+
fclose(ofp);
143
+
144
+
return res;
145
+
}
146
+
147
+
int kf_decompress(char *infile, char *outfile)
148
+
{
149
+
FILE *ifp = fopen(infile, "r");
150
+
if (!ifp)
151
+
{
152
+
KF_THROW("failed to open input file: %s", infile);
153
+
return 0; /* unreachable */
154
+
}
155
+
FILE *ofp = fopen(outfile, "w");
156
+
if (!ofp)
157
+
{
158
+
fclose(ifp);
159
+
KF_THROW("failed to open output file: %s", outfile);
160
+
return 0; /* unreachable */
161
+
}
162
+
163
+
lzma_stream s = LZMA_STREAM_INIT;
164
+
lzma_ret r = lzma_stream_decoder(&s, UINT64_MAX, LZMA_CONCATENATED);
165
+
if (r != LZMA_OK)
166
+
{
167
+
KF_THROW("failed to initialize lzma decoder: code=%d\n", r);
168
+
return 0; /* unreachable */
169
+
}
170
+
171
+
int res = _kf_compress(&s, ifp, ofp);
172
+
173
+
lzma_end(&s);
174
+
fclose(ifp);
175
+
fclose(ofp);
176
+
177
+
return res;
178
+
}
+261
src/graphics.c
+261
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
+
};
+3
-4
src/input.c
+3
-4
src/input.c
···
1
#include <keraforge.h>
2
#include <raylib.h>
3
-
#include <stdio.h>
4
#include <string.h>
5
6
···
16
kf_inputbind_t i = kf_inputbinds.count;
17
if (i >= KF_INPUTBIND_MAX)
18
{
19
-
fprintf(stderr, "error: max keybind count is 255.\n");
20
-
return -1;
21
}
22
kf_inputbinds.count++;
23
24
-
printf("add keybind: %d: %s (k=%d a=%d m=%d g=%d x=%d)\n", i, id, key, alt, mouse, gamepad, axis);
25
kf_inputbinds.id[i] = id;
26
kf_inputbinds.key[i] = key;
27
kf_inputbinds.alt[i] = alt;
···
1
#include <keraforge.h>
2
#include <raylib.h>
3
#include <string.h>
4
5
···
15
kf_inputbind_t i = kf_inputbinds.count;
16
if (i >= KF_INPUTBIND_MAX)
17
{
18
+
KF_THROW("max keybind count is 255");
19
+
return -1; /* unreachable */
20
}
21
kf_inputbinds.count++;
22
23
+
// kf_logdbg("add keybind: %d: %s (k=%d a=%d m=%d g=%d x=%d)", i, id, key, alt, mouse, gamepad, axis);
24
kf_inputbinds.id[i] = id;
25
kf_inputbinds.key[i] = key;
26
kf_inputbinds.alt[i] = alt;
+55
src/inputbinds.c
+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
+
+38
-336
src/main.c
+38
-336
src/main.c
···
1
-
#include "keraforge/input.h"
2
-
#include "keraforge/sprites.h"
3
-
#include "keraforge/world.h"
4
#include <keraforge.h>
5
#include <raylib.h>
6
#include <raymath.h>
7
-
#include <stdio.h>
8
-
#include <stdlib.h>
9
-
10
-
11
-
static Camera2D cam;
12
-
static struct kf_vec2(u32) select = { 0, 0 };
13
-
static int selected_tile = 0;
14
-
static enum {
15
-
menu_none,
16
-
menu_palette,
17
-
} menu;
18
-
static int target_fps = 60;
19
-
static enum {
20
-
modal_play,
21
-
modal_edit,
22
-
} modal;
23
-
static char *modals[] = { "play", "edit" };
24
-
static bool dirty = false;
25
-
26
-
static kf_inputbind_t
27
-
inputbind_move_up,
28
-
inputbind_move_down,
29
-
inputbind_move_left,
30
-
inputbind_move_right,
31
-
inputbind_ui_up,
32
-
inputbind_ui_down,
33
-
inputbind_ui_left,
34
-
inputbind_ui_right,
35
-
inputbind_select,
36
-
inputbind_cancel,
37
-
inputbind_pause,
38
-
inputbind_palette,
39
-
inputbind_zoom_reset,
40
-
inputbind_zoom_in,
41
-
inputbind_zoom_out,
42
-
inputbind_toggle_fps_limit,
43
-
inputbind_toggle_editor
44
-
;
45
46
47
static
48
-
void loadbinds()
49
-
{
50
-
inputbind_move_up = kf_addinput("move_up", KEY_W, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y);
51
-
inputbind_move_down = kf_addinput("move_down", KEY_S, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_Y);
52
-
inputbind_move_left = kf_addinput("move_left", KEY_A, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X);
53
-
inputbind_move_right = kf_addinput("move_right", KEY_D, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_LEFT_X);
54
-
55
-
inputbind_ui_up = kf_addinput("ui_up", KEY_W, KEY_UP, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_UP, GAMEPAD_AXIS_UNKNOWN);
56
-
inputbind_ui_down = kf_addinput("ui_down", KEY_S, KEY_DOWN, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN);
57
-
inputbind_ui_left = kf_addinput("ui_left", KEY_A, KEY_LEFT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_LEFT, GAMEPAD_AXIS_UNKNOWN);
58
-
inputbind_ui_right = kf_addinput("ui_right", KEY_D, KEY_RIGHT, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_LEFT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN);
59
-
60
-
inputbind_select = kf_addinput("select", KEY_E, KEY_ENTER, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_DOWN, GAMEPAD_AXIS_UNKNOWN);
61
-
inputbind_cancel = kf_addinput("cancel", KEY_Q, KEY_ESCAPE, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_RIGHT, GAMEPAD_AXIS_UNKNOWN);
62
-
63
-
inputbind_pause = kf_addinput("pause", KEY_ESCAPE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_MIDDLE_RIGHT, GAMEPAD_AXIS_UNKNOWN);
64
-
inputbind_palette = kf_addinput("palette", KEY_TAB, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_RIGHT_FACE_UP, GAMEPAD_AXIS_UNKNOWN);
65
-
66
-
inputbind_zoom_reset = kf_addinput("zoom_reset", KEY_ZERO, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
67
-
inputbind_zoom_in = kf_addinput("zoom_in", KEY_EQUAL, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
68
-
inputbind_zoom_out = kf_addinput("zoom_out", KEY_MINUS, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
69
-
70
-
inputbind_toggle_fps_limit = kf_addinput("toggle_fps_limit", KEY_NINE, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
71
-
inputbind_toggle_editor = kf_addinput("toggle_editor", KEY_EIGHT, KEY_NULL, MOUSE_BUTTON_UNKNOWN, GAMEPAD_BUTTON_UNKNOWN, GAMEPAD_AXIS_UNKNOWN);
72
-
}
73
-
74
-
static
75
-
void setmenu(int m)
76
{
77
-
menu = m;
78
-
}
79
-
80
-
static
81
-
void _player_tick_move(struct kf_actor *self)
82
-
{
83
-
struct kf_vec2(f32) v = {0, 0};
84
-
85
-
/* gamepad axis movement */
86
-
f32 gpx = kf_getgamepadaxis(inputbind_move_left);
87
-
f32 gpy = kf_getgamepadaxis(inputbind_move_up);
88
-
if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone)
89
-
{
90
-
v.y = gpy;
91
-
v.x = gpx;
92
-
93
-
f32 angle = Vector2LineAngle(Vector2Zero(), (Vector2){gpx, gpy}) * RAD2DEG;
94
-
angle /= 90;
95
-
switch ((int)roundf(angle))
96
-
{
97
-
case 0: self->pointing = kf_east; break;
98
-
case 1: self->pointing = kf_north; break;
99
-
case -2:
100
-
case 2: self->pointing = kf_west; break;
101
-
case -1: self->pointing = kf_south; break;
102
-
}
103
-
104
-
goto done;
105
-
}
106
-
107
-
/* non-axis movement */
108
-
bool w = kf_checkinputdown(inputbind_move_up);
109
-
bool s = kf_checkinputdown(inputbind_move_down);
110
-
bool a = kf_checkinputdown(inputbind_move_left);
111
-
bool d = kf_checkinputdown(inputbind_move_right);
112
-
113
-
if (a && d) { v.x = 0; }
114
-
else if (a) { v.x = -1; self->pointing = kf_west; }
115
-
else if (d) { v.x = 1; self->pointing = kf_east; }
116
-
117
-
if (w && s) { v.y = 0; }
118
-
else if (w) { v.y = -1; self->pointing = kf_north; }
119
-
else if (s) { v.y = 1; self->pointing = kf_south; }
120
-
121
-
v = kf_normalize_vec2(f32)(v);
122
-
123
-
done:
124
-
if (v.x || v.y)
125
-
kf_actor_addforce(self, v);
126
-
}
127
-
128
-
static
129
-
void _player_tick(struct kf_world *world, struct kf_actor *self)
130
-
{
131
-
if (menu == menu_none)
132
-
_player_tick_move(self);
133
-
134
-
kf_actor_move(world, self, kf_dts);
135
-
}
136
-
137
-
static
138
-
void _player_draw(struct kf_world *world, struct kf_actor *self)
139
-
{
140
-
(void)world;
141
-
kf_actor_draw(self);
142
-
143
-
cam.target.x = self->pos.x + (self->size.x / 2);
144
-
cam.target.y = self->pos.y + (self->size.y / 2);
145
-
}
146
-
147
-
static
148
-
void draw_palette(int *selected)
149
-
{
150
-
int px = 80, py = 80;
151
-
DrawRectangle(px, py, 400, 400, BLACK);
152
-
DrawText("tiles :3", px + 10, py + 10, 20, WHITE);
153
-
py += 40;
154
-
int x = 0, y = 0;
155
-
int s = KF_TILE_SIZE_PX * 2;
156
-
for (int i = 1 ; i <= kf_tiles.count ; i++)
157
-
{
158
-
Rectangle r = {px + x*s, py + y*s, s, s};
159
-
kf_drawsprite_wh(kf_tiles.sheet[i], r.x, r.y, r.width, r.height, kf_tiles.sprite[i].x + 0, kf_tiles.sprite[i].y + 3);
160
-
161
-
if (*selected == i)
162
-
DrawRectangleLinesEx(r, 1, GOLD);
163
-
164
-
if (CheckCollisionPointRec(GetMousePosition(), r))
165
-
{
166
-
DrawRectangleLinesEx(r, 1, WHITE);
167
-
if (IsMouseButtonPressed(MOUSE_BUTTON_LEFT))
168
-
*selected = i;
169
-
}
170
-
171
-
x += 1;
172
-
if (x >= 8)
173
-
{
174
-
x = 0;
175
-
y++;
176
-
}
177
-
}
178
-
179
-
if (kf_checkinputpress(inputbind_cancel))
180
-
setmenu(menu_none);
181
-
};
182
-
183
-
184
-
int main(int argc, const char *argv[])
185
-
{
186
-
(void)argc;
187
-
(void)argv;
188
-
189
-
// SetTraceLogLevel(LOG_WARNING);
190
-
InitWindow(800, 600, "Keraforge");
191
-
SetTargetFPS(target_fps);
192
-
SetExitKey(KEY_NULL);
193
-
194
-
loadbinds();
195
-
196
-
struct kf_spritesheet terrain = kf_loadspritesheet("data/res/img/tile/terrain.png", 16, 16);
197
KF_ADDTILE(
198
.key = "grass",
199
.mapcol = GREEN,
200
-
.sheet = &terrain,
201
.sprite = {0, 0},
202
);
203
KF_ADDTILE(
204
.key = "sand",
205
.mapcol = YELLOW,
206
-
.sheet = &terrain,
207
.sprite = {4, 0},
208
);
209
KF_ADDTILE(
210
.key = "stone",
211
.mapcol = GRAY,
212
-
.sheet = &terrain,
213
.sprite = {0, 4},
214
);
215
KF_ADDTILE(
216
.key = "debug",
217
.mapcol = BLUE,
218
-
.sheet = &terrain,
219
.sprite = {4, 4},
220
);
221
KF_ADDTILE(
222
.key = "brick",
223
.mapcol = RED,
224
-
.sheet = &terrain,
225
.sprite = {8, 0},
226
.collide = true,
227
);
228
-
printf("loaded %d tiles\n", kf_tiles.count);
229
230
-
struct kf_uiconfig *uiconfig = kf_ui_getconfig();
231
-
uiconfig->select = inputbind_select;
232
-
uiconfig->cancel = inputbind_cancel;
233
-
uiconfig->up = inputbind_ui_up;
234
-
uiconfig->down = inputbind_ui_down;
235
236
-
if (!DirectoryExists("data"))
237
-
MakeDirectory("data");
238
239
-
struct kf_world *world = NULL;
240
-
if (!kf_exists("data/map.bin"))
241
-
{
242
-
printf("-> creating world\n");
243
-
world = kf_world_new(4096, 4096, 2);
244
-
printf("-> saving world\n");
245
-
size_t len = kf_world_getsize(world);
246
-
printf("-> writing of %lu bytes\n", len);
247
-
if (!kf_writebin("data/map.bin", (u8 *)world, len))
248
-
{
249
-
fprintf(stderr, "error creating map: failed to save map.bin\n");
250
-
free(world);
251
-
exit(1);
252
-
}
253
-
}
254
-
else
255
-
{
256
-
printf("-> loading world\n");
257
-
size_t len = 0;
258
-
world = (struct kf_world *)kf_readbin("data/map.bin", &len);
259
-
printf("-> world is %lu bytes\n", len);
260
-
}
261
-
if (!world)
262
-
{
263
-
fprintf(stderr, "error: failed to load world\n");
264
-
exit(1);
265
-
}
266
267
-
struct kf_actor *player = kf_actor_new(kf_actor_loadspritesheet("data/res/img/char/whom.png"), 10, 10, true);
268
-
player->sizeoffset.y = 6;
269
-
player->pos.x = world->width / 4.0f * KF_TILE_SIZE_PX;
270
-
player->pos.y = world->height / 4.0f * KF_TILE_SIZE_PX;
271
-
player->tick = _player_tick;
272
-
player->draw = _player_draw;
273
-
player->controlled = true;
274
275
-
cam = (Camera2D){0};
276
-
cam.offset.x = GetScreenWidth() / 2.0f;
277
-
cam.offset.y = GetScreenHeight() / 2.0f;
278
-
cam.zoom = 2;
279
280
-
int running = 1;
281
-
while (!WindowShouldClose() && running)
282
-
{
283
-
if (IsWindowResized())
284
-
{
285
-
cam.offset.x = GetScreenWidth() / 2.0f;
286
-
cam.offset.y = GetScreenHeight() / 2.0f;
287
-
}
288
-
289
-
player->tick(world, player);
290
-
291
-
Vector2 v = GetScreenToWorld2D(GetMousePosition(), cam);
292
-
select.x = v.x / KF_TILE_SIZE_PX;
293
-
select.y = v.y / KF_TILE_SIZE_PX;
294
-
295
-
if (kf_checkinputpress(inputbind_palette) && modal == modal_edit)
296
-
setmenu(menu_palette);
297
-
else if (kf_checkinputpress(inputbind_cancel) && menu == menu_none)
298
-
running = 0;
299
-
else if (kf_checkinputpress(inputbind_zoom_reset))
300
-
cam.zoom = 2;
301
-
else if (kf_checkinputpress(inputbind_zoom_in) && cam.zoom < 3.50f)
302
-
cam.zoom += 0.25f;
303
-
else if (kf_checkinputpress(inputbind_zoom_out) && cam.zoom > 1.00f)
304
-
cam.zoom -= 0.25f;
305
-
else if (kf_checkinputpress(inputbind_toggle_fps_limit))
306
-
{
307
-
target_fps = target_fps <= 0 ? 60 : 0;
308
-
SetTargetFPS(target_fps);
309
-
}
310
-
else if (kf_checkinputpress(inputbind_toggle_editor))
311
-
{
312
-
if (modal == modal_edit)
313
-
modal = modal_play;
314
-
else
315
-
{
316
-
modal = modal_edit;
317
-
dirty = true;
318
-
}
319
-
}
320
-
321
-
BeginDrawing();
322
-
ClearBackground(BLACK);
323
-
324
-
BeginMode2D(cam);
325
-
kf_world_draw(world, cam);
326
-
// kf_world_drawcolliders(world, player, cam);
327
-
if (modal == modal_edit && menu == menu_none && select.x < world->width && select.y < world->height)
328
-
{
329
-
struct kf_tile *t = kf_world_gettile(world, select.x, select.y);
330
-
if (IsMouseButtonDown(MOUSE_BUTTON_LEFT))
331
-
{
332
-
t->id = (kf_tileid_t)selected_tile;
333
-
kf_world_updatetile(world, select.x, select.y, true);
334
-
}
335
-
else if (IsMouseButtonDown(MOUSE_BUTTON_RIGHT))
336
-
t->subid = (kf_tileid_t)selected_tile;
337
-
else if (IsMouseButtonPressed(MOUSE_BUTTON_MIDDLE))
338
-
selected_tile = t->id;
339
-
340
-
DrawRectangleLines(select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, KF_TILE_SIZE_PX, WHITE);
341
-
struct kf_vec2(u32) s = kf_getspritefortilebitmask(t->data);
342
-
DrawText(TextFormat("%d [0x%04x] (%d,%d) {%d,%d}", t->id, t->data, select.x, select.y, s.x, s.y), select.x * KF_TILE_SIZE_PX, select.y * KF_TILE_SIZE_PX - 10, 10, BLACK);
343
-
}
344
-
player->draw(world, player);
345
-
EndMode2D();
346
-
347
-
switch (menu)
348
-
{
349
-
case menu_none:
350
-
break;
351
-
case menu_palette:
352
-
draw_palette(&selected_tile);
353
-
break;
354
-
}
355
-
356
-
DrawFPS(0, 0);
357
-
DrawText(TextFormat("%f", kf_dts), 0, 20, 20, RED);
358
-
DrawText(TextFormat("%f", kf_s), 0, 40, 20, RED);
359
-
DrawText(TextFormat("%s", modals[modal]), 0, 60, 20, ORANGE);
360
-
361
-
EndDrawing();
362
-
363
-
kf_frame++;
364
-
kf_dts = GetFrameTime();
365
-
kf_dtms = kf_dts * 1000;
366
-
kf_s += kf_dts;
367
-
}
368
-
369
-
if (world)
370
-
{
371
-
if (dirty && !kf_writebin("data/map.bin", (u8 *)world, kf_world_getsize(world)))
372
-
fprintf(stderr, "error: failed to save map.bin\n");
373
-
free(world);
374
-
}
375
-
CloseWindow();
376
377
return 0;
378
}
···
1
#include <keraforge.h>
2
#include <raylib.h>
3
#include <raymath.h>
4
5
6
static
7
+
void loadtiles(struct kf_spritesheet *terrain)
8
{
9
KF_ADDTILE(
10
.key = "grass",
11
.mapcol = GREEN,
12
+
.sheet = terrain,
13
.sprite = {0, 0},
14
);
15
KF_ADDTILE(
16
.key = "sand",
17
.mapcol = YELLOW,
18
+
.sheet = terrain,
19
.sprite = {4, 0},
20
);
21
KF_ADDTILE(
22
.key = "stone",
23
.mapcol = GRAY,
24
+
.sheet = terrain,
25
.sprite = {0, 4},
26
);
27
KF_ADDTILE(
28
.key = "debug",
29
.mapcol = BLUE,
30
+
.sheet = terrain,
31
.sprite = {4, 4},
32
);
33
KF_ADDTILE(
34
.key = "brick",
35
.mapcol = RED,
36
+
.sheet = terrain,
37
.sprite = {8, 0},
38
.collide = true,
39
);
40
+
KF_ADDTILE(
41
+
.key = "ice",
42
+
.mapcol = BLUE,
43
+
.sheet = terrain,
44
+
.sprite = {8, 4},
45
+
.transparent = true,
46
+
);
47
+
KF_ADDTILE(
48
+
.key = "dirt",
49
+
.mapcol = BROWN,
50
+
.sheet = terrain,
51
+
.sprite = {12, 0},
52
+
);
53
+
KF_ADDTILE(
54
+
.key = "torch",
55
+
.mapcol = ORANGE,
56
+
.sheet = terrain,
57
+
.sprite = {12, 4},
58
+
.transparent = true,
59
+
);
60
+
kf_logdbg("loaded %d tiles", kf_tiles.count);
61
+
}
62
63
64
+
int main(int argc, const char *argv[])
65
+
{
66
+
(void)argc;
67
+
(void)argv;
68
69
+
kf_openwindow("Keraforge");
70
71
+
kf_window.font = LoadFont("data/res/font/MyDearestBit.ttf");
72
+
kf_window.fontsize = 16;
73
74
+
struct kf_spritesheet terrain = kf_loadspritesheet("data/res/img/tile/terrain.png", 16, 16);
75
+
loadtiles(&terrain);
76
77
+
kf_startwindow();
78
79
return 0;
80
}
+97
src/player.c
+97
src/player.c
···
···
1
+
#include "keraforge/bini.h"
2
+
#include <keraforge.h>
3
+
4
+
5
+
static
6
+
void _player_tick_move(struct kf_actor *self)
7
+
{
8
+
struct kf_vec2(f32) v = {0, 0};
9
+
10
+
/* gamepad axis movement */
11
+
f32 gpx = kf_getgamepadaxis(kf_inputbind_move_left);
12
+
f32 gpy = kf_getgamepadaxis(kf_inputbind_move_up);
13
+
if (gpx > kf_deadzone || gpx < -kf_deadzone || gpy > kf_deadzone || gpy < -kf_deadzone)
14
+
{
15
+
v.y = gpy;
16
+
v.x = gpx;
17
+
18
+
f32 angle = Vector2LineAngle(Vector2Zero(), (Vector2){gpx, gpy}) * RAD2DEG;
19
+
angle /= 90;
20
+
switch ((int)roundf(angle))
21
+
{
22
+
case 0: self->pointing = kf_east; break;
23
+
case 1: self->pointing = kf_north; break;
24
+
case -2: /* fallthrough */
25
+
case 2: self->pointing = kf_west; break;
26
+
case -1: self->pointing = kf_south; break;
27
+
}
28
+
29
+
goto done;
30
+
}
31
+
32
+
/* non-axis movement */
33
+
bool w = kf_checkinputdown(kf_inputbind_move_up);
34
+
bool s = kf_checkinputdown(kf_inputbind_move_down);
35
+
bool a = kf_checkinputdown(kf_inputbind_move_left);
36
+
bool d = kf_checkinputdown(kf_inputbind_move_right);
37
+
38
+
if (a && d) { v.x = 0; }
39
+
else if (a) { v.x = -1; self->pointing = kf_west; }
40
+
else if (d) { v.x = 1; self->pointing = kf_east; }
41
+
42
+
if (w && s) { v.y = 0; }
43
+
else if (w) { v.y = -1; self->pointing = kf_north; }
44
+
else if (s) { v.y = 1; self->pointing = kf_south; }
45
+
46
+
v = kf_normalize_vec2(f32)(v);
47
+
48
+
done:
49
+
if (v.x || v.y)
50
+
kf_actor_addforce(self, v);
51
+
}
52
+
53
+
void kf_player_tick(struct kf_actor *self)
54
+
{
55
+
if (!kf_window.menu && self->controlled)
56
+
{
57
+
_player_tick_move(self);
58
+
59
+
if (kf_checkinputpress(kf_inputbind_run))
60
+
{
61
+
self->running = true;
62
+
self->speedmod = 1.5;
63
+
}
64
+
else if (kf_checkinputrelease(kf_inputbind_run))
65
+
{
66
+
self->running = false;
67
+
self->speedmod = 1;
68
+
}
69
+
}
70
+
71
+
kf_actor_move(kf_window.room, self, kf_dts);
72
+
}
73
+
74
+
void kf_player_draw(struct kf_actor *self)
75
+
{
76
+
kf_actor_draw(self);
77
+
78
+
if (self->controlled)
79
+
{
80
+
kf_window.cam.target.x = self->pos.x + (self->size.x / 2);
81
+
kf_window.cam.target.y = self->pos.y + (self->size.y / 2);
82
+
}
83
+
}
84
+
85
+
void kf_player_serialize(struct kf_actor *self, struct bini_stream *bs)
86
+
{
87
+
bini_wf(bs, self->pos.x);
88
+
bini_wf(bs, self->pos.y);
89
+
bini_wb(bs, self->controlled);
90
+
}
91
+
92
+
void kf_player_deserialize(struct kf_actor *self, struct bini_stream *bs)
93
+
{
94
+
self->pos.x = bini_rf(bs);
95
+
self->pos.y = bini_rf(bs);
96
+
self->controlled = bini_rb(bs);
97
+
}
+43
src/state.c
+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
+
}
+22
-18
src/ui.c
+22
-18
src/ui.c
···
1
#include <keraforge.h>
2
#include <raylib.h>
3
4
5
-
static
6
-
struct kf_uiconfig _kf_uiconfig = {
7
.select = KF_INPUTBIND_NONE,
8
.cancel = KF_INPUTBIND_NONE,
9
.up = KF_INPUTBIND_NONE,
···
19
};
20
21
22
-
struct kf_uiconfig *kf_ui_getconfig(void)
23
{
24
-
return &_kf_uiconfig;
25
}
26
27
int kf_ui_panel(char *title)
28
{
29
-
int y = GetScreenHeight() - _kf_uiconfig.panelheight;
30
-
DrawRectangle(0, y, GetScreenWidth(), _kf_uiconfig.panelheight, _kf_uiconfig.bg);
31
32
if (title)
33
{
34
-
y += _kf_uiconfig.ypadding;
35
-
DrawText(title, _kf_uiconfig.xpadding, y, _kf_uiconfig.fontsize, _kf_uiconfig.fg);
36
-
y += _kf_uiconfig.fontsize;
37
}
38
39
return y;
···
50
{
51
char *c = choices[i];
52
53
-
DrawText(
54
-
TextFormat(i == *choice ? _kf_uiconfig.selectfmt : _kf_uiconfig.fmt, c),
55
-
_kf_uiconfig.xpadding,
56
y,
57
-
_kf_uiconfig.fontsize,
58
-
_kf_uiconfig.fg
59
);
60
61
-
y += _kf_uiconfig.fontsize;
62
if (y >= GetScreenHeight())
63
break;
64
}
65
66
skip_text:
67
68
-
if (kf_checkinputpress(_kf_uiconfig.select) || kf_checkinputpress(_kf_uiconfig.cancel))
69
return 1;
70
-
else if (kf_checkinputpress(_kf_uiconfig.down) && *choice < nchoices - 1)
71
(*choice)++;
72
-
else if (kf_checkinputpress(_kf_uiconfig.up) && *choice > 0)
73
(*choice)--;
74
75
return 0;
···
1
+
#include "keraforge/graphics.h"
2
#include <keraforge.h>
3
#include <raylib.h>
4
5
6
+
struct kf_uiconfig kf_uiconfig = {
7
.select = KF_INPUTBIND_NONE,
8
.cancel = KF_INPUTBIND_NONE,
9
.up = KF_INPUTBIND_NONE,
···
19
};
20
21
22
+
void kf_ui_init(void)
23
{
24
+
kf_uiconfig.select = kf_inputbind_select;
25
+
kf_uiconfig.cancel = kf_inputbind_cancel;
26
+
kf_uiconfig.up = kf_inputbind_ui_up;
27
+
kf_uiconfig.down = kf_inputbind_ui_down;
28
+
kf_uiconfig.fontsize = kf_window.fontsize;
29
}
30
31
int kf_ui_panel(char *title)
32
{
33
+
int y = GetScreenHeight() - kf_uiconfig.panelheight;
34
+
DrawRectangle(0, y, GetScreenWidth(), kf_uiconfig.panelheight, kf_uiconfig.bg);
35
36
if (title)
37
{
38
+
y += kf_uiconfig.ypadding;
39
+
kf_drawtextshadowed(kf_uiconfig.fg, kf_uiconfig.xpadding, y, kf_uiconfig.fontsize, title);
40
+
y += kf_uiconfig.fontsize;
41
}
42
43
return y;
···
54
{
55
char *c = choices[i];
56
57
+
kf_drawtextshadowed(
58
+
kf_uiconfig.fg,
59
+
kf_uiconfig.xpadding,
60
y,
61
+
kf_uiconfig.fontsize,
62
+
(char *)TextFormat(i == *choice ? kf_uiconfig.selectfmt : kf_uiconfig.fmt, c)
63
);
64
65
+
y += kf_uiconfig.fontsize;
66
if (y >= GetScreenHeight())
67
break;
68
}
69
70
skip_text:
71
72
+
if (kf_checkinputpress(kf_uiconfig.select) || kf_checkinputpress(kf_uiconfig.cancel))
73
return 1;
74
+
else if (kf_checkinputpress(kf_uiconfig.down) && *choice < nchoices - 1)
75
(*choice)++;
76
+
else if (kf_checkinputpress(kf_uiconfig.up) && *choice > 0)
77
(*choice)--;
78
79
return 0;
+147
-26
src/world.c
+147
-26
src/world.c
···
1
#include <keraforge.h>
2
#include <raylib.h>
3
#include <stdio.h>
···
25
kf_tiles.sheet[id] = opts.sheet;
26
kf_tiles.sprite[id] = opts.sprite;
27
kf_tiles.collide[id] = opts.collide;
28
29
return id;
30
}
···
33
{
34
const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height;
35
struct kf_world *world = malloc(len);
36
-
printf("creating world: %lu bytes: %p\n", len, world);
37
world->revision = 0;
38
world->width = width;
39
world->height = height;
···
70
w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id,
71
e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id,
72
s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id;
73
-
t->data = 0x0000;
74
if (t->id == n) t->data |= KF_TILEMASK_NORTH;
75
if (t->id == w) t->data |= KF_TILEMASK_WEST;
76
if (t->id == e) t->data |= KF_TILEMASK_EAST;
···
90
}
91
92
inline
93
-
struct kf_vec2(u32) kf_getspritefortilebitmask(kf_tiledatum_t t)
94
{
95
-
switch (t)
96
-
{
97
-
case 0x0000: return (struct kf_vec2(u32)){0, 3}; /* 0x0000: */
98
-
case 0x0001: return (struct kf_vec2(u32)){0, 2}; /* 0x0001: N */
99
-
case 0x0010: return (struct kf_vec2(u32)){3, 3}; /* 0x0010: W */
100
-
case 0x0011: return (struct kf_vec2(u32)){3, 2}; /* 0x0011: NW */
101
-
case 0x0100: return (struct kf_vec2(u32)){1, 3}; /* 0x0100: E */
102
-
case 0x0101: return (struct kf_vec2(u32)){1, 2}; /* 0x0101: EN */
103
-
case 0x0110: return (struct kf_vec2(u32)){2, 3}; /* 0x0110: EW */
104
-
case 0x0111: return (struct kf_vec2(u32)){2, 2}; /* 0x0111: EWN */
105
-
case 0x1000: return (struct kf_vec2(u32)){0, 0}; /* 0x1000: S */
106
-
case 0x1001: return (struct kf_vec2(u32)){0, 1}; /* 0x1001: SN */
107
-
case 0x1010: return (struct kf_vec2(u32)){3, 0}; /* 0x1010: SW */
108
-
case 0x1011: return (struct kf_vec2(u32)){3, 1}; /* 0x1011: SWN */
109
-
case 0x1100: return (struct kf_vec2(u32)){1, 0}; /* 0x1100: SE */
110
-
case 0x1101: return (struct kf_vec2(u32)){1, 1}; /* 0x1101: SEN */
111
-
case 0x1110: return (struct kf_vec2(u32)){2, 0}; /* 0x1110: SEW */
112
-
case 0x1111: return (struct kf_vec2(u32)){2, 1}; /* 0x1111: NESW */
113
-
}
114
-
KF_UNREACHABLE("invalid bitmask: 0x%x", t);
115
-
return (struct kf_vec2(u32)){-1, -1};
116
}
117
118
struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y)
···
183
{
184
KF_SANITY_CHECK(tile->subid <= kf_tiles.count, "erroneous subtile on map at %u,%u: %u (count=%u)", x, y, tile->subid, kf_tiles.count);
185
KF_SANITY_CHECK(tile->id <= kf_tiles.count, "erroneous tile on map at %u,%u: %u (count=%u)", x, y, tile->id, kf_tiles.count);
186
-
if (tile->data != 0x1111 && tile->subid && tile->id) /* 0x1111: full tile, no subtile rendering needed */
187
{
188
kf_drawsprite_wh(
189
kf_tiles.sheet[tile->subid],
···
195
kf_tiles.sprite[tile->subid].y + 1
196
);
197
}
198
if (tile->id)
199
{
200
-
struct kf_vec2(u32) s = kf_getspritefortilebitmask(tile->data);
201
kf_drawsprite_wh(
202
kf_tiles.sheet[tile->id],
203
x*KF_TILE_SIZE_PX,
···
208
kf_tiles.sprite[tile->id].y + s.y
209
);
210
}
211
tile++; /* shift tile pointer to the right */
212
}
213
tile += down; /* shift tile pointer down */
214
}
215
}
···
1
+
#include "keraforge/bini.h"
2
#include <keraforge.h>
3
#include <raylib.h>
4
#include <stdio.h>
···
26
kf_tiles.sheet[id] = opts.sheet;
27
kf_tiles.sprite[id] = opts.sprite;
28
kf_tiles.collide[id] = opts.collide;
29
+
kf_tiles.transparent[id] = opts.transparent;
30
31
return id;
32
}
···
35
{
36
const size_t len = sizeof(struct kf_world) + sizeof(struct kf_tile)*width*height;
37
struct kf_world *world = malloc(len);
38
+
kf_loginfo("creating world: %lu bytes: %p", len, world);
39
world->revision = 0;
40
world->width = width;
41
world->height = height;
···
72
w = x-1 >= world->width ? 0 : kf_world_gettile(world, x-1, y)->id,
73
e = x+1 >= world->width ? 0 : kf_world_gettile(world, x+1, y)->id,
74
s = y+1 >= world->height ? 0 : kf_world_gettile(world, x, y+1)->id;
75
+
t->data = 0b0000;
76
if (t->id == n) t->data |= KF_TILEMASK_NORTH;
77
if (t->id == w) t->data |= KF_TILEMASK_WEST;
78
if (t->id == e) t->data |= KF_TILEMASK_EAST;
···
92
}
93
94
inline
95
+
struct kf_vec2(u32) kf_getspritefordatum(kf_tiledatum_t t)
96
{
97
+
static const
98
+
struct kf_vec2(u32) v[] = {
99
+
{0, 3}, /* 0b0000: */
100
+
{0, 2}, /* 0b0001: N */
101
+
{3, 3}, /* 0b0010: W */
102
+
{3, 2}, /* 0b0011: NW */
103
+
{1, 3}, /* 0b0100: E */
104
+
{1, 2}, /* 0b0101: EN */
105
+
{2, 3}, /* 0b0110: EW */
106
+
{2, 2}, /* 0b0111: EWN */
107
+
{0, 0}, /* 0b1000: S */
108
+
{0, 1}, /* 0b1001: SN */
109
+
{3, 0}, /* 0b1010: SW */
110
+
{3, 1}, /* 0b1011: SWN */
111
+
{1, 0}, /* 0b1100: SE */
112
+
{1, 1}, /* 0b1101: SEN */
113
+
{2, 0}, /* 0b1110: SEW */
114
+
{2, 1}, /* 0b1111: NESW */
115
+
};
116
+
return v[t];
117
}
118
119
struct kf_tile *kf_world_gettile(struct kf_world *world, u32 x, u32 y)
···
184
{
185
KF_SANITY_CHECK(tile->subid <= kf_tiles.count, "erroneous subtile on map at %u,%u: %u (count=%u)", x, y, tile->subid, kf_tiles.count);
186
KF_SANITY_CHECK(tile->id <= kf_tiles.count, "erroneous tile on map at %u,%u: %u (count=%u)", x, y, tile->id, kf_tiles.count);
187
+
188
+
/* 15: full tile, no subtile rendering needed (unless transparent) */
189
+
if ((tile->data != 15 || kf_tiles.transparent[tile->id]) && tile->subid && tile->id)
190
{
191
kf_drawsprite_wh(
192
kf_tiles.sheet[tile->subid],
···
198
kf_tiles.sprite[tile->subid].y + 1
199
);
200
}
201
+
202
if (tile->id)
203
{
204
+
struct kf_vec2(u32) s = kf_getspritefordatum(tile->data);
205
kf_drawsprite_wh(
206
kf_tiles.sheet[tile->id],
207
x*KF_TILE_SIZE_PX,
···
212
kf_tiles.sprite[tile->id].y + s.y
213
);
214
}
215
+
216
tile++; /* shift tile pointer to the right */
217
}
218
+
219
tile += down; /* shift tile pointer down */
220
}
221
}
222
+
223
+
#define _KF_MAPFILE_TMP "data/tmp/map.bin"
224
+
#define _KF_MAPFILE_XZ "data/map.bin.xz"
225
+
#define _KF_MAPFILE "data/map.bin"
226
+
227
+
static
228
+
void _kf_world_save_bs(struct kf_world *world, struct bini_stream *bs)
229
+
{
230
+
bini_wiu(bs, world->revision);
231
+
bini_wiu(bs, world->width);
232
+
bini_wiu(bs, world->height);
233
+
struct kf_tile *t = &world->map[0];
234
+
for (size_t i = 0 ; i < world->width*world->height ; i++)
235
+
{
236
+
bini_wsu(bs, t->subid);
237
+
bini_wsu(bs, t->id);
238
+
bini_wcu(bs, t->data);
239
+
t++;
240
+
}
241
+
}
242
+
243
+
int kf_world_save(struct kf_world *world, bool compress, char *outfile)
244
+
{
245
+
outfile = outfile ? outfile : (compress ? _KF_MAPFILE_TMP : _KF_MAPFILE);
246
+
struct bini_stream *bs = bini_new();
247
+
_kf_world_save_bs(world, bs);
248
+
if (!kf_writebin(outfile, bs->buffer, bs->len))
249
+
KF_THROW("failed to open %s", outfile);
250
+
bini_close(bs);
251
+
252
+
if (compress)
253
+
{
254
+
if (!kf_compress(_KF_MAPFILE_TMP, _KF_MAPFILE_XZ))
255
+
{
256
+
KF_THROW("failed to compress %s", _KF_MAPFILE_XZ);
257
+
return 0; /* unreachable */
258
+
}
259
+
/* we don't need this anymore, might as well toss it to save file storage. */
260
+
remove(_KF_MAPFILE_TMP);
261
+
}
262
+
263
+
return 1;
264
+
}
265
+
266
+
static
267
+
void _kf_world_load_bs(struct kf_world **pworld, struct bini_stream *bs)
268
+
{
269
+
u32 r = bini_riu(bs);
270
+
u32 w = bini_riu(bs);
271
+
u32 h = bini_riu(bs);
272
+
struct kf_world *world = malloc(sizeof(*world) + sizeof(struct kf_tile)*w*h);
273
+
world->revision = r;
274
+
world->width = w;
275
+
world->height = h;
276
+
struct kf_tile *t = &world->map[0];
277
+
for (size_t i = 0 ; i < world->width*world->height ; i++)
278
+
{
279
+
t->subid = bini_rsu(bs);
280
+
t->id = bini_rsu(bs);
281
+
t->data = bini_rcu(bs);
282
+
t++;
283
+
}
284
+
*pworld = world;
285
+
}
286
+
287
+
int kf_world_load(struct kf_world **pworld, bool compressed, char *infile)
288
+
{
289
+
if (compressed) /* decompress before loading */
290
+
{
291
+
if (!kf_exists(_KF_MAPFILE_XZ))
292
+
{
293
+
KF_THROW("no such file: %s", _KF_MAPFILE_XZ);
294
+
return 0; /* unreachable */
295
+
}
296
+
297
+
kf_logdbg("decompressing %s to %s", _KF_MAPFILE_XZ, _KF_MAPFILE_TMP);
298
+
if (!kf_decompress(_KF_MAPFILE_XZ, _KF_MAPFILE_TMP))
299
+
{
300
+
KF_THROW("failed to decompress %s", _KF_MAPFILE_XZ);
301
+
return 0; /* unreachable */
302
+
}
303
+
}
304
+
305
+
infile = infile ? infile : (compressed ? _KF_MAPFILE_TMP : _KF_MAPFILE);
306
+
kf_logdbg("loading world: %s", infile);
307
+
308
+
size_t len = 0;
309
+
struct bini_stream bs = {
310
+
.mode = BINI_STREAM,
311
+
.buffer = kf_readbin(infile, &len),
312
+
};
313
+
if (!bs.buffer)
314
+
KF_THROW("failed to read/open %s", infile);
315
+
bs.cap = len;
316
+
bs.len = len;
317
+
kf_logdbg("loaded world into binary stream: len=%lu", len);
318
+
_kf_world_load_bs(pworld, &bs);
319
+
free(bs.buffer);
320
+
kf_logdbg("loaded world (%p): r=%d, wh=%dx%d, len=%lu", *pworld, (*pworld)->revision, (*pworld)->width, (*pworld)->height, len);
321
+
322
+
if (compressed)
323
+
{
324
+
/* we don't need this anymore, might as well toss it to save file storage. */
325
+
remove(_KF_MAPFILE_TMP);
326
+
}
327
+
328
+
if (!*pworld)
329
+
{
330
+
KF_THROW("failed to load world");
331
+
return 0; /* unreachable */
332
+
}
333
+
334
+
return 1;
335
+
}
336
+
+16
-3
todo
+16
-3
todo
···
9
10
. Core
11
. World
12
+
x Tiles
13
+
/ Actors
14
+
x Rendering
15
+
x Serialization
16
+
. NPC paths (i.e, walking to/from locations. Stardew Valley style)
17
+
x Compression
18
+
. Compress without saving the world binary as an intermediate step.
19
+
20
+
All I need to do for this is adapt the compression functions to have an
21
+
in-memory (`u8 *`) compression equivalent instead of just `FILE *`
22
+
compression.
23
+
24
+
Another good option would be to implement compression support in Bini,
25
+
i.e, a function that compresses a binary stream's buffer. I would
26
+
also need to mark the stream as read-only until a flush occurs, though.
27
. Dialogue
28
. Quests
29
+
. UI+layout library
30
. Combat
31
. Character Stats
32
. (De)Buffs
+91
tools/newgame.c
+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
+
}