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