Browse Source

Fix and improve control profile serialization. Fix gamepad config menu. Add support for loading string maps directly rather than building them at runtme.

master
C. J. Howard 1 year ago
parent
commit
676fbefb75
40 changed files with 1502 additions and 1298 deletions
  1. +0
    -1
      CMakeLists.txt
  2. +1
    -1
      src/animation/screen-transition.cpp
  3. +7
    -6
      src/game/context.hpp
  4. +178
    -0
      src/game/control-profile.cpp
  5. +43
    -0
      src/game/control-profile.hpp
  6. +181
    -696
      src/game/controls.cpp
  7. +17
    -54
      src/game/controls.hpp
  8. +7
    -6
      src/game/fonts.cpp
  9. +79
    -74
      src/game/state/boot.cpp
  10. +204
    -206
      src/game/state/gamepad-config-menu.cpp
  11. +12
    -3
      src/game/state/gamepad-config-menu.hpp
  12. +13
    -3
      src/game/state/keyboard-config-menu.cpp
  13. +2
    -0
      src/game/state/keyboard-config-menu.hpp
  14. +57
    -40
      src/game/state/language-menu.cpp
  15. +5
    -0
      src/game/state/language-menu.hpp
  16. +3
    -5
      src/game/state/main-menu.cpp
  17. +0
    -1
      src/game/state/main-menu.hpp
  18. +1
    -3
      src/game/strings.cpp
  19. +2
    -2
      src/game/world.cpp
  20. +64
    -18
      src/i18n/string-map.cpp
  21. +0
    -11
      src/i18n/string-map.hpp
  22. +3
    -3
      src/input/action-events.hpp
  23. +40
    -6
      src/input/action-map.cpp
  24. +16
    -15
      src/input/action-map.hpp
  25. +1
    -1
      src/input/action.hpp
  26. +4
    -1
      src/input/mapper.cpp
  27. +3
    -1
      src/input/mapping-type.hpp
  28. +131
    -1
      src/input/mapping.cpp
  29. +4
    -4
      src/input/mapping.hpp
  30. +41
    -0
      src/resources/control-profile-loader.cpp
  31. +85
    -19
      src/resources/deserialize-context.hpp
  32. +14
    -14
      src/resources/deserializer.cpp
  33. +69
    -45
      src/resources/serialize-context.cpp
  34. +115
    -6
      src/resources/serialize-context.hpp
  35. +14
    -14
      src/resources/serializer.cpp
  36. +41
    -0
      src/resources/string-map-loader.cpp
  37. +10
    -6
      src/type/freetype/typeface.cpp
  38. +0
    -3
      src/type/freetype/typeface.hpp
  39. +18
    -20
      src/type/typeface.hpp
  40. +17
    -9
      src/utility/dict.cpp

+ 0
- 1
CMakeLists.txt View File

@ -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")

+ 1
- 1
src/animation/screen-transition.cpp View File

@ -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<float>("progress");

+ 7
- 6
src/game/context.hpp View File

@ -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<i18n::string_map> string_maps;
std::string language_tag;
i18n::string_map* string_map;
// Fonts
std::unordered_map<std::string, type::typeface*> 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<float, 15> average_frame_time;
scene::text* frame_time_text;
debug::cli* cli;
// Hierarchichal state machine

