]> git.lizzy.rs Git - nothing.git/blob - src/game/level/player.c
Merge pull request #688 from tsoding/632
[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/player/dying_rect.h"
7 #include "game/level/player/rigid_rect.h"
8 #include "game/level/script.h"
9 #include "game/level/rigid_bodies.h"
10 #include "goals.h"
11 #include "math/point.h"
12 #include "platforms.h"
13 #include "player.h"
14 #include "system/line_stream.h"
15 #include "system/log.h"
16 #include "system/lt.h"
17 #include "system/nth_alloc.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     Dying_rect *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_line_stream(LineStream *line_stream, RigidBodies *rigid_bodies, Broadcast *broadcast)
50 {
51     trace_assert(line_stream);
52
53     Lt *lt = create_lt();
54
55     if (lt == NULL) {
56         return NULL;
57     }
58
59     Player *player = PUSH_LT(lt, nth_alloc(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     float x = 0.0f, y = 0.0f;
68     char colorstr[7];
69
70     if (sscanf(
71             line_stream_next(line_stream),
72             "%f%f%6s",
73             &x, &y, colorstr) == EOF) {
74         log_fail("Could not read player\n");
75         RETURN_LT(lt, NULL);
76     }
77
78     player->script = PUSH_LT(
79         lt,
80         create_script_from_line_stream(line_stream, broadcast),
81         destroy_script);
82     if (player->script == NULL) {
83         RETURN_LT(lt, NULL);
84     }
85
86     const Color color = hexstr(colorstr);
87
88     player->alive_body_id = rigid_bodies_add(
89         rigid_bodies,
90         rect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT),
91         color);
92
93     player->dying_body = PUSH_LT(
94         lt,
95         create_dying_rect(
96             color,
97             PLAYER_DEATH_DURATION),
98         destroy_dying_rect);
99     if (player->dying_body == NULL) {
100         RETURN_LT(lt, NULL);
101     }
102
103     player->jump_threshold = 0;
104     player->color = color;
105     player->checkpoint = vec(x, y);
106     player->play_die_cue = 0;
107     player->state = PLAYER_STATE_ALIVE;
108
109     return player;
110 }
111
112 void destroy_player(Player * player)
113 {
114     RETURN_LT0(player->lt);
115 }
116
117 int player_render(const Player * player,
118                   Camera *camera)
119 {
120     trace_assert(player);
121     trace_assert(camera);
122
123     char debug_text[256];
124
125     switch (player->state) {
126     case PLAYER_STATE_ALIVE: {
127         snprintf(debug_text, 256, "Jump: %d", player->jump_threshold);
128         Rect hitbox = rigid_bodies_hitbox(player->rigid_bodies, player->alive_body_id);
129
130         if (camera_render_debug_text(camera, debug_text, vec(hitbox.x, hitbox.y - 20.0f)) < 0) {
131             return -1;
132         }
133
134         return rigid_bodies_render(player->rigid_bodies, player->alive_body_id, camera);
135     }
136
137     case PLAYER_STATE_DYING:
138         return dying_rect_render(player->dying_body, camera);
139
140     default: {}
141     }
142
143     return 0;
144 }
145
146 void player_update(Player *player,
147                    float delta_time)
148 {
149     trace_assert(player);
150
151     switch (player->state) {
152     case PLAYER_STATE_ALIVE: {
153         rigid_bodies_update(player->rigid_bodies, player->alive_body_id, delta_time);
154
155         const Rect hitbox = rigid_bodies_hitbox(player->rigid_bodies, player->alive_body_id);
156
157
158         if (hitbox.y > 1000.0f) {
159             player_die(player);
160         }
161     } break;
162
163     case PLAYER_STATE_DYING: {
164         dying_rect_update(player->dying_body, delta_time);
165
166         if (dying_rect_is_dead(player->dying_body)) {
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         dying_rect_start_dying(player->dying_body, vec(hitbox.x, hitbox.y));
244         player->state = PLAYER_STATE_DYING;
245     }
246 }
247
248 void player_focus_camera(Player *player,
249                          Camera *camera)
250 {
251     trace_assert(player);
252     trace_assert(camera);
253
254     const Rect player_hitbox = rigid_bodies_hitbox(
255         player->rigid_bodies,
256         player->alive_body_id);
257
258     camera_center_at(
259         camera,
260         vec_sum(
261             vec(player_hitbox.x, player_hitbox.y),
262             vec(0.0f, -player_hitbox.h * 0.5f)));
263 }
264
265 void player_hide_goals(const Player *player,
266                        Goals *goals)
267 {
268     trace_assert(player);
269     trace_assert(goals);
270     goals_hide_from_player(
271         goals,
272         rigid_bodies_hitbox(
273             player->rigid_bodies,
274             player->alive_body_id));
275 }
276
277 void player_die_from_lava(Player *player,
278                           const Lava *lava)
279 {
280     if (lava_overlaps_rect(
281             lava,
282             rigid_bodies_hitbox(
283                 player->rigid_bodies,
284                 player->alive_body_id))) {
285         player_die(player);
286     }
287 }
288
289 void player_checkpoint(Player *player, Vec checkpoint)
290 {
291     player->checkpoint = checkpoint;
292 }
293
294 int player_sound(Player *player,
295                  Sound_samples *sound_samples)
296 {
297     if (player->play_die_cue) {
298         player->play_die_cue = 0;
299
300         if (sound_samples_play_sound(sound_samples, 0, 0) < 0) {
301             return -1;
302         }
303     }
304
305     return 0;
306 }
307
308 bool player_overlaps_rect(const Player *player,
309                           Rect rect)
310 {
311     trace_assert(player);
312
313     return player->state == PLAYER_STATE_ALIVE
314         && rects_overlap(
315             rect, rigid_bodies_hitbox(
316                 player->rigid_bodies,
317                 player->alive_body_id));
318 }