]> git.lizzy.rs Git - nothing.git/blob - src/game.c
Fixed `TODO` to comply with snitch
[nothing.git] / src / game.c
1 #include <SDL.h>
2 #include "system/stacktrace.h"
3 #include <stdio.h>
4
5 #include "game.h"
6 #include "game/level.h"
7 #include "game/sound_samples.h"
8 #include "game/level_picker.h"
9 #include "system/log.h"
10 #include "system/lt.h"
11 #include "system/nth_alloc.h"
12 #include "ui/console.h"
13 #include "ui/edit_field.h"
14 #include "system/str.h"
15 #include "ebisp/builtins.h"
16 #include "broadcast.h"
17 #include "sdl/texture.h"
18 #include "game/level/level_editor/background_layer.h"
19 #include "game/level/level_editor.h"
20
21 static int game_render_cursor(const Game *game);
22
23 typedef enum Game_state {
24     GAME_STATE_LEVEL = 0,
25     GAME_STATE_LEVEL_PICKER,
26     GAME_STATE_LEVEL_EDITOR,
27     GAME_STATE_QUIT
28 } Game_state;
29
30 typedef struct Game {
31     Lt *lt;
32
33     Game_state state;
34     Broadcast *broadcast;
35     Sprite_font *font;
36     LevelPicker *level_picker;
37     LevelEditor *level_editor;
38     Level *level;
39     Sound_samples *sound_samples;
40     Camera camera;
41     SDL_Renderer *renderer;
42     SDL_Texture *texture_cursor;
43     int cursor_x;
44     int cursor_y;
45 } Game;
46
47 static
48 void game_switch_state(Game *game, Game_state state)
49 {
50     game->camera = create_camera(game->renderer, game->font);
51     game->state = state;
52 }
53
54 Game *create_game(const char *level_folder,
55                   const char *sound_sample_files[],
56                   size_t sound_sample_files_count,
57                   SDL_Renderer *renderer)
58 {
59     trace_assert(level_folder);
60
61     Lt *lt = create_lt();
62
63     Game *game = PUSH_LT(lt, nth_calloc(1, sizeof(Game)), free);
64     if (game == NULL) {
65         RETURN_LT(lt, NULL);
66     }
67     game->lt = lt;
68
69
70     game->broadcast = PUSH_LT(
71         lt,
72         create_broadcast(game),
73         destroy_broadcast);
74     if (game->broadcast == NULL) {
75         RETURN_LT(lt, NULL);
76     }
77
78     game->font = PUSH_LT(
79         lt,
80         create_sprite_font_from_file(
81             "images/charmap-oldschool.bmp",
82             renderer),
83         destroy_sprite_font);
84     if (game->font == NULL) {
85         RETURN_LT(lt, NULL);
86     }
87
88     game->level_picker = PUSH_LT(
89         lt,
90         create_level_picker(
91             game->font,
92             level_folder),
93         destroy_level_picker);
94     if (game->level_picker == NULL) {
95         RETURN_LT(lt, NULL);
96     }
97
98     game->sound_samples = PUSH_LT(
99         lt,
100         create_sound_samples(
101             sound_sample_files,
102             sound_sample_files_count),
103         destroy_sound_samples);
104     if (game->sound_samples == NULL) {
105         RETURN_LT(lt, NULL);
106     }
107
108     game->renderer = renderer;
109     game->texture_cursor = PUSH_LT(
110         lt,
111         texture_from_bmp("images/cursor.bmp", renderer),
112         SDL_DestroyTexture);
113     if (SDL_SetTextureBlendMode(
114             game->texture_cursor,
115             SDL_ComposeCustomBlendMode(
116                 SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
117                 SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
118                 SDL_BLENDOPERATION_ADD,
119                 SDL_BLENDFACTOR_ONE,
120                 SDL_BLENDFACTOR_ZERO,
121                 SDL_BLENDOPERATION_ADD)) < 0) {
122         log_warn("SDL error: %s\n", SDL_GetError());
123     }
124     game->cursor_x = 0;
125     game->cursor_y = 0;
126
127     game_switch_state(game, GAME_STATE_LEVEL_PICKER);
128
129     return game;
130 }
131
132 void destroy_game(Game *game)
133 {
134     trace_assert(game);
135     RETURN_LT0(game->lt);
136 }
137
138 int game_render(const Game *game)
139 {
140     trace_assert(game);
141
142     switch(game->state) {
143     case GAME_STATE_LEVEL: {
144         if (level_render(game->level, &game->camera) < 0) {
145             return -1;
146         }
147     } break;
148
149     case GAME_STATE_LEVEL_PICKER: {
150         if (level_picker_render(game->level_picker, &game->camera) < 0) {
151             return -1;
152         }
153
154         if (game_render_cursor(game) < 0) {
155             return -1;
156         }
157     } break;
158
159     case GAME_STATE_LEVEL_EDITOR: {
160         if (level_editor_render(game->level_editor, &game->camera) < 0) {
161             return -1;
162         }
163
164         if (game_render_cursor(game) < 0) {
165             return -1;
166         }
167     } break;
168
169     case GAME_STATE_QUIT: break;
170     }
171
172     return 0;
173 }
174
175 int game_sound(Game *game)
176 {
177     switch (game->state) {
178     case GAME_STATE_LEVEL:
179         return level_sound(game->level, game->sound_samples);
180     case GAME_STATE_LEVEL_PICKER:
181     case GAME_STATE_LEVEL_EDITOR:
182     case GAME_STATE_QUIT:
183         return 0;
184     }
185
186     return 0;
187 }
188
189 int game_update(Game *game, float delta_time)
190 {
191     trace_assert(game);
192     trace_assert(delta_time > 0.0f);
193
194     switch (game->state) {
195     case GAME_STATE_LEVEL: {
196         if (level_update(game->level, delta_time) < 0) {
197             return -1;
198         }
199
200         if (level_enter_camera_event(game->level, &game->camera) < 0) {
201             return -1;
202         }
203
204     } break;
205
206     case GAME_STATE_LEVEL_PICKER: {
207         if (level_picker_update(game->level_picker, delta_time) < 0) {
208             return -1;
209         }
210
211         if (level_picker_enter_camera_event(game->level_picker, &game->camera) < 0) {
212             return -1;
213         }
214
215         const char *level_filename = level_picker_selected_level(game->level_picker);
216
217         if (level_filename != NULL) {
218             if (game->level_editor == NULL) {
219                 game->level_editor = PUSH_LT(
220                     game->lt,
221                     create_level_editor_from_file(level_filename),
222                     destroy_level_editor);
223             } else {
224                 game->level_editor = RESET_LT(
225                     game->lt,
226                     game->level_editor,
227                     create_level_editor_from_file(level_filename));
228             }
229
230             if (game->level_editor == NULL) {
231                 return -1;
232             }
233
234             if (game->level == NULL) {
235                 game->level = PUSH_LT(
236                     game->lt,
237                     create_level_from_level_editor(
238                         game->level_editor,
239                         game->broadcast),
240                     destroy_level);
241             } else {
242                 game->level = RESET_LT(
243                     game->lt,
244                     game->level,
245                     create_level_from_level_editor(
246                         game->level_editor,
247                         game->broadcast));
248             }
249
250             if (game->level == NULL) {
251                 return -1;
252             }
253
254             //TODO: Move level picker volume update to a more appropriate place
255             sound_samples_update_volume(game->sound_samples, level_picker_get_volume(game->level_picker));
256             game_switch_state(game, GAME_STATE_LEVEL);
257         }
258
259     } break;
260
261     case GAME_STATE_LEVEL_EDITOR: {
262         if (level_editor_focus_camera(
263                 game->level_editor,
264                 &game->camera) < 0) {
265             return -1;
266         }
267
268         level_editor_update(game->level_editor, delta_time);
269     } break;
270
271     case GAME_STATE_QUIT:
272         break;
273     }
274
275     return 0;
276 }
277
278 static int game_event_running(Game *game, const SDL_Event *event)
279 {
280     trace_assert(game);
281     trace_assert(event);
282
283     if (!SDL_IsTextInputActive()) {
284         switch (event->type) {
285         case SDL_KEYDOWN: {
286             switch (event->key.keysym.sym) {
287             case SDLK_r: {
288                 game->level = RESET_LT(
289                     game->lt,
290                     game->level,
291                     create_level_from_level_editor(
292                         game->level_editor,
293                         game->broadcast));
294                 if (game->level == NULL) {
295                     game_switch_state(game, GAME_STATE_QUIT);
296                     return -1;
297                 }
298
299                 camera_disable_debug_mode(&game->camera);
300             } break;
301
302             case SDLK_TAB: {
303                 game_switch_state(game, GAME_STATE_LEVEL_EDITOR);
304             } break;
305             }
306         } break;
307         }
308     }
309
310     return level_event(game->level, event, &game->camera, game->sound_samples);
311 }
312
313 static int game_event_level_picker(Game *game, const SDL_Event *event)
314 {
315     trace_assert(game);
316     trace_assert(event);
317
318     switch (event->type) {
319     case SDL_KEYDOWN: {
320         switch(event->key.keysym.sym) {
321         case SDLK_n: {
322             if (game->level_editor == NULL) {
323                 game->level_editor = PUSH_LT(
324                     game->lt,
325                     create_level_editor(),
326                     destroy_level_editor);
327             } else {
328                 game->level_editor = RESET_LT(
329                     game->lt,
330                     game->level_editor,
331                     create_level_editor());
332             }
333
334             if (game->level_editor == NULL) {
335                 return -1;
336             }
337
338             if (game->level == NULL) {
339                 game->level = PUSH_LT(
340                     game->lt,
341                     create_level_from_level_editor(
342                         game->level_editor,
343                         game->broadcast),
344                     destroy_level);
345             } else {
346                 game->level = RESET_LT(
347                     game->lt,
348                     game->level,
349                     create_level_from_level_editor(
350                         game->level_editor,
351                         game->broadcast));
352             }
353
354             if (game->level == NULL) {
355                 return -1;
356             }
357
358             game_switch_state(game, GAME_STATE_LEVEL);
359         } break;
360         }
361     } break;
362     }
363
364     return level_picker_event(game->level_picker, event, &game->camera);
365 }
366
367 static int game_event_level_editor(Game *game, const SDL_Event *event)
368 {
369     trace_assert(game);
370     trace_assert(event);
371
372     switch (event->type) {
373     case SDL_KEYDOWN: {
374         switch (event->key.keysym.sym) {
375         case SDLK_TAB: {
376             game->level = RESET_LT(
377                 game->lt,
378                 game->level,
379                 create_level_from_level_editor(
380                     game->level_editor,
381                     game->broadcast));
382             if (game->level == NULL) {
383                 return -1;
384             }
385             game_switch_state(game, GAME_STATE_LEVEL);
386         } break;
387         }
388     } break;
389     }
390
391     return level_editor_event(game->level_editor, event, &game->camera);
392 }
393
394 int game_event(Game *game, const SDL_Event *event)
395 {
396     trace_assert(game);
397     trace_assert(event);
398
399     switch (event->type) {
400     case SDL_QUIT: {
401         game_switch_state(game, GAME_STATE_QUIT);
402         return 0;
403     } break;
404
405     case SDL_MOUSEMOTION: {
406         game->cursor_x = event->motion.x;
407         game->cursor_y = event->motion.y;
408     } break;
409
410     case SDL_KEYDOWN: {
411         if (event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) {
412             game_switch_state(game, GAME_STATE_QUIT);
413             return 0;
414         }
415     } break;
416     }
417
418     switch (game->state) {
419     case GAME_STATE_LEVEL:
420         return game_event_running(game, event);
421
422     case GAME_STATE_LEVEL_PICKER:
423         return game_event_level_picker(game, event);
424
425     case GAME_STATE_LEVEL_EDITOR:
426         return game_event_level_editor(game, event);
427
428     case GAME_STATE_QUIT:
429         return 0;
430     }
431
432     return -1;
433 }
434
435
436 int game_input(Game *game,
437                const Uint8 *const keyboard_state,
438                SDL_Joystick *the_stick_of_joy)
439 {
440     trace_assert(game);
441     trace_assert(keyboard_state);
442
443     switch (game->state) {
444     case GAME_STATE_QUIT:
445     case GAME_STATE_LEVEL_EDITOR:
446         return 0;
447
448     case GAME_STATE_LEVEL:
449         return level_input(game->level, keyboard_state, the_stick_of_joy);
450
451     case GAME_STATE_LEVEL_PICKER:
452         return level_picker_input(game->level_picker, keyboard_state, the_stick_of_joy);
453     }
454
455     return 0;
456 }
457
458 int game_over_check(const Game *game)
459 {
460     return game->state == GAME_STATE_QUIT;
461 }
462
463 struct EvalResult
464 game_send(Game *game, Gc *gc, struct Scope *scope,
465           struct Expr path)
466 {
467     trace_assert(game);
468     trace_assert(gc);
469     trace_assert(scope);
470
471     const char *target = NULL;
472     struct Expr rest = void_expr();
473     struct EvalResult res = match_list(gc, "q*", path, &target, &rest);
474     if (res.is_error) {
475         return res;
476     }
477
478     if (strcmp(target, "level") == 0) {
479         return level_send(game->level, gc, scope, rest);
480     } else if (strcmp(target, "menu") == 0) {
481         level_picker_clean_selection(game->level_picker);
482         game_switch_state(game, GAME_STATE_LEVEL_PICKER);
483         return eval_success(NIL(gc));
484     }
485
486     return unknown_target(gc, "game", target);
487 }
488
489 // Private Functions
490
491 static int game_render_cursor(const Game *game)
492 {
493     trace_assert(game);
494
495     SDL_Rect src = {0, 0, 32, 32};
496     SDL_Rect dest = {game->cursor_x, game->cursor_y, 32, 32};
497     if (SDL_RenderCopy(game->renderer, game->texture_cursor, &src, &dest) < 0) {
498         return -1;
499     }
500
501     return 0;
502 }