]> git.lizzy.rs Git - nothing.git/blob - src/game/level/player.c
(#819) Integrate LevelEditor Regions with Level
[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             player_layer->color_picker.color,
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 = player_layer->color_picker.color;
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     RETURN_LT0(player->lt);
97 }
98
99 int player_render(const Player * player,
100                   Camera *camera)
101 {
102     trace_assert(player);
103     trace_assert(camera);
104
105     char debug_text[256];
106
107     switch (player->state) {
108     case PLAYER_STATE_ALIVE: {
109         snprintf(debug_text, 256, "Jump: %d", player->jump_threshold);
110         Rect hitbox = rigid_bodies_hitbox(player->rigid_bodies, player->alive_body_id);
111
112         if (camera_render_debug_text(camera, debug_text, vec(hitbox.x, hitbox.y - 20.0f)) < 0) {
113             return -1;
114         }
115
116         return rigid_bodies_render(
117             player->rigid_bodies,
118             player->alive_body_id,
119             player->color,
120             camera);
121     }
122
123     case PLAYER_STATE_DYING:
124         return explosion_render(player->dying_body, camera);
125
126     default: {}
127     }
128
129     return 0;
130 }
131
132 void player_update(Player *player,
133                    float delta_time)
134 {
135     trace_assert(player);
136
137     switch (player->state) {
138     case PLAYER_STATE_ALIVE: {
139         rigid_bodies_update(player->rigid_bodies, player->alive_body_id, delta_time);
140
141         const Rect hitbox = rigid_bodies_hitbox(player->rigid_bodies, player->alive_body_id);
142
143
144         if (hitbox.y > 1000.0f) {
145             player_die(player);
146         }
147     } break;
148
149     case PLAYER_STATE_DYING: {
150         explosion_update(player->dying_body, delta_time);
151
152         if (explosion_is_done(player->dying_body)) {
153             rigid_bodies_disable(player->rigid_bodies, player->alive_body_id, false);
154             rigid_bodies_transform_velocity(
155                 player->rigid_bodies,
156                 player->alive_body_id,
157                 make_mat3x3(0.0f, 0.0f, 0.0f,
158                             0.0f, 0.0f, 0.0f,
159                             0.0f, 0.0f, 1.0f));
160             rigid_bodies_teleport_to(
161                 player->rigid_bodies,
162                 player->alive_body_id,
163                 player->checkpoint);
164             player->state = PLAYER_STATE_ALIVE;
165         }
166     } break;
167
168     default: {}
169     }
170 }
171
172 void player_move_left(Player *player)
173 {
174     trace_assert(player);
175     rigid_bodies_move(player->rigid_bodies, player->alive_body_id, vec(-PLAYER_SPEED, 0.0f));
176 }
177
178 void player_move_right(Player *player)
179 {
180     trace_assert(player);
181
182     rigid_bodies_move(player->rigid_bodies, player->alive_body_id, vec(PLAYER_SPEED, 0.0f));
183 }
184
185 void player_stop(Player *player)
186 {
187     trace_assert(player);
188
189     rigid_bodies_move(player->rigid_bodies, player->alive_body_id, vec(0.0f, 0.0f));
190 }
191
192 void player_jump(Player *player, Script *supa_script)
193 {
194     trace_assert(player);
195     trace_assert(supa_script);
196
197     if (rigid_bodies_touches_ground(player->rigid_bodies, player->alive_body_id)) {
198         player->jump_threshold = 0;
199     }
200
201     if (player->jump_threshold < PLAYER_MAX_JUMP_THRESHOLD) {
202         rigid_bodies_transform_velocity(
203             player->rigid_bodies,
204             player->alive_body_id,
205             make_mat3x3(1.0f, 0.0f, 0.0f,
206                         0.0f, 0.0f, 0.0f,
207                         0.0f, 0.0f, 1.0f));
208         rigid_bodies_apply_force(
209             player->rigid_bodies,
210             player->alive_body_id,
211             vec(0.0f, -PLAYER_JUMP));
212         player->jump_threshold++;
213
214         if (script_has_scope_value(supa_script, "on-player-jump")) {
215             Gc *gc = script_gc(supa_script);
216             script_eval(supa_script, list(gc, "q", "on-player-jump"));
217         }
218     }
219 }
220
221 void player_die(Player *player)
222 {
223     trace_assert(player);
224
225     if (player->state == PLAYER_STATE_ALIVE) {
226         const Rect hitbox =
227             rigid_bodies_hitbox(
228                 player->rigid_bodies,
229                 player->alive_body_id);
230
231         player->play_die_cue = 1;
232         explosion_start(player->dying_body, vec(hitbox.x, hitbox.y));
233         player->state = PLAYER_STATE_DYING;
234         rigid_bodies_disable(player->rigid_bodies, player->alive_body_id, true);
235     }
236 }
237
238 void player_focus_camera(Player *player,
239                          Camera *camera)
240 {
241     trace_assert(player);
242     trace_assert(camera);
243
244     const Rect player_hitbox = rigid_bodies_hitbox(
245         player->rigid_bodies,
246         player->alive_body_id);
247
248     camera_center_at(
249         camera,
250         vec_sum(
251             vec(player_hitbox.x, player_hitbox.y),
252             vec(0.0f, -player_hitbox.h * 0.5f)));
253 }
254
255 void player_hide_goals(const Player *player,
256                        Goals *goals)
257 {
258     trace_assert(player);
259     trace_assert(goals);
260     goals_hide_from_player(
261         goals,
262         rigid_bodies_hitbox(
263             player->rigid_bodies,
264             player->alive_body_id));
265 }
266
267 void player_die_from_lava(Player *player,
268                           const Lava *lava)
269 {
270     if (lava_overlaps_rect(
271             lava,
272             rigid_bodies_hitbox(
273                 player->rigid_bodies,
274                 player->alive_body_id))) {
275         player_die(player);
276     }
277 }
278
279 void player_checkpoint(Player *player, Vec checkpoint)
280 {
281     player->checkpoint = checkpoint;
282 }
283
284 int player_sound(Player *player,
285                  Sound_samples *sound_samples)
286 {
287     if (player->play_die_cue) {
288         player->play_die_cue = 0;
289
290         if (sound_samples_play_sound(sound_samples, 0) < 0) {
291             return -1;
292         }
293     }
294
295     return 0;
296 }
297
298 bool player_overlaps_rect(const Player *player,
299                           Rect rect)
300 {
301     trace_assert(player);
302
303     return player->state == PLAYER_STATE_ALIVE
304         && rects_overlap(
305             rect, rigid_bodies_hitbox(
306                 player->rigid_bodies,
307                 player->alive_body_id));
308 }
309
310 Rect player_hitbox(const Player *player)
311 {
312     return rigid_bodies_hitbox(
313         player->rigid_bodies,
314         player->alive_body_id);
315 }