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