]> git.lizzy.rs Git - nothing.git/blob - src/game/level.c
Merge pull request #682 from tsoding/633
[nothing.git] / src / game / level.c
1 #include <SDL2/SDL.h>
2 #include "system/stacktrace.h"
3
4 #include "broadcast.h"
5 #include "color.h"
6 #include "ebisp/builtins.h"
7 #include "ebisp/interpreter.h"
8 #include "game/camera.h"
9 #include "game/level.h"
10 #include "game/level/background.h"
11 #include "game/level/boxes.h"
12 #include "game/level/goals.h"
13 #include "game/level/labels.h"
14 #include "game/level/lava.h"
15 #include "game/level/physical_world.h"
16 #include "game/level/platforms.h"
17 #include "game/level/player.h"
18 #include "game/level/regions.h"
19 #include "game/level/rigid_bodies.h"
20 #include "system/line_stream.h"
21 #include "system/lt.h"
22 #include "system/lt/lt_adapters.h"
23 #include "system/nth_alloc.h"
24
25 #define LEVEL_LINE_MAX_LENGTH 512
26 #define LEVEL_GRAVITY 1500.0f
27
28 struct Level
29 {
30     Lt *lt;
31
32     Background *background;
33     RigidBodies *rigid_bodies;
34     Player *player;
35     Platforms *platforms;
36     Goals *goals;
37     Lava *lava;
38     Platforms *back_platforms;
39     Boxes *boxes;
40     Labels *labels;
41     Regions *regions;
42     Physical_world *physical_world;
43
44     bool flying_mode;
45     Vec flying_camera_position;
46     float flying_camera_scale;
47 };
48
49 Level *create_level_from_file(const char *file_name, Broadcast *broadcast)
50 {
51     trace_assert(file_name);
52
53     Lt *const lt = create_lt();
54     if (lt == NULL) {
55         return NULL;
56     }
57
58     Level *const level = PUSH_LT(lt, nth_alloc(sizeof(Level)), free);
59     if (level == NULL) {
60         RETURN_LT(lt, NULL);
61     }
62
63     LineStream *level_stream = PUSH_LT(
64         lt,
65         create_line_stream(
66             file_name,
67             "r",
68             LEVEL_LINE_MAX_LENGTH),
69         destroy_line_stream);
70     if (level_stream == NULL) {
71         RETURN_LT(lt, NULL);
72     }
73     level->background = PUSH_LT(
74         lt,
75         create_background_from_line_stream(level_stream),
76         destroy_background);
77     if (level->background == NULL) {
78         RETURN_LT(lt, NULL);
79     }
80
81     level->rigid_bodies = PUSH_LT(lt, create_rigid_bodies(1024), destroy_rigid_bodies);
82     if (level->rigid_bodies == NULL) {
83         RETURN_LT(lt, NULL);
84     }
85
86     level->player = PUSH_LT(
87         lt,
88         create_player_from_line_stream(level_stream, level->rigid_bodies, broadcast),
89         destroy_player);
90     if (level->player == NULL) {
91         RETURN_LT(lt, NULL);
92     }
93
94     level->platforms = PUSH_LT(
95         lt,
96         create_platforms_from_line_stream(level_stream),
97         destroy_platforms);
98     if (level->platforms == NULL) {
99         RETURN_LT(lt, NULL);
100     }
101
102     level->goals = PUSH_LT(
103         lt,
104         create_goals_from_line_stream(level_stream),
105         destroy_goals);
106     if (level->goals == NULL) {
107         RETURN_LT(lt, NULL);
108     }
109
110     level->lava = PUSH_LT(
111         lt,
112         create_lava_from_line_stream(level_stream),
113         destroy_lava);
114     if (level->lava == NULL) {
115         RETURN_LT(lt, NULL);
116     }
117
118     level->back_platforms = PUSH_LT(
119         lt,
120         create_platforms_from_line_stream(level_stream),
121         destroy_platforms);
122     if (level->back_platforms == NULL) {
123         RETURN_LT(lt, NULL);
124     }
125
126     level->boxes = PUSH_LT(
127         lt,
128         create_boxes_from_line_stream(level_stream),
129         destroy_boxes);
130     if (level->boxes == NULL) {
131         RETURN_LT(lt, NULL);
132     }
133
134     level->labels = PUSH_LT(
135         lt,
136         create_labels_from_line_stream(level_stream),
137         destroy_labels);
138     if (level->labels == NULL) {
139         RETURN_LT(lt, NULL);
140     }
141
142     level->regions = PUSH_LT(
143         lt,
144         create_regions_from_line_stream(level_stream, broadcast),
145         destroy_regions);
146     if (level->regions == NULL) {
147         RETURN_LT(lt, NULL);
148     }
149
150     level->physical_world = PUSH_LT(lt, create_physical_world(), destroy_physical_world);
151     if (level->physical_world == NULL) {
152         RETURN_LT(lt, NULL);
153     }
154     if (boxes_add_to_physical_world(
155             level->boxes,
156             level->physical_world) < 0) { RETURN_LT(lt, NULL); }
157
158     level->flying_mode = false;
159     level->flying_camera_position = vec(0.0f, 0.0f);
160     level->flying_camera_scale = 1.0f;
161
162     level->lt = lt;
163
164     destroy_line_stream(RELEASE_LT(lt, level_stream));
165
166     return level;
167 }
168
169 void destroy_level(Level *level)
170 {
171     trace_assert(level);
172     RETURN_LT0(level->lt);
173 }
174
175 int level_render(const Level *level, Camera *camera)
176 {
177     trace_assert(level);
178
179     if (background_render(level->background, camera) < 0) {
180         return -1;
181     }
182
183     if (platforms_render(level->back_platforms, camera) < 0) {
184         return -1;
185     }
186
187     if (player_render(level->player, camera) < 0) {
188         return -1;
189     }
190
191     if (boxes_render(level->boxes, camera) < 0) {
192         return -1;
193     }
194
195     if (lava_render(level->lava, camera) < 0) {
196         return -1;
197     }
198
199     if (platforms_render(level->platforms, camera) < 0) {
200         return -1;
201     }
202
203     if (goals_render(level->goals, camera) < 0) {
204         return -1;
205     }
206
207     if (labels_render(level->labels, camera) < 0) {
208         return -1;
209     }
210
211     if (regions_render(level->regions, camera) < 0) {
212         return -1;
213     }
214
215     return 0;
216 }
217
218 int level_update(Level *level, float delta_time)
219 {
220     trace_assert(level);
221     trace_assert(delta_time > 0);
222
223     physical_world_apply_gravity(level->physical_world);
224     boxes_float_in_lava(level->boxes, level->lava);
225     rigid_bodies_apply_omniforce(level->rigid_bodies, vec(0.0f, LEVEL_GRAVITY));
226
227     boxes_update(level->boxes, delta_time);
228     player_update(level->player, delta_time);
229
230     rigid_bodies_collide(level->rigid_bodies, level->platforms);
231     physical_world_collide_solids(level->physical_world, level->platforms);
232
233     player_hide_goals(level->player, level->goals);
234     player_die_from_lava(level->player, level->lava);
235     regions_player_enter(level->regions, level->player);
236     regions_player_leave(level->regions, level->player);
237
238     goals_update(level->goals, delta_time);
239     lava_update(level->lava, delta_time);
240     labels_update(level->labels, delta_time);
241
242     return 0;
243 }
244
245 int level_event(Level *level, const SDL_Event *event)
246 {
247     trace_assert(level);
248     trace_assert(event);
249
250     switch (event->type) {
251     case SDL_KEYDOWN:
252         switch (event->key.keysym.sym) {
253         case SDLK_SPACE:
254             player_jump(level->player);
255             break;
256         }
257         break;
258
259     case SDL_JOYBUTTONDOWN:
260         if (event->jbutton.button == 1) {
261             player_jump(level->player);
262         }
263         break;
264
265     case SDL_MOUSEMOTION:
266         if (level->flying_mode) {
267             vec_add(&level->flying_camera_position,
268                     vec((float) event->motion.xrel, (float) event->motion.yrel));
269         }
270         break;
271
272     case SDL_MOUSEWHEEL:
273         if (level->flying_mode) {
274             // TODO(#679): zooming in flying mode is not smooth enough
275             if (event->wheel.y > 0) {
276                 level->flying_camera_scale += 0.1f;
277             } else if (event->wheel.y < 0) {
278                 level->flying_camera_scale = fmaxf(0.1f, level->flying_camera_scale - 0.1f);
279             }
280         }
281         break;
282     }
283
284     return 0;
285 }
286
287 int level_input(Level *level,
288                 const Uint8 *const keyboard_state,
289                 SDL_Joystick *the_stick_of_joy)
290 {
291     trace_assert(level);
292     trace_assert(keyboard_state);
293     (void) the_stick_of_joy;
294
295     if (keyboard_state[SDL_SCANCODE_A]) {
296         player_move_left(level->player);
297     } else if (keyboard_state[SDL_SCANCODE_D]) {
298         player_move_right(level->player);
299     } else if (the_stick_of_joy && SDL_JoystickGetAxis(the_stick_of_joy, 0) < 0) {
300         player_move_left(level->player);
301     } else if (the_stick_of_joy && SDL_JoystickGetAxis(the_stick_of_joy, 0) > 0) {
302         player_move_right(level->player);
303     } else {
304         player_stop(level->player);
305     }
306
307     return 0;
308 }
309
310 int level_reload_preserve_player(Level *level, const char *file_name, Broadcast *broadcast)
311 {
312     Lt * const lt = create_lt();
313     if (lt == NULL) {
314         return -1;
315     }
316
317     /* TODO(#104): duplicate code in create_level_from_file and level_reload_preserve_player */
318
319     LineStream * const level_stream = PUSH_LT(
320         lt,
321         create_line_stream(
322             file_name,
323             "r",
324             LEVEL_LINE_MAX_LENGTH),
325         destroy_line_stream);
326     if (level_stream == NULL) {
327         RETURN_LT(lt, -1);
328     }
329
330     Background * const background = create_background_from_line_stream(level_stream);
331     if (background == NULL) {
332         RETURN_LT(lt, -1);
333     }
334     level->background = RESET_LT(level->lt, level->background, background);
335
336     Player * const skipped_player = create_player_from_line_stream(level_stream, level->rigid_bodies, broadcast);
337     if (skipped_player == NULL) {
338         RETURN_LT(lt, -1);
339     }
340     destroy_player(skipped_player);
341
342     Platforms * const platforms = create_platforms_from_line_stream(level_stream);
343     if (platforms == NULL) {
344         RETURN_LT(lt, -1);
345     }
346     level->platforms = RESET_LT(level->lt, level->platforms, platforms);
347
348     Goals * const goals = create_goals_from_line_stream(level_stream);
349     if (goals == NULL) {
350         RETURN_LT(lt, -1);
351     }
352     level->goals = RESET_LT(level->lt, level->goals, goals);
353
354     Lava * const lava = create_lava_from_line_stream(level_stream);
355     if (lava == NULL) {
356         RETURN_LT(lt, -1);
357     }
358     level->lava = RESET_LT(level->lt, level->lava, lava);
359
360     Platforms * const back_platforms = create_platforms_from_line_stream(level_stream);
361     if (back_platforms == NULL) {
362         RETURN_LT(lt, -1);
363     }
364     level->back_platforms = RESET_LT(level->lt, level->back_platforms, back_platforms);
365
366     Boxes * const boxes = create_boxes_from_line_stream(level_stream);
367     if (boxes == NULL) {
368         RETURN_LT(lt, -1);
369     }
370     level->boxes = RESET_LT(level->lt, level->boxes, boxes);
371
372     Labels * const labels = create_labels_from_line_stream(level_stream);
373     if (labels == NULL) {
374         RETURN_LT(lt, -1);
375     }
376     level->labels = RESET_LT(level->lt, level->labels, labels);
377
378     Regions * const regions = create_regions_from_line_stream(level_stream, broadcast);
379     if (regions == NULL) {
380         RETURN_LT(lt, -1);
381     }
382     level->regions = RESET_LT(level->lt, level->regions, regions);
383
384     physical_world_clean(level->physical_world);
385     if (boxes_add_to_physical_world(
386             level->boxes,
387             level->physical_world) < 0) { RETURN_LT(lt, -1); }
388
389     RETURN_LT(lt, 0);
390 }
391
392 int level_sound(Level *level, Sound_samples *sound_samples)
393 {
394     if (goals_sound(level->goals, sound_samples) < 0) {
395         return -1;
396     }
397
398     if (player_sound(level->player, sound_samples) < 0) {
399         return -1;
400     }
401
402     return 0;
403 }
404
405 void level_toggle_debug_mode(Level *level)
406 {
407     background_toggle_debug_mode(level->background);
408 }
409
410 int level_enter_camera_event(Level *level, Camera *camera)
411 {
412     if (!level->flying_mode) {
413         player_focus_camera(level->player, camera);
414         camera_scale(camera, 1.0f);
415     } else {
416         camera_center_at(camera, level->flying_camera_position);
417         camera_scale(camera, level->flying_camera_scale);
418     }
419
420     goals_cue(level->goals, camera);
421     goals_checkpoint(level->goals, level->player);
422     labels_enter_camera_event(level->labels, camera);
423     return 0;
424 }
425
426 Rigid_rect *level_rigid_rect(Level *level,
427                              const char *rigid_rect_id)
428 {
429     trace_assert(level);
430     trace_assert(rigid_rect_id);
431
432     Rigid_rect *rigid_rect = boxes_rigid_rect(level->boxes, rigid_rect_id);
433     if (rigid_rect != NULL) {
434         return rigid_rect;
435     }
436
437     return NULL;
438 }
439
440 struct EvalResult level_send(Level *level, Gc *gc, struct Scope *scope, struct Expr path)
441 {
442     trace_assert(level);
443     trace_assert(gc);
444     trace_assert(scope);
445
446     const char *target = NULL;
447     struct Expr rest = void_expr();
448     struct EvalResult res = match_list(gc, "q*", path, &target, &rest);
449     if (res.is_error) {
450         return res;
451     }
452
453     if (strcmp(target, "goal") == 0) {
454         return goals_send(level->goals, gc, scope, rest);
455     } else if (strcmp(target, "label") == 0) {
456         return labels_send(level->labels, gc, scope, rest);
457     } else if (strcmp(target, "box") == 0) {
458         return boxes_send(level->boxes, gc, scope, rest);
459     } else if (strcmp(target, "body-push") == 0) {
460         long int id = 0, x = 0, y = 0;
461         res = match_list(gc, "ddd", rest, &id, &x, &y);
462         if (res.is_error) {
463             return res;
464         }
465
466         rigid_bodies_apply_force(level->rigid_bodies, (size_t) id, vec((float) x, (float) y));
467
468         return eval_success(NIL(gc));
469     } else if (strcmp(target, "body-add") == 0) {
470         long int x = 0, y = 0, w = 0, h = 0;
471         const char *color = 0;
472         res = match_list(gc, "dddds", rest, &x, &y, &w, &h, &color);
473         if (res.is_error) {
474             return res;
475         }
476
477         return eval_success(
478             NUMBER(
479                 gc,
480                 (long int) rigid_bodies_add(
481                     level->rigid_bodies,
482                     rect((float)x, (float)y, (float)w, (float)h),
483                     hexstr(color))));
484     } else if (strcmp(target, "fly") == 0) {
485         level->flying_mode = !level->flying_mode;
486         SDL_SetRelativeMouseMode(level->flying_mode);
487         return eval_success(NIL(gc));
488     }
489
490     return unknown_target(gc, "level", target);
491 }