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