+ 178
- 0
src/game/control-profile.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<game::control_profile>::serialize(const game::control_profile& profile, serialize_context& ctx)
{
// Write number of mappings
std::uint64_t size = static_cast<std::uint64_t>(profile.mappings.size());
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&size), 1);
// Write mappings
for (const auto& [key, value]: profile.mappings)
{
// Write key
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&key), 1);
// Write mapping type
const input::mapping_type mapping_type = value->get_mapping_type();
ctx.write8(reinterpret_cast<const std::byte*>(&mapping_type), 1);
// Write mapping
switch (mapping_type)
{
case input::mapping_type::gamepad_axis:
serializer<input::gamepad_axis_mapping>().serialize(*static_cast<const input::gamepad_axis_mapping*>(value.get()), ctx);
break;
case input::mapping_type::gamepad_button:
serializer<input::gamepad_button_mapping>().serialize(*static_cast<const input::gamepad_button_mapping*>(value.get()), ctx);
break;
case input::mapping_type::key:
serializer<input::key_mapping>().serialize(*static_cast<const input::key_mapping*>(value.get()), ctx);
break;
case input::mapping_type::mouse_button:
serializer<input::mouse_button_mapping>().serialize(*static_cast<const input::mouse_button_mapping*>(value.get()), ctx);
break;
case input::mapping_type::mouse_motion:
serializer<input::mouse_motion_mapping>().serialize(*static_cast<const input::mouse_motion_mapping*>(value.get()), ctx);
break;
case input::mapping_type::mouse_scroll:
serializer<input::mouse_scroll_mapping>().serialize(*static_cast<const input::mouse_scroll_mapping*>(value.get()), ctx);
break;
default:
throw serialize_error("Unsupported mapping type");
break;
}
}
// Write settings
serializer<dict<std::uint32_t>>().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<game::control_profile>::deserialize(game::control_profile& profile, deserialize_context& ctx)
{
profile.mappings.clear();
// Read number of mappings
std::uint64_t size = 0;
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&size), 1);
// Read mappings
for (std::uint64_t i = 0; i < size; ++i)
{
// Read key
std::uint32_t key = 0;
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&key), 1);
// Read mapping type
input::mapping_type mapping_type;
ctx.read8(reinterpret_cast<std::byte*>(&mapping_type), 1);
// Read mapping
switch (mapping_type)
{
case input::mapping_type::gamepad_axis:
{
input::gamepad_axis_mapping mapping;
deserializer<input::gamepad_axis_mapping>().deserialize(mapping, ctx);
profile.mappings.emplace(key, std::make_unique<input::gamepad_axis_mapping>(std::move(mapping)));
break;
}
case input::mapping_type::gamepad_button:
{
input::gamepad_button_mapping mapping;
deserializer<input::gamepad_button_mapping>().deserialize(mapping, ctx);
profile.mappings.emplace(key, std::make_unique<input::gamepad_button_mapping>(std::move(mapping)));
break;
}
case input::mapping_type::key:
{
input::key_mapping mapping;
deserializer<input::key_mapping>().deserialize(mapping, ctx);
profile.mappings.emplace(key, std::make_unique<input::key_mapping>(std::move(mapping)));
break;
}
case input::mapping_type::mouse_button:
{
input::mouse_button_mapping mapping;
deserializer<input::mouse_button_mapping>().deserialize(mapping, ctx);
profile.mappings.emplace(key, std::make_unique<input::mouse_button_mapping>(std::move(mapping)));
break;
}
case input::mapping_type::mouse_motion:
{
input::mouse_motion_mapping mapping;
deserializer<input::mouse_motion_mapping>().deserialize(mapping, ctx);
profile.mappings.emplace(key, std::make_unique<input::mouse_motion_mapping>(std::move(mapping)));
break;
}
case input::mapping_type::mouse_scroll:
{
input::mouse_scroll_mapping mapping;
deserializer<input::mouse_scroll_mapping>().deserialize(mapping, ctx);
profile.mappings.emplace(key, std::make_unique<input::mouse_scroll_mapping>(std::move(mapping)));
break;
}
default:
throw deserialize_error("Unsupported mapping type");
break;
}
}
// Read settings
deserializer<dict<std::uint32_t>>().deserialize(profile.settings, ctx);
}

