#include "scene_impl.h" #include "assets_tag.h" #include "keymaps.h" #include /** * A potential performance tweak is to reduce the lookup. * The number of configurable actions should be fixed and * therefore would be known on scene init. * * So delegate all lookup on scene init. * Then, only call lookup on key changes. */ static const char* get_action_name(ActionType_t action) { /** * This is sort of a hack to get it done. * In theory, the engine should provide a way to register a name for an action * However, i didn't plan that far and this is the only place that needs it * Maybe in the next version of the engine... * * I could hardcode this to be array, but nah... */ switch (action) { case ACTION_UP: return "Up"; case ACTION_DOWN: return "Down"; case ACTION_LEFT: return "Left"; case ACTION_RIGHT: return "Right"; case ACTION_JUMP: case ACTION_CONFIRM: return "Confirm/Jump"; case ACTION_EXIT: return "Return"; case ACTION_LOOKAHEAD: return "Look"; case ACTION_RESTART: return "Restart"; default: return "Undefined"; } } static void options_scene_render_func(Scene_t* scene) { OptionSceneData_t* data = &(CONTAINER_OF(scene, OptionScene_t, scene)->data); KeyBindInfo_t keybind_info; char buffer[64]; int y_offset = 32; uint16_t line = 0; Sprite_t* level_board = get_sprite(&scene->engine->assets, "lvl_board"); #define TITLE_FONT_SIZE 40 #define FONT_SIZE 24 #define FONT_COLOUR BLACK float start_x = scene->engine->intended_window_size.x / 2; start_x -= level_board->frame_size.x / 2; Font* menu_font = get_font(&scene->engine->assets, "MenuFont"); BeginTextureMode(scene->layers.render_layers[0].layer_tex); ClearBackground((Color){0,0,0,0}); draw_sprite(level_board, 0, (Vector2){start_x,0},0, false); Vector2 title_size = MeasureTextEx(*menu_font, "Options", TITLE_FONT_SIZE, 4); DrawTextEx(*menu_font, "Options", (Vector2){start_x + title_size.x/2,y_offset}, TITLE_FONT_SIZE, 4, BLACK); y_offset += TITLE_FONT_SIZE; start_x += 32; if (line == data->curr_selection) { DrawTextEx(*menu_font, ">>", (Vector2){start_x,y_offset}, FONT_SIZE, 4, BLACK); } float vol = GetMasterVolume(); sprintf(buffer, "Volume : %.1f", vol); DrawTextEx(*menu_font, buffer, (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, BLACK); y_offset += FONT_SIZE; line++; sc_array_foreach(&data->keybinds_info, keybind_info) { if (line == data->curr_selection) { DrawTextEx(*menu_font, ">>", (Vector2){start_x,y_offset}, FONT_SIZE, 4, BLACK); } if (keybind_info.key == keybind_info.original_key) { sprintf(buffer, "%s : %s", keybind_info.action_name, keybind_info.key_name); DrawTextEx(*menu_font, buffer, (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, BLACK); } else { sprintf(buffer, "%s : %s => %s", keybind_info.action_name, keybind_info.original_key_name, keybind_info.key_name); DrawTextEx(*menu_font, buffer, (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, BLUE); } y_offset += FONT_SIZE; line++; } y_offset += FONT_SIZE; if (line == data->curr_selection) { DrawTextEx(*menu_font, ">>", (Vector2){start_x, y_offset}, FONT_SIZE, 4, BLACK); } DrawTextEx(*menu_font, "Apply", (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, BLACK); y_offset += FONT_SIZE; line++; if (line == data->curr_selection) { DrawTextEx(*menu_font, ">>", (Vector2){start_x,y_offset}, FONT_SIZE, 4, BLACK); } DrawTextEx(*menu_font, "Return", (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, BLACK); y_offset += FONT_SIZE; line++; if (data->mode == OPTIONS_KEYBIND_MODE) { y_offset += FONT_SIZE; DrawTextEx(*menu_font, "Press a Key...", (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, BLACK); } EndTextureMode(); } static void wait_for_keymap_input(Scene_t* scene) { /** * Due to engine design limitation, I canot remove * the system when placed into the array * So, this will have to run as part of the loop * Even though it is only used in a particular mode * It's a small performance penalty for unfortunate design */ OptionSceneData_t* data = &(CONTAINER_OF(scene, OptionScene_t, scene)->data); if (data->mode == OPTIONS_NORMAL_MODE) { return; } if (data->mode == OPTIONS_KEYBIND_READY_MODE) { // Keys are processed before system Update // Need to skip the current frame to get the next key // This state is to do just that. data->mode = OPTIONS_KEYBIND_MODE; return; } if (scene->engine->last_input_key == KEY_NULL) { return; } data->keybinds_info.elems[data->curr_selection - 1].key = scene->engine->last_input_key; data->keybinds_info.elems[data->curr_selection - 1].key_name = ExtGetKeyName(scene->engine->last_input_key); data->mode = OPTIONS_NORMAL_MODE; } static void exec_component_function(Scene_t* scene, uint16_t sel) { OptionSceneData_t* data = &(CONTAINER_OF(scene, OptionScene_t, scene)->data); if (sel == 0) { // Volume option, does nothing return; } sel--; uint16_t n_binds = sc_map_size_64(&scene->engine->keybinds); if (sel >= n_binds) { // Either OK or Cancel // Both will return to menu scene // OK does nothing since the keybinds are already modified sel -= n_binds; if (sel == 0) { // OK will propagate to the engine KeyBindInfo_t info; sc_array_foreach(&data->keybinds_info, info) { register_keybind(scene->engine, info.key, info.action); } remap_scene_keys(scene->engine); scene->reset_function(scene); data->curr_selection = sel + n_binds + 1; } else { // Need to reset action map, otherwise will preserve over scene change scene->action_remap_function(scene); change_scene(scene->engine, MAIN_MENU_SCENE); } } else { // Keybind options, but need to wait one frame data->mode = OPTIONS_KEYBIND_READY_MODE; } } static void options_do_action(Scene_t* scene, ActionType_t action, bool pressed) { OptionSceneData_t* data = &(CONTAINER_OF(scene, OptionScene_t, scene)->data); switch(data->mode) { case OPTIONS_NORMAL_MODE: { uint16_t new_selection = data->curr_selection; if (!pressed) { switch(action) { case ACTION_UP: if (new_selection > 0) { data->curr_selection--; } break; case ACTION_DOWN: if (new_selection < data->n_selections - 1) { data->curr_selection++; } break; case ACTION_LEFT: // Left and right only works on volume option if (new_selection == 0) { } break; case ACTION_RIGHT: if (new_selection == 0) { } break; case ACTION_CONFIRM: exec_component_function(scene, new_selection); break; case ACTION_EXIT: if(scene->engine != NULL) { change_scene(scene->engine, MAIN_MENU_SCENE); } break; default: break; } } } break; default: break; } } static void reset_options_scene(Scene_t* scene) { OptionSceneData_t* data = &(CONTAINER_OF(scene, OptionScene_t, scene)->data); sc_array_clear(&data->keybinds_info); { int key; ActionType_t action; sc_map_foreach(&scene->engine->keybinds, action, key) { KeyBindInfo_t keybind = { .action_name = get_action_name(action), .key_name = ExtGetKeyName(key), .original_key_name = ExtGetKeyName(key), .action = action, .original_key = key, .key = key, }; sc_array_add(&data->keybinds_info, keybind); } } data->curr_selection = 0; data->mode = OPTIONS_NORMAL_MODE; } static void options_action_remap_func(Scene_t* scene) { sc_map_clear_64(&scene->action_map); sc_map_put_64(&scene->action_map, get_keybind_or_default(scene->engine, ACTION_UP, KEY_UP), ACTION_UP); sc_map_put_64(&scene->action_map, get_keybind_or_default(scene->engine, ACTION_DOWN, KEY_DOWN), ACTION_DOWN); sc_map_put_64(&scene->action_map, get_keybind_or_default(scene->engine, ACTION_LEFT, KEY_LEFT), ACTION_LEFT); sc_map_put_64(&scene->action_map, get_keybind_or_default(scene->engine, ACTION_RIGHT, KEY_RIGHT), ACTION_RIGHT); sc_map_put_64(&scene->action_map, get_keybind_or_default(scene->engine, ACTION_JUMP, KEY_ENTER), ACTION_CONFIRM); sc_map_put_64(&scene->action_map, get_keybind_or_default(scene->engine, ACTION_EXIT, KEY_Q), ACTION_EXIT); sc_map_put_64(&scene->action_map, KEY_ENTER, ACTION_CONFIRM); sc_map_put_64(&scene->action_map, KEY_BACKSPACE, ACTION_EXIT); } void init_options_scene(OptionScene_t* scene) { init_scene(&scene->scene, &options_do_action, 0); scene->scene.bg_colour = BLACK; scene->scene.reset_function = &reset_options_scene; scene->scene.action_remap_function = &options_action_remap_func; add_scene_layer( &scene->scene, scene->scene.engine->intended_window_size.x, scene->scene.engine->intended_window_size.y, (Rectangle){ 0, 0, scene->scene.engine->intended_window_size.x, scene->scene.engine->intended_window_size.y } ); scene->data.curr_selection = 0; scene->data.mode = OPTIONS_NORMAL_MODE; // 3 extra options: Volume, Ok, Cancel scene->data.n_selections = sc_map_size_64(&scene->scene.engine->keybinds) + 3; sc_array_init(&scene->data.keybinds_info); sc_array_add(&scene->scene.systems, &wait_for_keymap_input); sc_array_add(&scene->scene.systems, &options_scene_render_func); options_action_remap_func(&scene->scene); } void free_options_scene(OptionScene_t* scene) { sc_array_term(&scene->data.keybinds_info); free_scene(&scene->scene); }