]> git.lizzy.rs Git - nothing.git/blob - src/game.c
(#1189) Reset cursor style on switching game state
[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: %s\n", SDL_GetError());
109         }
110     }
111
112
113     game->console = PUSH_LT(
114         lt,
115         create_console(game),
116         destroy_console);
117     if (game->console == NULL) {
118         RETURN_LT(lt, NULL);
119     }
120     game->console_enabled = 0;
121
122     game_switch_state(game, GAME_STATE_LEVEL_PICKER);
123
124     return game;
125 }
126
127 void destroy_game(Game *game)
128 {
129     trace_assert(game);
130     destroy_level_picker(game->level_picker);
131     RETURN_LT0(game->lt);
132 }
133
134 int game_render(const Game *game)
135 {
136     trace_assert(game);
137
138     switch(game->state) {
139     case GAME_STATE_LEVEL: {
140         if (level_render(game->level, &game->camera) < 0) {
141             return -1;
142         }
143     } break;
144
145     case GAME_STATE_LEVEL_PICKER: {
146         if (level_picker_render(&game->level_picker, &game->camera) < 0) {
147             return -1;
148         }
149     } break;
150
151     case GAME_STATE_LEVEL_EDITOR: {
152         if (level_editor_render(game->level_editor, &game->camera) < 0) {
153             return -1;
154         }
155     } break;
156
157     case GAME_STATE_CREDITS: {
158         if (credits_render(game->credits, &game->camera) < 0) {
159             return -1;
160         }
161     } break;
162
163     case GAME_STATE_SETTINGS: {
164         settings_render(&game->settings, &game->camera);
165     } break;
166
167     case GAME_STATE_QUIT: break;
168     }
169
170     if (game->console_enabled) {
171         if (console_render(game->console, &game->camera) < 0) {
172             return -1;
173         }
174     }
175
176     if (cursor_render(&game->cursor, game->renderer) < 0) {
177         return -1;
178     }
179
180     return 0;
181 }
182
183 int game_sound(Game *game)
184 {
185     switch (game->state) {
186     case GAME_STATE_LEVEL:
187         return level_sound(game->level, game->sound_samples);
188     case GAME_STATE_LEVEL_EDITOR:
189         level_editor_sound(game->level_editor, game->sound_samples);
190         return 0;
191     case GAME_STATE_LEVEL_PICKER:
192     case GAME_STATE_CREDITS:
193     case GAME_STATE_SETTINGS:
194     case GAME_STATE_QUIT:
195         return 0;
196     }
197
198     return 0;
199 }
200
201 int game_update(Game *game, float delta_time)
202 {
203     trace_assert(game);
204     trace_assert(delta_time > 0.0f);
205
206     // TODO(#1218): effective scale recalculation should be probably done only when the size of the window is changed
207     SDL_Rect view_port;
208     SDL_RenderGetViewport(game->camera.renderer, &view_port);
209     game->camera.effective_scale = effective_scale(&view_port);
210
211     if (game->console_enabled) {
212         if (console_update(game->console, delta_time) < 0) {
213             return -1;
214         }
215     }
216
217     switch (game->state) {
218     case GAME_STATE_LEVEL: {
219         if (level_update(game->level, delta_time) < 0) {
220             return -1;
221         }
222
223         if (level_enter_camera_event(game->level, &game->camera) < 0) {
224             return -1;
225         }
226
227     } break;
228
229     case GAME_STATE_LEVEL_PICKER: {
230         if (level_picker_update(&game->level_picker, &game->camera, delta_time) < 0) {
231             return -1;
232         }
233
234         if (level_picker_enter_camera_event(&game->level_picker, &game->camera) < 0) {
235             return -1;
236         }
237
238         const char *level_filename = level_picker_selected_level(&game->level_picker);
239
240         if (level_filename != NULL) {
241             if (game_load_level(game, level_filename) < 0) {
242                 return -1;
243             }
244         }
245     } break;
246
247     case GAME_STATE_LEVEL_EDITOR: {
248         if (level_editor_focus_camera(
249                 game->level_editor,
250                 &game->camera) < 0) {
251             return -1;
252         }
253
254         level_editor_update(game->level_editor, delta_time);
255     } break;
256
257     case GAME_STATE_CREDITS: {
258         if (credits_update(game->credits, &game->camera, delta_time) < 0) {
259             return -1;
260         }
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                 level_disable_pause_mode(
298                     game->level,
299                     &game->camera,
300                     game->sound_samples);
301                 camera_disable_debug_mode(&game->camera);
302             } break;
303
304             case SDLK_TAB: {
305                 game_switch_state(game, GAME_STATE_LEVEL_EDITOR);
306             } break;
307             }
308         } break;
309         }
310     }
311
312     return level_event(game->level, event, &game->camera, game->sound_samples);
313 }
314
315 static int game_event_level_picker(Game *game, const SDL_Event *event)
316 {
317     trace_assert(game);
318     trace_assert(event);
319
320     switch (event->type) {
321     case SDL_KEYDOWN: {
322         switch(event->key.keysym.sym) {
323         case SDLK_n: {
324             if (game->level_editor == NULL) {
325                 game->level_editor = PUSH_LT(
326                     game->lt,
327                     create_level_editor(&game->cursor),
328                     destroy_level_editor);
329             } else {
330                 game->level_editor = RESET_LT(
331                     game->lt,
332                     game->level_editor,
333                     create_level_editor(&game->cursor));
334             }
335
336             if (game->level_editor == NULL) {
337                 return -1;
338             }
339
340             if (game->level == NULL) {
341                 game->level = PUSH_LT(
342                     game->lt,
343                     create_level_from_level_editor(
344                         game->level_editor),
345                     destroy_level);
346             } else {
347                 game->level = RESET_LT(
348                     game->lt,
349                     game->level,
350                     create_level_from_level_editor(
351                         game->level_editor));
352             }
353
354             if (game->level == NULL) {
355                 return -1;
356             }
357
358             game_switch_state(game, GAME_STATE_LEVEL);
359         } break;
360
361         case SDLK_i: {
362             game_switch_state(game, GAME_STATE_CREDITS);
363         } break;
364
365         case SDLK_s: {
366             game_switch_state(game, GAME_STATE_SETTINGS);
367         } break;
368         }
369     } break;
370     }
371
372     return level_picker_event(&game->level_picker, event);
373 }
374
375 static int game_event_level_editor(Game *game, const SDL_Event *event)
376 {
377     trace_assert(game);
378     trace_assert(event);
379
380     switch (event->type) {
381     case SDL_KEYDOWN: {
382         switch (event->key.keysym.sym) {
383         case SDLK_TAB: {
384             game->level = RESET_LT(
385                 game->lt,
386                 game->level,
387                 create_level_from_level_editor(
388                     game->level_editor));
389             if (game->level == NULL) {
390                 return -1;
391             }
392             game_switch_state(game, GAME_STATE_LEVEL);
393         } break;
394         }
395     } break;
396     }
397
398     return level_editor_event(game->level_editor, event, &game->camera);
399 }
400
401 int game_event(Game *game, const SDL_Event *event)
402 {
403     trace_assert(game);
404     trace_assert(event);
405
406     // Global event handling
407     switch (event->type) {
408     case SDL_QUIT: {
409         game_switch_state(game, GAME_STATE_QUIT);
410         return 0;
411     } break;
412
413     case SDL_KEYDOWN: {
414         if (event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) {
415             game_switch_state(game, GAME_STATE_QUIT);
416             return 0;
417         }
418     } break;
419     }
420
421     // Console event handling
422     if (game->console_enabled) {
423         switch (event->type) {
424         case SDL_KEYDOWN:
425             switch (event->key.keysym.sym) {
426             case SDLK_ESCAPE:
427                 SDL_StopTextInput();
428                 game->console_enabled = 0;
429                 return 0;
430             default: {}
431             }
432
433         default: {}
434         }
435
436         return console_handle_event(game->console, event);
437     } else {
438         switch (event->type) {
439         case SDL_KEYUP: {
440             switch (event->key.keysym.sym) {
441             case SDLK_BACKQUOTE:
442             case SDLK_c: {
443                 if (event->key.keysym.mod == KMOD_NONE || event->key.keysym.mod == KMOD_NUM) {
444                     SDL_StartTextInput();
445                     game->console_enabled = 1;
446                     console_slide_down(game->console);
447                 }
448             } break;
449             }
450         } break;
451         }
452     }
453
454     // State event handling
455     switch (game->state) {
456     case GAME_STATE_LEVEL:
457         return game_event_running(game, event);
458
459     case GAME_STATE_LEVEL_PICKER:
460         return game_event_level_picker(game, event);
461
462     case GAME_STATE_LEVEL_EDITOR:
463         return game_event_level_editor(game, event);
464
465     case GAME_STATE_CREDITS: {
466         switch (event->type) {
467         case SDL_KEYDOWN: {
468             if (event->key.keysym.sym == SDLK_ESCAPE) {
469                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
470                 return 0;
471             }
472         } break;
473         }
474
475         return 0;
476     } break;
477
478     case GAME_STATE_SETTINGS: {
479         switch (event->type) {
480         case SDL_KEYDOWN: {
481             if (event->key.keysym.sym == SDLK_ESCAPE) {
482                 game_switch_state(game, GAME_STATE_LEVEL_PICKER);
483                 return 0;
484             }
485         } break;
486         }
487
488         settings_event(&game->settings, &game->camera, event);
489         return 0;
490     } break;
491
492     case GAME_STATE_QUIT:
493         return 0;
494     }
495
496     return -1;
497 }
498
499 // TODO(#1145): get rid of keyboard_state and introduce *_joystick methods
500 //
501 // keyboard_state is a global var and can be check anywhere anyway
502 int game_input(Game *game,
503                const Uint8 *const keyboard_state,
504                SDL_Joystick *the_stick_of_joy)
505 {
506     trace_assert(game);
507     trace_assert(keyboard_state);
508
509     if (game->console_enabled) {
510         return 0;
511     }
512
513     switch (game->state) {
514     case GAME_STATE_SETTINGS:
515     case GAME_STATE_CREDITS:
516     case GAME_STATE_QUIT:
517     case GAME_STATE_LEVEL_EDITOR:
518         return 0;
519
520     case GAME_STATE_LEVEL:
521         return level_input(game->level, keyboard_state, the_stick_of_joy);
522
523     case GAME_STATE_LEVEL_PICKER:
524         return level_picker_input(&game->level_picker, keyboard_state, the_stick_of_joy);
525     }
526
527     return 0;
528 }
529
530 int game_over_check(const Game *game)
531 {
532     return game->state == GAME_STATE_QUIT;
533 }
534
535 int game_load_level(Game *game, const char *level_filename)
536 {
537     trace_assert(game);
538     trace_assert(level_filename);
539
540     if (game->level_editor == NULL) {
541         game->level_editor = PUSH_LT(
542             game->lt,
543             create_level_editor_from_file(level_filename, &game->cursor),
544             destroy_level_editor);
545     } else {
546         game->level_editor = RESET_LT(
547             game->lt,
548             game->level_editor,
549             create_level_editor_from_file(level_filename, &game->cursor));
550     }
551
552     if (game->level_editor == NULL) {
553         return -1;
554     }
555
556     if (game->level == NULL) {
557         game->level = PUSH_LT(
558             game->lt,
559             create_level_from_level_editor(
560                 game->level_editor),
561             destroy_level);
562     } else {
563         game->level = RESET_LT(
564             game->lt,
565             game->level,
566             create_level_from_level_editor(
567                 game->level_editor));
568     }
569
570     if (game->level == NULL) {
571         return -1;
572     }
573
574     game_switch_state(game, GAME_STATE_LEVEL);
575
576     return 0;
577 }