From 676fbefb75bc629713443805a61707ac315e726c Mon Sep 17 00:00:00 2001 From: "C. J. Howard" Date: Sun, 19 Feb 2023 01:26:50 +0800 Subject: [PATCH] Fix and improve control profile serialization. Fix gamepad config menu. Add support for loading string maps directly rather than building them at runtme. --- CMakeLists.txt | 1 - src/animation/screen-transition.cpp | 2 +- src/game/context.hpp | 13 +- src/game/control-profile.cpp | 178 +++++ src/game/control-profile.hpp | 43 ++ src/game/controls.cpp | 877 +++++------------------ src/game/controls.hpp | 71 +- src/game/fonts.cpp | 13 +- src/game/state/boot.cpp | 153 ++-- src/game/state/gamepad-config-menu.cpp | 410 ++++++----- src/game/state/gamepad-config-menu.hpp | 15 +- src/game/state/keyboard-config-menu.cpp | 16 +- src/game/state/keyboard-config-menu.hpp | 2 + src/game/state/language-menu.cpp | 97 +-- src/game/state/language-menu.hpp | 5 + src/game/state/main-menu.cpp | 8 +- src/game/state/main-menu.hpp | 1 - src/game/strings.cpp | 4 +- src/game/world.cpp | 4 +- src/i18n/string-map.cpp | 82 ++- src/i18n/string-map.hpp | 11 - src/input/action-events.hpp | 6 +- src/input/action-map.cpp | 46 +- src/input/action-map.hpp | 31 +- src/input/action.hpp | 2 +- src/input/mapper.cpp | 5 +- src/input/mapping-type.hpp | 4 +- src/input/mapping.cpp | 132 +++- src/input/mapping.hpp | 8 +- src/resources/control-profile-loader.cpp | 41 ++ src/resources/deserialize-context.hpp | 104 ++- src/resources/deserializer.cpp | 28 +- src/resources/serialize-context.cpp | 114 +-- src/resources/serialize-context.hpp | 121 +++- src/resources/serializer.cpp | 28 +- src/resources/string-map-loader.cpp | 41 ++ src/type/freetype/typeface.cpp | 16 +- src/type/freetype/typeface.hpp | 3 - src/type/typeface.hpp | 38 +- src/utility/dict.cpp | 26 +- 40 files changed, 1502 insertions(+), 1298 deletions(-) create mode 100644 src/game/control-profile.cpp create mode 100644 src/game/control-profile.hpp create mode 100644 src/resources/control-profile-loader.cpp create mode 100644 src/resources/string-map-loader.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index c9deac5..baf682a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,5 @@ cmake_minimum_required(VERSION 3.25) - option(APPLICATION_NAME "Application name" "Antkeeper") option(APPLICATION_VERSION "Application version string" "0.0.0") option(APPLICATION_AUTHOR "Application author" "C. J. Howard") diff --git a/src/animation/screen-transition.cpp b/src/animation/screen-transition.cpp index 086dbce..48f1588 100644 --- a/src/animation/screen-transition.cpp +++ b/src/animation/screen-transition.cpp @@ -24,7 +24,7 @@ screen_transition::screen_transition() { // Setup material - material.set_flags(MATERIAL_FLAG_X_RAY); + //material.set_flags(MATERIAL_FLAG_X_RAY); material.set_blend_mode(render::blend_mode::translucent); progress = material.add_property("progress"); diff --git a/src/game/context.hpp b/src/game/context.hpp index 7718081..1fbf01d 100644 --- a/src/game/context.hpp +++ b/src/game/context.hpp @@ -36,7 +36,6 @@ #include "gl/vertex-array.hpp" #include "gl/vertex-buffer.hpp" #include "i18n/string-map.hpp" -#include "i18n/string-table.hpp" #include "input/action-map.hpp" #include "input/action.hpp" #include "input/mapper.hpp" @@ -97,6 +96,8 @@ namespace game class steering; class spring; } + + struct control_profile; } namespace render @@ -162,10 +163,8 @@ struct context bool gamepad_active; // Localization and internationalization - std::uint16_t language_index; - std::uint16_t language_count; - i18n::string_table* string_table; - std::vector string_maps; + std::string language_tag; + i18n::string_map* string_map; // Fonts std::unordered_map typefaces; @@ -177,6 +176,8 @@ struct context render::material title_font_material; // Action maps, actions, and action event handling + std::string control_profile_filename; + game::control_profile* control_profile; input::mapper input_mapper; input::action_map window_actions; input::action fullscreen_action; @@ -202,7 +203,7 @@ struct context input::action pause_action; // Debugging - math::moving_average average_frame_time; + scene::text* frame_time_text; debug::cli* cli; // Hierarchichal state machine diff --git a/src/game/control-profile.cpp b/src/game/control-profile.cpp new file mode 100644 index 0000000..38b8689 --- /dev/null +++ b/src/game/control-profile.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "game/control-profile.hpp" +#include "resources/serializer.hpp" +#include "resources/serialize-error.hpp" +#include "resources/deserializer.hpp" +#include "resources/deserialize-error.hpp" +#include "debug/log.hpp" + +/** + * Serializes a control profile. + * + * @param[in] profile Control profile to serialize. + * @param[in,out] ctx Serialize context. + * + * @throw serialize_error Write error. + * @throw serialize_error Unsupported mapping type. + */ +template <> +void serializer::serialize(const game::control_profile& profile, serialize_context& ctx) +{ + // Write number of mappings + std::uint64_t size = static_cast(profile.mappings.size()); + ctx.write64(reinterpret_cast(&size), 1); + + // Write mappings + for (const auto& [key, value]: profile.mappings) + { + // Write key + ctx.write32(reinterpret_cast(&key), 1); + + // Write mapping type + const input::mapping_type mapping_type = value->get_mapping_type(); + ctx.write8(reinterpret_cast(&mapping_type), 1); + + // Write mapping + switch (mapping_type) + { + case input::mapping_type::gamepad_axis: + serializer().serialize(*static_cast(value.get()), ctx); + break; + + case input::mapping_type::gamepad_button: + serializer().serialize(*static_cast(value.get()), ctx); + break; + + case input::mapping_type::key: + serializer().serialize(*static_cast(value.get()), ctx); + break; + + case input::mapping_type::mouse_button: + serializer().serialize(*static_cast(value.get()), ctx); + break; + + case input::mapping_type::mouse_motion: + serializer().serialize(*static_cast(value.get()), ctx); + break; + + case input::mapping_type::mouse_scroll: + serializer().serialize(*static_cast(value.get()), ctx); + break; + + default: + throw serialize_error("Unsupported mapping type"); + break; + } + } + + // Write settings + serializer>().serialize(profile.settings, ctx); +} + +/** + * Deserializes a control profile. + * + * @param[out] profile Control profile to deserialize. + * @param[in,out] ctx Deserialize context. + * + * @throw deserialize_error Read error. + * @throw deserialize_error Unsupported mapping type. + */ +template <> +void deserializer::deserialize(game::control_profile& profile, deserialize_context& ctx) +{ + profile.mappings.clear(); + + // Read number of mappings + std::uint64_t size = 0; + ctx.read64(reinterpret_cast(&size), 1); + + // Read mappings + for (std::uint64_t i = 0; i < size; ++i) + { + // Read key + std::uint32_t key = 0; + ctx.read32(reinterpret_cast(&key), 1); + + // Read mapping type + input::mapping_type mapping_type; + ctx.read8(reinterpret_cast(&mapping_type), 1); + + // Read mapping + switch (mapping_type) + { + case input::mapping_type::gamepad_axis: + { + input::gamepad_axis_mapping mapping; + deserializer().deserialize(mapping, ctx); + profile.mappings.emplace(key, std::make_unique(std::move(mapping))); + break; + } + + case input::mapping_type::gamepad_button: + { + input::gamepad_button_mapping mapping; + deserializer().deserialize(mapping, ctx); + profile.mappings.emplace(key, std::make_unique(std::move(mapping))); + break; + } + + case input::mapping_type::key: + { + input::key_mapping mapping; + deserializer().deserialize(mapping, ctx); + profile.mappings.emplace(key, std::make_unique(std::move(mapping))); + break; + } + + case input::mapping_type::mouse_button: + { + input::mouse_button_mapping mapping; + deserializer().deserialize(mapping, ctx); + profile.mappings.emplace(key, std::make_unique(std::move(mapping))); + break; + } + + case input::mapping_type::mouse_motion: + { + input::mouse_motion_mapping mapping; + deserializer().deserialize(mapping, ctx); + profile.mappings.emplace(key, std::make_unique(std::move(mapping))); + break; + } + + case input::mapping_type::mouse_scroll: + { + input::mouse_scroll_mapping mapping; + deserializer().deserialize(mapping, ctx); + profile.mappings.emplace(key, std::make_unique(std::move(mapping))); + break; + } + + default: + throw deserialize_error("Unsupported mapping type"); + break; + } + } + + // Read settings + deserializer>().deserialize(profile.settings, ctx); +} diff --git a/src/game/control-profile.hpp b/src/game/control-profile.hpp new file mode 100644 index 0000000..c2a23f3 --- /dev/null +++ b/src/game/control-profile.hpp @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#ifndef ANTKEEER_GAME_CONTROL_PROFILE_HPP +#define ANTKEEER_GAME_CONTROL_PROFILE_HPP + +#include "input/mapping.hpp" +#include "utility/dict.hpp" +#include +#include +#include + +namespace game { + +struct control_profile +{ +public: + /// Input mappings. + std::multimap> mappings; + + /// Profile-specific settings. + dict settings; +}; + +} // namespace game + +#endif // ANTKEEER_GAME_CONTROL_PROFILE_HPP diff --git a/src/game/controls.cpp b/src/game/controls.cpp index 136d982..4b19150 100644 --- a/src/game/controls.cpp +++ b/src/game/controls.cpp @@ -20,20 +20,189 @@ #include "game/controls.hpp" #include "game/graphics.hpp" #include "game/menu.hpp" +#include "game/control-profile.hpp" #include "resources/resource-manager.hpp" #include "resources/json.hpp" #include "input/modifier-key.hpp" +#include "utility/hash/fnv1a.hpp" + +using namespace hash::literals; namespace game { -void setup_window_controls(game::context& ctx) +void reset_control_profile(game::control_profile& profile) +{ + auto& mappings = profile.mappings; + auto& settings = profile.settings; + + mappings.clear(); + settings.clear(); + + // Window controls + mappings.emplace("fullscreen"_fnv1a32, new input::key_mapping(nullptr, input::scancode::f11, 0, false)); + mappings.emplace("fullscreen"_fnv1a32, new input::key_mapping(nullptr, input::scancode::enter, input::modifier_key::alt, false)); + mappings.emplace("screenshot"_fnv1a32, new input::key_mapping(nullptr, input::scancode::f12, 0, false)); + mappings.emplace("screenshot"_fnv1a32, new input::key_mapping(nullptr, input::scancode::print_screen, 0, false)); + + // Menu controls + mappings.emplace("menu_up"_fnv1a32, new input::key_mapping(nullptr, input::scancode::up, 0, true)); + mappings.emplace("menu_up"_fnv1a32, new input::key_mapping(nullptr, input::scancode::w, 0, true)); + mappings.emplace("menu_up"_fnv1a32, new input::key_mapping(nullptr, input::scancode::i, 0, true)); + mappings.emplace("menu_up"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_y, true)); + mappings.emplace("menu_up"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_y, true)); + mappings.emplace("menu_up"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_up)); + mappings.emplace("menu_down"_fnv1a32, new input::key_mapping(nullptr, input::scancode::down, 0, true)); + mappings.emplace("menu_down"_fnv1a32, new input::key_mapping(nullptr, input::scancode::s, 0, true)); + mappings.emplace("menu_down"_fnv1a32, new input::key_mapping(nullptr, input::scancode::k, 0, true)); + mappings.emplace("menu_down"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_y, false)); + mappings.emplace("menu_down"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_y, false)); + mappings.emplace("menu_down"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_down)); + mappings.emplace("menu_left"_fnv1a32, new input::key_mapping(nullptr, input::scancode::left, 0, true)); + mappings.emplace("menu_left"_fnv1a32, new input::key_mapping(nullptr, input::scancode::a, 0, true)); + mappings.emplace("menu_left"_fnv1a32, new input::key_mapping(nullptr, input::scancode::j, 0, true)); + mappings.emplace("menu_left"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_x, true)); + mappings.emplace("menu_left"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_x, true)); + mappings.emplace("menu_left"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_left)); + mappings.emplace("menu_left"_fnv1a32, new input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::x, true)); + mappings.emplace("menu_left"_fnv1a32, new input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::y, true)); + mappings.emplace("menu_right"_fnv1a32, new input::key_mapping(nullptr, input::scancode::right, 0, true)); + mappings.emplace("menu_right"_fnv1a32, new input::key_mapping(nullptr, input::scancode::d, 0, true)); + mappings.emplace("menu_right"_fnv1a32, new input::key_mapping(nullptr, input::scancode::l, 0, true)); + mappings.emplace("menu_right"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_x, false)); + mappings.emplace("menu_right"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_x, false)); + mappings.emplace("menu_right"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_right)); + mappings.emplace("menu_right"_fnv1a32, new input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::x, false)); + mappings.emplace("menu_right"_fnv1a32, new input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::y, false)); + mappings.emplace("menu_select"_fnv1a32, new input::key_mapping(nullptr, input::scancode::enter, 0, false)); + mappings.emplace("menu_select"_fnv1a32, new input::key_mapping(nullptr, input::scancode::space, 0, false)); + mappings.emplace("menu_select"_fnv1a32, new input::key_mapping(nullptr, input::scancode::e, 0, false)); + mappings.emplace("menu_select"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::a)); + mappings.emplace("menu_back"_fnv1a32, new input::key_mapping(nullptr, input::scancode::escape, 0, false)); + mappings.emplace("menu_back"_fnv1a32, new input::key_mapping(nullptr, input::scancode::backspace, 0, false)); + mappings.emplace("menu_back"_fnv1a32, new input::key_mapping(nullptr, input::scancode::q, 0, false)); + mappings.emplace("menu_back"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::b)); + mappings.emplace("menu_back"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::back)); + mappings.emplace("menu_modifier"_fnv1a32, new input::key_mapping(nullptr, input::scancode::left_shift, 0, false)); + mappings.emplace("menu_modifier"_fnv1a32, new input::key_mapping(nullptr, input::scancode::right_shift, 0, false)); + + // Movement controls + mappings.emplace("move_forward"_fnv1a32, new input::key_mapping(nullptr, input::scancode::w, 0, false)); + mappings.emplace("move_forward"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_y, true)); + mappings.emplace("move_back"_fnv1a32, new input::key_mapping(nullptr, input::scancode::s, 0, false)); + mappings.emplace("move_back"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_y, false)); + mappings.emplace("move_left"_fnv1a32, new input::key_mapping(nullptr, input::scancode::a, 0, false)); + mappings.emplace("move_left"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_x, true)); + mappings.emplace("move_right"_fnv1a32, new input::key_mapping(nullptr, input::scancode::d, 0, false)); + mappings.emplace("move_right"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_x, false)); + mappings.emplace("move_up"_fnv1a32, new input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::y, false)); + mappings.emplace("move_up"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_trigger, false)); + mappings.emplace("move_down"_fnv1a32, new input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::y, true)); + mappings.emplace("move_down"_fnv1a32, new input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_trigger, false)); + mappings.emplace("pause"_fnv1a32, new input::key_mapping(nullptr, input::scancode::escape, 0, false)); + mappings.emplace("pause"_fnv1a32, new input::gamepad_button_mapping(nullptr, input::gamepad_button::start)); +} + +void apply_control_profile(game::context& ctx, const game::control_profile& profile) { - // Map window controls - ctx.window_actions.add_mapping(ctx.fullscreen_action, input::key_mapping(nullptr, input::scancode::f11, false)); - ctx.window_actions.add_mapping(ctx.fullscreen_action, input::key_mapping(nullptr, input::scancode::enter, false, input::modifier_key::alt)); - ctx.window_actions.add_mapping(ctx.screenshot_action, input::key_mapping(nullptr, input::scancode::f12, false)); - ctx.window_actions.add_mapping(ctx.screenshot_action, input::key_mapping(nullptr, input::scancode::print_screen, false)); + auto add_mappings = [&profile](input::action_map& map, input::action& action, std::uint32_t key) + { + auto range = profile.mappings.equal_range(key); + for (auto i = range.first; i != range.second; ++i) + { + map.add_mapping(action, *i->second); + } + }; + + // Window controls + ctx.window_actions.remove_mappings(); + add_mappings(ctx.window_actions, ctx.fullscreen_action, "fullscreen"_fnv1a32); + add_mappings(ctx.window_actions, ctx.screenshot_action, "screenshot"_fnv1a32); + // Menu controls + ctx.menu_actions.remove_mappings(); + add_mappings(ctx.menu_actions, ctx.menu_up_action, "menu_up"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_down_action, "menu_down"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_left_action, "menu_left"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_right_action, "menu_right"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_select_action, "menu_select"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_back_action, "menu_back"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_modifier_action, "menu_modifier"_fnv1a32); + + // Movement controls + ctx.movement_actions.remove_mappings(); + add_mappings(ctx.movement_actions, ctx.move_forward_action, "move_forward"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_back_action, "move_back"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_left_action, "move_left"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_right_action, "move_right"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_up_action, "move_up"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_down_action, "move_down"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.pause_action, "pause"_fnv1a32); +} + +void update_control_profile(game::context& ctx, game::control_profile& profile) +{ + auto add_mappings = [&profile](const input::action_map& map, const input::action& action, std::uint32_t key) + { + auto gamepad_axis_mappings = map.get_gamepad_axis_mappings(action); + auto gamepad_button_mappings = map.get_gamepad_button_mappings(action); + auto key_mappings = map.get_key_mappings(action); + auto mouse_button_mappings = map.get_mouse_button_mappings(action); + auto mouse_motion_mappings = map.get_mouse_motion_mappings(action); + auto mouse_scroll_mappings = map.get_mouse_scroll_mappings(action); + + for (const auto& mapping: gamepad_axis_mappings) + { + profile.mappings.emplace(key, new input::gamepad_axis_mapping(mapping)); + } + for (const auto& mapping: gamepad_button_mappings) + { + profile.mappings.emplace(key, new input::gamepad_button_mapping(mapping)); + } + for (const auto& mapping: key_mappings) + { + profile.mappings.emplace(key, new input::key_mapping(mapping)); + } + for (const auto& mapping: mouse_button_mappings) + { + profile.mappings.emplace(key, new input::mouse_button_mapping(mapping)); + } + for (const auto& mapping: mouse_motion_mappings) + { + profile.mappings.emplace(key, new input::mouse_motion_mapping(mapping)); + } + for (const auto& mapping: mouse_scroll_mappings) + { + profile.mappings.emplace(key, new input::mouse_scroll_mapping(mapping)); + } + }; + + profile.mappings.clear(); + + // Window controls + add_mappings(ctx.window_actions, ctx.fullscreen_action, "fullscreen"_fnv1a32); + add_mappings(ctx.window_actions, ctx.screenshot_action, "screenshot"_fnv1a32); + + // Menu controls + add_mappings(ctx.menu_actions, ctx.menu_up_action, "menu_up"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_down_action, "menu_down"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_left_action, "menu_left"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_right_action, "menu_right"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_select_action, "menu_select"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_back_action, "menu_back"_fnv1a32); + add_mappings(ctx.menu_actions, ctx.menu_modifier_action, "menu_modifier"_fnv1a32); + + // Movement controls + add_mappings(ctx.movement_actions, ctx.move_forward_action, "move_forward"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_back_action, "move_back"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_left_action, "move_left"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_right_action, "move_right"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_up_action, "move_up"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.move_down_action, "move_down"_fnv1a32); + add_mappings(ctx.movement_actions, ctx.pause_action, "pause"_fnv1a32); +} + +void setup_window_controls(game::context& ctx) +{ // Setup fullscreen control ctx.window_action_subscriptions.emplace_back ( @@ -61,53 +230,6 @@ void setup_window_controls(game::context& ctx) void setup_menu_controls(game::context& ctx) { - // Map menu controls - ctx.menu_actions.add_mapping(ctx.menu_up_action, input::key_mapping(nullptr, input::scancode::up, true)); - ctx.menu_actions.add_mapping(ctx.menu_up_action, input::key_mapping(nullptr, input::scancode::w, true)); - ctx.menu_actions.add_mapping(ctx.menu_up_action, input::key_mapping(nullptr, input::scancode::i, true)); - ctx.menu_actions.add_mapping(ctx.menu_up_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_y, true)); - ctx.menu_actions.add_mapping(ctx.menu_up_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_y, true)); - ctx.menu_actions.add_mapping(ctx.menu_up_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_up)); - - ctx.menu_actions.add_mapping(ctx.menu_down_action, input::key_mapping(nullptr, input::scancode::down, true)); - ctx.menu_actions.add_mapping(ctx.menu_down_action, input::key_mapping(nullptr, input::scancode::s, true)); - ctx.menu_actions.add_mapping(ctx.menu_down_action, input::key_mapping(nullptr, input::scancode::k, true)); - ctx.menu_actions.add_mapping(ctx.menu_down_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_y, false)); - ctx.menu_actions.add_mapping(ctx.menu_down_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_y, false)); - ctx.menu_actions.add_mapping(ctx.menu_down_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_down)); - - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::key_mapping(nullptr, input::scancode::left, true)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::key_mapping(nullptr, input::scancode::a, true)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::key_mapping(nullptr, input::scancode::j, true)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_x, true)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_x, true)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_left)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::x, true)); - ctx.menu_actions.add_mapping(ctx.menu_left_action, input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::y, true)); - - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::key_mapping(nullptr, input::scancode::right, true)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::key_mapping(nullptr, input::scancode::d, true)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::key_mapping(nullptr, input::scancode::l, true)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::left_stick_x, false)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::gamepad_axis_mapping(nullptr, input::gamepad_axis::right_stick_x, false)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::dpad_right)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::x, false)); - ctx.menu_actions.add_mapping(ctx.menu_right_action, input::mouse_scroll_mapping(nullptr, input::mouse_scroll_axis::y, false)); - - ctx.menu_actions.add_mapping(ctx.menu_select_action, input::key_mapping(nullptr, input::scancode::enter, false)); - ctx.menu_actions.add_mapping(ctx.menu_select_action, input::key_mapping(nullptr, input::scancode::space, false)); - ctx.menu_actions.add_mapping(ctx.menu_select_action, input::key_mapping(nullptr, input::scancode::e, false)); - ctx.menu_actions.add_mapping(ctx.menu_select_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::a)); - - ctx.menu_actions.add_mapping(ctx.menu_back_action, input::key_mapping(nullptr, input::scancode::escape, false)); - ctx.menu_actions.add_mapping(ctx.menu_back_action, input::key_mapping(nullptr, input::scancode::backspace, false)); - ctx.menu_actions.add_mapping(ctx.menu_back_action, input::key_mapping(nullptr, input::scancode::q, false)); - ctx.menu_actions.add_mapping(ctx.menu_back_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::b)); - ctx.menu_actions.add_mapping(ctx.menu_back_action, input::gamepad_button_mapping(nullptr, input::gamepad_button::back)); - - ctx.menu_actions.add_mapping(ctx.menu_modifier_action, input::key_mapping(nullptr, input::scancode::left_shift, false)); - ctx.menu_actions.add_mapping(ctx.menu_modifier_action, input::key_mapping(nullptr, input::scancode::right_shift, false)); - // Setup menu controls ctx.menu_action_subscriptions.emplace_back ( @@ -196,6 +318,11 @@ void setup_menu_controls(game::context& ctx) ctx.menu_right_action.set_threshold_function(menu_action_threshold); } +void setup_game_controls(game::context& ctx) +{ + +} + void enable_window_controls(game::context& ctx) { ctx.window_actions.connect(ctx.input_manager->get_event_queue()); @@ -219,6 +346,7 @@ void enable_menu_controls(game::context& ctx) float min_y = name_bounds.min_point.y(); float max_x = name_bounds.max_point.x(); float max_y = name_bounds.max_point.y(); + if (value) { const auto& value_bounds = static_cast&>(value->get_world_bounds()); @@ -321,647 +449,4 @@ void disable_menu_controls(game::context& ctx) ctx.menu_mouse_subscriptions.clear(); } -std::filesystem::path gamepad_calibration_path(const game::context& ctx, const input::gamepad& gamepad) -{ - return std::filesystem::path("gamepad-" + gamepad.get_uuid().string() + ".json"); -} - -json default_control_profile() -{ - return json(); -} - -json default_gamepad_calibration() -{ - const float activation_min = 0.15f; - const float activation_max = 0.98f; - const bool deadzone_cross = false; - const float deadzone_roundness = 1.0f; - const std::string response_curve = "linear"; - - json calibration; - - calibration["leftx_activation"] = {activation_min, activation_max}; - calibration["lefty_activation"] = {activation_min, activation_max}; - calibration["rightx_activation"] = {activation_min, activation_max}; - calibration["righty_activation"] = {activation_min, activation_max}; - calibration["lefttrigger_activation"] = {activation_min, activation_max}; - calibration["righttrigger_activation"] = {activation_min, activation_max}; - calibration["leftx_response_curve"] = response_curve; - calibration["lefty_response_curve"] = response_curve; - calibration["rightx_response_curve"] = response_curve; - calibration["righty_response_curve"] = response_curve; - calibration["lefttrigger_response_curve"] = response_curve; - calibration["righttrigger_response_curve"] = response_curve; - calibration["left_deadzone_cross"] = deadzone_cross; - calibration["right_deadzone_cross"] = deadzone_cross; - calibration["left_deadzone_roundness"] = deadzone_roundness; - calibration["right_deadzone_roundness"] = deadzone_roundness; - - return calibration; -} - -json* load_gamepad_calibration(game::context& ctx, const input::gamepad& gamepad) -{ - // Determine path to gamepad calibration file - std::filesystem::path path = gamepad_calibration_path(ctx, gamepad); - - // Load gamepad calibration file - json* calibration = ctx.resource_manager->load(path.string()); - - return calibration; -} - -bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& gamepad, const json& calibration) -{ - /* - // Determine absolute path to gamepad calibration file - std::filesystem::path path = ctx.controls_path / gamepad_calibration_path(ctx, gamepad); - - // Open calibration file - std::ofstream stream; - stream.open(path); - if (!stream) - return false; - - // Write calibration to file - stream << calibration.dump(1, '\t'); - if (stream.bad()) - { - stream.close(); - return false; - } - - // Close calibration file - stream.close(); - */ - - return true; -} - -void apply_control_profile(game::context& ctx, const json& profile) -{ - /* - // Map gamepad buttons to strings - const std::unordered_map gamepad_button_map = - { - {"a", input::gamepad_button::a}, - {"b", input::gamepad_button::b}, - {"x", input::gamepad_button::x}, - {"y", input::gamepad_button::y}, - {"back", input::gamepad_button::back}, - {"guide", input::gamepad_button::guide}, - {"start", input::gamepad_button::start}, - {"leftstick", input::gamepad_button::left_stick}, - {"rightstick", input::gamepad_button::right_stick}, - {"leftshoulder", input::gamepad_button::left_shoulder}, - {"rightshoulder", input::gamepad_button::right_shoulder}, - {"dpup", input::gamepad_button::dpad_up}, - {"dpdown", input::gamepad_button::dpad_down}, - {"dpleft", input::gamepad_button::dpad_left}, - {"dpright", input::gamepad_button::dpad_right} - }; - - // Map gamepad axes to strings - const std::unordered_map gamepad_axis_map = - { - {"leftx", input::gamepad_axis::left_x}, - {"lefty", input::gamepad_axis::left_y}, - {"rightx", input::gamepad_axis::right_x}, - {"righty", input::gamepad_axis::right_y}, - {"lefttrigger", input::gamepad_axis::left_trigger}, - {"righttrigger", input::gamepad_axis::right_trigger} - }; - - // Remove all existing input mappings - for (auto control = ctx.controls.begin(); control != ctx.controls.end(); ++control) - { - ctx.input_event_router->remove_mappings(control->second); - } - - // Get keyboard and mouse devices - input::keyboard* keyboard = ctx.input_manager->get_keyboards().front(); - input::mouse* mouse = ctx.input_manager->get_mice().front(); - - // Find profile gamepad device - input::gamepad* gamepad = nullptr; - auto gamepad_element = profile.find("gamepad"); - if (gamepad_element != profile.end()) - { - // Get gamepad UUID string - const std::string uuid_string = gamepad_element->get(); - - // Find gamepad with matching UUID - for (input::gamepad* device: ctx.input_manager->get_gamepads()) - { - if (device->get_uuid().string() == uuid_string) - { - gamepad = device; - break; - } - } - } - - // Find controls element - auto controls_element = profile.find("controls"); - if (controls_element != profile.end()) - { - // For each control in the profile - for (auto control_element = controls_element->cbegin(); control_element != controls_element->cend(); ++control_element) - { - // Get the control name - std::string control_name = control_element.key(); - - // Find or create control - input::action* control; - if (ctx.controls.count(control_name)) - { - control = ctx.controls[control_name]; - } - else - { - control = new input::action(); - ctx.controls[control_name] = control; - } - - // For each mapping in the control - for (auto mapping_element = control_element.value().cbegin(); mapping_element != control_element.value().cend(); ++mapping_element) - { - if (!mapping_element->contains("device")) - { - debug::log::warning("Control \"" + control_name + "\" not mapped to a device"); - continue; - } - - // Get the mapping device - const std::string device = (*mapping_element)["device"]; - - if (device == "keyboard") - { - // Parse key name - if (!mapping_element->contains("key")) - { - debug::log::warning("Control \"" + control_name + "\" has invalid keyboard mapping"); - continue; - } - std::string key = (*mapping_element)["key"].get(); - - // Get scancode from key name - input::scancode scancode = keyboard->get_scancode_from_name(key.c_str()); - if (scancode == input::scancode::unknown) - { - debug::log::warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\""); - continue; - } - - // Map control to keyboard key - ctx.input_event_router->add_mapping(input::key_mapping(control, keyboard, scancode)); - - debug::log::info("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\""); - } - else if (device == "mouse") - { - if (mapping_element->contains("button")) - { - // Parse mouse button index - int button = (*mapping_element)["button"].get(); - - // Map control to mouse button - ctx.input_event_router->add_mapping(input::mouse_button_mapping(control, mouse, button)); - - debug::log::info("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button)); - } - else if (mapping_element->contains("wheel")) - { - // Parse mouse wheel axis - std::string wheel = (*mapping_element)["wheel"].get(); - input::mouse_axis axis; - if (wheel == "x+") - axis = input::mouse_axis::positive_x; - else if (wheel == "x-") - axis = input::mouse_axis::negative_x; - else if (wheel == "y+") - axis = input::mouse_axis::positive_y; - else if (wheel == "y-") - axis = input::mouse_axis::negative_y; - else - { - debug::log::warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\""); - continue; - } - - // Map control to mouse wheel axis - ctx.input_event_router->add_mapping(input::mouse_wheel_mapping(control, mouse, axis)); - - debug::log::info("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel); - } - else if (mapping_element->contains("motion")) - { - std::string motion = (*mapping_element)["motion"].get(); - input::mouse_axis axis; - if (motion == "x+") - axis = input::mouse_axis::positive_x; - else if (motion == "x-") - axis = input::mouse_axis::negative_x; - else if (motion == "y+") - axis = input::mouse_axis::positive_y; - else if (motion == "y-") - axis = input::mouse_axis::negative_y; - else - { - debug::log::warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\""); - continue; - } - - // Map control to mouse motion axis - ctx.input_event_router->add_mapping(input::mouse_motion_mapping(control, mouse, axis)); - - debug::log::info("Mapped control \"" + control_name + "\" to mouse motion axis " + motion); - } - else - { - debug::log::warning("Control \"" + control_name + "\" has invalid mouse mapping"); - continue; - } - } - else if (device == "gamepad") - { - if (mapping_element->contains("button")) - { - // Parse gamepad button - std::string button = (*mapping_element)["button"].get(); - - auto button_it = gamepad_button_map.find(button); - if (button_it == gamepad_button_map.end()) - { - debug::log::warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\""); - continue; - } - - // Map control to gamepad button - ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, gamepad, button_it->second)); - - debug::log::info("Mapped control \"" + control_name + "\" to gamepad button " + button); - } - else if (mapping_element->contains("axis")) - { - std::string axis = (*mapping_element)["axis"].get(); - - // Parse gamepad axis name - const std::string axis_name = axis.substr(0, axis.length() - 1); - auto axis_it = gamepad_axis_map.find(axis_name); - if (axis_it == gamepad_axis_map.end()) - { - debug::log::warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\""); - continue; - } - - // Parse gamepad axis sign - const char axis_sign = axis.back(); - if (axis_sign != '-' && axis_sign != '+') - { - debug::log::warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\""); - continue; - } - bool axis_negative = (axis_sign == '-'); - - // Map control to gamepad axis - ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, gamepad, axis_it->second, axis_negative)); - - debug::log::info("Mapped control \"" + control_name + "\" to gamepad axis " + axis); - } - else - { - debug::log::info("Control \"" + control_name + "\" has invalid gamepad mapping"); - continue; - } - } - else - { - debug::log::warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\""); - } - } - } - } - */ -} - -void save_control_profile(game::context& ctx) -{ - /* - std::filesystem::path path; - if (ctx.config->contains("control_profile")) - path = ctx.shared_config_path / "controls" / (*ctx.config)["control_profile"].get(); - - debug::log::trace("Saving control profile to \"{}\"...", path.string()); - try - { - json control_profile; - - // Add controls element - auto& controls_element = control_profile["controls"]; - controls_element = json::object(); - - for (auto controls_it = ctx.controls.begin(); controls_it != ctx.controls.end(); ++controls_it) - { - const std::string& control_name = controls_it->first; - input::action* control = controls_it->second; - - // Add control element - auto& control_element = controls_element[control_name]; - control_element = json::array(); - - // Get control mappings - auto mappings = ctx.input_event_router->get_mappings(control); - - // Skip unmapped controls - if (!mappings) - continue; - - // Add control mapping elements - for (input::mapping* mapping: *mappings) - { - json mapping_element; - - switch (mapping->get_type()) - { - case input::mapping_type::key: - { - const input::key_mapping* key_mapping = static_cast(mapping); - mapping_element["device"] = "keyboard"; - mapping_element["key"] = input::keyboard::get_scancode_name(key_mapping->scancode); - break; - } - - case input::mapping_type::mouse_wheel: - { - const input::mouse_wheel_mapping* wheel_mapping = static_cast(mapping); - - mapping_element["device"] = "mouse"; - switch (wheel_mapping->axis) - { - case input::mouse_axis::negative_x: - mapping_element["wheel"] = "x-"; - break; - case input::mouse_axis::positive_x: - mapping_element["wheel"] = "x+"; - break; - case input::mouse_axis::negative_y: - mapping_element["wheel"] = "y-"; - break; - case input::mouse_axis::positive_y: - mapping_element["wheel"] = "y+"; - break; - default: - break; - } - break; - } - - case input::mapping_type::mouse_motion: - { - const input::mouse_motion_mapping* motion_mapping = static_cast(mapping); - - mapping_element["device"] = "mouse"; - switch (motion_mapping->axis) - { - case input::mouse_axis::negative_x: - mapping_element["motion"] = "x-"; - break; - case input::mouse_axis::positive_x: - mapping_element["motion"] = "x+"; - break; - case input::mouse_axis::negative_y: - mapping_element["motion"] = "y-"; - break; - case input::mouse_axis::positive_y: - mapping_element["motion"] = "y+"; - break; - default: - break; - } - break; - } - - case input::mapping_type::mouse_button: - { - const input::mouse_button_mapping* button_mapping = static_cast(mapping); - mapping_element["device"] = "mouse"; - mapping_element["button"] = button_mapping->button; - break; - } - - case input::mapping_type::gamepad_axis: - { - const input::gamepad_axis_mapping* axis_mapping = static_cast(mapping); - mapping_element["device"] = "gamepad"; - switch (axis_mapping->axis) - { - case input::gamepad_axis::left_x: - if (axis_mapping->negative) - mapping_element["axis"] = "leftx-"; - else - mapping_element["axis"] = "leftx+"; - break; - case input::gamepad_axis::left_y: - if (axis_mapping->negative) - mapping_element["axis"] = "lefty-"; - else - mapping_element["axis"] = "lefty+"; - break; - case input::gamepad_axis::right_x: - if (axis_mapping->negative) - mapping_element["axis"] = "rightx-"; - else - mapping_element["axis"] = "rightx+"; - break; - case input::gamepad_axis::right_y: - if (axis_mapping->negative) - mapping_element["axis"] = "righty-"; - else - mapping_element["axis"] = "righty+"; - break; - case input::gamepad_axis::left_trigger: - mapping_element["axis"] = "lefttrigger+"; - break; - case input::gamepad_axis::right_trigger: - mapping_element["axis"] = "righttrigger+"; - break; - default: - break; - } - break; - } - - case input::mapping_type::gamepad_button: - { - const input::gamepad_button_mapping* button_mapping = static_cast(mapping); - mapping_element["device"] = "gamepad"; - - switch (button_mapping->button) - { - case input::gamepad_button::a: - mapping_element["button"] = "a"; - break; - case input::gamepad_button::b: - mapping_element["button"] = "b"; - break; - case input::gamepad_button::x: - mapping_element["button"] = "x"; - break; - case input::gamepad_button::y: - mapping_element["button"] = "y"; - break; - case input::gamepad_button::back: - mapping_element["button"] = "back"; - break; - case input::gamepad_button::guide: - mapping_element["button"] = "guide"; - break; - case input::gamepad_button::start: - mapping_element["button"] = "start"; - break; - case input::gamepad_button::left_stick: - mapping_element["button"] = "leftstick"; - break; - case input::gamepad_button::right_stick: - mapping_element["button"] = "rightstick"; - break; - case input::gamepad_button::left_shoulder: - mapping_element["button"] = "leftshoulder"; - break; - case input::gamepad_button::right_shoulder: - mapping_element["button"] = "rightshoulder"; - break; - case input::gamepad_button::dpad_up: - mapping_element["button"] = "dpup"; - break; - case input::gamepad_button::dpad_down: - mapping_element["button"] = "dpdown"; - break; - case input::gamepad_button::dpad_left: - mapping_element["button"] = "dpleft"; - break; - case input::gamepad_button::dpad_right: - mapping_element["button"] = "dpright"; - break; - default: - break; - } - break; - } - - default: - break; - } - - control_element.push_back(mapping_element); - } - } - - std::ofstream control_profile_file(path); - control_profile_file << control_profile; - } - catch (...) - { - debug::log::pop_task(EXIT_FAILURE); - } - debug::log::pop_task(EXIT_SUCCESS); - */ -} - -void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration) -{ - /* - // Parse and apply activation thresholds - if (calibration.contains("leftx_activation")) - { - float min = calibration["leftx_activation"][0].get(); - float max = calibration["leftx_activation"][1].get(); - gamepad.set_activation_threshold(input::gamepad_axis::left_x, min, max); - } - if (calibration.contains("lefty_activation")) - { - float min = calibration["lefty_activation"][0].get(); - float max = calibration["lefty_activation"][1].get(); - gamepad.set_activation_threshold(input::gamepad_axis::left_y, min, max); - } - if (calibration.contains("rightx_activation")) - { - float min = calibration["rightx_activation"][0].get(); - float max = calibration["rightx_activation"][1].get(); - gamepad.set_activation_threshold(input::gamepad_axis::right_x, min, max); - } - if (calibration.contains("righty_activation")) - { - float min = calibration["righty_activation"][0].get(); - float max = calibration["righty_activation"][1].get(); - gamepad.set_activation_threshold(input::gamepad_axis::right_y, min, max); - } - if (calibration.contains("lefttrigger_activation")) - { - float min = calibration["lefttrigger_activation"][0].get(); - float max = calibration["lefttrigger_activation"][1].get(); - gamepad.set_activation_threshold(input::gamepad_axis::left_trigger, min, max); - } - if (calibration.contains("righttrigger_activation")) - { - float min = calibration["righttrigger_activation"][0].get(); - float max = calibration["righttrigger_activation"][1].get(); - gamepad.set_activation_threshold(input::gamepad_axis::right_trigger, min, max); - } - - // Parse and apply deadzone shapes - if (calibration.contains("left_deadzone_cross")) - gamepad.set_left_deadzone_cross(calibration["left_deadzone_cross"].get()); - if (calibration.contains("right_deadzone_cross")) - gamepad.set_right_deadzone_cross(calibration["right_deadzone_cross"].get()); - if (calibration.contains("left_deadzone_roundness")) - gamepad.set_left_deadzone_roundness(calibration["left_deadzone_roundness"].get()); - if (calibration.contains("right_deadzone_roundness")) - gamepad.set_right_deadzone_roundness(calibration["right_deadzone_roundness"].get()); - - auto parse_response_curve = [](const std::string& curve) -> input::gamepad_response_curve - { - if (curve == "square") - return input::gamepad_response_curve::square; - else if (curve == "cube") - return input::gamepad_response_curve::cube; - return input::gamepad_response_curve::linear; - }; - - // Parse and apply axis response curves - if (calibration.contains("leftx_response_curve")) - { - auto curve = parse_response_curve(calibration["leftx_response_curve"].get()); - gamepad.set_response_curve(input::gamepad_axis::left_x, curve); - } - if (calibration.contains("lefty_response_curve")) - { - auto curve = parse_response_curve(calibration["lefty_response_curve"].get()); - gamepad.set_response_curve(input::gamepad_axis::left_y, curve); - } - if (calibration.contains("rightx_response_curve")) - { - auto curve = parse_response_curve(calibration["rightx_response_curve"].get()); - gamepad.set_response_curve(input::gamepad_axis::right_x, curve); - } - if (calibration.contains("righty_response_curve")) - { - auto curve = parse_response_curve(calibration["righty_response_curve"].get()); - gamepad.set_response_curve(input::gamepad_axis::right_y, curve); - } - if (calibration.contains("lefttrigger_response_curve")) - { - auto curve = parse_response_curve(calibration["lefttrigger_response_curve"].get()); - gamepad.set_response_curve(input::gamepad_axis::left_trigger, curve); - } - if (calibration.contains("righttrigger_response_curve")) - { - auto curve = parse_response_curve(calibration["righttrigger_response_curve"].get()); - gamepad.set_response_curve(input::gamepad_axis::right_trigger, curve); - } - */ -} - } // namespace game diff --git a/src/game/controls.hpp b/src/game/controls.hpp index b6a7a1a..7c87480 100644 --- a/src/game/controls.hpp +++ b/src/game/controls.hpp @@ -27,75 +27,38 @@ namespace game { -void setup_window_controls(game::context& ctx); -void setup_menu_controls(game::context& ctx); - -void enable_window_controls(game::context& ctx); -void enable_menu_controls(game::context& ctx); - -void disable_window_controls(game::context& ctx); -void disable_menu_controls(game::context& ctx); - - /** - * Applies a control profile to the game context. + * Resets a control profile to default settings. * - * @param ctx Game context. - * @param profile Control profile. + * @param profile Control profile to reset. */ -void apply_control_profile(game::context& ctx, const json& profile); +void reset_control_profile(game::control_profile& profile); /** - * Saves the current control profile. + * Applies a control profile to the game context. * * @param ctx Game context. + * @param profile Control profile to apply. */ -void save_control_profile(game::context& ctx); - -/** - * Generates a default control profile. - * - * @return Default control profile. - */ -json default_control_profile(); - -/** - * Returns a string containing the path to the gamepad calibration file. - */ -std::filesystem::path gamepad_calibration_path(const game::context& ctx, const input::gamepad& gamepad); - -/** - * Generates default gamepad calibration settings. - * - * @return Default gamepad calibration settings. - */ -json default_gamepad_calibration(); +void apply_control_profile(game::context& ctx, const game::control_profile& profile); /** - * Loads gamepad calibration settings. + * Updates a control profile after actions have been remapped. * * @param ctx Game context. - * @param gamepad Gamepad for which to load calibration settings. - * @return Gamepad calibration settings, or `nullptr` if not loaded. + * @param profile Control profile to update. */ -json* load_gamepad_calibration(game::context& ctx, const input::gamepad& gamepad); +void update_control_profile(game::context& ctx, game::control_profile& profile); -/** - * Saves gamepad calibration settings. - * - * @param ctx Game context. - * @param gamepad Gamepad for which to save calibration settings. - * @return `true` if calibration settings were successfully saved, `false` otherwise. - */ -bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& gamepad, const json& calibration); +void setup_window_controls(game::context& ctx); +void setup_menu_controls(game::context& ctx); +void setup_game_controls(game::context& ctx); -/** - * Applies gamepad calibration settings. - * - * @param gamepad Gamepad to calibrate. - * @param calibration JSON element containing gamepad calibration settings. - */ -void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration); +void enable_window_controls(game::context& ctx); +void enable_menu_controls(game::context& ctx); + +void disable_window_controls(game::context& ctx); +void disable_menu_controls(game::context& ctx); } // namespace game diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp index ab09de0..90147a2 100644 --- a/src/game/fonts.cpp +++ b/src/game/fonts.cpp @@ -46,7 +46,7 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const for (char32_t code: charset) { // Skip missing glyphs - if (!typeface.has_glyph(code)) + if (!typeface.get_charset().contains(code)) continue; // Add glyph to font @@ -101,6 +101,7 @@ void load_fonts(game::context& ctx) } // Build character set + /* std::unordered_set charset; { // Add all character codes from the basic Latin unicode block @@ -108,8 +109,7 @@ void load_fonts(game::context& ctx) charset.insert(code); // Add all character codes from game strings - const auto& string_map = ctx.string_maps[ctx.language_index]; - for (auto it = string_map.begin(); it != string_map.end(); ++it) + for (auto it = ctx.string_map->begin(); it != ctx.string_map->end(); ++it) { // Convert UTF-8 string to UTF-32 std::wstring_convert, char32_t> convert; @@ -120,6 +120,7 @@ void load_fonts(game::context& ctx) charset.insert(code); } } + */ // Load bitmap font shader gl::shader_program* bitmap_font_shader = ctx.resource_manager->load("bitmap-font.glsl"); @@ -131,19 +132,19 @@ void load_fonts(game::context& ctx) // Build debug font if (auto it = ctx.typefaces.find("monospace"); it != ctx.typefaces.end()) { - build_bitmap_font(*it->second, ctx.debug_font_size_pt * pt_to_px, charset, ctx.debug_font, ctx.debug_font_material, bitmap_font_shader); + build_bitmap_font(*it->second, ctx.debug_font_size_pt * pt_to_px, it->second->get_charset(), ctx.debug_font, ctx.debug_font_material, bitmap_font_shader); } // Build menu font if (auto it = ctx.typefaces.find("sans_serif"); it != ctx.typefaces.end()) { - build_bitmap_font(*it->second, ctx.menu_font_size_pt * pt_to_px, charset, ctx.menu_font, ctx.menu_font_material, bitmap_font_shader); + build_bitmap_font(*it->second, ctx.menu_font_size_pt * pt_to_px, it->second->get_charset(), ctx.menu_font, ctx.menu_font_material, bitmap_font_shader); } // Build title font if (auto it = ctx.typefaces.find("serif"); it != ctx.typefaces.end()) { - build_bitmap_font(*it->second, ctx.title_font_size_pt * pt_to_px, charset, ctx.title_font, ctx.title_font_material, bitmap_font_shader); + build_bitmap_font(*it->second, ctx.title_font_size_pt * pt_to_px, it->second->get_charset(), ctx.title_font, ctx.title_font_material, bitmap_font_shader); } } diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp index 042e58a..405989f 100644 --- a/src/game/state/boot.cpp +++ b/src/game/state/boot.cpp @@ -29,6 +29,7 @@ #include "entity/commands.hpp" #include "game/context.hpp" #include "game/controls.hpp" +#include "game/control-profile.hpp" #include "game/fonts.hpp" #include "game/graphics.hpp" #include "game/menu.hpp" @@ -91,6 +92,7 @@ #include "utility/hash/fnv1a.hpp" #include "input/application-events.hpp" #include +#include #include #include #include @@ -162,6 +164,7 @@ boot::~boot() delete ctx.window; // Save settings + ctx.resource_manager->set_write_dir(ctx.shared_config_path); ctx.resource_manager->save(ctx.settings, "settings.cfg"); // Destruct input and window managers @@ -294,9 +297,6 @@ void boot::setup_resources() debug::log::info("Shared config path: \"{}\"", ctx.shared_config_path.string()); debug::log::info("Mods path: \"{}\"", ctx.mods_path.string()); - // Set write dir - ctx.resource_manager->set_write_dir(ctx.shared_config_path); - // Create nonexistent config directories std::vector config_paths; config_paths.push_back(ctx.local_config_path); @@ -357,6 +357,7 @@ void boot::load_settings() { // Command-line reset option found, reset settings ctx.settings = new dict(); + ctx.resource_manager->set_write_dir(ctx.shared_config_path); ctx.resource_manager->save(ctx.settings, "settings.cfg"); debug::log::info("Settings reset"); } @@ -538,33 +539,32 @@ void boot::load_strings() debug::log::trace("Loading strings..."); // Default strings settings - ctx.language_index = 0; + ctx.language_tag = "en"; // Read strings settings - read_or_write_setting(ctx, "language_index"_fnv1a32, ctx.language_index); + read_or_write_setting(ctx, "language_tag"_fnv1a32, ctx.language_tag); - // Load string table - ctx.string_table = ctx.resource_manager->load("strings.tsv"); - - // Count languages - ctx.language_count = static_cast((*ctx.string_table)[0].size() - 2); - - if (ctx.language_index >= ctx.language_count) - { - debug::log::error("Language index ({}) exceeds language count ({}). Language index reset to 0", ctx.language_index, ctx.language_count); - ctx.language_index = 0; - (*ctx.settings)["language_index"_fnv1a32] = ctx.language_index; - } + // Slugify language tag + std::string language_slug = ctx.language_tag; + std::transform + ( + language_slug.begin(), + language_slug.end(), + language_slug.begin(), + [](unsigned char c) + { + return std::tolower(c); + } + ); - // Build string map - ctx.string_maps.resize(ctx.language_count); - i18n::build_string_map(*ctx.string_table, 0, ctx.language_index + 2, ctx.string_maps[ctx.language_index]); + // Load string map + ctx.string_map = ctx.resource_manager->load(language_slug + ".str"); // Log language info - debug::log::info("Language index: {}; code: {}", ctx.language_index, get_string(ctx, "language_code"_fnv1a32)); + debug::log::info("Language tag: {}", ctx.language_tag); // Change window title - const std::string window_title = get_string(ctx, "application_title"_fnv1a32); + const std::string window_title = get_string(ctx, "window_title"_fnv1a32); ctx.window->set_title(window_title); // Update window title setting @@ -791,9 +791,13 @@ void boot::setup_audio() // Get audio device name const ALCchar* alc_device_name = nullptr; if (alcIsExtensionPresent(ctx.alc_device, "ALC_ENUMERATE_ALL_EXT")) + { alc_device_name = alcGetString(ctx.alc_device, ALC_ALL_DEVICES_SPECIFIER); + } if (alcGetError(ctx.alc_device) != AL_NO_ERROR || !alc_device_name) + { alc_device_name = alcGetString(ctx.alc_device, ALC_DEVICE_SPECIFIER); + } // Log audio device name debug::log::info("Opened audio device \"{}\"", alc_device_name); @@ -951,6 +955,8 @@ void boot::setup_animation() ctx.fade_transition->get_material()->set_shader_program(ctx.resource_manager->load("fade-transition.glsl")); ctx.fade_transition_color = ctx.fade_transition->get_material()->add_property("color"); ctx.fade_transition_color->set_value({0, 0, 0}); + ctx.fade_transition->get_billboard()->set_translation({0, 0, 98}); + ctx.fade_transition->get_billboard()->update_tweens(); ctx.ui_scene->add_object(ctx.fade_transition->get_billboard()); ctx.animator->add_animation(ctx.fade_transition->get_animation()); @@ -1112,6 +1118,8 @@ void boot::setup_systems() void boot::setup_controls() { + debug::log::trace("Setting up controls..."); + // Load SDL game controller mappings database // debug::log::trace("Loading SDL game controller mappings..."); // file_buffer* game_controller_db = ctx.resource_manager->load("gamecontrollerdb.txt"); @@ -1127,70 +1135,49 @@ void boot::setup_controls() // ctx.resource_manager->unload("gamecontrollerdb.txt"); // } + // Default control profile settings + ctx.control_profile_filename = "controls.cfg"; + ctx.control_profile = nullptr; - setup_window_controls(ctx); - setup_menu_controls(ctx); - - enable_window_controls(ctx); - - // Load controls - debug::log::trace("Loading controls..."); - try + // Read control profile settings + if (read_or_write_setting(ctx, "control_profile"_fnv1a32, ctx.control_profile_filename)) { // Load control profile - // if (ctx.config->contains("control_profile")) - // { - // json* profile = ctx.resource_manager->load((*ctx.config)["control_profile"].get()); - - // if (profile) - // { - // game::apply_control_profile(ctx, *profile); - // } - // } - - // Calibrate gamepads - // for (input::gamepad* gamepad: ctx.app->get_gamepads()) - // { - // const std::string uuid_string = gamepad->get_uuid().to_string(); - - // debug::log::push_task("Loading calibration for gamepad " + uuid_string); - // json* calibration = game::load_gamepad_calibration(ctx, *gamepad); - // if (!calibration) - // { - // debug::log::pop_task(EXIT_FAILURE); - - // debug::log::push_task("Generating default calibration for gamepad " + uuid_string); - // json default_calibration = game::default_gamepad_calibration(); - // apply_gamepad_calibration(*gamepad, default_calibration); - - // if (!save_gamepad_calibration(ctx, *gamepad, default_calibration)) - // debug::log::pop_task(EXIT_FAILURE); - // else - // debug::log::pop_task(EXIT_SUCCESS); - // } - // else - // { - // debug::log::pop_task(EXIT_SUCCESS); - // apply_gamepad_calibration(*gamepad, *calibration); - // } - // } - - - debug::log::trace("Loaded controls"); + //ctx.control_profile = ctx.resource_manager->load(ctx.controls_path / ctx.control_profile_filename); + ctx.control_profile = ctx.resource_manager->load(ctx.control_profile_filename); } - catch (...) + + if (!ctx.control_profile) { - debug::log::error("Failed to load controls"); + // Allocate control profile + ctx.control_profile = new game::control_profile(); + + // Reset control profile to default settings. + game::reset_control_profile(*ctx.control_profile); + + // Save control profile + ctx.resource_manager->set_write_dir(ctx.controls_path); + ctx.resource_manager->save(ctx.control_profile, ctx.control_profile_filename); } - + // Apply control profile + game::apply_control_profile(ctx, *ctx.control_profile); + + // Setup action callbacks + setup_window_controls(ctx); + setup_menu_controls(ctx); + + // Enable window controls + enable_window_controls(ctx); + + debug::log::trace("Set up controls"); } void boot::setup_ui() { // Default UI settings ctx.font_scale = 1.0f; - ctx.debug_font_size_pt = 12.0f; + ctx.debug_font_size_pt = 10.0f; ctx.menu_font_size_pt = 22.0f; ctx.title_font_size_pt = 80.0f; ctx.dyslexia_font = false; @@ -1245,6 +1232,10 @@ void boot::setup_ui() ctx.ui_camera->get_clip_far() ); + // Re-align debug text + ctx.frame_time_text->set_translation({std::round(0.0f), std::round(viewport_size.y() - ctx.debug_font.get_font_metrics().size), 99.0f}); + ctx.frame_time_text->update_tweens(); + // Re-align menu text game::menu::align_text(ctx); } @@ -1255,6 +1246,17 @@ void boot::setup_debugging() { ctx.cli = new debug::cli(); //debug::log::info(ctx.cli->interpret("echo hi 123")); + + const auto& viewport_size = ctx.window->get_viewport_size(); + + ctx.frame_time_text = new scene::text(); + ctx.frame_time_text->set_material(&ctx.debug_font_material); + ctx.frame_time_text->set_color({1.0f, 1.0f, 0.0f, 1.0f}); + ctx.frame_time_text->set_font(&ctx.debug_font); + ctx.frame_time_text->set_translation({std::round(0.0f), std::round(viewport_size.y() - ctx.debug_font.get_font_metrics().size), 99.0f}); + ctx.frame_time_text->update_tweens(); + + ctx.ui_scene->add_object(ctx.frame_time_text); } void boot::setup_loop() @@ -1340,6 +1342,7 @@ void boot::setup_loop() void boot::loop() { ctx.closed = false; + math::moving_average average_frame_time; while (!ctx.closed) { @@ -1347,7 +1350,9 @@ void boot::loop() ctx.loop.tick(); // Sample frame duration - ctx.average_frame_time(static_cast(ctx.loop.get_frame_duration())); + average_frame_time(static_cast(ctx.loop.get_frame_duration() * 1000.0)); + + ctx.frame_time_text->set_content(std::format("â—·{:5.02f}", average_frame_time.average())); } // Exit all active game states diff --git a/src/game/state/gamepad-config-menu.cpp b/src/game/state/gamepad-config-menu.cpp index f7aa6d2..05edf7a 100644 --- a/src/game/state/gamepad-config-menu.cpp +++ b/src/game/state/gamepad-config-menu.cpp @@ -35,21 +35,19 @@ namespace game { namespace state { gamepad_config_menu::gamepad_config_menu(game::context& ctx): - game::state::base(ctx) + game::state::base(ctx), + action_remapped(false) { debug::log::trace("Entering gamepad config menu state..."); - // Add camera control menu items - add_control_item("move_forward"); - add_control_item("move_back"); - add_control_item("move_left"); - add_control_item("move_right"); - add_control_item("move_up"); - add_control_item("move_down"); - - // Add application control menu items - add_control_item("toggle_fullscreen"); - add_control_item("screenshot"); + // Add control menu items + add_control_item(ctx.movement_actions, ctx.move_forward_action, "control_move_forward"_fnv1a32); + add_control_item(ctx.movement_actions, ctx.move_back_action, "control_move_back"_fnv1a32); + add_control_item(ctx.movement_actions, ctx.move_left_action, "control_move_left"_fnv1a32); + add_control_item(ctx.movement_actions, ctx.move_right_action, "control_move_right"_fnv1a32); + add_control_item(ctx.movement_actions, ctx.move_up_action, "control_move_up"_fnv1a32); + add_control_item(ctx.movement_actions, ctx.move_down_action, "control_move_down"_fnv1a32); + add_control_item(ctx.movement_actions, ctx.pause_action, "control_pause"_fnv1a32); // Construct menu item texts scene::text* back_text = new scene::text(); @@ -126,168 +124,173 @@ gamepad_config_menu::~gamepad_config_menu() game::menu::remove_text_from_ui(ctx); game::menu::delete_text(ctx); - // Save control profile - game::save_control_profile(ctx); + if (action_remapped) + { + // Update control profile + game::update_control_profile(ctx, *ctx.control_profile); + + // Save control profile + ctx.resource_manager->set_write_dir(ctx.controls_path); + ctx.resource_manager->save(ctx.control_profile, ctx.control_profile_filename); + } debug::log::trace("Exited gamepad config menu state"); } -std::string gamepad_config_menu::get_binding_string(input::action* control) +std::string gamepad_config_menu::get_mapping_string(const input::action_map& action_map, const input::action& control) { - std::string binding_string; - /* - auto mappings = ctx.input_event_router->get_mappings(control); - for (input::mapping* mapping: *mappings) + std::string mapping_string; + + if (auto gamepad_axis_mappings = action_map.get_gamepad_axis_mappings(control); !gamepad_axis_mappings.empty()) { - std::string mapping_string; + const auto& gamepad_axis_mapping = gamepad_axis_mappings.front(); - switch (mapping->get_type()) + switch (gamepad_axis_mapping.axis) { - case input::mapping_type::gamepad_axis: - { - const input::gamepad_axis_mapping* axis_mapping = static_cast(mapping); - - switch (axis_mapping->axis) + case input::gamepad_axis::left_stick_x: + if (gamepad_axis_mapping.direction) + { + mapping_string = get_string(ctx, "gamepad_left_stick_left"_fnv1a32); + } + else { - case input::gamepad_axis::left_x: - if (axis_mapping->negative) - mapping_string = (*ctx.strings)["gamepad_left_stick_left"]; - else - mapping_string = (*ctx.strings)["gamepad_left_stick_right"]; - break; - - case input::gamepad_axis::left_y: - if (axis_mapping->negative) - mapping_string = (*ctx.strings)["gamepad_left_stick_up"]; - else - mapping_string = (*ctx.strings)["gamepad_left_stick_down"]; - break; - - case input::gamepad_axis::right_x: - if (axis_mapping->negative) - mapping_string = (*ctx.strings)["gamepad_right_stick_left"]; - else - mapping_string = (*ctx.strings)["gamepad_right_stick_right"]; - break; - - case input::gamepad_axis::right_y: - if (axis_mapping->negative) - mapping_string = (*ctx.strings)["gamepad_right_stick_up"]; - else - mapping_string = (*ctx.strings)["gamepad_right_stick_down"]; - break; - - case input::gamepad_axis::left_trigger: - mapping_string = (*ctx.strings)["gamepad_left_trigger"]; - break; - - case input::gamepad_axis::right_trigger: - mapping_string = (*ctx.strings)["gamepad_right_trigger"]; - break; - - default: - break; + mapping_string = get_string(ctx, "gamepad_left_stick_right"_fnv1a32); } break; - } - case input::mapping_type::gamepad_button: - { - const input::gamepad_button_mapping* button_mapping = static_cast(mapping); - - switch (button_mapping->button) + case input::gamepad_axis::left_stick_y: + if (gamepad_axis_mapping.direction) + { + mapping_string = get_string(ctx, "gamepad_left_stick_up"_fnv1a32); + } + else { - case input::gamepad_button::a: - mapping_string = (*ctx.strings)["gamepad_button_a"]; - break; - - case input::gamepad_button::b: - mapping_string = (*ctx.strings)["gamepad_button_b"]; - break; - - case input::gamepad_button::x: - mapping_string = (*ctx.strings)["gamepad_button_x"]; - break; - - case input::gamepad_button::y: - mapping_string = (*ctx.strings)["gamepad_button_y"]; - break; - - case input::gamepad_button::back: - mapping_string = (*ctx.strings)["gamepad_button_back"]; - break; - - case input::gamepad_button::guide: - mapping_string = (*ctx.strings)["gamepad_button_guide"]; - break; - - case input::gamepad_button::start: - mapping_string = (*ctx.strings)["gamepad_button_start"]; - break; - - case input::gamepad_button::left_stick: - mapping_string = (*ctx.strings)["gamepad_button_left_stick"]; - break; - - case input::gamepad_button::right_stick: - mapping_string = (*ctx.strings)["gamepad_button_right_stick"]; - break; - - case input::gamepad_button::left_shoulder: - mapping_string = (*ctx.strings)["gamepad_button_left_shoulder"]; - break; - - case input::gamepad_button::right_shoulder: - mapping_string = (*ctx.strings)["gamepad_button_right_shoulder"]; - break; - - case input::gamepad_button::dpad_up: - mapping_string = (*ctx.strings)["gamepad_button_dpad_up"]; - break; - - case input::gamepad_button::dpad_down: - mapping_string = (*ctx.strings)["gamepad_button_dpad_down"]; - break; - - case input::gamepad_button::dpad_left: - mapping_string = (*ctx.strings)["gamepad_button_dpad_left"]; - break; - - case input::gamepad_button::dpad_right: - mapping_string = (*ctx.strings)["gamepad_button_dpad_right"]; - break; - - default: - break; + mapping_string = get_string(ctx, "gamepad_left_stick_down"_fnv1a32); + } + break; + + case input::gamepad_axis::right_stick_x: + if (gamepad_axis_mapping.direction) + { + mapping_string = get_string(ctx, "gamepad_right_stick_left"_fnv1a32); + } + else + { + mapping_string = get_string(ctx, "gamepad_right_stick_right"_fnv1a32); } break; - } + + case input::gamepad_axis::right_stick_y: + if (gamepad_axis_mapping.direction) + { + mapping_string = get_string(ctx, "gamepad_right_stick_up"_fnv1a32); + } + else + { + mapping_string = get_string(ctx, "gamepad_right_stick_down"_fnv1a32); + } + break; + + case input::gamepad_axis::left_trigger: + mapping_string = get_string(ctx, "gamepad_left_trigger"_fnv1a32); + break; + + case input::gamepad_axis::right_trigger: + mapping_string = get_string(ctx, "gamepad_right_trigger"_fnv1a32); + break; default: + { + const char sign = (gamepad_axis_mapping.direction) ? '-' : '+'; + const std::string format_string = get_string(ctx, "gamepad_axis_n_format"_fnv1a32); + mapping_string = std::vformat(format_string, std::make_format_args(std::to_underlying(gamepad_axis_mapping.axis), sign)); break; + } } - - if (!mapping_string.empty()) + } + else if (auto gamepad_button_mappings = action_map.get_gamepad_button_mappings(control); !gamepad_button_mappings.empty()) + { + const auto& gamepad_button_mapping = gamepad_button_mappings.front(); + switch (gamepad_button_mapping.button) { - if (binding_string.empty()) - { - binding_string = mapping_string; - } - else + case input::gamepad_button::a: + mapping_string = get_string(ctx, "gamepad_button_a"_fnv1a32); + break; + + case input::gamepad_button::b: + mapping_string = get_string(ctx, "gamepad_button_b"_fnv1a32); + break; + + case input::gamepad_button::x: + mapping_string = get_string(ctx, "gamepad_button_x"_fnv1a32); + break; + + case input::gamepad_button::y: + mapping_string = get_string(ctx, "gamepad_button_y"_fnv1a32); + break; + + case input::gamepad_button::back: + mapping_string = get_string(ctx, "gamepad_button_back"_fnv1a32); + break; + + case input::gamepad_button::guide: + mapping_string = get_string(ctx, "gamepad_button_guide"_fnv1a32); + break; + + case input::gamepad_button::start: + mapping_string = get_string(ctx, "gamepad_button_start"_fnv1a32); + break; + + case input::gamepad_button::left_stick: + mapping_string = get_string(ctx, "gamepad_button_left_stick"_fnv1a32); + break; + + case input::gamepad_button::right_stick: + mapping_string = get_string(ctx, "gamepad_button_right_stick"_fnv1a32); + break; + + case input::gamepad_button::left_shoulder: + mapping_string = get_string(ctx, "gamepad_button_left_shoulder"_fnv1a32); + break; + + case input::gamepad_button::right_shoulder: + mapping_string = get_string(ctx, "gamepad_button_right_shoulder"_fnv1a32); + break; + + case input::gamepad_button::dpad_up: + mapping_string = get_string(ctx, "gamepad_button_dpad_up"_fnv1a32); + break; + + case input::gamepad_button::dpad_down: + mapping_string = get_string(ctx, "gamepad_button_dpad_down"_fnv1a32); + break; + + case input::gamepad_button::dpad_left: + mapping_string = get_string(ctx, "gamepad_button_dpad_left"_fnv1a32); + break; + + case input::gamepad_button::dpad_right: + mapping_string = get_string(ctx, "gamepad_button_dpad_right"_fnv1a32); + break; + + default: { - binding_string += " " + mapping_string; + const std::string format_string = get_string(ctx, "gamepad_button_n_format"_fnv1a32); + mapping_string = std::vformat(format_string, std::make_format_args(std::to_underlying(gamepad_button_mapping.button))); + break; } } } - */ - return binding_string; + else + { + mapping_string = get_string(ctx, "control_unmapped"_fnv1a32); + } + + return mapping_string; } -void gamepad_config_menu::add_control_item(const std::string& control_name) +void gamepad_config_menu::add_control_item(input::action_map& action_map, input::action& control, std::uint32_t control_name_hash) { - // Get pointer to control - //input::action* control = ctx.controls[control_name]; - // Construct texts scene::text* name_text = new scene::text(); scene::text* value_text = new scene::text(); @@ -295,76 +298,71 @@ void gamepad_config_menu::add_control_item(const std::string& control_name) // Add texts to list of menu item texts ctx.menu_item_texts.push_back({name_text, value_text}); - // Set content of name text - name_text->set_content(control_name); + // Set control name and mapping texts + name_text->set_content(get_string(ctx, control_name_hash)); + value_text->set_content(get_mapping_string(action_map, control)); - // Set content of value text - //value_text->set_content(get_binding_string(control)); - - auto select_callback = [this, &ctx = this->ctx, value_text]() + // Callback invoked when an input has been mapped to the control + auto input_mapped_callback = [this, &ctx = this->ctx, action_map = &action_map, control = &control, value_text](const auto& event) { - // Disable menu controls - ctx.function_queue.push(std::bind(game::disable_menu_controls, std::ref(ctx))); + if (event.mapping.get_mapping_type() != input::mapping_type::key) + { + this->action_remapped = true; + + // Remove gamepad axis mappings and gamepad button mappings mapped to the control + action_map->remove_mappings(*control, input::mapping_type::gamepad_axis); + action_map->remove_mappings(*control, input::mapping_type::gamepad_button); + + action_map->add_mapping(*control, event.mapping); + } - // Clear binding string from value text - value_text->set_content(get_string(ctx, "ellipsis"_fnv1a32)); + // Update control mapping text + value_text->set_content(this->get_mapping_string(*action_map, *control)); game::menu::align_text(ctx); game::menu::update_text_tweens(ctx); - /* - // Remove gamepad event mappings from control - ctx.input_event_router->remove_mappings(control, input::mapping_type::gamepad_axis); - ctx.input_event_router->remove_mappings(control, input::mapping_type::gamepad_button); + // Queue disabling of input mapper re-enabling of menu controls + ctx.function_queue.push + ( + [&ctx]() + { + ctx.input_mapper.disconnect(); + game::enable_menu_controls(ctx); + } + ); + }; + + // Callback invoked when the control menu item has been selected + auto select_callback = [this, &ctx = this->ctx, action_map = &action_map, control = &control, value_text, input_mapped_callback]() + { + // Set control mapping text to "..." + value_text->set_content(get_string(ctx, "control_mapping"_fnv1a32)); + game::menu::align_text(ctx); + game::menu::update_text_tweens(ctx); - // Setup input binding listener - ctx.input_listener->set_callback + // Setup input mapped callbacks + gamepad_axis_mapped_subscription = ctx.input_mapper.get_gamepad_axis_mapped_channel().subscribe ( - [this, &ctx, control, value_text](const event_base& event) + input_mapped_callback + ); + gamepad_button_mapped_subscription = ctx.input_mapper.get_gamepad_button_mapped_channel().subscribe + ( + input_mapped_callback + ); + key_mapped_subscription = ctx.input_mapper.get_key_mapped_channel().subscribe + ( + input_mapped_callback + ); + + // Queue disabling of menu controls and enabling of input mapper + ctx.function_queue.push + ( + [&]() { - auto id = event.get_event_type_id(); - if (id == gamepad_axis_moved_event::event_type_id) - { - // Map gamepad axis event to control - const gamepad_axis_moved_event& axis_event = static_cast(event); - if (std::abs(axis_event.value) < 0.5f) - return; - - ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, nullptr, axis_event.axis, (axis_event.value < 0))); - } - else if (id == gamepad_button_pressed_event::event_type_id) - { - // Map gamepad button event to control - const gamepad_button_pressed_event& button_event = static_cast(event); - ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, nullptr, button_event.button)); - } - else if (id == key_pressed_event::event_type_id) - { - // Map key pressed event to control - const key_pressed_event& key_event = static_cast(event); - - if (key_event.scancode != input::scancode::escape && key_event.scancode != input::scancode::backspace) - return; - } - else - { - return; - } - - // Update menu text - value_text->set_content(this->get_binding_string(control)); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - - // Disable input listener - ctx.input_listener->set_enabled(false); - ctx.input_listener->set_callback(nullptr); - - // Queue menu control setup - ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx))); + game::disable_menu_controls(ctx); + ctx.input_mapper.connect(ctx.input_manager->get_event_queue()); } ); - ctx.input_listener->set_enabled(true); - */ }; // Register menu item callbacks diff --git a/src/game/state/gamepad-config-menu.hpp b/src/game/state/gamepad-config-menu.hpp index 28d4acf..7846f3e 100644 --- a/src/game/state/gamepad-config-menu.hpp +++ b/src/game/state/gamepad-config-menu.hpp @@ -22,7 +22,10 @@ #include "game/state/base.hpp" #include "input/action.hpp" -#include +#include "input/action-map.hpp" +#include "event/subscription.hpp" +#include +#include namespace game { namespace state { @@ -34,8 +37,14 @@ public: virtual ~gamepad_config_menu(); private: - std::string get_binding_string(input::action* control); - void add_control_item(const std::string& control_name); + std::string get_mapping_string(const input::action_map& action_map, const input::action& control); + void add_control_item(input::action_map& action_map, input::action& control, std::uint32_t control_name_hash); + + std::shared_ptr gamepad_axis_mapped_subscription; + std::shared_ptr gamepad_button_mapped_subscription; + std::shared_ptr key_mapped_subscription; + + bool action_remapped; }; } // namespace state diff --git a/src/game/state/keyboard-config-menu.cpp b/src/game/state/keyboard-config-menu.cpp index dac0de0..caf96eb 100644 --- a/src/game/state/keyboard-config-menu.cpp +++ b/src/game/state/keyboard-config-menu.cpp @@ -36,7 +36,8 @@ namespace game { namespace state { keyboard_config_menu::keyboard_config_menu(game::context& ctx): - game::state::base(ctx) + game::state::base(ctx), + action_remapped(false) { debug::log::trace("Entering keyboard config menu state..."); @@ -124,8 +125,15 @@ keyboard_config_menu::~keyboard_config_menu() game::menu::remove_text_from_ui(ctx); game::menu::delete_text(ctx); - // Save control profile - game::save_control_profile(ctx); + if (action_remapped) + { + // Update control profile + game::update_control_profile(ctx, *ctx.control_profile); + + // Save control profile + ctx.resource_manager->set_write_dir(ctx.controls_path); + ctx.resource_manager->save(ctx.control_profile, ctx.control_profile_filename); + } debug::log::trace("Exited keyboard config menu state..."); } @@ -220,6 +228,8 @@ void keyboard_config_menu::add_control_item(input::action_map& action_map, input // Callback invoked when an input has been mapped to the control auto input_mapped_callback = [this, &ctx = this->ctx, action_map = &action_map, control = &control, value_text](const auto& event) { + this->action_remapped = true; + // Remove key mappings, mouse button mappings, and mouse scroll mappings mapped to the control action_map->remove_mappings(*control, input::mapping_type::key); action_map->remove_mappings(*control, input::mapping_type::mouse_button); diff --git a/src/game/state/keyboard-config-menu.hpp b/src/game/state/keyboard-config-menu.hpp index 588276d..9ea0df2 100644 --- a/src/game/state/keyboard-config-menu.hpp +++ b/src/game/state/keyboard-config-menu.hpp @@ -43,6 +43,8 @@ private: std::shared_ptr key_mapped_subscription; std::shared_ptr mouse_button_mapped_subscription; std::shared_ptr mouse_scroll_mapped_subscription; + + bool action_remapped; }; } // namespace state diff --git a/src/game/state/language-menu.cpp b/src/game/state/language-menu.cpp index 12a5d54..c4496b6 100644 --- a/src/game/state/language-menu.cpp +++ b/src/game/state/language-menu.cpp @@ -26,6 +26,9 @@ #include "game/menu.hpp" #include "game/strings.hpp" #include "utility/hash/fnv1a.hpp" +#include "resources/resource-manager.hpp" +#include +#include using namespace hash::literals; @@ -37,6 +40,25 @@ language_menu::language_menu(game::context& ctx): { debug::log::trace("Entering language menu state..."); + /// @TODO Don't hardcode this + language_tags = + { + "en", + "zh-Hans", + "zh-Hant" + }; + + // Determine index of current language + language_index = 0; + for (std::size_t i = 0; i < language_tags.size(); ++i) + { + if (ctx.language_tag == language_tags[i]) + { + language_index = i; + break; + } + } + // Construct menu item texts scene::text* language_name_text = new scene::text(); scene::text* language_value_text = new scene::text(); @@ -59,69 +81,64 @@ language_menu::language_menu(game::context& ctx): game::menu::add_text_to_ui(ctx); game::menu::setup_animations(ctx); - // Construct menu item callbacks - auto next_language_callback = [this, &ctx]() + auto change_language = [this, &ctx]() { - // Increment language index - ctx.language_index = (ctx.language_index + 1) % ctx.language_count; + const std::string& language_tag = this->language_tags[this->language_index]; + + // Slugify language tag + std::string language_slug = language_tag; + std::transform + ( + language_slug.begin(), + language_slug.end(), + language_slug.begin(), + [](unsigned char c) + { + return std::tolower(c); + } + ); - // Update language index setting - (*ctx.settings)["language_index"_fnv1a32] = ctx.language_index; + // Load language strings + ctx.string_map = ctx.resource_manager->load(language_slug + ".str"); - // Build string map for current language if not built - if (ctx.string_maps[ctx.language_index].empty()) - { - i18n::build_string_map(*ctx.string_table, 0, ctx.language_index + 2, ctx.string_maps[ctx.language_index]); - } + // Update language settings + ctx.language_tag = language_tag; + (*ctx.settings)["language_tag"_fnv1a32] = ctx.language_tag; // Log language change - debug::log::info("Language index: {}; code: {}", ctx.language_index, get_string(ctx, "language_code"_fnv1a32)); + debug::log::info("Language tag: {}", ctx.language_tag); // Reload fonts debug::log::trace("Reloading fonts..."); game::load_fonts(ctx); debug::log::trace("Reloaded fonts"); + // Update menus game::menu::update_text_font(ctx); this->update_text_content(); game::menu::refresh_text(ctx); game::menu::align_text(ctx); game::menu::update_text_tweens(ctx); }; - auto previous_language_callback = [this, &ctx]() + + // Construct menu item callbacks + auto next_language_callback = [this, &ctx, change_language]() { - // Decrement language index - if (ctx.language_index > 0) + this->language_index = (this->language_index + 1) % this->language_tags.size(); + change_language(); + }; + auto previous_language_callback = [this, &ctx, change_language]() + { + if (this->language_index > 0) { - --ctx.language_index; + --this->language_index; } else { - ctx.language_index = ctx.language_count - 1; - } - - // Update language index setting - (*ctx.settings)["language_index"_fnv1a32] = ctx.language_index; - - // Build string map for current language if not built - if (ctx.string_maps[ctx.language_index].empty()) - { - i18n::build_string_map(*ctx.string_table, 0, ctx.language_index + 2, ctx.string_maps[ctx.language_index]); + this->language_index = this->language_tags.size() - 1; } - // Log language change - debug::log::info("Language index: {}; code: {}", ctx.language_index, get_string(ctx, "language_code"_fnv1a32)); - - // Reload fonts - debug::log::trace("Reloading fonts..."); - game::load_fonts(ctx); - debug::log::trace("Reloaded fonts"); - - game::menu::update_text_font(ctx); - this->update_text_content(); - game::menu::refresh_text(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); + change_language(); }; auto select_back_callback = [&ctx]() { @@ -190,7 +207,7 @@ void language_menu::update_text_content() auto [back_name, back_value] = ctx.menu_item_texts[1]; language_name->set_content(get_string(ctx, "language_menu_language"_fnv1a32)); - language_value->set_content(get_string(ctx, "language_name"_fnv1a32)); + language_value->set_content(get_string(ctx, "language_name_native"_fnv1a32)); back_name->set_content(get_string(ctx, "back"_fnv1a32)); } diff --git a/src/game/state/language-menu.hpp b/src/game/state/language-menu.hpp index 6190425..d8840d7 100644 --- a/src/game/state/language-menu.hpp +++ b/src/game/state/language-menu.hpp @@ -21,6 +21,8 @@ #define ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP #include "game/state/base.hpp" +#include +#include namespace game { namespace state { @@ -33,6 +35,9 @@ public: private: void update_text_content(); + + std::vector language_tags; + std::size_t language_index; }; diff --git a/src/game/state/main-menu.cpp b/src/game/state/main-menu.cpp index 2ead391..c39690f 100644 --- a/src/game/state/main-menu.cpp +++ b/src/game/state/main-menu.cpp @@ -23,7 +23,6 @@ #include "animation/ease.hpp" #include "animation/screen-transition.hpp" #include "config.hpp" -#include "game/ant/swarm.hpp" #include "game/component/model.hpp" #include "game/component/steering.hpp" #include "game/component/transform.hpp" @@ -45,6 +44,7 @@ #include "render/passes/sky-pass.hpp" #include "resources/resource-manager.hpp" #include "utility/hash/fnv1a.hpp" +#include #include using namespace hash::literals; @@ -68,15 +68,13 @@ main_menu::main_menu(game::context& ctx, bool fade_in): title_text.set_color({1.0f, 1.0f, 1.0f, (fade_in) ? 1.0f : 0.0f}); title_text.set_font(&ctx.title_font); title_text.set_content(get_string(ctx, "title_antkeeper"_fnv1a32)); - - // Align title text const auto& title_aabb = static_cast&>(title_text.get_local_bounds()); float title_w = title_aabb.max_point.x() - title_aabb.min_point.x(); float title_h = title_aabb.max_point.y() - title_aabb.min_point.y(); title_text.set_translation({std::round(viewport_center.x() - title_w * 0.5f), std::round(viewport_center.y() - title_h * 0.5f + (viewport_size.y() / 3.0f) / 2.0f), 0.0f}); title_text.update_tweens(); - // Add title text to UI + // Add text to UI ctx.ui_scene->add_object(&title_text); // Construct title fade animation @@ -331,7 +329,7 @@ main_menu::~main_menu() // Destruct title animation ctx.animator->remove_animation(&title_fade_animation); - // Destruct title text + // Destruct text ctx.ui_scene->remove_object(&title_text); debug::log::trace("Exited main menu state"); diff --git a/src/game/state/main-menu.hpp b/src/game/state/main-menu.hpp index 6f226c0..3b01d4d 100644 --- a/src/game/state/main-menu.hpp +++ b/src/game/state/main-menu.hpp @@ -43,7 +43,6 @@ private: scene::text title_text; animation title_fade_animation; - entity::id swarm_eid; std::shared_ptr window_resized_subscription; }; diff --git a/src/game/strings.cpp b/src/game/strings.cpp index 532712a..fece625 100644 --- a/src/game/strings.cpp +++ b/src/game/strings.cpp @@ -24,9 +24,7 @@ namespace game { std::string get_string(const game::context& ctx, std::uint32_t key) { - const auto& string_map = ctx.string_maps[ctx.language_index]; - - if (auto i = string_map.find(key); i != string_map.end()) + if (auto i = ctx.string_map->find(key); i != ctx.string_map->end()) { return i->second; } diff --git a/src/game/world.cpp b/src/game/world.cpp index e803ed4..ff9dfc3 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -17,6 +17,7 @@ * along with Antkeeper source code. If not, see . */ +#include "game/world.hpp" #include "color/color.hpp" #include "config.hpp" #include "debug/log.hpp" @@ -33,7 +34,6 @@ #include "game/system/atmosphere.hpp" #include "game/system/orbit.hpp" #include "game/system/terrain.hpp" -#include "game/world.hpp" #include "geom/solid-angle.hpp" #include "geom/spherical.hpp" #include "gl/drawing-mode.hpp" @@ -42,6 +42,7 @@ #include "gl/vertex-array.hpp" #include "gl/vertex-attribute.hpp" #include "gl/vertex-buffer.hpp" +#include "i18n/string-table.hpp" #include "math/hash/hash.hpp" #include "math/noise/noise.hpp" #include "physics/light/photometry.hpp" @@ -64,7 +65,6 @@ #include "scene/ambient-light.hpp" #include "scene/directional-light.hpp" #include "scene/text.hpp" -#include "i18n/string-table.hpp" #include #include #include diff --git a/src/i18n/string-map.cpp b/src/i18n/string-map.cpp index 15361f3..36b8a4f 100644 --- a/src/i18n/string-map.cpp +++ b/src/i18n/string-map.cpp @@ -19,33 +19,79 @@ #include "i18n/string-map.hpp" #include "utility/hash/fnv1a.hpp" +#include "resources/serializer.hpp" +#include "resources/serialize-error.hpp" +#include "resources/deserializer.hpp" +#include "resources/deserialize-error.hpp" #include -namespace i18n { +/** + * Serializes a string map. + * + * @param[in] map String map to serialize. + * @param[in,out] ctx Serialize context. + * + * @throw serialize_error Write error. + */ +template <> +void serializer::serialize(const i18n::string_map& map, serialize_context& ctx) +{ + // Write number of entries + std::uint32_t size = static_cast(map.size()); + ctx.write32(reinterpret_cast(&size), 1); + + // Write entries + for (const auto& [key, value]: map) + { + // Write key + ctx.write32(reinterpret_cast(&key), 1); + + // Write string length + std::uint32_t length = static_cast(value.length()); + ctx.write32(reinterpret_cast(&length), 1); + + // Write string + ctx.write8(reinterpret_cast(value.data()), length); + } +} -void build_string_map(const string_table& table, std::size_t key_column, std::size_t value_column, string_map& map) +/** + * Deserializes a string map. + * + * @param[out] map String map to deserialize. + * @param[in,out] ctx Deserialize context. + * + * @throw deserialize_error Read error. + */ +template <> +void deserializer::deserialize(i18n::string_map& map, deserialize_context& ctx) { map.clear(); - const std::size_t max_column = std::max(key_column, value_column); + // Read number of entries + std::uint32_t size = 0; + ctx.read32(reinterpret_cast(&size), 1); - for (const auto& row: table) + // Read entries + for (std::uint32_t i = 0; i < size; ++i) { - if (row.size() <= max_column) - { - continue; - } + // Read key + std::uint32_t key = 0; + ctx.read32(reinterpret_cast(&key), 1); + + // Read string length + std::uint32_t length = 0; + ctx.read32(reinterpret_cast(&length), 1); - const auto& value_string = row[value_column]; - if (value_string.empty()) - { - continue; - } + // Insert empty string into map + auto [iterator, inserted] = map.emplace + ( + std::piecewise_construct, + std::forward_as_tuple(key), + std::forward_as_tuple(static_cast(length), '\0') + ); - const auto& key_string = row[key_column]; - const std::uint32_t key_hash = hash::fnv1a32(key_string.c_str(), key_string.length()); - map[key_hash] = row[value_column]; + // Read string + ctx.read8(reinterpret_cast(iterator->second.data()), length); } } - -} // namespace i18n diff --git a/src/i18n/string-map.hpp b/src/i18n/string-map.hpp index 5c8d538..efbbc2e 100644 --- a/src/i18n/string-map.hpp +++ b/src/i18n/string-map.hpp @@ -20,7 +20,6 @@ #ifndef ANTKEEPER_I18N_STRING_MAP_HPP #define ANTKEEPER_I18N_STRING_MAP_HPP -#include "i18n/string-table.hpp" #include #include @@ -31,16 +30,6 @@ namespace i18n { */ typedef std::unordered_map string_map; -/** - * Builds a string map from a string table. Keys are generated with the 32-bit FNV-1a hash function. - * - * @param[in] table String table from which the string map will be built. - * @param[in] key_column Column containing key strings. - * @param[in] value_column Column containing value strings. - * @param[out] String map to build. - */ -void build_string_map(const string_table& table, std::size_t key_column, std::size_t value_column, string_map& map); - } // namespace i18n #endif // ANTKEEPER_I18N_STRING_MAP_HPP diff --git a/src/input/action-events.hpp b/src/input/action-events.hpp index 458fcc1..a9860c9 100644 --- a/src/input/action-events.hpp +++ b/src/input/action-events.hpp @@ -25,7 +25,7 @@ namespace input { class action; /** - * Event generated when a action has been activated. + * Event generated when an action has been activated. */ struct action_activated_event { @@ -34,7 +34,7 @@ struct action_activated_event }; /** - * Event generated while a action is active. + * Event generated while an action is active. */ struct action_active_event { @@ -46,7 +46,7 @@ struct action_active_event }; /** - * Event generated when a action has been deactivated. + * Event generated when an action has been deactivated. */ struct action_deactivated_event { diff --git a/src/input/action-map.cpp b/src/input/action-map.cpp index ae6f1d0..422830c 100644 --- a/src/input/action-map.cpp +++ b/src/input/action-map.cpp @@ -46,32 +46,66 @@ void action_map::disconnect() subscriptions.clear(); } -void action_map::add_mapping(action& action, gamepad_axis_mapping mapping) +void action_map::add_mapping(action& action, const mapping& mapping) +{ + switch (mapping.get_mapping_type()) + { + case mapping_type::gamepad_axis: + add_gamepad_axis_mapping(action, static_cast(mapping)); + break; + + case mapping_type::gamepad_button: + add_gamepad_button_mapping(action, static_cast(mapping)); + break; + + case mapping_type::key: + add_key_mapping(action, static_cast(mapping)); + break; + + case mapping_type::mouse_button: + add_mouse_button_mapping(action, static_cast(mapping)); + break; + + case mapping_type::mouse_motion: + add_mouse_motion_mapping(action, static_cast(mapping)); + break; + + case mapping_type::mouse_scroll: + add_mouse_scroll_mapping(action, static_cast(mapping)); + break; + + default: + //std::unreachable(); + break; + } +} + +void action_map::add_gamepad_axis_mapping(action& action, gamepad_axis_mapping mapping) { gamepad_axis_mappings.emplace_back(&action, std::move(mapping)); } -void action_map::add_mapping(action& action, gamepad_button_mapping mapping) +void action_map::add_gamepad_button_mapping(action& action, gamepad_button_mapping mapping) { gamepad_button_mappings.emplace_back(&action, std::move(mapping)); } -void action_map::add_mapping(action& action, key_mapping mapping) +void action_map::add_key_mapping(action& action, key_mapping mapping) { key_mappings.emplace_back(&action, std::move(mapping)); } -void action_map::add_mapping(action& action, mouse_button_mapping mapping) +void action_map::add_mouse_button_mapping(action& action, mouse_button_mapping mapping) { mouse_button_mappings.emplace_back(&action, std::move(mapping)); } -void action_map::add_mapping(action& action, mouse_motion_mapping mapping) +void action_map::add_mouse_motion_mapping(action& action, mouse_motion_mapping mapping) { mouse_motion_mappings.emplace_back(&action, std::move(mapping)); } -void action_map::add_mapping(action& action, mouse_scroll_mapping mapping) +void action_map::add_mouse_scroll_mapping(action& action, mouse_scroll_mapping mapping) { mouse_scroll_mappings.emplace_back(&action, std::move(mapping)); } diff --git a/src/input/action-map.hpp b/src/input/action-map.hpp index b92a84a..4128e6d 100644 --- a/src/input/action-map.hpp +++ b/src/input/action-map.hpp @@ -53,22 +53,23 @@ public: void disconnect(); /** - * Maps input to a action. + * Maps input to an action. * * @param action Action to which input will be mapped. * @param mapping Input mapping to add. */ /// @{ - void add_mapping(action& action, gamepad_axis_mapping mapping); - void add_mapping(action& action, gamepad_button_mapping mapping); - void add_mapping(action& action, key_mapping mapping); - void add_mapping(action& action, mouse_button_mapping mapping); - void add_mapping(action& action, mouse_motion_mapping mapping); - void add_mapping(action& action, mouse_scroll_mapping mapping); + void add_mapping(action& action, const mapping& mapping); + void add_gamepad_axis_mapping(action& action, gamepad_axis_mapping mapping); + void add_gamepad_button_mapping(action& action, gamepad_button_mapping mapping); + void add_key_mapping(action& action, key_mapping mapping); + void add_mouse_button_mapping(action& action, mouse_button_mapping mapping); + void add_mouse_motion_mapping(action& action, mouse_motion_mapping mapping); + void add_mouse_scroll_mapping(action& action, mouse_scroll_mapping mapping); /// @} /** - * Unmaps input from a action. + * Unmaps input from an action. * * @param action Action from which input will be unmapped. * @param type Type of input mapping to remove. @@ -76,7 +77,7 @@ public: void remove_mappings(const action& action, mapping_type type); /** - * Unmaps all input from a action. + * Unmaps all input from an action. * * @param action Action from which input will be unmapped. */ @@ -88,42 +89,42 @@ public: void remove_mappings(); /** - * Returns all of the gamepad axis mappings associated with a action. + * Returns all of the gamepad axis mappings associated with an action. * * @param action Action with which associated mappings will be returned. */ std::vector get_gamepad_axis_mappings(const action& action) const; /** - * Returns all of the gamepad button mappings associated with a action. + * Returns all of the gamepad button mappings associated with an action. * * @param action Action with which associated mappings will be returned. */ std::vector get_gamepad_button_mappings(const action& action) const; /** - * Returns all of the key mappings associated with a action. + * Returns all of the key mappings associated with an action. * * @param action Action with which associated mappings will be returned. */ std::vector get_key_mappings(const action& action) const; /** - * Returns all of the mouse button mappings associated with a action. + * Returns all of the mouse button mappings associated with an action. * * @param action Action with which associated mappings will be returned. */ std::vector get_mouse_button_mappings(const action& action) const; /** - * Returns all of the mouse motion mappings associated with a action. + * Returns all of the mouse motion mappings associated with an action. * * @param action Action with which associated mappings will be returned. */ std::vector get_mouse_motion_mappings(const action& action) const; /** - * Returns all of the mouse scroll associated with a action. + * Returns all of the mouse scroll associated with an action. * * @param action Action with which associated mappings will be returned. */ diff --git a/src/input/action.hpp b/src/input/action.hpp index 8351548..af68cc6 100644 --- a/src/input/action.hpp +++ b/src/input/action.hpp @@ -39,7 +39,7 @@ public: */ typedef std::function threshold_function_type; - /// Constructs a action. + /// Constructs an action. action(); /** diff --git a/src/input/mapper.cpp b/src/input/mapper.cpp index be83bfb..1d8110b 100644 --- a/src/input/mapper.cpp +++ b/src/input/mapper.cpp @@ -39,7 +39,10 @@ void mapper::disconnect() void mapper::handle_gamepad_axis_moved(const gamepad_axis_moved_event& event) { - gamepad_axis_mapped_publisher.publish({gamepad_axis_mapping(event.gamepad, event.axis, std::signbit(event.position))}); + if (std::abs(event.position) > 0.5f) + { + gamepad_axis_mapped_publisher.publish({gamepad_axis_mapping(event.gamepad, event.axis, std::signbit(event.position))}); + } } void mapper::handle_gamepad_button_pressed(const gamepad_button_pressed_event& event) diff --git a/src/input/mapping-type.hpp b/src/input/mapping-type.hpp index b715edf..053081a 100644 --- a/src/input/mapping-type.hpp +++ b/src/input/mapping-type.hpp @@ -20,6 +20,8 @@ #ifndef ANTKEEPER_INPUT_MAPPING_TYPE_HPP #define ANTKEEPER_INPUT_MAPPING_TYPE_HPP +#include + namespace input { /** @@ -27,7 +29,7 @@ namespace input { * * @see input::mapping */ -enum class mapping_type +enum class mapping_type: std::uint8_t { /// Gamepad axis mapping. gamepad_axis, diff --git a/src/input/mapping.cpp b/src/input/mapping.cpp index 019dff6..9e9ee11 100644 --- a/src/input/mapping.cpp +++ b/src/input/mapping.cpp @@ -18,6 +18,10 @@ */ #include "input/mapping.hpp" +#include "resources/serializer.hpp" +#include "resources/serialize-error.hpp" +#include "resources/deserializer.hpp" +#include "resources/deserialize-error.hpp" namespace input { @@ -32,7 +36,7 @@ gamepad_button_mapping::gamepad_button_mapping(input::gamepad* gamepad, gamepad_ button(button) {} -key_mapping::key_mapping(input::keyboard* keyboard, input::scancode scancode, bool repeat, std::uint16_t modifiers): +key_mapping::key_mapping(input::keyboard* keyboard, input::scancode scancode, std::uint16_t modifiers, bool repeat): keyboard(keyboard), scancode(scancode), repeat(repeat), @@ -57,3 +61,129 @@ mouse_scroll_mapping::mouse_scroll_mapping(input::mouse* mouse, mouse_scroll_axi {} } // namespace input + +/** + * Serializes an input mapping. + * + * @param[in] mapping Input mapping to serialize. + * @param[in,out] ctx Serialize context. + * + * @throw serialize_error Write error. + */ +/// @{ +template <> +void serializer::serialize(const input::gamepad_axis_mapping& mapping, serialize_context& ctx) +{ + ctx.write8(reinterpret_cast(&mapping.axis), 1); + const std::uint8_t direction = mapping.direction; + ctx.write8(reinterpret_cast(&direction), 1); +} + +template <> +void serializer::serialize(const input::gamepad_button_mapping& mapping, serialize_context& ctx) +{ + ctx.write8(reinterpret_cast(&mapping.button), 1); +} + +template <> +void serializer::serialize(const input::key_mapping& mapping, serialize_context& ctx) +{ + ctx.write16(reinterpret_cast(&mapping.scancode), 1); + ctx.write16(reinterpret_cast(&mapping.modifiers), 1); + const std::uint8_t repeat = mapping.repeat; + ctx.write8(reinterpret_cast(&repeat), 1); +} + +template <> +void serializer::serialize(const input::mouse_button_mapping& mapping, serialize_context& ctx) +{ + ctx.write8(reinterpret_cast(&mapping.button), 1); +} + +template <> +void serializer::serialize(const input::mouse_motion_mapping& mapping, serialize_context& ctx) +{ + ctx.write8(reinterpret_cast(&mapping.axis), 1); + const std::uint8_t direction = mapping.direction; + ctx.write8(reinterpret_cast(&direction), 1); +} + +template <> +void serializer::serialize(const input::mouse_scroll_mapping& mapping, serialize_context& ctx) +{ + ctx.write8(reinterpret_cast(&mapping.axis), 1); + const std::uint8_t direction = mapping.direction; + ctx.write8(reinterpret_cast(&direction), 1); +} +/// @} + +/** + * Deserializes an input mapping. + * + * @param[out] mapping Input mapping to deserialize. + * @param[in,out] ctx Deserialize context. + * + * @throw deserialize_error Read error. + */ +/// @{ +template <> +void deserializer::deserialize(input::gamepad_axis_mapping& mapping, deserialize_context& ctx) +{ + mapping.gamepad = nullptr; + + ctx.read8(reinterpret_cast(&mapping.axis), 1); + std::uint8_t direction = 0; + ctx.read8(reinterpret_cast(&direction), 1); + mapping.direction = direction; +} + +template <> +void deserializer::deserialize(input::gamepad_button_mapping& mapping, deserialize_context& ctx) +{ + mapping.gamepad = nullptr; + + ctx.read8(reinterpret_cast(&mapping.button), 1); +} + +template <> +void deserializer::deserialize(input::key_mapping& mapping, deserialize_context& ctx) +{ + mapping.keyboard = nullptr; + + ctx.read16(reinterpret_cast(&mapping.scancode), 1); + ctx.read16(reinterpret_cast(&mapping.modifiers), 1); + std::uint8_t repeat = 0; + ctx.read8(reinterpret_cast(&repeat), 1); + mapping.repeat = repeat; +} + +template <> +void deserializer::deserialize(input::mouse_button_mapping& mapping, deserialize_context& ctx) +{ + mapping.mouse = nullptr; + + ctx.read8(reinterpret_cast(&mapping.button), 1); +} + +template <> +void deserializer::deserialize(input::mouse_motion_mapping& mapping, deserialize_context& ctx) +{ + mapping.mouse = nullptr; + + ctx.read8(reinterpret_cast(&mapping.axis), 1); + std::uint8_t direction = 0; + ctx.read8(reinterpret_cast(&direction), 1); + mapping.direction = direction; +} + +template <> +void deserializer::deserialize(input::mouse_scroll_mapping& mapping, deserialize_context& ctx) +{ + mapping.mouse = nullptr; + + ctx.read8(reinterpret_cast(&mapping.axis), 1); + std::uint8_t direction = 0; + ctx.read8(reinterpret_cast(&direction), 1); + mapping.direction = direction; +} +/// @} diff --git a/src/input/mapping.hpp b/src/input/mapping.hpp index 0fb7fe0..02b4f5e 100644 --- a/src/input/mapping.hpp +++ b/src/input/mapping.hpp @@ -139,7 +139,7 @@ public: * @param modifiers Modifier keys bitmask. */ /// @{ - key_mapping(input::keyboard* keyboard, input::scancode scancode, bool repeat = false, std::uint16_t modifiers = 0); + key_mapping(input::keyboard* keyboard, input::scancode scancode, std::uint16_t modifiers = 0, bool repeat = false); key_mapping() = default; /// @} @@ -158,11 +158,11 @@ public: /// Scancode of the mapped key. scancode scancode; - /// `false` if the mapping ignores key repeats, `true` otherwise. - bool repeat; - /// Modifier keys bitbask. std::uint16_t modifiers; + + /// `false` if the mapping ignores key repeats, `true` otherwise. + bool repeat; }; /** diff --git a/src/resources/control-profile-loader.cpp b/src/resources/control-profile-loader.cpp new file mode 100644 index 0000000..38cd58d --- /dev/null +++ b/src/resources/control-profile-loader.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "resources/resource-loader.hpp" +#include "resources/serializer.hpp" +#include "resources/deserializer.hpp" +#include "game/control-profile.hpp" + +template <> +game::control_profile* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) +{ + game::control_profile* profile = new game::control_profile(); + + deserialize_context ctx(file); + deserializer().deserialize(*profile, ctx); + + return profile; +} + +template <> +void resource_loader::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const game::control_profile* profile) +{ + serialize_context ctx(file); + serializer().serialize(*profile, ctx); +} diff --git a/src/resources/deserialize-context.hpp b/src/resources/deserialize-context.hpp index 7e00cc4..6ba847c 100644 --- a/src/resources/deserialize-context.hpp +++ b/src/resources/deserialize-context.hpp @@ -32,8 +32,8 @@ public: /** * Reads 8-bit (byte) data. * - * @param data Pointer to data source. - * @param count Number of bytes to read. + * @param[out] data Pointer to data destination. + * @param[in] count Number of bytes to read. * * @return Number of bytes read. * @@ -41,19 +41,43 @@ public: */ std::size_t read8(std::byte* data, std::size_t count) noexcept(false); + /** + * Reads 16-bit (word) little-endian data. + * + * @param[out] data Pointer to data destination. + * @param[in] count Number of words to read. + * + * @return Number of words read. + * + * @throw deserialize_error Read error. + */ + std::size_t read16_le(std::byte* data, std::size_t count) noexcept(false); + + /** + * Reads 16-bit (word) big-endian data. + * + * @param[out] data Pointer to data destination. + * @param[in] count Number of words to read. + * + * @return Number of words read. + * + * @throw deserialize_error Read error. + */ + std::size_t read16_be(std::byte* data, std::size_t count) noexcept(false); + /** * Reads 16-bit (word) data. * * @tparam Endian Endianness of the read operation. * - * @param data Pointer to data destination. - * @param count Number of words to read. + * @param[out] data Pointer to data destination. + * @param[in] count Number of words to read. * * @return Number of words read. * * @throw deserialize_error Read error. */ - template + template inline std::size_t read16(std::byte* data, std::size_t count) noexcept(false) { if constexpr (Endian == std::endian::little) @@ -66,19 +90,43 @@ public: } } + /** + * Reads 32-bit (double word) little-endian data. + * + * @param[out] data Pointer to data destination. + * @param[in] count Number of double words to read. + * + * @return Number of double words read. + * + * @throw deserialize_error Read error. + */ + std::size_t read32_le(std::byte* data, std::size_t count) noexcept(false); + + /** + * Reads 32-bit (double word) big-endian data. + * + * @param[out] data Pointer to data destination. + * @param[in] count Number of double words to read. + * + * @return Number of double words read. + * + * @throw deserialize_error Read error. + */ + std::size_t read32_be(std::byte* data, std::size_t count) noexcept(false); + /** * Reads 32-bit (double word) data. * * @tparam Endian Endianness of the read operation. * - * @param data Pointer to data destination. - * @param count Number of double words to read. + * @param[out] data Pointer to data destination. + * @param[in] count Number of double words to read. * * @return Number of double words read. * * @throw deserialize_error Read error. */ - template + template inline std::size_t read32(std::byte* data, std::size_t count) noexcept(false) { if constexpr (Endian == std::endian::little) @@ -91,19 +139,43 @@ public: } } + /** + * Reads 64-bit (quad word) little-endian data. + * + * @param[out] data Pointer to data destination. + * @param[in] count Number of quad words to read. + * + * @return Number of quad words read. + * + * @throw deserialize_error Read error. + */ + std::size_t read64_le(std::byte* data, std::size_t count) noexcept(false); + + /** + * Reads 64-bit (quad word) big-endian data. + * + * @param[out] data Pointer to data destination. + * @param[in] count Number of quad words to read. + * + * @return Number of quad words read. + * + * @throw deserialize_error Read error. + */ + std::size_t read64_be(std::byte* data, std::size_t count) noexcept(false); + /** * Reads 64-bit (quad word) data. * * @tparam Endian Endianness of the read operation. * - * @param data Pointer to data destination. - * @param count Number of quad words to read. + * @param[out] data Pointer to data destination. + * @param[in] count Number of quad words to read. * * @return Number of quad words read. * * @throw deserialize_error Read error. */ - template + template inline std::size_t read64(std::byte* data, std::size_t count) noexcept(false) { if constexpr (Endian == std::endian::little) @@ -119,7 +191,7 @@ public: /** * Returns `true` if the end of a file was reached. */ - inline bool eof() const noexcept + [[nodiscard]] inline bool eof() const noexcept { return m_eof; } @@ -127,7 +199,7 @@ public: /** * Returns `true` if an error occured during a read operation, `false` otherwise. */ - inline bool error() const noexcept + [[nodiscard]] inline bool error() const noexcept { return m_error; } @@ -137,12 +209,6 @@ private: friend class resource_loader; deserialize_context(void* handle); - std::size_t read16_le(std::byte* data, std::size_t count) noexcept(false); - std::size_t read16_be(std::byte* data, std::size_t count) noexcept(false); - std::size_t read32_le(std::byte* data, std::size_t count) noexcept(false); - std::size_t read32_be(std::byte* data, std::size_t count) noexcept(false); - std::size_t read64_le(std::byte* data, std::size_t count) noexcept(false); - std::size_t read64_be(std::byte* data, std::size_t count) noexcept(false); void* handle; bool m_eof; diff --git a/src/resources/deserializer.cpp b/src/resources/deserializer.cpp index 3727b9e..b56111c 100644 --- a/src/resources/deserializer.cpp +++ b/src/resources/deserializer.cpp @@ -37,19 +37,19 @@ void deserializer::deserialize(std::uint8_t& value, deserialize_co template <> void deserializer::deserialize(std::uint16_t& value, deserialize_context& ctx) { - ctx.read16(reinterpret_cast(&value), 1); + ctx.read16(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(std::uint32_t& value, deserialize_context& ctx) { - ctx.read32(reinterpret_cast(&value), 1); + ctx.read32(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(std::uint64_t& value, deserialize_context& ctx) { - ctx.read64(reinterpret_cast(&value), 1); + ctx.read64(reinterpret_cast(&value), 1); }; template <> @@ -61,38 +61,38 @@ void deserializer::deserialize(std::int8_t& value, deserialize_cont template <> void deserializer::deserialize(std::int16_t& value, deserialize_context& ctx) { - ctx.read16(reinterpret_cast(&value), 1); + ctx.read16(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(std::int32_t& value, deserialize_context& ctx) { - ctx.read32(reinterpret_cast(&value), 1); + ctx.read32(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(std::int64_t& value, deserialize_context& ctx) { - ctx.read64(reinterpret_cast(&value), 1); + ctx.read64(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(float& value, deserialize_context& ctx) { - ctx.read32(reinterpret_cast(&value), 1); + ctx.read32(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(double& value, deserialize_context& ctx) { - ctx.read64(reinterpret_cast(&value), 1); + ctx.read64(reinterpret_cast(&value), 1); }; template <> void deserializer::deserialize(std::string& value, deserialize_context& ctx) { std::uint64_t length = 0; - ctx.read64(reinterpret_cast(&length), 1); + ctx.read64(reinterpret_cast(&length), 1); value.resize(static_cast(length)); ctx.read8(reinterpret_cast(value.data()), static_cast(length)); }; @@ -101,7 +101,7 @@ template <> void deserializer::deserialize(std::u8string& value, deserialize_context& ctx) { std::uint64_t length = 0; - ctx.read64(reinterpret_cast(&length), 1); + ctx.read64(reinterpret_cast(&length), 1); value.resize(static_cast(length)); ctx.read8(reinterpret_cast(value.data()), static_cast(length)); }; @@ -110,16 +110,16 @@ template <> void deserializer::deserialize(std::u16string& value, deserialize_context& ctx) { std::uint64_t length = 0; - ctx.read64(reinterpret_cast(&length), 1); + ctx.read64(reinterpret_cast(&length), 1); value.resize(static_cast(length)); - ctx.read16(reinterpret_cast(value.data()), static_cast(length)); + ctx.read16(reinterpret_cast(value.data()), static_cast(length)); }; template <> void deserializer::deserialize(std::u32string& value, deserialize_context& ctx) { std::uint64_t length = 0; - ctx.read64(reinterpret_cast(&length), 1); + ctx.read64(reinterpret_cast(&length), 1); value.resize(static_cast(length)); - ctx.read32(reinterpret_cast(value.data()), static_cast(length)); + ctx.read32(reinterpret_cast(value.data()), static_cast(length)); }; diff --git a/src/resources/serialize-context.cpp b/src/resources/serialize-context.cpp index 750837d..b80a65b 100644 --- a/src/resources/serialize-context.cpp +++ b/src/resources/serialize-context.cpp @@ -47,30 +47,38 @@ std::size_t serialize_context::write8(const std::byte* data, std::size_t count) return count; } -std::size_t serialize_context::write16(const std::byte* data, std::size_t count) +std::size_t serialize_context::write16_le(const std::byte* data, std::size_t count) { PHYSFS_File* file = reinterpret_cast(handle); const PHYSFS_uint16* data16 = reinterpret_cast(data); for (std::size_t i = 0; i < count; ++i) { - if constexpr (serialize_context::endian == std::endian::little) + if (!PHYSFS_writeULE16(file, *data16)) { - if (!PHYSFS_writeULE16(file, *data16)) - { - m_error = true; - throw serialize_error(PHYSFS_getLastError()); - //return i; - } + m_error = true; + throw serialize_error(PHYSFS_getLastError()); + //return i; } - else + + ++data16; + } + + return count; +} + +std::size_t serialize_context::write16_be(const std::byte* data, std::size_t count) +{ + PHYSFS_File* file = reinterpret_cast(handle); + const PHYSFS_uint16* data16 = reinterpret_cast(data); + + for (std::size_t i = 0; i < count; ++i) + { + if (!PHYSFS_writeUBE16(file, *data16)) { - if (!PHYSFS_writeUBE16(file, *data16)) - { - m_error = true; - throw serialize_error(PHYSFS_getLastError()); - //return i; - } + m_error = true; + throw serialize_error(PHYSFS_getLastError()); + //return i; } ++data16; @@ -79,30 +87,38 @@ std::size_t serialize_context::write16(const std::byte* data, std::size_t count) return count; } -std::size_t serialize_context::write32(const std::byte* data, std::size_t count) +std::size_t serialize_context::write32_le(const std::byte* data, std::size_t count) { PHYSFS_File* file = reinterpret_cast(handle); const PHYSFS_uint32* data32 = reinterpret_cast(data); for (std::size_t i = 0; i < count; ++i) { - if constexpr (serialize_context::endian == std::endian::little) + if (!PHYSFS_writeULE32(file, *data32)) { - if (!PHYSFS_writeULE32(file, *data32)) - { - m_error = true; - throw serialize_error(PHYSFS_getLastError()); - //return i; - } + m_error = true; + throw serialize_error(PHYSFS_getLastError()); + //return i; } - else + + ++data32; + } + + return count; +} + +std::size_t serialize_context::write32_be(const std::byte* data, std::size_t count) +{ + PHYSFS_File* file = reinterpret_cast(handle); + const PHYSFS_uint32* data32 = reinterpret_cast(data); + + for (std::size_t i = 0; i < count; ++i) + { + if (!PHYSFS_writeUBE32(file, *data32)) { - if (!PHYSFS_writeUBE32(file, *data32)) - { - m_error = true; - throw serialize_error(PHYSFS_getLastError()); - //return i; - } + m_error = true; + throw serialize_error(PHYSFS_getLastError()); + //return i; } ++data32; @@ -111,30 +127,38 @@ std::size_t serialize_context::write32(const std::byte* data, std::size_t count) return count; } -std::size_t serialize_context::write64(const std::byte* data, std::size_t count) +std::size_t serialize_context::write64_le(const std::byte* data, std::size_t count) { PHYSFS_File* file = reinterpret_cast(handle); const PHYSFS_uint64* data64 = reinterpret_cast(data); for (std::size_t i = 0; i < count; ++i) { - if constexpr (serialize_context::endian == std::endian::little) + if (!PHYSFS_writeULE64(file, *data64)) { - if (!PHYSFS_writeULE64(file, *data64)) - { - m_error = true; - throw serialize_error(PHYSFS_getLastError()); - //return i; - } + m_error = true; + throw serialize_error(PHYSFS_getLastError()); + //return i; } - else + + ++data64; + } + + return count; +} + +std::size_t serialize_context::write64_be(const std::byte* data, std::size_t count) +{ + PHYSFS_File* file = reinterpret_cast(handle); + const PHYSFS_uint64* data64 = reinterpret_cast(data); + + for (std::size_t i = 0; i < count; ++i) + { + if (!PHYSFS_writeUBE64(file, *data64)) { - if (!PHYSFS_writeUBE64(file, *data64)) - { - m_error = true; - throw serialize_error(PHYSFS_getLastError()); - //return i; - } + m_error = true; + throw serialize_error(PHYSFS_getLastError()); + //return i; } ++data64; diff --git a/src/resources/serialize-context.hpp b/src/resources/serialize-context.hpp index d0ab774..1d64bc1 100644 --- a/src/resources/serialize-context.hpp +++ b/src/resources/serialize-context.hpp @@ -29,8 +29,6 @@ struct serialize_context { public: - static inline constexpr std::endian endian = std::endian::little; - /** * Writes 8-bit (byte) data. * @@ -43,9 +41,35 @@ public: */ std::size_t write8(const std::byte* data, std::size_t count) noexcept(false); + /** + * Writes 16-bit (word) little-endian data. + * + * @param data Pointer to data source. + * @param count Number of words to write. + * + * @return Number of words written. + * + * @throw serialize_error Write error. + */ + std::size_t write16_le(const std::byte* data, std::size_t count) noexcept(false); + + /** + * Writes 16-bit (word) big-endian data. + * + * @param data Pointer to data source. + * @param count Number of words to write. + * + * @return Number of words written. + * + * @throw serialize_error Write error. + */ + std::size_t write16_be(const std::byte* data, std::size_t count) noexcept(false); + /** * Writes 16-bit (word) data. * + * @tparam Endian Endianness of the write operation. + * * @param data Pointer to data source. * @param count Number of words to write. * @@ -53,11 +77,48 @@ public: * * @throw serialize_error Write error. */ - std::size_t write16(const std::byte* data, std::size_t count) noexcept(false); + template + inline std::size_t write16(const std::byte* data, std::size_t count) noexcept(false) + { + if constexpr (Endian == std::endian::little) + { + return write16_le(data, count); + } + else + { + return write16_be(data, count); + } + } + + /** + * Writes 32-bit (double word) little-endian data. + * + * @param data Pointer to data source. + * @param count Number of double words to write. + * + * @return Number of double words written. + * + * @throw serialize_error Write error. + */ + std::size_t write32_le(const std::byte* data, std::size_t count) noexcept(false); + + /** + * Writes 32-bit (double word) big-endian data. + * + * @param data Pointer to data source. + * @param count Number of double words to write. + * + * @return Number of double words written. + * + * @throw serialize_error Write error. + */ + std::size_t write32_be(const std::byte* data, std::size_t count) noexcept(false); /** * Writes 32-bit (double word) data. * + * @tparam Endian Endianness of the write operation. + * * @param data Pointer to data source. * @param count Number of double words to write. * @@ -65,11 +126,48 @@ public: * * @throw serialize_error Write error. */ - std::size_t write32(const std::byte* data, std::size_t count) noexcept(false); + template + inline std::size_t write32(const std::byte* data, std::size_t count) noexcept(false) + { + if constexpr (Endian == std::endian::little) + { + return write32_le(data, count); + } + else + { + return write32_be(data, count); + } + } + + /** + * Writes 64-bit (quad word) little-endian data. + * + * @param data Pointer to data source. + * @param count Number of quad words to write. + * + * @return Number of quad words written. + * + * @throw serialize_error Write error. + */ + std::size_t write64_le(const std::byte* data, std::size_t count) noexcept(false); + + /** + * Writes 64-bit (quad word) big-endian data. + * + * @param data Pointer to data source. + * @param count Number of quad words to write. + * + * @return Number of quad words written. + * + * @throw serialize_error Write error. + */ + std::size_t write64_be(const std::byte* data, std::size_t count) noexcept(false); /** * Writes 64-bit (quad word) data. * + * @tparam Endian Endianness of the write operation. + * * @param data Pointer to data source. * @param count Number of quad words to write. * @@ -77,12 +175,23 @@ public: * * @throw serialize_error Write error. */ - std::size_t write64(const std::byte* data, std::size_t count) noexcept(false); + template + inline std::size_t write64(const std::byte* data, std::size_t count) noexcept(false) + { + if constexpr (Endian == std::endian::little) + { + return write64_le(data, count); + } + else + { + return write64_be(data, count); + } + } /** * Returns `true` if an error occured during a write operation, `false` otherwise. */ - inline bool error() const noexcept + [[nodiscard]] inline bool error() const noexcept { return m_error; } diff --git a/src/resources/serializer.cpp b/src/resources/serializer.cpp index 1bd7b15..3a66cc9 100644 --- a/src/resources/serializer.cpp +++ b/src/resources/serializer.cpp @@ -36,19 +36,19 @@ void serializer::serialize(const std::uint8_t& value, serialize_co template <> void serializer::serialize(const std::uint16_t& value, serialize_context& ctx) { - ctx.write16(reinterpret_cast(&value), 1); + ctx.write16(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const std::uint32_t& value, serialize_context& ctx) { - ctx.write32(reinterpret_cast(&value), 1); + ctx.write32(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const std::uint64_t& value, serialize_context& ctx) { - ctx.write64(reinterpret_cast(&value), 1); + ctx.write64(reinterpret_cast(&value), 1); }; template <> @@ -60,38 +60,38 @@ void serializer::serialize(const std::int8_t& value, serialize_cont template <> void serializer::serialize(const std::int16_t& value, serialize_context& ctx) { - ctx.write16(reinterpret_cast(&value), 1); + ctx.write16(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const std::int32_t& value, serialize_context& ctx) { - ctx.write32(reinterpret_cast(&value), 1); + ctx.write32(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const std::int64_t& value, serialize_context& ctx) { - ctx.write64(reinterpret_cast(&value), 1); + ctx.write64(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const float& value, serialize_context& ctx) { - ctx.write32(reinterpret_cast(&value), 1); + ctx.write32(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const double& value, serialize_context& ctx) { - ctx.write64(reinterpret_cast(&value), 1); + ctx.write64(reinterpret_cast(&value), 1); }; template <> void serializer::serialize(const std::string& value, serialize_context& ctx) { const std::uint64_t length = static_cast(value.length()); - ctx.write64(reinterpret_cast(&length), 1); + ctx.write64(reinterpret_cast(&length), 1); ctx.write8(reinterpret_cast(value.data()), static_cast(length)); }; @@ -99,7 +99,7 @@ template <> void serializer::serialize(const std::u8string& value, serialize_context& ctx) { const std::uint64_t length = static_cast(value.length()); - ctx.write64(reinterpret_cast(&length), 1); + ctx.write64(reinterpret_cast(&length), 1); ctx.write8(reinterpret_cast(value.data()), static_cast(length)); }; @@ -107,14 +107,14 @@ template <> void serializer::serialize(const std::u16string& value, serialize_context& ctx) { const std::uint64_t length = static_cast(value.length()); - ctx.write64(reinterpret_cast(&length), 1); - ctx.write16(reinterpret_cast(value.data()), static_cast(length)); + ctx.write64(reinterpret_cast(&length), 1); + ctx.write16(reinterpret_cast(value.data()), static_cast(length)); }; template <> void serializer::serialize(const std::u32string& value, serialize_context& ctx) { const std::uint64_t length = static_cast(value.length()); - ctx.write64(reinterpret_cast(&length), 1); - ctx.write32(reinterpret_cast(value.data()), static_cast(length)); + ctx.write64(reinterpret_cast(&length), 1); + ctx.write32(reinterpret_cast(value.data()), static_cast(length)); }; diff --git a/src/resources/string-map-loader.cpp b/src/resources/string-map-loader.cpp new file mode 100644 index 0000000..6dd3ced --- /dev/null +++ b/src/resources/string-map-loader.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 Christopher J. Howard + * + * This file is part of Antkeeper source code. + * + * Antkeeper source code is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Antkeeper source code is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Antkeeper source code. If not, see . + */ + +#include "resources/resource-loader.hpp" +#include "resources/serializer.hpp" +#include "resources/deserializer.hpp" +#include "i18n/string-map.hpp" + +template <> +i18n::string_map* resource_loader::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path) +{ + i18n::string_map* map = new i18n::string_map(); + + deserialize_context ctx(file); + deserializer().deserialize(*map, ctx); + + return map; +} + +template <> +void resource_loader::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const i18n::string_map* map) +{ + serialize_context ctx(file); + serializer().serialize(*map, ctx); +} diff --git a/src/type/freetype/typeface.cpp b/src/type/freetype/typeface.cpp index 958078a..15740f7 100644 --- a/src/type/freetype/typeface.cpp +++ b/src/type/freetype/typeface.cpp @@ -29,7 +29,16 @@ typeface::typeface(FT_Library library, FT_Face face, unsigned char* buffer): face(face), buffer(buffer), height(-1.0f) -{} +{ + /// Build charset + FT_UInt index; + FT_ULong c = FT_Get_First_Char(face, &index); + while (index) + { + this->charset.insert(static_cast(c)); + c = FT_Get_Next_Char(face, c, &index); + } +} typeface::~typeface() { @@ -43,11 +52,6 @@ bool typeface::has_kerning() const return FT_HAS_KERNING(face); } -bool typeface::has_glyph(char32_t code) const -{ - return FT_Get_Char_Index(face, static_cast(code)) != 0; -} - bool typeface::get_metrics(float height, font_metrics& metrics) const { // Set font size diff --git a/src/type/freetype/typeface.hpp b/src/type/freetype/typeface.hpp index 9fb4d87..a705cb5 100644 --- a/src/type/freetype/typeface.hpp +++ b/src/type/freetype/typeface.hpp @@ -50,9 +50,6 @@ public: /// @copydoc type::typeface::has_kerning() const virtual bool has_kerning() const; - /// @copydoc type::typeface::has_glyph(char32_t) const - virtual bool has_glyph(char32_t code) const; - /// @copydoc type::typeface::get_metrics(float, font_metrics&) const virtual bool get_metrics(float height, font_metrics& metrics) const; diff --git a/src/type/typeface.hpp b/src/type/typeface.hpp index 4974f3d..212be2b 100644 --- a/src/type/typeface.hpp +++ b/src/type/typeface.hpp @@ -24,6 +24,7 @@ #include "type/glyph-metrics.hpp" #include "resources/image.hpp" #include "utility/fundamental-types.hpp" +#include namespace type { @@ -77,22 +78,20 @@ public: void set_weight(int weight); /// Returns the style of the typeface. - typeface_style get_style() const; + [[nodiscard]] inline typeface_style get_style() const noexcept + { + return style; + } /// Returns the weight of the typeface. - int get_weight() const; + [[nodiscard]] inline int get_weight() const noexcept + { + return weight; + } /// Returns `true` if the typeface contains kerning information, `false` otherwise. virtual bool has_kerning() const = 0; - /** - * Returns `true` if the typeface contains a glyph, `false` otherwise. - * - * @param code UTF-32 character code of a glyph. - * @return `true` if the typeface contains the glyph, `false` otherwise. - */ - virtual bool has_glyph(char32_t code) const = 0; - /** * Gets metrics for a font of the specified size. * @@ -133,21 +132,20 @@ public: */ virtual bool get_kerning(float height, char32_t first, char32_t second, float2& offset) const = 0; + /// Returns the set of characters supported by the typeface. + [[nodiscard]] inline const std::unordered_set& get_charset() const noexcept + { + return charset; + } + +protected: + std::unordered_set charset; + private: typeface_style style; int weight; }; -inline typeface_style typeface::get_style() const -{ - return style; -} - -inline int typeface::get_weight() const -{ - return weight; -} - } // namespace type #endif // ANTKEEPER_TYPE_TYPEFACE_HPP diff --git a/src/utility/dict.cpp b/src/utility/dict.cpp index ed6a5f8..2dfda06 100644 --- a/src/utility/dict.cpp +++ b/src/utility/dict.cpp @@ -48,6 +48,9 @@ static void deserialize_any(std::any& any, deserialize_context& ctx) /** * Serializes a dict with an unsigned 32-bit key. * + * @param[in] dict Dict to serialize. + * @param[in,out] ctx Serialize context. + * * @throw serialize_error Write error. * @throw serialize_error Unsupported dict value type. */ @@ -84,7 +87,7 @@ void serializer>::serialize(const dict& dict, // Write dict size std::uint64_t size = static_cast(dict.size()); - ctx.write64(reinterpret_cast(&size), 1); + ctx.write64(reinterpret_cast(&size), 1); // Write dict entries for (const auto& [key, value]: dict) @@ -94,8 +97,8 @@ void serializer>::serialize(const dict& dict, const auto& [type_hash, type_serializer] = i->second; // Write entry type hash and key - ctx.write32(reinterpret_cast(&type_hash), 1); - ctx.write32(reinterpret_cast(&key), 1); + ctx.write32(reinterpret_cast(&type_hash), 1); + ctx.write32(reinterpret_cast(&key), 1); // Serialize entry value type_serializer(value, ctx); @@ -105,12 +108,15 @@ void serializer>::serialize(const dict& dict, throw serialize_error("Unsupported dict value type"); } } -}; +} /** * Deserializes a dict with an unsigned 32-bit key. * - * @throw deserialize_error Write error. + * @param[out] dict Dict to serialize. + * @param[in,out] ctx Deserialize context. + * + * @throw deserialize_error Read error. * @throw deserialize_error Unsupported dict value type. */ template <> @@ -140,22 +146,24 @@ void deserializer>::deserialize(dict& dict, d {"u32string"_fnv1a32, &deserialize_any} }; + dict.clear(); + // Read dict size std::uint64_t size = 0; - ctx.read64(reinterpret_cast(&size), 1); + ctx.read64(reinterpret_cast(&size), 1); // Read dict entries for (std::size_t i = 0; i < size; ++i) { // Read entry type hash std::uint32_t type_hash = 0; - ctx.read32(reinterpret_cast(&type_hash), 1); + ctx.read32(reinterpret_cast(&type_hash), 1); if (auto i = type_map.find(type_hash); i != type_map.end()) { // Read entry key std::uint32_t key = 0; - ctx.read32(reinterpret_cast(&key), 1); + ctx.read32(reinterpret_cast(&key), 1); // Deserialize entry value i->second(dict[key], ctx); @@ -165,4 +173,4 @@ void deserializer>::deserialize(dict& dict, d throw deserialize_error("Unsupported dict value type"); } } -}; +}