]> git.lizzy.rs Git - nothing.git/blob - src/game/level/player.c
Merge pull request #595 from tsoding/594
[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 "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     Rigid_rect *alive_body;
35     Dying_rect *dying_body;
36     Script *script;
37
38     int jump_threshold;
39     Color color;
40
41     Vec checkpoint;
42
43     int play_die_cue;
44 };
45
46 Player *create_player_from_line_stream(LineStream *line_stream, Level *level)
47 {
48     trace_assert(line_stream);
49
50     Lt *lt = create_lt();
51
52     if (lt == NULL) {
53         return NULL;
54     }
55
56     Player *player = PUSH_LT(lt, nth_alloc(sizeof(Player)), free);
57     if (player == NULL) {
58         RETURN_LT(lt, NULL);
59     }
60     player->lt = lt;
61
62     float x = 0.0f, y = 0.0f;
63     char colorstr[7];
64
65     if (sscanf(
66             line_stream_next(line_stream),
67             "%f%f%6s",
68             &x, &y, colorstr) == EOF) {
69         log_fail("Could not read player\n");
70         RETURN_LT(lt, NULL);
71     }
72
73     player->script = PUSH_LT(
74         lt,
75         create_script_from_line_stream(line_stream, level),
76         destroy_script);
77     if (player->script == NULL) {
78         RETURN_LT(lt, NULL);
79     }
80
81     const Color color = hexstr(colorstr);
82
83     player->alive_body = PUSH_LT(
84         lt,
85         create_rigid_rect(
86             rect(x, y, PLAYER_WIDTH, PLAYER_HEIGHT),
87             color,
88             "player"),
89         destroy_rigid_rect);
90     if (player->alive_body == NULL) {
91         RETURN_LT(lt, NULL);
92     }
93
94     player->dying_body = PUSH_LT(
95         lt,
96         create_dying_rect(
97             color,
98             PLAYER_DEATH_DURATION),
99         destroy_dying_rect);
100     if (player->dying_body == NULL) {
101         RETURN_LT(lt, NULL);
102     }
103
104     player->jump_threshold = 0;
105     player->color = color;
106     player->checkpoint = vec(x, y);
107     player->play_die_cue = 0;
108     player->state = PLAYER_STATE_ALIVE;
109
110     return player;
111 }
112
113 void destroy_player(Player * player)
114 {
115     RETURN_LT0(player->lt);
116 }
117
118 Solid_ref player_as_solid(Player *player)
119 {
120     Solid_ref ref = {
121         .tag = SOLID_PLAYER,
122         .ptr = (void*) player
123     };
124
125     return ref;
126 }
127
128 int player_render(const Player * player,
129                   Camera *camera)
130 {
131     trace_assert(player);
132     trace_assert(camera);
133
134     switch (player->state) {
135     case PLAYER_STATE_ALIVE:
136         return rigid_rect_render(player->alive_body, camera);
137
138     case PLAYER_STATE_DYING:
139         return dying_rect_render(player->dying_body, camera);
140
141     default: {}
142     }
143
144     return 0;
145 }
146
147 void player_update(Player *player,
148                    float delta_time)
149 {
150     trace_assert(player);
151
152     switch (player->state) {
153     case PLAYER_STATE_ALIVE: {
154         rigid_rect_update(player->alive_body, delta_time);
155
156         const Rect hitbox = rigid_rect_hitbox(player->alive_body);
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_rect_transform_velocity(
168                 player->alive_body,
169                 make_mat3x3(0.0f, 0.0f, 0.0f,
170                             0.0f, 0.0f, 0.0f,
171                             0.0f, 0.0f, 1.0f));
172             rigid_rect_teleport_to(player->alive_body, player->checkpoint);
173             player->state = PLAYER_STATE_ALIVE;
174         }
175     } break;
176
177     default: {}
178     }
179 }
180
181 void player_collide_with_solid(Player *player, Solid_ref solid)
182 {
183     if (player->state == PLAYER_STATE_ALIVE) {
184         rigid_rect_collide_with_solid(player->alive_body, solid);
185
186         if (rigid_rect_touches_ground(player->alive_body)) {
187             player->jump_threshold = 0;
188         }
189     }
190 }
191
192 void player_move_left(Player *player)
193 {
194     trace_assert(player);
195     rigid_rect_move(player->alive_body, vec(-PLAYER_SPEED, 0.0f));
196 }
197
198 void player_move_right(Player *player)
199 {
200     trace_assert(player);
201
202     rigid_rect_move(player->alive_body, vec(PLAYER_SPEED, 0.0f));
203 }
204
205 void player_stop(Player *player)
206 {
207     trace_assert(player);
208
209     rigid_rect_move(player->alive_body, vec(0.0f, 0.0f));
210 }
211
212 void player_jump(Player *player)
213 {
214     trace_assert(player);
215     if (player->jump_threshold < PLAYER_MAX_JUMP_THRESHOLD) {
216         rigid_rect_transform_velocity(player->alive_body,
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_rect_apply_force(player->alive_body,
221                                vec(0.0f, -PLAYER_JUMP));
222         player->jump_threshold++;
223
224         if (script_has_scope_value(player->script, "on-jump")) {
225             script_eval(player->script, "(on-jump)");
226         }
227     }
228 }
229
230 void player_die(Player *player)
231 {
232     trace_assert(player);
233
234     if (player->state == PLAYER_STATE_ALIVE) {
235         const Rect hitbox =
236             rigid_rect_hitbox(player->alive_body);
237
238         player->play_die_cue = 1;
239         dying_rect_start_dying(player->dying_body, vec(hitbox.x, hitbox.y));
240         player->state = PLAYER_STATE_DYING;
241     }
242 }
243
244 void player_focus_camera(Player *player,
245                          Camera *camera)
246 {
247     trace_assert(player);
248     trace_assert(camera);
249
250     const Rect player_hitbox = rigid_rect_hitbox(player->alive_body);
251
252     camera_center_at(
253         camera,
254         vec_sum(
255             vec(player_hitbox.x, player_hitbox.y),
256             vec(0.0f, -player_hitbox.h * 0.5f)));
257 }
258
259 void player_hide_goals(const Player *player,
260                        Goals *goals)
261 {
262     trace_assert(player);
263     trace_assert(goals);
264     goals_hide_from_player(goals, rigid_rect_hitbox(player->alive_body));
265 }
266
267 void player_die_from_lava(Player *player,
268                           const Lava *lava)
269 {
270     if (lava_overlaps_rect(lava, rigid_rect_hitbox(player->alive_body))) {
271         player_die(player);
272     }
273 }
274
275 void player_checkpoint(Player *player, Vec checkpoint)
276 {
277     player->checkpoint = checkpoint;
278 }
279
280 int player_sound(Player *player,
281                  Sound_samples *sound_samples)
282 {
283     if (player->play_die_cue) {
284         player->play_die_cue = 0;
285
286         if (sound_samples_play_sound(sound_samples, 0, 0) < 0) {
287             return -1;
288         }
289     }
290
291     return 0;
292 }
293
294 void player_touches_rect_sides(Player *player,
295                                Rect object,
296                                int sides[RECT_SIDE_N])
297 {
298     if (player->state == PLAYER_STATE_ALIVE) {
299         rigid_rect_touches_rect_sides(player->alive_body, object, sides);
300     }
301 }
302
303 void player_apply_force(Player *player, Vec force)
304 {
305     if (player->state == PLAYER_STATE_ALIVE) {
306         rigid_rect_apply_force(player->alive_body, force);
307     }
308 }
309
310 Rigid_rect *player_rigid_rect(Player *player, const char *id)
311 {
312     trace_assert(player);
313     trace_assert(id);
314
315     if (player->state == PLAYER_STATE_ALIVE) {
316         if (rigid_rect_has_id(player->alive_body, id)) {
317             return player->alive_body;
318         }
319     }
320
321     return NULL;
322 }
323
324 bool player_overlaps_rect(const Player *player,
325                           Rect rect)
326 {
327     trace_assert(player);
328
329     return player->state == PLAYER_STATE_ALIVE
330         && rects_overlap(
331             rect, rigid_rect_hitbox(
332                 player->alive_body));
333 }