]> git.lizzy.rs Git - nothing.git/blob - src/game.c
Merge pull request #1065 from tsoding/1059
[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 "ebisp/builtins.h"
16 #include "broadcast.h"
17 #include "sdl/texture.h"
18 #include "game/level/level_editor/background_layer.h"
19 #include "game/level/level_editor.h"
20
21 static int game_render_cursor(const Game *game);
22
23 typedef enum Game_state {
24     GAME_STATE_LEVEL = 0,
25     GAME_STATE_LEVEL_PICKER,
26     GAME_STATE_LEVEL_EDITOR,
27     GAME_STATE_QUIT
28 } Game_state;
29
30 typedef struct Game {
31     Lt *lt;
32
33     Game_state state;
34     Broadcast *broadcast;
35     Sprite_font *font;
36     LevelPicker *level_picker;
37     LevelEditor *level_editor;
38     Level *level;
39     Sound_samples *sound_samples;
40     Camera camera;
41     SDL_Renderer *renderer;
42     SDL_Texture *texture_cursor;
43     int cursor_x;
44     int cursor_y;
45 } Game;
46
47 static
48 void game_switch_state(Game *game, Game_state state)
49 {
50     game->camera = create_camera(game->renderer, game->font);
51     game->state = state;
52 }
53
54 Game *create_game(const char *level_folder,
55                   const char *sound_sample_files[],
56                   size_t sound_sample_files_count,
57                   SDL_Renderer *renderer)
58 {
59     trace_assert(level_folder);
60
61     Lt *lt = create_lt();
62
63     Game *game = PUSH_LT(lt, nth_calloc(1, sizeof(Game)), free);
64     if (game == NULL) {
65         RETURN_LT(lt, NULL);
66     }
67     game->lt = lt;
68
69
70     game->broadcast = PUSH_LT(
71         lt,
72         create_broadcast(game),
73         destroy_broadcast);
74     if (game->broadcast == NULL) {
75         RETURN_LT(lt, NULL);
76     }
77
78     game->font = PUSH_LT(
79         lt,
80         create_sprite_font_from_file(
81             "images/charmap-oldschool.bmp",
82             renderer),
83         destroy_sprite_font);
84     if (game->font == NULL) {
85         RETURN_LT(lt, NULL);
86     }
87
88     game->level_picker = PUSH_LT(
89         lt,
90         create_level_picker(
91             game->font,
92             level_folder),
93         destroy_level_picker);
94     if (game->level_picker == NULL) {
95         RETURN_LT(lt, NULL);
96     }
97
98     game->sound_samples = PUSH_LT(
99         lt,
100         create_sound_samples(
101             sound_sample_files,
102             sound_sample_files_count),
103         destroy_sound_samples);
104     if (game->sound_samples == NULL) {
105         RETURN_LT(lt, NULL);
106     }
107
108     game->renderer = renderer;
109     game->texture_cursor = PUSH_LT(
110         lt,
111         texture_from_bmp("images/cursor.bmp", renderer),
112         SDL_DestroyTexture);
113     if (SDL_SetTextureBlendMode(
114             game->texture_cursor,
115             SDL_ComposeCustomBlendMode(
116                 SDL_BLENDFACTOR_ONE_MINUS_DST_COLOR,
117                 SDL_BLENDFACTOR_ONE_MINUS_SRC_COLOR,
118                 SDL_BLENDOPERATION_ADD,
119                 SDL_BLENDFACTOR_ONE,
120                 SDL_BLENDFACTOR_ZERO,
121                 SDL_BLENDOPERATION_ADD)) < 0) {
122         log_warn("SDL error: %s\n", SDL_GetError());
123     }
124     game->cursor_x = 0;
125     game->cursor_y = 0;
126
127     game_switch_state(game, GAME_STATE_LEVEL_PICKER);
128
129     return game;
130 }
131
132 void destroy_game(Game *game)
133 {
134     trace_assert(game);
135     RETURN_LT0(game->lt);
136 }
137
138 int game_render(const Game *game)
139 {
140     trace_assert(game);
141
142     switch(game->state) {
143     case GAME_STATE_LEVEL: {
144         if (level_render(game->level, &game->camera) < 0) {
145             return -1;
146         }
147     } break;
148
149     case GAME_STATE_LEVEL_PICKER: {
150         if (level_picker_render(game->level_picker, &game->camera) < 0) {
151             return -1;
152         }
153
154         if (game_render_cursor(game) < 0) {
155             return -1;
156         }
157     } break;
158
159     case GAME_STATE_LEVEL_EDITOR: {
160         if (level_editor_render(game->level_editor, &game->camera) < 0) {
161             return -1;
162         }
163
164         if (game_render_cursor(game) < 0) {
165             return -1;
166         }
167     } break;
168
169     case GAME_STATE_QUIT: break;
170     }
171
172     return 0;
173 }
174
175 int game_sound(Game *game)
176 {
177     switch (game->state) {
178     case GAME_STATE_LEVEL:
179         return level_sound(game->level, game->sound_samples);
180     case GAME_STATE_LEVEL_PICKER:
181     case GAME_STATE_LEVEL_EDITOR:
182     case GAME_STATE_QUIT:
183         return 0;
184     }
185
186     return 0;
187 }
188
189 int game_update(Game *game, float delta_time)
190 {
191     trace_assert(game);
192     trace_assert(delta_time > 0.0f);
193
194     switch (game->state) {
195     case GAME_STATE_LEVEL: {
196         if (level_update(game->level, delta_time) < 0) {
197             return -1;
198         }
199
200         if (level_enter_camera_event(game->level, &game->camera) < 0) {
201             return -1;
202         }
203
204     } break;
205
206     case GAME_STATE_LEVEL_PICKER: {
207         if (level_picker_update(game->level_picker, delta_time) < 0) {
208             return -1;
209         }
210
211         if (level_picker_enter_camera_event(game->level_picker, &game->camera) < 0) {
212             return -1;
213         }
214
215         const char *level_filename = level_picker_selected_level(game->level_picker);
216
217         if (level_filename != NULL) {
218             if (game->level_editor == NULL) {
219                 game->level_editor = PUSH_LT(
220                     game->lt,
221                     create_level_editor_from_file(level_filename),
222                     destroy_level_editor);
223             } else {
224                 game->level_editor = RESET_LT(
225                     game->lt,
226                     game->level_editor,
227                     create_level_editor_from_file(level_filename));
228             }
229
230             if (game->level_editor == NULL) {
231                 return -1;
232             }
233
234             if (game->level == NULL) {
235                 game->level = PUSH_LT(
236                     game->lt,
237                     create_level_from_level_editor(
238                         game->level_editor,
239                         game->broadcast),
240                     destroy_level);
241             } else {
242                 game->level = RESET_LT(
243                     game->lt,
244                     game->level,
245                     create_level_from_level_editor(
246                         game->level_editor,
247                         game->broadcast));
248             }
249
250             if (game->level == NULL) {
251                 return -1;
252             }
253
254             game_switch_state(game, GAME_STATE_LEVEL);
255         }
256
257     } break;
258
259     case GAME_STATE_LEVEL_EDITOR: {
260         if (level_editor_focus_camera(
261                 game->level_editor,
262                 &game->camera) < 0) {
263             return -1;
264         }
265
266         level_editor_update(game->level_editor, delta_time);
267     } break;
268
269     case GAME_STATE_QUIT:
270         break;
271     }
272
273     return 0;
274 }
275
276 static int game_event_running(Game *game, const SDL_Event *event)
277 {
278     trace_assert(game);
279     trace_assert(event);
280
281     if (!SDL_IsTextInputActive()) {
282         switch (event->type) {
283         case SDL_KEYDOWN: {
284             switch (event->key.keysym.sym) {
285             case SDLK_r: {
286                 game->level = RESET_LT(
287                     game->lt,
288                     game->level,
289                     create_level_from_level_editor(
290                         game->level_editor,
291                         game->broadcast));
292                 if (game->level == NULL) {
293                     game_switch_state(game, GAME_STATE_QUIT);
294                     return -1;
295                 }
296
297                 camera_disable_debug_mode(&game->camera);
298             } break;
299
300             case SDLK_TAB: {
301                 game_switch_state(game, GAME_STATE_LEVEL_EDITOR);
302             } break;
303             }
304         } break;
305         }
306     }
307
308     return level_event(game->level, event, &game->camera, game->sound_samples);
309 }
310
311 static int game_event_level_picker(Game *game, const SDL_Event *event)
312 {
313     trace_assert(game);
314     trace_assert(event);
315
316     switch (event->type) {
317     case SDL_KEYDOWN: {
318         switch(event->key.keysym.sym) {
319         case SDLK_n: {
320             if (game->level_editor == NULL) {
321                 game->level_editor = PUSH_LT(
322                     game->lt,
323                     create_level_editor(),
324                     destroy_level_editor);
325             } else {
326                 game->level_editor = RESET_LT(
327                     game->lt,
328                     game->level_editor,
329                     create_level_editor());
330             }
331
332             if (game->level_editor == NULL) {
333                 return -1;
334             }
335
336             if (game->level == NULL) {
337                 game->level = PUSH_LT(
338                     game->lt,
339                     create_level_from_level_editor(
340                         game->level_editor,
341                         game->broadcast),
342                     destroy_level);
343             } else {
344                 game->level = RESET_LT(
345                     game->lt,
346                     game->level,
347                     create_level_from_level_editor(
348                         game->level_editor,
349                         game->broadcast));
350             }
351
352             if (game->level == NULL) {
353                 return -1;
354             }
355
356             game_switch_state(game, GAME_STATE_LEVEL);
357         } break;
358         }
359     } break;
360     }
361
362     return level_picker_event(game->level_picker, event, &game->camera);
363 }
364
365 static int game_event_level_editor(Game *game, const SDL_Event *event)
366 {
367     trace_assert(game);
368     trace_assert(event);
369
370     switch (event->type) {
371     case SDL_KEYDOWN: {
372         switch (event->key.keysym.sym) {
373         case SDLK_TAB: {
374             game->level = RESET_LT(
375                 game->lt,
376                 game->level,
377                 create_level_from_level_editor(
378                     game->level_editor,
379                     game->broadcast));
380             if (game->level == NULL) {
381                 return -1;
382             }
383             game_switch_state(game, GAME_STATE_LEVEL);
384         } break;
385         }
386     } break;
387     }
388
389     return level_editor_event(game->level_editor, event, &game->camera);
390 }
391
392 int game_event(Game *game, const SDL_Event *event)
393 {
394     trace_assert(game);
395     trace_assert(event);
396
397     switch (event->type) {
398     case SDL_QUIT: {
399         game_switch_state(game, GAME_STATE_QUIT);
400         return 0;
401     } break;
402
403     case SDL_MOUSEMOTION: {
404         game->cursor_x = event->motion.x;
405         game->cursor_y = event->motion.y;
406     } break;
407
408     case SDL_KEYDOWN: {
409         if (event->key.keysym.sym == SDLK_q && event->key.keysym.mod & KMOD_CTRL) {
410             game_switch_state(game, GAME_STATE_QUIT);
411             return 0;
412         }
413     } break;
414     }
415
416     switch (game->state) {
417     case GAME_STATE_LEVEL:
418         return game_event_running(game, event);
419
420     case GAME_STATE_LEVEL_PICKER:
421         return game_event_level_picker(game, event);
422
423     case GAME_STATE_LEVEL_EDITOR:
424         return game_event_level_editor(game, event);
425
426     case GAME_STATE_QUIT:
427         return 0;
428     }
429
430     return -1;
431 }
432
433
434 int game_input(Game *game,
435                const Uint8 *const keyboard_state,
436                SDL_Joystick *the_stick_of_joy)
437 {
438     trace_assert(game);
439     trace_assert(keyboard_state);
440
441     switch (game->state) {
442     case GAME_STATE_QUIT:
443     case GAME_STATE_LEVEL_EDITOR:
444         return 0;
445
446     case GAME_STATE_LEVEL:
447         return level_input(game->level, keyboard_state, the_stick_of_joy);
448
449     case GAME_STATE_LEVEL_PICKER:
450         return level_picker_input(game->level_picker, keyboard_state, the_stick_of_joy);
451     }
452
453     return 0;
454 }
455
456 int game_over_check(const Game *game)
457 {
458     return game->state == GAME_STATE_QUIT;
459 }
460
461 struct EvalResult
462 game_send(Game *game, Gc *gc, struct Scope *scope,
463           struct Expr path)
464 {
465     trace_assert(game);
466     trace_assert(gc);
467     trace_assert(scope);
468
469     const char *target = NULL;
470     struct Expr rest = void_expr();
471     struct EvalResult res = match_list(gc, "q*", path, &target, &rest);
472     if (res.is_error) {
473         return res;
474     }
475
476     if (strcmp(target, "level") == 0) {
477         return level_send(game->level, gc, scope, rest);
478     } else if (strcmp(target, "menu") == 0) {
479         level_picker_clean_selection(game->level_picker);
480         game_switch_state(game, GAME_STATE_LEVEL_PICKER);
481         return eval_success(NIL(gc));
482     }
483
484     return unknown_target(gc, "game", target);
485 }
486
487 // Private Functions
488
489 static int game_render_cursor(const Game *game)
490 {
491     trace_assert(game);
492
493     SDL_Rect src = {0, 0, 32, 32};
494     SDL_Rect dest = {game->cursor_x, game->cursor_y, 32, 32};
495     if (SDL_RenderCopy(game->renderer, game->texture_cursor, &src, &dest) < 0) {
496         return -1;
497     }
498
499     return 0;
500 }