]> git.lizzy.rs Git - nothing.git/blob - src/game/level/player.c
(#639) Introduce rigid_bodies_collide_with_itself
[nothing.git] / src / game / level / player.c
1 #include <SDL2/SDL.h>
2 #include "system/stacktrace.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5
6 #include "game/level/player/dying_rect.h"
7 #include "game/level/player/rigid_rect.h"
8 #include "game/level/script.h"
9 #include "goals.h"
10 #include "math/point.h"
11 #include "platforms.h"
12 #include "player.h"
13 #include "system/line_stream.h"
14 #include "system/log.h"
15 #include "system/lt.h"
16 #include "system/nth_alloc.h"
17
18 #define PLAYER_WIDTH 25.0f
19 #define PLAYER_HEIGHT 25.0f
20 #define PLAYER_SPEED 500.0f
21 #define PLAYER_JUMP 32000.0f
22 #define PLAYER_DEATH_DURATION 0.75f
23 #define PLAYER_MAX_JUMP_THRESHOLD 2
24
25 typedef enum Player_state {
26     PLAYER_STATE_ALIVE = 0,
27     PLAYER_STATE_DYING
28 } Player_state;
29
30 /* TODO(#633): Player's alive_body should be a body from RigidBodies */
31 struct Player {
32     Lt *lt;
33     Player_state state;
34
35     Rigid_rect *alive_body;
36     Dying_rect *dying_body;
37     Script *script;
38
39     int jump_threshold;
40     Color color;
41
42     Vec checkpoint;
43
44     int play_die_cue;
45 };
46
47 Player *create_player_from_line_stream(LineStream *line_stream, Broadcast *broadcast)
48 {
49     trace_assert(line_stream);
50
51     Lt *lt = create_lt();
52
53     if (lt == NULL) {
54         return NULL;
55     }
56
57     Player *player = PUSH_LT(lt, nth_alloc(sizeof(Player)), free);
58     if (player == NULL) {
59         RETURN_LT(lt, NULL);
60     }
61     player->lt = lt;
62
63     float x = 0.0f, y = 0.0f;
64     char colorstr[7];
65
66     if (sscanf(
67             line_stream_next(line_stream),
68             "%f%f%6s",
69             &x, &y, colorstr) == EOF) {
70         log_fail("Could not read player\n");
71         RETURN_LT(lt, NULL);
72     }
73
74     player->script = PUSH_LT(
75         lt,
76         create_script_from_line_stream(line_stream, broadcast),
77         destroy_script);
78     if (player->script == NULL) {
79         RETURN_LT(lt, NULL);
80     }
81
82     const Color color = hexstr(colorstr);
83
84     player->alive_body = PUSH_LT(
85         lt,
86         create_rigid_rect(
87             rect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT),
88             color,
89             "player"),
90         destroy_rigid_rect);
91     if (player->alive_body == NULL) {
92         RETURN_LT(lt, NULL);
93     }
94
95     player->dying_body = PUSH_LT(
96         lt,
97         create_dying_rect(
98             color,
99             PLAYER_DEATH_DURATION),
100         destroy_dying_rect);
101     if (player->dying_body == NULL) {
102         RETURN_LT(lt, NULL);
103     }
104
105     player->jump_threshold = 0;
106     player->color = color;
107     player->checkpoint = vec(x, y);
108     player->play_die_cue = 0;
109     player->state = PLAYER_STATE_ALIVE;
110
111     return player;
112 }
113
114 void destroy_player(Player * player)
115 {
116     RETURN_LT0(player->lt);
117 }
118
119 Solid_ref player_as_solid(Player *player)
120 {
121     Solid_ref ref = {
122         .tag = SOLID_PLAYER,
123         .ptr = (void*) player
124     };
125
126     return ref;
127 }
128
129 int player_render(const Player * player,
130                   Camera *camera)
131 {
132     trace_assert(player);
133     trace_assert(camera);
134
135     switch (player->state) {
136     case PLAYER_STATE_ALIVE:
137         return rigid_rect_render(player->alive_body, camera);
138
139     case PLAYER_STATE_DYING:
140         return dying_rect_render(player->dying_body, camera);
141
142     default: {}
143     }
144
145     return 0;
146 }
147
148 void player_update(Player *player,
149                    float delta_time)
150 {
151     trace_assert(player);
152
153     switch (player->state) {
154     case PLAYER_STATE_ALIVE: {
155         rigid_rect_update(player->alive_body, delta_time);
156
157         const Rect hitbox = rigid_rect_hitbox(player->alive_body);
158
159         if (hitbox.y > 1000.0f) {
160             player_die(player);
161         }
162     } break;
163
164     case PLAYER_STATE_DYING: {
165         dying_rect_update(player->dying_body, delta_time);
166
167         if (dying_rect_is_dead(player->dying_body)) {
168             rigid_rect_transform_velocity(
169                 player->alive_body,
170                 make_mat3x3(0.0f, 0.0f, 0.0f,
171                             0.0f, 0.0f, 0.0f,
172                             0.0f, 0.0f, 1.0f));
173             rigid_rect_teleport_to(player->alive_body, player->checkpoint);
174             player->state = PLAYER_STATE_ALIVE;
175         }
176     } break;
177
178     default: {}
179     }
180 }
181
182 void player_collide_with_solid(Player *player, Solid_ref solid)
183 {
184     if (player->state == PLAYER_STATE_ALIVE) {
185         rigid_rect_collide_with_solid(player->alive_body, solid);
186
187         if (rigid_rect_touches_ground(player->alive_body)) {
188             player->jump_threshold = 0;
189         }
190     }
191 }
192
193 void player_move_left(Player *player)
194 {
195     trace_assert(player);
196     rigid_rect_move(player->alive_body, vec(-PLAYER_SPEED, 0.0f));
197 }
198
199 void player_move_right(Player *player)
200 {
201     trace_assert(player);
202
203     rigid_rect_move(player->alive_body, vec(PLAYER_SPEED, 0.0f));
204 }
205
206 void player_stop(Player *player)
207 {
208     trace_assert(player);
209
210     rigid_rect_move(player->alive_body, vec(0.0f, 0.0f));
211 }
212
213 void player_jump(Player *player)
214 {
215     trace_assert(player);
216     if (player->jump_threshold < PLAYER_MAX_JUMP_THRESHOLD) {
217         rigid_rect_transform_velocity(player->alive_body,
218                                       make_mat3x3(1.0f, 0.0f, 0.0f,
219                                                   0.0f, 0.0f, 0.0f,
220                                                   0.0f, 0.0f, 1.0f));
221         rigid_rect_apply_force(player->alive_body,
222                                vec(0.0f, -PLAYER_JUMP));
223         player->jump_threshold++;
224
225         if (script_has_scope_value(player->script, "on-jump")) {
226             script_eval(player->script, "(on-jump)");
227         }
228     }
229 }
230
231 void player_die(Player *player)
232 {
233     trace_assert(player);
234
235     if (player->state == PLAYER_STATE_ALIVE) {
236         const Rect hitbox =
237             rigid_rect_hitbox(player->alive_body);
238
239         player->play_die_cue = 1;
240         dying_rect_start_dying(player->dying_body, vec(hitbox.x, hitbox.y));
241         player->state = PLAYER_STATE_DYING;
242     }
243 }
244
245 void player_focus_camera(Player *player,
246                          Camera *camera)
247 {
248     trace_assert(player);
249     trace_assert(camera);
250
251     const Rect player_hitbox = rigid_rect_hitbox(player->alive_body);
252
253     camera_center_at(
254         camera,
255         vec_sum(
256             vec(player_hitbox.x, player_hitbox.y),
257             vec(0.0f, -player_hitbox.h * 0.5f)));
258 }
259
260 void player_hide_goals(const Player *player,
261                        Goals *goals)
262 {
263     trace_assert(player);
264     trace_assert(goals);
265     goals_hide_from_player(goals, rigid_rect_hitbox(player->alive_body));
266 }
267
268 void player_die_from_lava(Player *player,
269                           const Lava *lava)
270 {
271     if (lava_overlaps_rect(lava, rigid_rect_hitbox(player->alive_body))) {
272         player_die(player);
273     }
274 }
275
276 void player_checkpoint(Player *player, Vec checkpoint)
277 {
278     player->checkpoint = checkpoint;
279 }
280
281 int player_sound(Player *player,
282                  Sound_samples *sound_samples)
283 {
284     if (player->play_die_cue) {
285         player->play_die_cue = 0;
286
287         if (sound_samples_play_sound(sound_samples, 0, 0) < 0) {
288             return -1;
289         }
290     }
291
292     return 0;
293 }
294
295 void player_touches_rect_sides(Player *player,
296                                Rect object,
297                                int sides[RECT_SIDE_N])
298 {
299     if (player->state == PLAYER_STATE_ALIVE) {
300         rigid_rect_touches_rect_sides(player->alive_body, object, sides);
301     }
302 }
303
304 void player_apply_force(Player *player, Vec force)
305 {
306     if (player->state == PLAYER_STATE_ALIVE) {
307         rigid_rect_apply_force(player->alive_body, force);
308     }
309 }
310
311 Rigid_rect *player_rigid_rect(Player *player, const char *id)
312 {
313     trace_assert(player);
314     trace_assert(id);
315
316     if (player->state == PLAYER_STATE_ALIVE) {
317         if (rigid_rect_has_id(player->alive_body, id)) {
318             return player->alive_body;
319         }
320     }
321
322     return NULL;
323 }
324
325 bool player_overlaps_rect(const Player *player,
326                           Rect rect)
327 {
328     trace_assert(player);
329
330     return player->state == PLAYER_STATE_ALIVE
331         && rects_overlap(
332             rect, rigid_rect_hitbox(
333                 player->alive_body));
334 }