Browse Source

Add i18n namespace. Switch to using hash strings for localized strings. Change settings to be dict-based. Improve path finding on windows. Improve window management. Add serializer and deserializer template classes

master
C. J. Howard 1 year ago
parent
commit
a615ef7cf1
66 changed files with 2634 additions and 1337 deletions
  1. +1
    -0
      CMakeLists.txt
  2. +2
    -3
      CONTRIBUTING.md
  3. +4
    -4
      README.md
  4. +250
    -94
      src/application.cpp
  5. +66
    -16
      src/application.hpp
  6. +1
    -1
      src/color/aces.hpp
  7. +0
    -26
      src/debug/log.cpp
  8. +12
    -31
      src/debug/log.hpp
  9. +6
    -6
      src/event/queue.hpp
  10. +18
    -13
      src/game/context.hpp
  11. +1
    -1
      src/game/controls.cpp
  12. +21
    -40
      src/game/fonts.cpp
  13. +11
    -39
      src/game/graphics.cpp
  14. +0
    -101
      src/game/save.cpp
  15. +67
    -0
      src/game/settings.hpp
  16. +211
    -208
      src/game/state/boot.cpp
  17. +2
    -2
      src/game/state/boot.hpp
  18. +11
    -7
      src/game/state/controls-menu.cpp
  19. +12
    -12
      src/game/state/credits.cpp
  20. +10
    -6
      src/game/state/extras-menu.cpp
  21. +11
    -11
      src/game/state/gamepad-config-menu.cpp
  22. +75
    -123
      src/game/state/graphics-menu.cpp
  23. +11
    -11
      src/game/state/keyboard-config-menu.cpp
  24. +44
    -45
      src/game/state/language-menu.cpp
  25. +18
    -17
      src/game/state/main-menu.cpp
  26. +13
    -28
      src/game/state/nest-selection.cpp
  27. +10
    -17
      src/game/state/nuptial-flight.cpp
  28. +14
    -11
      src/game/state/options-menu.cpp
  29. +13
    -10
      src/game/state/pause-menu.cpp
  30. +17
    -21
      src/game/state/sound-menu.cpp
  31. +7
    -13
      src/game/state/splash.cpp
  32. +13
    -17
      src/game/strings.cpp
  33. +42
    -0
      src/game/strings.hpp
  34. +4
    -4
      src/game/system/subterrain.cpp
  35. +51
    -128
      src/game/world.cpp
  36. +21
    -21
      src/gl/gl.hpp
  37. +26
    -0
      src/i18n/i18n.hpp
  38. +51
    -0
      src/i18n/string-map.cpp
  39. +14
    -22
      src/i18n/string-map.hpp
  40. +14
    -16
      src/i18n/string-table.hpp
  41. +56
    -10
      src/input/event.hpp
  42. +28
    -10
      src/main.cpp
  43. +3
    -1
      src/render/anti-aliasing-method.hpp
  44. +1
    -0
      src/render/passes/fxaa-pass.cpp
  45. +0
    -1
      src/render/passes/ground-pass.cpp
  46. +0
    -2
      src/render/passes/material-pass.cpp
  47. +0
    -1
      src/render/passes/sky-pass.cpp
  48. +176
    -0
      src/resources/deserialize-context.cpp
  49. +152
    -0
      src/resources/deserialize-context.hpp
  50. +10
    -23
      src/resources/deserialize-error.hpp
  51. +125
    -0
      src/resources/deserializer.cpp
  52. +42
    -0
      src/resources/deserializer.hpp
  53. +43
    -0
      src/resources/dict-loader.cpp
  54. +32
    -14
      src/resources/resource-loader.cpp
  55. +31
    -0
      src/resources/resource-manager.cpp
  56. +2
    -0
      src/resources/resource-manager.hpp
  57. +144
    -0
      src/resources/serialize-context.cpp
  58. +100
    -0
      src/resources/serialize-context.hpp
  59. +33
    -0
      src/resources/serialize-error.hpp
  60. +120
    -0
      src/resources/serializer.cpp
  61. +42
    -0
      src/resources/serializer.hpp
  62. +43
    -101
      src/resources/string-table-loader.cpp
  63. +168
    -0
      src/utility/dict.cpp
  64. +34
    -0
      src/utility/dict.hpp
  65. +54
    -35
      src/utility/paths.cpp
  66. +20
    -14
      src/utility/paths.hpp

+ 1
- 0
CMakeLists.txt View File

@ -1,5 +1,6 @@
cmake_minimum_required(VERSION 3.25)
option(VERSION_STRING "Project version string" "0.0.0")
project(antkeeper VERSION ${VERSION_STRING} LANGUAGES CXX)

+ 2
- 3
CONTRIBUTING.md View File

@ -6,7 +6,7 @@ Thanks for taking the time to make Antkeeper better! ❤🐜
To report a bug, please do one of the following:
* [Open an issue](https://git.antkeeper.com/antkeeper/source/issues/new) on Antkeeper Git
* [Open an issue](https://github.com/antkeeper/antkeeper-source/issues) on GitHub
* [Email us](mailto:contact@antkeeper.com) a description of the bug
* [Chat with us](https://discord.gg/AQA955HbK3) about the bug on Discord
@ -19,7 +19,7 @@ To suggest changes or new features, please do one of the following:
## Contributing Code
If you have written code that fixes a bug or provides new functionality, please [open a pull request](https://git.antkeeper.com/antkeeper/source/pulls) on Antkeeper Git.
If you have written code that fixes a bug or provides new functionality, please [open a pull request](https://github.com/antkeeper/antkeeper-source/pulls) on GitHub.
### Coding Conventions
@ -27,4 +27,3 @@ Ensure that all code contributions adhere to the following conventions:
* C++ STL naming conventions
* Allman indent style with tabs
* Unlimited line length

+ 4
- 4
README.md View File

@ -1,16 +1,16 @@
# Antkeeper Source
Antkeeper is an ant colony simulation game currently in development for Windows, Mac, and Linux. This repository contains all of the source code to Antkeeper.
Antkeeper is a 3D ant colony simulation game currently in development for Windows, Mac, and Linux. This repository contains all of the source code to Antkeeper.
Head over to [antkeeper.com](https://antkeeper.com/) if you're interested in following the development of the game or purchasing a copy when it's released. Antkeeper is an indie game with a single developer, so feel free to reach out to me personally with any questions, comments, or feedback you may have.
## Building
Download the Antkeeper build system, source code, and all dependencies via the [Antkeeper superbuild](https://git.antkeeper.com/antkeeper/superbuild) repository:
Download the Antkeeper source code, build system, and all dependencies via the [Antkeeper superbuild](https://github.com/antkeeper/antkeeper-superbuild) repository:
git clone --recursive https://git.antkeeper.com/antkeeper/superbuild.git
git clone --recursive https://github.com/antkeeper/antkeeper-superbuild.git
Detailed configuration and build instructions can be found in the [README](https://git.antkeeper.com/antkeeper/superbuild/src/branch/master/README.md) of the superbuild repository.
Detailed configuration and build instructions can be found in the [README](https://github.com/antkeeper/antkeeper-superbuild/blob/master/README.md) of the superbuild repository.
## Documentation

+ 250
- 94
src/application.cpp View File

@ -22,36 +22,51 @@
#include "debug/log.hpp"
#include "input/scancode.hpp"
#include "math/map.hpp"
#include "resources/image.hpp"
#include <SDL2/SDL.h>
#include <glad/glad.h>
#include <cstring>
#include <iomanip>
#include <stdexcept>
#include <utility>
application::application():
application::application
(
const std::string& window_title,
int window_x,
int window_y,
int window_w,
int window_h,
bool maximized,
bool fullscreen,
bool v_sync
):
closed(false),
maximized(false),
fullscreen(true),
v_sync(false),
cursor_visible(true),
display_dimensions({0, 0}),
display_size({0, 0}),
display_dpi(0.0f),
window_dimensions({0, 0}),
viewport_dimensions({0, 0}),
windowed_position({-1, -1}),
windowed_size({-1, -1}),
viewport_size({-1, -1}),
mouse_position({0, 0}),
sdl_window(nullptr),
sdl_gl_context(nullptr)
{
// Log SDL compiled version
SDL_version sdl_compiled_version;
SDL_VERSION(&sdl_compiled_version);
debug::log::debug("Compiled against SDL {}.{}.{}", sdl_compiled_version.major, sdl_compiled_version.minor, sdl_compiled_version.patch);
// Log SDL linked version
SDL_version sdl_linked_version;
SDL_GetVersion(&sdl_linked_version);
debug::log::debug("Linking against SDL {}.{}.{}", sdl_linked_version.major, sdl_linked_version.minor, sdl_linked_version.patch);
// Log SDL info
// SDL_version sdl_compiled_version;
// SDL_version sdl_linked_version;
// SDL_VERSION(&sdl_compiled_version);
// SDL_GetVersion(&sdl_linked_version);
// debug::log::info
// (
// "SDL compiled version: {}.{}.{}; linked version: {}.{}.{}",
// sdl_compiled_version.major,
// sdl_compiled_version.minor,
// sdl_compiled_version.patch,
// sdl_linked_version.major,
// sdl_linked_version.minor,
// sdl_linked_version.patch
// );
// Init SDL events and video subsystems
debug::log::trace("Initializing SDL events and video subsystems...");
@ -62,6 +77,53 @@ application::application():
}
debug::log::trace("Initialized SDL events and video subsystems");
// Query displays
debug::log::trace("Querying displays...");
const int sdl_display_count = SDL_GetNumVideoDisplays();
if (sdl_display_count < 1)
{
debug::log::fatal("No displays detected: {}", SDL_GetError());
throw std::runtime_error("No displays detected");
}
debug::log::info("Display count: {}", sdl_display_count);
for (int i = 0; i < sdl_display_count; ++i)
{
// Query display mode
SDL_DisplayMode sdl_display_mode;
if (SDL_GetDesktopDisplayMode(i, &sdl_display_mode) != 0)
{
debug::log::error("Failed to get mode of display {}: {}", i, SDL_GetError());
SDL_ClearError();
continue;
}
// Query display name
const char* sdl_display_name = SDL_GetDisplayName(i);
if (!sdl_display_name)
{
debug::log::warning("Failed to get name of display {}: {}", i, SDL_GetError());
SDL_ClearError();
sdl_display_name = "";
}
// Query display DPI
float sdl_display_dpi;
if (SDL_GetDisplayDPI(i, &sdl_display_dpi, nullptr, nullptr) != 0)
{
const float default_dpi = 96.0f;
debug::log::warning("Failed to get DPI of display {}: {}; Defaulting to {} DPI", i, SDL_GetError(), default_dpi);
SDL_ClearError();
}
// Log display information
debug::log::info("Display {} name: \"{}\"; resolution: {}x{}; refresh rate: {}Hz; DPI: {}", i, sdl_display_name, sdl_display_mode.w, sdl_display_mode.h, sdl_display_mode.refresh_rate, sdl_display_dpi);
}
debug::log::trace("Queried displays");
// Detect display dimensions
SDL_DisplayMode sdl_desktop_display_mode;
if (SDL_GetDesktopDisplayMode(0, &sdl_desktop_display_mode) != 0)
@ -69,7 +131,7 @@ application::application():
debug::log::fatal("Failed to detect desktop display mode: {}", SDL_GetError());
throw std::runtime_error("Failed to detect desktop display mode");
}
display_dimensions = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
display_size = {sdl_desktop_display_mode.w, sdl_desktop_display_mode.h};
// Detect display DPI
if (SDL_GetDisplayDPI(0, &display_dpi, nullptr, nullptr) != 0)
@ -104,21 +166,55 @@ application::application():
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, config::opengl_min_depth_size);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, config::opengl_min_stencil_size);
Uint32 sdl_window_flags = SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE;
if (fullscreen)
{
sdl_window_flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
}
if (maximized)
{
sdl_window_flags |= SDL_WINDOW_MAXIMIZED;
}
if (window_x == -1 && window_y == -1)
{
window_x = SDL_WINDOWPOS_CENTERED;
window_y = SDL_WINDOWPOS_CENTERED;
}
if (window_w <= 0 || window_h <= 0)
{
window_w = sdl_desktop_display_mode.w / 2;
window_h = sdl_desktop_display_mode.h / 2;
}
// Create a hidden fullscreen window
debug::log::trace("Creating {}x{} window...", display_dimensions[0], display_dimensions[1]);
debug::log::trace("Creating window...");
sdl_window = SDL_CreateWindow
(
config::application_name,
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
display_dimensions[0], display_dimensions[1],
SDL_WINDOW_OPENGL | SDL_WINDOW_FULLSCREEN_DESKTOP | SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_HIDDEN
window_title.c_str(),
window_x,
window_y,
window_w,
window_h,
sdl_window_flags
);
if (!sdl_window)
{
debug::log::fatal("Failed to create {}x{} window: {}", display_dimensions[0], display_dimensions[1], SDL_GetError());
debug::log::fatal("Failed to create {}x{} window: {}", display_size[0], display_size[1], SDL_GetError());
throw std::runtime_error("Failed to create SDL window");
}
debug::log::trace("Created {}x{} window", display_dimensions[0], display_dimensions[1]);
debug::log::trace("Created window");
if (window_x != SDL_WINDOWPOS_CENTERED && window_y != SDL_WINDOWPOS_CENTERED)
{
this->windowed_position = {window_x, window_y};
}
this->windowed_size = {window_w, window_h};
this->maximized = maximized;
this->fullscreen = fullscreen;
// Set hard window minimum size
SDL_SetWindowMinimumSize(sdl_window, 160, 120);
// Create OpenGL context
debug::log::trace("Creating OpenGL {}.{} context...", config::opengl_version_major, config::opengl_version_minor);
@ -156,7 +252,7 @@ application::application():
SDL_GL_GetAttribute(SDL_GL_ALPHA_SIZE, &opengl_context_alpha_size);
SDL_GL_GetAttribute(SDL_GL_DEPTH_SIZE, &opengl_context_depth_size);
SDL_GL_GetAttribute(SDL_GL_STENCIL_SIZE, &opengl_context_stencil_size);
debug::log::info("OpenGL context default framebuffer format: R{}G{}B{}A{}D{}S{}", opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size);
debug::log::info("OpenGL context format: R{}G{}B{}A{}D{}S{}", opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size);
// Compare OpenGL context default framebuffer format with request format
if (opengl_context_red_size < config::opengl_min_red_size ||
@ -168,7 +264,7 @@ application::application():
{
debug::log::warning
(
"OpenGL default framebuffer format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})",
"OpenGL context format (R{}G{}B{}A{}D{}S{}) does not meet minimum requested format (R{}G{}B{}A{}D{}S{})",
opengl_context_red_size, opengl_context_green_size, opengl_context_blue_size, opengl_context_alpha_size, opengl_context_depth_size, opengl_context_stencil_size,
config::opengl_min_red_size, config::opengl_min_green_size, config::opengl_min_blue_size, config::opengl_min_alpha_size, config::opengl_min_depth_size, config::opengl_min_stencil_size
);
@ -183,13 +279,27 @@ application::application():
}
debug::log::trace("Loaded OpenGL functions");
// Update window size and viewport size
SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
// Log OpenGL context information
debug::log::info
(
"OpenGL vendor: {}; renderer: {}; version: {}; shading language version: {}",
reinterpret_cast<const char*>(glGetString(GL_VENDOR)),
reinterpret_cast<const char*>(glGetString(GL_RENDERER)),
reinterpret_cast<const char*>(glGetString(GL_VERSION)),
reinterpret_cast<const char*>(glGetString(GL_SHADING_LANGUAGE_VERSION))
);
// Clear window color
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
swap_buffers();
// Set v-sync mode
set_v_sync(true);
set_v_sync(v_sync);
// Update viewport size
SDL_GL_GetDrawableSize(sdl_window, &viewport_size[0], &viewport_size[1]);
// Init SDL joystick and controller subsystems
debug::log::trace("Initializing SDL joystick and controller subsystems...");
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) != 0)
@ -203,6 +313,7 @@ application::application():
// Setup rasterizer
rasterizer = new gl::rasterizer();
rasterizer->context_resized(viewport_size[0], viewport_size[1]);
// Register keyboard and mouse with input device manager
device_manager.register_device(keyboard);
@ -266,8 +377,8 @@ void application::set_relative_mouse_mode(bool enabled)
void application::resize_window(int width, int height)
{
int x = (display_dimensions[0] >> 1) - (width >> 1);
int y = (display_dimensions[1] >> 1) - (height >> 1);
int x = (display_size[0] >> 1) - (width >> 1);
int y = (display_size[1] >> 1) - (height >> 1);
// Resize and center window
SDL_SetWindowPosition(sdl_window, x, y);
@ -282,58 +393,51 @@ void application::set_fullscreen(bool fullscreen)
if (fullscreen)
{
SDL_HideWindow(sdl_window);
SDL_SetWindowFullscreen(sdl_window, SDL_WINDOW_FULLSCREEN_DESKTOP);
SDL_ShowWindow(sdl_window);
}
else
{
SDL_SetWindowFullscreen(sdl_window, 0);
SDL_SetWindowBordered(sdl_window, SDL_TRUE);
SDL_SetWindowResizable(sdl_window, SDL_TRUE);
}
}
}
void application::set_v_sync(bool v_sync)
{
if (this->v_sync != v_sync)
if (v_sync)
{
if (v_sync)
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
{
debug::log::trace("Enabling adaptive v-sync...");
if (SDL_GL_SetSwapInterval(-1) != 0)
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
debug::log::error("Failed to enable adaptive v-sync: {}", SDL_GetError());
debug::log::trace("Enabling synchronized v-sync...");
if (SDL_GL_SetSwapInterval(1) != 0)
{
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
debug::log::debug("Enabled synchronized v-sync");
}
debug::log::error("Failed to enable synchronized v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
debug::log::debug("Enabled adaptive v-sync");
debug::log::debug("Enabled synchronized v-sync");
}
}
else
{
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
debug::log::debug("Disabled v-sync");
}
this->v_sync = v_sync;
debug::log::debug("Enabled adaptive v-sync");
}
}
else
{
debug::log::trace("Disabling v-sync...");
if (SDL_GL_SetSwapInterval(0) != 0)
{
debug::log::error("Failed to disable v-sync: {}", SDL_GetError());
}
else
{
this->v_sync = v_sync;
debug::log::debug("Disabled v-sync");
}
}
}
@ -525,27 +629,97 @@ void application::process_events()
switch (sdl_event.window.event)
{
case SDL_WINDOWEVENT_SIZE_CHANGED:
window_resized();
{
// Query SDL window parameters
SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID);
const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window);
int sdl_window_drawable_w = 0;
int sdl_window_drawable_h = 0;
SDL_GL_GetDrawableSize(sdl_window, &sdl_window_drawable_w, &sdl_window_drawable_h);
// Build window resized event
input::event::window_resized event;
event.window = nullptr;
event.size.x() = static_cast<std::int32_t>(sdl_event.window.data1);
event.size.y() = static_cast<std::int32_t>(sdl_event.window.data2);
event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED;
event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN;
event.viewport_size.x() = static_cast<std::int32_t>(sdl_window_drawable_w);
event.viewport_size.y() = static_cast<std::int32_t>(sdl_window_drawable_h);
// Update windowed size
if (!event.maximized && !event.fullscreen)
{
windowed_size = event.size;
}
// Update GL context size
rasterizer->context_resized(event.viewport_size.x(), event.viewport_size.y());
// Publish window resized event
window_resized_publisher.publish(event);
break;
}
case SDL_WINDOWEVENT_MOVED:
{
// Query SDL window parameters
SDL_Window* sdl_window = SDL_GetWindowFromID(sdl_event.window.windowID);
const auto sdl_window_flags = SDL_GetWindowFlags(sdl_window);
// Build window moved event
input::event::window_moved event;
event.window = nullptr;
event.position.x() = static_cast<std::int32_t>(sdl_event.window.data1);
event.position.y() = static_cast<std::int32_t>(sdl_event.window.data2);
event.maximized = sdl_window_flags & SDL_WINDOW_MAXIMIZED;
event.fullscreen = sdl_window_flags & SDL_WINDOW_FULLSCREEN;
// Update windowed position
if (!event.maximized && !event.fullscreen)
{
windowed_position = event.position;
}
// Publish window moved event
window_moved_publisher.publish(event);
break;
}
case SDL_WINDOWEVENT_FOCUS_GAINED:
debug::log::debug("Window focus gained");
window_focus_changed_publisher.publish({true});
// Build and publish window focused gained event
window_focus_changed_publisher.publish({nullptr, true});
break;
case SDL_WINDOWEVENT_FOCUS_LOST:
debug::log::debug("Window focus lost");
window_focus_changed_publisher.publish({false});
// Build and publish window focused lost event
window_focus_changed_publisher.publish({nullptr, false});
break;
case SDL_WINDOWEVENT_MOVED:
debug::log::trace("Window moved to ({}, {})", sdl_event.window.data1, sdl_event.window.data2);
window_moved_publisher.publish({static_cast<int>(sdl_event.window.data1), static_cast<int>(sdl_event.window.data2)});
case SDL_WINDOWEVENT_MAXIMIZED:
// Update window maximized
maximized = true;
// Build and publish window maximized event
window_maximized_publisher.publish({nullptr});
break;
case SDL_WINDOWEVENT_RESTORED:
// Update window maximized
maximized = false;
// Build and publish window restored event
window_restored_publisher.publish({nullptr});
break;
case SDL_WINDOWEVENT_MINIMIZED:
// Build and publish window minimized event
window_minimized_publisher.publish({nullptr});
break;
[[unlikely]] case SDL_WINDOWEVENT_CLOSE:
debug::log::info("Window closed");
window_closed_publisher.publish({});
// Build and publish window closed event
window_closed_publisher.publish({nullptr});
break;
default:
@ -630,6 +804,9 @@ void application::process_events()
close();
break;
}
default:
break;
}
}
@ -639,24 +816,3 @@ void application::process_events()
// mouse.move(mouse_x, mouse_y, mouse_dx, mouse_dy);
// }
}
void application::window_resized()
{
// Update window size and viewport size
SDL_GetWindowSize(sdl_window, &window_dimensions[0], &window_dimensions[1]);
SDL_GL_GetDrawableSize(sdl_window, &viewport_dimensions[0], &viewport_dimensions[1]);
debug::log::debug("Window resized to {}x{}", window_dimensions[0], window_dimensions[1]);
rasterizer->context_resized(viewport_dimensions[0], viewport_dimensions[1]);
window_resized_publisher.publish
(
{
window_dimensions[0],
window_dimensions[1],
viewport_dimensions[0],
viewport_dimensions[1]
}
);
}

+ 66
- 16
src/application.hpp View File