+ 43
- 0
src/game/control-profile.hpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEER_GAME_CONTROL_PROFILE_HPP
#define ANTKEEER_GAME_CONTROL_PROFILE_HPP
#include "input/mapping.hpp"
#include "utility/dict.hpp"
#include <cstdint>
#include <map>
#include <memory>
namespace game {
struct control_profile
{
public:
/// Input mappings.
std::multimap<std::uint32_t, std::unique_ptr<input::mapping>> mappings;
/// Profile-specific settings.
dict<std::uint32_t> settings;
};
} // namespace game
#endif // ANTKEEER_GAME_CONTROL_PROFILE_HPP

+ 181
- 696
src/game/controls.cpp View File

@ -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<const geom::aabb<float>&>(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<json>(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<std::string, input::gamepad_button> 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<std::string, input::gamepad_axis> 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<std::string>();
// 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<std::string>();
// 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<int>();
// 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<std::string>();
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<std::string>();
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<std::string>();
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<std::string>();
// 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<std::string>();
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<const input::key_mapping*>(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<const input::mouse_wheel_mapping*>(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<const input::mouse_motion_mapping*>(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<const input::mouse_button_mapping*>(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<const input::gamepad_axis_mapping*>(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<const input::gamepad_button_mapping*>(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>();
float max = calibration["leftx_activation"][1].get<float>();
gamepad.set_activation_threshold(input::gamepad_axis::left_x, min, max);
}
if (calibration.contains("lefty_activation"))
{
float min = calibration["lefty_activation"][0].get<float>();
float max = calibration["lefty_activation"][1].get<float>();
gamepad.set_activation_threshold(input::gamepad_axis::left_y, min, max);
}
if (calibration.contains("rightx_activation"))
{
float min = calibration["rightx_activation"][0].get<float>();
float max = calibration["rightx_activation"][1].get<float>();
gamepad.set_activation_threshold(input::gamepad_axis::right_x, min, max);
}
if (calibration.contains("righty_activation"))
{
float min = calibration["righty_activation"][0].get<float>();
float max = calibration["righty_activation"][1].get<float>();
gamepad.set_activation_threshold(input::gamepad_axis::right_y, min, max);
}
if (calibration.contains("lefttrigger_activation"))
{
float min = calibration["lefttrigger_activation"][0].get<float>();
float max = calibration["lefttrigger_activation"][1].get<float>();
gamepad.set_activation_threshold(input::gamepad_axis::left_trigger, min, max);
}
if (calibration.contains("righttrigger_activation"))
{
float min = calibration["righttrigger_activation"][0].get<float>();
float max = calibration["righttrigger_activation"][1].get<float>();
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<bool>());
if (calibration.contains("right_deadzone_cross"))
gamepad.set_right_deadzone_cross(calibration["right_deadzone_cross"].get<bool>());
if (calibration.contains("left_deadzone_roundness"))
gamepad.set_left_deadzone_roundness(calibration["left_deadzone_roundness"].get<float>());
if (calibration.contains("right_deadzone_roundness"))
gamepad.set_right_deadzone_roundness(calibration["right_deadzone_roundness"].get<float>());
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<std::string>());
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<std::string>());
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<std::string>());
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<std::string>());
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<std::string>());
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<std::string>());
gamepad.set_response_curve(input::gamepad_axis::right_trigger, curve);
}
*/
}
} // namespace game

+ 17
- 54
src/game/controls.hpp View File

@ -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

+ 7
- 6
src/game/fonts.cpp View File

@ -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<char32_t> 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<std::codecvt_utf8<char32_t>, 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<gl::shader_program>("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);
}
}

+ 79
- 74
src/game/state/boot.cpp View File

