A game engine for top-down 2D RPG games.
rpg
game-engine
raylib
c99
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
9struct kf_actorregistry kf_actorregistry = {0};
10struct kf_actor *kf_actors = NULL, *kf_actors_last = NULL;
11u32 kf_actor_count = 0;
12
13
14struct kf_actor *kf_actor_new(char *key)
15{
16 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor));
17 kf_actor_count++;
18
19 if (!kf_actors)
20 {
21 kf_actors = kf_actors_last = actor;
22 }
23 else
24 {
25 actor->prev = kf_actors_last;
26 kf_actors_last->next = actor;
27 kf_actors_last = actor;
28 }
29
30 actor->id = kf_actor_getregistryid(key);
31 actor->size.x = 10;
32 actor->size.y = 10;
33 actor->collide = true;
34
35 actor->speed = 25;
36 actor->speedmod = 1;
37 actor->friction = 1.5f;
38 actor->pointing = kf_north;
39
40 return actor;
41}
42
43int kf_actor_register(char *key)
44{
45 int id = kf_actorregistry.count++;
46 kf_actorregistry.key[id] = key;
47 return id;
48}
49
50struct kf_spritesheet kf_actor_loadspritesheet(char *filename)
51{
52 return kf_loadspritesheet(filename, 20, 20);
53}
54
55int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir)
56{
57 Rectangle r = {
58 actor->pos.x + (actor->size.x / 2) + actor->sizeoffset.x,
59 actor->pos.y + (actor->size.y / 2) + actor->sizeoffset.y,
60 actor->size.x,
61 actor->size.y
62 };
63 r.x += dir.x;
64 r.y += dir.y;
65
66 /* get a range of tiles to check */
67 const u32 sx = fmax(0, floorf(r.x / KF_TILE_SIZE_PX) - 1);
68 const u32 sy = fmax(0, floorf(r.y / KF_TILE_SIZE_PX) - 1);
69 const u32 ex = fmin(world->width, ceilf(r.width / KF_TILE_SIZE_PX) + sx + 2);
70 const u32 ey = fmin(world->height, ceilf(r.height / KF_TILE_SIZE_PX) + sy + 2);
71 const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */
72
73 /* check if any tiles will collide with the actor's rect */
74 const f32 trx = sx * KF_TILE_SIZE_PX;
75 Rectangle tr = {
76 trx,
77 sy * KF_TILE_SIZE_PX,
78 /* TODO:
79 Subtracting 1 as a bandaid fix to high velocities causing the player to be stopped early in collisions.
80 This is a very notorious problem in 3D collision and fwik there are plenty of 2D solutions.
81 I'll research and implement one eventually:tm: */
82 KF_TILE_SIZE_PX - 1,
83 KF_TILE_SIZE_PX - 1,
84 }; /* tile rect */
85 u32 x;
86 struct kf_tile *tile = kf_world_gettile(world, sx, sy);
87
88 for (u32 y = sy ; y <= ey ; y++)
89 {
90 for (x = sx ; x <= ex ; x++)
91 {
92 if (kf_tiles.collide[tile->id] && CheckCollisionRecs(r, tr))
93 return 0;
94 tile++; /* shift tile pointer to the right */
95 tr.x += KF_TILE_SIZE_PX;
96 }
97 tile += down; /* shift tile pointer down */
98 tr.x = trx;
99 tr.y += KF_TILE_SIZE_PX;
100 }
101
102 return 1;
103}
104
105void kf_actor_addforce(struct kf_actor *self, struct kf_vec2(f32) force)
106{
107 self->vel.x += force.x;
108 self->vel.y += force.y;
109}
110
111void kf_actor_move(struct kf_world *world, struct kf_actor *self, f32 dt)
112{
113 struct kf_vec2(f32) delta = kf_mulval_vec2(f32)(self->vel, (self->speed * self->speedmod) * dt);
114
115 if (self->collide)
116 {
117 if (delta.x && !kf_actor_canmovetowards(world, self, kf_vec2_x(f32)(delta)))
118 delta.x = 0;
119 if (delta.y && !kf_actor_canmovetowards(world, self, kf_vec2_y(f32)(delta)))
120 delta.y = 0;
121 }
122
123 if (!self->controlled)
124 {
125 if (delta.y > 0)
126 self->pointing = kf_south;
127 else if (delta.y < 0)
128 self->pointing = kf_north;
129 else if (delta.x < 0)
130 self->pointing = kf_west;
131 else if (delta.x > 0)
132 self->pointing = kf_east;
133 }
134
135 self->pos = kf_add_vec2(f32)(self->pos, delta);
136
137 static const f32 speed_deadzone = 0.1f;
138
139 if (self->vel.x > -speed_deadzone && self->vel.x < speed_deadzone)
140 self->vel.x = 0;
141 else if (self->vel.x)
142 self->vel.x /= self->friction;
143
144 if (self->vel.y > -speed_deadzone && self->vel.y < speed_deadzone)
145 self->vel.y = 0;
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
155void kf_actor_draw(struct kf_actor *actor)
156{
157 int frames = 4;
158 int x = 0, y = 0;
159
160 switch (actor->pointing)
161 {
162 case kf_south: y = 0; break;
163 case kf_east: y = 1; break;
164 case kf_west: y = 2; break;
165 case kf_north: y = 3; break;
166 }
167
168 bool moving = actor->vel.x != 0 || actor->vel.y != 0;
169
170 if (actor->running && moving)
171 {
172 y += 5; /* run sprites */
173 frames = 6;
174 }
175 else if (moving)
176 {
177 x += 7; /* walk sprites */
178 }
179
180 /* todo: jump */
181
182 x += (int)(kf_s * (frames+1)) % frames;
183
184 kf_drawsprite(&kf_actorregistry.sprite[actor->id], actor->pos.x, actor->pos.y, x, y);
185}
186
187int 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
206int 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
239int 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}