@ -45,7 +45,17 @@ public:
/**
* Constructs and initializes an application.
*/
application();
application
(
const std::string& window_title,
int window_x,
int window_y,
int window_w,
int window_h,
bool maximized,
bool fullscreen,
bool v_sync
);
/**
* Destructs an application.
@ -87,9 +97,9 @@ public:
void resize_window(int width, int height);
/**
* Puts the application window into either fullscreen or windowed mode.
* Puts the application window into either fullscreen or window mode.
*
* @param fullscreen `true` if the window should be fullscreen, `false` if it should be windowed.
* @param fullscreen `true` if the window should be fullscreen, `false` if it should be window.
*/
void set_fullscreen(bool fullscreen);
@ -110,16 +120,22 @@ public:
void add_game_controller_mappings(const void* mappings, std::size_t size);
/// Returns the dimensions of the current display.
[[nodiscard]] const int2& get_display_dimensions() const;
[[nodiscard]] const int2& get_display_size() const;
/// Returns the DPI of the display.
[[nodiscard]] float get_display_dpi() const;
/// Returns the dimensions of the window.
[[nodiscard]] const int2& get_window_dimensions() const;
/// Returns the position of the window when not maximized or fullscreen.
[[nodiscard]] const int2& get_windowed_position() const;
/// Returns the dimensions of the window when not maximized or fullscreen.
[[nodiscard]] const int2& get_windowed_size() const;
/// Returns the dimensions of the window's drawable viewport.
[[nodiscard]] const int2& get_viewport_dimensions() const;
[[nodiscard]] const int2& get_viewport_size() const;
/// Returns `true` if the window is maximized, `false` otherwise.
[[nodiscard]] bool is_maximized() const;
/// Returns `true` if the window is in fullscreen mode, `false` otherwise.
[[nodiscard]] bool is_fullscreen() const;
@ -172,17 +188,38 @@ public:
return window_resized_publisher.channel();
}
/// Returns the channel through which window maximized events are published.
[[nodiscard]] inline event::channel<input::event::window_maximized>& get_window_maximized_channel() noexcept
{
return window_maximized_publisher.channel();
}
/// Returns the channel through which window restored events are published.
[[nodiscard]] inline event::channel<input::event::window_restored>& get_window_restored_channel() noexcept
{
return window_restored_publisher.channel();
}
/// Returns the channel through which window minimized events are published.
[[nodiscard]] inline event::channel<input::event::window_minimized>& get_window_minimized_channel() noexcept
{
return window_minimized_publisher.channel();
}
private:
void window_moved();
void window_resized();
bool closed;
bool maximized;
bool fullscreen;
bool v_sync;
bool cursor_visible;
int2 display_dimensions;
int2 display_size;
float display_dpi;
int2 window_dimensions;
int2 viewport_dimensions;
int2 windowed_position;
int2 windowed_size;
int2 viewport_size;
int2 mouse_position;
SDL_Window* sdl_window;
@ -200,11 +237,14 @@ private:
event::publisher<input::event::window_focus_changed> window_focus_changed_publisher;
event::publisher<input::event::window_moved> window_moved_publisher;
event::publisher<input::event::window_resized> window_resized_publisher;
event::publisher<input::event::window_maximized> window_maximized_publisher;
event::publisher<input::event::window_restored> window_restored_publisher;
event::publisher<input::event::window_minimized> window_minimized_publisher;
};
inline const int2& application::get_display_dimensions() const
inline const int2& application::get_display_size() const
{
return display_dimensions;
return display_size;
}
inline float application::get_display_dpi() const
@ -212,14 +252,24 @@ inline float application::get_display_dpi() const
return display_dpi;
}
inline const int2& application::get_window_dimensions() const
inline const int2& application::get_windowed_position() const
{
return windowed_position;
}
inline const int2& application::get_windowed_size() const
{
return windowed_size;
}
inline const int2& application::get_viewport_size() const
{
return window_dimensions;
return viewport_size;
}
inline const int2& application::get_viewport_dimensions() const
inline bool application::is_maximized() const
{
return viewport_dimensions;
return maximized;
}
inline bool application::is_fullscreen() const

+ 1
- 1
src/color/aces.hpp View File

@ -31,7 +31,7 @@ namespace aces {
/// CIE xy chromaticity coordinates of the ACES white point (~D60).
template <class T>
constexpr math::vector2<T> white_point = {T{0.32168}, {0.33767}};
constexpr math::vector2<T> white_point = {T{0.32168}, T{0.33767}};
/// ACES AP0 color space.
template <class T>

+ 0
- 26
src/debug/log.cpp View File

@ -28,31 +28,5 @@ logger& default_logger() noexcept
return instance;
}
void push_task(const std::string& description, std::source_location&& location)
{
default_logger().log(description + " {", message_severity::info, std::forward<std::source_location>(location));
}
void pop_task(int status, const std::string& description, std::source_location&& location)
{
std::string message = "} => ";
if (status == EXIT_SUCCESS)
{
message += "success";
default_logger().log(std::move(message), message_severity::info, std::forward<std::source_location>(location));
}
else
{
message += "failure";
if (!description.empty())
{
message += "(" + description + ")";
}
default_logger().log(std::move(message), message_severity::error, std::forward<std::source_location>(location));
}
}
} // namespace log
} // namespace debug

+ 12
- 31
src/debug/log.hpp View File