@ -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 <algorithm>
#include <cctype>
#include <entt/entt.hpp>
#include <execution>
#include <filesystem>
@ -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<std::filesystem::path> 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<std::uint32_t>();
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<i18n::string_table>("strings.tsv");
// Count languages
ctx.language_count = static_cast<std::uint16_t>((*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<i18n::string_map>(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<gl::shader_program>("fade-transition.glsl"));
ctx.fade_transition_color = ctx.fade_transition->get_material()->add_property<float3>("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<file_buffer>("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<json>((*ctx.config)["control_profile"].get<std::string>());
// 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<game::control_profile>(ctx.controls_path / ctx.control_profile_filename);
ctx.control_profile = ctx.resource_manager->load<game::control_profile>(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<float, 30> 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<float>(ctx.loop.get_frame_duration()));
average_frame_time(static_cast<float>(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

+ 204
- 206
src/game/state/gamepad-config-menu.cpp View File

@ -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<const input::gamepad_axis_mapping*>(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<const input::gamepad_button_mapping*>(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<const gamepad_axis_moved_event&>(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<const gamepad_button_pressed_event&>(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<const key_pressed_event&>(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

+ 12
- 3
src/game/state/gamepad-config-menu.hpp View File

@ -22,7 +22,10 @@
#include "game/state/base.hpp"
#include "input/action.hpp"
#include <string>
#include "input/action-map.hpp"
#include "event/subscription.hpp"
#include <cstdint>
#include <memory>
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<event::subscription> gamepad_axis_mapped_subscription;
std::shared_ptr<event::subscription> gamepad_button_mapped_subscription;
std::shared_ptr<event::subscription> key_mapped_subscription;
bool action_remapped;
};
} // namespace state

+ 13
- 3
src/game/state/keyboard-config-menu.cpp View File

@ -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);

+ 2
- 0
src/game/state/keyboard-config-menu.hpp View File

@ -43,6 +43,8 @@ private:
std::shared_ptr<event::subscription> key_mapped_subscription;
std::shared_ptr<event::subscription> mouse_button_mapped_subscription;
std::shared_ptr<event::subscription> mouse_scroll_mapped_subscription;
bool action_remapped;
};
} // namespace state

+ 57
- 40
src/game/state/language-menu.cpp View File

@ -26,6 +26,9 @@
#include "game/menu.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
#include "resources/resource-manager.hpp"
#include <algorithm>
#include <cctype>
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<i18n::string_map>(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));
}

+ 5
- 0
src/game/state/language-menu.hpp View File

@ -21,6 +21,8 @@
#define ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP
#include "game/state/base.hpp"
#include <string>
#include <vector>
namespace game {
namespace state {
@ -33,6 +35,9 @@ public:
private:
void update_text_content();
std::vector<std::string> language_tags;
std::size_t language_index;
};

+ 3
- 5
src/game/state/main-menu.cpp View File

@ -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 <format>
#include <limits>
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<const geom::aabb<float>&>(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");

+ 0
- 1
src/game/state/main-menu.hpp View File

@ -43,7 +43,6 @@ private:
scene::text title_text;
animation<float> title_fade_animation;
entity::id swarm_eid;
std::shared_ptr<event::subscription> window_resized_subscription;
};

+ 1
- 3
src/game/strings.cpp View File

@ -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;
}

+ 2
- 2
src/game/world.cpp View File

@ -17,6 +17,7 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#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 <algorithm>
#include <execution>
#include <fstream>

+ 64
- 18
src/i18n/string-map.cpp View File

@ -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 <algorithm>
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<i18n::string_map>::serialize(const i18n::string_map& map, serialize_context& ctx)
{
// Write number of entries
std::uint32_t size = static_cast<std::uint32_t>(map.size());
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&size), 1);
// Write entries
for (const auto& [key, value]: map)
{
// Write key
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&key), 1);
// Write string length
std::uint32_t length = static_cast<std::uint32_t>(value.length());
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&length), 1);
// Write string
ctx.write8(reinterpret_cast<const std::byte*>(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<i18n::string_map>::deserialize(i18n::string_map& map, deserialize_context& ctx)
{
map.clear();
const std::size_t max_column = std::max<std::size_t>(key_column, value_column);
// Read number of entries
std::uint32_t size = 0;
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&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<std::endian::big>(reinterpret_cast<std::byte*>(&key), 1);
// Read string length
std::uint32_t length = 0;
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&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<std::size_t>(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<std::byte*>(iterator->second.data()), length);
}
}
} // namespace i18n

