#include "map.h" #include "level.h" #include "render.h" #include #include #include #include #include #include #include #include typedef struct { /* Same mechanism as in game_t */ level_t const *level; map_t const *map; uint16_t *map_anim; camera_t camera; } menu_game_t; static menu_game_t *menu_load_game(level_t const *level) { menu_game_t *mg = malloc(sizeof *mg); mg->level = level; mg->map = level->map; mg->map_anim = malloc(mg->map->width * mg->map->height * sizeof *mg->map_anim); for(int i = 0; i < mg->map->width * mg->map->height; i++) mg->map_anim[i] = rand() & 4095; camera_init(&mg->camera, mg->map); return mg; } static void menu_destroy_game(menu_game_t *mg) { free(mg->map_anim); free(mg); } static void menu_render_game(menu_game_t *mg) { render_map_layer(mg->map, &mg->camera, 0, 0, HORIZONTAL, mg->map_anim, DIMAGE_NONE); render_map_layer(mg->map, &mg->camera, 0, 0, VERTICAL, mg->map_anim, DIMAGE_NONE); render_map_layer(mg->map, &mg->camera, 0, 0, CEILING, mg->map_anim, DIMAGE_NONE); } static void darken_vram(void) { int top_margin = 0; // 24; int bottom_margin = 0; // 32; uint32_t *v = (void *)(gint_vram + top_margin * DWIDTH); int N = DWIDTH * (DHEIGHT - top_margin - bottom_margin) / 2; for(int i = 0; i < N; i++) // v[i] = (v[i] & 0xf7def7de) >> 1; v[i] = (v[i] & 0xe79ce79c) >> 2; } static void menu_update_animations(menu_game_t *mg, fixed_t dt) { for(int i = 0; i < mg->map->width * mg->map->height; i++) mg->map_anim[i] += fround(dt * 1000); } static void dsprint(int x, int y, int fg1, int fg2, char const *fmt, ...) { va_list args; char str[256]; va_start(args, fmt); vsnprintf(str, 256, fmt, args); va_end(args); dtext(x, y+1, fg2, str); dtext(x, y, fg1, str); } int menu_level_select(int start) { extern bopti_image_t img_menu_title; menu_game_t *options[LEVEL_COUNT]; /* Don't show the sandbox in the menu; ALPHA+EXE to enter it instead */ #define OPTION_COUNT (LEVEL_COUNT-1) for(int i = 0; i < LEVEL_COUNT; i++) options[i] = menu_load_game(level_all[i]); int selection = (start >= 0 && start < OPTION_COUNT) ? start : 0; int target_y=0, y=0; /* Time spent in the transition animation that moves the GUI items away before starting the level. */ fixed_t transition_time = fix(0.0); int volatile frame_tick = 1; int t = timer_configure(TIMER_ANY, 1000000 / FRAME_RATE, GINT_CALL_SET(&frame_tick)); timer_start(t); while(1) { while(!frame_tick) sleep(); fixed_t dt = fix(1) / FRAME_RATE; if(y != target_y) { int dy = (target_y - y) / 4; if(y < target_y && dy < 6) dy = min(target_y-y, 6); if(y > target_y && dy > -6) dy = max(target_y-y, -6); y += dy; } /* GUI positioning variables, accounting for the final transition */ fixed_t TRANSITION_LEN = fix(0.75); int LOGO_Y = cubic(5, 5-80, transition_time, TRANSITION_LEN); int LEVELS_X = cubic(40, 40-200, transition_time, TRANSITION_LEN); int HISCORE_X = cubic(DWIDTH-80, DWIDTH, transition_time, TRANSITION_LEN); extern font_t font_rogue; font_t const *old_font = dfont(&font_rogue); dclear(C_BLACK); for(int i = 0; i < OPTION_COUNT; i++) { if(-y <= (i-1)*DHEIGHT) continue; if(-y >= (i+1)*DHEIGHT) continue; int local_offset = y + i*DHEIGHT; fixed_t map_center = fix(options[i]->map->height) / 2; options[i]->camera.y = map_center - fix(local_offset + 2) / TILE_HEIGHT; menu_render_game(options[i]); } if(transition_time == 0) darken_vram(); dimage(148, LOGO_Y, &img_menu_title); render_small_text(LEVELS_X, 80, C_WHITE, "PLAY", -1); render_small_text(HISCORE_X, 80, C_WHITE, "HIGH SCORE", -1); for(int i = 0; i < OPTION_COUNT; i++) { dsprint(LEVELS_X, 95+16*i, (i == selection) ? C_WHITE : C_RGB(20, 20, 20), C_BLACK, "%s%s", (i == selection) ? "> " : "", options[i]->level->name); // TODO: Wire high score to main menu dsprint(HISCORE_X, 95+16*i, (i == selection) ? C_WHITE : C_RGB(20, 20, 20), C_BLACK, "%03d", 0); } render_small_text(LEVELS_X, 180, C_WHITE, "OTHER", -1); dsprint(LEVELS_X, 195, (selection == OPTION_COUNT) ? C_WHITE : C_RGB(20, 20, 20), C_BLACK, (selection == OPTION_COUNT) ? "> Credits" : "Credits"); dfont(old_font); dupdate(); for(int i = 0; i < OPTION_COUNT; i++) menu_update_animations(options[i], dt); int key = getkey_opt(GETKEY_MENU, &frame_tick).key; extern bool rogue_life_video_capture; if(key == KEY_F6 && keydown(KEY_ALPHA) && !keydown(KEY_VARS) && usb_is_open()) { rogue_life_video_capture = !rogue_life_video_capture; } if(rogue_life_video_capture) { usb_fxlink_videocapture(false); } if(key == KEY_UP && selection > 0) { selection--; target_y += DHEIGHT; } if(key == KEY_DOWN && selection < OPTION_COUNT) { selection++; target_y -= DHEIGHT; } if(key == KEY_EXE && keydown(KEY_ALPHA)) { selection = LEVEL_COUNT - 1; break; } if((key == KEY_EXE || key == KEY_SHIFT) && transition_time == 0) { transition_time = dt; } else if(transition_time > fix(0.0)) { transition_time += dt; if(transition_time >= TRANSITION_LEN) break; } } timer_stop(t); for(size_t i = 0; i < OPTION_COUNT; i++) menu_destroy_game(options[i]); return selection; }