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