+ 0
- 11
src/i18n/string-map.hpp View File

@ -20,7 +20,6 @@
#ifndef ANTKEEPER_I18N_STRING_MAP_HPP
#define ANTKEEPER_I18N_STRING_MAP_HPP
#include "i18n/string-table.hpp"
#include <string>
#include <unordered_map>
@ -31,16 +30,6 @@ namespace i18n {
*/
typedef std::unordered_map<std::uint32_t, std::string> 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

+ 3
- 3
src/input/action-events.hpp View File

@ -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
{

+ 40
- 6
src/input/action-map.cpp View File

@ -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<const gamepad_axis_mapping&>(mapping));
break;
case mapping_type::gamepad_button:
add_gamepad_button_mapping(action, static_cast<const gamepad_button_mapping&>(mapping));
break;
case mapping_type::key:
add_key_mapping(action, static_cast<const key_mapping&>(mapping));
break;
case mapping_type::mouse_button:
add_mouse_button_mapping(action, static_cast<const mouse_button_mapping&>(mapping));
break;
case mapping_type::mouse_motion:
add_mouse_motion_mapping(action, static_cast<const mouse_motion_mapping&>(mapping));
break;
case mapping_type::mouse_scroll:
add_mouse_scroll_mapping(action, static_cast<const mouse_scroll_mapping&>(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));
}

+ 16
- 15
src/input/action-map.hpp View File

@ -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<gamepad_axis_mapping> 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<gamepad_button_mapping> 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<key_mapping> 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<mouse_button_mapping> 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<mouse_motion_mapping> 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.
*/

+ 1
- 1
src/input/action.hpp View File