@ -90,7 +90,8 @@ message(std::string_view, Args&&...) -> message;
using trace = message<message_severity::trace, Args...>;
#else
// Disable trace message logging.
inline void trace([[maybe_unused]] ...) noexcept {};
template <class... Args>
inline void trace([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_DEBUG)
@ -103,7 +104,8 @@ message(std::string_view, Args&&...) -> message;
using debug = message<message_severity::debug, Args...>;
#else
// Disable debug message logging.
inline void debug([[maybe_unused]] ...) noexcept {};
template <class... Args>
inline void debug([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_INFO)
@ -116,7 +118,8 @@ message(std::string_view, Args&&...) -> message;
using info = message<message_severity::info, Args...>;
#else
// Disable info message logging.
inline void info([[maybe_unused]] ...) noexcept {};
template <class... Args>
inline void info([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_WARNING)
@ -129,7 +132,8 @@ message(std::string_view, Args&&...) -> message;
using warning = message<message_severity::warning, Args...>;
#else
// Disable warning message logging.
inline void warning([[maybe_unused]] ...) noexcept {};
template <class... Args>
inline void warning([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_ERROR)
@ -142,7 +146,8 @@ message(std::string_view, Args&&...) -> message;
using error = message<message_severity::error, Args...>;
#else
// Disable error message logging.
inline void error([[maybe_unused]] ...) noexcept {};
template <class... Args>
inline void error([[maybe_unused]] Args&&...) noexcept {};
#endif
#if (ANTKEEPER_DEBUG_LOG_MIN_MESSAGE_SEVERITY <= ANTKEEPER_DEBUG_LOG_MESSAGE_SEVERITY_FATAL)
@ -155,34 +160,10 @@ message(std::string_view, Args&&...) -> message;
using fatal = message<message_severity::fatal, Args...>;
#else
// Disable fatal error message logging.
inline void fatal([[maybe_unused]] ...) noexcept {};
template <class... Args>
inline void fatal([[maybe_unused]] Args&&...) noexcept {};
#endif
/**
* Pushes a task onto the default logger's task stack and writes its description the log.
*
* @param description Task description.
* @param location Source location from which the message was sent.
*/
void push_task
(
const std::string& description,
std::source_location&& location = std::source_location::current()
);
/**
* Pops a task off the default logger's task stack and writes its status to the log.
*
* @param status Exit status of the task. A value of `0` or `EXIT_SUCCESS` indicates the task exited successfully. A non-zero exit status indicates the task failed.
* @param description Error code description.
*/
void pop_task
(
int status,
const std::string& description = std::string(),
std::source_location&& location = std::source_location::current()
);
} // namespace log
} // namespace debug

+ 6
- 6
src/event/queue.hpp View File

@ -20,15 +20,15 @@
#ifndef ANTKEEPER_EVENT_QUEUE_HPP
#define ANTKEEPER_EVENT_QUEUE_HPP
#include "event/subscriber.hpp"
#include "event/subscription.hpp"
#include <any>
#include <functional>
#include <list>
#include <map>
#include <memory>
#include <typeindex>
#include <utility>
#include "event/subscriber.hpp"
#include "event/subscription.hpp"
#include "utility/type-id.hpp"
namespace event {
@ -54,7 +54,7 @@ public:
std::shared_ptr<std::any> shared_subscriber = std::make_shared<std::any>(std::make_any<event::subscriber<T>>(std::move(subscriber)));
// Append subscriber to subscriber list and store iterator
auto iterator = subscribers.emplace(type_id<T>, shared_subscriber);
auto iterator = subscribers.emplace(std::type_index(typeid(T)), shared_subscriber);
// Construct and return a shared subscription object which removes the subscriber from the subscriber list when unsubscribed or destructed
return std::make_shared<subscription>
@ -126,7 +126,7 @@ private:
void distribute(const T& message) const
{
// For each subscriber of the given message type
const auto range = subscribers.equal_range(type_id<T>);
const auto range = subscribers.equal_range(std::type_index(typeid(T)));
for (auto i = range.first; i != range.second; ++i)
{
// Send message to subscriber
@ -134,7 +134,7 @@ private:
}
}
std::multimap<type_id_t, std::shared_ptr<std::any>> subscribers;
std::multimap<std::type_index, std::shared_ptr<std::any>> subscribers;
std::list<std::function<void()>> messages;
};

+ 18
- 13
src/game/context.hpp View File

@ -42,12 +42,14 @@
#include "render/material-property.hpp"
#include "render/material.hpp"
#include "resources/json.hpp"
#include "resources/string-table.hpp"
#include "scene/scene.hpp"
#include "state-machine.hpp"
#include "type/bitmap-font.hpp"
#include "type/typeface.hpp"
#include "utility/dict.hpp"
#include "utility/fundamental-types.hpp"
#include "i18n/string-table.hpp"
#include "i18n/string-map.hpp"
#include <AL/al.h>
#include <AL/alc.h>
#include <entt/entt.hpp>
@ -121,6 +123,9 @@ namespace game {
/// Container for data shared between game states.
struct context
{
// Configuration
dict<std::uint32_t>* settings;
/// Hierarchichal state machine
hsm::state_machine<game::state::base> state_machine;
std::function<void()> resume_callback;
@ -162,7 +167,8 @@ struct context
// Paths
std::filesystem::path data_path;
std::filesystem::path config_path;
std::filesystem::path local_config_path;
std::filesystem::path shared_config_path;
std::filesystem::path mods_path;
std::filesystem::path saves_path;
std::filesystem::path screenshots_path;
@ -172,16 +178,11 @@ struct context
// Resources
resource_manager* resource_manager;
// Configuration
json* config;
// Localization
std::string language_code;
int language_count;
int language_index;
string_table* string_table;
string_table_map string_table_map;
std::unordered_map<std::string, std::string>* strings;
std::uint16_t language_index;
std::uint16_t language_count;
i18n::string_table* string_table;
std::vector<i18n::string_map> string_maps;
std::unordered_map<std::string, type::typeface*> typefaces;
type::bitmap_font debug_font;
type::bitmap_font menu_font;
@ -206,6 +207,7 @@ struct context
render::renderer* renderer;
int2 render_resolution;
float render_scale;
int shadow_map_resolution;
gl::vertex_buffer* billboard_vbo;
gl::vertex_array* billboard_vao;
render::material* fallback_material;
@ -237,8 +239,12 @@ struct context
scene::collection* ui_scene;
scene::camera* ui_camera;
scene::billboard* camera_flash_billboard;
float font_size;
float font_scale;
bool dyslexia_font;
float debug_font_size_pt;
float menu_font_size_pt;
float title_font_size_pt;
std::vector<std::function<void()>> menu_select_callbacks;
std::vector<std::function<void()>> menu_left_callbacks;
std::vector<std::function<void()>> menu_right_callbacks;
@ -310,7 +316,6 @@ struct context
const ecoregion* active_ecoregion;
bool bloom_enabled;
render::anti_aliasing_method anti_aliasing_method;
std::shared_ptr<::event::subscription> ui_resize_subscription;

+ 1
- 1
src/game/controls.cpp View File

@ -358,7 +358,7 @@ void save_control_profile(game::context& ctx)
/*
std::filesystem::path path;
if (ctx.config->contains("control_profile"))
path = ctx.config_path / "controls" / (*ctx.config)["control_profile"].get<std::string>();
path = ctx.shared_config_path / "controls" / (*ctx.config)["control_profile"].get<std::string>();
debug::log::trace("Saving control profile to \"{}\"...", path.string());
try

+ 21
- 40
src/game/fonts.cpp View File

@ -25,8 +25,12 @@
#include "gl/texture-filter.hpp"
#include "render/material.hpp"
#include "render/material-flags.hpp"
#include "utility/hash/fnv1a.hpp"
#include "game/strings.hpp"
#include <codecvt>
using namespace hash::literals;
namespace game {
static void build_bitmap_font(const type::typeface& typeface, float size, const std::unordered_set<char32_t>& charset, type::bitmap_font& font, render::material& material, gl::shader_program* shader_program)
@ -72,12 +76,9 @@ void load_fonts(game::context& ctx)
bool dyslexia_font_loaded = false;
if (ctx.dyslexia_font)
{
if (auto it = ctx.strings->find("font_dyslexia"); it != ctx.strings->end() && !it->second.empty() && it->second[0] != '#')
{
debug::log::info(it->second);
ctx.typefaces["dyslexia"] = ctx.resource_manager->load<type::typeface>(it->second);
dyslexia_font_loaded = true;
}
const auto dyslexia_font_path = get_string(ctx, "font_dyslexia"_fnv1a32);
ctx.typefaces["dyslexia"] = ctx.resource_manager->load<type::typeface>(dyslexia_font_path);
dyslexia_font_loaded = true;
}
// Load typefaces
@ -91,12 +92,13 @@ void load_fonts(game::context& ctx)
else
{
// Load standard typefaces
if (auto it = ctx.strings->find("font_serif"); it != ctx.strings->end())
ctx.typefaces["serif"] = ctx.resource_manager->load<type::typeface>(it->second);
if (auto it = ctx.strings->find("font_sans_serif"); it != ctx.strings->end())
ctx.typefaces["sans_serif"] = ctx.resource_manager->load<type::typeface>(it->second);
if (auto it = ctx.strings->find("font_monospace"); it != ctx.strings->end())
ctx.typefaces["monospace"] = ctx.resource_manager->load<type::typeface>(it->second);
const auto serif_font_path = get_string(ctx, "font_serif"_fnv1a32);
const auto sans_serif_font_path = get_string(ctx, "font_sans_serif"_fnv1a32);
const auto monospace_font_path = get_string(ctx, "font_monospace"_fnv1a32);
ctx.typefaces["serif"] = ctx.resource_manager->load<type::typeface>(serif_font_path);
ctx.typefaces["sans_serif"] = ctx.resource_manager->load<type::typeface>(sans_serif_font_path);
ctx.typefaces["monospace"] = ctx.resource_manager->load<type::typeface>(monospace_font_path);
}
// Build character set
@ -107,7 +109,8 @@ void load_fonts(game::context& ctx)
charset.insert(code);
// Add all character codes from game strings
for (auto it = ctx.strings->begin(); it != ctx.strings->end(); ++it)
const auto& string_map = ctx.string_maps[ctx.language_index];
for (auto it = string_map.begin(); it != string_map.end(); ++it)
{
// Convert UTF-8 string to UTF-32
std::wstring_convert<std::codecvt_utf8<char32_t>, char32_t> convert;
@ -122,47 +125,25 @@ void load_fonts(game::context& ctx)
// Load bitmap font shader
gl::shader_program* bitmap_font_shader = ctx.resource_manager->load<gl::shader_program>("bitmap-font.glsl");
// Determine font point sizes
float debug_font_size_pt = 12.0f;
float menu_font_size_pt = 12.0f;
float title_font_size_pt = 12.0f;
if (ctx.config->contains("debug_font_size"))
debug_font_size_pt = (*ctx.config)["debug_font_size"].get<float>();
if (ctx.config->contains("menu_font_size"))
menu_font_size_pt = (*ctx.config)["menu_font_size"].get<float>();
if (ctx.config->contains("title_font_size"))
title_font_size_pt = (*ctx.config)["title_font_size"].get<float>();
// Scale font point sizes
const float font_size = (*ctx.config)["font_size"].get<float>();
debug_font_size_pt *= font_size;
menu_font_size_pt *= font_size;
title_font_size_pt *= font_size;
// Convert font point sizes to pixel sizes
const float dpi = ctx.app->get_display_dpi();
const float debug_font_size_px = (debug_font_size_pt * dpi) / 72.0f;
const float menu_font_size_px = (menu_font_size_pt * dpi) / 72.0f;
const float title_font_size_px = (title_font_size_pt * dpi) / 72.0f;
debug::log::info("font size: " + std::to_string(menu_font_size_px));
// Point size to pixel size conversion factor
const float pt_to_px = (ctx.app->get_display_dpi() / 72.0f) * ctx.font_scale;
// Build debug font
if (auto it = ctx.typefaces.find("monospace"); it != ctx.typefaces.end())
{
build_bitmap_font(*it->second, debug_font_size_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, 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, menu_font_size_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, 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, title_font_size_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, charset, ctx.title_font, ctx.title_font_material, bitmap_font_shader);
}
}

+ 11
- 39
src/game/graphics.cpp View File

@ -44,14 +44,9 @@ void create_framebuffers(game::context& ctx)
{
debug::log::trace("Creating framebuffers...");
// Load render resolution scale from config
ctx.render_scale = 1.0f;
if (ctx.config->contains("render_scale"))
ctx.render_scale = (*ctx.config)["render_scale"].get<float>();
// Calculate render resolution
const int2& viewport_dimensions = ctx.app->get_viewport_dimensions();
ctx.render_resolution = {static_cast<int>(viewport_dimensions.x() * ctx.render_scale + 0.5f), static_cast<int>(viewport_dimensions.y() * ctx.render_scale + 0.5f)};
const int2& viewport_size = ctx.app->get_viewport_size();
ctx.render_resolution = {static_cast<int>(viewport_size.x() * ctx.render_scale + 0.5f), static_cast<int>(viewport_size.y() * ctx.render_scale + 0.5f)};
// Create HDR framebuffer (32F color, 32F depth)
ctx.hdr_color_texture = new gl::texture_2d(ctx.render_resolution.x(), ctx.render_resolution.y(), gl::pixel_type::float_32, gl::pixel_format::rgb);
@ -82,17 +77,12 @@ void create_framebuffers(game::context& ctx)
ctx.ldr_framebuffer_b = new gl::framebuffer(ctx.render_resolution.x(), ctx.render_resolution.y());
ctx.ldr_framebuffer_b->attach(gl::framebuffer_attachment_type::color, ctx.ldr_color_texture_b);
// Load shadow map resolution from config
int shadow_map_resolution = 4096;
if (ctx.config->contains("shadow_map_resolution"))
shadow_map_resolution = (*ctx.config)["shadow_map_resolution"].get<int>();
// Create shadow map framebuffer
ctx.shadow_map_depth_texture = new gl::texture_2d(shadow_map_resolution, shadow_map_resolution, gl::pixel_type::float_32, gl::pixel_format::d);
ctx.shadow_map_depth_texture = new gl::texture_2d(ctx.shadow_map_resolution, ctx.shadow_map_resolution, gl::pixel_type::float_32, gl::pixel_format::d);
ctx.shadow_map_depth_texture->set_wrapping(gl::texture_wrapping::extend, gl::texture_wrapping::extend);
ctx.shadow_map_depth_texture->set_filters(gl::texture_min_filter::linear, gl::texture_mag_filter::linear);
ctx.shadow_map_depth_texture->set_max_anisotropy(0.0f);
ctx.shadow_map_framebuffer = new gl::framebuffer(shadow_map_resolution, shadow_map_resolution);
ctx.shadow_map_framebuffer = new gl::framebuffer(ctx.shadow_map_resolution, ctx.shadow_map_resolution);
ctx.shadow_map_framebuffer->attach(gl::framebuffer_attachment_type::depth, ctx.shadow_map_depth_texture);
debug::log::trace("Created framebuffers");
@ -138,8 +128,8 @@ void change_render_resolution(game::context& ctx, float scale)
ctx.render_scale = scale;
// Recalculate render resolution
const int2& viewport_dimensions = ctx.app->get_viewport_dimensions();
ctx.render_resolution = {static_cast<int>(viewport_dimensions.x() * ctx.render_scale + 0.5f), static_cast<int>(viewport_dimensions.y() * ctx.render_scale + 0.5f)};
const int2& viewport_size = ctx.app->get_viewport_size();
ctx.render_resolution = {static_cast<int>(viewport_size.x() * ctx.render_scale + 0.5f), static_cast<int>(viewport_size.y() * ctx.render_scale + 0.5f)};
// Resize HDR framebuffer and attachments
ctx.hdr_framebuffer->resize({ctx.render_resolution.x(), ctx.render_resolution.y()});
@ -156,7 +146,7 @@ void change_render_resolution(game::context& ctx, float scale)
ctx.bloom_pass->resize();
// Enable or disable resample pass
if (viewport_dimensions.x() != ctx.render_resolution.x() || viewport_dimensions.y() != ctx.render_resolution.y())
if (viewport_size.x() != ctx.render_resolution.x() || viewport_size.y() != ctx.render_resolution.y())
{
ctx.resample_pass->set_enabled(true);
}
@ -176,21 +166,21 @@ void save_screenshot(game::context& ctx)
const std::string screenshot_filename = std::format("{0}-{1:%Y%m%d}T{1:%H%M%S}Z.png", config::application_name, time);
// Determine path to screenshot file
std::filesystem::path screenshot_filepath = ctx.config_path / "gallery" / screenshot_filename;
std::filesystem::path screenshot_filepath = ctx.screenshots_path / screenshot_filename;
std::string screenshot_filepath_string = screenshot_filepath.string();
debug::log::debug("Saving screenshot to \"{}\"...", screenshot_filepath_string);
// Get viewport dimensions
const int2& viewport_dimensions = ctx.app->get_viewport_dimensions();
const int2& viewport_size = ctx.app->get_viewport_size();
// Allocate screenshot image
std::shared_ptr<image> frame = std::make_shared<image>();
frame->format(1, 3);
frame->resize(viewport_dimensions.x(), viewport_dimensions.y());
frame->resize(viewport_size.x(), viewport_size.y());
// Read pixel data from backbuffer into image
glReadBuffer(GL_BACK);
glReadPixels(0, 0, viewport_dimensions.x(), viewport_dimensions.y(), GL_RGB, GL_UNSIGNED_BYTE, frame->data());
glReadPixels(0, 0, viewport_size.x(), viewport_size.y(), GL_RGB, GL_UNSIGNED_BYTE, frame->data());
// Write screenshot file in separate thread
std::thread
@ -207,24 +197,6 @@ void save_screenshot(game::context& ctx)
}
void toggle_bloom(game::context& ctx, bool enabled)
{
if (enabled)
{
ctx.bloom_pass->set_mip_chain_length(6);
ctx.bloom_pass->set_enabled(true);
ctx.common_final_pass->set_bloom_weight(0.04f);
}
else
{
ctx.bloom_pass->set_mip_chain_length(0);
ctx.bloom_pass->set_enabled(false);
ctx.common_final_pass->set_bloom_weight(0.0f);
}
ctx.bloom_enabled = enabled;
}
void select_anti_aliasing_method(game::context& ctx, render::anti_aliasing_method method)
{
// Switch AA method

+ 0
- 101
src/game/save.cpp View File

@ -1,101 +0,0 @@
/*
* 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/save.hpp"
#include "application.hpp"
#include "debug/log.hpp"
#include "resources/json.hpp"
#include <fstream>
namespace game {
namespace save {
void colony(game::context& ctx)
{
std::filesystem::path path = ctx.saves_path / "colony.sav";
const std::string path_string = path.string();
debug::log::trace("Saving colony to \"{}\"...", path_string);
try
{
// Construct JSON data describing the colony
json data;
auto& colony = data["colony"];
{
auto& species = colony["species"];
{
auto& morphology = species["morphology"];
{
}
auto& diet = species["diet"];
auto& aggression = species["aggression"];
auto& nest = species["nest"];
}
auto& habitat = colony["habitat"];
{
auto& biome = habitat["biome"];
auto& nest = habitat["nest"];
{
nest["entrance"] = {0, 0, 0};
}
}
auto& members = colony["members"];
members = json::array();
{
}
}
std::ofstream file(path);
file << data;
debug::log::trace("Saved colony to \"{}\"", path_string);
}
catch (...)
{
debug::log::error("Failed to save colony to \"{}\"", path_string);
}
}
void config(game::context& ctx)
{
std::filesystem::path path = ctx.config_path / "config.json";
const std::string path_string = path.string();
debug::log::trace("Saving config to \"{}\"...", path_string);
try
{
std::ofstream file(path);
file << *(ctx.config);
debug::log::trace("Saved config to \"{}\"", path_string);
}
catch (...)
{
debug::log::error("Failed to save config to \"{}\"", path_string);
}
}
} // namespace save
} // namespace game

+ 67
- 0
src/game/settings.hpp View File

@ -0,0 +1,67 @@
/*
* 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 ANTKEEPER_GAME_SETTINGS_HPP
#define ANTKEEPER_GAME_SETTINGS_HPP
#include "game/context.hpp"
#include "debug/log.hpp"
namespace game {
/**
* Reads a setting if found, inserts a setting if not found, and overwrites a setting if a type mismatch occurs.
*
* @tparam T Setting value type.
*
* @param[in] ctx Game context.
* @param[in] key Setting key.
* @param[in,out] Setting value.
*
* @return `true` if the setting was read, `false` if the setting was written.
*/
template <class T>
bool read_or_write_setting(game::context& ctx, std::uint32_t key, T& value)
{
if (auto i = ctx.settings->find(key); i != ctx.settings->end())
{
try
{
value = std::any_cast<T>(i->second);
}
catch (const std::bad_any_cast& e)
{
debug::log::error("Setting type mismatch ({:x}={})", key, value);
i->second = value;
return false;
}
}
else
{
debug::log::trace("Setting key not found ({:x}={})", key, value);
(*ctx.settings)[key] = value;
return false;
}
return true;
}
} // namespace game
#endif // ANTKEEPER_GAME_SETTINGS_HPP

+ 211
- 208
src/game/state/boot.cpp View File

@ -33,7 +33,7 @@
#include "game/fonts.hpp"
#include "game/graphics.hpp"
#include "game/menu.hpp"
#include "game/save.hpp"
#include "game/strings.hpp"
#include "game/state/boot.hpp"
#include "game/state/splash.hpp"
#include "game/system/astronomy.hpp"
@ -52,6 +52,7 @@
#include "game/system/subterrain.hpp"
#include "game/system/terrain.hpp"
#include "game/system/vegetation.hpp"
#include "game/settings.hpp"
#include "gl/framebuffer.hpp"
#include "gl/pixel-format.hpp"
#include "gl/pixel-type.hpp"
@ -86,6 +87,8 @@
#include "resources/resource-manager.hpp"
#include "scene/scene.hpp"
#include "utility/paths.hpp"
#include "utility/dict.hpp"
#include "utility/hash/fnv1a.hpp"
#include <algorithm>
#include <cxxopts.hpp>
#include <entt/entt.hpp>
@ -95,6 +98,8 @@
#include <string>
#include <vector>
using namespace hash::literals;
namespace game {
namespace state {
@ -104,16 +109,50 @@ boot::boot(game::context& ctx, int argc, char** argv):
// Boot process
debug::log::trace("Booting up...");
// Allocate application
ctx.app = new application();
// Parse command line options
parse_options(argc, argv);
// Parse command line arguments
parse_arguments(argc, argv);
// Setup resource management
setup_resources();
load_config();
load_strings();
// Load settings
load_settings();
// Default window settings
std::string window_title = config::application_name;
int window_x = -1;
int window_y = -1;
int window_w = -1;
int window_h = -1;
bool maximized = true;
bool fullscreen = true;
bool v_sync = true;
// Read window settings
read_or_write_setting(ctx, "window_title"_fnv1a32, window_title);
read_or_write_setting(ctx, "window_x"_fnv1a32, window_x);
read_or_write_setting(ctx, "window_y"_fnv1a32, window_y);
read_or_write_setting(ctx, "window_w"_fnv1a32, window_w);
read_or_write_setting(ctx, "window_h"_fnv1a32, window_h);
read_or_write_setting(ctx, "maximized"_fnv1a32, maximized);
read_or_write_setting(ctx, "fullscreen"_fnv1a32, fullscreen);
read_or_write_setting(ctx, "v_sync"_fnv1a32, v_sync);
// Allocate application
ctx.app = new application
(
window_title,
window_x,
window_y,
window_w,
window_h,
maximized,
fullscreen,
v_sync
);
setup_window();
load_strings();
setup_rendering();
setup_audio();
setup_scenes();
@ -140,6 +179,21 @@ boot::~boot()
{
debug::log::trace("Booting down...");
// Update window settings
const auto& windowed_position = ctx.app->get_windowed_position();
const auto& windowed_size = ctx.app->get_windowed_size();
const bool maximized = ctx.app->is_maximized();
const bool fullscreen = ctx.app->is_fullscreen();
(*ctx.settings)["window_x"_fnv1a32] = windowed_position.x();
(*ctx.settings)["window_y"_fnv1a32] = windowed_position.y();
(*ctx.settings)["window_w"_fnv1a32] = windowed_size.x();
(*ctx.settings)["window_h"_fnv1a32] = windowed_size.y();
(*ctx.settings)["maximized"_fnv1a32] = maximized;
(*ctx.settings)["fullscreen"_fnv1a32] = fullscreen;
// Save settings
ctx.resource_manager->save<dict<std::uint32_t>>(ctx.settings, "settings.cfg");
shutdown_audio();
// Close application
@ -149,7 +203,7 @@ boot::~boot()
debug::log::trace("Boot down complete");
}
void boot::parse_options(int argc, char** argv)
void boot::parse_arguments(int argc, char** argv)
{
debug::log::trace("Parsing {} command line arguments...", argc);
@ -163,8 +217,8 @@ void boot::parse_options(int argc, char** argv)
("n,new-game", "Starts a new game")
("q,quick-start", "Skips to the main menu")
("r,reset", "Restores all settings to default")
("v,vsync", "Enables or disables v-sync", cxxopts::value<int>())
("w,windowed", "Starts in windowed mode");
("v,v_sync", "Enables or disables v-sync", cxxopts::value<int>())
("w,window", "Starts in window mode");
auto result = options.parse(argc, argv);
// --continue
@ -191,12 +245,12 @@ void boot::parse_options(int argc, char** argv)
if (result.count("reset"))
option_reset = true;
// --vsync
if (result.count("vsync"))
option_v_sync = (result["vsync"].as<int>()) ? true : false;
// --v_sync
if (result.count("v_sync"))
option_v_sync = (result["v_sync"].as<int>()) ? true : false;
// --windowed
if (result.count("windowed"))
// --window
if (result.count("window"))
option_windowed = true;
debug::log::trace("Parsed {} command line arguments", argc);
@ -207,27 +261,43 @@ void boot::parse_options(int argc, char** argv)
}
}
void boot::load_settings()
{
ctx.settings = ctx.resource_manager->load<dict<std::uint32_t>>("settings.cfg");
if (!ctx.settings)
{
debug::log::info("Settings not found");
ctx.settings = new dict<std::uint32_t>();
}
}
void boot::setup_resources()
{
// Setup resource manager
ctx.resource_manager = new resource_manager();
// Detect paths
ctx.data_path = get_data_path(config::application_name);
ctx.config_path = get_config_path(config::application_name);
ctx.mods_path = ctx.config_path / "mods";
ctx.saves_path = ctx.config_path / "saves";
ctx.screenshots_path = ctx.config_path / "gallery";
ctx.controls_path = ctx.config_path / "controls";
ctx.data_path = get_executable_data_path();
ctx.local_config_path = get_local_config_path() / config::application_name;
ctx.shared_config_path = get_shared_config_path() / config::application_name;
ctx.mods_path = ctx.data_path / "mods";
ctx.saves_path = ctx.shared_config_path / "saves";
ctx.screenshots_path = ctx.shared_config_path / "gallery";
ctx.controls_path = ctx.shared_config_path / "controls";
// Log resource paths
debug::log::info("Data path: \"{}\"", ctx.data_path.string());
debug::log::info("Config path: \"{}\"", ctx.config_path.string());
debug::log::info("Local config path: \"{}\"", ctx.local_config_path.string());
debug::log::info("Shared config path: \"{}\"", ctx.shared_config_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.config_path);
config_paths.push_back(ctx.mods_path);
config_paths.push_back(ctx.local_config_path);
config_paths.push_back(ctx.shared_config_path);
//config_paths.push_back(ctx.mods_path);
config_paths.push_back(ctx.saves_path);
config_paths.push_back(ctx.screenshots_path);
config_paths.push_back(ctx.controls_path);
@ -251,10 +321,16 @@ void boot::setup_resources()
// Scan for mods
std::vector<std::filesystem::path> mod_paths;
for (const auto& directory_entry: std::filesystem::directory_iterator(ctx.mods_path))
if (std::filesystem::is_directory(ctx.mods_path))
{
if (directory_entry.is_directory())
mod_paths.push_back(directory_entry.path());
for (const auto& entry: std::filesystem::directory_iterator{ctx.mods_path})
{
if (entry.is_directory() || (entry.is_regular_file() && entry.path().extension() == ".zip"))
{
mod_paths.push_back(entry.path());
debug::log::info("Found mod \"{}\"", entry.path().filename().string());
}
}
}
// Determine data package path
@ -271,10 +347,13 @@ void boot::setup_resources()
// Mount mods
for (const std::filesystem::path& mod_path: mod_paths)
{
ctx.resource_manager->mount(ctx.mods_path / mod_path);
}
// Mount config path
ctx.resource_manager->mount(ctx.config_path);
ctx.resource_manager->mount(ctx.local_config_path);
ctx.resource_manager->mount(ctx.shared_config_path);
// Mount data package
ctx.resource_manager->mount(ctx.data_package_path);
@ -284,110 +363,74 @@ void boot::setup_resources()
ctx.resource_manager->include("/");
}
void boot::load_config()
void boot::setup_window()
{
debug::log::trace("Loading config...");
// debug::log::trace("Setting up window...");
// Load config file
ctx.config = ctx.resource_manager->load<json>("config.json");
if (ctx.config)
{
debug::log::trace("Loaded config");
}
else
{
debug::log::error("Failed to load config");
}
// application* app = ctx.app;
// ctx.app->get_rasterizer()->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
// ctx.app->get_rasterizer()->clear_framebuffer(true, false, false);
// app->show_window();
// ctx.app->swap_buffers();
// debug::log::trace("Set up window");
}
void boot::load_strings()
{
debug::log::trace("Loading strings...");
ctx.string_table = ctx.resource_manager->load<string_table>("strings.csv");
// Default strings settings
ctx.language_index = 0;
// Read strings settings
read_or_write_setting(ctx, "language_index"_fnv1a32, ctx.language_index);
// Load string table
ctx.string_table = ctx.resource_manager->load<i18n::string_table>("strings.tsv");
build_string_table_map(&ctx.string_table_map, *ctx.string_table);
// Count languages
ctx.language_count = static_cast<std::uint16_t>((*ctx.string_table)[0].size() - 2);
ctx.language_code = (*ctx.config)["language"].get<std::string>();
ctx.language_index = -1;
for (int i = 2; i < (*ctx.string_table)[0].size(); ++i)
if (ctx.language_index >= ctx.language_count)
{
if ((*ctx.string_table)[0][i] == ctx.language_code)
ctx.language_index = i - 2;
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;
}
ctx.language_count = (*ctx.string_table)[0].size() - 2;
debug::log::info("Languages available: {}", ctx.language_count);
debug::log::info("Language index: {}", ctx.language_index);
debug::log::info("Language code: {}", ctx.language_code);
// 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]);
ctx.strings = &ctx.string_table_map[ctx.language_code];
// Log language info
debug::log::info("Language index: {}; code: {}", ctx.language_index, get_string(ctx, "language_code"_fnv1a32));
debug::log::trace("Loaded strings");
}
void boot::setup_window()
{
debug::log::trace("Setting up window...");
application* app = ctx.app;
json* config = ctx.config;
// Change window title
const std::string window_title = get_string(ctx, "application_title"_fnv1a32);
ctx.app->set_title(window_title);
// Set fullscreen or windowed mode
bool fullscreen = true;
if (option_fullscreen.has_value())
fullscreen = true;
else if (option_windowed.has_value())
fullscreen = false;
else if (config->contains("fullscreen"))
fullscreen = (*config)["fullscreen"].get<bool>();
app->set_fullscreen(fullscreen);
// Set resolution
const auto& display_dimensions = ctx.app->get_display_dimensions();
int2 resolution = {display_dimensions[0], display_dimensions[1]};
if (fullscreen)
{
if (config->contains("fullscreen_resolution"))
{
resolution.x() = (*config)["fullscreen_resolution"][0].get<int>();
resolution.y() = (*config)["fullscreen_resolution"][1].get<int>();
}
}
else
{
if (config->contains("windowed_resolution"))
{
resolution.x() = (*config)["windowed_resolution"][0].get<int>();
resolution.y() = (*config)["windowed_resolution"][1].get<int>();
}
}
app->resize_window(resolution.x(), resolution.y());
// Update window title setting
(*ctx.settings)["window_title"_fnv1a32] = window_title;
// Set v-sync
bool v_sync = true;
if (option_v_sync.has_value())
v_sync = (option_v_sync.value() != 0);
else if (config->contains("v_sync"))
v_sync = (*config)["v_sync"].get<bool>();
app->set_v_sync(v_sync);
// Set title
app->set_title((*ctx.strings)["application_title"]);
// Show window
ctx.app->get_rasterizer()->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f);
ctx.app->get_rasterizer()->clear_framebuffer(true, false, false);
app->show_window();
ctx.app->swap_buffers();
debug::log::trace("Set up window");
debug::log::trace("Loaded strings");
}
void boot::setup_rendering()
{
debug::log::trace("Setting up rendering...");
// Default rendering settings
ctx.render_scale = 1.0f;
ctx.anti_aliasing_method = render::anti_aliasing_method::fxaa;
ctx.shadow_map_resolution = 4096;
// Read rendering settings
read_or_write_setting(ctx, "render_scale"_fnv1a32, ctx.render_scale);
read_or_write_setting(ctx, "anti_aliasing_method"_fnv1a32, *reinterpret_cast<std::underlying_type_t<render::anti_aliasing_method>*>(&ctx.anti_aliasing_method));
read_or_write_setting(ctx, "shadow_map_resolution"_fnv1a32, ctx.shadow_map_resolution);
// Get rasterizer from application
ctx.rasterizer = ctx.app->get_rasterizer();
@ -421,38 +464,11 @@ void boot::setup_rendering()
ctx.resample_pass->set_source_texture(ctx.ldr_color_texture_b);
ctx.resample_pass->set_enabled(false);
// Toggle bloom according to settings
ctx.bloom_enabled = true;
if (ctx.config->contains("bloom_enabled"))
ctx.bloom_enabled = (*ctx.config)["bloom_enabled"].get<bool>();
graphics::toggle_bloom(ctx, ctx.bloom_enabled);
// Configure anti-aliasing according to settings
ctx.anti_aliasing_method = render::anti_aliasing_method::fxaa;
if (ctx.config->contains("anti_aliasing_method"))
{
const std::string aa_method = (*ctx.config)["anti_aliasing_method"].get<std::string>();
if (aa_method == "fxaa")
{
ctx.anti_aliasing_method = render::anti_aliasing_method::fxaa;
}
else
{
ctx.anti_aliasing_method = render::anti_aliasing_method::none;
}
}
graphics::select_anti_aliasing_method(ctx, ctx.anti_aliasing_method);
// Configure render scaling according to settings
ctx.render_scale = 1.0f;
if (ctx.config->contains("render_scale"))
{
ctx.render_scale = (*ctx.config)["render_scale"].get<float>();
if (ctx.render_scale != 1.0f)
{
graphics::change_render_resolution(ctx, ctx.render_scale);
}
}
graphics::change_render_resolution(ctx, ctx.render_scale);
}
// Setup UI compositor
@ -593,35 +609,21 @@ void boot::setup_audio()
{
debug::log::trace("Setting up audio...");
// Load master volume config
// Default audio settings
ctx.master_volume = 1.0f;
if (ctx.config->contains("master_volume"))
ctx.master_volume = (*ctx.config)["master_volume"].get<float>();
// Load ambience volume config
ctx.ambience_volume = 1.0f;
if (ctx.config->contains("ambience_volume"))
ctx.ambience_volume = (*ctx.config)["ambience_volume"].get<float>();
// Load effects volume config
ctx.effects_volume = 1.0f;
if (ctx.config->contains("effects_volume"))
ctx.effects_volume = (*ctx.config)["effects_volume"].get<float>();
// Load mono audio config
ctx.mono_audio = false;
if (ctx.config->contains("mono_audio"))
ctx.mono_audio = (*ctx.config)["mono_audio"].get<bool>();
// Load captions config
ctx.captions = false;
if (ctx.config->contains("captions"))
ctx.captions = (*ctx.config)["captions"].get<bool>();
// Load captions size config
ctx.captions_size = 1.0f;
if (ctx.config->contains("captions_size"))
ctx.captions_size = (*ctx.config)["captions_size"].get<float>();
// Read audio settings
read_or_write_setting(ctx, "master_volume"_fnv1a32, ctx.master_volume);
read_or_write_setting(ctx, "ambience_volume"_fnv1a32, ctx.ambience_volume);
read_or_write_setting(ctx, "effects_volume"_fnv1a32, ctx.effects_volume);
read_or_write_setting(ctx, "mono_audio"_fnv1a32, ctx.mono_audio);
read_or_write_setting(ctx, "captions"_fnv1a32, ctx.captions);
read_or_write_setting(ctx, "captions_size"_fnv1a32, ctx.captions_size);
// Open audio device
debug::log::trace("Opening audio device...");
@ -683,17 +685,16 @@ void boot::setup_scenes()
debug::log::trace("Setting up scenes...");
// Get default framebuffer
const auto& viewport_dimensions = ctx.rasterizer->get_default_framebuffer().get_dimensions();
const float viewport_aspect_ratio = static_cast<float>(viewport_dimensions[0]) / static_cast<float>(viewport_dimensions[1]);
const auto& viewport_size = ctx.app->get_viewport_size();
const float viewport_aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
// Setup UI camera
ctx.ui_camera = new scene::camera();
ctx.ui_camera->set_compositor(ctx.ui_compositor);
auto viewport = ctx.app->get_viewport_dimensions();
float clip_left = -viewport[0] * 0.5f;
float clip_right = viewport[0] * 0.5f;
float clip_top = -viewport[1] * 0.5f;
float clip_bottom = viewport[1] * 0.5f;
float clip_left = -viewport_size[0] * 0.5f;
float clip_right = viewport_size[0] * 0.5f;
float clip_top = -viewport_size[1] * 0.5f;
float clip_bottom = viewport_size[1] * 0.5f;
float clip_near = 0.0f;
float clip_far = 1000.0f;
ctx.ui_camera->set_orthographic(clip_left, clip_right, clip_top, clip_bottom, clip_near, clip_far);
@ -728,7 +729,7 @@ void boot::setup_scenes()
ctx.menu_bg_billboard = new scene::billboard();
ctx.menu_bg_billboard->set_active(false);
ctx.menu_bg_billboard->set_material(menu_bg_material);
ctx.menu_bg_billboard->set_scale({(float)viewport_dimensions[0] * 0.5f, (float)viewport_dimensions[1] * 0.5f, 1.0f});
ctx.menu_bg_billboard->set_scale({(float)viewport_size[0] * 0.5f, (float)viewport_size[1] * 0.5f, 1.0f});
ctx.menu_bg_billboard->set_translation({0.0f, 0.0f, -100.0f});
ctx.menu_bg_billboard->update_tweens();
@ -744,7 +745,7 @@ void boot::setup_scenes()
ctx.camera_flash_billboard = new scene::billboard();
ctx.camera_flash_billboard->set_material(flash_material);
ctx.camera_flash_billboard->set_scale({(float)viewport_dimensions[0] * 0.5f, (float)viewport_dimensions[1] * 0.5f, 1.0f});
ctx.camera_flash_billboard->set_scale({(float)viewport_size[0] * 0.5f, (float)viewport_size[1] * 0.5f, 1.0f});
ctx.camera_flash_billboard->set_translation({0.0f, 0.0f, 0.0f});
ctx.camera_flash_billboard->update_tweens();
@ -882,8 +883,8 @@ void boot::setup_entities()
void boot::setup_systems()
{
const auto& viewport_dimensions = ctx.app->get_viewport_dimensions();
float4 viewport = {0.0f, 0.0f, static_cast<float>(viewport_dimensions[0]), static_cast<float>(viewport_dimensions[1])};
const auto& viewport_size = ctx.app->get_viewport_size();
float4 viewport = {0.0f, 0.0f, static_cast<float>(viewport_size[0]), static_cast<float>(viewport_size[1])};
// Setup terrain system
ctx.terrain_system = new game::system::terrain(*ctx.entity_registry);
@ -977,18 +978,16 @@ void boot::setup_controls()
debug::log::trace("Loading controls...");
try
{
// If a control profile is set in the config file
if (ctx.config->contains("control_profile"))
{
// Load control profile
json* profile = ctx.resource_manager->load<json>((*ctx.config)["control_profile"].get<std::string>());
// Load control profile
// if (ctx.config->contains("control_profile"))
// {
// json* profile = ctx.resource_manager->load<json>((*ctx.config)["control_profile"].get<std::string>());
// Apply control profile
if (profile)
{
game::apply_control_profile(ctx, *profile);
}
}
// if (profile)
// {
// game::apply_control_profile(ctx, *profile);
// }
// }
// Calibrate gamepads
// for (input::gamepad* gamepad: ctx.app->get_gamepads())
@ -1026,20 +1025,17 @@ void boot::setup_controls()
{
bool fullscreen = !ctx.app->is_fullscreen();
// Toggle fullscreen
ctx.app->set_fullscreen(fullscreen);
// Update fullscreen setting
(*ctx.settings)["fullscreen"_fnv1a32] = fullscreen;
if (!fullscreen)
{
int2 resolution;
resolution.x() = (*ctx.config)["windowed_resolution"][0].get<int>();
resolution.y() = (*ctx.config)["windowed_resolution"][1].get<int>();
ctx.app->resize_window(resolution.x(), resolution.y());
// Restore window size and position
//ctx.app->resize_window(resolution.x(), resolution.y());
}
// Save display mode config
(*ctx.config)["fullscreen"] = fullscreen;
game::save::config(ctx);
}
)
);
@ -1081,15 +1077,19 @@ void boot::setup_controls()
void boot::setup_ui()
{
// Load font size config
ctx.font_size = 1.0f;
if (ctx.config->contains("font_size"))
ctx.font_size = (*ctx.config)["font_size"].get<float>();
// Load dyslexia font config
// Default UI settings
ctx.font_scale = 1.0f;
ctx.debug_font_size_pt = 12.0f;
ctx.menu_font_size_pt = 22.0f;
ctx.title_font_size_pt = 80.0f;
ctx.dyslexia_font = false;
if (ctx.config->contains("dyslexia_font"))
ctx.dyslexia_font = (*ctx.config)["dyslexia_font"].get<bool>();
// Read UI settings
read_or_write_setting(ctx, "font_scale"_fnv1a32, ctx.font_scale);
read_or_write_setting(ctx, "debug_font_size_pt"_fnv1a32, ctx.debug_font_size_pt);
read_or_write_setting(ctx, "menu_font_size_pt"_fnv1a32, ctx.menu_font_size_pt);
read_or_write_setting(ctx, "title_font_size_pt"_fnv1a32, ctx.title_font_size_pt);
read_or_write_setting(ctx, "dyslexia_font"_fnv1a32, ctx.dyslexia_font);
// Load fonts
debug::log::trace("Loading fonts...");
@ -1108,10 +1108,10 @@ void boot::setup_ui()
(
[&](const auto& event)
{
const float clip_left = static_cast<float>(event.viewport_width) * -0.5f;
const float clip_right = static_cast<float>(event.viewport_width) * 0.5f;
const float clip_top = static_cast<float>(event.viewport_height) * -0.5f;
const float clip_bottom = static_cast<float>(event.viewport_height) * 0.5f;
const float clip_left = static_cast<float>(event.viewport_size.x()) * -0.5f;
const float clip_right = static_cast<float>(event.viewport_size.x()) * 0.5f;
const float clip_top = static_cast<float>(event.viewport_size.y()) * -0.5f;
const float clip_bottom = static_cast<float>(event.viewport_size.y()) * 0.5f;
const float clip_near = ctx.ui_camera->get_clip_near();
const float clip_far = ctx.ui_camera->get_clip_far();
@ -1131,11 +1131,14 @@ void boot::setup_debugging()
void boot::setup_loop()
{
// Default loop settings
double update_frequency = 60.0;
// Read loop settings
read_or_write_setting(ctx, "update_frequency"_fnv1a32, update_frequency);
// Set update frequency
if (ctx.config->contains("update_frequency"))
{
ctx.loop.set_update_frequency((*ctx.config)["update_frequency"].get<double>());
}
ctx.loop.set_update_frequency(update_frequency);
// Set update callback
ctx.loop.set_update_callback

+ 2
- 2
src/game/state/boot.hpp View File

@ -48,9 +48,9 @@ public:
virtual ~boot();
private:
void parse_options(int argc, char** argv);
void parse_arguments(int argc, char** argv);
void setup_resources();
void load_config();
void load_settings();
void load_strings();
void setup_window();
void setup_rendering();

+ 11
- 7
src/game/state/controls-menu.cpp View File

@ -25,6 +25,10 @@
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/menu.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -32,7 +36,7 @@ namespace state {
controls_menu::controls_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering controls menu state");
debug::log::trace("Entering controls menu state...");
// Construct menu item texts
scene::text* keyboard_text = new scene::text();
@ -45,9 +49,9 @@ controls_menu::controls_menu(game::context& ctx):
ctx.menu_item_texts.push_back({back_text, nullptr});
// Set content of menu item texts
keyboard_text->set_content((*ctx.strings)["controls_menu_keyboard"]);
gamepad_text->set_content((*ctx.strings)["controls_menu_gamepad"]);
back_text->set_content((*ctx.strings)["back"]);
keyboard_text->set_content(get_string(ctx, "controls_menu_keyboard"_fnv1a32));
gamepad_text->set_content(get_string(ctx, "controls_menu_gamepad"_fnv1a32));
back_text->set_content(get_string(ctx, "back"_fnv1a32));
// Init menu item index
game::menu::init_menu_item_index(ctx, "controls");
@ -151,12 +155,12 @@ controls_menu::controls_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered controls menu state");
}
controls_menu::~controls_menu()
{
debug::log::push_task("Exiting options menu state");
debug::log::trace("Exiting options menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -165,7 +169,7 @@ controls_menu::~controls_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited controls menu state");
}
} // namespace state

+ 12
- 12
src/game/state/credits.cpp View File

@ -26,6 +26,10 @@
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -33,13 +37,13 @@ namespace state {
credits::credits(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering credits state");
debug::log::trace("Entering credits state...");
// Construct credits text
credits_text.set_material(&ctx.menu_font_material);
credits_text.set_font(&ctx.menu_font);
credits_text.set_color({1.0f, 1.0f, 1.0f, 0.0f});
credits_text.set_content((*ctx.strings)["credits"]);
credits_text.set_content(get_string(ctx, "credits"_fnv1a32));
// Align credits text
const auto& credits_aabb = static_cast<const geom::aabb<float>&>(credits_text.get_local_bounds());
@ -48,13 +52,9 @@ credits::credits(game::context& ctx):
credits_text.set_translation({std::round(-credits_w * 0.5f), std::round(-credits_h * 0.5f), 0.0f});
credits_text.update_tweens();
// Load animation timing configuration
double credits_fade_in_duration = 0.0;
double credits_scroll_duration = 0.0;
if (ctx.config->contains("credits_fade_in_duration"))
credits_fade_in_duration = (*ctx.config)["credits_fade_in_duration"].get<double>();
if (ctx.config->contains("credits_scroll_duration"))
credits_scroll_duration = (*ctx.config)["credits_scroll_duration"].get<double>();
// Set up animation timing configuration
const double credits_fade_in_duration = 0.5;
const double credits_scroll_duration = 5.0;
auto set_credits_opacity = [this](int channel, const float& opacity)
{
@ -98,12 +98,12 @@ credits::credits(game::context& ctx):
ctx.ui_scene->add_object(&credits_text);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered credits state");
}
credits::~credits()
{
debug::log::push_task("Exiting credits state");
debug::log::trace("Exiting credits state...");
// Disable credits skipper
ctx.input_mapper.disconnect();
@ -114,7 +114,7 @@ credits::~credits()
// Destruct credits animations
ctx.animator->remove_animation(&credits_fade_in_animation);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited credits state");
}
} // namespace state

+ 10
- 6
src/game/state/extras-menu.cpp View File

@ -25,6 +25,10 @@
#include "debug/log.hpp"
#include "game/fonts.hpp"
#include "game/menu.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -32,7 +36,7 @@ namespace state {
extras_menu::extras_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering extras menu state");
debug::log::trace("Entering extras menu state...");
// Construct menu item texts
scene::text* credits_text = new scene::text();
@ -43,8 +47,8 @@ extras_menu::extras_menu(game::context& ctx):
ctx.menu_item_texts.push_back({back_text, nullptr});
// Set content of menu item texts
credits_text->set_content((*ctx.strings)["extras_menu_credits"]);
back_text->set_content((*ctx.strings)["back"]);
credits_text->set_content(get_string(ctx, "extras_menu_credits"_fnv1a32));
back_text->set_content(get_string(ctx, "back"_fnv1a32));
// Init menu item index
game::menu::init_menu_item_index(ctx, "extras");
@ -123,12 +127,12 @@ extras_menu::extras_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered extras menu state");
}
extras_menu::~extras_menu()
{
debug::log::push_task("Exiting extras menu state");
debug::log::trace("Exiting extras menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -137,7 +141,7 @@ extras_menu::~extras_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited extras menu state");
}
} // namespace state

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

@ -26,6 +26,10 @@
#include "resources/resource-manager.hpp"
#include "game/menu.hpp"
#include "game/controls.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -33,7 +37,7 @@ namespace state {
gamepad_config_menu::gamepad_config_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering gamepad config menu state");
debug::log::trace("Entering gamepad config menu state...");
// Add camera control menu items
add_control_item("move_forward");
@ -54,7 +58,7 @@ gamepad_config_menu::gamepad_config_menu(game::context& ctx):
ctx.menu_item_texts.push_back({back_text, nullptr});
// Set content of menu item texts
back_text->set_content((*ctx.strings)["back"]);
back_text->set_content(get_string(ctx, "back"_fnv1a32));
// Init menu item index
game::menu::init_menu_item_index(ctx, "gamepad_config");
@ -108,12 +112,12 @@ gamepad_config_menu::gamepad_config_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered gamepad config menu state");
}
gamepad_config_menu::~gamepad_config_menu()
{
debug::log::push_task("Exiting gamepad config menu state");
debug::log::trace("Exiting gamepad config menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -125,7 +129,7 @@ gamepad_config_menu::~gamepad_config_menu()
// Save control profile
game::save_control_profile(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited gamepad config menu state");
}
std::string gamepad_config_menu::get_binding_string(input::control* control)
@ -292,11 +296,7 @@ void gamepad_config_menu::add_control_item(const std::string& control_name)
ctx.menu_item_texts.push_back({name_text, value_text});
// Set content of name text
std::string string_name = "control_" + control_name;
if (auto it = ctx.strings->find(string_name); it != ctx.strings->end())
name_text->set_content(it->second);
else
name_text->set_content(control_name);
name_text->set_content(control_name);
// Set content of value text
//value_text->set_content(get_binding_string(control));
@ -304,7 +304,7 @@ void gamepad_config_menu::add_control_item(const std::string& control_name)
auto select_callback = [this, &ctx = this->ctx, value_text]()
{
// Clear binding string from value text
value_text->set_content((*ctx.strings)["ellipsis"]);
value_text->set_content(get_string(ctx, "ellipsis"_fnv1a32));
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);

+ 75
- 123
src/game/state/graphics-menu.cpp View File

@ -26,6 +26,10 @@
#include "game/menu.hpp"
#include "game/graphics.hpp"
#include "animation/timeline.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -35,7 +39,7 @@ static void update_value_text_content(game::context* ctx);
graphics_menu::graphics_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering graphics menu state");
debug::log::trace("Entering graphics menu state...");
// Construct menu item texts
scene::text* fullscreen_name_text = new scene::text();
@ -46,10 +50,8 @@ graphics_menu::graphics_menu(game::context& ctx):
scene::text* v_sync_value_text = new scene::text();
scene::text* aa_method_name_text = new scene::text();
scene::text* aa_method_value_text = new scene::text();
scene::text* bloom_name_text = new scene::text();
scene::text* bloom_value_text = new scene::text();
scene::text* font_size_name_text = new scene::text();
scene::text* font_size_value_text = new scene::text();
scene::text* font_scale_name_text = new scene::text();
scene::text* font_scale_value_text = new scene::text();
scene::text* dyslexia_font_name_text = new scene::text();
scene::text* dyslexia_font_value_text = new scene::text();
scene::text* back_text = new scene::text();
@ -59,20 +61,18 @@ graphics_menu::graphics_menu(game::context& ctx):
ctx.menu_item_texts.push_back({resolution_name_text, resolution_value_text});
ctx.menu_item_texts.push_back({v_sync_name_text, v_sync_value_text});
ctx.menu_item_texts.push_back({aa_method_name_text, aa_method_value_text});
ctx.menu_item_texts.push_back({bloom_name_text, bloom_value_text});
ctx.menu_item_texts.push_back({font_size_name_text, font_size_value_text});
ctx.menu_item_texts.push_back({font_scale_name_text, font_scale_value_text});
ctx.menu_item_texts.push_back({dyslexia_font_name_text, dyslexia_font_value_text});
ctx.menu_item_texts.push_back({back_text, nullptr});
// Set content of menu item texts
fullscreen_name_text->set_content((*ctx.strings)["graphics_menu_fullscreen"]);
resolution_name_text->set_content((*ctx.strings)["graphics_menu_resolution"]);
v_sync_name_text->set_content((*ctx.strings)["graphics_menu_v_sync"]);
aa_method_name_text->set_content((*ctx.strings)["graphics_menu_aa_method"]);
bloom_name_text->set_content((*ctx.strings)["graphics_menu_bloom"]);
font_size_name_text->set_content((*ctx.strings)["graphics_menu_font_size"]);
dyslexia_font_name_text->set_content((*ctx.strings)["graphics_menu_dyslexia_font"]);
back_text->set_content((*ctx.strings)["back"]);
fullscreen_name_text->set_content(get_string(ctx, "graphics_menu_fullscreen"_fnv1a32));
resolution_name_text->set_content(get_string(ctx, "graphics_menu_resolution"_fnv1a32));
v_sync_name_text->set_content(get_string(ctx, "graphics_menu_v_sync"_fnv1a32));
aa_method_name_text->set_content(get_string(ctx, "graphics_menu_aa_method"_fnv1a32));
font_scale_name_text->set_content(get_string(ctx, "graphics_menu_font_scale"_fnv1a32));
dyslexia_font_name_text->set_content(get_string(ctx, "graphics_menu_dyslexia_font"_fnv1a32));
back_text->set_content(get_string(ctx, "back"_fnv1a32));
update_value_text_content();
// Init menu item index
@ -92,21 +92,13 @@ graphics_menu::graphics_menu(game::context& ctx):
ctx.app->set_fullscreen(fullscreen);
if (!fullscreen)
{
int2 resolution;
resolution.x() = (*ctx.config)["windowed_resolution"][0].get<int>();
resolution.y() = (*ctx.config)["windowed_resolution"][1].get<int>();
ctx.app->resize_window(resolution.x(), resolution.y());
}
this->update_value_text_content();
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
// Save display mode config
(*ctx.config)["fullscreen"] = fullscreen;
// Update fullscreen settings
(*ctx.settings)["fullscreen"_fnv1a32] = fullscreen;
};
auto increase_resolution_callback = [this, &ctx]()
@ -121,6 +113,9 @@ graphics_menu::graphics_menu(game::context& ctx):
if (ctx.render_scale > 2.0f)
ctx.render_scale = 2.0f;
// Update render scale setting
(*ctx.settings)["render_scale"_fnv1a32] = ctx.render_scale;
// Resize framebuffers
game::graphics::change_render_resolution(ctx, ctx.render_scale);
@ -128,9 +123,6 @@ graphics_menu::graphics_menu(game::context& ctx):
this->update_value_text_content();
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
// Update config
(*ctx.config)["render_scale"] = ctx.render_scale;
};
auto decrease_resolution_callback = [this, &ctx]()
@ -145,6 +137,9 @@ graphics_menu::graphics_menu(game::context& ctx):
if (ctx.render_scale < 0.25f)
ctx.render_scale = 0.25f;
// Update render scale setting
(*ctx.settings)["render_scale"_fnv1a32] = ctx.render_scale;
// Resize framebuffers
game::graphics::change_render_resolution(ctx, ctx.render_scale);
@ -152,23 +147,20 @@ graphics_menu::graphics_menu(game::context& ctx):
this->update_value_text_content();
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
// Update config
(*ctx.config)["render_scale"] = ctx.render_scale;
};
auto toggle_v_sync_callback = [this, &ctx]()
{
bool v_sync = !ctx.app->get_v_sync();
// Update v-sync setting
(*ctx.settings)["v_sync"_fnv1a32] = v_sync;
ctx.app->set_v_sync(v_sync);
this->update_value_text_content();
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
// Save v-sync config
(*ctx.config)["v_sync"] = v_sync;
};
auto next_aa_method_callback = [this, &ctx]()
@ -177,15 +169,16 @@ graphics_menu::graphics_menu(game::context& ctx):
{
case render::anti_aliasing_method::none:
ctx.anti_aliasing_method = render::anti_aliasing_method::fxaa;
(*ctx.config)["anti_aliasing_method"] = "fxaa";
break;
case render::anti_aliasing_method::fxaa:
ctx.anti_aliasing_method = render::anti_aliasing_method::none;
(*ctx.config)["anti_aliasing_method"] = "none";
break;
}
// Update anti-aliasing method setting
(*ctx.settings)["anti_aliasing_method"_fnv1a32] = std::to_underlying(ctx.anti_aliasing_method);
game::graphics::select_anti_aliasing_method(ctx, ctx.anti_aliasing_method);
// Update value text
@ -203,71 +196,49 @@ graphics_menu::graphics_menu(game::context& ctx):
{
case render::anti_aliasing_method::none:
ctx.anti_aliasing_method = render::anti_aliasing_method::fxaa;
(*ctx.config)["anti_aliasing_method"] = "fxaa";
break;
case render::anti_aliasing_method::fxaa:
ctx.anti_aliasing_method = render::anti_aliasing_method::none;
(*ctx.config)["anti_aliasing_method"] = "none";
break;
}
game::graphics::select_anti_aliasing_method(ctx, ctx.anti_aliasing_method);
// Update value text
this->update_value_text_content();
// Update anti-aliasing method setting
(*ctx.settings)["anti_aliasing_method"_fnv1a32] = std::to_underlying(ctx.anti_aliasing_method);
// Refresh and realign text
game::menu::refresh_text(ctx);
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
};
auto toggle_bloom_callback = [this, &ctx]()
{
game::graphics::toggle_bloom(ctx, !ctx.bloom_enabled);
game::graphics::select_anti_aliasing_method(ctx, ctx.anti_aliasing_method);
// Update value text
this->update_value_text_content();
// Update config
(*ctx.config)["bloom_enabled"] = ctx.bloom_enabled;
// Refresh and realign text
game::menu::refresh_text(ctx);
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
};
auto increase_font_size_callback = [this, &ctx]()
auto increase_font_scale_callback = [this, &ctx]()
{
// Increase font size
// Increase font scale
if (ctx.menu_modifier_control.is_active())
ctx.font_size += 0.01f;
ctx.font_scale += 0.01f;
else
ctx.font_size += 0.1f;
ctx.font_scale += 0.1f;
// Limit font scale
if (ctx.font_scale > 2.0f)
ctx.font_scale = 2.0f;
// Limit font size
if (ctx.font_size > 2.0f)
ctx.font_size = 2.0f;
// Update font scale setting
(*ctx.settings)["font_scale"_fnv1a32] = ctx.font_scale;
// Update value text
this->update_value_text_content();
// Update config
(*ctx.config)["font_size"] = ctx.font_size;
// Reload fonts
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
debug::log::pop_task(EXIT_FAILURE);
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Reloading fonts...");
game::load_fonts(ctx);
debug::log::trace("Reloaded fonts");
// Refresh and realign text
game::menu::refresh_text(ctx);
@ -275,35 +246,28 @@ graphics_menu::graphics_menu(game::context& ctx):
game::menu::update_text_tweens(ctx);
};
auto decrease_font_size_callback = [this, &ctx]()
auto decrease_font_scale_callback = [this, &ctx]()
{
// Decrease font size
// Decrease font scale
if (ctx.menu_modifier_control.is_active())
ctx.font_size -= 0.01f;
ctx.font_scale -= 0.01f;
else
ctx.font_size -= 0.1f;
ctx.font_scale -= 0.1f;
// Limit font size
if (ctx.font_size < 0.1f)
ctx.font_size = 0.1f;
// Limit font scale
if (ctx.font_scale < 0.1f)
ctx.font_scale = 0.1f;
// Update font scale setting
(*ctx.settings)["font_scale"_fnv1a32] = ctx.font_scale;
// Update value text
this->update_value_text_content();
// Update config
(*ctx.config)["font_size"] = ctx.font_size;
// Reload fonts
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
debug::log::pop_task(EXIT_FAILURE);
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Reloading fonts...");
game::load_fonts(ctx);
debug::log::trace("Reloaded fonts");
// Refresh and realign text
game::menu::refresh_text(ctx);
@ -318,20 +282,13 @@ graphics_menu::graphics_menu(game::context& ctx):
// Update value text
this->update_value_text_content();
// Save dyslexia font config
(*ctx.config)["dyslexia_font"] = ctx.dyslexia_font;
// Save dyslexia font setting
(*ctx.settings)["dyslexia_font"_fnv1a32] = ctx.dyslexia_font;
// Reload fonts
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
debug::log::pop_task(EXIT_FAILURE);
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Reloading fonts...");
game::load_fonts(ctx);
debug::log::trace("Reloaded fonts");
// Refresh and realign text
game::menu::refresh_text(ctx);
@ -366,8 +323,7 @@ graphics_menu::graphics_menu(game::context& ctx):
ctx.menu_select_callbacks.push_back(increase_resolution_callback);
ctx.menu_select_callbacks.push_back(toggle_v_sync_callback);
ctx.menu_select_callbacks.push_back(next_aa_method_callback);
ctx.menu_select_callbacks.push_back(toggle_bloom_callback);
ctx.menu_select_callbacks.push_back(increase_font_size_callback);
ctx.menu_select_callbacks.push_back(increase_font_scale_callback);
ctx.menu_select_callbacks.push_back(toggle_dyslexia_font_callback);
ctx.menu_select_callbacks.push_back(select_back_callback);
@ -376,8 +332,7 @@ graphics_menu::graphics_menu(game::context& ctx):
ctx.menu_left_callbacks.push_back(decrease_resolution_callback);
ctx.menu_left_callbacks.push_back(toggle_v_sync_callback);
ctx.menu_left_callbacks.push_back(previous_aa_method_callback);
ctx.menu_left_callbacks.push_back(toggle_bloom_callback);
ctx.menu_left_callbacks.push_back(decrease_font_size_callback);
ctx.menu_left_callbacks.push_back(decrease_font_scale_callback);
ctx.menu_left_callbacks.push_back(toggle_dyslexia_font_callback);
ctx.menu_left_callbacks.push_back(nullptr);
@ -386,8 +341,7 @@ graphics_menu::graphics_menu(game::context& ctx):
ctx.menu_right_callbacks.push_back(increase_resolution_callback);
ctx.menu_right_callbacks.push_back(toggle_v_sync_callback);
ctx.menu_right_callbacks.push_back(next_aa_method_callback);
ctx.menu_right_callbacks.push_back(toggle_bloom_callback);
ctx.menu_right_callbacks.push_back(increase_font_size_callback);
ctx.menu_right_callbacks.push_back(increase_font_scale_callback);
ctx.menu_right_callbacks.push_back(toggle_dyslexia_font_callback);
ctx.menu_right_callbacks.push_back(nullptr);
@ -400,12 +354,12 @@ graphics_menu::graphics_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered graphics menu state");
}
graphics_menu::~graphics_menu()
{
debug::log::push_task("Exiting graphics menu state");
debug::log::trace("Exiting graphics menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -414,7 +368,7 @@ graphics_menu::~graphics_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited graphics menu state");
}
void graphics_menu::update_value_text_content()
@ -423,12 +377,11 @@ void graphics_menu::update_value_text_content()
const float render_scale = ctx.render_scale;
const bool v_sync = ctx.app->get_v_sync();
const int aa_method_index = static_cast<int>(ctx.anti_aliasing_method);
const bool bloom_enabled = ctx.bloom_enabled;
const float font_size = ctx.font_size;
const float font_scale = ctx.font_scale;
const bool dyslexia_font = ctx.dyslexia_font;
const std::string string_on = (*ctx.strings)["on"];
const std::string string_off = (*ctx.strings)["off"];
const std::string string_on = get_string(ctx, "on"_fnv1a32);
const std::string string_off = get_string(ctx, "off"_fnv1a32);
/*
const std::string string_quality[4] =
@ -442,16 +395,15 @@ void graphics_menu::update_value_text_content()
const std::string string_aa_methods[2] =
{
(*ctx.strings)["graphics_menu_aa_method_none"],
(*ctx.strings)["graphics_menu_aa_method_fxaa"]
get_string(ctx, "graphics_menu_aa_method_none"_fnv1a32),
get_string(ctx, "graphics_menu_aa_method_fxaa"_fnv1a32)
};
std::get<1>(ctx.menu_item_texts[0])->set_content((fullscreen) ? string_on : string_off);
std::get<1>(ctx.menu_item_texts[1])->set_content(std::to_string(static_cast<int>(std::round(render_scale * 100.0f))) + "%");
std::get<1>(ctx.menu_item_texts[2])->set_content((v_sync) ? string_on : string_off);
std::get<1>(ctx.menu_item_texts[3])->set_content(string_aa_methods[aa_method_index]);
std::get<1>(ctx.menu_item_texts[4])->set_content((bloom_enabled) ? string_on : string_off);
std::get<1>(ctx.menu_item_texts[5])->set_content(std::to_string(static_cast<int>(std::round(font_size * 100.0f))) + "%");
std::get<1>(ctx.menu_item_texts[5])->set_content(std::to_string(static_cast<int>(std::round(font_scale * 100.0f))) + "%");
std::get<1>(ctx.menu_item_texts[6])->set_content((dyslexia_font) ? string_on : string_off);
}

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

@ -25,6 +25,10 @@
#include "resources/resource-manager.hpp"
#include "game/menu.hpp"
#include "game/controls.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -32,7 +36,7 @@ namespace state {
keyboard_config_menu::keyboard_config_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering keyboard config menu state");
debug::log::trace("Entering keyboard config menu state...");
// Add camera control menu items
add_control_item("move_forward");
@ -53,7 +57,7 @@ keyboard_config_menu::keyboard_config_menu(game::context& ctx):
ctx.menu_item_texts.push_back({back_text, nullptr});
// Set content of menu item texts
back_text->set_content((*ctx.strings)["back"]);
back_text->set_content(get_string(ctx, "back"_fnv1a32));
// Init menu item index
game::menu::init_menu_item_index(ctx, "keyboard_config");
@ -107,12 +111,12 @@ keyboard_config_menu::keyboard_config_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered keyboard config menu state");
}
keyboard_config_menu::~keyboard_config_menu()
{
debug::log::push_task("Exiting keyboard config menu state");
debug::log::trace("Exiting keyboard config menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -124,7 +128,7 @@ keyboard_config_menu::~keyboard_config_menu()
// Save control profile
game::save_control_profile(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited keyboard config menu state...");
}
std::string keyboard_config_menu::get_binding_string(input::control* control)
@ -234,11 +238,7 @@ void keyboard_config_menu::add_control_item(const std::string& control_name)
ctx.menu_item_texts.push_back({name_text, value_text});
// Set content of name text
std::string string_name = "control_" + control_name;
if (auto it = ctx.strings->find(string_name); it != ctx.strings->end())
name_text->set_content(it->second);
else
name_text->set_content(control_name);
name_text->set_content(control_name);
// Set content of value text
//value_text->set_content(get_binding_string( control));
@ -246,7 +246,7 @@ void keyboard_config_menu::add_control_item(const std::string& control_name)
auto select_callback = [this, &ctx = this->ctx, value_text]()
{
// Clear binding string from value text
value_text->set_content((*ctx.strings)["ellipsis"]);
value_text->set_content(get_string(ctx, "ellipsis"_fnv1a32));
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);

+ 44
- 45
src/game/state/language-menu.cpp View File

@ -24,6 +24,10 @@
#include "debug/log.hpp"
#include "game/fonts.hpp"
#include "game/menu.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -31,7 +35,7 @@ namespace state {
language_menu::language_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering language menu state");
debug::log::trace("Entering language menu state...");
// Construct menu item texts
scene::text* language_name_text = new scene::text();
@ -59,30 +63,24 @@ language_menu::language_menu(game::context& ctx):
auto next_language_callback = [this, &ctx]()
{
// Increment language index
++ctx.language_index;
if (ctx.language_index >= ctx.language_count)
ctx.language_index = 0;
ctx.language_index = (ctx.language_index + 1) % ctx.language_count;
// Find corresponding language code and strings
ctx.language_code = (*ctx.string_table)[0][ctx.language_index + 2];
ctx.strings = &ctx.string_table_map[ctx.language_code];
// Update language index setting
(*ctx.settings)["language_index"_fnv1a32] = ctx.language_index;
// Update language in config
(*ctx.config)["language"] = ctx.language_code;
// 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]);
}
debug::log::info("Language changed to \"" + ctx.language_code + "\"");
// Log language change
debug::log::info("Language index: {}; code: {}", ctx.language_index, get_string(ctx, "language_code"_fnv1a32));
// Reload fonts
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
debug::log::pop_task(EXIT_FAILURE);
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Reloading fonts...");
game::load_fonts(ctx);
debug::log::trace("Reloaded fonts");
game::menu::update_text_font(ctx);
this->update_text_content();
@ -92,31 +90,32 @@ language_menu::language_menu(game::context& ctx):
};
auto previous_language_callback = [this, &ctx]()
{
// Increment language index
--ctx.language_index;
if (ctx.language_index < 0)
// Decrement language index
if (ctx.language_index > 0)
{
--ctx.language_index;
}
else
{
ctx.language_index = ctx.language_count - 1;
}
// Find corresponding language code and strings
ctx.language_code = (*ctx.string_table)[0][ctx.language_index + 2];
ctx.strings = &ctx.string_table_map[ctx.language_code];
// Update language index setting
(*ctx.settings)["language_index"_fnv1a32] = ctx.language_index;
// Update language in config
(*ctx.config)["language"] = ctx.language_code;
// 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]);
}
debug::log::info("Language changed to \"" + ctx.language_code + "\"");
// Log language change
debug::log::info("Language index: {}; code: {}", ctx.language_index, get_string(ctx, "language_code"_fnv1a32));
// Reload fonts
debug::log::push_task("Reloading fonts");
try
{
game::load_fonts(ctx);
}
catch (...)
{
debug::log::pop_task(EXIT_FAILURE);
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Reloading fonts...");
game::load_fonts(ctx);
debug::log::trace("Reloaded fonts");
game::menu::update_text_font(ctx);
this->update_text_content();
@ -168,12 +167,12 @@ language_menu::language_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered language menu state");
}
language_menu::~language_menu()
{
debug::log::push_task("Exiting language menu state");
debug::log::trace("Exiting language menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -182,7 +181,7 @@ language_menu::~language_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited language menu state...");
}
void language_menu::update_text_content()
@ -190,9 +189,9 @@ void language_menu::update_text_content()
auto [language_name, language_value] = ctx.menu_item_texts[0];
auto [back_name, back_value] = ctx.menu_item_texts[1];
language_name->set_content((*ctx.strings)["language_menu_language"]);
language_value->set_content((*ctx.strings)["language_name"]);
back_name->set_content((*ctx.strings)["back"]);
language_name->set_content(get_string(ctx, "language_menu_language"_fnv1a32));
language_value->set_content(get_string(ctx, "language_name"_fnv1a32));
back_name->set_content(get_string(ctx, "back"_fnv1a32));
}
} // namespace state

+ 18
- 17
src/game/state/main-menu.cpp View File

@ -22,7 +22,6 @@
#include "game/state/extras-menu.hpp"
#include "game/state/nuptial-flight.hpp"
#include "game/world.hpp"
#include "game/load.hpp"
#include "game/menu.hpp"
#include "game/ecoregion.hpp"
#include "game/ant/swarm.hpp"
@ -43,6 +42,10 @@
#include "game/component/transform.hpp"
#include "math/projection.hpp"
#include <limits>
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -50,7 +53,7 @@ namespace state {
main_menu::main_menu(game::context& ctx, bool fade_in):
game::state::base(ctx)
{
debug::log::push_task("Entering main menu state");
debug::log::trace("Entering main menu state...");
ctx.ui_clear_pass->set_cleared_buffers(true, true, false);
@ -58,13 +61,13 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
title_text.set_material(&ctx.title_font_material);
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((*ctx.strings)["title_antkeeper"]);
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(-title_w * 0.5f), std::round(-title_h * 0.5f + (ctx.app->get_viewport_dimensions().y() / 3.0f) / 2.0f), 0.0f});
title_text.set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (ctx.app->get_viewport_size().y() / 3.0f) / 2.0f), 0.0f});
title_text.update_tweens();
// Add title text to UI
@ -97,17 +100,17 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
ctx.menu_item_texts.push_back({quit_text, nullptr});
// Set content of menu item texts
start_text->set_content((*ctx.strings)["main_menu_start"]);
options_text->set_content((*ctx.strings)["main_menu_options"]);
extras_text->set_content((*ctx.strings)["main_menu_extras"]);
quit_text->set_content((*ctx.strings)["main_menu_quit"]);
start_text->set_content(get_string(ctx, "main_menu_start"_fnv1a32));
options_text->set_content(get_string(ctx, "main_menu_options"_fnv1a32));
extras_text->set_content(get_string(ctx, "main_menu_extras"_fnv1a32));
quit_text->set_content(get_string(ctx, "main_menu_quit"_fnv1a32));
// Init menu item index
game::menu::init_menu_item_index(ctx, "main");
game::menu::update_text_color(ctx);
game::menu::update_text_font(ctx);
game::menu::align_text(ctx, true, false, (-ctx.app->get_viewport_dimensions().y() / 3.0f) / 2.0f);
game::menu::align_text(ctx, true, false, (-ctx.app->get_viewport_size().y() / 3.0f) / 2.0f);
game::menu::update_text_tweens(ctx);
game::menu::add_text_to_ui(ctx);
game::menu::setup_animations(ctx);
@ -248,7 +251,7 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
{
game::world::cosmogenesis(ctx);
game::world::create_observer(ctx);
game::world::enter_ecoregion(ctx, *ctx.resource_manager->load<game::ecoregion>("seedy-scrub.eco"));
//game::world::enter_ecoregion(ctx, *ctx.resource_manager->load<game::ecoregion>("seedy-scrub.eco"));
}
// Set world time
@ -261,12 +264,10 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f);
ctx.surface_camera->set_exposure(ev100_sunny16);
const auto& viewport_dimensions = ctx.app->get_viewport_dimensions();
const float aspect_ratio = static_cast<float>(viewport_dimensions[0]) / static_cast<float>(viewport_dimensions[1]);
const auto& viewport_size = ctx.app->get_viewport_size();
const float aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
float fov = math::vertical_fov(math::radians(100.0f), aspect_ratio);
if (ctx.config->contains("near_fov"))
fov = math::vertical_fov(math::radians((*ctx.config)["near_fov"].get<float>()), aspect_ratio);
ctx.surface_camera->look_at({0, 2.0f, 0}, {0, 0, 0}, {0, 0, 1});
ctx.surface_camera->set_perspective(fov, ctx.surface_camera->get_aspect_ratio(), ctx.surface_camera->get_clip_near(), ctx.surface_camera->get_clip_far());
@ -282,12 +283,12 @@ main_menu::main_menu(game::context& ctx, bool fade_in):
//if (!ctx.menu_bg_billboard->is_active())
// game::menu::fade_in_bg(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered main menu state");
}
main_menu::~main_menu()
{
debug::log::push_task("Exiting main menu state");
debug::log::trace("Exiting main menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -305,7 +306,7 @@ main_menu::~main_menu()
// Destruct title text
ctx.ui_scene->remove_object(&title_text);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited main menu state");
}
void main_menu::fade_in_title()

+ 13
- 28
src/game/state/nest-selection.cpp View File

@ -45,7 +45,6 @@
#include "render/passes/ground-pass.hpp"
#include "state-machine.hpp"
#include "config.hpp"
#include "game/load.hpp"
#include "math/interpolation.hpp"
#include "physics/light/exposure.hpp"
#include "application.hpp"
@ -66,20 +65,20 @@ namespace state {
nest_selection::nest_selection(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering nest selection state");
debug::log::trace("Entering nest selection state...");
debug::log::push_task("Generating genome");
debug::log::trace("Generating genome...");
std::random_device rng;
ant::genome* genome = ant::cladogenesis(ctx.active_ecoregion->gene_pools[0], rng);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated genome");
debug::log::push_task("Building worker phenome");
debug::log::trace("Building worker phenome...");
ant::phenome worker_phenome = ant::phenome(*genome, ant::caste::worker);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Built worker phenome...");
debug::log::push_task("Generating worker model");
debug::log::trace("Generating worker model...");
render::model* worker_model = ant::morphogenesis(worker_phenome);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated worker model");
// Create worker entity(s)
entity::id worker_eid = ctx.entity_registry->create();
@ -110,11 +109,7 @@ nest_selection::nest_selection(game::context& ctx):
}
// Init time scale
double time_scale = 1.0;
// Read time scale settings
if (ctx.config->contains("time_scale"))
time_scale = (*ctx.config)["time_scale"].get<double>();
double time_scale = 60.0;
// Set time scale
game::world::set_time_scale(ctx, time_scale);
@ -131,8 +126,8 @@ nest_selection::nest_selection(game::context& ctx):
const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f);
ctx.surface_camera->set_exposure(ev100_sunny16);
const auto& viewport_dimensions = ctx.app->get_viewport_dimensions();
const float aspect_ratio = static_cast<float>(viewport_dimensions[0]) / static_cast<float>(viewport_dimensions[1]);
const auto& viewport_size = ctx.app->get_viewport_size();
const float aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
// Init first person camera rig parameters
first_person_camera_rig_translation_spring_angular_frequency = period_to_rads(0.125f);
@ -147,16 +142,6 @@ nest_selection::nest_selection(game::context& ctx):
first_person_camera_rig_pedestal_speed = 2.0f;
first_person_camera_rig_pedestal = 0.0f;
// Read first person camera rig settings
if (ctx.config->contains("standing_eye_height"))
first_person_camera_rig_max_elevation = (*ctx.config)["standing_eye_height"].get<float>();
if (ctx.config->contains("walking_speed"))
first_person_camera_far_speed = (*ctx.config)["walking_speed"].get<float>();
if (ctx.config->contains("near_fov"))
first_person_camera_near_fov = math::vertical_fov(math::radians((*ctx.config)["near_fov"].get<float>()), aspect_ratio);
if (ctx.config->contains("far_fov"))
first_person_camera_far_fov = math::vertical_fov(math::radians((*ctx.config)["far_fov"].get<float>()), aspect_ratio);
// Create first person camera rig
create_first_person_camera_rig();
@ -198,16 +183,16 @@ nest_selection::nest_selection(game::context& ctx):
// Queue control setup
ctx.function_queue.push(std::bind(&nest_selection::enable_controls, this));
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered nest selection state");
}
nest_selection::~nest_selection()
{
debug::log::push_task("Exiting nest selection state");
debug::log::trace("Exiting nest selection state...");
destroy_first_person_camera_rig();
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited nest selection state");
}
void nest_selection::create_first_person_camera_rig()

+ 10
- 17
src/game/state/nuptial-flight.cpp View File

@ -48,7 +48,6 @@
#include "render/passes/ground-pass.hpp"
#include "state-machine.hpp"
#include "config.hpp"
#include "game/load.hpp"
#include "math/interpolation.hpp"
#include "physics/light/exposure.hpp"
#include "color/color.hpp"
@ -61,7 +60,7 @@ namespace state {
nuptial_flight::nuptial_flight(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering nuptial flight state");
debug::log::trace("Entering nuptial flight state...");
// Init selected picking flag
selected_picking_flag = std::uint32_t{1} << (sizeof(std::uint32_t) * 8 - 1);
@ -101,8 +100,8 @@ nuptial_flight::nuptial_flight(game::context& ctx):
const float ev100_sunny16 = physics::light::ev::from_settings(16.0f, 1.0f / 100.0f, 100.0f);
ctx.surface_camera->set_exposure(ev100_sunny16);
const auto& viewport_dimensions = ctx.app->get_viewport_dimensions();
const float aspect_ratio = static_cast<float>(viewport_dimensions[0]) / static_cast<float>(viewport_dimensions[1]);
const auto& viewport_size = ctx.app->get_viewport_size();
const float aspect_ratio = static_cast<float>(viewport_size[0]) / static_cast<float>(viewport_size[1]);
// Init camera rig params
camera_rig_near_distance = 1.0f;
@ -115,12 +114,6 @@ nuptial_flight::nuptial_flight(game::context& ctx):
camera_rig_fov_spring_angular_frequency = period_to_rads(0.125f);
camera_rig_focus_ease_to_duration = 1.0f;
// Read camera rig settingss
if (ctx.config->contains("near_fov"))
camera_rig_near_fov = math::vertical_fov(math::radians((*ctx.config)["near_fov"].get<float>()), aspect_ratio);
if (ctx.config->contains("far_fov"))
camera_rig_far_fov = math::vertical_fov(math::radians((*ctx.config)["far_fov"].get<float>()), aspect_ratio);
// Create camera rig
create_camera_rig();
@ -143,12 +136,12 @@ nuptial_flight::nuptial_flight(game::context& ctx):
// Queue control setup
ctx.function_queue.push(std::bind(&nuptial_flight::enable_controls, this));
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered nuptial flight state");
}
nuptial_flight::~nuptial_flight()
{
debug::log::push_task("Exiting nuptial flight state");
debug::log::trace("Exiting nuptial flight state...");
// Deselect selected entity
select_entity(entt::null);
@ -156,7 +149,7 @@ nuptial_flight::~nuptial_flight()
destroy_camera_rig();
game::ant::destroy_swarm(ctx, swarm_eid);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited nuptial flight state");
}
void nuptial_flight::create_camera_rig()
@ -619,14 +612,14 @@ void nuptial_flight::enable_controls()
// Get window-space mouse coordinates
auto [mouse_x, mouse_y] = ctx.app->get_mouse()->get_current_position();
// Get window viewport dimensions
const auto viewport_dimensions = ctx.app->get_viewport_dimensions();
// Get window viewport size
const auto viewport_size = ctx.app->viewport_size();
// Transform mouse coordinates from window space to NDC space
const float2 mouse_ndc =
{
static_cast<float>(mouse_x) / static_cast<float>(viewport_dimensions[0] - 1) * 2.0f - 1.0f,
(1.0f - static_cast<float>(mouse_y) / static_cast<float>(viewport_dimensions[1] - 1)) * 2.0f - 1.0f
static_cast<float>(mouse_x) / static_cast<float>(viewport_size[0] - 1) * 2.0f - 1.0f,
(1.0f - static_cast<float>(mouse_y) / static_cast<float>(viewport_size[1] - 1)) * 2.0f - 1.0f
};
// Get picking ray from camera

+ 14
- 11
src/game/state/options-menu.cpp View File

@ -24,7 +24,6 @@
#include "game/state/sound-menu.hpp"
#include "game/state/language-menu.hpp"
#include "game/state/pause-menu.hpp"
#include "game/save.hpp"
#include "game/menu.hpp"
#include "animation/ease.hpp"
#include "animation/animation.hpp"
@ -32,6 +31,10 @@
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -39,7 +42,7 @@ namespace state {
options_menu::options_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering options menu state");
debug::log::trace("Entering options menu state...");
// Construct menu item texts
scene::text* controls_text = new scene::text();
@ -49,11 +52,11 @@ options_menu::options_menu(game::context& ctx):
scene::text* back_text = new scene::text();
// Set content of menu item texts
controls_text->set_content((*ctx.strings)["options_menu_controls"]);
graphics_text->set_content((*ctx.strings)["options_menu_graphics"]);
sound_text->set_content((*ctx.strings)["options_menu_sound"]);
language_text->set_content((*ctx.strings)["options_menu_language"]);
back_text->set_content((*ctx.strings)["back"]);
controls_text->set_content(get_string(ctx, "options_menu_controls"_fnv1a32));
graphics_text->set_content(get_string(ctx, "options_menu_graphics"_fnv1a32));
sound_text->set_content(get_string(ctx, "options_menu_sound"_fnv1a32));
language_text->set_content(get_string(ctx, "options_menu_language"_fnv1a32));
back_text->set_content(get_string(ctx, "back"_fnv1a32));
// Build list of menu item texts
ctx.menu_item_texts.push_back({controls_text, nullptr});
@ -171,7 +174,7 @@ options_menu::options_menu(game::context& ctx):
game::menu::clear_controls(ctx);
// Save config
game::save::config(ctx);
//game::save::config(ctx);
game::menu::fade_out
(
@ -216,12 +219,12 @@ options_menu::options_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered options menu state");
}
options_menu::~options_menu()
{
debug::log::push_task("Exiting options menu state");
debug::log::trace("Exiting options menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -230,7 +233,7 @@ options_menu::~options_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited options menu state");
}
} // namespace state

+ 13
- 10
src/game/state/pause-menu.cpp View File

@ -30,7 +30,10 @@
#include "debug/log.hpp"
#include "animation/screen-transition.hpp"
#include "config.hpp"
#include "game/save.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -38,7 +41,7 @@ namespace state {
pause_menu::pause_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering pause menu state");
debug::log::trace("Entering pause menu state...");
// Construct menu item texts
scene::text* resume_text = new scene::text();
@ -47,10 +50,10 @@ pause_menu::pause_menu(game::context& ctx):
scene::text* quit_text = new scene::text();
// Set content of menu item texts
resume_text->set_content((*ctx.strings)["pause_menu_resume"]);
options_text->set_content((*ctx.strings)["pause_menu_options"]);
main_menu_text->set_content((*ctx.strings)["pause_menu_main_menu"]);
quit_text->set_content((*ctx.strings)["pause_menu_quit"]);
resume_text->set_content(get_string(ctx, "pause_menu_resume"_fnv1a32));
options_text->set_content(get_string(ctx, "pause_menu_options"_fnv1a32));
main_menu_text->set_content(get_string(ctx, "pause_menu_main_menu"_fnv1a32));
quit_text->set_content(get_string(ctx, "pause_menu_quit"_fnv1a32));
// Build list of menu item texts
ctx.menu_item_texts.push_back({resume_text, nullptr});
@ -205,14 +208,14 @@ pause_menu::pause_menu(game::context& ctx):
game::menu::fade_in_bg(ctx);
// Save colony
game::save::colony(ctx);
//game::save::colony(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered pause menu state");
}
pause_menu::~pause_menu()
{
debug::log::push_task("Exiting pause menu state");
debug::log::trace("Exiting pause menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -221,7 +224,7 @@ pause_menu::~pause_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited pause menu state");
}
} // namespace state

+ 17
- 21
src/game/state/sound-menu.cpp View File

@ -23,6 +23,10 @@
#include "scene/text.hpp"
#include "debug/log.hpp"
#include "game/menu.hpp"
#include "game/strings.hpp"
#include "utility/hash/fnv1a.hpp"
using namespace hash::literals;
namespace game {
namespace state {
@ -30,7 +34,7 @@ namespace state {
sound_menu::sound_menu(game::context& ctx):
game::state::base(ctx)
{
debug::log::push_task("Entering sound menu state");
debug::log::trace("Entering sound menu state...");
// Construct menu item texts
scene::text* master_volume_name_text = new scene::text();
@ -57,13 +61,13 @@ sound_menu::sound_menu(game::context& ctx):
ctx.menu_item_texts.push_back({back_text, nullptr});
// Set content of menu item texts
master_volume_name_text->set_content((*ctx.strings)["sound_menu_master_volume"]);
ambience_volume_name_text->set_content((*ctx.strings)["sound_menu_ambience_volume"]);
effects_volume_name_text->set_content((*ctx.strings)["sound_menu_effects_volume"]);
mono_audio_name_text->set_content((*ctx.strings)["sound_menu_mono_audio"]);
captions_name_text->set_content((*ctx.strings)["sound_menu_captions"]);
captions_size_name_text->set_content((*ctx.strings)["sound_menu_captions_size"]);
back_text->set_content((*ctx.strings)["back"]);
master_volume_name_text->set_content(get_string(ctx, "sound_menu_master_volume"_fnv1a32));
ambience_volume_name_text->set_content(get_string(ctx, "sound_menu_ambience_volume"_fnv1a32));
effects_volume_name_text->set_content(get_string(ctx, "sound_menu_effects_volume"_fnv1a32));
mono_audio_name_text->set_content(get_string(ctx, "sound_menu_mono_audio"_fnv1a32));
captions_name_text->set_content(get_string(ctx, "sound_menu_captions"_fnv1a32));
captions_size_name_text->set_content(get_string(ctx, "sound_menu_captions_size"_fnv1a32));
back_text->set_content(get_string(ctx, "back"_fnv1a32));
update_value_text_content();
// Init menu item index
@ -220,12 +224,12 @@ sound_menu::sound_menu(game::context& ctx):
// Fade in menu
game::menu::fade_in(ctx, nullptr);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered sound menu state");
}
sound_menu::~sound_menu()
{
debug::log::push_task("Exiting sound menu state");
debug::log::trace("Exiting sound menu state...");
// Destruct menu
game::menu::clear_controls(ctx);
@ -234,21 +238,13 @@ sound_menu::~sound_menu()
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
// Update config
(*ctx.config)["master_volume"] = ctx.master_volume;
(*ctx.config)["ambience_volume"] = ctx.ambience_volume;
(*ctx.config)["effects_volume"] = ctx.effects_volume;
(*ctx.config)["mono_audio"] = ctx.mono_audio;
(*ctx.config)["captions"] = ctx.captions;
(*ctx.config)["captions_size"] = ctx.captions_size;
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited sound menu state");
}
void sound_menu::update_value_text_content()
{
const std::string string_on = (*ctx.strings)["on"];
const std::string string_off = (*ctx.strings)["off"];
const std::string string_on = get_string(ctx, "on"_fnv1a32);
const std::string string_off = get_string(ctx, "off"_fnv1a32);
std::get<1>(ctx.menu_item_texts[0])->set_content(std::to_string(static_cast<int>(std::round(ctx.master_volume * 100.0f))) + "%");
std::get<1>(ctx.menu_item_texts[1])->set_content(std::to_string(static_cast<int>(std::round(ctx.ambience_volume * 100.0f))) + "%");

+ 7
- 13
src/game/state/splash.cpp View File

@ -38,7 +38,7 @@ splash::splash(game::context& ctx):
game::state::base(ctx),
skipped(false)
{
debug::log::push_task("Entering splash state");
debug::log::trace("Entering splash state...");
// Enable color buffer clearing in UI pass
ctx.ui_clear_pass->set_cleared_buffers(true, true, false);
@ -67,15 +67,9 @@ splash::splash(game::context& ctx):
ctx.ui_scene->add_object(&splash_billboard);
// Load animation timing configuration
double splash_fade_in_duration = 0.0;
double splash_duration = 0.0;
double splash_fade_out_duration = 0.0;
if (ctx.config->contains("splash_fade_in_duration"))
splash_fade_in_duration = (*ctx.config)["splash_fade_in_duration"].get<double>();
if (ctx.config->contains("splash_duration"))
splash_duration = (*ctx.config)["splash_duration"].get<double>();
if (ctx.config->contains("splash_fade_out_duration"))
splash_fade_out_duration = (*ctx.config)["splash_fade_out_duration"].get<double>();
const double splash_fade_in_duration = 0.5;
const double splash_duration = 2.0;
const double splash_fade_out_duration = 0.5;
// Construct splash fade in animation
splash_fade_in_animation.set_interpolator(ease<float>::out_cubic);
@ -164,12 +158,12 @@ splash::splash(game::context& ctx):
);
ctx.input_mapper.connect(ctx.app->get_device_manager().get_event_queue());
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered splash state");
}
splash::~splash()
{
debug::log::push_task("Exiting splash state");
debug::log::trace("Exiting splash state...");
// Disable splash skipper
ctx.input_mapper.disconnect();
@ -187,7 +181,7 @@ splash::~splash()
// Disable color buffer clearing in UI pass
ctx.ui_clear_pass->set_cleared_buffers(false, true, false);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Exited splash state");
}
} // namespace state

src/game/save.hpp → src/game/strings.cpp View File

@ -17,25 +17,21 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_GAME_SAVE_HPP
#define ANTKEEPER_GAME_SAVE_HPP
#include "game/context.hpp"
#include "game/strings.hpp"
#include <format>
namespace game {
namespace save {
/**
* Saves the current ant colony.
*/
void colony(game::context& ctx);
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())
{
return i->second;
}
return std::format("${:x}", key);
}
/**
* Saves the current configuration.
*/
void config(game::context& ctx);
} // namespace save
} // namespace game
#endif // ANTKEEPER_GAME_SAVE_HPP

