A game engine for top-down 2D RPG games.
rpg
game-engine
raylib
c99
1#include "keraforge/fs.h"
2#include <keraforge.h>
3#include <stdlib.h>
4#include <raymath.h>
5#include <string.h>
6
7
8struct kf_actorregistry kf_actorregistry = {0};
9struct kf_actor *kf_actors = NULL, *kf_actors_last = NULL;
10u32 kf_actor_count = 0;
11
12
13struct kf_actor *kf_actor_new(char *id, struct kf_spritesheet sprites, f32 width, f32 height, bool collides)
14{
15 struct kf_actor *actor = calloc(1, sizeof(struct kf_actor));
16 kf_actor_count++;
17
18 if (!kf_actors)
19 {
20 kf_actors = kf_actors_last = actor;
21 }
22 else
23 {
24 actor->prev = kf_actors_last;
25 kf_actors_last->next = actor;
26 kf_actors_last = actor;
27 }
28
29 actor->id = id;
30 actor->sprites = sprites;
31 actor->size.x = width;
32 actor->size.y = height;
33 actor->collide = collides;
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
43struct kf_spritesheet kf_actor_loadspritesheet(char *filename)
44{
45 return kf_loadspritesheet(filename, 20, 20);
46}
47
48int kf_actor_canmovetowards(struct kf_world *world, struct kf_actor *actor, struct kf_vec2(f32) dir)
49{
50 Rectangle r = {
51 actor->pos.x + (actor->size.x / 2) + actor->sizeoffset.x,
52 actor->pos.y + (actor->size.y / 2) + actor->sizeoffset.y,
53 actor->size.x,
54 actor->size.y
55 };
56 r.x += dir.x;
57 r.y += dir.y;
58
59 /* get a range of tiles to check */
60 const u32 sx = fmax(0, floorf(r.x / KF_TILE_SIZE_PX) - 1);
61 const u32 sy = fmax(0, floorf(r.y / KF_TILE_SIZE_PX) - 1);
62 const u32 ex = fmin(world->width, ceilf(r.width / KF_TILE_SIZE_PX) + sx + 2);
63 const u32 ey = fmin(world->height, ceilf(r.height / KF_TILE_SIZE_PX) + sy + 2);
64 const size_t down = world->width - ex + sx - 1; /* number of indexes to add to reach the next tile down */
65
66 /* check if any tiles will collide with the actor's rect */
67 const f32 trx = sx * KF_TILE_SIZE_PX;
68 Rectangle tr = {
69 trx,
70 sy * KF_TILE_SIZE_PX,
71 /* TODO:
72 Subtracting 1 as a bandaid fix to high velocities causing the player to be stopped early in collisions.
73 This is a very notorious problem in 3D collision and fwik there are plenty of 2D solutions.
74 I'll research and implement one eventually:tm: */
75 KF_TILE_SIZE_PX - 1,
76 KF_TILE_SIZE_PX - 1,
77 }; /* tile rect */
78 u32 x;
79 struct kf_tile *tile = kf_world_gettile(world, sx, sy);
80
81 for (u32 y = sy ; y <= ey ; y++)
82 {
83 for (x = sx ; x <= ex ; x++)
84 {
85 if (kf_tiles.collide[tile->id] && CheckCollisionRecs(r, tr))
86 return 0;
87 tile++; /* shift tile pointer to the right */
88 tr.x += KF_TILE_SIZE_PX;
89 }
90 tile += down; /* shift tile pointer down */
91 tr.x = trx;
92 tr.y += KF_TILE_SIZE_PX;
93 }
94
95 return 1;
96}
97
98void kf_actor_addforce(struct kf_actor *self, struct kf_vec2(f32) force)
99{
100 self->vel.x += force.x;
101 self->vel.y += force.y;
102}
103
104void kf_actor_move(struct kf_world *world, struct kf_actor *self, f32 dt)
105{
106 struct kf_vec2(f32) delta = kf_mulval_vec2(f32)(self->vel, (self->speed * self->speedmod) * dt);
107
108 if (self->collide)
109 {
110 if (delta.x && !kf_actor_canmovetowards(world, self, kf_vec2_x(f32)(delta)))
111 delta.x = 0;
112 if (delta.y && !kf_actor_canmovetowards(world, self, kf_vec2_y(f32)(delta)))
113 delta.y = 0;
114 }
115
116 if (!self->controlled)
117 {
118 if (delta.y > 0)
119 self->pointing = kf_south;
120 else if (delta.y < 0)
121 self->pointing = kf_north;
122 else if (delta.x < 0)
123 self->pointing = kf_west;
124 else if (delta.x > 0)
125 self->pointing = kf_east;
126 }
127
128 self->pos = kf_add_vec2(f32)(self->pos, delta);
129
130 static const f32 speed_deadzone = 0.1f;
131
132 if (self->vel.x > -speed_deadzone && self->vel.x < speed_deadzone)
133 self->vel.x = 0;
134 else if (self->vel.x)
135 self->vel.x /= self->friction;
136
137 if (self->vel.y > -speed_deadzone && self->vel.y < speed_deadzone)
138 self->vel.y = 0;
139 else if (self->vel.y)
140 self->vel.y /= self->friction;
141
142 // if (self->speedmod > -(1+speed_deadzone) && self->speedmod < 1+speed_deadzone)
143 // self->speedmod = 1;
144 // else if (self->speedmod)
145 // self->speedmod -= self->friction * self->friction * dt;
146}
147
148void kf_actor_draw(struct kf_actor *actor)
149{
150 int frames = 4;
151 int x = 0, y = 0;
152
153 switch (actor->pointing)
154 {
155 case kf_south: y = 0; break;
156 case kf_east: y = 1; break;
157 case kf_west: y = 2; break;
158 case kf_north: y = 3; break;
159 }
160
161 bool moving = actor->vel.x != 0 || actor->vel.y != 0;
162
163 if (actor->running && moving)
164 {
165 y += 5; /* run sprites */
166 frames = 6;
167 }
168 else if (moving)
169 {
170 x += 7; /* walk sprites */
171 }
172
173 /* todo: jump */
174
175 x += (int)(kf_s * (frames+1)) % frames;
176
177 kf_drawsprite(&actor->sprites, actor->pos.x, actor->pos.y, x, y);
178}
179
180int kf_actor_getregistryid(char *id)
181{
182 size_t l = strlen(id);
183 for (int i = 0 ; i < kf_actorregistry.count ; i++)
184 {
185 char *c = kf_actorregistry.id[i];
186 size_t cl = strlen(c);
187 if (strncmp(c, id, l>cl?l:cl) == 0)
188 {
189 return i;
190 }
191 }
192 return -1;
193}
194
195#define _KF_ACTORFILE_TMP "data/tmp/actors.bin"
196#define _KF_ACTORFILE_XZ "data/actors.bin.xz"
197#define _KF_ACTORFILE "data/actors.bin"
198
199int kf_saveactors(void)
200{
201 // char *outfile = compress ? _KF_ACTORFILE_TMP : _KF_ACTORFILE;
202 char *outfile = _KF_ACTORFILE;
203
204 FILE *fp = fopen(outfile, "wb");
205 if (!fp)
206 KF_THROW("failed to open %s", outfile);
207
208 size_t nactors = 0;
209 size_t total_bytes = 0;
210
211 for (struct kf_actor *actor = kf_actors ; actor != NULL ; actor = actor->next)
212 {
213 if (!actor->id)
214 continue;
215 int id = kf_actor_getregistryid(actor->id);
216 if (id == -1)
217 continue;
218 size_t n = strlen(actor->id);
219 if (fwrite(actor->id, 1, n, fp) != n)
220 {
221 KF_THROWSOFTER("failed to write actor ID: %s", actor->id);
222 fclose(fp);
223 return 0;
224 }
225 n = 0;
226 u8 *data = kf_actorregistry.serialize[id](actor, &n);
227 if (!data)
228 {
229 KF_THROWSOFTER("failed to serialize actor of type %s", actor->id);
230 fclose(fp);
231 return 0;
232 }
233 if (fwrite(data, 1, n, fp) != n)
234 {
235 KF_THROWSOFTER("failed to write serialized actor of type %s", actor->id);
236 fclose(fp);
237 return 0;
238 }
239 nactors++;
240 total_bytes += n;
241 }
242
243 kf_logdbg("serialized %d actors (%lu bytes)", nactors, total_bytes);
244 fclose(fp);
245
246 return 1;
247}