]> git.lizzy.rs Git - nothing.git/blob - src/game.c
(#1071) Actually apply volume from settings page
[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 "sdl/texture.h"
16 #include "game/level/level_editor/background_layer.h"
17 #include "game/level/level_editor.h"
18 #include "game/settings.h"
19
20 static int game_render_cursor(const Game *game);
21
22 typedef enum Game_state {
23     GAME_STATE_LEVEL = 0,
24     GAME_STATE_LEVEL_PICKER,
25     GAME_STATE_LEVEL_EDITOR,
26     GAME_STATE_SETTINGS,
27     GAME_STATE_QUIT
28 } Game_state;
29
30 typedef struct Game {
31     Lt *lt;
32
33     Game_state state;
34     Sprite_font *font;
35     LevelPicker *level_picker;
36     LevelEditor *level_editor;
37     Level *level;
38     Settings settings;
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     game->font = PUSH_LT(
70         lt,
71         create_sprite_font_from_file(
72             "./assets/images/charmap-oldschool.bmp",
73             renderer),
74         destroy_sprite_font);
75     if (game->font == NULL) {
76         RETURN_LT(lt, NULL);
77     }
78
79     game->level_picker = PUSH_LT(
80         lt,
81         create_level_picker(
82             game->font,
83             level_folder),
84         destroy_level_picker);
85     if (game->level_picker == NULL) {
86         RETURN_LT(lt, NULL);
87     }
88
89     game->sound_samples = PUSH_LT(
90         lt,
91         create_sound_samples(
92             sound_sample_files,
93             sound_sample_files_count),
94         destroy_sound_samples);
95     if (game->sound_samples == NULL) {
96         RETURN_LT(lt, NULL);
97     }
98
99     game->settings = create_settings();
100
101     game->renderer = renderer;
102     game->texture_cursor = PUSH_LT(
103         lt,
104         texture_from_bmp("./assets/images/cursor.bmp", renderer),
105         SDL_DestroyTexture);
106     if (SDL_SetTextureBlendMode(
107             game->texture_cursor,
108             SDL_ComposeCustomBlendMode(
109                 SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
110                 SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
111                 SDL_BLENDOPERATION_ADD,
112                 SDL_BLENDFACTOR_ONE,
113                 SDL_BLENDFACTOR_ZERO,
114                 SDL_BLENDOPERATION_ADD)) < 0) {
115         log_warn("SDL error: %s\n", SDL_GetError());
116     }
117     game->cursor_x = 0;
118     game->cursor_y = 0;
119
120     game_switch_state(game, GAME_STATE_LEVEL_PICKER);
121
122     return game;
123 }
124
125 void destroy_game(Game *game)
126 {
127     trace_assert(game);
128     RETURN_LT0(game->lt);
129 }
130
131 int game_render(const Game *game)
132 {
133     trace_assert(game);
134
135     switch(game->state) {
136     case GAME_STATE_LEVEL: {
137         if (level_render(game->level, &game->camera) < 0) {
138             return -1;
139         }
140     } break;
141
142     case GAME_STATE_LEVEL_PICKER: {
143         if (level_picker_render(game->level_picker, &game->camera) < 0) {
144             return -1;
145         }
146     } break;
147
148     case GAME_STATE_LEVEL_EDITOR: {
149         if (level_editor_render(game->level_editor, &game->camera) < 0) {
150             return -1;
151         }
152     } break;
153
154     case GAME_STATE_SETTINGS: {
155         settings_render(&game->settings, &game->camera);
156     } break;
157
158     case GAME_STATE_QUIT: break;
159     }
160
161     if (game_render_cursor(game) < 0) {
162         return -1;
163     }
164
165     return 0;
166 }
167
168 int game_sound(Game *game)
169 {
170     switch (game->state) {
171     case GAME_STATE_LEVEL:
172         return level_sound(game->level, game->sound_samples);
173     case GAME_STATE_LEVEL_EDITOR:
174         level_editor_sound(game->level_editor, game->sound_samples);
175         return 0;
176     case GAME_STATE_LEVEL_PICKER:
177     case GAME_STATE_SETTINGS:
178     case GAME_STATE_QUIT:
179         return 0;
180     }
181
182     return 0;
183 }
184
185 int game_update(Game *game, float delta_time)
186 {
187     trace_assert(game);
188     trace_assert(delta_time > 0.0f);
189
190     switch (game->state) {
191     case GAME_STATE_LEVEL: {
192         if (level_update(game->level, delta_time) < 0) {
193             return -1;
194         }
195
196         if (level_enter_camera_event(game->level, &game->camera) < 0) {
197             return -1;
198         }
199
200     } break;
201
202     case GAME_STATE_LEVEL_PICKER: {
203         if (level_picker_update(game->level_picker, delta_time) < 0) {
204             return -1;
205         }
206
207         if (level_picker_enter_camera_event(game->level_picker, &game->camera) < 0) {
208             return -1;
209         }
210
211         const char *level_filename = level_picker_selected_level(game->level_picker);
212
213         if (level_filename != NULL) {
214             if (game->level_editor == NULL) {
215                 game->level_editor = PUSH_LT(
216                     game->lt,
217                     create_level_editor_from_file(level_filename),
218                     destroy_level_editor);
219             } else {
220                 game->level_editor = RESET_LT(
221                     game->lt,
222                     game->level_editor,
223                     create_level_editor_from_file(level_filename));
224             }
225
226             if (game->level_editor == NULL) {
227                 return -1;
228             }
229
230             if (game->level == NULL) {
231                 game->level = PUSH_LT(
232                     game->lt,
233                     create_level_from_level_editor(
234                         game->level_editor),
235                     destroy_level);
236             } else {
237                 game->level = RESET_LT(
238                     game->lt,
239                     game->level,
240                     create_level_from_level_editor(
241                         game->level_editor));
242             }
243
244             if (game->level == NULL) {
245                 return -1;
246             }
247
248             game_switch_state(game, GAME_STATE_LEVEL);
249         }
250
251     } break;
252
253     case GAME_STATE_LEVEL_EDITOR: {
254         if (level_editor_focus_camera(
255                 game->level_editor,
256                 &game->camera) < 0) {
257             return -1;
258         }
259
260         level_editor_update(game->level_editor, delta_time);
261     } break;
262
263     case GAME_STATE_SETTINGS: {
264         settings_update(&game->settings, &game->camera, delta_time);
265         sound_samples_update_volume(
266             game->sound_samples,
267             game->settings.volume_slider.value);
268     } break;
269
270     case GAME_STATE_QUIT:
271         break;
272     }
273
274     return 0;
275 }
276
277 static int game_event_running(Game *game, const SDL_Event *event)
278 {
279     trace_assert(game);
280     trace_assert(event);
281
282     if (!SDL_IsTextInputActive()) {
283         switch (event->type) {
284         case SDL_KEYDOWN: {
285             switch (event->key.keysym.sym) {
286             case SDLK_r: {
287                 game->level = RESET_LT(
288                     game->lt,
289                     game->level,
290                     create_level_from_level_editor(
291                         game->level_editor));
292                 if (game->level == NULL) {
293                     game_switch_state(game, GAME_STATE_QUIT);
294                     return -1;
295                 }
296
297                 camera_disable_debug_mode(&game->camera);
298             } break;
299
300             case SDLK_TAB: {
301                 game_switch_state(game, GAME_STATE_LEVEL_EDITOR);
302             } break;
303             }
304         } break;
305         }
306     }
307
308     return level_event(game->level, event, &game->camera, game->sound_samples);
309 }
310
311 static int game_event_level_picker(Game *game, const SDL_Event *event)
312 {
313     trace_assert(game);
314     trace_assert(event);
315
316     switch (event->type) {
317     case SDL_KEYDOWN: {
318         switch(event->key.keysym.sym) {
319         case SDLK_n: {
320             if (game->level_editor == NULL) {
321                 game->level_editor = PUSH_LT(
322                     game->lt,
323                     create_level_editor(),
324                     destroy_level_editor);
325             } else {
326                 game->level_editor = RESET_LT(
327                     game->lt,
328                     game->level_editor,
329                     create_level_editor());
330             }
331
332             if (game->level_editor == NULL) {
333                 return -1;
334             }
335
336             if (game->level == NULL) {
337                 game->level = PUSH_LT(
338                     game->lt,
339                     create_level_from_level_editor(
340                         game->level_editor),
341                     destroy_level);
342             } else {
343                 game->level = RESET_LT(
344                     game->lt,
345                     game->level,
346                     create_level_from_level_editor(
347                         game->level_editor));
348             }
349
350             if (game->level == NULL) {
351                 return -1;
352             }
353
354             game_switch_state(game, GAME_STATE_LEVEL);
355         } break;
356
357         case SDLK_s: {
358             game_switch_state(game, GAME_STATE_SETTINGS);
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             if (game->level == NULL) {
382                 return -1;
383             }
384             game_switch_state(game, GAME_STATE_LEVEL);
385         } break;
386         }
387     } break;
388     }
389
390     return level_editor_event(game->level_editor, event, &game->camera);
391 }
392
393 int game_event(Game *game, const SDL_Event *event)
394 {
395     trace_assert(game);
396     trace_assert(event);
397
398     switch (event->type) {
399     case SDL_QUIT: {
400         game_switch_state(game, GAME_STATE_QUIT);
401         return 0;
402     } break;
403
404     case SDL_MOUSEMOTION: {
405         game->cursor_x = event->motion.x;
406         game->cursor_y = event->motion.y;
407     } break;
408
409     case SDL_KEYDOWN: {
410         if (event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) {
411             game_switch_state(game, GAME_STATE_QUIT);
412             return 0;
413         }
414     } break;
415     }
416
417     switch (game->state) {
418     case GAME_STATE_LEVEL:
419         return game_event_running(game, event);
420
421     case GAME_STATE_LEVEL_PICKER:
422         return game_event_level_picker(game, event);
423
424     case GAME_STATE_LEVEL_EDITOR:
425         return game_event_level_editor(game, event);
426
427     case GAME_STATE_SETTINGS: {
428         switch (event->type) {
429         case SDL_KEYDOWN: {
430             if (event->key.keysym.sym == SDLK_ESCAPE) {
431                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
432                 return 0;
433             }
434         } break;
435         }
436
437         settings_event(&game->settings, &game->camera, event);
438         return 0;
439     } break;
440
441     case GAME_STATE_QUIT:
442         return 0;
443     }
444
445     return -1;
446 }
447
448
449 // TODO: get rid of keyboard_state (because it's a global var and can
450 // be check anywhere anyway). And introduce *_joystick methods.
451 int game_input(Game *game,
452                const Uint8 *const keyboard_state,
453                SDL_Joystick *the_stick_of_joy)
454 {
455     trace_assert(game);
456     trace_assert(keyboard_state);
457
458     switch (game->state) {
459     case GAME_STATE_SETTINGS:
460     case GAME_STATE_QUIT:
461     case GAME_STATE_LEVEL_EDITOR:
462         return 0;
463
464     case GAME_STATE_LEVEL:
465         return level_input(game->level, keyboard_state, the_stick_of_joy);
466
467     case GAME_STATE_LEVEL_PICKER:
468         return level_picker_input(game->level_picker, keyboard_state, the_stick_of_joy);
469     }
470
471     return 0;
472 }
473
474 int game_over_check(const Game *game)
475 {
476     return game->state == GAME_STATE_QUIT;
477 }
478
479 // Private Functions
480
481 static int game_render_cursor(const Game *game)
482 {
483     trace_assert(game);
484
485     SDL_Rect src = {0, 0, 32, 32};
486     SDL_Rect dest = {game->cursor_x, game->cursor_y, 32, 32};
487     if (SDL_RenderCopy(game->renderer, game->texture_cursor, &src, &dest) < 0) {
488         return -1;
489     }
490
491     return 0;
492 }