+ 42
- 0
src/game/strings.hpp View File

@ -0,0 +1,42 @@
/*
* 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 ANTKEEPER_GAME_STRINGS_HPP
#define ANTKEEPER_GAME_STRINGS_HPP
#include "game/context.hpp"
#include <cstdint>
#include <string>
namespace game {
/**
* Returns a localized string.
*
* @param[in] ctx Game context.
* @param[in] key String key.
* @param[out] string String value.
*
* @return `true` if the string was found, `false` otherwise.
*/
[[nodiscard]] std::string get_string(const game::context& ctx, std::uint32_t key);
} // namespace game
#endif // ANTKEEPER_GAME_STRINGS_HPP

+ 4
- 4
src/game/system/subterrain.cpp View File

@ -195,22 +195,22 @@ subterrain::subterrain(entity::registry& registry, ::resource_manager* resource_
{
// Load subterrain materials
subterrain_inside_material = resource_manager->load<::render::material>("subterrain-inside.mtl");
subterrain_inside_material = resource_manager->load<::render::material>("subterrain-outside.mtl");
subterrain_inside_material = nullptr;//resource_manager->load<::render::material>("subterrain-inside.mtl");
subterrain_outside_material = nullptr;//resource_manager->load<::render::material>("subterrain-outside.mtl");
// Allocate subterrain model
subterrain_model = new ::render::model();
// Create inside model group
subterrain_inside_group = subterrain_model->add_group("inside");
subterrain_inside_group->set_material(resource_manager->load<::render::material>("subterrain-inside.mtl"));
subterrain_inside_group->set_material(subterrain_inside_material);
subterrain_inside_group->set_drawing_mode(gl::drawing_mode::triangles);
subterrain_inside_group->set_start_index(0);
subterrain_inside_group->set_index_count(0);
// Create outside model group
subterrain_outside_group = subterrain_model->add_group("outside");
subterrain_outside_group->set_material(resource_manager->load<::render::material>("subterrain-outside.mtl"));
subterrain_outside_group->set_material(subterrain_outside_material);
subterrain_outside_group->set_drawing_mode(gl::drawing_mode::triangles);
subterrain_outside_group->set_start_index(0);
subterrain_outside_group->set_index_count(0);

+ 51
- 128
src/game/world.cpp View File

@ -65,6 +65,7 @@
#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>
@ -93,21 +94,20 @@ static void create_moon(game::context& ctx);
void cosmogenesis(game::context& ctx)
{
debug::log::push_task("Generating cosmos");
debug::log::trace("Generating cosmos...");
load_ephemeris(ctx);
create_stars(ctx);
create_sun(ctx);
create_earth_moon_system(ctx);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated cosmos");
}
void create_observer(game::context& ctx)
{
debug::log::push_task("Creating observer");
debug::log::trace("Creating observer...");
try
{
// Create observer entity
entity::id observer_eid = ctx.entity_registry->create();
@ -136,13 +136,8 @@ void create_observer(game::context& ctx)
// Set astronomy system observer
ctx.astronomy_system->set_observer(observer_eid);
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Created observer");
}
void set_location(game::context& ctx, double elevation, double latitude, double longitude)
@ -170,18 +165,17 @@ void set_location(game::context& ctx, double elevation, double latitude, double
void set_time(game::context& ctx, double t)
{
debug::log::push_task("Setting time to UT1 " + std::to_string(t));
try
{
ctx.astronomy_system->set_time(t);
ctx.orbit_system->set_time(t);
debug::log::info("Set time to UT1 {}", t);
}
catch (const std::exception&)
catch (const std::exception& e)
{
debug::log::pop_task(EXIT_FAILURE);
return;
debug::log::error("Failed to set time to UT1 {}: {}", t, e.what());
}
debug::log::pop_task(EXIT_SUCCESS);
}
void set_time(game::context& ctx, int year, int month, int day, int hour, int minute, double second)
@ -220,60 +214,16 @@ void set_time_scale(game::context& ctx, double scale)
void load_ephemeris(game::context& ctx)
{
debug::log::push_task("Loading ephemeris");
try
{
std::string ephemeris_filename;
if (ctx.config->contains("ephemeris"))
{
ephemeris_filename = (*ctx.config)["ephemeris"].get<std::string>();
}
else
{
debug::log::warning("No ephemeris set in config");
debug::log::pop_task(EXIT_FAILURE);
return;
}
ctx.orbit_system->set_ephemeris(ctx.resource_manager->load<physics::orbit::ephemeris<double>>(ephemeris_filename));
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
debug::log::pop_task(EXIT_SUCCESS);
ctx.orbit_system->set_ephemeris(ctx.resource_manager->load<physics::orbit::ephemeris<double>>("de421.eph"));
}
void create_stars(game::context& ctx)
{
debug::log::push_task("Generating fixed stars");
debug::log::trace("Generating fixed stars...");
// Load star catalog
string_table* star_catalog = nullptr;
try
{
std::string star_catalog_filename;
if (ctx.config->contains("star_catalog"))
{
star_catalog_filename = (*ctx.config)["star_catalog"].get<std::string>();
}
else
{
debug::log::warning("No star catalog set in config");
debug::log::pop_task(EXIT_FAILURE);
return;
}
star_catalog = ctx.resource_manager->load<string_table>(star_catalog_filename);
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
i18n::string_table* star_catalog = nullptr;
star_catalog = ctx.resource_manager->load<i18n::string_table>("hipparcos-7.tsv");
// Allocate star catalog vertex data
std::size_t star_count = 0;
@ -290,23 +240,24 @@ void create_stars(game::context& ctx)
// Build star catalog vertex data
for (std::size_t i = 1; i < star_catalog->size(); ++i)
{
const string_table_row& catalog_row = (*star_catalog)[i];
const i18n::string_table_row& catalog_row = (*star_catalog)[i];
// Parse star catalog item
double ra = 0.0;
double dec = 0.0;
double vmag = 0.0;
double bv = 0.0;
float ra = 0.0;
float dec = 0.0;
float vmag = 0.0;
float bv = 0.0;
try
{
ra = std::stod(catalog_row[1]);
dec = std::stod(catalog_row[2]);
vmag = std::stod(catalog_row[3]);
bv = std::stod(catalog_row[4]);
ra = std::stof(catalog_row[1]);
dec = std::stof(catalog_row[2]);
vmag = std::stof(catalog_row[3]);
bv = std::stof(catalog_row[4]);
}
catch (const std::exception&)
{
debug::log::warning("Invalid star catalog item on row " + std::to_string(i));
debug::log::warning("Invalid star catalog item on row {}", i);
continue;
}
// Convert right ascension and declination from degrees to radians
@ -314,38 +265,38 @@ void create_stars(game::context& ctx)
dec = math::wrap_radians(math::radians(dec));
// Convert ICRF coordinates from spherical to Cartesian
double3 position = physics::orbit::frame::bci::cartesian(double3{1.0, dec, ra});
float3 position = physics::orbit::frame::bci::cartesian(float3{1.0f, dec, ra});
// Convert color index to color temperature
double cct = color::index::bv_to_cct(bv);
float cct = color::index::bv_to_cct(bv);
// Calculate XYZ color from color temperature
double3 color_xyz = color::cct::to_xyz(cct);
float3 color_xyz = color::cct::to_xyz(cct);
// Transform XYZ color to ACEScg colorspace
double3 color_acescg = color::aces::ap1<double>.from_xyz * color_xyz;
float3 color_acescg = color::aces::ap1<float>.from_xyz * color_xyz;
// Convert apparent magnitude to brightness factor relative to a 0th magnitude star
double brightness = physics::light::vmag::to_brightness(vmag);
float brightness = physics::light::vmag::to_brightness(vmag);
// Build vertex
*(star_vertex++) = static_cast<float>(position.x());
*(star_vertex++) = static_cast<float>(position.y());
*(star_vertex++) = static_cast<float>(position.z());
*(star_vertex++) = static_cast<float>(color_acescg.x());
*(star_vertex++) = static_cast<float>(color_acescg.y());
*(star_vertex++) = static_cast<float>(color_acescg.z());
*(star_vertex++) = static_cast<float>(brightness);
*(star_vertex++) = position.x();
*(star_vertex++) = position.y();
*(star_vertex++) = position.z();
*(star_vertex++) = color_acescg.x();
*(star_vertex++) = color_acescg.y();
*(star_vertex++) = color_acescg.z();
*(star_vertex++) = brightness;
// Calculate spectral illuminance
double3 illuminance = color_acescg * physics::light::vmag::to_illuminance(vmag);
double3 illuminance = double3(color_acescg * physics::light::vmag::to_illuminance(vmag));
// Add spectral illuminance to total starlight illuminance
starlight_illuminance += illuminance;
}
// Unload star catalog
ctx.resource_manager->unload("stars.csv");
ctx.resource_manager->unload("hipparcos-7.tsv");
// Allocate stars model
render::model* stars_model = new render::model();
@ -400,14 +351,13 @@ void create_stars(game::context& ctx)
// Pass starlight illuminance to astronomy system
ctx.astronomy_system->set_starlight_illuminance(starlight_illuminance);
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated fixed stars");
}
void create_sun(game::context& ctx)
{
debug::log::push_task("Generating Sun");
debug::log::trace("Generating Sun...");
try
{
// Create sun entity
entity::archetype* sun_archetype = ctx.resource_manager->load<entity::archetype>("sun.ent");
@ -446,20 +396,14 @@ void create_sun(game::context& ctx)
ctx.astronomy_system->set_sky_light(sky_light);
ctx.astronomy_system->set_bounce_light(bounce_light);
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated Sun");
}
void create_earth_moon_system(game::context& ctx)
{
debug::log::push_task("Generating Earth-Moon system");
debug::log::trace("Generating Earth-Moon system...");
try
{
// Create Earth-Moon barycenter entity
entity::archetype* em_bary_archetype = ctx.resource_manager->load<entity::archetype>("em-bary.ent");
@ -472,20 +416,14 @@ void create_earth_moon_system(game::context& ctx)
// Create Moon
create_moon(ctx);
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated Earth-Moon system");
}
void create_earth(game::context& ctx)
{
debug::log::push_task("Generating Earth");
debug::log::trace("Generating Earth...");
try
{
// Create earth entity
entity::archetype* earth_archetype = ctx.resource_manager->load<entity::archetype>("earth.ent");
@ -495,20 +433,14 @@ void create_earth(game::context& ctx)
// Assign orbital parent
ctx.entity_registry->get<game::component::orbit>(earth_eid).parent = ctx.entities["em_bary"];
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated Earth");
}
void create_moon(game::context& ctx)
{
debug::log::push_task("Generating Moon");
debug::log::trace("Generating Moon...");
try
{
// Create lunar entity
entity::archetype* moon_archetype = ctx.resource_manager->load<entity::archetype>("moon.ent");
@ -532,13 +464,8 @@ void create_moon(game::context& ctx)
// Pass moon light scene object to astronomy system
ctx.astronomy_system->set_moon_light(moon_light);
}
catch (const std::exception&)
{
debug::log::pop_task(EXIT_FAILURE);
return;
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Generated Moon");
}
void enter_ecoregion(game::context& ctx, const ecoregion& ecoregion)
@ -595,12 +522,11 @@ void enter_ecoregion(game::context& ctx, const ecoregion& ecoregion)
);
stbi_flip_vertically_on_write(1);
stbi_write_tga((ctx.config_path / "gallery" / "voronoi-f1-400-nc8-2k.tga").string().c_str(), img.get_width(), img.get_height(), img.get_channel_count(), img.data());
stbi_write_tga((ctx.screenshots_path / "voronoi-f1-400-nc8-2k.tga").string().c_str(), img.get_width(), img.get_height(), img.get_channel_count(), img.data());
*/
debug::log::push_task("Entering ecoregion " + ecoregion.name);
try
debug::log::trace("Entering ecoregion {}...", ecoregion.name);
{
// Set active ecoregion
ctx.active_ecoregion = &ecoregion;
@ -641,11 +567,8 @@ void enter_ecoregion(game::context& ctx, const ecoregion& ecoregion)
);
ctx.astronomy_system->set_bounce_albedo(double3(ecoregion.terrain_albedo));
}
catch (...)
{
debug::log::pop_task(EXIT_FAILURE);
}
debug::log::pop_task(EXIT_SUCCESS);
debug::log::trace("Entered ecoregion {}", ecoregion.name);
}
} // namespace world

+ 21
- 21
src/gl/gl.hpp View File

@ -20,28 +20,28 @@
#ifndef ANTKEEPER_GL_HPP
#define ANTKEEPER_GL_HPP
/// Graphics library (GL) is a cross-platform GPU interface.
/// Graphics library interface.
namespace gl {}
#include "buffer-usage.hpp"
#include "color-space.hpp"
#include "drawing-mode.hpp"
#include "element-array-type.hpp"
#include "framebuffer.hpp"
#include "pixel-format.hpp"
#include "pixel-type.hpp"
#include "rasterizer.hpp"
#include "shader-input.hpp"
#include "shader-object.hpp"
#include "shader-program.hpp"
#include "shader-stage.hpp"
#include "shader-variable-type.hpp"
#include "texture-2d.hpp"
#include "texture-cube.hpp"
#include "texture-filter.hpp"
#include "texture-wrapping.hpp"
#include "vertex-array.hpp"
#include "vertex-attribute.hpp"
#include "vertex-buffer.hpp"
#include "gl/buffer-usage.hpp"
#include "gl/color-space.hpp"
#include "gl/drawing-mode.hpp"
#include "gl/element-array-type.hpp"
#include "gl/framebuffer.hpp"
#include "gl/pixel-format.hpp"
#include "gl/pixel-type.hpp"
#include "gl/rasterizer.hpp"
#include "gl/shader-input.hpp"
#include "gl/shader-object.hpp"
#include "gl/shader-program.hpp"
#include "gl/shader-stage.hpp"
#include "gl/shader-variable-type.hpp"
#include "gl/texture-2d.hpp"
#include "gl/texture-cube.hpp"
#include "gl/texture-filter.hpp"
#include "gl/texture-wrapping.hpp"
#include "gl/vertex-array.hpp"
#include "gl/vertex-attribute.hpp"
#include "gl/vertex-buffer.hpp"
#endif // ANTKEEPER_GL_HPP

+ 26
- 0
src/i18n/i18n.hpp View File

@ -0,0 +1,26 @@
/*
* 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 ANTKEEPER_I18N_HPP
#define ANTKEEPER_I18N_HPP
/// Internationalization and localization.
namespace i18n {}
#endif // ANTKEEPER_I18N_HPP

+ 51
- 0
src/i18n/string-map.cpp View File

@ -0,0 +1,51 @@
/*
* 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 "i18n/string-map.hpp"
#include "utility/hash/fnv1a.hpp"
#include <algorithm>
namespace i18n {
void build_string_map(const string_table& table, std::size_t key_column, std::size_t value_column, string_map& map)
{
map.clear();
const std::size_t max_column = std::max<std::size_t>(key_column, value_column);
for (const auto& row: table)
{
if (row.size() <= max_column)
{
continue;
}
const auto& value_string = row[value_column];
if (value_string.empty())
{
continue;
}
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];
}
}
} // namespace i18n

src/resources/string-table.hpp → src/i18n/string-map.hpp View File

@ -17,38 +17,30 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_STRING_TABLE_HPP
#define ANTKEEPER_STRING_TABLE_HPP
#ifndef ANTKEEPER_I18N_STRING_MAP_HPP
#define ANTKEEPER_I18N_STRING_MAP_HPP
#include "i18n/string-table.hpp"
#include <string>
#include <unordered_map>
#include <vector>
/**
* A single row in a string table.
*/
typedef std::vector<std::string> string_table_row;
namespace i18n {
/**
* A table of strings.
* Maps 32-bit keys to strings.
*/
typedef std::vector<string_table_row> string_table;
/**
* An index for finding elements in a string table.
*/
typedef std::unordered_map<std::string, std::size_t> string_table_index;
typedef std::unordered_map<std::string, std::unordered_map<std::string, std::string>> string_table_map;
void build_string_table_map(string_table_map* map, const string_table& table);
typedef std::unordered_map<std::uint32_t, std::string> string_map;
/**
* Creates an index for a string table using strings in the first column as keys.
* Builds a string map from a string table. Keys are generated with the 32-bit FNV-1a hash function.
*
* @param table Table for which an index will be created.
* @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.
*/
string_table_index index_string_table(const string_table& table);
void build_string_map(const string_table& table, std::size_t key_column, std::size_t value_column, string_map& map);
#endif // ANTKEEPER_STRING_TABLE_HPP
} // namespace i18n
#endif // ANTKEEPER_I18N_STRING_MAP_HPP

src/utility/type-id.hpp → src/i18n/string-table.hpp View File

@ -17,26 +17,24 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_UTILITY_TYPE_ID_HPP
#define ANTKEEPER_UTILITY_TYPE_ID_HPP
#ifndef ANTKEEPER_I18N_STRING_TABLE_HPP
#define ANTKEEPER_I18N_STRING_TABLE_HPP
#include <string>
#include <vector>
namespace i18n {
/**
* Provides unique type IDs at compile-time via the templated addresses of this function.
*
* @tparam T Type.
* A single row in a string table.
*/
template <class T>
void type_id_generator() noexcept {}
/// Type ID type.
using type_id_t = void(*)(void);
typedef std::vector<std::string> string_table_row;
/**
* Compile-time constant type ID.
*
* @tparam T Type.
* A table of strings.
*/
template <class T>
constexpr type_id_t type_id = &type_id_generator<T>;
typedef std::vector<string_table_row> string_table;
} // namespace i18n
#endif // ANTKEEPER_UTILITY_TYPE_ID_HPP
#endif // ANTKEEPER_I18N_STRING_TABLE_HPP

+ 56
- 10
src/input/event.hpp View File

@ -237,15 +237,22 @@ struct mouse_scrolled
};
/**
* Event generated when a window has been closed.
* Event generated when a window has been requested to close.
*/
struct window_closed {};
struct window_closed
{
/// Pointer to the window that has been requested to close.
void* window;
};
/**
* Event generated when a window has gained or lost focus.
*/
struct window_focus_changed
{
/// Pointer to the window that has gained or lost focus.
void* window;
/// `true` if the window is in focus, `false` otherwise.
bool in_focus;
};
@ -255,8 +262,44 @@ struct window_focus_changed
*/
struct window_moved
{
/// Pointer to the window that has been moved.
void* window;
/// Position of the window, in pixels.
math::vector<std::int32_t, 2> position;
/// `true` if the window is maximized, `false` otherwise.
bool maximized;
/// `true` if the window is fullscreen, `false` otherwise.
bool fullscreen;
};
/**
* Event generated when a window has been maximized.
*/
struct window_maximized
{
/// Pointer to the window that has been maximized.
void* window;
};
/**
* Event generated when a window has been minimized.
*/
struct window_minimized
{
/// Pointer to the window that has been minimized.
void* window;
};
/**
* Event generated when a window has been restored.
*/
struct window_restored
{
/// Pointer to the window that has been restored.
void* window;
};
/**
@ -264,17 +307,20 @@ struct window_moved
*/
struct window_resized
{
/// Window width, in pixels.
std::int32_t window_width;
/// Pointer to the window that has been resized.
void* window;
/// Window size, in display units.
math::vector<std::int32_t, 2> size;
/// Window height, in pixels.
std::int32_t window_height;
/// `true` if the window is maximized, `false` otherwise.
bool maximized;
/// Viewport width, in pixels.
std::int32_t viewport_width;
/// `true` if the window is fullscreen, `false` otherwise.
bool fullscreen;
/// Viewport height, in pixels.
std::int32_t viewport_height;
/// Window viewport size, in pixels.
math::vector<std::int32_t, 2> viewport_size;
};
} // namespace event

+ 28
- 10
src/main.cpp View File

@ -24,21 +24,26 @@
#include "game/state/boot.hpp"
#include "utility/ansi.hpp"
#include "utility/paths.hpp"
#include <chrono>
#include <fstream>
#include <iostream>
#include <SDL2/SDL.h>
#include <set>
#include <stdexcept>
#include <syncstream>
int main(int argc, char* argv[])
{
// Get time at which the application launched
const auto launch_time = std::chrono::system_clock::now();
// Enable VT100 sequences in console for colored text
debug::console::enable_vt100();
// Subscribe log to cout function to message logged events
auto log_to_cout_subscription = debug::log::default_logger().get_message_logged_channel().subscribe
(
[&](const auto& event)
[&launch_time](const auto& event)
{
static const char* severities[] =
{
@ -62,7 +67,8 @@ int main(int argc, char* argv[])
std::osyncstream(std::cout) << std::format
(
"{}:{}:{}: {}{}: {}{}\n",
"{:8.03f} {}:{}:{}: {}{}: {}{}\n",
std::chrono::duration<float>(event.time - launch_time).count(),
std::filesystem::path(event.location.file_name()).filename().string(),
event.location.line(),
event.location.column(),
@ -75,7 +81,7 @@ int main(int argc, char* argv[])
);
// Determine path to log archive
const std::filesystem::path log_archive_path = get_config_path(config::application_name) / "logs";
const std::filesystem::path log_archive_path = get_shared_config_path() / config::application_name / "logs";
// Set up log archive
bool log_archive_exists = false;
@ -130,14 +136,15 @@ int main(int argc, char* argv[])
// Set up logging to file
std::shared_ptr<event::subscription> log_to_file_subscription;
std::filesystem::path log_filepath;
if (config::debug_log_archive_capacity && log_archive_exists)
{
// Determine log filename
const auto time = std::chrono::floor<std::chrono::seconds>(std::chrono::system_clock::now());
const auto time = std::chrono::floor<std::chrono::seconds>(launch_time);
const std::string log_filename = std::format("{0}-{1:%Y%m%d}T{1:%H%M%S}Z.log", config::application_name, time);
// Open log file
std::filesystem::path log_filepath = log_archive_path / log_filename;
log_filepath = log_archive_path / log_filename;
const std::string log_filepath_string = log_filepath.string();
auto log_filestream = std::make_shared<std::ofstream>(log_filepath);
@ -153,12 +160,12 @@ int main(int argc, char* argv[])
// Subscribe log to file function to message logged events
log_to_file_subscription = debug::log::default_logger().get_message_logged_channel().subscribe
(
[log_filestream](const auto& event)
[&launch_time, log_filestream](const auto& event)
{
std::osyncstream(*log_filestream) << std::format
(
"\n{0:%Y%m%d}T{0:%H%M%S}Z\t{1}\t{2}\t{3}\t{4}\t{5}",
std::chrono::floor<std::chrono::milliseconds>(event.time),
"\n{:.03f}\t{}\t{}\t{}\t{}\t{}",
std::chrono::duration<float>(event.time - launch_time).count(),
std::filesystem::path(event.location.file_name()).filename().string(),
event.location.line(),
event.location.column(),
@ -184,8 +191,11 @@ int main(int argc, char* argv[])
}
}
// Log application name and version string
debug::log::info("{} v{}", config::application_name, config::application_version_string);
// Log application name and version string, followed by launch time
debug::log::info("{0} {1}; {2:%Y%m%d}T{2:%H%M%S}Z", config::application_name, config::application_version_string, std::chrono::floor<std::chrono::milliseconds>(launch_time));
// Start marker
debug::log::debug("Hi! 🐜");
try
{
@ -198,8 +208,16 @@ int main(int argc, char* argv[])
catch (const std::exception& e)
{
debug::log::fatal("Unhandled exception: {}", e.what());
#if defined(NDEBUG)
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "Error", std::format("Unhandled exception: {}", e.what()).c_str(), nullptr);
#endif
return EXIT_FAILURE;
}
// Clean exit marker
debug::log::debug("Bye! 🐜");
return EXIT_SUCCESS;
}

+ 3
- 1
src/render/anti-aliasing-method.hpp View File

@ -20,12 +20,14 @@
#ifndef ANTKEEPER_RENDER_ANTI_ALIASING_METHOD_HPP
#define ANTKEEPER_RENDER_ANTI_ALIASING_METHOD_HPP
#include <cstdint>
namespace render {
/**
* Anti-aliasing methods.
*/
enum class anti_aliasing_method
enum class anti_aliasing_method: std::uint8_t
{
/// No anti-aliasing.
none,

+ 1
- 0
src/render/passes/fxaa-pass.cpp View File

@ -30,6 +30,7 @@
#include "gl/texture-2d.hpp"
#include "render/vertex-attribute.hpp"
#include "render/context.hpp"
#include "debug/log.hpp"
#include <glad/glad.h>
namespace render {

+ 0
- 1
src/render/passes/ground-pass.cpp View File

@ -19,7 +19,6 @@
#include "render/passes/ground-pass.hpp"
#include "resources/resource-manager.hpp"
#include "resources/string-table.hpp"
#include "gl/rasterizer.hpp"
#include "gl/framebuffer.hpp"
#include "gl/shader-program.hpp"

+ 0
- 2
src/render/passes/material-pass.cpp View File

@ -591,8 +591,6 @@ const material_pass::parameter_set* material_pass::load_parameter_set(const gl::
bool operation_compare(const render::operation& a, const render::operation& b)
{
/// @TODO: something is wrong with this compare op, assertion fails
if (!a.material)
return false;
else if (!b.material)

+ 0
- 1
src/render/passes/sky-pass.cpp View File

@ -19,7 +19,6 @@
#include "render/passes/sky-pass.hpp"
#include "resources/resource-manager.hpp"
#include "resources/string-table.hpp"
#include "gl/rasterizer.hpp"
#include "gl/framebuffer.hpp"
#include "gl/shader-program.hpp"

+ 176
- 0
src/resources/deserialize-context.cpp View File

@ -0,0 +1,176 @@
/*
* 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/deserialize-context.hpp"
#include "resources/deserialize-error.hpp"
#include <physfs.h>
deserialize_context::deserialize_context(void* handle):
handle(handle),
m_eof(false),
m_error(false)
{}
std::size_t deserialize_context::read8(std::byte* data, std::size_t count)
{
const PHYSFS_sint64 status = PHYSFS_readBytes(reinterpret_cast<PHYSFS_File*>(handle), data, count);
if (status < 0)
{
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return 0;
}
if (status != count)
{
m_eof = true;
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return static_cast<std::size_t>(count);
}
return count;
}
std::size_t deserialize_context::read16_le(std::byte* data, std::size_t count)
{
PHYSFS_File* file = reinterpret_cast<PHYSFS_File*>(handle);
PHYSFS_uint16* data16 = reinterpret_cast<PHYSFS_uint16*>(data);
for (std::size_t i = 0; i < count; ++i)
{
if (!PHYSFS_readULE16(file, data16))
{
m_eof = (PHYSFS_eof(file) != 0);
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return i;
}
++data16;
}
return count;
}
std::size_t deserialize_context::read16_be(std::byte* data, std::size_t count)
{
PHYSFS_File* file = reinterpret_cast<PHYSFS_File*>(handle);
PHYSFS_uint16* data16 = reinterpret_cast<PHYSFS_uint16*>(data);
for (std::size_t i = 0; i < count; ++i)
{
if (!PHYSFS_readUBE16(file, data16))
{
m_eof = (PHYSFS_eof(file) != 0);
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return i;
}
++data16;
}
return count;
}
std::size_t deserialize_context::read32_le(std::byte* data, std::size_t count)
{
PHYSFS_File* file = reinterpret_cast<PHYSFS_File*>(handle);
PHYSFS_uint32* data32 = reinterpret_cast<PHYSFS_uint32*>(data);
for (std::size_t i = 0; i < count; ++i)
{
if (!PHYSFS_readULE32(file, data32))
{
m_eof = (PHYSFS_eof(file) != 0);
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return i;
}
++data32;
}
return count;
}
std::size_t deserialize_context::read32_be(std::byte* data, std::size_t count)
{
PHYSFS_File* file = reinterpret_cast<PHYSFS_File*>(handle);
PHYSFS_uint32* data32 = reinterpret_cast<PHYSFS_uint32*>(data);
for (std::size_t i = 0; i < count; ++i)
{
if (!PHYSFS_readUBE32(file, data32))
{
m_eof = (PHYSFS_eof(file) != 0);
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return i;
}
++data32;
}
return count;
}
std::size_t deserialize_context::read64_le(std::byte* data, std::size_t count)
{
PHYSFS_File* file = reinterpret_cast<PHYSFS_File*>(handle);
PHYSFS_uint64* data64 = reinterpret_cast<PHYSFS_uint64*>(data);
for (std::size_t i = 0; i < count; ++i)
{
if (!PHYSFS_readULE64(file, data64))
{
m_eof = (PHYSFS_eof(file) != 0);
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return i;
}
++data64;
}
return count;
}
std::size_t deserialize_context::read64_be(std::byte* data, std::size_t count)
{
PHYSFS_File* file = reinterpret_cast<PHYSFS_File*>(handle);
PHYSFS_uint64* data64 = reinterpret_cast<PHYSFS_uint64*>(data);
for (std::size_t i = 0; i < count; ++i)
{
if (!PHYSFS_readUBE64(file, data64))
{
m_eof = (PHYSFS_eof(file) != 0);
m_error = true;
throw deserialize_error(PHYSFS_getLastError());
//return i;
}
++data64;
}
return count;
}

+ 152
- 0
src/resources/deserialize-context.hpp View File

@ -0,0 +1,152 @@
/*
* 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 ANTKEEPER_RESOURCES_DESERIALIZE_CONTEXT_HPP
#define ANTKEEPER_RESOURCES_DESERIALIZE_CONTEXT_HPP
#include <cstddef>
#include <bit>
/**
* Provides access to a deserialization state.
*/
struct deserialize_context
{
public:
/**
* Reads 8-bit (byte) data.
*
* @param data Pointer to data source.
* @param count Number of bytes to read.
*
* @return Number of bytes read.
*
* @throw deserialize_error Read error.
*/
std::size_t read8(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.
*
* @return Number of words read.
*
* @throw deserialize_error Read error.
*/
template <std::endian Endian = std::endian::native>
inline std::size_t read16(std::byte* data, std::size_t count) noexcept(false)
{
if constexpr (Endian == std::endian::little)
{
return read16_le(data, count);
}
else
{
return read16_be(data, count);
}
}
/**
* 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.
*
* @return Number of double words read.
*
* @throw deserialize_error Read error.
*/
template <std::endian Endian = std::endian::native>
inline std::size_t read32(std::byte* data, std::size_t count) noexcept(false)
{
if constexpr (Endian == std::endian::little)
{
return read32_le(data, count);
}
else
{
return read32_be(data, count);
}
}
/**
* 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.
*
* @return Number of quad words read.
*
* @throw deserialize_error Read error.
*/
template <std::endian Endian = std::endian::native>
inline std::size_t read64(std::byte* data, std::size_t count) noexcept(false)
{
if constexpr (Endian == std::endian::little)
{
return read64_le(data, count);
}
else
{
return read64_be(data, count);
}
}
/**
* Returns `true` if the end of a file was reached.
*/
inline bool eof() const noexcept
{
return m_eof;
}
/**
* Returns `true` if an error occured during a read operation, `false` otherwise.
*/
inline bool error() const noexcept
{
return m_error;
}
private:
template <class T>
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;
bool m_error;
};
#endif // ANTKEEPER_RESOURCES_DESERIALIZE_CONTEXT_HPP

src/resources/string-table.cpp → src/resources/deserialize-error.hpp View File

@ -17,30 +17,17 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resources/string-table.hpp"
#ifndef ANTKEEPER_RESOURCES_DESERIALIZE_ERROR_HPP
#define ANTKEEPER_RESOURCES_DESERIALIZE_ERROR_HPP
void build_string_table_map(string_table_map* map, const string_table& table)
{
map->clear();
for (std::size_t i = 0; i < table.size(); ++i)
{
for (std::size_t j = 2; j < table[i].size(); ++j)
{
const std::string& string = table[i][j];
(*map)[table[0][j]][table[i][0]] = string.empty() ? "# MISSING STRING #" : string;
}
}
}
#include <stdexcept>
string_table_index index_string_table(const string_table& table)
/**
* An exception of this type is thrown when an error occurs during deserialization.
*/
class deserialize_error: public std::runtime_error
{
string_table_index index;
using std::runtime_error::runtime_error;
};
for (std::size_t i = 0; i < table.size(); ++i)
{
index[table[i][0]] = i;
}
return index;
}
#endif // ANTKEEPER_RESOURCES_DESERIALIZE_ERROR_HPP

+ 125
- 0
src/resources/deserializer.cpp View File

@ -0,0 +1,125 @@
/*
* 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/deserializer.hpp"
#include <string>
template <>
void deserializer<bool>::deserialize(bool& value, deserialize_context& ctx)
{
std::uint8_t temp;
ctx.read8(reinterpret_cast<std::byte*>(&temp), 1);
value = (temp) ? true : false;
};
template <>
void deserializer<std::uint8_t>::deserialize(std::uint8_t& value, deserialize_context& ctx)
{
ctx.read8(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::uint16_t>::deserialize(std::uint16_t& value, deserialize_context& ctx)
{
ctx.read16(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);
};
template <>
void deserializer<std::uint64_t>::deserialize(std::uint64_t& value, deserialize_context& ctx)
{
ctx.read64(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::int8_t>::deserialize(std::int8_t& value, deserialize_context& ctx)
{
ctx.read8(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<std::int16_t>::deserialize(std::int16_t& value, deserialize_context& ctx)
{
ctx.read16(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);
};
template <>
void deserializer<std::int64_t>::deserialize(std::int64_t& value, deserialize_context& ctx)
{
ctx.read64(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<float>::deserialize(float& value, deserialize_context& ctx)
{
ctx.read32(reinterpret_cast<std::byte*>(&value), 1);
};
template <>
void deserializer<double>::deserialize(double& value, deserialize_context& ctx)
{
ctx.read64(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);
value.resize(static_cast<std::size_t>(length));
ctx.read8(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};
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);
value.resize(static_cast<std::size_t>(length));
ctx.read8(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};
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);
value.resize(static_cast<std::size_t>(length));
ctx.read16(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);
value.resize(static_cast<std::size_t>(length));
ctx.read32(reinterpret_cast<std::byte*>(value.data()), static_cast<std::size_t>(length));
};

+ 42
- 0
src/resources/deserializer.hpp View File

@ -0,0 +1,42 @@
/*
* 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 ANTKEEPER_RESOURCES_DESERIALIZER_HPP
#define ANTKEEPER_RESOURCES_DESERIALIZER_HPP
#include "resources/deserialize-context.hpp"
/**
* Specializations of deserializer define the deserialization process for a given type.
*
* @tparam T Deserializable type.
*/
template <class T>
struct deserializer
{
/**
* Deserializes a value.
*
* @param value Value to deserialize.
* @param ctx Deserialize context.
*/
void deserialize(T& value, deserialize_context& ctx);
};
#endif // ANTKEEPER_RESOURCES_DESERIALIZER_HPP

+ 43
- 0
src/resources/dict-loader.cpp 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/>.
*/
#include "resources/resource-loader.hpp"
#include "resources/serializer.hpp"
#include "resources/deserializer.hpp"
#include "utility/dict.hpp"
#include <cstdint>
#include <physfs.h>
template <>
dict<std::uint32_t>* resource_loader<dict<std::uint32_t>>::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path)
{
dict<std::uint32_t>* dict = new ::dict<std::uint32_t>();
deserialize_context ctx(file);
deserializer<::dict<std::uint32_t>>().deserialize(*dict, ctx);
return dict;
}
template <>
void resource_loader<dict<std::uint32_t>>::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const dict<std::uint32_t>* dict)
{
serialize_context ctx(file);
serializer<::dict<std::uint32_t>>().serialize(*dict, ctx);
}

+ 32
- 14
src/resources/resource-loader.cpp View File

@ -17,27 +17,45 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resource-loader.hpp"
#include "resources/resource-loader.hpp"
#include "resources/deserialize-error.hpp"
#include <physfs.h>
void physfs_getline(PHYSFS_File* file, std::string& line)
{
PHYSFS_sint64 bytes;
char c;
line.clear();
do
for (;;)
{
bytes = PHYSFS_readBytes(file, &c, 1);
if (bytes != 1 || c == '\n')
break;
if (c == '\r')
continue;
char c;
const PHYSFS_sint64 status = PHYSFS_readBytes(file, &c, 1);
line.append(1, c);
if (status == 1)
{
if (c == '\r')
{
continue;
}
else if (c == '\n')
{
break;
}
else
{
line.append(1, c);
}
}
else
{
if (PHYSFS_eof(file))
{
break;
}
else
{
throw deserialize_error(PHYSFS_getLastError());
}
}
}
while (!PHYSFS_eof(file));
}

+ 31
- 0
src/resources/resource-manager.cpp View File

@ -18,14 +18,33 @@
*/
#include "resources/resource-manager.hpp"
#include "debug/log.hpp"
#include <stdexcept>
resource_manager::resource_manager()
{
// Log PhysicsFS info
// PHYSFS_Version physfs_compiled_version;
// PHYSFS_Version physfs_linked_version;
// PHYSFS_VERSION(&physfs_compiled_version);
// PHYSFS_getLinkedVersion(&physfs_linked_version);
// debug::log::info
// (
// "PhysicsFS compiled version: {}.{}.{}; linked version: {}.{}.{}",
// physfs_compiled_version.major,
// physfs_compiled_version.minor,
// physfs_compiled_version.patch,
// physfs_linked_version.major,
// physfs_linked_version.minor,
// physfs_linked_version.patch
// );
// Init PhysicsFS
debug::log::trace("Initializing PhysicsFS...");
if (!PHYSFS_init(nullptr))
{
debug::log::error("Failed to initialize PhysicsFS: {}", PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
throw std::runtime_error("Failed to initialize PhysicsFS");
}
else
{
@ -72,6 +91,18 @@ bool resource_manager::mount(const std::filesystem::path& path)
}
}
void resource_manager::set_write_dir(const std::filesystem::path& path)
{
if (!PHYSFS_setWriteDir(path.string().c_str()))
{
debug::log::error("Failed set write directory to \"{}\": {}", path.string(), PHYSFS_getErrorByCode(PHYSFS_getLastErrorCode()));
}
else
{
debug::log::trace("Set write directory to \"{}\"", path.string());
}
}
void resource_manager::unload(const std::filesystem::path& path)
{
// Check if resource is in the cache

+ 2
- 0
src/resources/resource-manager.hpp View File

@ -47,6 +47,8 @@ public:
~resource_manager();
bool mount(const std::filesystem::path& path);
void set_write_dir(const std::filesystem::path& path);
/**
* Adds a path to be searched when a resource is requested.

+ 144
- 0
src/resources/serialize-context.cpp View File

@ -0,0 +1,144 @@
/*
* 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/serialize-context.hpp"
#include "resources/serialize-error.hpp"
#include <physfs.h>
serialize_context::serialize_context(void* handle):
handle(handle),
m_error(false)
{}
std::size_t serialize_context::write8(const std::byte* data, std::size_t count)
{
const PHYSFS_sint64 status = PHYSFS_writeBytes(reinterpret_cast<PHYSFS_File*>(handle), data, count);
if (status < 0)
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return 0;
}
if (status != count)
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return static_cast<std::size_t>(count);
}
return count;
}
std::size_t serialize_context::write16(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))
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return i;
}
}
else
{
if (!PHYSFS_writeUBE16(file, *data16))
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return i;
}
}
++data16;
}
return count;
}
std::size_t serialize_context::write32(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))
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return i;
}
}
else
{
if (!PHYSFS_writeUBE32(file, *data32))
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return i;
}
}
++data32;
}
return count;
}
std::size_t serialize_context::write64(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))
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return i;
}
}
else
{
if (!PHYSFS_writeUBE64(file, *data64))
{
m_error = true;
throw serialize_error(PHYSFS_getLastError());
//return i;
}
}
++data64;
}
return count;
}

+ 100
- 0
src/resources/serialize-context.hpp View File

@ -0,0 +1,100 @@
/*
* 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 ANTKEEPER_RESOURCES_SERIALIZE_CONTEXT_HPP
#define ANTKEEPER_RESOURCES_SERIALIZE_CONTEXT_HPP
#include <cstddef>
#include <bit>
/**
* Provides access to a serialization state.
*/
struct serialize_context
{
public:
static inline constexpr std::endian endian = std::endian::little;
/**
* Writes 8-bit (byte) data.
*
* @param data Pointer to data source.
* @param count Number of bytes to write.
*
* @return Number of bytes written.
*
* @throw serialize_error Write error.
*/
std::size_t write8(const std::byte* data, std::size_t count) noexcept(false);
/**
* Writes 16-bit (word) 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(const std::byte* data, std::size_t count) noexcept(false);
/**
* Writes 32-bit (double word) 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(const std::byte* data, std::size_t count) noexcept(false);
/**
* Writes 64-bit (quad word) 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(const std::byte* data, std::size_t count) noexcept(false);
/**
* Returns `true` if an error occured during a write operation, `false` otherwise.
*/
inline bool error() const noexcept
{
return m_error;
}
private:
template <class T>
friend class resource_loader;
serialize_context(void* handle);
void* handle;
bool m_error;
};
#endif // ANTKEEPER_RESOURCES_SERIALIZE_CONTEXT_HPP

+ 33
- 0
src/resources/serialize-error.hpp View File

@ -0,0 +1,33 @@
/*
* 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 ANTKEEPER_RESOURCES_SERIALIZE_ERROR_HPP
#define ANTKEEPER_RESOURCES_SERIALIZE_ERROR_HPP
#include <stdexcept>
/**
* An exception of this type is thrown when an error occurs during serialization.
*/
class serialize_error: public std::runtime_error
{
using std::runtime_error::runtime_error;
};
#endif // ANTKEEPER_RESOURCES_SERIALIZE_ERROR_HPP

+ 120
- 0
src/resources/serializer.cpp View File

@ -0,0 +1,120 @@
/*
* 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/serializer.hpp"
#include <string>
template <>
void serializer<bool>::serialize(const bool& value, serialize_context& ctx)
{
const std::uint8_t temp = (value) ? 1 : 0;
ctx.write8(reinterpret_cast<const std::byte*>(&temp), 1);
};
template <>
void serializer<std::uint8_t>::serialize(const std::uint8_t& value, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::uint16_t>::serialize(const std::uint16_t& value, serialize_context& ctx)
{
ctx.write16(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);
};
template <>
void serializer<std::uint64_t>::serialize(const std::uint64_t& value, serialize_context& ctx)
{
ctx.write64(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::int8_t>::serialize(const std::int8_t& value, serialize_context& ctx)
{
ctx.write8(reinterpret_cast<const std::byte*>(&value), 1);
};
template <>
void serializer<std::int16_t>::serialize(const std::int16_t& value, serialize_context& ctx)
{
ctx.write16(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);
};
template <>
void serializer<std::int64_t>::serialize(const std::int64_t& value, serialize_context& ctx)
{
ctx.write64(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);
};
template <>
void serializer<double>::serialize(const double& value, serialize_context& ctx)
{
ctx.write64(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.write8(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
};
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.write8(reinterpret_cast<const std::byte*>(value.data()), static_cast<std::size_t>(length));
};
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));
};
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));
};

+ 42
- 0
src/resources/serializer.hpp View File

@ -0,0 +1,42 @@
/*
* 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 ANTKEEPER_RESOURCES_SERIALIZER_HPP
#define ANTKEEPER_RESOURCES_SERIALIZER_HPP
#include "resources/serialize-context.hpp"
/**
* Specializations of serializer define the serialization process for a given type.
*
* @tparam T Serializable type.
*/
template <class T>
struct serializer
{
/**
* Serializes a value.
*
* @param value Value to serialize.
* @param ctx Serialize context.
*/
void serialize(const T& value, serialize_context& ctx);
};
#endif // ANTKEEPER_RESOURCES_SERIALIZER_HPP

+ 43
- 101
src/resources/string-table-loader.cpp View File

@ -17,121 +17,63 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "resource-loader.hpp"
#include "string-table.hpp"
#include "resources/resource-loader.hpp"
#include "i18n/string-table.hpp"
#include "resources/deserialize-error.hpp"
#include <physfs.h>
static string_table_row parse_row(const std::string& line)
template <>
i18n::string_table* resource_loader<i18n::string_table>::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path)
{
std::vector<std::string> row;
std::string column;
bool quoted = false;
bool escape = false;
for (char c: line)
i18n::string_table* table = new i18n::string_table();
i18n::string_table_row row;
std::string entry;
for (;;)
{
if (escape)
char c;
const PHYSFS_sint64 status = PHYSFS_readBytes(file, &c, 1);
if (status == 1)
{
switch (c)
if (c == '\t')
{
case 'n':
column.push_back('\n');
break;
case 't':
column.push_back('\t');
break;
default:
column.push_back(c);
break;
row.push_back(entry);
entry.clear();
}
else if (c == '\n')
{
row.push_back(entry);
entry.clear();
table->push_back(row);
row.clear();
}
else if (c != '\r')
{
entry.push_back(c);
}
escape = false;
}
else
{
switch (c)
if (PHYSFS_eof(file))
{
case '\\':
escape = true;
break;
case ',':
if (quoted)
{
column.push_back(c);
}
else
{
row.push_back(column);
column.clear();
}
break;
case '"':
if (!quoted)
{
quoted = true;
}
else
{
quoted = false;
}
break;
default:
column.push_back(c);
break;
if (!entry.empty())
{
row.push_back(entry);
}
if (!row.empty())
{
table->push_back(row);
}
break;
}
}
}
row.push_back(column);
return row;
}
template <>
string_table* resource_loader<string_table>::load(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path)
{
string_table* table = new string_table();
std::string line;
while (!PHYSFS_eof(file))
{
physfs_getline(file, line);
table->push_back(parse_row(line));
}
return table;
}
template <>
void resource_loader<string_table>::save(resource_manager* resource_manager, PHYSFS_File* file, const std::filesystem::path& path, const string_table* table)
{
const char* delimeter = ",";
const char* newline = "\n";
for (std::size_t i = 0; i < table->size(); ++i)
{
const string_table_row& row = (*table)[i];
for (std::size_t j = 0; j < row.size(); ++j)
{
const std::string& column = row[j];
PHYSFS_writeBytes(file, column.data(), column.length());
if (j < row.size() - 1)
else
{
PHYSFS_writeBytes(file, delimeter, 1);
throw deserialize_error(PHYSFS_getLastError());
}
}
if (i < table->size() - 1)
{
PHYSFS_writeBytes(file, newline, 1);
}
}
return table;
}

+ 168
- 0
src/utility/dict.cpp View File

@ -0,0 +1,168 @@
/*
* 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 "utility/dict.hpp"
#include "resources/serializer.hpp"
#include "resources/serialize-error.hpp"
#include "resources/deserializer.hpp"
#include "resources/deserialize-error.hpp"
#include "utility/hash/fnv1a.hpp"
#include <cstdint>
#include <string>
#include <tuple>
#include <typeindex>
#include <unordered_map>
using namespace hash::literals;
template <class T>
static void serialize_any(const std::any& any, serialize_context& ctx)
{
serializer<T>().serialize(std::any_cast<T>(any), ctx);
}
template <class T>
static void deserialize_any(std::any& any, deserialize_context& ctx)
{
T value;
deserializer<T>().deserialize(value, ctx);
any = std::move(value);
}
/**
* Serializes a dict with an unsigned 32-bit key.
*
* @throw serialize_error Write error.
* @throw serialize_error Unsupported dict value type.
*/
template <>
void serializer<dict<std::uint32_t>>::serialize(const dict<std::uint32_t>& dict, serialize_context& ctx)
{
// Map type indices to tuples containing a type hash and serialize function pointer
static const std::unordered_map
<
std::type_index,
std::tuple
<
std::uint32_t,
void (*)(const std::any&, serialize_context&)
>
> type_map
{
{std::type_index(typeid(bool)), {"bool"_fnv1a32, &serialize_any<bool>}},
{std::type_index(typeid(std::uint8_t)), {"uint8"_fnv1a32, &serialize_any<std::uint8_t>}},
{std::type_index(typeid(std::uint16_t)), {"uint16"_fnv1a32, &serialize_any<std::uint16_t>}},
{std::type_index(typeid(std::uint32_t)), {"uint32"_fnv1a32, &serialize_any<std::uint32_t>}},
{std::type_index(typeid(std::uint64_t)), {"uint64"_fnv1a32, &serialize_any<std::uint64_t>}},
{std::type_index(typeid(std::int8_t)), {"int8"_fnv1a32, &serialize_any<std::int8_t>}},
{std::type_index(typeid(std::int16_t)), {"int16"_fnv1a32, &serialize_any<std::int16_t>}},
{std::type_index(typeid(std::int32_t)), {"int32"_fnv1a32, &serialize_any<std::int32_t>}},
{std::type_index(typeid(std::int64_t)), {"int64"_fnv1a32, &serialize_any<std::int64_t>}},
{std::type_index(typeid(float)), {"float"_fnv1a32, &serialize_any<float>}},
{std::type_index(typeid(double)), {"double"_fnv1a32, &serialize_any<double>}},
{std::type_index(typeid(std::string)), {"string"_fnv1a32, &serialize_any<std::string>}},
{std::type_index(typeid(std::u8string)), {"u8string"_fnv1a32, &serialize_any<std::u8string>}},
{std::type_index(typeid(std::u16string)), {"u16string"_fnv1a32, &serialize_any<std::u16string>}},
{std::type_index(typeid(std::u32string)), {"u32string"_fnv1a32, &serialize_any<std::u32string>}}
};
// Write dict size
std::uint64_t size = static_cast<std::uint64_t>(dict.size());
ctx.write64(reinterpret_cast<const std::byte*>(&size), 1);
// Write dict entries
for (const auto& [key, value]: dict)
{
if (auto i = type_map.find(value.type()); i != type_map.end())
{
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);
// Serialize entry value
type_serializer(value, ctx);
}
else
{
throw serialize_error("Unsupported dict value type");
}
}
};
/**
* Deserializes a dict with an unsigned 32-bit key.
*
* @throw deserialize_error Write error.
* @throw deserialize_error Unsupported dict value type.
*/
template <>
void deserializer<dict<std::uint32_t>>::deserialize(dict<std::uint32_t>& dict, deserialize_context& ctx)
{
// Map type hashes to deserialize function pointers
static const std::unordered_map
<
std::uint32_t,
void (*)(std::any&, deserialize_context&)
> type_map
{
{"bool"_fnv1a32, &deserialize_any<bool>},
{"uint8"_fnv1a32, &deserialize_any<std::uint8_t>},
{"uint16"_fnv1a32, &deserialize_any<std::uint16_t>},
{"uint32"_fnv1a32, &deserialize_any<std::uint32_t>},
{"uint64"_fnv1a32, &deserialize_any<std::uint64_t>},
{"int8"_fnv1a32, &deserialize_any<std::int8_t>},
{"int16"_fnv1a32, &deserialize_any<std::int16_t>},
{"int32"_fnv1a32, &deserialize_any<std::int32_t>},
{"int64"_fnv1a32, &deserialize_any<std::int64_t>},
{"float"_fnv1a32, &deserialize_any<float>},
{"double"_fnv1a32, &deserialize_any<double>},
{"string"_fnv1a32, &deserialize_any<std::string>},
{"u8string"_fnv1a32, &deserialize_any<std::u8string>},
{"u16string"_fnv1a32, &deserialize_any<std::u16string>},
{"u32string"_fnv1a32, &deserialize_any<std::u32string>}
};
// Read dict size
std::uint64_t size = 0;
ctx.read64(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);
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);
// Deserialize entry value
i->second(dict[key], ctx);
}
else
{
throw deserialize_error("Unsupported dict value type");
}
}
};

+ 34
- 0
src/utility/dict.hpp View File

@ -0,0 +1,34 @@
/*
* 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 ANTKEEPER_UTILITY_DICT_HPP
#define ANTKEEPER_UTILITY_DICT_HPP
#include <any>
#include <unordered_map>
/**
* Unordered dictionary type.
*
* @tparam Key Key type.
*/
template <class Key>
using dict = std::unordered_map<Key, std::any>;
#endif // ANTKEEPER_UTILITY_DICT_HPP

+ 54
- 35
src/utility/paths.cpp View File

@ -17,7 +17,7 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#include "paths.hpp"
#include "utility/paths.hpp"
#include <cstddef>
#include <limits.h>
#include <stdexcept>
@ -32,33 +32,16 @@
#include <unistd.h>
#endif
#if defined(_WIN32)
std::string narrow(const std::wstring& wstring)
{
std::string string(WideCharToMultiByte(CP_UTF8, 0, &wstring[0], static_cast<int>(wstring.size()), nullptr, 0, nullptr, nullptr), '\0');
WideCharToMultiByte(CP_UTF8, 0, &wstring[0], static_cast<int>(wstring.size()), &string[0], static_cast<int>(string.size()), nullptr, nullptr);
return string;
}
std::wstring widen(const std::string& string)
{
std::wstring wstring(MultiByteToWideChar(CP_UTF8, 0, &string[0], static_cast<int>(string.size()), nullptr, 0), L'\0');
MultiByteToWideChar(CP_UTF8, 0, &string[0], static_cast<int>(string.size()), &wstring[0], static_cast<int>(wstring.size()));
return wstring;
}
#endif
std::filesystem::path get_executable_path()
{
std::filesystem::path executable_path;
#if defined(_WIN32)
// Get executable path on Windows
HMODULE hModule = GetModuleHandleW(nullptr);
std::wstring wpath(MAX_PATH, L'\0');
GetModuleFileNameW(hModule, &wpath[0], MAX_PATH);
wpath.erase(std::find(wpath.begin(), wpath.end(), L'\0'), wpath.end());
executable_path = narrow(wpath);
std::wstring path(MAX_PATH, L'\0');
GetModuleFileNameW(GetModuleHandleW(nullptr), path.data(), MAX_PATH);
path.erase(std::find(path.begin(), path.end(), L'\0'), path.end());
executable_path = path;
#else
// Get executable path on Linux
char path[PATH_MAX];
@ -73,43 +56,79 @@ std::filesystem::path get_executable_path()
return executable_path;
}
std::filesystem::path get_data_path(const std::string& application_name)
std::filesystem::path get_executable_data_path()
{
#if defined(_WIN32)
return get_executable_path().parent_path();
#else
return get_executable_path().parent_path().parent_path() / "share" / application_name;
return get_executable_path().parent_path().parent_path() / "share";
#endif
}
std::filesystem::path get_config_path(const std::string& application_name)
std::filesystem::path get_local_config_path()
{
std::filesystem::path config_path;
std::filesystem::path local_config_path;
#if defined(_WIN32)
std::wstring wpath(MAX_PATH, L'\0');
if (SHGetSpecialFolderPathW(nullptr, &wpath[0], CSIDL_LOCAL_APPDATA, FALSE))
std::wstring path(MAX_PATH, L'\0');
if (SHGetFolderPathW(nullptr, CSIDL_LOCAL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, path.data()) == S_OK)
{
wpath.erase(std::find(wpath.begin(), wpath.end(), L'\0'), wpath.end());
config_path = std::filesystem::path(narrow(wpath)) / application_name;
path.erase(std::find(path.begin(), path.end(), L'\0'), path.end());
local_config_path = path;
}
// Windows Vista+
// wchar_t* path_buffer = nullptr;
// if (SHGetKnownFolderPath(FOLDERID_LocalAppData, KF_FLAG_DEFAULT, nullptr, &path_buffer) == S_OK)
// {
// local_config_path = std::filesystem::path(path_buffer);
// CoTaskMemFree(static_cast<void*>(path_buffer));
// }
#else
// Determine home path
std::filesystem::path home_path = getpwuid(getuid())->pw_dir;
// Determine config path
char* xdgConfigHome = std::getenv("XDG_CONFIG_HOME");
if (!xdgConfigHome)
char* xdg_config_home = std::getenv("XDG_CONFIG_HOME");
if (!xdg_config_home)
{
// Default to $HOME/.config/ as per:
// https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables
config_path = home_path / ".config/" / application_name;
local_config_path = home_path / ".config/";
}
else
{
config_path = std::filesystem::path(xdgConfigHome) / application_name;
local_config_path = xdg_config_home;
}
#endif
return config_path;
return local_config_path;
}
std::filesystem::path get_shared_config_path()
{
#if defined(_WIN32)
std::filesystem::path shared_config_path;
std::wstring path(MAX_PATH, L'\0');
if (SHGetFolderPathW(nullptr, CSIDL_APPDATA, nullptr, SHGFP_TYPE_CURRENT, path.data()) == S_OK)
{
path.erase(std::find(path.begin(), path.end(), L'\0'), path.end());
shared_config_path = path;
}
// Windows Vista+
// wchar_t* path_buffer = nullptr;
// if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_DEFAULT, nullptr, &path_buffer) == S_OK)
// {
// shared_config_path = path_buffer;
// CoTaskMemFree(static_cast<void*>(path_buffer));
// }
return shared_config_path;
#else
return get_local_config_path();
#endif
}

+ 20
- 14
src/utility/paths.hpp View File

@ -17,11 +17,10 @@
* along with Antkeeper source code. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef ANTKEEPER_PATHS_HPP
#define ANTKEEPER_PATHS_HPP
#ifndef ANTKEEPER_UTILITY_PATHS_HPP
#define ANTKEEPER_UTILITY_PATHS_HPP
#include <filesystem>
#include <string>
/**
* Returns the absolute path to the current executable.
@ -33,24 +32,31 @@
/**
* Returns the absolute path to the directory containing application data.
*
* Windows: executable_directory
* GNU/Linux: executable_directory/../share/applicationName/
* Windows: ` get_executable_path()`
* GNU/Linux: ` get_executable_path()/../share/`
*
* @param application_name Name of the application.
* @return Path to the application's data directory.
*/
[[nodiscard]] std::filesystem::path get_data_path(const std::string& application_name);
[[nodiscard]] std::filesystem::path get_executable_data_path();
/**
* Returns the absolute path to the directory containing user-specific application data.
* Returns the absolute path to the directory containing user-specific, device-specific application data.
*
* Windows: %LOCALAPPDATA%\application_name\
* GNU/Linux: $XDG_CONFIG_HOME/application_name/ or ~/.config/application_name/ if $XDG_CONFIG_HOME is not set.
* Windows: `%LOCALAPPDATA%`
* GNU/Linux: `$XDG_CONFIG_HOME` or `~/.config/` if `$XDG_CONFIG_HOME` is not set.
*
* @param application_name Name of the application.
* @return Path to the application's config directory.
* @return Path to the local config directory.
*/
[[nodiscard]] std::filesystem::path get_config_path(const std::string& application_name);
[[nodiscard]] std::filesystem::path get_local_config_path();
#endif // ANTKEEPER_PATHS_HPP
/**
* Returns the absolute path to the directory containing user-specific application data that may be shared across devices.
*
* Windows: `%APPDATA%`
* GNU/Linux: `$XDG_CONFIG_HOME` or `~/.config/` if `$XDG_CONFIG_HOME` is not set.
*
* @return Path to the shared config directory.
*/
[[nodiscard]] std::filesystem::path get_shared_config_path();
#endif // ANTKEEPER_UTILITY_PATHS_HPP

Loading…
Cancel
Save