]> git.lizzy.rs Git - nothing.git/blob - src/game.c
2dd84e4c46cf364a7269cff4c04a57b7cb945400
[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             game_switch_state(game, GAME_STATE_QUIT);
418             return 0;
419         }
420     } break;
421     }
422
423     // Console event handling
424     if (game->console_enabled) {
425         switch (event->type) {
426         case SDL_KEYDOWN:
427             switch (event->key.keysym.sym) {
428             case SDLK_ESCAPE:
429                 SDL_StopTextInput();
430                 game->console_enabled = 0;
431                 return 0;
432             default: {}
433             }
434
435         default: {}
436         }
437
438         return console_handle_event(game->console, event);
439     } else {
440         switch (event->type) {
441         case SDL_KEYUP: {
442             switch (event->key.keysym.sym) {
443             case SDLK_BACKQUOTE:
444             case SDLK_c: {
445                 if (event->key.keysym.mod == KMOD_NONE || event->key.keysym.mod == KMOD_NUM) {
446                     SDL_StartTextInput();
447                     game->console_enabled = 1;
448                     console_slide_down(game->console);
449                 }
450             } break;
451             }
452         } break;
453         }
454     }
455
456     // State event handling
457     switch (game->state) {
458     case GAME_STATE_LEVEL:
459         return game_event_running(game, event);
460
461     case GAME_STATE_LEVEL_PICKER:
462         return game_event_level_picker(game, event);
463
464     case GAME_STATE_LEVEL_EDITOR:
465         return game_event_level_editor(game, event);
466
467     case GAME_STATE_CREDITS: {
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         return 0;
478     } break;
479
480     case GAME_STATE_SETTINGS: {
481         switch (event->type) {
482         case SDL_KEYDOWN: {
483             if (event->key.keysym.sym == SDLK_ESCAPE) {
484                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
485                 return 0;
486             }
487         } break;
488         }
489
490         settings_event(&game->settings, &game->camera, event);
491         return 0;
492     } break;
493
494     case GAME_STATE_QUIT:
495         return 0;
496     }
497
498     return -1;
499 }
500
501 // TODO(#1145): get rid of keyboard_state and introduce *_joystick methods
502 //
503 // keyboard_state is a global var and can be check anywhere anyway
504 int game_input(Game *game,
505                const Uint8 *const keyboard_state,
506                SDL_Joystick *the_stick_of_joy)
507 {
508     trace_assert(game);
509     trace_assert(keyboard_state);
510
511     if (game->console_enabled) {
512         return 0;
513     }
514
515     switch (game->state) {
516     case GAME_STATE_SETTINGS:
517     case GAME_STATE_CREDITS:
518     case GAME_STATE_QUIT:
519     case GAME_STATE_LEVEL_EDITOR:
520         return 0;
521
522     case GAME_STATE_LEVEL:
523         return level_input(game->level, keyboard_state, the_stick_of_joy);
524
525     case GAME_STATE_LEVEL_PICKER:
526         return level_picker_input(&game->level_picker, keyboard_state, the_stick_of_joy);
527     }
528
529     return 0;
530 }
531
532 int game_over_check(const Game *game)
533 {
534     return game->state == GAME_STATE_QUIT;
535 }
536
537 int game_load_level(Game *game, const char *level_filename)
538 {
539     trace_assert(game);
540     trace_assert(level_filename);
541
542     if (game->level_editor == NULL) {
543         game->level_editor = PUSH_LT(
544             game->lt,
545             create_level_editor_from_file(level_filename, &game->cursor),
546             destroy_level_editor);
547     } else {
548         game->level_editor = RESET_LT(
549             game->lt,
550             game->level_editor,
551             create_level_editor_from_file(level_filename, &game->cursor));
552     }
553
554     if (game->level_editor == NULL) {
555         return -1;
556     }
557
558     if (game->level == NULL) {
559         game->level = PUSH_LT(
560             game->lt,
561             create_level_from_level_editor(
562                 game->level_editor),
563             destroy_level);
564     } else {
565         game->level = RESET_LT(
566             game->lt,
567             game->level,
568             create_level_from_level_editor(
569                 game->level_editor));
570     }
571
572     if (game->level == NULL) {
573         return -1;
574     }
575
576     game_switch_state(game, GAME_STATE_LEVEL);
577
578     return 0;
579 }