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->key = key;
32 actor->size.x = 10;
33 actor->size.y = 10;
34 actor->collide = true;
35
36 actor->speed = 25;
37 actor->speedmod = 1;
38 actor->friction = 1.5f;
39 actor->pointing = kf_north;
40
41 return actor;
42}
43
44int kf_actor_register(char *key)
45{
46 int id = kf_actorregistry.count++;
47 kf_actorregistry.key[id] = key;
48 return id;
49}
50
51struct kf_spritesheet kf_actor_loadspritesheet(char *filename)
52{
53 return kf_loadspritesheet(filename, 20, 20);
54}
55
56int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir)
57{
58 Rectangle r = {
59 actor->pos.x + (actor->size.x / 2) + actor->sizeoffset.x,
60 actor->pos.y + (actor->size.y / 2) + actor->sizeoffset.y,
61 actor->size.x,
62 actor->size.y
63 };
64 r.x += dir.x;
65 r.y += dir.y;
66
67 /* get a range of tiles to check */
68 const u32 sx = fmax(0, floorf(r.x / KF_TILE_SIZE_PX) - 1);
69 const u32 sy = fmax(0, floorf(r.y / KF_TILE_SIZE_PX) - 1);
70 const u32 ex = fmin(world->width, ceilf(r.width / KF_TILE_SIZE_PX) + sx + 2);
71 const u32 ey = fmin(world->height, ceilf(r.height / KF_TILE_SIZE_PX) + sy + 2);
72 const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */
73
74 /* check if any tiles will collide with the actor's rect */
75 const f32 trx = sx * KF_TILE_SIZE_PX;
76 Rectangle tr = {
77 trx,
78 sy * KF_TILE_SIZE_PX,
79 /* TODO:
80 Subtracting 1 as a bandaid fix to high velocities causing the player to be stopped early in collisions.
81 This is a very notorious problem in 3D collision and fwik there are plenty of 2D solutions.
82 I'll research and implement one eventually:tm: */
83 KF_TILE_SIZE_PX - 1,
84 KF_TILE_SIZE_PX - 1,
85 }; /* tile rect */
86 u32 x;
87 struct kf_tile *tile = kf_world_gettile(world, sx, sy);
88
89 for (u32 y = sy ; y <= ey ; y++)
90 {
91 for (x = sx ; x <= ex ; x++)
92 {
93 if (kf_tiles.collide[tile->id] && CheckCollisionRecs(r, tr))
94 return 0;
95 tile++; /* shift tile pointer to the right */
96 tr.x += KF_TILE_SIZE_PX;
97 }
98 tile += down; /* shift tile pointer down */
99 tr.x = trx;
100 tr.y += KF_TILE_SIZE_PX;
101 }
102
103 return 1;
104}
105
106void kf_actor_addforce(struct kf_actor *self, struct kf_vec2(f32) force)
107{
108 self->vel.x += force.x;
109 self->vel.y += force.y;
110}
111
112void kf_actor_move(struct kf_world *world, struct kf_actor *self, f32 dt)
113{
114 struct kf_vec2(f32) delta = kf_mulval_vec2(f32)(self->vel, (self->speed * self->speedmod) * dt);
115
116 if (self->collide)
117 {
118 if (delta.x && !kf_actor_canmovetowards(world, self, kf_vec2_x(f32)(delta)))
119 delta.x = 0;
120 if (delta.y && !kf_actor_canmovetowards(world, self, kf_vec2_y(f32)(delta)))
121 delta.y = 0;
122 }
123
124 if (!self->controlled)
125 {
126 if (delta.y > 0)
127 self->pointing = kf_south;
128 else if (delta.y < 0)
129 self->pointing = kf_north;
130 else if (delta.x < 0)
131 self->pointing = kf_west;
132 else if (delta.x > 0)
133 self->pointing = kf_east;
134 }
135
136 self->pos = kf_add_vec2(f32)(self->pos, delta);
137
138 static const f32 speed_deadzone = 0.1f;
139
140 if (self->vel.x > -speed_deadzone && self->vel.x < speed_deadzone)
141 self->vel.x = 0;
142 else if (self->vel.x)
143 self->vel.x /= self->friction;
144
145 if (self->vel.y > -speed_deadzone && self->vel.y < speed_deadzone)
146 self->vel.y = 0;
147 else if (self->vel.y)
148 self->vel.y /= self->friction;
149
150 // if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone)
151 // self->speedmod = 1;
152 // else if (self->speedmod)
153 // self->speedmod -= self->friction * self->friction * dt;
154}
155
156void kf_actor_draw(struct kf_actor *actor)
157{
158 int frames = 4;
159 int x = 0, y = 0;
160
161 switch (actor->pointing)
162 {
163 case kf_south: y = 0; break;
164 case kf_east: y = 1; break;
165 case kf_west: y = 2; break;
166 case kf_north: y = 3; break;
167 }
168
169 bool moving = actor->vel.x != 0 || actor->vel.y != 0;
170
171 if (actor->running && moving)
172 {
173 y += 5; /* run sprites */
174 frames = 6;
175 }
176 else if (moving)
177 {
178 x += 7; /* walk sprites */
179 }
180
181 /* todo: jump */
182
183 x += (int)(kf_s * (frames+1)) % frames;
184
185 kf_drawsprite(&kf_actorregistry.sprite[actor->id], actor->pos.x, actor->pos.y, x, y);
186}
187
188int kf_actor_getregistryid(char *key)
189{
190 size_t l = strlen(key);
191 for (int i = 0 ; i < kf_actorregistry.count ; i++)
192 {
193 char *c = kf_actorregistry.key[i];
194 size_t cl = strlen(c);
195 if (strncmp(c, key, l>cl?l:cl) == 0)
196 {
197 return i;
198 }
199 }
200 return -1;
201}
202
203#define _KF_ACTORFILE_TMP "data/tmp/actors.bin"
204#define _KF_ACTORFILE_XZ "data/actors.bin.xz"
205#define _KF_ACTORFILE "data/actors.bin"
206
207int kf_saveactors(void)
208{
209 size_t nactors = 0;
210
211 struct bini_stream *bs = bini_new();
212
213 /* kf_actor_count includes unserialized actors, so we have to manually count. */
214 for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next)
215 {
216 if (!actor->key)
217 continue;
218 int id = kf_actor_getregistryid(actor->key);
219 if (id == -1)
220 continue;
221 nactors++;
222 }
223 bini_wlu(bs, nactors);
224
225 for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next)
226 {
227 if (!actor->key)
228 continue;
229 int id = kf_actor_getregistryid(actor->key);
230 if (id == -1)
231 continue;
232 bini_wstr(bs, actor->key);
233 kf_actorregistry.serialize[id](actor, bs);
234 }
235
236 kf_logdbg("serialized %d actors (%lu bytes)", nactors, bs->len);
237
238 // char *outfile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE;
239 char *outfile = _KF_ACTORFILE;
240 if (!kf_writebin(outfile, bs->buffer, bs->len))
241 KF_THROW("failed to write actors to %s", outfile);
242
243 return 1;
244}
245
246int kf_loadactors(void)
247{
248 // char *infile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE;
249 char *infile = _KF_ACTORFILE;
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
264 for (size_t i = 0 ; i < nactors ; i++)
265 {
266 char key[4096] = {0};
267 bini_rstr(&bs, key);
268 int id = kf_actor_getregistryid(key);
269 if (id == -1)
270 continue;
271 struct kf_actor *actor = kf_actor_new(key);
272 kf_actorregistry.deserialize[id](actor, &bs);
273 }
274
275 kf_logdbg("loaded %d actors", nactors);
276
277 free(bs.buffer);
278
279 return 1;
280}