#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 22 #define FONT_COLOUR BLACK #define SELECTION_SYMBOL "=>" 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, SELECTION_SYMBOL, (Vector2){start_x,y_offset}, FONT_SIZE, 4, BLACK); } float vol = data->volume_value; sprintf(buffer, "Volume : %.1f", vol); DrawTextEx(*menu_font, buffer, (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, (data->old_volume_value == data->volume_value) ? BLACK : BLUE); y_offset += FONT_SIZE; line++; sc_array_foreach(&data->keybinds_info, keybind_info) { if (line == data->curr_selection) { DrawTextEx(*menu_font, SELECTION_SYMBOL, (Vector2){start_x,y_offset}, FONT_SIZE, 4, BLACK); } bool changed = false; if (keybind_info.key == keybind_info.original_key) { sprintf(buffer, "%s : %s", keybind_info.action_name, keybind_info.key_name); } else { changed = true; sprintf(buffer, "%s : %s => %s", keybind_info.action_name, keybind_info.original_key_name, keybind_info.key_name); } if (data->mode == OPTIONS_KEYBIND_MODE && line == data->curr_selection) { DrawTextEx(*menu_font, buffer, (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, RED); } else { DrawTextEx(*menu_font, buffer, (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, changed? BLUE : BLACK); } y_offset += FONT_SIZE; line++; } y_offset += FONT_SIZE; if (line == data->curr_selection) { DrawTextEx(*menu_font, SELECTION_SYMBOL, (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, SELECTION_SYMBOL, (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); } y_offset += FONT_SIZE; if (data->keybind_status == 1) { DrawTextEx(*menu_font, "Reserved key! Try another key.", (Vector2){start_x+32, y_offset}, FONT_SIZE, 4, RED); } 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; } int new_key = scene->engine->last_input_key; if (new_key == KEY_NULL) { return; } // Invalid/Reserved key are empty strings if (ExtGetKeyName(new_key)[0] == 0) { data->keybind_status = 1; return; } { // To check for keys already set by new_key // Also to fill in any KEY_NULL if the original key is not new key // Requires the index to modify the current element KeyBindInfo_t info; size_t i = 0; sc_array_foreach(&data->keybinds_info, info) { if (info.key == new_key) { data->keybinds_info.elems[i].key = data->keybinds_info.elems[i].original_key; data->keybinds_info.elems[i].key_name = ExtGetKeyName(data->keybinds_info.elems[i].key); if (info.original_key == new_key) { data->keybinds_info.elems[i].key = KEY_NULL; data->keybinds_info.elems[i].key_name = ExtGetKeyName(data->keybinds_info.elems[i].key); } } else if (info.key == KEY_NULL) { data->keybinds_info.elems[i].key = data->keybinds_info.elems[i].original_key; data->keybinds_info.elems[i].key_name = ExtGetKeyName(data->keybinds_info.elems[i].key); } i++; } } data->keybinds_info.elems[data->curr_selection - 1].key = new_key; data->keybinds_info.elems[data->curr_selection - 1].key_name = ExtGetKeyName(new_key); data->keybind_status = 0; 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; data->old_volume_value = data->volume_value; } else { // Need to reset action map, otherwise will preserve over scene change scene->action_remap_function(scene); change_scene(scene->engine, MAIN_MENU_SCENE); SetMasterVolume(data->old_volume_value); } } 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) { data->volume_value -= 0.1f; data->volume_value = (data->volume_value < 0.0f) ? 0.0f : data->volume_value; SetMasterVolume(data->volume_value); play_sfx(scene->engine, BOULDER_DESTROY_SFX); } break; case ACTION_RIGHT: if (new_selection == 0) { data->volume_value += 0.1f; data->volume_value = (data->volume_value > 1.0f) ? 1.0f : data->volume_value; SetMasterVolume(data->volume_value); play_sfx(scene->engine, BOULDER_DESTROY_SFX); } 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; data->keybind_status = 0; data->volume_value = GetMasterVolume(); data->old_volume_value = data->volume_value; } 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); }