]> git.lizzy.rs Git - nothing.git/blob - src/game.c
7e59f094b5dccccd2476bb10a8a54adf6f105bdb
[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 "ui/cursor.h"
15 #include "system/str.h"
16 #include "sdl/texture.h"
17 #include "game/level/level_editor/background_layer.h"
18 #include "game/level/level_editor.h"
19 #include "game/settings.h"
20 #include "game/credits.h"
21
22
23 typedef struct Game {
24     Lt *lt;
25
26     Game_state state;
27     Sprite_font font;
28     LevelPicker level_picker;
29     LevelEditor *level_editor;
30     Credits *credits;
31     Level *level;
32     Settings settings;
33     Sound_samples *sound_samples;
34     Camera camera;
35     SDL_Renderer *renderer;
36     Console *console;
37     Cursor cursor;
38     int console_enabled;
39 } Game;
40
41 void game_switch_state(Game *game, Game_state state)
42 {
43     game->cursor.style = CURSOR_STYLE_POINTER;
44     if (state == GAME_STATE_LEVEL_PICKER) {
45         level_picker_clean_selection(&game->level_picker);
46     }
47     game->camera = create_camera(game->renderer, game->font);
48     game->state = state;
49 }
50
51 Game *create_game(const char *level_folder,
52                   const char *sound_sample_files[],
53                   size_t sound_sample_files_count,
54                   SDL_Renderer *renderer)
55 {
56     trace_assert(level_folder);
57
58     Lt *lt = create_lt();
59
60     Game *game = PUSH_LT(lt, nth_calloc(1, sizeof(Game)), free);
61     if (game == NULL) {
62         RETURN_LT(lt, NULL);
63     }
64     game->lt = lt;
65
66     game->font.texture = load_bmp_font_texture(
67         renderer,
68         "./assets/images/charmap-oldschool.bmp");
69
70     level_picker_populate(&game->level_picker, level_folder);
71
72     game->credits = PUSH_LT(
73         lt,
74         create_credits(),
75         destroy_credits);
76     if (game->credits == NULL) {
77         RETURN_LT(lt, NULL);
78     }
79
80     game->sound_samples = PUSH_LT(
81         lt,
82         create_sound_samples(
83             sound_sample_files,
84             sound_sample_files_count),
85         destroy_sound_samples);
86     if (game->sound_samples == NULL) {
87         RETURN_LT(lt, NULL);
88     }
89
90     game->settings = create_settings();
91
92     game->renderer = renderer;
93
94     for (Cursor_Style style = 0; style < CURSOR_STYLE_N; ++style) {
95         game->cursor.texs[style] = PUSH_LT(
96             lt,
97             texture_from_bmp(cursor_style_tex_files[style], renderer),
98             SDL_DestroyTexture);
99         if (SDL_SetTextureBlendMode(
100                 game->cursor.texs[style],
101                 SDL_ComposeCustomBlendMode(
102                     SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
103                     SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
104                     SDL_BLENDOPERATION_ADD,
105                     SDL_BLENDFACTOR_ONE,
106                     SDL_BLENDFACTOR_ZERO,
107                     SDL_BLENDOPERATION_ADD)) < 0) {
108             log_warn("SDL error while setting blending mode for `%s': %s\n",
109                      cursor_style_tex_files[style],
110                      SDL_GetError());
111         }
112     }
113
114
115     game->console = PUSH_LT(
116         lt,
117         create_console(game),
118         destroy_console);
119     if (game->console == NULL) {
120         RETURN_LT(lt, NULL);
121     }
122     game->console_enabled = 0;
123
124     game_switch_state(game, GAME_STATE_LEVEL_PICKER);
125
126     return game;
127 }
128
129 void destroy_game(Game *game)
130 {
131     trace_assert(game);
132     destroy_level_picker(game->level_picker);
133     RETURN_LT0(game->lt);
134 }
135
136 int game_render(const Game *game)
137 {
138     trace_assert(game);
139
140     switch(game->state) {
141     case GAME_STATE_LEVEL: {
142         if (level_render(game->level, &game->camera) < 0) {
143             return -1;
144         }
145     } break;
146
147     case GAME_STATE_LEVEL_PICKER: {
148         if (level_picker_render(&game->level_picker, &game->camera) < 0) {
149             return -1;
150         }
151     } break;
152
153     case GAME_STATE_LEVEL_EDITOR: {
154         if (level_editor_render(game->level_editor, &game->camera) < 0) {
155             return -1;
156         }
157     } break;
158
159     case GAME_STATE_CREDITS: {
160         if (credits_render(game->credits, &game->camera) < 0) {
161             return -1;
162         }
163     } break;
164
165     case GAME_STATE_SETTINGS: {
166         settings_render(&game->settings, &game->camera);
167     } break;
168
169     case GAME_STATE_QUIT: break;
170     }
171
172     if (game->console_enabled) {
173         if (console_render(game->console, &game->camera) < 0) {
174             return -1;
175         }
176     }
177
178     if (cursor_render(&game->cursor, game->renderer) < 0) {
179         return -1;
180     }
181
182     return 0;
183 }
184
185 int game_sound(Game *game)
186 {
187     switch (game->state) {
188     case GAME_STATE_LEVEL:
189         return level_sound(game->level, game->sound_samples);
190     case GAME_STATE_LEVEL_EDITOR:
191         level_editor_sound(game->level_editor, game->sound_samples);
192         return 0;
193     case GAME_STATE_LEVEL_PICKER:
194     case GAME_STATE_CREDITS:
195     case GAME_STATE_SETTINGS:
196     case GAME_STATE_QUIT:
197         return 0;
198     }
199
200     return 0;
201 }
202
203 int game_update(Game *game, float delta_time)
204 {
205     trace_assert(game);
206     trace_assert(delta_time > 0.0f);
207
208     // TODO(#1218): effective scale recalculation should be probably done only when the size of the window is changed
209     SDL_Rect view_port;
210     SDL_RenderGetViewport(game->camera.renderer, &view_port);
211     game->camera.effective_scale = effective_scale(&view_port);
212
213     if (game->console_enabled) {
214         if (console_update(game->console, delta_time) < 0) {
215             return -1;
216         }
217     }
218
219     switch (game->state) {
220     case GAME_STATE_LEVEL: {
221         if (level_update(game->level, delta_time) < 0) {
222             return -1;
223         }
224
225         if (level_enter_camera_event(game->level, &game->camera) < 0) {
226             return -1;
227         }
228
229     } break;
230
231     case GAME_STATE_LEVEL_PICKER: {
232         if (level_picker_update(&game->level_picker, &game->camera, delta_time) < 0) {
233             return -1;
234         }
235
236         if (level_picker_enter_camera_event(&game->level_picker, &game->camera) < 0) {
237             return -1;
238         }
239
240         const char *level_filename = level_picker_selected_level(&game->level_picker);
241
242         if (level_filename != NULL) {
243             if (game_load_level(game, level_filename) < 0) {
244                 return -1;
245             }
246         }
247     } break;
248
249     case GAME_STATE_LEVEL_EDITOR: {
250         if (level_editor_focus_camera(
251                 game->level_editor,
252                 &game->camera) < 0) {
253             return -1;
254         }
255
256         level_editor_update(game->level_editor, delta_time);
257     } break;
258
259     case GAME_STATE_CREDITS: {
260         if (credits_update(game->credits, &game->camera, delta_time) < 0) {
261             return -1;
262         }
263     } break;
264
265     case GAME_STATE_SETTINGS: {
266         settings_update(&game->settings, &game->camera, delta_time);
267         sound_samples_update_volume(
268             game->sound_samples,
269             game->settings.volume_slider.value);
270     } break;
271
272     case GAME_STATE_QUIT:
273         break;
274     }
275
276     return 0;
277 }
278
279 static int game_event_running(Game *game, const SDL_Event *event)
280 {
281     trace_assert(game);
282     trace_assert(event);
283
284     if (!SDL_IsTextInputActive()) {
285         switch (event->type) {
286         case SDL_KEYDOWN: {
287             switch (event->key.keysym.sym) {
288             case SDLK_r: {
289                 game->level = RESET_LT(
290                     game->lt,
291                     game->level,
292                     create_level_from_level_editor(
293                         game->level_editor));
294                 if (game->level == NULL) {
295                     game_switch_state(game, GAME_STATE_QUIT);
296                     return -1;
297                 }
298
299                 level_disable_pause_mode(
300                     game->level,
301                     &game->camera,
302                     game->sound_samples);
303                 camera_disable_debug_mode(&game->camera);
304             } break;
305
306             case SDLK_TAB: {
307                 game_switch_state(game, GAME_STATE_LEVEL_EDITOR);
308             } break;
309             }
310         } break;
311         }
312     }
313
314     return level_event(game->level, event, &game->camera, game->sound_samples);
315 }
316
317 static int game_event_level_picker(Game *game, const SDL_Event *event)
318 {
319     trace_assert(game);
320     trace_assert(event);
321
322     switch (event->type) {
323     case SDL_KEYDOWN: {
324         switch(event->key.keysym.sym) {
325         case SDLK_n: {
326             if (game->level_editor == NULL) {
327                 game->level_editor = PUSH_LT(
328                     game->lt,
329                     create_level_editor(&game->cursor),
330                     destroy_level_editor);
331             } else {
332                 game->level_editor = RESET_LT(
333                     game->lt,
334                     game->level_editor,
335                     create_level_editor(&game->cursor));
336             }
337
338             if (game->level_editor == NULL) {
339                 return -1;
340             }
341
342             if (game->level == NULL) {
343                 game->level = PUSH_LT(
344                     game->lt,
345                     create_level_from_level_editor(
346                         game->level_editor),
347                     destroy_level);
348             } else {
349                 game->level = RESET_LT(
350                     game->lt,
351                     game->level,
352                     create_level_from_level_editor(
353                         game->level_editor));
354             }
355
356             if (game->level == NULL) {
357                 return -1;
358             }
359
360             game_switch_state(game, GAME_STATE_LEVEL);
361         } break;
362
363         case SDLK_i: {
364             game_switch_state(game, GAME_STATE_CREDITS);
365         } break;
366
367         case SDLK_s: {
368             game_switch_state(game, GAME_STATE_SETTINGS);
369         } break;
370         }
371     } break;
372     }
373
374     return level_picker_event(&game->level_picker, event);
375 }
376
377 static int game_event_level_editor(Game *game, const SDL_Event *event)
378 {
379     trace_assert(game);
380     trace_assert(event);
381
382     switch (event->type) {
383     case SDL_KEYDOWN: {
384         switch (event->key.keysym.sym) {
385         case SDLK_TAB: {
386             game->level = RESET_LT(
387                 game->lt,
388                 game->level,
389                 create_level_from_level_editor(
390                     game->level_editor));
391             if (game->level == NULL) {
392                 return -1;
393             }
394             game_switch_state(game, GAME_STATE_LEVEL);
395         } break;
396         }
397     } break;
398     }
399
400     return level_editor_event(game->level_editor, event, &game->camera);
401 }
402
403 int game_event(Game *game, const SDL_Event *event)
404 {
405     trace_assert(game);
406     trace_assert(event);
407
408     // Global event handling
409     switch (event->type) {
410     case SDL_QUIT: {
411         game_switch_state(game, GAME_STATE_QUIT);
412         return 0;
413     } break;
414
415     case SDL_KEYDOWN: {
416         if ((event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) ||
417             (event->key.keysym.sym == SDLK_F4 && event->key.keysym.mod & KMOD_ALT)) {
418             game_switch_state(game, GAME_STATE_QUIT);
419             return 0;
420         }
421     } break;
422     }
423
424     // Console event handling
425     if (game->console_enabled) {
426         switch (event->type) {
427         case SDL_KEYDOWN:
428             switch (event->key.keysym.sym) {
429             case SDLK_ESCAPE:
430                 SDL_StopTextInput();
431                 game->console_enabled = 0;
432                 return 0;
433             default: {}
434             }
435
436         default: {}
437         }
438
439         return console_handle_event(game->console, event);
440     } else {
441         switch (event->type) {
442         case SDL_KEYUP: {
443             switch (event->key.keysym.sym) {
444             case SDLK_BACKQUOTE:
445             case SDLK_c: {
446                 if (event->key.keysym.mod == KMOD_NONE || event->key.keysym.mod == KMOD_NUM) {
447                     SDL_StartTextInput();
448                     game->console_enabled = 1;
449                     console_slide_down(game->console);
450                 }
451             } break;
452             }
453         } break;
454         }
455     }
456
457     // State event handling
458     switch (game->state) {
459     case GAME_STATE_LEVEL:
460         return game_event_running(game, event);
461
462     case GAME_STATE_LEVEL_PICKER:
463         return game_event_level_picker(game, event);
464
465     case GAME_STATE_LEVEL_EDITOR:
466         return game_event_level_editor(game, event);
467
468     case GAME_STATE_CREDITS: {
469         switch (event->type) {
470         case SDL_KEYDOWN: {
471             if (event->key.keysym.sym == SDLK_ESCAPE) {
472                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
473                 return 0;
474             }
475         } break;
476         }
477
478         return 0;
479     } break;
480
481     case GAME_STATE_SETTINGS: {
482         switch (event->type) {
483         case SDL_KEYDOWN: {
484             if (event->key.keysym.sym == SDLK_ESCAPE) {
485                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
486                 return 0;
487             }
488         } break;
489         }
490
491         settings_event(&game->settings, &game->camera, event);
492         return 0;
493     } break;
494
495     case GAME_STATE_QUIT:
496         return 0;
497     }
498
499     return -1;
500 }
501
502 // TODO(#1145): get rid of keyboard_state and introduce *_joystick methods
503 //
504 // keyboard_state is a global var and can be check anywhere anyway
505 int game_input(Game *game,
506                const Uint8 *const keyboard_state,
507                SDL_Joystick *the_stick_of_joy)
508 {
509     trace_assert(game);
510     trace_assert(keyboard_state);
511
512     if (game->console_enabled) {
513         return 0;
514     }
515
516     switch (game->state) {
517     case GAME_STATE_SETTINGS:
518     case GAME_STATE_CREDITS:
519     case GAME_STATE_QUIT:
520     case GAME_STATE_LEVEL_EDITOR:
521         return 0;
522
523     case GAME_STATE_LEVEL:
524         return level_input(game->level, keyboard_state, the_stick_of_joy);
525
526     case GAME_STATE_LEVEL_PICKER:
527         return level_picker_input(&game->level_picker, keyboard_state, the_stick_of_joy);
528     }
529
530     return 0;
531 }
532
533 int game_over_check(const Game *game)
534 {
535     return game->state == GAME_STATE_QUIT;
536 }
537
538 int game_load_level(Game *game, const char *level_filename)
539 {
540     trace_assert(game);
541     trace_assert(level_filename);
542
543     if (game->level_editor == NULL) {
544         game->level_editor = PUSH_LT(
545             game->lt,
546             create_level_editor_from_file(level_filename, &game->cursor),
547             destroy_level_editor);
548     } else {
549         game->level_editor = RESET_LT(
550             game->lt,
551             game->level_editor,
552             create_level_editor_from_file(level_filename, &game->cursor));
553     }
554
555     if (game->level_editor == NULL) {
556         return -1;
557     }
558
559     if (game->level == NULL) {
560         game->level = PUSH_LT(
561             game->lt,
562             create_level_from_level_editor(
563                 game->level_editor),
564             destroy_level);
565     } else {
566         game->level = RESET_LT(
567             game->lt,
568             game->level,
569             create_level_from_level_editor(
570                 game->level_editor));
571     }
572
573     if (game->level == NULL) {
574         return -1;
575     }
576
577     game_switch_state(game, GAME_STATE_LEVEL);
578
579     return 0;
580 }