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