]> git.lizzy.rs Git - nothing.git/blob - src/game.c
d009528332486d6850404fb6abb5707874d11ecc
[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 typedef struct Game {
23     Lt *lt;
24
25     Game_state state;
26     Sprite_font font;
27     Memory tmpmem;
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     game->tmpmem.capacity = TMPMEM_CAPACITY;
71     game->tmpmem.buffer = malloc(TMPMEM_CAPACITY);
72     trace_assert(game->tmpmem.buffer);
73
74     level_picker_populate(&game->level_picker, level_folder);
75
76     game->credits = create_credits();
77
78     game->sound_samples = PUSH_LT(
79         lt,
80         create_sound_samples(
81             sound_sample_files,
82             sound_sample_files_count),
83         destroy_sound_samples);
84     if (game->sound_samples == NULL) {
85         RETURN_LT(lt, NULL);
86     }
87
88     game->settings = create_settings();
89
90     game->renderer = renderer;
91
92     for (Cursor_Style style = 0; style < CURSOR_STYLE_N; ++style) {
93         game->cursor.texs[style] = PUSH_LT(
94             lt,
95             texture_from_bmp(cursor_style_tex_files[style], renderer),
96             SDL_DestroyTexture);
97         if (SDL_SetTextureBlendMode(
98                 game->cursor.texs[style],
99                 SDL_ComposeCustomBlendMode(
100                     SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
101                     SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
102                     SDL_BLENDOPERATION_ADD,
103                     SDL_BLENDFACTOR_ONE,
104                     SDL_BLENDFACTOR_ZERO,
105                     SDL_BLENDOPERATION_ADD)) < 0) {
106             log_warn("SDL error while setting blending mode for `%s': %s\n",
107                      cursor_style_tex_files[style],
108                      SDL_GetError());
109         }
110     }
111
112     create_level_editor(&game->level_editor, &game->cursor);
113
114     game->console = PUSH_LT(
115         lt,
116         create_console(game),
117         destroy_console);
118     if (game->console == NULL) {
119         RETURN_LT(lt, NULL);
120     }
121     game->console_enabled = 0;
122
123     game_switch_state(game, GAME_STATE_LEVEL_PICKER);
124
125     return game;
126 }
127
128 void destroy_game(Game *game)
129 {
130     trace_assert(game);
131     destroy_level_picker(game->level_picker);
132     free(game->tmpmem.buffer);
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             level_editor_clean(&game->level_editor);
327
328             if (game->level == NULL) {
329                 game->level = PUSH_LT(
330                     game->lt,
331                     create_level_from_level_editor(
332                         &game->level_editor),
333                     destroy_level);
334             } else {
335                 game->level = RESET_LT(
336                     game->lt,
337                     game->level,
338                     create_level_from_level_editor(
339                         &game->level_editor));
340             }
341
342             if (game->level == NULL) {
343                 return -1;
344             }
345
346             game_switch_state(game, GAME_STATE_LEVEL);
347         } break;
348
349         case SDLK_i: {
350             game_switch_state(game, GAME_STATE_CREDITS);
351         } break;
352
353         case SDLK_s: {
354             game_switch_state(game, GAME_STATE_SETTINGS);
355         } break;
356         }
357     } break;
358     }
359
360     return level_picker_event(&game->level_picker, event);
361 }
362
363 static int game_event_level_editor(Game *game, const SDL_Event *event)
364 {
365     trace_assert(game);
366     trace_assert(event);
367
368     switch (event->type) {
369     case SDL_KEYDOWN: {
370         switch (event->key.keysym.sym) {
371         case SDLK_TAB: {
372             game->level = RESET_LT(
373                 game->lt,
374                 game->level,
375                 create_level_from_level_editor(
376                     &game->level_editor));
377             if (game->level == NULL) {
378                 return -1;
379             }
380             game_switch_state(game, GAME_STATE_LEVEL);
381         } break;
382         }
383     } break;
384     }
385
386     return level_editor_event(&game->level_editor, event, &game->camera);
387 }
388
389 int game_event(Game *game, const SDL_Event *event)
390 {
391     trace_assert(game);
392     trace_assert(event);
393
394     // Global event handling
395     switch (event->type) {
396     case SDL_QUIT: {
397         game_switch_state(game, GAME_STATE_QUIT);
398         return 0;
399     } break;
400
401     case SDL_KEYDOWN: {
402         if ((event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) ||
403             (event->key.keysym.sym == SDLK_F4 && event->key.keysym.mod & KMOD_ALT)) {
404             game_switch_state(game, GAME_STATE_QUIT);
405             return 0;
406         }
407     } break;
408     }
409
410     // Console event handling
411     if (game->console_enabled) {
412         switch (event->type) {
413         case SDL_KEYDOWN:
414             switch (event->key.keysym.sym) {
415             case SDLK_ESCAPE:
416                 SDL_StopTextInput();
417                 game->console_enabled = 0;
418                 return 0;
419             default: {}
420             }
421
422         default: {}
423         }
424
425         return console_handle_event(game->console, event);
426     } else {
427         switch (event->type) {
428         case SDL_KEYUP: {
429             switch (event->key.keysym.sym) {
430             case SDLK_BACKQUOTE:
431             case SDLK_c: {
432                 if (event->key.keysym.mod == KMOD_NONE || event->key.keysym.mod == KMOD_NUM) {
433                     SDL_StartTextInput();
434                     game->console_enabled = 1;
435                     console_slide_down(game->console);
436                 }
437             } break;
438             }
439         } break;
440         }
441     }
442
443     // State event handling
444     switch (game->state) {
445     case GAME_STATE_LEVEL:
446         return game_event_running(game, event);
447
448     case GAME_STATE_LEVEL_PICKER:
449         return game_event_level_picker(game, event);
450
451     case GAME_STATE_LEVEL_EDITOR:
452         return game_event_level_editor(game, event);
453
454     case GAME_STATE_CREDITS: {
455         switch (event->type) {
456         case SDL_KEYDOWN: {
457             if (event->key.keysym.sym == SDLK_ESCAPE) {
458                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
459                 return 0;
460             }
461         } break;
462         }
463
464         return 0;
465     } break;
466
467     case GAME_STATE_SETTINGS: {
468         switch (event->type) {
469         case SDL_KEYDOWN: {
470             if (event->key.keysym.sym == SDLK_ESCAPE) {
471                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
472                 return 0;
473             }
474         } break;
475         }
476
477         settings_event(&game->settings, &game->camera, event);
478         return 0;
479     } break;
480
481     case GAME_STATE_QUIT:
482         return 0;
483     }
484
485     return -1;
486 }
487
488 // TODO(#1145): get rid of keyboard_state and introduce *_joystick methods
489 //
490 // keyboard_state is a global var and can be check anywhere anyway
491 int game_input(Game *game,
492                const Uint8 *const keyboard_state,
493                SDL_Joystick *the_stick_of_joy)
494 {
495     trace_assert(game);
496     trace_assert(keyboard_state);
497
498     if (game->console_enabled) {
499         return 0;
500     }
501
502     switch (game->state) {
503     case GAME_STATE_SETTINGS:
504     case GAME_STATE_CREDITS:
505     case GAME_STATE_QUIT:
506     case GAME_STATE_LEVEL_EDITOR:
507         return 0;
508
509     case GAME_STATE_LEVEL:
510         return level_input(game->level, keyboard_state, the_stick_of_joy);
511
512     case GAME_STATE_LEVEL_PICKER:
513         return level_picker_input(&game->level_picker, keyboard_state, the_stick_of_joy);
514     }
515
516     return 0;
517 }
518
519 int game_over_check(const Game *game)
520 {
521     return game->state == GAME_STATE_QUIT;
522 }
523
524 int game_load_level(Game *game, const char *level_filename)
525 {
526     trace_assert(game);
527     trace_assert(level_filename);
528
529     memory_clean(&game->tmpmem);
530     level_editor_load_from_file(&game->level_editor, &game->tmpmem, level_filename);
531
532     if (game->level == NULL) {
533         game->level = PUSH_LT(
534             game->lt,
535             create_level_from_level_editor(
536                 &game->level_editor),
537             destroy_level);
538     } else {
539         game->level = RESET_LT(
540             game->lt,
541             game->level,
542             create_level_from_level_editor(
543                 &game->level_editor));
544     }
545
546     if (game->level == NULL) {
547         return -1;
548     }
549
550     game_switch_state(game, GAME_STATE_LEVEL);
551
552     return 0;
553 }