@ -39,7 +39,7 @@ public:
*/
typedef std::function<bool(float)> threshold_function_type;
/// Constructs a action.
/// Constructs an action.
action();
/**

+ 4
- 1
src/input/mapper.cpp View File

@ -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)

+ 3
- 1
src/input/mapping-type.hpp View File

@ -20,6 +20,8 @@
#ifndef ANTKEEPER_INPUT_MAPPING_TYPE_HPP
#define ANTKEEPER_INPUT_MAPPING_TYPE_HPP
#include <cstdint>
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,

+ 131
- 1
src/input/mapping.cpp View File

@ -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<input::gamepad_axis_mapping>::serialize(const input::gamepad_axis_mapping& mapping, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&mapping.axis), 1);
const std::uint8_t direction = mapping.direction;
ctx.write8(reinterpret_cast<const std::byte*>(&direction), 1);
}
template <>
void serializer<input::gamepad_button_mapping>::serialize(const input::gamepad_button_mapping& mapping, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&mapping.button), 1);
}
template <>
void serializer<input::key_mapping>::serialize(const input::key_mapping& mapping, serialize_context& ctx)
{
ctx.write16<std::endian::big>(reinterpret_cast<const std::byte*>(&mapping.scancode), 1);
ctx.write16<std::endian::big>(reinterpret_cast<const std::byte*>(&mapping.modifiers), 1);
const std::uint8_t repeat = mapping.repeat;
ctx.write8(reinterpret_cast<const std::byte*>(&repeat), 1);
}
template <>
void serializer<input::mouse_button_mapping>::serialize(const input::mouse_button_mapping& mapping, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&mapping.button), 1);
}
template <>
void serializer<input::mouse_motion_mapping>::serialize(const input::mouse_motion_mapping& mapping, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&mapping.axis), 1);
const std::uint8_t direction = mapping.direction;
ctx.write8(reinterpret_cast<const std::byte*>(&direction), 1);
}
template <>
void serializer<input::mouse_scroll_mapping>::serialize(const input::mouse_scroll_mapping& mapping, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&mapping.axis), 1);
const std::uint8_t direction = mapping.direction;
ctx.write8(reinterpret_cast<const std::byte*>(&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<input::gamepad_axis_mapping>::deserialize(input::gamepad_axis_mapping& mapping, deserialize_context& ctx)
{
mapping.gamepad = nullptr;
ctx.read8(reinterpret_cast<std::byte*>(&mapping.axis), 1);
std::uint8_t direction = 0;
ctx.read8(reinterpret_cast<std::byte*>(&direction), 1);
mapping.direction = direction;
}
template <>
void deserializer<input::gamepad_button_mapping>::deserialize(input::gamepad_button_mapping& mapping, deserialize_context& ctx)
{
mapping.gamepad = nullptr;
ctx.read8(reinterpret_cast<std::byte*>(&mapping.button), 1);
}
template <>
void deserializer<input::key_mapping>::deserialize(input::key_mapping& mapping, deserialize_context& ctx)
{
mapping.keyboard = nullptr;
ctx.read16<std::endian::big>(reinterpret_cast<std::byte*>(&mapping.scancode), 1);
ctx.read16<std::endian::big>(reinterpret_cast<std::byte*>(&mapping.modifiers), 1);
std::uint8_t repeat = 0;
ctx.read8(reinterpret_cast<std::byte*>(&repeat), 1);
mapping.repeat = repeat;
}
template <>
void deserializer<input::mouse_button_mapping>::deserialize(input::mouse_button_mapping& mapping, deserialize_context& ctx)
{
mapping.mouse = nullptr;
ctx.read8(reinterpret_cast<std::byte*>(&mapping.button), 1);
}
template <>
void deserializer<input::mouse_motion_mapping>::deserialize(input::mouse_motion_mapping& mapping, deserialize_context& ctx)
{
mapping.mouse = nullptr;
ctx.read8(reinterpret_cast<std::byte*>(&mapping.axis), 1);
std::uint8_t direction = 0;
ctx.read8(reinterpret_cast<std::byte*>(&direction), 1);
mapping.direction = direction;
}
template <>
void deserializer<input::mouse_scroll_mapping>::deserialize(input::mouse_scroll_mapping& mapping, deserialize_context& ctx)
{
mapping.mouse = nullptr;
ctx.read8(reinterpret_cast<std::byte*>(&mapping.axis), 1);
std::uint8_t direction = 0;
ctx.read8(reinterpret_cast<std::byte*>(&direction), 1);
mapping.direction = direction;
}
/// @}

+ 4
- 4
src/input/mapping.hpp View File

@ -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;
};
/**

+ 41
- 0
src/resources/control-profile-loader.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "resources/resource-loader.hpp"
#include "resources/serializer.hpp"
#include "resources/deserializer.hpp"
#include "game/control-profile.hpp"
template <>
game::control_profile* resource_loader<game::control_profile>::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<game::control_profile>().deserialize(*profile, ctx);
return profile;
}
template <>
void resource_loader<game::control_profile>::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const game::control_profile* profile)
{
serialize_context ctx(file);
serializer<game::control_profile>().serialize(*profile, ctx);
}

+ 85
- 19
src/resources/deserialize-context.hpp View File

@ -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 <std::endian Endian = std::endian::native>
template <std::endian Endian>
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 <std::endian Endian = std::endian::native>
template <std::endian Endian>
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 <std::endian Endian = std::endian::native>
template <std::endian Endian>
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;

+ 14
- 14
src/resources/deserializer.cpp View File

@ -37,19 +37,19 @@ void deserializer::deserialize(std::uint8_t& value, deserialize_co
template <>
void deserializer<std::uint16_t>::deserialize(std::uint16_t& value, deserialize_context& ctx)
{
ctx.read16(reinterpret_cast<std::byte*>(&value), 1);
ctx.read16<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::uint32_t>::deserialize(std::uint32_t& value, deserialize_context& ctx)
{
ctx.read32(reinterpret_cast<std::byte*>(&value), 1);
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::uint64_t>::deserialize(std::uint64_t& value, deserialize_context& ctx)
{
ctx.read64(reinterpret_cast<std::byte*>(&value), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
@ -61,38 +61,38 @@ void deserializer::deserialize(std::int8_t& value, deserialize_cont
template <>
void deserializer<std::int16_t>::deserialize(std::int16_t& value, deserialize_context& ctx)
{
ctx.read16(reinterpret_cast<std::byte*>(&value), 1);
ctx.read16<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::int32_t>::deserialize(std::int32_t& value, deserialize_context& ctx)
{
ctx.read32(reinterpret_cast<std::byte*>(&value), 1);
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::int64_t>::deserialize(std::int64_t& value, deserialize_context& ctx)
{
ctx.read64(reinterpret_cast<std::byte*>(&value), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<float>::deserialize(float& value, deserialize_context& ctx)
{
ctx.read32(reinterpret_cast<std::byte*>(&value), 1);
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<double>::deserialize(double& value, deserialize_context& ctx)
{
ctx.read64(reinterpret_cast<std::byte*>(&value), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::string>::deserialize(std::string& value, deserialize_context& ctx)
{
std::uint64_t length = 0;
ctx.read64(reinterpret_cast<std::byte*>(&length), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&length), 1);
value.resize(static_cast<std::size_t>(length));
ctx.read8(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};
@ -101,7 +101,7 @@ template <>
void deserializer<std::u8string>::deserialize(std::u8string& value, deserialize_context& ctx)
{
std::uint64_t length = 0;
ctx.read64(reinterpret_cast<std::byte*>(&length), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&length), 1);
value.resize(static_cast<std::size_t>(length));
ctx.read8(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};
@ -110,16 +110,16 @@ template <>
void deserializer<std::u16string>::deserialize(std::u16string& value, deserialize_context& ctx)
{
std::uint64_t length = 0;
ctx.read64(reinterpret_cast<std::byte*>(&length), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&length), 1);
value.resize(static_cast<std::size_t>(length));
ctx.read16(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
ctx.read16<std::endian::big>(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};
template <>
void deserializer<std::u32string>::deserialize(std::u32string& value, deserialize_context& ctx)
{
std::uint64_t length = 0;
ctx.read64(reinterpret_cast<std::byte*>(&length), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&length), 1);
value.resize(static_cast<std::size_t>(length));
ctx.read32(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};

+ 69
- 45
src/resources/serialize-context.cpp View File

@ -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<PHYSFS_File*>(handle);
const PHYSFS_uint16* data16 = reinterpret_cast<const PHYSFS_uint16*>(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<PHYSFS_File*>(handle);
const PHYSFS_uint16* data16 = reinterpret_cast<const PHYSFS_uint16*>(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<PHYSFS_File*>(handle);
const PHYSFS_uint32* data32 = reinterpret_cast<const PHYSFS_uint32*>(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<PHYSFS_File*>(handle);
const PHYSFS_uint32* data32 = reinterpret_cast<const PHYSFS_uint32*>(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<PHYSFS_File*>(handle);
const PHYSFS_uint64* data64 = reinterpret_cast<const PHYSFS_uint64*>(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<PHYSFS_File*>(handle);
const PHYSFS_uint64* data64 = reinterpret_cast<const PHYSFS_uint64*>(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;

+ 115
- 6
src/resources/serialize-context.hpp View File

@ -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 <std::endian Endian>
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 <std::endian Endian>
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 <std::endian Endian>
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;
}

+ 14
- 14
src/resources/serializer.cpp View File

@ -36,19 +36,19 @@ void serializer::serialize(const std::uint8_t& value, serialize_co
template <>
void serializer<std::uint16_t>::serialize(const std::uint16_t& value, serialize_context& ctx)
{
ctx.write16(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write16<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::uint32_t>::serialize(const std::uint32_t& value, serialize_context& ctx)
{
ctx.write32(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::uint64_t>::serialize(const std::uint64_t& value, serialize_context& ctx)
{
ctx.write64(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
@ -60,38 +60,38 @@ void serializer::serialize(const std::int8_t& value, serialize_cont
template <>
void serializer<std::int16_t>::serialize(const std::int16_t& value, serialize_context& ctx)
{
ctx.write16(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write16<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::int32_t>::serialize(const std::int32_t& value, serialize_context& ctx)
{
ctx.write32(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::int64_t>::serialize(const std::int64_t& value, serialize_context& ctx)
{
ctx.write64(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<float>::serialize(const float& value, serialize_context& ctx)
{
ctx.write32(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<double>::serialize(const double& value, serialize_context& ctx)
{
ctx.write64(reinterpret_cast<const std::byte*>(&value), 1);
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::string>::serialize(const std::string& value, serialize_context& ctx)
{
const std::uint64_t length = static_cast<std::uint64_t>(value.length());
ctx.write64(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write8(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
};
@ -99,7 +99,7 @@ template <>
void serializer<std::u8string>::serialize(const std::u8string& value, serialize_context& ctx)
{
const std::uint64_t length = static_cast<std::uint64_t>(value.length());
ctx.write64(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write8(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
};
@ -107,14 +107,14 @@ template <>
void serializer<std::u16string>::serialize(const std::u16string& value, serialize_context& ctx)
{
const std::uint64_t length = static_cast<std::uint64_t>(value.length());
ctx.write64(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write16(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write16<std::endian::big>(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
};
template <>
void serializer<std::u32string>::serialize(const std::u32string& value, serialize_context& ctx)
{
const std::uint64_t length = static_cast<std::uint64_t>(value.length());
ctx.write64(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write32(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&length), 1);
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
};

+ 41
- 0
src/resources/string-map-loader.cpp View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "resources/resource-loader.hpp"
#include "resources/serializer.hpp"
#include "resources/deserializer.hpp"
#include "i18n/string-map.hpp"
template <>
i18n::string_map* resource_loader<i18n::string_map>::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<i18n::string_map>().deserialize(*map, ctx);
return map;
}
template <>
void resource_loader<i18n::string_map>::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const i18n::string_map* map)
{
serialize_context ctx(file);
serializer<i18n::string_map>().serialize(*map, ctx);
}

+ 10
- 6
src/type/freetype/typeface.cpp View File

@ -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<char32_t>(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<FT_ULong>(code)) != 0;
}
bool typeface::get_metrics(float height, font_metrics& metrics) const
{
// Set font size

+ 0
- 3
src/type/freetype/typeface.hpp View File

@ -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;

+ 18
- 20
src/type/typeface.hpp View File

@ -24,6 +24,7 @@
#include "type/glyph-metrics.hpp"
#include "resources/image.hpp"
#include "utility/fundamental-types.hpp"
#include <unordered_set>
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<char32_t>& get_charset() const noexcept
{
return charset;
}
protected:
std::unordered_set<char32_t> 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

+ 17
- 9
src/utility/dict.cpp View File

@ -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<std::uint64_t>(dict.size());
ctx.write64(reinterpret_cast<const std::byte*>(&size), 1);
ctx.write64<std::endian::big>(reinterpret_cast<const std::byte*>(&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<const std::byte*>(&type_hash), 1);
ctx.write32(reinterpret_cast<const std::byte*>(&key), 1);
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&type_hash), 1);
ctx.write32<std::endian::big>(reinterpret_cast<const std::byte*>(&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<std::u32string>}
};
dict.clear();
// Read dict size
std::uint64_t size = 0;
ctx.read64(reinterpret_cast<std::byte*>(&size), 1);
ctx.read64<std::endian::big>(reinterpret_cast<std::byte*>(&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<std::byte*>(&type_hash), 1);
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&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<std::byte*>(&key), 1);
ctx.read32<std::endian::big>(reinterpret_cast<std::byte*>(&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");
}
}
};
}

Loading…
Cancel
Save