/* SPDX-License-Identifier: GPL-3.0-or-later */ /* Copyright (C) 2021 KikooDX */ #include "conf.h" #include "filepaths.h" #include "game_state.h" #include "input.h" #include "level.h" #include "levelselection.h" #include "mainmenu.h" #include "particles.h" #include "pause.h" #include "player.h" #include "titlescreen.h" #include "trail.h" #include "transition.h" #include "zxcolors.h" #include #include #include #include #include #include #ifdef RECORDING #define DUPDATE() \ do { \ dupdate(); \ if (usb_is_open()) \ usb_fxlink_screenshot(1); \ } while (0); #else #define DUPDATE() dupdate(); #endif #define PANIC(msg) \ do { \ dclear(ZX_BLACK); \ dprint_opt(DWIDTH / 2, DHEIGHT / 2, ZX_RED, C_NONE, \ DTEXT_CENTER, DTEXT_MIDDLE, "ERROR: %s", msg); \ DUPDATE(); \ getkey(); \ return 0; \ } while (0); #define LOAD_LEVEL() \ do { \ level_load_binary(); \ if (fatal_error == -1) \ PANIC(fatal_error_msg); \ particles_init(); \ trail_init(); \ player = player_init(); \ } while (0); extern int level_id; extern int fatal_error; extern char *fatal_error_msg; extern const font_t font_main; static int callback(volatile int *arg); int main(void) { int i; int timer; int return_code; int draw_pause; int frameskip = 0; /* int level_pack_beaten; */ enum TransitionMode transition_previous_mode; enum GameState game_state = MainMenu; volatile int has_ticked = 1; struct Player player; struct Input input = input_init(); struct TitleScreen titlescreen = titlescreen_init(); struct LevelSelection levelselection = levelselection_init(); struct MainMenu mainmenu = mainmenu_init(); struct Pause pause = pause_init(); struct Transition transition; particles_init(); #ifdef RECORDING /* open usb for screenshots */ usb_interface_t const *interfaces[] = {&usb_ff_bulk, NULL}; usb_open(interfaces, GINT_CALL_NULL); #endif /* set font */ dfont(&font_main); init_level_binaries(); /* load level */ level_id = 0; level_load_binary(); if (fatal_error == -1) PANIC(fatal_error_msg); /* timer setup */ timer = timer_configure(TIMER_ANY, 1000000 / TARGET_FPS, GINT_CALL(callback, &has_ticked)); if (timer == -1) PANIC("timer_configure failed"); timer_start(timer); /* initialize the player (has to be done after level loading */ player = player_init(); /* main game loop */ while (1) { /* skip render frames */ i = 1 + frameskip; while (i-- > 0) { #ifndef RECORDING /* frameskip adjustement */ if (has_ticked - 1 > frameskip) frameskip = has_ticked - 1; #endif /* speed limiter */ while (!has_ticked) sleep(); while (has_ticked) has_ticked = 0; /* update */ input_update(&input); switch (game_state) { case TitleScreen: if (titlescreen_update(&titlescreen, input)) game_state = MainMenu; break; case MainMenu: if (mainmenu_update(&mainmenu, input)) { switch (mainmenu.cursor) { case 0: game_state = LevelSelection; break; case 1: goto exit_game; default: PANIC("unknown return code " "(MainMenu)"); break; } } break; case LevelSelection: return_code = levelselection_update( &levelselection, input); switch (return_code) { case -1: game_state = MainMenu; break; case 0: break; case 1: game_state = Playing; /* set level according to * selected pack */ level_id = levelselection.pack_cursor * LVL_PER_PACK; LOAD_LEVEL(); transition = transition_init( H_TRANS_SPD, ZX_BLUE, TransitionHIn); break; default: PANIC("unknown return code " "(LevelSelection)"); break; } break; case Playing: if (transition.mode == TransitionNone) { trail_update(player); particles_update(); return_code = player_update(&player, input); level_update(); switch (return_code) { case -1: game_state = GamePause; break; case 1: level_id += 1; transition = transition_init( H_TRANS_SPD, ZX_BLUE, TransitionHOut); break; case 2: transition = transition_init( V_TRANS_SPD, ZX_RED, TransitionVOut); break; default: break; } } transition_previous_mode = transition_update(&transition); if (transition_previous_mode != transition.mode && transition_previous_mode != TransitionNone) { switch (transition_previous_mode) { case TransitionNone: break; case TransitionHIn: break; case TransitionHOut: /* end level pack */ if (level_id % 4 == 0) { game_state = PackDone; /* level_pack_beaten = level_id / 4; */ } else { LOAD_LEVEL(); } transition = transition_init( transition.speed, transition.color, TransitionHIn); break; case TransitionVIn: break; case TransitionVOut: LOAD_LEVEL(); transition = transition_init( transition.speed, transition.color, TransitionVIn); break; default: break; } } break; case GamePause: if (pause_update(&pause, input)) { game_state = Playing; /* pause draw takes is _very_ slow, * reset frameskip to compensate */ frameskip = 0; has_ticked = 0; } break; case PackDone: game_state = LevelSelection; break; case OptionsMenu: default: PANIC("missing game_state case (update)"); break; } } /* draw */ draw_pause = 0; dclear(ZX_BLACK); switch (game_state) { case TitleScreen: titlescreen_draw(titlescreen); break; case MainMenu: mainmenu_draw(mainmenu); break; case LevelSelection: levelselection_draw(levelselection); break; case GamePause: draw_pause = 1; /* fallthrough */ case Playing: level_draw(); trail_draw(); particles_draw(); trail_draw(); player_draw(player); transition_draw(transition); if (!draw_pause) break; /* GamePause */ pause_draw(pause); break; case PackDone: break; case OptionsMenu: default: PANIC("missing game_state case (draw)"); break; } DUPDATE(); /* return to main menu */ /* TODO don't hardcode this */ if (keydown(KEY_MENU)) { #ifdef RECORDING if (usb_is_open()) usb_close(); #endif gint_osmenu(); if (game_state == Playing) game_state = GamePause; } } exit_game: timer_stop(timer); #ifdef RECORDING if (usb_is_open()) usb_close(); #endif return 1; } static int callback(volatile int *arg) { *arg += 1; return 0; }