]> git.lizzy.rs Git - nothing.git/blob - src/game/level/player.c
Remove TODO(#964)
[nothing.git] / src / game / level / player.c
1 #include <stdio.h>
2 #include <stdlib.h>
3
4 #include <SDL.h>
5
6 #include "game/level/explosion.h"
7 #include "game/level/script.h"
8 #include "game/level/rigid_bodies.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 #include "system/stacktrace.h"
18 #include "ebisp/builtins.h"
19
20 #define PLAYER_WIDTH 25.0f
21 #define PLAYER_HEIGHT 25.0f
22 #define PLAYER_SPEED 500.0f
23 #define PLAYER_JUMP 32000.0f
24 #define PLAYER_DEATH_DURATION 0.75f
25 #define PLAYER_MAX_JUMP_THRESHOLD 2
26
27 typedef enum Player_state {
28     PLAYER_STATE_ALIVE = 0,
29     PLAYER_STATE_DYING
30 } Player_state;
31
32 struct Player {
33     Lt *lt;
34     Player_state state;
35
36     RigidBodies *rigid_bodies;
37
38     RigidBodyId alive_body_id;
39     Explosion *dying_body;
40
41     int jump_threshold;
42     Color color;
43
44     Vec checkpoint;
45
46     int play_die_cue;
47 };
48
49 Player *create_player_from_player_layer(const PlayerLayer *player_layer,
50                                         RigidBodies *rigid_bodies,
51                                         Broadcast *broadcast)
52 {
53     trace_assert(player_layer);
54     trace_assert(rigid_bodies);
55     trace_assert(broadcast);
56
57     Lt *lt = create_lt();
58
59     Player *player = PUSH_LT(lt, nth_calloc(1, sizeof(Player)), free);
60     if (player == NULL) {
61         RETURN_LT(lt, NULL);
62     }
63     player->lt = lt;
64
65     player->rigid_bodies = rigid_bodies;
66
67     player->alive_body_id = rigid_bodies_add(
68         rigid_bodies,
69         rect(
70             player_layer->position.x,
71             player_layer->position.y,
72             PLAYER_WIDTH,
73             PLAYER_HEIGHT));
74
75     player->dying_body = PUSH_LT(
76         lt,
77         create_explosion(
78             color_picker_rgba(&player_layer->color_picker),
79             PLAYER_DEATH_DURATION),
80         destroy_explosion);
81     if (player->dying_body == NULL) {
82         RETURN_LT(lt, NULL);
83     }
84
85     player->jump_threshold = 0;
86     player->color = color_picker_rgba(&player_layer->color_picker);
87     player->checkpoint = player_layer->position;
88     player->play_die_cue = 0;
89     player->state = PLAYER_STATE_ALIVE;
90
91     return player;
92 }
93
94 void destroy_player(Player * player)
95 {
96     rigid_bodies_remove(player->rigid_bodies, player->alive_body_id);
97     RETURN_LT0(player->lt);
98 }
99
100 int player_render(const Player * player,
101                   Camera *camera)
102 {
103     trace_assert(player);
104     trace_assert(camera);
105
106     char debug_text[256];
107
108     switch (player->state) {
109     case PLAYER_STATE_ALIVE: {
110         snprintf(debug_text, 256, "Jump: %d", player->jump_threshold);
111         Rect hitbox = rigid_bodies_hitbox(player->rigid_bodies, player->alive_body_id);
112
113         if (camera_render_debug_text(camera, debug_text, vec(hitbox.x, hitbox.y - 20.0f)) < 0) {
114             return -1;
115         }
116
117         return rigid_bodies_render(
118             player->rigid_bodies,
119             player->alive_body_id,
120             player->color,
121             camera);
122     }
123
124     case PLAYER_STATE_DYING:
125         return explosion_render(player->dying_body, camera);
126
127     default: {}
128     }
129
130     return 0;
131 }
132
133 void player_update(Player *player,
134                    float delta_time)
135 {
136     trace_assert(player);
137
138     switch (player->state) {
139     case PLAYER_STATE_ALIVE: {
140         rigid_bodies_update(player->rigid_bodies, player->alive_body_id, delta_time);
141
142         const Rect hitbox = rigid_bodies_hitbox(player->rigid_bodies, player->alive_body_id);
143
144
145         if (hitbox.y > 1000.0f) {
146             player_die(player);
147         }
148     } break;
149
150     case PLAYER_STATE_DYING: {
151         explosion_update(player->dying_body, delta_time);
152
153         if (explosion_is_done(player->dying_body)) {
154             rigid_bodies_disable(player->rigid_bodies, player->alive_body_id, false);
155             rigid_bodies_transform_velocity(
156                 player->rigid_bodies,
157                 player->alive_body_id,
158                 make_mat3x3(0.0f, 0.0f, 0.0f,
159                             0.0f, 0.0f, 0.0f,
160                             0.0f, 0.0f, 1.0f));
161             rigid_bodies_teleport_to(
162                 player->rigid_bodies,
163                 player->alive_body_id,
164                 player->checkpoint);
165             player->state = PLAYER_STATE_ALIVE;
166         }
167     } break;
168
169     default: {}
170     }
171 }
172
173 void player_move_left(Player *player)
174 {
175     trace_assert(player);
176     rigid_bodies_move(player->rigid_bodies, player->alive_body_id, vec(-PLAYER_SPEED, 0.0f));
177 }
178
179 void player_move_right(Player *player)
180 {
181     trace_assert(player);
182
183     rigid_bodies_move(player->rigid_bodies, player->alive_body_id, vec(PLAYER_SPEED, 0.0f));
184 }
185
186 void player_stop(Player *player)
187 {
188     trace_assert(player);
189
190     rigid_bodies_move(player->rigid_bodies, player->alive_body_id, vec(0.0f, 0.0f));
191 }
192
193 void player_jump(Player *player, Script *supa_script)
194 {
195     trace_assert(player);
196     trace_assert(supa_script);
197
198     if (rigid_bodies_touches_ground(player->rigid_bodies, player->alive_body_id)) {
199         player->jump_threshold = 0;
200     }
201
202     if (player->jump_threshold < PLAYER_MAX_JUMP_THRESHOLD) {
203         rigid_bodies_transform_velocity(
204             player->rigid_bodies,
205             player->alive_body_id,
206             make_mat3x3(1.0f, 0.0f, 0.0f,
207                         0.0f, 0.0f, 0.0f,
208                         0.0f, 0.0f, 1.0f));
209         rigid_bodies_apply_force(
210             player->rigid_bodies,
211             player->alive_body_id,
212             vec(0.0f, -PLAYER_JUMP));
213         player->jump_threshold++;
214
215         if (script_has_scope_value(supa_script, "on-player-jump")) {
216             Gc *gc = script_gc(supa_script);
217             script_eval(supa_script, list(gc, "q", "on-player-jump"));
218         }
219     }
220 }
221
222 void player_die(Player *player)
223 {
224     trace_assert(player);
225
226     if (player->state == PLAYER_STATE_ALIVE) {
227         const Rect hitbox =
228             rigid_bodies_hitbox(
229                 player->rigid_bodies,
230                 player->alive_body_id);
231
232         player->play_die_cue = 1;
233         explosion_start(player->dying_body, vec(hitbox.x, hitbox.y));
234         player->state = PLAYER_STATE_DYING;
235         rigid_bodies_disable(player->rigid_bodies, player->alive_body_id, true);
236     }
237 }
238
239 void player_focus_camera(Player *player,
240                          Camera *camera)
241 {
242     trace_assert(player);
243     trace_assert(camera);
244
245     const Rect player_hitbox = rigid_bodies_hitbox(
246         player->rigid_bodies,
247         player->alive_body_id);
248
249     camera_center_at(
250         camera,
251         vec_sum(
252             vec(player_hitbox.x, player_hitbox.y),
253             vec(0.0f, -player_hitbox.h * 0.5f)));
254 }
255
256 void player_hide_goals(const Player *player,
257                        Goals *goals)
258 {
259     trace_assert(player);
260     trace_assert(goals);
261     goals_hide_from_player(
262         goals,
263         rigid_bodies_hitbox(
264             player->rigid_bodies,
265             player->alive_body_id));
266 }
267
268 void player_die_from_lava(Player *player,
269                           const Lava *lava)
270 {
271     if (lava_overlaps_rect(
272             lava,
273             rigid_bodies_hitbox(
274                 player->rigid_bodies,
275                 player->alive_body_id))) {
276         player_die(player);
277     }
278 }
279
280 void player_checkpoint(Player *player, Vec checkpoint)
281 {
282     player->checkpoint = checkpoint;
283 }
284
285 int player_sound(Player *player,
286                  Sound_samples *sound_samples)
287 {
288     if (player->play_die_cue) {
289         player->play_die_cue = 0;
290
291         if (sound_samples_play_sound(sound_samples, 0) < 0) {
292             return -1;
293         }
294     }
295
296     return 0;
297 }
298
299 bool player_overlaps_rect(const Player *player,
300                           Rect rect)
301 {
302     trace_assert(player);
303
304     return player->state == PLAYER_STATE_ALIVE
305         && rects_overlap(
306             rect, rigid_bodies_hitbox(
307                 player->rigid_bodies,
308                 player->alive_body_id));
309 }
310
311 Rect player_hitbox(const Player *player)
312 {
313     return rigid_bodies_hitbox(
314         player->rigid_bodies,
315         player->alive_body_id);
316 }