diff --git a/src/application.cpp b/src/application.cpp
index 01c0f7a..805fc10 100644
--- a/src/application.cpp
+++ b/src/application.cpp
@@ -17,10 +17,8 @@
* along with Antkeeper source code. If not, see .
*/
-#include "animation/frame-scheduler.hpp"
#include "application.hpp"
#include "debug/logger.hpp"
-#include "debug/performance-sampler.hpp"
#include "event/event-dispatcher.hpp"
#include "event/window-events.hpp"
#include "input/scancode.hpp"
@@ -31,18 +29,11 @@
#include
#include
#include
-#include
-#include
#include
#include
application::application():
closed(false),
- exit_status(EXIT_SUCCESS),
- current_state{std::string(), nullptr, nullptr},
- queued_state{std::string(), nullptr, nullptr},
- update_callback(nullptr),
- render_callback(nullptr),
fullscreen(true),
v_sync(false),
cursor_visible(true),
@@ -51,7 +42,6 @@ application::application():
window_dimensions({0, 0}),
viewport_dimensions({0, 0}),
mouse_position({0, 0}),
- update_rate(60.0),
logger(nullptr),
sdl_window(nullptr),
sdl_gl_context(nullptr)
@@ -213,18 +203,7 @@ application::application():
mouse->set_event_dispatcher(event_dispatcher);
// Connect gamepads
- translate_sdl_events();
-
- // Setup frame scheduler
- frame_scheduler = new ::frame_scheduler();
- frame_scheduler->set_update_callback(std::bind(&application::update, this, std::placeholders::_1, std::placeholders::_2));
- frame_scheduler->set_render_callback(std::bind(&application::render, this, std::placeholders::_1));
- frame_scheduler->set_update_rate(update_rate);
- frame_scheduler->set_max_frame_duration(0.25);
-
- // Setup performance sampling
- performance_sampler = new debug::performance_sampler();
- performance_sampler->set_sample_size(15);
+ process_events();
}
application::~application()
@@ -240,186 +219,9 @@ application::~application()
SDL_Quit();
}
-void application::close(int status)
+void application::close()
{
closed = true;
- exit_status = status;
-}
-
-int application::execute(const application::state& initial_state)
-{
- try
- {
- // Enter initial application state
- change_state(initial_state);
-
- // Perform initial update
- update(0.0, 0.0);
-
- // Reset frame scheduler
- frame_scheduler->reset();
-
- // Schedule frames until closed
- while (!closed)
- {
- translate_sdl_events();
-
- // Enter queued state (if any)
- if (queued_state.enter != nullptr || queued_state.exit != nullptr)
- {
- // Make a copy of the queued state
- application::state queued_state_copy = queued_state;
-
- // Clear the queued state
- queued_state = {std::string(), nullptr, nullptr};
-
- // Enter the queued state
- change_state(queued_state_copy);
- }
-
- // Tick frame scheduler
- frame_scheduler->tick();
-
- // Sample frame duration
- performance_sampler->sample(frame_scheduler->get_frame_duration());
- }
-
- // Exit current state
- change_state({std::string(), nullptr, nullptr});
- }
- catch (const std::exception& e)
- {
- // Print exception to logger
- logger->error(std::string("Unhandled exception: \"") + e.what() + std::string("\""));
-
- // Show error message box with unhandled exception
- SDL_ShowSimpleMessageBox
- (
- SDL_MESSAGEBOX_ERROR,
- "Unhandled Exception",
- e.what(),
- sdl_window
- );
-
- // Set exit status to failure
- exit_status = EXIT_FAILURE;
- }
-
- return exit_status;
-}
-
-void application::change_state(const application::state& next_state)
-{
- // Exit current state
- if (current_state.exit)
- {
- logger->push_task("Exiting application state \"" + current_state.name + "\"");
-
- try
- {
- current_state.exit();
- }
- catch (...)
- {
- logger->pop_task(EXIT_FAILURE);
- throw;
- }
- logger->pop_task(EXIT_SUCCESS);
- }
-
- current_state = next_state;
-
- // Enter next state
- if (current_state.enter)
- {
- logger->push_task("Entering application state \"" + current_state.name + "\"");
-
- try
- {
- current_state.enter();
- }
- catch (...)
- {
- logger->pop_task(EXIT_FAILURE);
- throw;
- }
- logger->pop_task(EXIT_SUCCESS);
- }
-
- // Enter queued state (if any)
- if (queued_state.enter != nullptr || queued_state.exit != nullptr)
- {
- // Make a copy of the queued state
- application::state queued_state_copy = queued_state;
-
- // Clear the queued state
- queued_state = {std::string(), nullptr, nullptr};
-
- // Enter the queued state
- change_state(queued_state_copy);
- }
-}
-
-void application::queue_state(const application::state& next_state)
-{
- queued_state = next_state;
- logger->log("Queued application state \"" + queued_state.name + "\"");
-}
-
-std::shared_ptr application::capture_frame() const
-{
- int w = viewport_dimensions[0];
- int h = viewport_dimensions[1];
-
- std::shared_ptr frame = std::make_shared();
- frame->format(1, 3);
- frame->resize(w, h);
-
- logger->log("starting read");
-
- // Read pixel data from framebuffer into image
- glReadBuffer(GL_BACK);
-
- logger->log("buffer read");
- glReadPixels(0, 0, w, h, GL_RGB, GL_UNSIGNED_BYTE, frame->get_pixels());
-
- logger->log("ending read");
-
- return std::move(frame);
-}
-
-void application::save_frame(const std::string& path) const
-{
- logger->push_task("Saving screenshot to \"" + path + "\"");
-
- auto frame = capture_frame();
-
- std::thread
- (
- [frame, path]
- {
- stbi_flip_vertically_on_write(1);
- stbi_write_png(path.c_str(), frame->get_width(), frame->get_height(), frame->get_channel_count(), frame->get_pixels(), frame->get_width() * frame->get_channel_count());
- }
- ).detach();
-
- logger->pop_task(EXIT_SUCCESS);
-}
-
-void application::set_update_callback(const update_callback_type& callback)
-{
- update_callback = callback;
-}
-
-void application::set_render_callback(const render_callback_type& callback)
-{
- render_callback = callback;
-}
-
-void application::set_update_rate(double frequency)
-{
- update_rate = frequency;
- frame_scheduler->set_update_rate(update_rate);
}
void application::set_title(const std::string& title)
@@ -563,45 +365,7 @@ void application::add_game_controller_mappings(const void* mappings, std::size_t
}
}
-void application::update(double t, double dt)
-{
- translate_sdl_events();
- event_dispatcher->update(t);
-
- if (update_callback)
- {
- update_callback(t, dt);
- }
-
- /*
- static int frame = 0;
- if (frame % 60 == 0)
- {
- std::cout << std::fixed;
- std::cout << std::setprecision(2);
- std::cout << performance_sampler->mean_frame_duration() * 1000.0 << "\n";
- }
- ++frame;
- */
-}
-
-void application::render(double alpha)
-{
- /*
- std::cout << std::fixed;
- std::cout << std::setprecision(2);
- std::cout << performance_sampler->mean_frame_duration() * 1000.0 << std::endl;
- */
-
- if (render_callback)
- {
- render_callback(alpha);
- }
-
- SDL_GL_SwapWindow(sdl_window);
-}
-
-void application::translate_sdl_events()
+void application::process_events()
{
// Mouse motion event accumulators
bool mouse_motion = false;
@@ -787,7 +551,7 @@ void application::translate_sdl_events()
case SDL_QUIT:
{
- close(EXIT_SUCCESS);
+ close();
break;
}
}
diff --git a/src/application.hpp b/src/application.hpp
index 8235eef..3914e81 100644
--- a/src/application.hpp
+++ b/src/application.hpp
@@ -20,8 +20,6 @@
#ifndef ANTKEEPER_APPLICATION_HPP
#define ANTKEEPER_APPLICATION_HPP
-#include
-#include
#include
#include
#include
@@ -35,8 +33,6 @@
typedef struct SDL_Window SDL_Window;
typedef void* SDL_GLContext;
class event_dispatcher;
-class frame_scheduler;
-class image;
namespace debug
{
@@ -50,23 +46,6 @@ namespace debug
class application
{
public:
- /// Application state type.
- struct state
- {
- /// Name of the state.
- std::string name;
-
- /// State enter function.
- std::function enter;
-
- /// State exit function.
- std::function exit;
- };
-
- typedef std::function bootloader_type;
- typedef std::function update_callback_type;
- typedef std::function render_callback_type;
-
/**
* Creates and initializes an application.
*/
@@ -76,69 +55,11 @@ public:
* Destroys an application.
*/
~application();
-
- /**
- * Executes the application, causing it to run the bootloader then enter the execution loop until closed.
- *
- * @param initial_state Initial state of the application.
- *
- * @return Exit status code.
- */
- int execute(const application::state& initial_state);
-
- /**
- * Requests the application's execution loop to cleanly terminate, and specifies its exit status code.
- *
- * @param status Status to be returned by application::execute() upon execution loop termination.
- */
- void close(int status);
-
- /**
- * Changes the applications state, resulting in the execution of the current state's exit function (if any), followed by the new state's enter function (if any).
- *
- * @param next_state Next application state.
- */
- void change_state(const application::state& next_state);
-
- /**
- * Queues the next applications state. This may be called from within a state's enter function.
- *
- * @param next_state Next application state.
- */
- void queue_state(const application::state& next_state);
-
- /**
- * Captures a screenshot of the most recently rendered frame.
- *
- * @return Image containing the captured frame.
- */
- std::shared_ptr capture_frame() const;
/**
- * Saves a PNG screenshot of the most recently rendered frame.
- *
- * @param path File path to the where the screenshot should be saved.
+ * Requests the application to close.
*/
- void save_frame(const std::string& path) const;
-
- /**
- * Sets the update callback, which is executed at regular intervals until the application is closed. The update callback expects two parameters, the first being the total time in seconds since the application was executed (t), and the second being the time in seconds since the last update (dt). dt will always be a fixed value, and is determined by the user-specified update rate.
- *
- * @see application::set_update_rate()
- */
- void set_update_callback(const update_callback_type& callback);
-
- /**
- * Sets the render callback, which is executed as many times as possible between update callbacks. The render callback expects one parameter, alpha, which is always between 0 and 1 and can be used to interpolate between update states.
- */
- void set_render_callback(const render_callback_type& callback);
-
- /**
- * Sets the frequency with which the update callback should be called.
- *
- * @param frequency Number of times per second the update callback should be called.
- */
- void set_update_rate(double frequency);
+ void close();
/**
* Sets the application window's title.
@@ -227,20 +148,15 @@ public:
/// Returns the application's event dispatcher.
event_dispatcher* get_event_dispatcher();
+
+ void process_events();
+
+ bool was_closed() const;
private:
- void update(double t, double dt);
- void render(double alpha);
-
- void translate_sdl_events();
void window_resized();
bool closed;
- int exit_status;
- application::state current_state;
- application::state queued_state;
- update_callback_type update_callback;
- render_callback_type render_callback;
bool fullscreen;
bool v_sync;
bool cursor_visible;
@@ -249,7 +165,6 @@ private:
int2 window_dimensions;
int2 viewport_dimensions;
int2 mouse_position;
- double update_rate;
debug::logger* logger;
SDL_Window* sdl_window;
@@ -257,10 +172,6 @@ private:
gl::rasterizer* rasterizer;
- // Frame timing
- frame_scheduler* frame_scheduler;
- debug::performance_sampler* performance_sampler;
-
// Events
event_dispatcher* event_dispatcher;
@@ -331,4 +242,9 @@ inline event_dispatcher* application::get_event_dispatcher()
return event_dispatcher;
}
+inline bool application::was_closed() const
+{
+ return closed;
+}
+
#endif // ANTKEEPER_APPLICATION_HPP
diff --git a/src/debug/console-commands.cpp b/src/debug/console-commands.cpp
index 857adef..7fc0baf 100644
--- a/src/debug/console-commands.cpp
+++ b/src/debug/console-commands.cpp
@@ -32,7 +32,7 @@ std::string echo(std::string text)
std::string exit(game::context* ctx)
{
- ctx->app->close(EXIT_SUCCESS);
+ ctx->app->close();
return std::string();
}
diff --git a/src/game/context.hpp b/src/game/context.hpp
index 919bd34..0594d4a 100644
--- a/src/game/context.hpp
+++ b/src/game/context.hpp
@@ -52,6 +52,11 @@
#include "render/material-property.hpp"
#include "ui/mouse-tracker.hpp"
#include "application.hpp"
+#include "game/state/base.hpp"
+#include "game/loop.hpp"
+#include "state-machine.hpp"
+#include "debug/performance-sampler.hpp"
+#include
// Forward declarations
class animator;
@@ -114,35 +119,50 @@ namespace game {
/// Container for data that is shared between game states.
struct context
{
- application* app;
+ /// Hierarchichal state machine
+ hsm::state_machine state_machine;
+ std::function resume_callback;
+
+ /// Debugging
debug::logger* logger;
std::ofstream log_filestream;
+ debug::performance_sampler performance_sampler;
+ debug::cli* cli;
- // Command-line options
- std::optional option_continue;
- std::optional option_data;
- std::optional option_fullscreen;
- std::optional option_new_game;
- std::optional option_quick_start;
- std::optional option_reset;
- std::optional option_v_sync;
- std::optional option_windowed;
+ /// Queue for scheduling "next frame" function calls
+ std::queue> function_queue;
- // Paths
- std::string data_path;
- std::string config_path;
- std::string mods_path;
- std::string saves_path;
- std::string screenshots_path;
- std::string controls_path;
- std::string data_package_path;
+ // Parallel processes
+ std::unordered_map> processes;
- // Configuration
- json* config;
+ /// Interface for window management and input events
+ application* app;
+
+ // Controls
+ input::event_router* input_event_router;
+ input::mapper* input_mapper;
+ input::listener* input_listener;
+ std::unordered_map controls;
+ bool mouse_look;
+
+ /// Game loop
+ game::loop loop;
+
+ // Paths
+ std::filesystem::path data_path;
+ std::filesystem::path config_path;
+ std::filesystem::path mods_path;
+ std::filesystem::path saves_path;
+ std::filesystem::path screenshots_path;
+ std::filesystem::path controls_path;
+ std::filesystem::path data_package_path;
// Resources
resource_manager* resource_manager;
+ // Configuration
+ json* config;
+
// Localization
std::string language_code;
int language_count;
@@ -252,14 +272,6 @@ struct context
animation* credits_scroll_animation;
animation* menu_bg_fade_in_animation;
animation* menu_bg_fade_out_animation;
-
-
- // Controls
- input::event_router* input_event_router;
- input::mapper* input_mapper;
- input::listener* input_listener;
- std::unordered_map controls;
- bool mouse_look;
// Sound
float master_volume;
@@ -268,9 +280,6 @@ struct context
bool mono_audio;
bool captions;
float captions_size;
-
- // Parallel processes
- std::unordered_map> processes;
// Entities
entity::registry* entity_registry;
@@ -295,15 +304,6 @@ struct context
entity::system::astronomy* astronomy_system;
entity::system::orbit* orbit_system;
entity::system::proteome* proteome_system;
-
- // State management
- std::optional paused_state;
-
- // Misc
- std::queue> function_queue;
-
- // Debug
- debug::cli* cli;
};
} // namespace game
diff --git a/src/game/controls.cpp b/src/game/controls.cpp
index b9475e1..8db64ac 100644
--- a/src/game/controls.cpp
+++ b/src/game/controls.cpp
@@ -25,9 +25,9 @@
namespace game {
-std::string gamepad_calibration_path(const game::context* ctx, const input::gamepad* gamepad)
+std::filesystem::path gamepad_calibration_path(const game::context& ctx, const input::gamepad& gamepad)
{
- return "gamepad-" + gamepad->get_guid() + ".json";
+ return std::filesystem::path("gamepad-" + gamepad.get_guid() + ".json");
}
json default_control_profile()
@@ -65,25 +65,25 @@ json default_gamepad_calibration()
return calibration;
}
-json* load_gamepad_calibration(game::context* ctx, input::gamepad* gamepad)
+json* load_gamepad_calibration(game::context& ctx, const input::gamepad& gamepad)
{
// Determine path to gamepad calibration file
- std::string filepath = gamepad_calibration_path(ctx, gamepad);
+ std::filesystem::path path = gamepad_calibration_path(ctx, gamepad);
// Load gamepad calibration file
- json* calibration = ctx->resource_manager->load(filepath);
+ json* calibration = ctx.resource_manager->load(path.string());
return calibration;
}
-bool save_gamepad_calibration(const game::context* ctx, const input::gamepad* gamepad, const json& calibration)
+bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& gamepad, const json& calibration)
{
- // Determine path to gamepad calibration file
- std::string filepath = ctx->controls_path + gamepad_calibration_path(ctx, gamepad);
+ // Determine absolute path to gamepad calibration file
+ std::filesystem::path path = ctx.controls_path / gamepad_calibration_path(ctx, gamepad);
// Open calibration file
std::ofstream stream;
- stream.open(filepath);
+ stream.open(path);
if (!stream)
return false;
@@ -101,7 +101,7 @@ bool save_gamepad_calibration(const game::context* ctx, const input::gamepad* ga
return true;
}
-void apply_control_profile(game::context* ctx, const json& profile)
+void apply_control_profile(game::context& ctx, const json& profile)
{
// Map gamepad buttons to strings
const std::unordered_map gamepad_button_map =
@@ -135,14 +135,14 @@ void apply_control_profile(game::context* ctx, const json& profile)
};
// Remove all existing input mappings
- for (auto control = ctx->controls.begin(); control != ctx->controls.end(); ++control)
+ for (auto control = ctx.controls.begin(); control != ctx.controls.end(); ++control)
{
- ctx->input_event_router->remove_mappings(control->second);
+ ctx.input_event_router->remove_mappings(control->second);
}
// Get keyboard and mouse devices
- input::keyboard* keyboard = ctx->app->get_keyboard();
- input::mouse* mouse = ctx->app->get_mouse();
+ input::keyboard* keyboard = ctx.app->get_keyboard();
+ input::mouse* mouse = ctx.app->get_mouse();
// Find profile gamepad device
input::gamepad* gamepad = nullptr;
@@ -153,7 +153,7 @@ void apply_control_profile(game::context* ctx, const json& profile)
const std::string gamepad_guid = gamepad_element->get();
// Find gamepad with matching GUID
- for (input::gamepad* device: ctx->app->get_gamepads())
+ for (input::gamepad* device: ctx.app->get_gamepads())
{
if (device->get_guid() == gamepad_guid)
{
@@ -175,14 +175,14 @@ void apply_control_profile(game::context* ctx, const json& profile)
// Find or create control
input::control* control;
- if (ctx->controls.count(control_name))
+ if (ctx.controls.count(control_name))
{
- control = ctx->controls[control_name];
+ control = ctx.controls[control_name];
}
else
{
control = new input::control();
- ctx->controls[control_name] = control;
+ ctx.controls[control_name] = control;
}
// For each mapping in the control
@@ -190,7 +190,7 @@ void apply_control_profile(game::context* ctx, const json& profile)
{
if (!mapping_element->contains("device"))
{
- ctx->logger->warning("Control \"" + control_name + "\" not mapped to a device");
+ ctx.logger->warning("Control \"" + control_name + "\" not mapped to a device");
continue;
}
@@ -202,7 +202,7 @@ void apply_control_profile(game::context* ctx, const json& profile)
// Parse key name
if (!mapping_element->contains("key"))
{
- ctx->logger->warning("Control \"" + control_name + "\" has invalid keyboard mapping");
+ ctx.logger->warning("Control \"" + control_name + "\" has invalid keyboard mapping");
continue;
}
std::string key = (*mapping_element)["key"].get();
@@ -211,14 +211,14 @@ void apply_control_profile(game::context* ctx, const json& profile)
input::scancode scancode = keyboard->get_scancode_from_name(key.c_str());
if (scancode == input::scancode::unknown)
{
- ctx->logger->warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" mapped to unknown keyboard key \"" + key + "\"");
continue;
}
// Map control to keyboard key
- ctx->input_event_router->add_mapping(input::key_mapping(control, keyboard, scancode));
+ ctx.input_event_router->add_mapping(input::key_mapping(control, keyboard, scancode));
- ctx->logger->log("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\"");
+ ctx.logger->log("Mapped control \"" + control_name + "\" to keyboard key \"" + key + "\"");
}
else if (device == "mouse")
{
@@ -228,9 +228,9 @@ void apply_control_profile(game::context* ctx, const json& profile)
int button = (*mapping_element)["button"].get();
// Map control to mouse button
- ctx->input_event_router->add_mapping(input::mouse_button_mapping(control, mouse, button));
+ ctx.input_event_router->add_mapping(input::mouse_button_mapping(control, mouse, button));
- ctx->logger->log("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button));
+ ctx.logger->log("Mapped control \"" + control_name + "\" to mouse button " + std::to_string(button));
}
else if (mapping_element->contains("wheel"))
{
@@ -247,14 +247,14 @@ void apply_control_profile(game::context* ctx, const json& profile)
axis = input::mouse_wheel_axis::negative_y;
else
{
- ctx->logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse wheel axis \"" + wheel + "\"");
continue;
}
// Map control to mouse wheel axis
- ctx->input_event_router->add_mapping(input::mouse_wheel_mapping(control, mouse, axis));
+ ctx.input_event_router->add_mapping(input::mouse_wheel_mapping(control, mouse, axis));
- ctx->logger->log("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel);
+ ctx.logger->log("Mapped control \"" + control_name + "\" to mouse wheel axis " + wheel);
}
else if (mapping_element->contains("motion"))
{
@@ -270,18 +270,18 @@ void apply_control_profile(game::context* ctx, const json& profile)
axis = input::mouse_motion_axis::negative_y;
else
{
- ctx->logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid mouse motion axis \"" + motion + "\"");
continue;
}
// Map control to mouse motion axis
- ctx->input_event_router->add_mapping(input::mouse_motion_mapping(control, mouse, axis));
+ ctx.input_event_router->add_mapping(input::mouse_motion_mapping(control, mouse, axis));
- ctx->logger->log("Mapped control \"" + control_name + "\" to mouse motion axis " + motion);
+ ctx.logger->log("Mapped control \"" + control_name + "\" to mouse motion axis " + motion);
}
else
{
- ctx->logger->warning("Control \"" + control_name + "\" has invalid mouse mapping");
+ ctx.logger->warning("Control \"" + control_name + "\" has invalid mouse mapping");
continue;
}
}
@@ -295,14 +295,14 @@ void apply_control_profile(game::context* ctx, const json& profile)
auto button_it = gamepad_button_map.find(button);
if (button_it == gamepad_button_map.end())
{
- ctx->logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad button \"" + button + "\"");
continue;
}
// Map control to gamepad button
- ctx->input_event_router->add_mapping(input::gamepad_button_mapping(control, gamepad, button_it->second));
+ ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, gamepad, button_it->second));
- ctx->logger->log("Mapped control \"" + control_name + "\" to gamepad button " + button);
+ ctx.logger->log("Mapped control \"" + control_name + "\" to gamepad button " + button);
}
else if (mapping_element->contains("axis"))
{
@@ -313,7 +313,7 @@ void apply_control_profile(game::context* ctx, const json& profile)
auto axis_it = gamepad_axis_map.find(axis_name);
if (axis_it == gamepad_axis_map.end())
{
- ctx->logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" is mapped to invalid gamepad axis \"" + axis_name + "\"");
continue;
}
@@ -321,38 +321,38 @@ void apply_control_profile(game::context* ctx, const json& profile)
const char axis_sign = axis.back();
if (axis_sign != '-' && axis_sign != '+')
{
- ctx->logger->warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" is mapped to gamepad axis with invalid sign \"" + axis_sign + "\"");
continue;
}
bool axis_negative = (axis_sign == '-');
// Map control to gamepad axis
- ctx->input_event_router->add_mapping(input::gamepad_axis_mapping(control, gamepad, axis_it->second, axis_negative));
+ ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, gamepad, axis_it->second, axis_negative));
- ctx->logger->log("Mapped control \"" + control_name + "\" to gamepad axis " + axis);
+ ctx.logger->log("Mapped control \"" + control_name + "\" to gamepad axis " + axis);
}
else
{
- ctx->logger->log("Control \"" + control_name + "\" has invalid gamepad mapping");
+ ctx.logger->log("Control \"" + control_name + "\" has invalid gamepad mapping");
continue;
}
}
else
{
- ctx->logger->warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\"");
+ ctx.logger->warning("Control \"" + control_name + "\" bound to unknown device \"" + device + "\"");
}
}
}
}
}
-void save_control_profile(game::context* ctx)
+void save_control_profile(game::context& ctx)
{
- std::string control_profile_path;
- if (ctx->config->contains("control_profile"))
- control_profile_path = ctx->config_path + "controls/" + (*ctx->config)["control_profile"].get();
+ std::filesystem::path path;
+ if (ctx.config->contains("control_profile"))
+ path = ctx.config_path / "controls" / (*ctx.config)["control_profile"].get();
- ctx->logger->push_task("Saving control profile to \"" + control_profile_path + "\"");
+ ctx.logger->push_task("Saving control profile to \"" + path.string() + "\"");
try
{
json control_profile;
@@ -361,8 +361,8 @@ void save_control_profile(game::context* ctx)
auto& controls_element = control_profile["controls"];
controls_element = json::object();
- for (auto controls_it = ctx->controls.begin(); controls_it != ctx->controls.end(); ++controls_it)
- {
+ for (auto controls_it = ctx.controls.begin(); controls_it != ctx.controls.end(); ++controls_it)
+ {
const std::string& control_name = controls_it->first;
input::control* control = controls_it->second;
@@ -370,8 +370,14 @@ void save_control_profile(game::context* ctx)
auto& control_element = controls_element[control_name];
control_element = json::array();
- // Add control mappings
- auto mappings = ctx->input_event_router->get_mappings(control);
+ // Get control mappings
+ auto mappings = ctx.input_event_router->get_mappings(control);
+
+ // Skip unmapped controls
+ if (!mappings)
+ continue;
+
+ // Add control mapping elements
for (input::mapping* mapping: *mappings)
{
json mapping_element;
@@ -454,7 +460,7 @@ void save_control_profile(game::context* ctx)
if (axis_mapping->negative)
mapping_element["axis"] = "leftx-";
else
- mapping_element["axis+"] = "leftx+";
+ mapping_element["axis"] = "leftx+";
break;
case input::gamepad_axis::left_y:
if (axis_mapping->negative)
@@ -466,7 +472,7 @@ void save_control_profile(game::context* ctx)
if (axis_mapping->negative)
mapping_element["axis"] = "rightx-";
else
- mapping_element["axis+"] = "rightx+";
+ mapping_element["axis"] = "rightx+";
break;
case input::gamepad_axis::right_y:
if (axis_mapping->negative)
@@ -552,65 +558,65 @@ void save_control_profile(game::context* ctx)
}
}
- std::ofstream control_profile_file(control_profile_path);
+ std::ofstream control_profile_file(path);
control_profile_file << control_profile;
}
catch (...)
{
- ctx->logger->pop_task(EXIT_FAILURE);
+ ctx.logger->pop_task(EXIT_FAILURE);
}
- ctx->logger->pop_task(EXIT_SUCCESS);
+ ctx.logger->pop_task(EXIT_SUCCESS);
}
-void apply_gamepad_calibration(input::gamepad* gamepad, const json& calibration)
+void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration)
{
// Parse and apply activation thresholds
if (calibration.contains("leftx_activation"))
{
float min = calibration["leftx_activation"][0].get();
float max = calibration["leftx_activation"][1].get();
- gamepad->set_activation_threshold(input::gamepad_axis::left_x, min, max);
+ gamepad.set_activation_threshold(input::gamepad_axis::left_x, min, max);
}
if (calibration.contains("lefty_activation"))
{
float min = calibration["lefty_activation"][0].get();
float max = calibration["lefty_activation"][1].get();
- gamepad->set_activation_threshold(input::gamepad_axis::left_y, min, max);
+ gamepad.set_activation_threshold(input::gamepad_axis::left_y, min, max);
}
if (calibration.contains("rightx_activation"))
{
float min = calibration["rightx_activation"][0].get();
float max = calibration["rightx_activation"][1].get();
- gamepad->set_activation_threshold(input::gamepad_axis::right_x, min, max);
+ gamepad.set_activation_threshold(input::gamepad_axis::right_x, min, max);
}
if (calibration.contains("righty_activation"))
{
float min = calibration["righty_activation"][0].get();
float max = calibration["righty_activation"][1].get();
- gamepad->set_activation_threshold(input::gamepad_axis::right_y, min, max);
+ gamepad.set_activation_threshold(input::gamepad_axis::right_y, min, max);
}
if (calibration.contains("lefttrigger_activation"))
{
float min = calibration["lefttrigger_activation"][0].get();
float max = calibration["lefttrigger_activation"][1].get();
- gamepad->set_activation_threshold(input::gamepad_axis::left_trigger, min, max);
+ gamepad.set_activation_threshold(input::gamepad_axis::left_trigger, min, max);
}
if (calibration.contains("righttrigger_activation"))
{
float min = calibration["righttrigger_activation"][0].get();
float max = calibration["righttrigger_activation"][1].get();
- gamepad->set_activation_threshold(input::gamepad_axis::right_trigger, min, max);
+ gamepad.set_activation_threshold(input::gamepad_axis::right_trigger, min, max);
}
// Parse and apply deadzone shapes
if (calibration.contains("left_deadzone_cross"))
- gamepad->set_left_deadzone_cross(calibration["left_deadzone_cross"].get());
+ gamepad.set_left_deadzone_cross(calibration["left_deadzone_cross"].get());
if (calibration.contains("right_deadzone_cross"))
- gamepad->set_right_deadzone_cross(calibration["right_deadzone_cross"].get());
+ gamepad.set_right_deadzone_cross(calibration["right_deadzone_cross"].get());
if (calibration.contains("left_deadzone_roundness"))
- gamepad->set_left_deadzone_roundness(calibration["left_deadzone_roundness"].get());
+ gamepad.set_left_deadzone_roundness(calibration["left_deadzone_roundness"].get());
if (calibration.contains("right_deadzone_roundness"))
- gamepad->set_right_deadzone_roundness(calibration["right_deadzone_roundness"].get());
+ gamepad.set_right_deadzone_roundness(calibration["right_deadzone_roundness"].get());
auto parse_response_curve = [](const std::string& curve) -> input::gamepad_response_curve
{
@@ -625,32 +631,32 @@ void apply_gamepad_calibration(input::gamepad* gamepad, const json& calibration)
if (calibration.contains("leftx_response_curve"))
{
auto curve = parse_response_curve(calibration["leftx_response_curve"].get());
- gamepad->set_response_curve(input::gamepad_axis::left_x, curve);
+ gamepad.set_response_curve(input::gamepad_axis::left_x, curve);
}
if (calibration.contains("lefty_response_curve"))
{
auto curve = parse_response_curve(calibration["lefty_response_curve"].get());
- gamepad->set_response_curve(input::gamepad_axis::left_y, curve);
+ gamepad.set_response_curve(input::gamepad_axis::left_y, curve);
}
if (calibration.contains("rightx_response_curve"))
{
auto curve = parse_response_curve(calibration["rightx_response_curve"].get());
- gamepad->set_response_curve(input::gamepad_axis::right_x, curve);
+ gamepad.set_response_curve(input::gamepad_axis::right_x, curve);
}
if (calibration.contains("righty_response_curve"))
{
auto curve = parse_response_curve(calibration["righty_response_curve"].get());
- gamepad->set_response_curve(input::gamepad_axis::right_y, curve);
+ gamepad.set_response_curve(input::gamepad_axis::right_y, curve);
}
if (calibration.contains("lefttrigger_response_curve"))
{
auto curve = parse_response_curve(calibration["lefttrigger_response_curve"].get());
- gamepad->set_response_curve(input::gamepad_axis::left_trigger, curve);
+ gamepad.set_response_curve(input::gamepad_axis::left_trigger, curve);
}
if (calibration.contains("righttrigger_response_curve"))
{
auto curve = parse_response_curve(calibration["righttrigger_response_curve"].get());
- gamepad->set_response_curve(input::gamepad_axis::right_trigger, curve);
+ gamepad.set_response_curve(input::gamepad_axis::right_trigger, curve);
}
}
diff --git a/src/game/controls.hpp b/src/game/controls.hpp
index cd17c1d..37f817c 100644
--- a/src/game/controls.hpp
+++ b/src/game/controls.hpp
@@ -23,6 +23,7 @@
#include "game/context.hpp"
#include "resources/json.hpp"
#include "input/gamepad.hpp"
+#include
namespace game {
@@ -32,14 +33,14 @@ namespace game {
* @param ctx Game context.
* @param profile Control profile.
*/
-void apply_control_profile(game::context* ctx, const json& profile);
+void apply_control_profile(game::context& ctx, const json& profile);
/**
* Saves the current control profile.
*
* @param ctx Game context.
*/
-void save_control_profile(game::context* ctx);
+void save_control_profile(game::context& ctx);
/**
* Generates a default control profile.
@@ -51,7 +52,7 @@ json default_control_profile();
/**
* Returns a string containing the path to the gamepad calibration file.
*/
-std::string gamepad_calibration_path(const game::context* ctx, const input::gamepad* gamepad);
+std::filesystem::path gamepad_calibration_path(const game::context& ctx, const input::gamepad& gamepad);
/**
* Generates default gamepad calibration settings.
@@ -67,7 +68,7 @@ json default_gamepad_calibration();
* @param gamepad Gamepad for which to load calibration settings.
* @return Gamepad calibration settings, or `nullptr` if not loaded.
*/
-json* load_gamepad_calibration(game::context* ctx, input::gamepad* gamepad);
+json* load_gamepad_calibration(game::context& ctx, const input::gamepad& gamepad);
/**
* Saves gamepad calibration settings.
@@ -76,7 +77,7 @@ json* load_gamepad_calibration(game::context* ctx, input::gamepad* gamepad);
* @param gamepad Gamepad for which to save calibration settings.
* @return `true` if calibration settings were successfully saved, `false` otherwise.
*/
-bool save_gamepad_calibration(const game::context* ctx, const input::gamepad* gamepad, const json& calibration);
+bool save_gamepad_calibration(const game::context& ctx, const input::gamepad& gamepad, const json& calibration);
/**
* Applies gamepad calibration settings.
@@ -84,7 +85,7 @@ bool save_gamepad_calibration(const game::context* ctx, const input::gamepad* ga
* @param gamepad Gamepad to calibrate.
* @param calibration JSON element containing gamepad calibration settings.
*/
-void apply_gamepad_calibration(input::gamepad* gamepad, const json& calibration);
+void apply_gamepad_calibration(input::gamepad& gamepad, const json& calibration);
} // namespace game
diff --git a/src/game/fonts.cpp b/src/game/fonts.cpp
index ad60479..a25a490 100644
--- a/src/game/fonts.cpp
+++ b/src/game/fonts.cpp
@@ -66,16 +66,16 @@ static void build_bitmap_font(const type::typeface& typeface, float size, const
material.set_shader_program(shader_program);
}
-void load_fonts(game::context* ctx)
+void load_fonts(game::context& ctx)
{
// Load dyslexia-friendly typeface (if enabled)
bool dyslexia_font_loaded = false;
- if (ctx->dyslexia_font)
+ if (ctx.dyslexia_font)
{
- if (auto it = ctx->strings->find("font_dyslexia"); it != ctx->strings->end() && !it->second.empty() && it->second[0] != '#')
+ if (auto it = ctx.strings->find("font_dyslexia"); it != ctx.strings->end() && !it->second.empty() && it->second[0] != '#')
{
- ctx->logger->log(it->second);
- ctx->typefaces["dyslexia"] = ctx->resource_manager->load(it->second);
+ ctx.logger->log(it->second);
+ ctx.typefaces["dyslexia"] = ctx.resource_manager->load(it->second);
dyslexia_font_loaded = true;
}
}
@@ -84,19 +84,19 @@ void load_fonts(game::context* ctx)
if (dyslexia_font_loaded)
{
// Override standard typefaces with dyslexia-friendly typeface
- ctx->typefaces["serif"] = ctx->typefaces["dyslexia"];
- ctx->typefaces["sans_serif"] = ctx->typefaces["dyslexia"];
- ctx->typefaces["monospace"] = ctx->typefaces["dyslexia"];
+ ctx.typefaces["serif"] = ctx.typefaces["dyslexia"];
+ ctx.typefaces["sans_serif"] = ctx.typefaces["dyslexia"];
+ ctx.typefaces["monospace"] = ctx.typefaces["dyslexia"];
}
else
{
// Load standard typefaces
- if (auto it = ctx->strings->find("font_serif"); it != ctx->strings->end())
- ctx->typefaces["serif"] = ctx->resource_manager->load(it->second);
- if (auto it = ctx->strings->find("font_sans_serif"); it != ctx->strings->end())
- ctx->typefaces["sans_serif"] = ctx->resource_manager->load(it->second);
- if (auto it = ctx->strings->find("font_monospace"); it != ctx->strings->end())
- ctx->typefaces["monospace"] = ctx->resource_manager->load(it->second);
+ if (auto it = ctx.strings->find("font_serif"); it != ctx.strings->end())
+ ctx.typefaces["serif"] = ctx.resource_manager->load(it->second);
+ if (auto it = ctx.strings->find("font_sans_serif"); it != ctx.strings->end())
+ ctx.typefaces["sans_serif"] = ctx.resource_manager->load(it->second);
+ if (auto it = ctx.strings->find("font_monospace"); it != ctx.strings->end())
+ ctx.typefaces["monospace"] = ctx.resource_manager->load(it->second);
}
// Build character set
@@ -107,7 +107,7 @@ 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)
+ for (auto it = ctx.strings->begin(); it != ctx.strings->end(); ++it)
{
// Convert UTF-8 string to UTF-32
std::wstring_convert, char32_t> convert;
@@ -120,49 +120,49 @@ void load_fonts(game::context* ctx)
}
// Load bitmap font shader
- gl::shader_program* bitmap_font_shader = ctx->resource_manager->load("bitmap-font.glsl");
+ gl::shader_program* bitmap_font_shader = ctx.resource_manager->load("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();
- if (ctx->config->contains("menu_font_size"))
- menu_font_size_pt = (*ctx->config)["menu_font_size"].get();
- if (ctx->config->contains("title_font_size"))
- title_font_size_pt = (*ctx->config)["title_font_size"].get();
+ if (ctx.config->contains("debug_font_size"))
+ debug_font_size_pt = (*ctx.config)["debug_font_size"].get();
+ if (ctx.config->contains("menu_font_size"))
+ menu_font_size_pt = (*ctx.config)["menu_font_size"].get();
+ if (ctx.config->contains("title_font_size"))
+ title_font_size_pt = (*ctx.config)["title_font_size"].get();
// Scale font point sizes
- const float font_size = (*ctx->config)["font_size"].get();
+ const float font_size = (*ctx.config)["font_size"].get();
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 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;
- ctx->logger->log("font size: " + std::to_string(menu_font_size_px));
+ ctx.logger->log("font size: " + std::to_string(menu_font_size_px));
// Build debug font
- if (auto it = ctx->typefaces.find("monospace"); it != ctx->typefaces.end())
+ 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, debug_font_size_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())
+ 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, menu_font_size_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())
+ 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, title_font_size_px, charset, ctx.title_font, ctx.title_font_material, bitmap_font_shader);
}
}
diff --git a/src/game/fonts.hpp b/src/game/fonts.hpp
index 8fabdfb..723f0f2 100644
--- a/src/game/fonts.hpp
+++ b/src/game/fonts.hpp
@@ -24,7 +24,7 @@
namespace game {
-void load_fonts(game::context* ctx);
+void load_fonts(game::context& ctx);
} // namespace game
diff --git a/src/game/graphics.cpp b/src/game/graphics.cpp
index 21c2af4..43606ae 100644
--- a/src/game/graphics.cpp
+++ b/src/game/graphics.cpp
@@ -23,6 +23,11 @@
#include "gl/texture-wrapping.hpp"
#include "gl/texture-filter.hpp"
#include "debug/logger.hpp"
+#include "utility/timestamp.hpp"
+#include
+#include
+#include
+#include
namespace game {
namespace graphics {
@@ -141,5 +146,37 @@ void resize_framebuffer_attachment(gl::texture_2d& texture, const int2& resoluti
texture.resize(resolution.x, resolution.y, texture.get_pixel_type(), texture.get_pixel_format(), texture.get_color_space(), nullptr);
}
+void save_screenshot(game::context& ctx)
+{
+ // Determine screenshot path
+ std::string filename = "antkeeper-" + timestamp() + ".png";
+ std::filesystem::path path = ctx.config_path / "gallery" / filename;
+
+ ctx.logger->push_task("Saving screenshot to \"" + path.string() + "\"");
+
+ const int2& viewport_dimensions = ctx.app->get_viewport_dimensions();
+
+ // Allocate image
+ std::shared_ptr frame = std::make_shared();
+ frame->format(1, 3);
+ frame->resize(viewport_dimensions.x, viewport_dimensions.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->get_pixels());
+
+ // Write screenshot file in separate thread
+ std::thread
+ (
+ [frame, path]
+ {
+ stbi_flip_vertically_on_write(1);
+ stbi_write_png(path.string().c_str(), frame->get_width(), frame->get_height(), frame->get_channel_count(), frame->get_pixels(), frame->get_width() * frame->get_channel_count());
+ }
+ ).detach();
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
} // namespace graphics
} // namespace game
diff --git a/src/game/graphics.hpp b/src/game/graphics.hpp
index acce8bb..563be3b 100644
--- a/src/game/graphics.hpp
+++ b/src/game/graphics.hpp
@@ -28,6 +28,7 @@ namespace graphics {
void create_framebuffers(game::context& ctx);
void destroy_framebuffers(game::context& ctx);
void change_render_resolution(game::context& ctx, float scale);
+void save_screenshot(game::context& ctx);
} // namespace graphics
} // namespace game
diff --git a/src/animation/frame-scheduler.cpp b/src/game/loop.cpp
similarity index 76%
rename from src/animation/frame-scheduler.cpp
rename to src/game/loop.cpp
index 7894b3e..0816a5f 100644
--- a/src/animation/frame-scheduler.cpp
+++ b/src/game/loop.cpp
@@ -17,10 +17,12 @@
* along with Antkeeper source code. If not, see .
*/
-#include "frame-scheduler.hpp"
+#include "game/loop.hpp"
#include
-frame_scheduler::frame_scheduler():
+namespace game {
+
+loop::loop():
update_callback([](double, double){}),
render_callback([](double){}),
update_rate(60.0),
@@ -30,33 +32,33 @@ frame_scheduler::frame_scheduler():
reset();
}
-void frame_scheduler::set_update_callback(std::function callback)
+void loop::set_update_callback(std::function callback)
{
update_callback = callback;
}
-void frame_scheduler::set_render_callback(std::function callback)
+void loop::set_render_callback(std::function callback)
{
render_callback = callback;
}
-void frame_scheduler::set_update_rate(double frequency)
+void loop::set_update_rate(double frequency)
{
update_rate = frequency;
update_timestep = 1.0 / frequency;
}
-void frame_scheduler::set_max_frame_duration(double duration)
+void loop::set_max_frame_duration(double duration)
{
max_frame_duration = duration;
}
-double frame_scheduler::get_frame_duration() const
+double loop::get_frame_duration() const
{
return frame_duration;
}
-void frame_scheduler::reset()
+void loop::reset()
{
elapsed_time = 0.0;
accumulator = 0.0;
@@ -65,10 +67,10 @@ void frame_scheduler::reset()
frame_duration = 0.0;
}
-void frame_scheduler::tick()
+void loop::tick()
{
frame_end = std::chrono::high_resolution_clock::now();
- frame_duration = static_cast(std::chrono::duration_cast(frame_end - frame_start).count()) / 1000000.0;
+ frame_duration = static_cast(std::chrono::duration_cast(frame_end - frame_start).count()) / 1000000000.0;
frame_start = frame_end;
accumulator += std::min(max_frame_duration, frame_duration);
@@ -82,3 +84,5 @@ void frame_scheduler::tick()
render_callback(accumulator * update_rate);
}
+
+} // namespace game
diff --git a/src/animation/frame-scheduler.hpp b/src/game/loop.hpp
similarity index 90%
rename from src/animation/frame-scheduler.hpp
rename to src/game/loop.hpp
index 82d679b..1d41dc8 100644
--- a/src/animation/frame-scheduler.hpp
+++ b/src/game/loop.hpp
@@ -17,26 +17,28 @@
* along with Antkeeper source code. If not, see .
*/
-#ifndef ANTKEEPER_FRAME_SCHEDULER_HPP
-#define ANTKEEPER_FRAME_SCHEDULER_HPP
+#ifndef ANTKEEPER_GAME_LOOP_HPP
+#define ANTKEEPER_GAME_LOOP_HPP
#include
#include
+namespace game {
+
/**
- * Schedules fixed-timestep update calls and variable timestep render calls.
+ * Game loop with fixed timestep update calls and variable timestep render calls.
*
* @see https://gafferongames.com/post/fix_your_timestep/
*/
-class frame_scheduler
+class loop
{
public:
- frame_scheduler();
+ loop();
/**
* Sets the update callback.
*
- * @param callback Function which takes two parameters: `t`, the total elapsed time, and `dt`, delta time (timestep) which is calculated as `1.0 / update_rate`. This function will be called at the frequency specified by `frame_scheduler::set_update_rate()`.
+ * @param callback Function which takes two parameters: `t`, the total elapsed time, and `dt`, delta time (timestep) which is calculated as `1.0 / update_rate`. This function will be called at the frequency specified by `loop::set_update_rate()`.
*/
void set_update_callback(std::function callback);
@@ -89,5 +91,6 @@ private:
double frame_duration;
};
-#endif // ANTKEEPER_FRAME_SCHEDULER_HPP
+} // namespace game
+#endif // ANTKEEPER_GAME_LOOP_HPP
diff --git a/src/game/menu.cpp b/src/game/menu.cpp
index 3206a3d..c08c836 100644
--- a/src/game/menu.cpp
+++ b/src/game/menu.cpp
@@ -28,41 +28,41 @@
namespace game {
namespace menu {
-void init_menu_item_index(game::context* ctx, const std::string& menu_name)
+void init_menu_item_index(game::context& ctx, const std::string& menu_name)
{
- if (auto it = ctx->menu_item_indices.find(menu_name); it != ctx->menu_item_indices.end())
+ if (auto it = ctx.menu_item_indices.find(menu_name); it != ctx.menu_item_indices.end())
{
- ctx->menu_item_index = &it->second;
+ ctx.menu_item_index = &it->second;
}
else
{
- ctx->menu_item_index = &ctx->menu_item_indices[menu_name];
- *ctx->menu_item_index = 0;
+ ctx.menu_item_index = &ctx.menu_item_indices[menu_name];
+ *ctx.menu_item_index = 0;
}
}
-void update_text_font(game::context* ctx)
+void update_text_font(game::context& ctx)
{
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
- name->set_material(&ctx->menu_font_material);
- name->set_font(&ctx->menu_font);
+ name->set_material(&ctx.menu_font_material);
+ name->set_font(&ctx.menu_font);
if (value)
{
- value->set_material(&ctx->menu_font_material);
- value->set_font(&ctx->menu_font);
+ value->set_material(&ctx.menu_font_material);
+ value->set_font(&ctx.menu_font);
}
}
}
-void update_text_color(game::context* ctx)
+void update_text_color(game::context& ctx)
{
- for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i)
+ for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
- auto [name, value] = ctx->menu_item_texts[i];
+ auto [name, value] = ctx.menu_item_texts[i];
- const float4& color = (i == *ctx->menu_item_index) ? active_color : inactive_color;
+ const float4& color = (i == *ctx.menu_item_index) ? active_color : inactive_color;
name->set_color(color);
if (value)
@@ -70,9 +70,9 @@ void update_text_color(game::context* ctx)
}
}
-void update_text_tweens(game::context* ctx)
+void update_text_tweens(game::context& ctx)
{
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
name->update_tweens();
if (value)
@@ -80,13 +80,13 @@ void update_text_tweens(game::context* ctx)
}
}
-void align_text(game::context* ctx, bool center, bool has_back, float anchor_y)
+void align_text(game::context& ctx, bool center, bool has_back, float anchor_y)
{
// Calculate menu width
float menu_width = 0.0f;
- float menu_spacing = ctx->menu_font.get_glyph_metrics(U'M').width;
+ float menu_spacing = ctx.menu_font.get_glyph_metrics(U'M').width;
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
float row_width = 0.0f;
@@ -110,23 +110,23 @@ void align_text(game::context* ctx, bool center, bool has_back, float anchor_y)
// Align texts
float menu_height;
if (has_back)
- menu_height = (ctx->menu_item_texts.size() - 1) * ctx->menu_font.get_font_metrics().linespace - ctx->menu_font.get_font_metrics().linegap;
+ menu_height = (ctx.menu_item_texts.size() - 1) * ctx.menu_font.get_font_metrics().linespace - ctx.menu_font.get_font_metrics().linegap;
else
- menu_height = ctx->menu_item_texts.size() * ctx->menu_font.get_font_metrics().linespace - ctx->menu_font.get_font_metrics().linegap;
+ menu_height = ctx.menu_item_texts.size() * ctx.menu_font.get_font_metrics().linespace - ctx.menu_font.get_font_metrics().linegap;
float menu_x = -menu_width * 0.5f;
- float menu_y = anchor_y + menu_height * 0.5f - ctx->menu_font.get_font_metrics().size;
+ float menu_y = anchor_y + menu_height * 0.5f - ctx.menu_font.get_font_metrics().size;
- for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i)
+ for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
- auto [name, value] = ctx->menu_item_texts[i];
+ auto [name, value] = ctx.menu_item_texts[i];
float x = menu_x;
- float y = menu_y - ctx->menu_font.get_font_metrics().linespace * i;
- if (has_back && i == ctx->menu_item_texts.size() - 1)
- y -= ctx->menu_font.get_font_metrics().linespace;
+ float y = menu_y - ctx.menu_font.get_font_metrics().linespace * i;
+ if (has_back && i == ctx.menu_item_texts.size() - 1)
+ y -= ctx.menu_font.get_font_metrics().linespace;
- if (center || i == ctx->menu_item_texts.size() - 1)
+ if (center || i == ctx.menu_item_texts.size() - 1)
{
const auto& name_bounds = static_cast&>(name->get_local_bounds());
const float name_width = name_bounds.max_point.x - name_bounds.min_point.x;
@@ -140,7 +140,7 @@ void align_text(game::context* ctx, bool center, bool has_back, float anchor_y)
const auto& value_bounds = static_cast&>(value->get_local_bounds());
const float value_width = value_bounds.max_point.x - value_bounds.min_point.x;
- if (center || i == ctx->menu_item_texts.size() - 1)
+ if (center || i == ctx.menu_item_texts.size() - 1)
x = -value_width * 0.5f;
else
x = menu_x + menu_width - value_width;
@@ -150,9 +150,9 @@ void align_text(game::context* ctx, bool center, bool has_back, float anchor_y)
}
}
-void refresh_text(game::context* ctx)
+void refresh_text(game::context& ctx)
{
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
name->refresh();
if (value)
@@ -160,67 +160,67 @@ void refresh_text(game::context* ctx)
}
}
-void add_text_to_ui(game::context* ctx)
+void add_text_to_ui(game::context& ctx)
{
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
- ctx->ui_scene->add_object(name);
+ ctx.ui_scene->add_object(name);
if (value)
- ctx->ui_scene->add_object(value);
+ ctx.ui_scene->add_object(value);
}
}
-void remove_text_from_ui(game::context* ctx)
+void remove_text_from_ui(game::context& ctx)
{
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
- ctx->ui_scene->remove_object(name);
+ ctx.ui_scene->remove_object(name);
if (value)
- ctx->ui_scene->remove_object(value);
+ ctx.ui_scene->remove_object(value);
}
}
-void delete_text(game::context* ctx)
+void delete_text(game::context& ctx)
{
- for (auto [name, value]: ctx->menu_item_texts)
+ for (auto [name, value]: ctx.menu_item_texts)
{
delete name;
if (value)
delete value;
}
- ctx->menu_item_texts.clear();
+ ctx.menu_item_texts.clear();
}
-void delete_animations(game::context* ctx)
+void delete_animations(game::context& ctx)
{
- ctx->animator->remove_animation(ctx->menu_fade_animation);
- delete ctx->menu_fade_animation;
- ctx->menu_fade_animation = nullptr;
+ ctx.animator->remove_animation(ctx.menu_fade_animation);
+ delete ctx.menu_fade_animation;
+ ctx.menu_fade_animation = nullptr;
}
-void clear_callbacks(game::context* ctx)
+void clear_callbacks(game::context& ctx)
{
// Clear menu item callbacks
- ctx->menu_left_callbacks.clear();
- ctx->menu_right_callbacks.clear();
- ctx->menu_select_callbacks.clear();
- ctx->menu_back_callback = nullptr;
+ ctx.menu_left_callbacks.clear();
+ ctx.menu_right_callbacks.clear();
+ ctx.menu_select_callbacks.clear();
+ ctx.menu_back_callback = nullptr;
}
-void setup_animations(game::context* ctx)
+void setup_animations(game::context& ctx)
{
- ctx->menu_fade_animation = new animation();
- animation_channel* opacity_channel = ctx->menu_fade_animation->add_channel(0);
+ ctx.menu_fade_animation = new animation();
+ animation_channel* opacity_channel = ctx.menu_fade_animation->add_channel(0);
- ctx->menu_fade_animation->set_frame_callback
+ ctx.menu_fade_animation->set_frame_callback
(
- [ctx](int channel, const float& opacity)
+ [&ctx](int channel, const float& opacity)
{
- for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i)
+ for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
- auto [name, value] = ctx->menu_item_texts[i];
+ auto [name, value] = ctx.menu_item_texts[i];
- float4 color = (i == *ctx->menu_item_index) ? active_color : inactive_color;
+ float4 color = (i == *ctx.menu_item_index) ? active_color : inactive_color;
color[3] = color[3] * opacity;
if (name)
@@ -231,23 +231,23 @@ void setup_animations(game::context* ctx)
}
);
- ctx->animator->add_animation(ctx->menu_fade_animation);
+ ctx.animator->add_animation(ctx.menu_fade_animation);
}
-void fade_in(game::context* ctx, const std::function& end_callback)
+void fade_in(game::context& ctx, const std::function& end_callback)
{
- ctx->menu_fade_animation->set_interpolator(ease::out_cubic);
- animation_channel* opacity_channel = ctx->menu_fade_animation->get_channel(0);
+ ctx.menu_fade_animation->set_interpolator(ease::out_cubic);
+ animation_channel* opacity_channel = ctx.menu_fade_animation->get_channel(0);
opacity_channel->remove_keyframes();
opacity_channel->insert_keyframe({0.0, 0.0f});
opacity_channel->insert_keyframe({game::menu::fade_in_duration, 1.0f});
- ctx->menu_fade_animation->set_end_callback(end_callback);
+ ctx.menu_fade_animation->set_end_callback(end_callback);
- for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i)
+ for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
- auto [name, value] = ctx->menu_item_texts[i];
+ auto [name, value] = ctx.menu_item_texts[i];
- float4 color = (i == *ctx->menu_item_index) ? active_color : inactive_color;
+ float4 color = (i == *ctx.menu_item_index) ? active_color : inactive_color;
color[3] = 0.0f;
if (name)
@@ -262,106 +262,106 @@ void fade_in(game::context* ctx, const std::function& end_callback)
}
}
- ctx->menu_fade_animation->stop();
- ctx->menu_fade_animation->play();
+ ctx.menu_fade_animation->stop();
+ ctx.menu_fade_animation->play();
}
-void fade_out(game::context* ctx, const std::function& end_callback)
+void fade_out(game::context& ctx, const std::function& end_callback)
{
- ctx->menu_fade_animation->set_interpolator(ease::out_cubic);
- animation_channel* opacity_channel = ctx->menu_fade_animation->get_channel(0);
+ ctx.menu_fade_animation->set_interpolator(ease::out_cubic);
+ animation_channel* opacity_channel = ctx.menu_fade_animation->get_channel(0);
opacity_channel->remove_keyframes();
opacity_channel->insert_keyframe({0.0, 1.0f});
opacity_channel->insert_keyframe({game::menu::fade_out_duration, 0.0f});
- ctx->menu_fade_animation->set_end_callback(end_callback);
+ ctx.menu_fade_animation->set_end_callback(end_callback);
- ctx->menu_fade_animation->stop();
- ctx->menu_fade_animation->play();
+ ctx.menu_fade_animation->stop();
+ ctx.menu_fade_animation->play();
}
-void fade_in_bg(game::context* ctx)
+void fade_in_bg(game::context& ctx)
{
- ctx->menu_bg_fade_out_animation->stop();
- ctx->menu_bg_fade_in_animation->stop();
- ctx->menu_bg_fade_in_animation->play();
+ ctx.menu_bg_fade_out_animation->stop();
+ ctx.menu_bg_fade_in_animation->stop();
+ ctx.menu_bg_fade_in_animation->play();
}
-void fade_out_bg(game::context* ctx)
+void fade_out_bg(game::context& ctx)
{
- ctx->menu_bg_fade_in_animation->stop();
- ctx->menu_bg_fade_out_animation->stop();
- ctx->menu_bg_fade_out_animation->play();
+ ctx.menu_bg_fade_in_animation->stop();
+ ctx.menu_bg_fade_out_animation->stop();
+ ctx.menu_bg_fade_out_animation->play();
}
-void setup_controls(game::context* ctx)
+void setup_controls(game::context& ctx)
{
- ctx->controls["menu_up"]->set_activated_callback
+ ctx.controls["menu_up"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
- --(*ctx->menu_item_index);
- if (*ctx->menu_item_index < 0)
- *ctx->menu_item_index = ctx->menu_item_texts.size() - 1;
+ --(*ctx.menu_item_index);
+ if (*ctx.menu_item_index < 0)
+ *ctx.menu_item_index = ctx.menu_item_texts.size() - 1;
update_text_color(ctx);
}
);
- ctx->controls["menu_down"]->set_activated_callback
+ ctx.controls["menu_down"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
- ++(*ctx->menu_item_index);
- if (*ctx->menu_item_index >= ctx->menu_item_texts.size())
- *ctx->menu_item_index = 0;
+ ++(*ctx.menu_item_index);
+ if (*ctx.menu_item_index >= ctx.menu_item_texts.size())
+ *ctx.menu_item_index = 0;
update_text_color(ctx);
}
);
- ctx->controls["menu_left"]->set_activated_callback
+ ctx.controls["menu_left"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
- auto callback = ctx->menu_left_callbacks[*ctx->menu_item_index];
+ auto callback = ctx.menu_left_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
);
- ctx->controls["menu_right"]->set_activated_callback
+ ctx.controls["menu_right"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
- auto callback = ctx->menu_right_callbacks[*ctx->menu_item_index];
+ auto callback = ctx.menu_right_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
);
- ctx->controls["menu_select"]->set_activated_callback
+ ctx.controls["menu_select"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
- auto callback = ctx->menu_select_callbacks[*ctx->menu_item_index];
+ auto callback = ctx.menu_select_callbacks[*ctx.menu_item_index];
if (callback != nullptr)
callback();
}
);
- ctx->controls["menu_back"]->set_activated_callback
+ ctx.controls["menu_back"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
- if (ctx->menu_back_callback != nullptr)
- ctx->menu_back_callback();
+ if (ctx.menu_back_callback != nullptr)
+ ctx.menu_back_callback();
}
);
- ctx->menu_mouse_tracker->set_mouse_moved_callback
+ ctx.menu_mouse_tracker->set_mouse_moved_callback
(
- [ctx](const mouse_moved_event& event)
+ [&ctx](const mouse_moved_event& event)
{
- const float padding = game::menu::mouseover_padding * ctx->menu_font.get_font_metrics().size;
+ const float padding = game::menu::mouseover_padding * ctx.menu_font.get_font_metrics().size;
- for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i)
+ for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
- auto [name, value] = ctx->menu_item_texts[i];
+ auto [name, value] = ctx.menu_item_texts[i];
const auto& name_bounds = static_cast&>(name->get_world_bounds());
float min_x = name_bounds.min_point.x;
@@ -377,7 +377,7 @@ void setup_controls(game::context* ctx)
max_y = std::max(max_y, value_bounds.max_point.y);
}
- const auto& viewport = ctx->app->get_viewport_dimensions();
+ const auto& viewport = ctx.app->get_viewport_dimensions();
const float x = static_cast(event.x - viewport[0] / 2);
const float y = static_cast((viewport[1] - event.y + 1) - viewport[1] / 2);
@@ -390,7 +390,7 @@ void setup_controls(game::context* ctx)
{
if (y >= min_y && y <= max_y)
{
- *ctx->menu_item_index = i;
+ *ctx.menu_item_index = i;
update_text_color(ctx);
break;
}
@@ -399,13 +399,13 @@ void setup_controls(game::context* ctx)
}
);
- ctx->menu_mouse_tracker->set_mouse_button_pressed_callback
+ ctx.menu_mouse_tracker->set_mouse_button_pressed_callback
(
- [ctx](const mouse_button_pressed_event& event)
+ [&ctx](const mouse_button_pressed_event& event)
{
- for (std::size_t i = 0; i < ctx->menu_item_texts.size(); ++i)
+ for (std::size_t i = 0; i < ctx.menu_item_texts.size(); ++i)
{
- auto [name, value] = ctx->menu_item_texts[i];
+ auto [name, value] = ctx.menu_item_texts[i];
const auto& name_bounds = static_cast&>(name->get_world_bounds());
float min_x = name_bounds.min_point.x;
@@ -421,7 +421,7 @@ void setup_controls(game::context* ctx)
max_y = std::max(max_y, value_bounds.max_point.y);
}
- const auto& viewport = ctx->app->get_viewport_dimensions();
+ const auto& viewport = ctx.app->get_viewport_dimensions();
const float x = static_cast(event.x - viewport[0] / 2);
const float y = static_cast((viewport[1] - event.y + 1) - viewport[1] / 2);
@@ -429,18 +429,18 @@ void setup_controls(game::context* ctx)
{
if (y >= min_y && y <= max_y)
{
- *ctx->menu_item_index = i;
+ *ctx.menu_item_index = i;
update_text_color(ctx);
if (event.button == 1)
{
- auto callback = ctx->menu_select_callbacks[i];
+ auto callback = ctx.menu_select_callbacks[i];
if (callback)
callback();
}
else if (event.button == 3)
{
- auto callback = ctx->menu_left_callbacks[i];
+ auto callback = ctx.menu_left_callbacks[i];
if (callback)
callback();
}
@@ -453,17 +453,17 @@ void setup_controls(game::context* ctx)
);
}
-void clear_controls(game::context* ctx)
+void clear_controls(game::context& ctx)
{
- ctx->controls["menu_up"]->set_activated_callback(nullptr);
- ctx->controls["menu_down"]->set_activated_callback(nullptr);
- ctx->controls["menu_left"]->set_activated_callback(nullptr);
- ctx->controls["menu_right"]->set_activated_callback(nullptr);
- ctx->controls["menu_select"]->set_activated_callback(nullptr);
- ctx->controls["menu_back"]->set_activated_callback(nullptr);
+ ctx.controls["menu_up"]->set_activated_callback(nullptr);
+ ctx.controls["menu_down"]->set_activated_callback(nullptr);
+ ctx.controls["menu_left"]->set_activated_callback(nullptr);
+ ctx.controls["menu_right"]->set_activated_callback(nullptr);
+ ctx.controls["menu_select"]->set_activated_callback(nullptr);
+ ctx.controls["menu_back"]->set_activated_callback(nullptr);
- ctx->menu_mouse_tracker->set_mouse_moved_callback(nullptr);
- ctx->menu_mouse_tracker->set_mouse_button_pressed_callback(nullptr);
+ ctx.menu_mouse_tracker->set_mouse_moved_callback(nullptr);
+ ctx.menu_mouse_tracker->set_mouse_button_pressed_callback(nullptr);
}
} // namespace menu
diff --git a/src/game/menu.hpp b/src/game/menu.hpp
index 03a5a2b..dbfe553 100644
--- a/src/game/menu.hpp
+++ b/src/game/menu.hpp
@@ -46,28 +46,28 @@ static constexpr float fade_in_duration = 0.25f;
/// Duration of the menu fade out animation
static constexpr float fade_out_duration = 0.125f;
-void init_menu_item_index(game::context* ctx, const std::string& menu_name);
-void setup_controls(game::context* ctx);
-void setup_animations(game::context* ctx);
-
-void clear_controls(game::context* ctx);
-void clear_callbacks(game::context* ctx);
-void remove_text_from_ui(game::context* ctx);
-void delete_text(game::context* ctx);
-void delete_animations(game::context* ctx);
-
-void fade_in(game::context* ctx, const std::function& end_callback);
-void fade_out(game::context* ctx, const std::function& end_callback);
-
-void fade_in_bg(game::context* ctx);
-void fade_out_bg(game::context* ctx);
-
-void update_text_color(game::context* ctx);
-void update_text_font(game::context* ctx);
-void update_text_tweens(game::context* ctx);
-void align_text(game::context* ctx, bool center = false, bool has_back = true, float anchor_y = 0.0f);
-void refresh_text(game::context* ctx);
-void add_text_to_ui(game::context* ctx);
+void init_menu_item_index(game::context& ctx, const std::string& menu_name);
+void setup_controls(game::context& ctx);
+void setup_animations(game::context& ctx);
+
+void clear_controls(game::context& ctx);
+void clear_callbacks(game::context& ctx);
+void remove_text_from_ui(game::context& ctx);
+void delete_text(game::context& ctx);
+void delete_animations(game::context& ctx);
+
+void fade_in(game::context& ctx, const std::function& end_callback);
+void fade_out(game::context& ctx, const std::function& end_callback);
+
+void fade_in_bg(game::context& ctx);
+void fade_out_bg(game::context& ctx);
+
+void update_text_color(game::context& ctx);
+void update_text_font(game::context& ctx);
+void update_text_tweens(game::context& ctx);
+void align_text(game::context& ctx, bool center = false, bool has_back = true, float anchor_y = 0.0f);
+void refresh_text(game::context& ctx);
+void add_text_to_ui(game::context& ctx);
} // namespace menu
} // namespace game
diff --git a/src/game/save.cpp b/src/game/save.cpp
index 6501c1c..a33985c 100644
--- a/src/game/save.cpp
+++ b/src/game/save.cpp
@@ -24,20 +24,20 @@
namespace game {
-void save_config(game::context* ctx)
+void save_config(game::context& ctx)
{
- const std::string config_file_path = ctx->config_path + "config.json";
- ctx->logger->push_task("Saving config to \"" + config_file_path + "\"");
+ std::filesystem::path path = ctx.config_path / "config.json";
+ ctx.logger->push_task("Saving config to \"" + path.string() + "\"");
try
{
- std::ofstream config_file(config_file_path);
- config_file << *(ctx->config);
+ std::ofstream config_file(path);
+ config_file << *(ctx.config);
}
catch (...)
{
- ctx->logger->pop_task(EXIT_FAILURE);
+ ctx.logger->pop_task(EXIT_FAILURE);
}
- ctx->logger->pop_task(EXIT_SUCCESS);
+ ctx.logger->pop_task(EXIT_SUCCESS);
}
} // namespace game
diff --git a/src/game/save.hpp b/src/game/save.hpp
index d67b430..dc60af5 100644
--- a/src/game/save.hpp
+++ b/src/game/save.hpp
@@ -24,7 +24,7 @@
namespace game {
-void save_config(game::context* ctx);
+void save_config(game::context& ctx);
} // namespace game
diff --git a/src/game/states/boot.hpp b/src/game/state/base.cpp
similarity index 74%
rename from src/game/states/boot.hpp
rename to src/game/state/base.cpp
index 038bdbb..d389493 100644
--- a/src/game/states/boot.hpp
+++ b/src/game/state/base.cpp
@@ -17,23 +17,17 @@
* along with Antkeeper source code. If not, see .
*/
-#ifndef ANTKEEPER_GAME_STATE_BOOT_HPP
-#define ANTKEEPER_GAME_STATE_BOOT_HPP
-
-#include "application.hpp"
+#include "game/state/base.hpp"
namespace game {
namespace state {
-/// Boot game state functions.
-namespace boot {
-
-void enter(application* app, int argc, char** argv);
-void exit(application* app);
+base::base(game::context& ctx):
+ ctx(ctx)
+{}
-} // namespace boot
+base::~base()
+{}
} // namespace state
} // namespace game
-
-#endif // ANTKEEPER_GAME_STATE_BOOT_HPP
diff --git a/src/game/state/base.hpp b/src/game/state/base.hpp
new file mode 100644
index 0000000..346b3ec
--- /dev/null
+++ b/src/game/state/base.hpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#ifndef ANTKEEPER_GAME_STATE_BASE_HPP
+#define ANTKEEPER_GAME_STATE_BASE_HPP
+
+namespace game {
+
+struct context;
+
+namespace state {
+
+/**
+ * Abstract base class for game states.
+ */
+class base
+{
+public:
+ /**
+ * Constructs a game state.
+ *
+ * @param ctx Reference to the game context on which this state will operate.
+ */
+ base(game::context& ctx);
+
+ /**
+ * Destructs a game state.
+ */
+ virtual ~base() = 0;
+
+protected:
+ game::context& ctx;
+};
+
+} // namespace state
+} // namespace game
+
+#endif // ANTKEEPER_GAME_STATE_BASE_HPP
diff --git a/src/game/state/boot.cpp b/src/game/state/boot.cpp
new file mode 100644
index 0000000..28a3b0f
--- /dev/null
+++ b/src/game/state/boot.cpp
@@ -0,0 +1,1202 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#include "game/state/boot.hpp"
+#include "animation/animation.hpp"
+#include "animation/animator.hpp"
+#include "animation/ease.hpp"
+#include "animation/screen-transition.hpp"
+#include "animation/timeline.hpp"
+#include "application.hpp"
+#include "debug/cli.hpp"
+#include "debug/console-commands.hpp"
+#include "debug/logger.hpp"
+#include "game/context.hpp"
+#include "gl/framebuffer.hpp"
+#include "gl/pixel-format.hpp"
+#include "gl/pixel-type.hpp"
+#include "gl/rasterizer.hpp"
+#include "gl/texture-2d.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"
+#include "render/material-flags.hpp"
+#include "render/material-property.hpp"
+#include "render/passes/bloom-pass.hpp"
+#include "render/passes/clear-pass.hpp"
+#include "render/passes/final-pass.hpp"
+#include "render/passes/material-pass.hpp"
+#include "render/passes/outline-pass.hpp"
+#include "render/passes/shadow-map-pass.hpp"
+#include "render/passes/sky-pass.hpp"
+#include "render/passes/simple-pass.hpp"
+#include "render/vertex-attribute.hpp"
+#include "render/compositor.hpp"
+#include "render/renderer.hpp"
+#include "resources/resource-manager.hpp"
+#include "resources/file-buffer.hpp"
+#include "scene/scene.hpp"
+#include "game/state/splash.hpp"
+#include "entity/systems/behavior.hpp"
+#include "entity/systems/camera.hpp"
+#include "entity/systems/collision.hpp"
+#include "entity/systems/constraint.hpp"
+#include "entity/systems/locomotion.hpp"
+#include "entity/systems/snapping.hpp"
+#include "entity/systems/render.hpp"
+#include "entity/systems/samara.hpp"
+#include "entity/systems/subterrain.hpp"
+#include "entity/systems/terrain.hpp"
+#include "entity/systems/vegetation.hpp"
+#include "entity/systems/spatial.hpp"
+#include "entity/systems/painting.hpp"
+#include "entity/systems/astronomy.hpp"
+#include "entity/systems/blackbody.hpp"
+#include "entity/systems/atmosphere.hpp"
+#include "entity/systems/orbit.hpp"
+#include "entity/systems/proteome.hpp"
+#include "entity/commands.hpp"
+#include "utility/paths.hpp"
+#include "event/event-dispatcher.hpp"
+#include "input/event-router.hpp"
+#include "input/mapper.hpp"
+#include "input/listener.hpp"
+#include "input/gamepad.hpp"
+#include "input/mouse.hpp"
+#include "input/keyboard.hpp"
+#include "configuration.hpp"
+#include "input/scancode.hpp"
+#include "game/fonts.hpp"
+#include "game/controls.hpp"
+#include "game/save.hpp"
+#include "game/menu.hpp"
+#include "game/graphics.hpp"
+#include "utility/timestamp.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+namespace game {
+namespace state {
+
+boot::boot(game::context& ctx, int argc, char** argv):
+ game::state::base(ctx)
+{
+ // Allocate application
+ ctx.app = new application();
+
+ // Get application logger
+ ctx.logger = ctx.app->get_logger();
+
+ // Boot process
+ ctx.logger->push_task("Entering boot state");
+ try
+ {
+ // Parse command line options
+ parse_options(argc, argv);
+ setup_resources();
+ load_config();
+ load_strings();
+ setup_window();
+ setup_rendering();
+ setup_sound();
+ setup_scenes();
+ setup_animation();
+ setup_entities();
+ setup_systems();
+ setup_controls();
+ setup_ui();
+ setup_debugging();
+ setup_loop();
+ }
+ catch (const std::exception& e)
+ {
+ ctx.logger->error("Caught exception: \"" + std::string(e.what()) + "\"");
+ ctx.logger->pop_task(EXIT_FAILURE);
+ return;
+ }
+ ctx.logger->pop_task(EXIT_SUCCESS);
+
+ // Push splash state
+ ctx.state_machine.emplace(new game::state::splash(ctx));
+
+ // Enter main loop
+ loop();
+}
+
+boot::~boot()
+{
+ ctx.logger->push_task("Exiting boot state");
+
+ // Close application
+ delete ctx.app;
+ ctx.app = nullptr;
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::parse_options(int argc, char** argv)
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Parsing command line options");
+
+ try
+ {
+ cxxopts::Options options("Antkeeper", "Ant colony simulation game");
+ options.add_options()
+ ("c,continue", "Continues from the last save")
+ ("d,data", "Sets the data package path", cxxopts::value())
+ ("f,fullscreen", "Starts in fullscreen mode")
+ ("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())
+ ("w,windowed", "Starts in windowed mode");
+ auto result = options.parse(argc, argv);
+
+ // --continue
+ if (result.count("continue"))
+ option_continue = true;
+
+ // --data
+ if (result.count("data"))
+ option_data = result["data"].as();
+
+ // --fullscreen
+ if (result.count("fullscreen"))
+ option_fullscreen = true;
+
+ // --new-game
+ if (result.count("new-game"))
+ option_new_game = true;
+
+ // --quick-start
+ if (result.count("quick-start"))
+ option_quick_start = true;
+
+ // --reset
+ if (result.count("reset"))
+ option_reset = true;
+
+ // --vsync
+ if (result.count("vsync"))
+ option_v_sync = (result["vsync"].as()) ? true : false;
+
+ // --windowed
+ if (result.count("windowed"))
+ option_windowed = true;
+ }
+ catch (const std::exception& e)
+ {
+ logger->error("Exception caught: \"" + std::string(e.what()) + "\"");
+ logger->pop_task(EXIT_FAILURE);
+ return;
+ }
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::setup_resources()
+{
+ debug::logger* logger = ctx.logger;
+
+ // Setup resource manager
+ ctx.resource_manager = new resource_manager(logger);
+
+ // Determine application name
+ std::string application_name;
+ #if defined(_WIN32) || defined(__APPLE__)
+ application_name = "Antkeeper";
+ #else
+ application_name = "antkeeper";
+ #endif
+
+ // Detect paths
+ ctx.data_path = get_data_path(application_name);
+ ctx.config_path = get_config_path(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";
+
+ // Log resource paths
+ logger->log("Detected data path as \"" + ctx.data_path.string());
+ logger->log("Detected config path as \"" + ctx.config_path.string());
+
+ // Create nonexistent config directories
+ std::vector config_paths;
+ config_paths.push_back(ctx.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);
+ for (const std::filesystem::path& path: config_paths)
+ {
+ if (!std::filesystem::exists(path))
+ {
+ logger->push_task("Creating directory \"" + path.string());
+ if (std::filesystem::create_directories(path))
+ {
+ logger->pop_task(EXIT_SUCCESS);
+ }
+ else
+ {
+ logger->pop_task(EXIT_FAILURE);
+ }
+ }
+ }
+
+ // Redirect logger output to log file on non-debug builds
+ #if defined(NDEBUG)
+ std::filesystem::path log_path = ctx.config_path / "log.txt";
+ ctx.log_filestream.open(log_path);
+ ctx.log_filestream << logger->get_history();
+ logger->redirect(&ctx.log_filestream);
+ #endif
+
+ // Scan for mods
+ std::vector mod_paths;
+ for (const auto& directory_entry: std::filesystem::directory_iterator(ctx.mods_path))
+ {
+ if (directory_entry.is_directory())
+ mod_paths.push_back(directory_entry.path());
+ }
+
+ // Determine data package path
+ if (option_data.has_value())
+ {
+ ctx.data_package_path = std::filesystem::path(option_data.value());
+ if (ctx.data_package_path.is_relative())
+ ctx.data_package_path = ctx.data_path / ctx.data_package_path;
+ }
+ else
+ {
+ ctx.data_package_path = ctx.data_path / "data.zip";
+ }
+
+ // Mount mods
+ for (const std::filesystem::path& mod_path: mod_paths)
+ ctx.resource_manager->mount((ctx.mods_path / mod_path).string());
+
+ // Mount config path
+ ctx.resource_manager->mount(ctx.config_path.string());
+
+ // Mount data package
+ ctx.resource_manager->mount(ctx.data_package_path.string());
+
+ // Include resource search paths in order of priority
+ ctx.resource_manager->include("/shaders/");
+ ctx.resource_manager->include("/models/");
+ ctx.resource_manager->include("/images/");
+ ctx.resource_manager->include("/textures/");
+ ctx.resource_manager->include("/materials/");
+ ctx.resource_manager->include("/entities/");
+ ctx.resource_manager->include("/behaviors/");
+ ctx.resource_manager->include("/controls/");
+ ctx.resource_manager->include("/localization/");
+ ctx.resource_manager->include("/localization/fonts/");
+ ctx.resource_manager->include("/biomes/");
+ ctx.resource_manager->include("/traits/");
+ ctx.resource_manager->include("/");
+}
+
+void boot::load_config()
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Loading config");
+
+ // Load config file
+ ctx.config = ctx.resource_manager->load("config.json");
+ if (!ctx.config)
+ {
+ logger->pop_task(EXIT_FAILURE);
+ return;
+ }
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::load_strings()
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Loading strings");
+
+ ctx.string_table = ctx.resource_manager->load("strings.csv");
+
+ build_string_table_map(&ctx.string_table_map, *ctx.string_table);
+
+ ctx.language_code = (*ctx.config)["language"].get();
+ ctx.language_index = -1;
+ for (int i = 2; i < (*ctx.string_table)[0].size(); ++i)
+ {
+ if ((*ctx.string_table)[0][i] == ctx.language_code)
+ ctx.language_index = i - 2;
+ }
+
+ ctx.language_count = (*ctx.string_table)[0].size() - 2;
+ logger->log("language count: " + std::to_string(ctx.language_count));
+ logger->log("language index: " + std::to_string(ctx.language_index));
+ logger->log("language code: " + ctx.language_code);
+
+ ctx.strings = &ctx.string_table_map[ctx.language_code];
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::setup_window()
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Setting up window");
+
+ application* app = ctx.app;
+ json* config = ctx.config;
+
+ // 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();
+ 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();
+ resolution.y = (*config)["fullscreen_resolution"][1].get();
+ }
+ }
+ else
+ {
+ if (config->contains("windowed_resolution"))
+ {
+ resolution.x = (*config)["windowed_resolution"][0].get();
+ resolution.y = (*config)["windowed_resolution"][1].get();
+ }
+ }
+ app->resize_window(resolution.x, resolution.y);
+
+ // 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();
+ 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();
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::setup_rendering()
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Setting up rendering");
+
+ // Get rasterizer from application
+ ctx.rasterizer = ctx.app->get_rasterizer();
+
+ // Create framebuffers
+ game::graphics::create_framebuffers(ctx);
+
+ // Load blue noise texture
+ gl::texture_2d* blue_noise_map = ctx.resource_manager->load("blue-noise.tex");
+
+ // Load fallback material
+ ctx.fallback_material = ctx.resource_manager->load("fallback.mtl");
+
+ // Setup common render passes
+ {
+ ctx.common_bloom_pass = new render::bloom_pass(ctx.rasterizer, ctx.bloom_framebuffer, ctx.resource_manager);
+ ctx.common_bloom_pass->set_source_texture(ctx.hdr_color_texture);
+ ctx.common_bloom_pass->set_brightness_threshold(1.0f);
+ ctx.common_bloom_pass->set_blur_iterations(5);
+
+ ctx.common_final_pass = new render::final_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer(), ctx.resource_manager);
+ ctx.common_final_pass->set_color_texture(ctx.hdr_color_texture);
+ ctx.common_final_pass->set_bloom_texture(ctx.bloom_color_texture);
+ ctx.common_final_pass->set_blue_noise_texture(blue_noise_map);
+ }
+
+ // Setup UI compositor
+ {
+ ctx.ui_clear_pass = new render::clear_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer());
+ ctx.ui_clear_pass->set_cleared_buffers(false, true, false);
+ ctx.ui_clear_pass->set_clear_depth(0.0f);
+
+ ctx.ui_material_pass = new render::material_pass(ctx.rasterizer, &ctx.rasterizer->get_default_framebuffer(), ctx.resource_manager);
+ ctx.ui_material_pass->set_fallback_material(ctx.fallback_material);
+
+ ctx.ui_compositor = new render::compositor();
+ ctx.ui_compositor->add_pass(ctx.ui_clear_pass);
+ ctx.ui_compositor->add_pass(ctx.ui_material_pass);
+ }
+
+ // Setup underground compositor
+ {
+ ctx.underground_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.hdr_framebuffer);
+ ctx.underground_clear_pass->set_cleared_buffers(true, true, false);
+ ctx.underground_clear_pass->set_clear_color({1, 0, 1, 0});
+ ctx.underground_clear_pass->set_clear_depth(0.0f);
+
+ ctx.underground_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
+ ctx.underground_material_pass->set_fallback_material(ctx.fallback_material);
+ ctx.app->get_event_dispatcher()->subscribe(ctx.underground_material_pass);
+
+ ctx.underground_compositor = new render::compositor();
+ ctx.underground_compositor->add_pass(ctx.underground_clear_pass);
+ ctx.underground_compositor->add_pass(ctx.underground_material_pass);
+ ctx.underground_compositor->add_pass(ctx.common_bloom_pass);
+ ctx.underground_compositor->add_pass(ctx.common_final_pass);
+ }
+
+ // Setup surface compositor
+ {
+ ctx.surface_shadow_map_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.shadow_map_framebuffer);
+ ctx.surface_shadow_map_clear_pass->set_cleared_buffers(false, true, false);
+ ctx.surface_shadow_map_clear_pass->set_clear_depth(1.0f);
+
+ ctx.surface_shadow_map_pass = new render::shadow_map_pass(ctx.rasterizer, ctx.shadow_map_framebuffer, ctx.resource_manager);
+ ctx.surface_shadow_map_pass->set_split_scheme_weight(0.75f);
+
+ ctx.surface_clear_pass = new render::clear_pass(ctx.rasterizer, ctx.hdr_framebuffer);
+ ctx.surface_clear_pass->set_cleared_buffers(true, true, true);
+ ctx.surface_clear_pass->set_clear_depth(0.0f);
+
+ ctx.surface_sky_pass = new render::sky_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
+ ctx.app->get_event_dispatcher()->subscribe(ctx.surface_sky_pass);
+
+ ctx.surface_material_pass = new render::material_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
+ ctx.surface_material_pass->set_fallback_material(ctx.fallback_material);
+ ctx.surface_material_pass->shadow_map_pass = ctx.surface_shadow_map_pass;
+ ctx.surface_material_pass->shadow_map = ctx.shadow_map_depth_texture;
+ ctx.app->get_event_dispatcher()->subscribe(ctx.surface_material_pass);
+
+ ctx.surface_outline_pass = new render::outline_pass(ctx.rasterizer, ctx.hdr_framebuffer, ctx.resource_manager);
+ ctx.surface_outline_pass->set_outline_width(0.25f);
+ ctx.surface_outline_pass->set_outline_color(float4{1.0f, 1.0f, 1.0f, 1.0f});
+
+ ctx.surface_compositor = new render::compositor();
+ ctx.surface_compositor->add_pass(ctx.surface_shadow_map_clear_pass);
+ ctx.surface_compositor->add_pass(ctx.surface_shadow_map_pass);
+ ctx.surface_compositor->add_pass(ctx.surface_clear_pass);
+ ctx.surface_compositor->add_pass(ctx.surface_sky_pass);
+ ctx.surface_compositor->add_pass(ctx.surface_material_pass);
+ //ctx.surface_compositor->add_pass(ctx.surface_outline_pass);
+ ctx.surface_compositor->add_pass(ctx.common_bloom_pass);
+ ctx.surface_compositor->add_pass(ctx.common_final_pass);
+ }
+
+ // Create billboard VAO
+ {
+ const float billboard_vertex_data[] =
+ {
+ -1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
+ 1.0f, 1.0f, 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f,
+ -1.0f, -1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f,
+ 1.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 1.0f
+ };
+
+ std::size_t billboard_vertex_size = 8;
+ std::size_t billboard_vertex_stride = sizeof(float) * billboard_vertex_size;
+ std::size_t billboard_vertex_count = 6;
+
+ ctx.billboard_vbo = new gl::vertex_buffer(sizeof(float) * billboard_vertex_size * billboard_vertex_count, billboard_vertex_data);
+ ctx.billboard_vao = new gl::vertex_array();
+
+ std::size_t attribute_offset = 0;
+
+ // Define position vertex attribute
+ gl::vertex_attribute position_attribute;
+ position_attribute.buffer = ctx.billboard_vbo;
+ position_attribute.offset = attribute_offset;
+ position_attribute.stride = billboard_vertex_stride;
+ position_attribute.type = gl::vertex_attribute_type::float_32;
+ position_attribute.components = 3;
+ attribute_offset += position_attribute.components * sizeof(float);
+
+ // Define UV vertex attribute
+ gl::vertex_attribute uv_attribute;
+ uv_attribute.buffer = ctx.billboard_vbo;
+ uv_attribute.offset = attribute_offset;
+ uv_attribute.stride = billboard_vertex_stride;
+ uv_attribute.type = gl::vertex_attribute_type::float_32;
+ uv_attribute.components = 2;
+ attribute_offset += uv_attribute.components * sizeof(float);
+
+ // Define barycentric vertex attribute
+ gl::vertex_attribute barycentric_attribute;
+ barycentric_attribute.buffer = ctx.billboard_vbo;
+ barycentric_attribute.offset = attribute_offset;
+ barycentric_attribute.stride = billboard_vertex_stride;
+ barycentric_attribute.type = gl::vertex_attribute_type::float_32;
+ barycentric_attribute.components = 3;
+ attribute_offset += barycentric_attribute.components * sizeof(float);
+
+ // Bind vertex attributes to VAO
+ ctx.billboard_vao->bind(render::vertex_attribute::position, position_attribute);
+ ctx.billboard_vao->bind(render::vertex_attribute::uv, uv_attribute);
+ ctx.billboard_vao->bind(render::vertex_attribute::barycentric, barycentric_attribute);
+ }
+
+ // Create renderer
+ ctx.renderer = new render::renderer();
+ ctx.renderer->set_billboard_vao(ctx.billboard_vao);
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::setup_sound()
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Setting up sound");
+
+ // Load master volume config
+ ctx.master_volume = 1.0f;
+ if (ctx.config->contains("master_volume"))
+ ctx.master_volume = (*ctx.config)["master_volume"].get();
+
+ // Load ambience volume config
+ ctx.ambience_volume = 1.0f;
+ if (ctx.config->contains("ambience_volume"))
+ ctx.ambience_volume = (*ctx.config)["ambience_volume"].get();
+
+ // Load effects volume config
+ ctx.effects_volume = 1.0f;
+ if (ctx.config->contains("effects_volume"))
+ ctx.effects_volume = (*ctx.config)["effects_volume"].get();
+
+ // Load mono audio config
+ ctx.mono_audio = false;
+ if (ctx.config->contains("mono_audio"))
+ ctx.mono_audio = (*ctx.config)["mono_audio"].get();
+
+ // Load captions config
+ ctx.captions = false;
+ if (ctx.config->contains("captions"))
+ ctx.captions = (*ctx.config)["captions"].get();
+
+ // Load captions size config
+ ctx.captions_size = 1.0f;
+ if (ctx.config->contains("captions_size"))
+ ctx.captions_size = (*ctx.config)["captions_size"].get();
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::setup_scenes()
+{
+ debug::logger* logger = ctx.logger;
+ logger->push_task("Setting up scenes");
+
+ // Get default framebuffer
+ const auto& viewport_dimensions = ctx.rasterizer->get_default_framebuffer().get_dimensions();
+ const float viewport_aspect_ratio = static_cast(viewport_dimensions[0]) / static_cast(viewport_dimensions[1]);
+
+ // Create infinite culling mask
+ const float inf = std::numeric_limits::infinity();
+ ctx.no_cull = {{-inf, -inf, -inf}, {inf, inf, inf}};
+
+ // 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_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);
+ ctx.ui_camera->look_at({0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, -1.0f}, {0.0f, 1.0f, 0.0f});
+ ctx.ui_camera->update_tweens();
+
+ // Setup underground camera
+ ctx.underground_camera = new scene::camera();
+ ctx.underground_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.1f, 1000.0f);
+ ctx.underground_camera->set_compositor(ctx.underground_compositor);
+ ctx.underground_camera->set_composite_index(0);
+ ctx.underground_camera->set_active(false);
+
+ // Setup surface camera
+ ctx.surface_camera = new scene::camera();
+ ctx.surface_camera->set_perspective(math::radians(45.0f), viewport_aspect_ratio, 0.1f, 1000.0f);
+ ctx.surface_camera->set_compositor(ctx.surface_compositor);
+ ctx.surface_camera->set_composite_index(0);
+ ctx.surface_camera->set_active(false);
+
+ // Setup UI scene
+ {
+ ctx.ui_scene = new scene::collection();
+ const gl::texture_2d* splash_texture = ctx.resource_manager->load("splash.tex");
+ auto splash_dimensions = splash_texture->get_dimensions();
+ ctx.splash_billboard_material = new render::material();
+ ctx.splash_billboard_material->set_flags(MATERIAL_FLAG_TRANSLUCENT);
+ ctx.splash_billboard_material->set_shader_program(ctx.resource_manager->load("ui-element-textured.glsl"));
+ ctx.splash_billboard_material->add_property("background")->set_value(splash_texture);
+ ctx.splash_billboard_material->add_property("tint")->set_value(float4{1, 1, 1, 1});
+ ctx.splash_billboard_material->update_tweens();
+ ctx.splash_billboard = new scene::billboard();
+ ctx.splash_billboard->set_material(ctx.splash_billboard_material);
+ ctx.splash_billboard->set_scale({(float)std::get<0>(splash_dimensions) * 0.5f, (float)std::get<1>(splash_dimensions) * 0.5f, 1.0f});
+ ctx.splash_billboard->set_translation({0.0f, 0.0f, 0.0f});
+ ctx.splash_billboard->update_tweens();
+
+ // Menu BG billboard
+ render::material* menu_bg_material = new render::material();
+ menu_bg_material->set_shader_program(ctx.resource_manager->load("ui-element-untextured.glsl"));
+ auto menu_bg_tint = menu_bg_material->add_property("tint");
+ menu_bg_tint->set_value(float4{0.0f, 0.0f, 0.0f, 0.5f});
+ menu_bg_material->set_flags(MATERIAL_FLAG_TRANSLUCENT);
+ menu_bg_material->update_tweens();
+ 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_translation({0.0f, 0.0f, -100.0f});
+ ctx.menu_bg_billboard->update_tweens();
+
+ // Create camera flash billboard
+
+ render::material* flash_material = new render::material();
+ flash_material->set_shader_program(ctx.resource_manager->load("ui-element-untextured.glsl"));
+ auto flash_tint = flash_material->add_property("tint");
+ flash_tint->set_value(float4{1, 1, 1, 1});
+ //flash_tint->set_tween_interpolator(ease::out_quad);
+
+ flash_material->set_flags(MATERIAL_FLAG_TRANSLUCENT);
+ flash_material->update_tweens();
+
+ 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_translation({0.0f, 0.0f, 0.0f});
+ ctx.camera_flash_billboard->update_tweens();
+
+ // Create depth debug billboard
+ /*
+ material* depth_debug_material = new material();
+ depth_debug_material->set_shader_program(ctx.resource_manager->load("ui-element-textured.glsl"));
+ depth_debug_material->add_property("background")->set_value(shadow_map_depth_texture);
+ depth_debug_material->add_property("tint")->set_value(float4{1, 1, 1, 1});
+ billboard* depth_debug_billboard = new billboard();
+ depth_debug_billboard->set_material(depth_debug_material);
+ depth_debug_billboard->set_scale({128, 128, 1});
+ depth_debug_billboard->set_translation({-960 + 128, 1080 * 0.5f - 128, 0});
+ depth_debug_billboard->update_tweens();
+ ui_system->get_scene()->add_object(depth_debug_billboard);
+ */
+
+ ctx.ui_scene->add_object(ctx.ui_camera);
+ }
+
+ // Setup underground scene
+ {
+ ctx.underground_scene = new scene::collection();
+
+ ctx.underground_ambient_light = new scene::ambient_light();
+ ctx.underground_ambient_light->set_color({1, 1, 1});
+ ctx.underground_ambient_light->set_intensity(0.1f);
+ ctx.underground_ambient_light->update_tweens();
+
+ ctx.flashlight_spot_light = new scene::spot_light();
+ ctx.flashlight_spot_light->set_color({1, 1, 1});
+ ctx.flashlight_spot_light->set_intensity(1.0f);
+ ctx.flashlight_spot_light->set_attenuation({1.0f, 0.0f, 0.0f});
+ ctx.flashlight_spot_light->set_cutoff({math::radians(10.0f), math::radians(19.0f)});
+
+ ctx.underground_scene->add_object(ctx.underground_camera);
+ ctx.underground_scene->add_object(ctx.underground_ambient_light);
+ //ctx.underground_scene->add_object(ctx.flashlight_spot_light);
+ }
+
+ // Setup surface scene
+ {
+ ctx.surface_scene = new scene::collection();
+ ctx.surface_scene->add_object(ctx.surface_camera);
+ }
+
+ // Clear active scene
+ ctx.active_scene = nullptr;
+
+ logger->pop_task(EXIT_SUCCESS);
+}
+
+void boot::setup_animation()
+{
+ // Setup timeline system
+ ctx.timeline = new timeline();
+ ctx.timeline->set_autoremove(true);
+
+ // Setup animator
+ ctx.animator = new animator();
+
+ // Create fade transition
+ ctx.fade_transition = new screen_transition();
+ ctx.fade_transition->get_material()->set_shader_program(ctx.resource_manager->load("fade-transition.glsl"));
+ ctx.fade_transition_color = ctx.fade_transition->get_material()->add_property("color");
+ ctx.fade_transition_color->set_value({0, 0, 0});
+ ctx.ui_scene->add_object(ctx.fade_transition->get_billboard());
+ ctx.animator->add_animation(ctx.fade_transition->get_animation());
+
+ // Create inner radial transition
+ ctx.radial_transition_inner = new screen_transition();
+ ctx.radial_transition_inner->get_material()->set_shader_program(ctx.resource_manager->load("radial-transition-inner.glsl"));
+ //ctx.ui_scene->add_object(ctx.radial_transition_inner->get_billboard());
+ //ctx.animator->add_animation(ctx.radial_transition_inner->get_animation());
+
+ // Create outer radial transition
+ ctx.radial_transition_outer = new screen_transition();
+ ctx.radial_transition_outer->get_material()->set_shader_program(ctx.resource_manager->load("radial-transition-outer.glsl"));
+ //ctx.ui_scene->add_object(ctx.radial_transition_outer->get_billboard());
+ //ctx.animator->add_animation(ctx.radial_transition_outer->get_animation());
+
+ // Menu BG animations
+ {
+ render::material_property* menu_bg_tint = static_cast*>(ctx.menu_bg_billboard->get_material()->get_property("tint"));
+ auto menu_bg_frame_callback = [menu_bg_tint](int channel, const float& opacity)
+ {
+ menu_bg_tint->set_value(float4{0.0f, 0.0f, 0.0f, opacity});
+ };
+
+ // Create menu BG fade in animation
+ ctx.menu_bg_fade_in_animation = new animation();
+ {
+ ctx.menu_bg_fade_in_animation->set_interpolator(ease::out_cubic);
+ animation_channel* channel = ctx.menu_bg_fade_in_animation->add_channel(0);
+ channel->insert_keyframe({0.0f, 0.0f});
+ channel->insert_keyframe({game::menu::fade_in_duration, game::menu::bg_opacity});
+ ctx.menu_bg_fade_in_animation->set_frame_callback(menu_bg_frame_callback);
+ ctx.menu_bg_fade_in_animation->set_start_callback
+ (
+ [&ctx = this->ctx]()
+ {
+ ctx.ui_scene->add_object(ctx.menu_bg_billboard);
+ ctx.menu_bg_billboard->set_active(true);
+ }
+ );
+ }
+
+ // Create menu BG fade out animation
+ ctx.menu_bg_fade_out_animation = new animation();
+ {
+ ctx.menu_bg_fade_out_animation->set_interpolator(ease::out_cubic);
+ animation_channel* channel = ctx.menu_bg_fade_out_animation->add_channel(0);
+ channel->insert_keyframe({0.0f, game::menu::bg_opacity});
+ channel->insert_keyframe({game::menu::fade_out_duration, 0.0f});
+ ctx.menu_bg_fade_out_animation->set_frame_callback(menu_bg_frame_callback);
+ ctx.menu_bg_fade_out_animation->set_end_callback
+ (
+ [&ctx = this->ctx]()
+ {
+ ctx.ui_scene->remove_object(ctx.menu_bg_billboard);
+ ctx.menu_bg_billboard->set_active(false);
+ }
+ );
+ }
+
+ ctx.animator->add_animation(ctx.menu_bg_fade_in_animation);
+ ctx.animator->add_animation(ctx.menu_bg_fade_out_animation);
+ }
+
+ // Create camera flash animation
+ ctx.camera_flash_animation = new animation();
+ {
+ ctx.camera_flash_animation->set_interpolator(ease::out_sine);
+ const float duration = 0.5f;
+ animation_channel* channel = ctx.camera_flash_animation->add_channel(0);
+ channel->insert_keyframe({0.0f, 1.0f});
+ channel->insert_keyframe({duration, 0.0f});
+ }
+}
+
+void boot::setup_entities()
+{
+ // Create entity registry
+ ctx.entity_registry = new entt::registry();
+}
+
+void boot::setup_systems()
+{
+ event_dispatcher* event_dispatcher = ctx.app->get_event_dispatcher();
+
+ const auto& viewport_dimensions = ctx.app->get_viewport_dimensions();
+ float4 viewport = {0.0f, 0.0f, static_cast(viewport_dimensions[0]), static_cast(viewport_dimensions[1])};
+
+ // RGB wavelengths determined by matching wavelengths to XYZ, transforming XYZ to ACEScg, then selecting the max wavelengths for R, G, and B.
+ const double3 rgb_wavelengths_nm = {602.224, 541.069, 448.143};
+
+ // Setup terrain system
+ ctx.terrain_system = new entity::system::terrain(*ctx.entity_registry);
+ ctx.terrain_system->set_patch_subdivisions(30);
+ ctx.terrain_system->set_patch_scene_collection(ctx.surface_scene);
+ ctx.terrain_system->set_max_error(200.0);
+
+ // Setup vegetation system
+ //ctx.vegetation_system = new entity::system::vegetation(*ctx.entity_registry);
+ //ctx.vegetation_system->set_terrain_patch_size(TERRAIN_PATCH_SIZE);
+ //ctx.vegetation_system->set_vegetation_patch_resolution(VEGETATION_PATCH_RESOLUTION);
+ //ctx.vegetation_system->set_vegetation_density(1.0f);
+ //ctx.vegetation_system->set_vegetation_model(ctx.resource_manager->load("grass-tuft.mdl"));
+ //ctx.vegetation_system->set_scene(ctx.surface_scene);
+
+ // Setup camera system
+ ctx.camera_system = new entity::system::camera(*ctx.entity_registry);
+ ctx.camera_system->set_viewport(viewport);
+ event_dispatcher->subscribe(ctx.camera_system);
+
+ // Setup subterrain system
+ ctx.subterrain_system = new entity::system::subterrain(*ctx.entity_registry, ctx.resource_manager);
+ ctx.subterrain_system->set_scene(ctx.underground_scene);
+
+ // Setup collision system
+ ctx.collision_system = new entity::system::collision(*ctx.entity_registry);
+
+ // Setup samara system
+ ctx.samara_system = new entity::system::samara(*ctx.entity_registry);
+
+ // Setup snapping system
+ ctx.snapping_system = new entity::system::snapping(*ctx.entity_registry);
+
+ // Setup behavior system
+ ctx.behavior_system = new entity::system::behavior(*ctx.entity_registry);
+
+ // Setup locomotion system
+ ctx.locomotion_system = new entity::system::locomotion(*ctx.entity_registry);
+
+ // Setup spatial system
+ ctx.spatial_system = new entity::system::spatial(*ctx.entity_registry);
+
+ // Setup constraint system
+ ctx.constraint_system = new entity::system::constraint(*ctx.entity_registry);
+
+ // Setup painting system
+ ctx.painting_system = new entity::system::painting(*ctx.entity_registry, event_dispatcher, ctx.resource_manager);
+ ctx.painting_system->set_scene(ctx.surface_scene);
+
+ // Setup orbit system
+ ctx.orbit_system = new entity::system::orbit(*ctx.entity_registry);
+
+ // Setup blackbody system
+ ctx.blackbody_system = new entity::system::blackbody(*ctx.entity_registry);
+ ctx.blackbody_system->set_rgb_wavelengths(rgb_wavelengths_nm);
+
+ // Setup atmosphere system
+ ctx.atmosphere_system = new entity::system::atmosphere(*ctx.entity_registry);
+ ctx.atmosphere_system->set_rgb_wavelengths(rgb_wavelengths_nm);
+
+ // Setup astronomy system
+ ctx.astronomy_system = new entity::system::astronomy(*ctx.entity_registry);
+ ctx.astronomy_system->set_sky_pass(ctx.surface_sky_pass);
+
+ // Setup proteome system
+ ctx.proteome_system = new entity::system::proteome(*ctx.entity_registry);
+
+ // Setup render system
+ ctx.render_system = new entity::system::render(*ctx.entity_registry);
+ ctx.render_system->add_layer(ctx.underground_scene);
+ ctx.render_system->add_layer(ctx.surface_scene);
+ ctx.render_system->add_layer(ctx.ui_scene);
+ ctx.render_system->set_renderer(ctx.renderer);
+}
+
+void boot::setup_controls()
+{
+ event_dispatcher* event_dispatcher = ctx.app->get_event_dispatcher();
+
+ // Setup input event routing
+ ctx.input_event_router = new input::event_router();
+ ctx.input_event_router->set_event_dispatcher(event_dispatcher);
+
+ // Setup input mapper
+ ctx.input_mapper = new input::mapper();
+ ctx.input_mapper->set_event_dispatcher(event_dispatcher);
+
+ // Setup input listener
+ ctx.input_listener = new input::listener();
+ ctx.input_listener->set_event_dispatcher(event_dispatcher);
+
+ // Load SDL game controller mappings database
+ ctx.logger->push_task("Loading SDL game controller mappings from database");
+ file_buffer* game_controller_db = ctx.resource_manager->load("gamecontrollerdb.txt");
+ if (!game_controller_db)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+ }
+ else
+ {
+ ctx.app->add_game_controller_mappings(game_controller_db->data(), game_controller_db->size());
+ ctx.resource_manager->unload("gamecontrollerdb.txt");
+ ctx.logger->pop_task(EXIT_SUCCESS);
+ }
+
+ // Load controls
+ ctx.logger->push_task("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((*ctx.config)["control_profile"].get());
+
+ // Apply control profile
+ if (profile)
+ {
+ game::apply_control_profile(ctx, *profile);
+ }
+ }
+
+ // Calibrate gamepads
+ for (input::gamepad* gamepad: ctx.app->get_gamepads())
+ {
+ ctx.logger->push_task("Loading calibration for gamepad " + gamepad->get_guid());
+ json* calibration = game::load_gamepad_calibration(ctx, *gamepad);
+ if (!calibration)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+
+ ctx.logger->push_task("Generating default calibration for gamepad " + gamepad->get_guid());
+ json default_calibration = game::default_gamepad_calibration();
+ apply_gamepad_calibration(*gamepad, default_calibration);
+
+ if (!save_gamepad_calibration(ctx, *gamepad, default_calibration))
+ ctx.logger->pop_task(EXIT_FAILURE);
+ else
+ ctx.logger->pop_task(EXIT_SUCCESS);
+ }
+ else
+ {
+ ctx.logger->pop_task(EXIT_SUCCESS);
+ apply_gamepad_calibration(*gamepad, *calibration);
+ }
+ }
+
+ // Toggle fullscreen
+ ctx.controls["toggle_fullscreen"]->set_activated_callback
+ (
+ [&ctx = this->ctx]()
+ {
+ bool fullscreen = !ctx.app->is_fullscreen();
+
+ ctx.app->set_fullscreen(fullscreen);
+
+ if (!fullscreen)
+ {
+ int2 resolution;
+ resolution.x = (*ctx.config)["windowed_resolution"][0].get();
+ resolution.y = (*ctx.config)["windowed_resolution"][1].get();
+
+ ctx.app->resize_window(resolution.x, resolution.y);
+ }
+
+ // Save display mode config
+ (*ctx.config)["fullscreen"] = fullscreen;
+ game::save_config(ctx);
+ }
+ );
+
+ // Screenshot
+ ctx.controls["screenshot"]->set_activated_callback(std::bind(game::graphics::save_screenshot, std::ref(ctx)));
+
+ // Set activation threshold for menu navigation controls to mitigate drifting gamepad axes
+ const float menu_activation_threshold = 0.1f;
+ ctx.controls["menu_up"]->set_activation_threshold(menu_activation_threshold);
+ ctx.controls["menu_down"]->set_activation_threshold(menu_activation_threshold);
+ ctx.controls["menu_left"]->set_activation_threshold(menu_activation_threshold);
+ ctx.controls["menu_right"]->set_activation_threshold(menu_activation_threshold);
+ }
+ catch (...)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+ }
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+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();
+
+ // Load dyslexia font config
+ ctx.dyslexia_font = false;
+ if (ctx.config->contains("dyslexia_font"))
+ ctx.dyslexia_font = (*ctx.config)["dyslexia_font"].get();
+
+ // Load fonts
+ ctx.logger->push_task("Loading fonts");
+ try
+ {
+ game::load_fonts(ctx);
+ }
+ catch (...)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+ }
+ ctx.logger->pop_task(EXIT_SUCCESS);
+
+ // Construct mouse tracker
+ ctx.menu_mouse_tracker = new ui::mouse_tracker();
+ ctx.app->get_event_dispatcher()->subscribe(ctx.menu_mouse_tracker);
+ ctx.app->get_event_dispatcher()->subscribe(ctx.menu_mouse_tracker);
+ ctx.app->get_event_dispatcher()->subscribe(ctx.menu_mouse_tracker);
+ ctx.app->get_event_dispatcher()->subscribe(ctx.menu_mouse_tracker);
+}
+
+void boot::setup_debugging()
+{
+ // Setup performance sampling
+ ctx.performance_sampler.set_sample_size(15);
+
+ ctx.cli = new debug::cli();
+ ctx.cli->register_command("echo", debug::cc::echo);
+ ctx.cli->register_command("exit", std::function(std::bind(&debug::cc::exit, &ctx)));
+ ctx.cli->register_command("scrot", std::function(std::bind(&debug::cc::scrot, &ctx)));
+ ctx.cli->register_command("cue", std::function(std::bind(&debug::cc::cue, &ctx, std::placeholders::_1, std::placeholders::_2)));
+ //std::string cmd = "cue 20 exit";
+ //logger->log(cmd);
+ //logger->log(cli.interpret(cmd));
+}
+
+void boot::setup_loop()
+{
+ // Set update rate
+ if (ctx.config->contains("update_rate"))
+ {
+ ctx.loop.set_update_rate((*ctx.config)["update_rate"].get());
+ }
+
+ // Set update callback
+ ctx.loop.set_update_callback
+ (
+ [&ctx = this->ctx](double t, double dt)
+ {
+ // Update tweens
+ ctx.surface_sky_pass->update_tweens();
+ ctx.surface_scene->update_tweens();
+ ctx.underground_scene->update_tweens();
+ ctx.ui_scene->update_tweens();
+
+ // Process events
+ ctx.app->process_events();
+ ctx.app->get_event_dispatcher()->update(t);
+
+ // Update controls
+ for (const auto& control: ctx.controls)
+ control.second->update();
+
+ // Process function queue
+ while (!ctx.function_queue.empty())
+ {
+ ctx.function_queue.front()();
+ ctx.function_queue.pop();
+ }
+
+ // Update processes
+ std::for_each
+ (
+ std::execution::par,
+ ctx.processes.begin(),
+ ctx.processes.end(),
+ [t, dt](const auto& process)
+ {
+ process.second(t, dt);
+ }
+ );
+
+ // Advance timeline
+ ctx.timeline->advance(dt);
+
+ // Update entity systems
+ ctx.terrain_system->update(t, dt);
+ //ctx.vegetation_system->update(t, dt);
+ ctx.snapping_system->update(t, dt);
+ ctx.subterrain_system->update(t, dt);
+ ctx.collision_system->update(t, dt);
+ ctx.samara_system->update(t, dt);
+ ctx.behavior_system->update(t, dt);
+ ctx.locomotion_system->update(t, dt);
+ ctx.camera_system->update(t, dt);
+ ctx.orbit_system->update(t, dt);
+ ctx.blackbody_system->update(t, dt);
+ ctx.atmosphere_system->update(t, dt);
+ ctx.astronomy_system->update(t, dt);
+ ctx.spatial_system->update(t, dt);
+ ctx.constraint_system->update(t, dt);
+ ctx.painting_system->update(t, dt);
+ ctx.proteome_system->update(t, dt);
+ ctx.animator->animate(dt);
+ ctx.render_system->update(t, dt);
+ }
+ );
+
+ // Set render callback
+ ctx.loop.set_render_callback
+ (
+ [&ctx = this->ctx](double alpha)
+ {
+ ctx.render_system->draw(alpha);
+ ctx.app->swap_buffers();
+ }
+ );
+}
+
+void boot::loop()
+{
+ while (!ctx.app->was_closed())
+ {
+ // Execute main loop
+ ctx.loop.tick();
+
+ // Sample frame duration
+ ctx.performance_sampler.sample(ctx.loop.get_frame_duration());
+ }
+
+ // Exit all active game states
+ while (!ctx.state_machine.empty())
+ ctx.state_machine.pop();
+}
+
+} // namespace state
+} // namespace game
diff --git a/src/game/state/boot.hpp b/src/game/state/boot.hpp
new file mode 100644
index 0000000..7160317
--- /dev/null
+++ b/src/game/state/boot.hpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#ifndef ANTKEEPER_GAME_STATE_BOOT_HPP
+#define ANTKEEPER_GAME_STATE_BOOT_HPP
+
+#include "game/state/base.hpp"
+#include "game/context.hpp"
+#include
+
+namespace game {
+namespace state {
+
+class boot: public game::state::base
+{
+public:
+ boot(game::context& ctx, int argc, char** argv);
+ virtual ~boot();
+
+private:
+ void parse_options(int argc, char** argv);
+ void setup_resources();
+ void load_config();
+ void load_strings();
+ void setup_window();
+ void setup_rendering();
+ void setup_sound();
+ void setup_scenes();
+ void setup_animation();
+ void setup_entities();
+ void setup_systems();
+ void setup_controls();
+ void setup_ui();
+ void setup_debugging();
+ void setup_loop();
+ void loop();
+
+ // Command line options
+ std::optional option_continue;
+ std::optional option_data;
+ std::optional option_fullscreen;
+ std::optional option_new_game;
+ std::optional option_quick_start;
+ std::optional option_reset;
+ std::optional option_v_sync;
+ std::optional option_windowed;
+};
+
+} // namespace state
+} // namespace game
+
+#endif // ANTKEEPER_GAME_STATE_BOOT_HPP
diff --git a/src/game/states/brood.cpp b/src/game/state/brood.cpp
similarity index 99%
rename from src/game/states/brood.cpp
rename to src/game/state/brood.cpp
index bba854c..d5a8d3e 100644
--- a/src/game/states/brood.cpp
+++ b/src/game/state/brood.cpp
@@ -17,7 +17,7 @@
* along with Antkeeper source code. If not, see .
*/
-#include "game/states/brood.hpp"
+#include "game/state/brood.hpp"
#include "entity/archetype.hpp"
#include "entity/commands.hpp"
#include "entity/components/observer.hpp"
@@ -326,14 +326,14 @@ void setup_controls(game::context* ctx)
// Mouse rotate
ctx->controls["mouse_rotate"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
ctx->app->set_relative_mouse_mode(true);
}
);
ctx->controls["mouse_rotate"]->set_deactivated_callback
(
- [ctx]()
+ [&ctx]()
{
ctx->app->set_relative_mouse_mode(false);
}
diff --git a/src/game/states/brood.hpp b/src/game/state/brood.hpp
similarity index 100%
rename from src/game/states/brood.hpp
rename to src/game/state/brood.hpp
diff --git a/src/game/states/controls-menu.cpp b/src/game/state/controls-menu.cpp
similarity index 51%
rename from src/game/states/controls-menu.cpp
rename to src/game/state/controls-menu.cpp
index a18a9b1..c9d739b 100644
--- a/src/game/states/controls-menu.cpp
+++ b/src/game/state/controls-menu.cpp
@@ -17,10 +17,10 @@
* along with Antkeeper source code. If not, see .
*/
-#include "game/states/controls-menu.hpp"
-#include "game/states/keyboard-config-menu.hpp"
-#include "game/states/gamepad-config-menu.hpp"
-#include "game/states/options-menu.hpp"
+#include "game/state/controls-menu.hpp"
+#include "game/state/keyboard-config-menu.hpp"
+#include "game/state/gamepad-config-menu.hpp"
+#include "game/state/options-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
@@ -28,24 +28,26 @@
namespace game {
namespace state {
-namespace controls_menu {
-void enter(game::context* ctx)
+controls_menu::controls_menu(game::context& ctx):
+ game::state::base(ctx)
{
+ ctx.logger->push_task("Entering controls menu state");
+
// Construct menu item texts
scene::text* keyboard_text = new scene::text();
scene::text* gamepad_text = new scene::text();
scene::text* back_text = new scene::text();
// Build list of menu item texts
- ctx->menu_item_texts.push_back({keyboard_text, nullptr});
- ctx->menu_item_texts.push_back({gamepad_text, nullptr});
- ctx->menu_item_texts.push_back({back_text, nullptr});
+ ctx.menu_item_texts.push_back({keyboard_text, nullptr});
+ ctx.menu_item_texts.push_back({gamepad_text, nullptr});
+ 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((*ctx.strings)["controls_menu_keyboard"]);
+ gamepad_text->set_content((*ctx.strings)["controls_menu_gamepad"]);
+ back_text->set_content((*ctx.strings)["back"]);
// Init menu item index
game::menu::init_menu_item_index(ctx, "controls");
@@ -58,7 +60,7 @@ void enter(game::context* ctx)
game::menu::setup_animations(ctx);
// Construct menu item callbacks
- auto select_keyboard_callback = [ctx]()
+ auto select_keyboard_callback = [&ctx]()
{
// Disable controls
game::menu::clear_controls(ctx);
@@ -66,17 +68,21 @@ void enter(game::context* ctx)
game::menu::fade_out
(
ctx,
- [ctx]()
+ [&ctx]()
{
- application::state next_state;
- next_state.name = "keyboard_config_menu";
- next_state.enter = std::bind(game::state::keyboard_config_menu::enter, ctx);
- next_state.exit = std::bind(game::state::keyboard_config_menu::exit, ctx);
- ctx->app->queue_state(next_state);
+ // Queue change to keyboard config menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::keyboard_config_menu(ctx));
+ }
+ );
}
);
};
- auto select_gamepad_callback = [ctx]()
+ auto select_gamepad_callback = [&ctx]()
{
// Disable controls
game::menu::clear_controls(ctx);
@@ -84,17 +90,21 @@ void enter(game::context* ctx)
game::menu::fade_out
(
ctx,
- [ctx]()
+ [&ctx]()
{
- application::state next_state;
- next_state.name = "gamepad_config_menu";
- next_state.enter = std::bind(game::state::gamepad_config_menu::enter, ctx);
- next_state.exit = std::bind(game::state::gamepad_config_menu::exit, ctx);
- ctx->app->queue_state(next_state);
+ // Queue change to gamepad config menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::gamepad_config_menu(ctx));
+ }
+ );
}
);
};
- auto select_back_callback = [ctx]()
+ auto select_back_callback = [&ctx]()
{
// Disable controls
game::menu::clear_controls(ctx);
@@ -102,52 +112,61 @@ void enter(game::context* ctx)
game::menu::fade_out
(
ctx,
- [ctx]()
+ [&ctx]()
{
- application::state next_state;
- next_state.name = "options_menu";
- next_state.enter = std::bind(game::state::options_menu::enter, ctx);
- next_state.exit = std::bind(game::state::options_menu::exit, ctx);
- ctx->app->queue_state(next_state);
+ // Queue change to options menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::options_menu(ctx));
+ }
+ );
}
);
};
// Build list of menu select callbacks
- ctx->menu_select_callbacks.push_back(select_keyboard_callback);
- ctx->menu_select_callbacks.push_back(select_gamepad_callback);
- ctx->menu_select_callbacks.push_back(select_back_callback);
+ ctx.menu_select_callbacks.push_back(select_keyboard_callback);
+ ctx.menu_select_callbacks.push_back(select_gamepad_callback);
+ ctx.menu_select_callbacks.push_back(select_back_callback);
// Build list of menu left callbacks
- ctx->menu_left_callbacks.push_back(nullptr);
- ctx->menu_left_callbacks.push_back(nullptr);
- ctx->menu_left_callbacks.push_back(nullptr);
+ ctx.menu_left_callbacks.push_back(nullptr);
+ ctx.menu_left_callbacks.push_back(nullptr);
+ ctx.menu_left_callbacks.push_back(nullptr);
// Build list of menu right callbacks
- ctx->menu_right_callbacks.push_back(nullptr);
- ctx->menu_right_callbacks.push_back(nullptr);
- ctx->menu_right_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
// Set menu back callback
- ctx->menu_back_callback = select_back_callback;
+ ctx.menu_back_callback = select_back_callback;
// Queue menu control setup
- ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx));
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
// Fade in menu
game::menu::fade_in(ctx, nullptr);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
}
-void exit(game::context* ctx)
+controls_menu::~controls_menu()
{
+ ctx.logger->push_task("Exiting options menu state");
+
// Destruct menu
game::menu::clear_controls(ctx);
game::menu::clear_callbacks(ctx);
game::menu::delete_animations(ctx);
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
}
-} // namespace controls_menu
} // namespace state
} // namespace game
diff --git a/src/game/states/controls-menu.hpp b/src/game/state/controls-menu.hpp
similarity index 83%
rename from src/game/states/controls-menu.hpp
rename to src/game/state/controls-menu.hpp
index 4ddf975..b47367e 100644
--- a/src/game/states/controls-menu.hpp
+++ b/src/game/state/controls-menu.hpp
@@ -20,18 +20,17 @@
#ifndef ANTKEEPER_GAME_STATE_CONTROLS_MENU_HPP
#define ANTKEEPER_GAME_STATE_CONTROLS_MENU_HPP
-#include "game/context.hpp"
+#include "game/state/base.hpp"
namespace game {
namespace state {
-/// Controls menu screen game state functions.
-namespace controls_menu {
-
-void enter(game::context* ctx);
-void exit(game::context* ctx);
-
-} // namespace controls_menu
+class controls_menu: public game::state::base
+{
+public:
+ controls_menu(game::context& ctx);
+ virtual ~controls_menu();
+};
} // namespace state
} // namespace game
diff --git a/src/game/state/credits.cpp b/src/game/state/credits.cpp
new file mode 100644
index 0000000..b488c03
--- /dev/null
+++ b/src/game/state/credits.cpp
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#include "game/state/credits.hpp"
+#include "game/state/extras-menu.hpp"
+#include "game/context.hpp"
+#include "animation/ease.hpp"
+#include "animation/animation.hpp"
+#include "animation/animator.hpp"
+#include "application.hpp"
+#include "scene/text.hpp"
+#include "debug/logger.hpp"
+
+namespace game {
+namespace state {
+
+credits::credits(game::context& ctx):
+ game::state::base(ctx)
+{
+ ctx.logger->push_task("Entering credits state");
+
+ // Construct credits text
+ ctx.credits_text = new scene::text();
+ ctx.credits_text->set_material(&ctx.menu_font_material);
+ ctx.credits_text->set_font(&ctx.menu_font);
+ ctx.credits_text->set_color({1.0f, 1.0f, 1.0f, 0.0f});
+ ctx.credits_text->set_content((*ctx.strings)["credits"]);
+
+ // Align credits text
+ const auto& credits_aabb = static_cast&>(ctx.credits_text->get_local_bounds());
+ float credits_w = credits_aabb.max_point.x - credits_aabb.min_point.x;
+ float credits_h = credits_aabb.max_point.y - credits_aabb.min_point.y;
+ ctx.credits_text->set_translation({std::round(-credits_w * 0.5f), std::round(-credits_h * 0.5f), 0.0f});
+
+ // 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();
+ if (ctx.config->contains("credits_scroll_duration"))
+ credits_scroll_duration = (*ctx.config)["credits_scroll_duration"].get();
+
+ auto set_credits_opacity = [&ctx](int channel, const float& opacity)
+ {
+ ctx.credits_text->set_color({1.0f, 1.0f, 1.0f, opacity});
+ };
+
+ // Build credits fade in animation
+ ctx.credits_fade_in_animation = new animation();
+ animation_channel* credits_fade_in_opacity_channel = ctx.credits_fade_in_animation->add_channel(0);
+ ctx.credits_fade_in_animation->set_interpolator(ease::in_quad);
+ credits_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f});
+ credits_fade_in_opacity_channel->insert_keyframe({credits_fade_in_duration, 1.0f});
+ ctx.credits_fade_in_animation->set_frame_callback(set_credits_opacity);
+
+ // Build credits scroll in animation
+ ctx.credits_scroll_animation = new animation();
+
+ // Trigger credits scroll animation after credits fade in animation ends
+ ctx.credits_fade_in_animation->set_end_callback
+ (
+ [&ctx]()
+ {
+ ctx.credits_scroll_animation->play();
+ }
+ );
+
+ // Add credits animations to animator
+ ctx.animator->add_animation(ctx.credits_fade_in_animation);
+ ctx.animator->add_animation(ctx.credits_scroll_animation);
+
+ // Start credits fade in animation
+ ctx.credits_fade_in_animation->play();
+
+ // Set up credits skipper
+ ctx.input_listener->set_callback
+ (
+ [&ctx](const event_base& event)
+ {
+ auto id = event.get_event_type_id();
+ if (id != mouse_moved_event::event_type_id && id != mouse_wheel_scrolled_event::event_type_id && id != gamepad_axis_moved_event::event_type_id)
+ {
+ if (ctx.credits_text->get_color()[3] > 0.0f)
+ {
+ ctx.input_listener->set_enabled(false);
+
+ // Change state
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::extras_menu(ctx));
+ }
+ }
+ }
+ );
+ ctx.input_listener->set_enabled(true);
+
+ ctx.ui_scene->add_object(ctx.credits_text);
+ ctx.credits_text->update_tweens();
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+credits::~credits()
+{
+ ctx.logger->push_task("Exiting credits state");
+
+ // Disable credits skipper
+ ctx.input_listener->set_enabled(false);
+ ctx.input_listener->set_callback(nullptr);
+
+ // Destruct credits text
+ ctx.ui_scene->remove_object(ctx.credits_text);
+ delete ctx.credits_text;
+ ctx.credits_text = nullptr;
+
+ // Destruct credits animations
+ ctx.animator->remove_animation(ctx.credits_fade_in_animation);
+ ctx.animator->remove_animation(ctx.credits_scroll_animation);
+ delete ctx.credits_fade_in_animation;
+ delete ctx.credits_scroll_animation;
+ ctx.credits_fade_in_animation = nullptr;
+ ctx.credits_scroll_animation = nullptr;
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+} // namespace state
+} // namespace game
diff --git a/src/game/states/credits.hpp b/src/game/state/credits.hpp
similarity index 84%
rename from src/game/states/credits.hpp
rename to src/game/state/credits.hpp
index 6bbf69c..c8da4fe 100644
--- a/src/game/states/credits.hpp
+++ b/src/game/state/credits.hpp
@@ -20,18 +20,17 @@
#ifndef ANTKEEPER_GAME_STATE_CREDITS_HPP
#define ANTKEEPER_GAME_STATE_CREDITS_HPP
-#include "game/context.hpp"
+#include "game/state/base.hpp"
namespace game {
namespace state {
-/// Credits screen game state functions.
-namespace credits {
-
-void enter(game::context* ctx);
-void exit(game::context* ctx);
-
-} // namespace credits
+class credits: public game::state::base
+{
+public:
+ credits(game::context& ctx);
+ virtual ~credits();
+};
} // namespace state
} // namespace game
diff --git a/src/game/states/extras-menu.cpp b/src/game/state/extras-menu.cpp
similarity index 58%
rename from src/game/states/extras-menu.cpp
rename to src/game/state/extras-menu.cpp
index ef49d48..b51e3f0 100644
--- a/src/game/states/extras-menu.cpp
+++ b/src/game/state/extras-menu.cpp
@@ -17,9 +17,9 @@
* along with Antkeeper source code. If not, see .
*/
-#include "game/states/extras-menu.hpp"
-#include "game/states/main-menu.hpp"
-#include "game/states/credits.hpp"
+#include "game/state/extras-menu.hpp"
+#include "game/state/main-menu.hpp"
+#include "game/state/credits.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
@@ -28,21 +28,23 @@
namespace game {
namespace state {
-namespace extras_menu {
-void enter(game::context* ctx)
+extras_menu::extras_menu(game::context& ctx):
+ game::state::base(ctx)
{
+ ctx.logger->push_task("Entering extras menu state");
+
// Construct menu item texts
scene::text* credits_text = new scene::text();
scene::text* back_text = new scene::text();
// Build list of menu item texts
- ctx->menu_item_texts.push_back({credits_text, nullptr});
- ctx->menu_item_texts.push_back({back_text, nullptr});
+ ctx.menu_item_texts.push_back({credits_text, nullptr});
+ 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((*ctx.strings)["extras_menu_credits"]);
+ back_text->set_content((*ctx.strings)["back"]);
// Init menu item index
game::menu::init_menu_item_index(ctx, "extras");
@@ -55,7 +57,7 @@ void enter(game::context* ctx)
game::menu::setup_animations(ctx);
// Construct menu item callbacks
- auto select_credits_callback = [ctx]()
+ auto select_credits_callback = [&ctx]()
{
// Disable controls
game::menu::clear_controls(ctx);
@@ -63,17 +65,21 @@ void enter(game::context* ctx)
game::menu::fade_out
(
ctx,
- [ctx]()
+ [&ctx]()
{
- application::state next_state;
- next_state.name = "credits";
- next_state.enter = std::bind(game::state::credits::enter, ctx);
- next_state.exit = std::bind(game::state::credits::exit, ctx);
- ctx->app->queue_state(next_state);
+ // Queue change to credits state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::credits(ctx));
+ }
+ );
}
);
};
- auto select_back_callback = [ctx]()
+ auto select_back_callback = [&ctx]()
{
// Disable controls
game::menu::clear_controls(ctx);
@@ -81,49 +87,58 @@ void enter(game::context* ctx)
game::menu::fade_out
(
ctx,
- [ctx]()
+ [&ctx]()
{
- application::state next_state;
- next_state.name = "main_menu";
- next_state.enter = std::bind(game::state::main_menu::enter, ctx, false);
- next_state.exit = std::bind(game::state::main_menu::exit, ctx);
- ctx->app->queue_state(next_state);
+ // Queue change to main menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::main_menu(ctx, false));
+ }
+ );
}
);
};
// Build list of menu select callbacks
- ctx->menu_select_callbacks.push_back(select_credits_callback);
- ctx->menu_select_callbacks.push_back(select_back_callback);
+ ctx.menu_select_callbacks.push_back(select_credits_callback);
+ ctx.menu_select_callbacks.push_back(select_back_callback);
// Build list of menu left callbacks
- ctx->menu_left_callbacks.push_back(nullptr);
- ctx->menu_left_callbacks.push_back(nullptr);
+ ctx.menu_left_callbacks.push_back(nullptr);
+ ctx.menu_left_callbacks.push_back(nullptr);
// Build list of menu right callbacks
- ctx->menu_right_callbacks.push_back(nullptr);
- ctx->menu_right_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
// Set menu back callback
- ctx->menu_back_callback = select_back_callback;
+ ctx.menu_back_callback = select_back_callback;
// Queue menu control setup
- ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx));
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
// Fade in menu
game::menu::fade_in(ctx, nullptr);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
}
-void exit(game::context* ctx)
+extras_menu::~extras_menu()
{
+ ctx.logger->push_task("Exiting extras menu state");
+
// Destruct menu
game::menu::clear_controls(ctx);
game::menu::clear_callbacks(ctx);
game::menu::delete_animations(ctx);
game::menu::remove_text_from_ui(ctx);
game::menu::delete_text(ctx);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
}
-} // namespace extras_menu
} // namespace state
} // namespace game
diff --git a/src/game/states/extras-menu.hpp b/src/game/state/extras-menu.hpp
similarity index 84%
rename from src/game/states/extras-menu.hpp
rename to src/game/state/extras-menu.hpp
index ee1a9ac..322fb4a 100644
--- a/src/game/states/extras-menu.hpp
+++ b/src/game/state/extras-menu.hpp
@@ -20,18 +20,17 @@
#ifndef ANTKEEPER_GAME_STATE_EXTRAS_MENU_HPP
#define ANTKEEPER_GAME_STATE_EXTRAS_MENU_HPP
-#include "game/context.hpp"
+#include "game/state/base.hpp"
namespace game {
namespace state {
-/// Extras menu screen game state functions.
-namespace extras_menu {
-
-void enter(game::context* ctx);
-void exit(game::context* ctx);
-
-} // namespace extras_menu
+class extras_menu: public game::state::base
+{
+public:
+ extras_menu(game::context& ctx);
+ virtual ~extras_menu();
+};
} // namespace state
} // namespace game
diff --git a/src/game/states/forage.cpp b/src/game/state/forage.cpp
similarity index 98%
rename from src/game/states/forage.cpp
rename to src/game/state/forage.cpp
index dc7be3b..402599d 100644
--- a/src/game/states/forage.cpp
+++ b/src/game/state/forage.cpp
@@ -17,7 +17,7 @@
* along with Antkeeper source code. If not, see .
*/
-#include "game/states/forage.hpp"
+#include "game/state/forage.hpp"
#include "entity/archetype.hpp"
#include "entity/commands.hpp"
#include "entity/components/observer.hpp"
@@ -203,8 +203,8 @@ void setup_camera(game::context* ctx)
void setup_tools(game::context* ctx)
{
- ctx->entities["camera_tool"] = build_camera_tool(ctx);
- ctx->entities["time_tool"] = build_time_tool(ctx);
+ ctx->entities["camera_tool"] = build_camera_tool(*ctx);
+ ctx->entities["time_tool"] = build_time_tool(*ctx);
// Set active tool
ctx->entities["active_tool"] = ctx->entities["time_tool"];
@@ -477,7 +477,7 @@ void setup_controls(game::context* ctx)
// Use tool
ctx->controls["use_tool"]->set_activated_callback
(
- [ctx]()
+ [&ctx]()
{
if (ctx->entities.count("active_tool"))
{
@@ -490,7 +490,7 @@ void setup_controls(game::context* ctx)
);
ctx->controls["use_tool"]->set_deactivated_callback
(
- [ctx]()
+ [&ctx]()
{
if (ctx->entities.count("active_tool"))
{
@@ -503,7 +503,7 @@ void setup_controls(game::context* ctx)
);
ctx->controls["use_tool"]->set_active_callback
(
- [ctx](float value)
+ [&ctx](float value)
{
if (ctx->entities.count("active_tool"))
{
diff --git a/src/game/states/forage.hpp b/src/game/state/forage.hpp
similarity index 100%
rename from src/game/states/forage.hpp
rename to src/game/state/forage.hpp
diff --git a/src/game/states/gamepad-config-menu.cpp b/src/game/state/gamepad-config-menu.cpp
similarity index 58%
rename from src/game/states/gamepad-config-menu.cpp
rename to src/game/state/gamepad-config-menu.cpp
index fcf9a3f..2a8ff2e 100644
--- a/src/game/states/gamepad-config-menu.cpp
+++ b/src/game/state/gamepad-config-menu.cpp
@@ -17,8 +17,9 @@
* along with Antkeeper source code. If not, see .
*/
-#include "game/states/gamepad-config-menu.hpp"
-#include "game/states/controls-menu.hpp"
+#include "game/state/gamepad-config-menu.hpp"
+#include "game/state/controls-menu.hpp"
+#include "game/context.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
@@ -28,13 +29,110 @@
namespace game {
namespace state {
-namespace gamepad_config_menu {
-static std::string get_binding_string(game::context* ctx, input::control* control)
+gamepad_config_menu::gamepad_config_menu(game::context& ctx):
+ game::state::base(ctx)
+{
+ ctx.logger->push_task("Entering gamepad config menu state");
+
+ // Add camera control menu items
+ add_control_item("move_forward");
+ add_control_item("move_back");
+ add_control_item("move_left");
+ add_control_item("move_right");
+ add_control_item("move_up");
+ add_control_item("move_down");
+
+ // Add application control menu items
+ add_control_item("toggle_fullscreen");
+ add_control_item("screenshot");
+
+ // Construct menu item texts
+ scene::text* back_text = new scene::text();
+
+ // Build list of menu item texts
+ ctx.menu_item_texts.push_back({back_text, nullptr});
+
+ // Set content of menu item texts
+ back_text->set_content((*ctx.strings)["back"]);
+
+ // Init menu item index
+ game::menu::init_menu_item_index(ctx, "gamepad_config");
+
+ game::menu::update_text_color(ctx);
+ game::menu::update_text_font(ctx);
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+ game::menu::add_text_to_ui(ctx);
+ game::menu::setup_animations(ctx);
+
+ // Construct menu item callbacks
+ auto select_back_callback = [&ctx]()
+ {
+ // Disable controls
+ game::menu::clear_controls(ctx);
+
+ game::menu::fade_out
+ (
+ ctx,
+ [&ctx]()
+ {
+ // Queue change to controls menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::controls_menu(ctx));
+ }
+ );
+ }
+ );
+ };
+
+ // Build list of menu select callbacks
+ ctx.menu_select_callbacks.push_back(select_back_callback);
+
+ // Build list of menu left callbacks
+ ctx.menu_left_callbacks.push_back(nullptr);
+
+ // Build list of menu right callbacks
+ ctx.menu_right_callbacks.push_back(nullptr);
+
+ // Set menu back callback
+ ctx.menu_back_callback = select_back_callback;
+
+ // Queue menu control setup
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
+
+ // Fade in menu
+ game::menu::fade_in(ctx, nullptr);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+gamepad_config_menu::~gamepad_config_menu()
+{
+ ctx.logger->push_task("Exiting gamepad config menu state");
+
+ // Destruct menu
+ game::menu::clear_controls(ctx);
+ game::menu::clear_callbacks(ctx);
+ game::menu::delete_animations(ctx);
+ game::menu::remove_text_from_ui(ctx);
+ game::menu::delete_text(ctx);
+
+ // Save control profile
+ game::save_control_profile(ctx);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+std::string gamepad_config_menu::get_binding_string(input::control* control)
{
std::string binding_string;
- auto mappings = ctx->input_event_router->get_mappings(control);
+ auto mappings = ctx.input_event_router->get_mappings(control);
for (input::mapping* mapping: *mappings)
{
std::string mapping_string;
@@ -49,38 +147,38 @@ static std::string get_binding_string(game::context* ctx, input::control* contro
{
case input::gamepad_axis::left_x:
if (axis_mapping->negative)
- mapping_string = (*ctx->strings)["gamepad_left_stick_left"];
+ mapping_string = (*ctx.strings)["gamepad_left_stick_left"];
else
- mapping_string = (*ctx->strings)["gamepad_left_stick_right"];
+ mapping_string = (*ctx.strings)["gamepad_left_stick_right"];
break;
case input::gamepad_axis::left_y:
if (axis_mapping->negative)
- mapping_string = (*ctx->strings)["gamepad_left_stick_up"];
+ mapping_string = (*ctx.strings)["gamepad_left_stick_up"];
else
- mapping_string = (*ctx->strings)["gamepad_left_stick_down"];
+ mapping_string = (*ctx.strings)["gamepad_left_stick_down"];
break;
case input::gamepad_axis::right_x:
if (axis_mapping->negative)
- mapping_string = (*ctx->strings)["gamepad_right_stick_left"];
+ mapping_string = (*ctx.strings)["gamepad_right_stick_left"];
else
- mapping_string = (*ctx->strings)["gamepad_right_stick_right"];
+ mapping_string = (*ctx.strings)["gamepad_right_stick_right"];
break;
case input::gamepad_axis::right_y:
if (axis_mapping->negative)
- mapping_string = (*ctx->strings)["gamepad_right_stick_up"];
+ mapping_string = (*ctx.strings)["gamepad_right_stick_up"];
else
- mapping_string = (*ctx->strings)["gamepad_right_stick_down"];
+ mapping_string = (*ctx.strings)["gamepad_right_stick_down"];
break;
case input::gamepad_axis::left_trigger:
- mapping_string = (*ctx->strings)["gamepad_left_trigger"];
+ mapping_string = (*ctx.strings)["gamepad_left_trigger"];
break;
case input::gamepad_axis::right_trigger:
- mapping_string = (*ctx->strings)["gamepad_right_trigger"];
+ mapping_string = (*ctx.strings)["gamepad_right_trigger"];
break;
default:
@@ -96,63 +194,63 @@ static std::string get_binding_string(game::context* ctx, input::control* contro
switch (button_mapping->button)
{
case input::gamepad_button::a:
- mapping_string = (*ctx->strings)["gamepad_button_a"];
+ mapping_string = (*ctx.strings)["gamepad_button_a"];
break;
case input::gamepad_button::b:
- mapping_string = (*ctx->strings)["gamepad_button_b"];
+ mapping_string = (*ctx.strings)["gamepad_button_b"];
break;
case input::gamepad_button::x:
- mapping_string = (*ctx->strings)["gamepad_button_x"];
+ mapping_string = (*ctx.strings)["gamepad_button_x"];
break;
case input::gamepad_button::y:
- mapping_string = (*ctx->strings)["gamepad_button_y"];
+ mapping_string = (*ctx.strings)["gamepad_button_y"];
break;
case input::gamepad_button::back:
- mapping_string = (*ctx->strings)["gamepad_button_back"];
+ mapping_string = (*ctx.strings)["gamepad_button_back"];
break;
case input::gamepad_button::guide:
- mapping_string = (*ctx->strings)["gamepad_button_guide"];
+ mapping_string = (*ctx.strings)["gamepad_button_guide"];
break;
case input::gamepad_button::start:
- mapping_string = (*ctx->strings)["gamepad_button_start"];
+ mapping_string = (*ctx.strings)["gamepad_button_start"];
break;
case input::gamepad_button::left_stick:
- mapping_string = (*ctx->strings)["gamepad_button_left_stick"];
+ mapping_string = (*ctx.strings)["gamepad_button_left_stick"];
break;
case input::gamepad_button::right_stick:
- mapping_string = (*ctx->strings)["gamepad_button_right_stick"];
+ mapping_string = (*ctx.strings)["gamepad_button_right_stick"];
break;
case input::gamepad_button::left_shoulder:
- mapping_string = (*ctx->strings)["gamepad_button_left_shoulder"];
+ mapping_string = (*ctx.strings)["gamepad_button_left_shoulder"];
break;
case input::gamepad_button::right_shoulder:
- mapping_string = (*ctx->strings)["gamepad_button_right_shoulder"];
+ mapping_string = (*ctx.strings)["gamepad_button_right_shoulder"];
break;
case input::gamepad_button::dpad_up:
- mapping_string = (*ctx->strings)["gamepad_button_dpad_up"];
+ mapping_string = (*ctx.strings)["gamepad_button_dpad_up"];
break;
case input::gamepad_button::dpad_down:
- mapping_string = (*ctx->strings)["gamepad_button_dpad_down"];
+ mapping_string = (*ctx.strings)["gamepad_button_dpad_down"];
break;
case input::gamepad_button::dpad_left:
- mapping_string = (*ctx->strings)["gamepad_button_dpad_left"];
+ mapping_string = (*ctx.strings)["gamepad_button_dpad_left"];
break;
case input::gamepad_button::dpad_right:
- mapping_string = (*ctx->strings)["gamepad_button_dpad_right"];
+ mapping_string = (*ctx.strings)["gamepad_button_dpad_right"];
break;
default:
@@ -181,32 +279,32 @@ static std::string get_binding_string(game::context* ctx, input::control* contro
return binding_string;
}
-static void add_control_item(game::context* ctx, const std::string& control_name)
+void gamepad_config_menu::add_control_item(const std::string& control_name)
{
// Get pointer to control
- input::control* control = ctx->controls[control_name];
+ input::control* control = ctx.controls[control_name];
// Construct texts
scene::text* name_text = new scene::text();
scene::text* value_text = new scene::text();
// Add texts to list of menu item texts
- ctx->menu_item_texts.push_back({name_text, value_text});
+ 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())
+ 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);
// Set content of value text
- value_text->set_content(get_binding_string(ctx, control));
+ value_text->set_content(get_binding_string(control));
- auto select_callback = [ctx, control, value_text]()
+ auto select_callback = [this, &ctx = this->ctx, control, value_text]()
{
// Clear binding string from value text
- value_text->set_content((*ctx->strings)["ellipsis"]);
+ value_text->set_content((*ctx.strings)["ellipsis"]);
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
@@ -214,13 +312,13 @@ static void add_control_item(game::context* ctx, const std::string& control_name
game::menu::clear_controls(ctx);
// Remove gamepad event mappings from control
- ctx->input_event_router->remove_mappings(control, input::mapping_type::gamepad_axis);
- ctx->input_event_router->remove_mappings(control, input::mapping_type::gamepad_button);
+ ctx.input_event_router->remove_mappings(control, input::mapping_type::gamepad_axis);
+ ctx.input_event_router->remove_mappings(control, input::mapping_type::gamepad_button);
// Setup input binding listener
- ctx->input_listener->set_callback
+ ctx.input_listener->set_callback
(
- [ctx, control, value_text](const event_base& event)
+ [this, &ctx, control, value_text](const event_base& event)
{
auto id = event.get_event_type_id();
if (id == gamepad_axis_moved_event::event_type_id)
@@ -230,13 +328,13 @@ static void add_control_item(game::context* ctx, const std::string& control_name
if (std::abs(axis_event.value) < 0.5f)
return;
- ctx->input_event_router->add_mapping(input::gamepad_axis_mapping(control, nullptr, axis_event.axis, (axis_event.value < 0)));
+ ctx.input_event_router->add_mapping(input::gamepad_axis_mapping(control, nullptr, axis_event.axis, (axis_event.value < 0)));
}
else if (id == gamepad_button_pressed_event::event_type_id)
{
// Map gamepad button event to control
const gamepad_button_pressed_event& button_event = static_cast(event);
- ctx->input_event_router->add_mapping(input::gamepad_button_mapping(control, nullptr, button_event.button));
+ ctx.input_event_router->add_mapping(input::gamepad_button_mapping(control, nullptr, button_event.button));
}
else if (id == key_pressed_event::event_type_id)
{
@@ -252,112 +350,26 @@ static void add_control_item(game::context* ctx, const std::string& control_name
}
// Update menu text
- value_text->set_content(get_binding_string(ctx, control));
+ value_text->set_content(this->get_binding_string(control));
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
// Disable input listener
- ctx->input_listener->set_enabled(false);
- ctx->input_listener->set_callback(nullptr);
+ ctx.input_listener->set_enabled(false);
+ ctx.input_listener->set_callback(nullptr);
// Queue menu control setup
- ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx));
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
}
);
- ctx->input_listener->set_enabled(true);
+ ctx.input_listener->set_enabled(true);
};
// Register menu item callbacks
- ctx->menu_select_callbacks.push_back(select_callback);
- ctx->menu_left_callbacks.push_back(nullptr);
- ctx->menu_right_callbacks.push_back(nullptr);
-}
-
-void enter(game::context* ctx)
-{
- // Add camera control menu items
- add_control_item(ctx, "move_forward");
- add_control_item(ctx, "move_back");
- add_control_item(ctx, "move_left");
- add_control_item(ctx, "move_right");
- add_control_item(ctx, "move_up");
- add_control_item(ctx, "move_down");
-
- // Add application control menu items
- add_control_item(ctx, "toggle_fullscreen");
- add_control_item(ctx, "screenshot");
-
- // Construct menu item texts
- scene::text* back_text = new scene::text();
-
- // Build list of menu item texts
- ctx->menu_item_texts.push_back({back_text, nullptr});
-
- // Set content of menu item texts
- back_text->set_content((*ctx->strings)["back"]);
-
- // Init menu item index
- game::menu::init_menu_item_index(ctx, "gamepad_config");
-
- game::menu::update_text_color(ctx);
- game::menu::update_text_font(ctx);
- game::menu::align_text(ctx);
- game::menu::update_text_tweens(ctx);
- game::menu::add_text_to_ui(ctx);
- game::menu::setup_animations(ctx);
-
- // Construct menu item callbacks
- auto select_back_callback = [ctx]()
- {
- // Disable controls
- game::menu::clear_controls(ctx);
-
- game::menu::fade_out
- (
- ctx,
- [ctx]()
- {
- application::state next_state;
- next_state.name = "controls_menu";
- next_state.enter = std::bind(game::state::controls_menu::enter, ctx);
- next_state.exit = std::bind(game::state::controls_menu::exit, ctx);
- ctx->app->queue_state(next_state);
- }
- );
- };
-
- // Build list of menu select callbacks
- ctx->menu_select_callbacks.push_back(select_back_callback);
-
- // Build list of menu left callbacks
- ctx->menu_left_callbacks.push_back(nullptr);
-
- // Build list of menu right callbacks
- ctx->menu_right_callbacks.push_back(nullptr);
-
- // Set menu back callback
- ctx->menu_back_callback = select_back_callback;
-
- // Queue menu control setup
- ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx));
-
- // Fade in menu
- game::menu::fade_in(ctx, nullptr);
-}
-
-void exit(game::context* ctx)
-{
- // Destruct menu
- game::menu::clear_controls(ctx);
- game::menu::clear_callbacks(ctx);
- game::menu::delete_animations(ctx);
- game::menu::remove_text_from_ui(ctx);
- game::menu::delete_text(ctx);
-
- // Save control profile
- game::save_control_profile(ctx);
+ ctx.menu_select_callbacks.push_back(select_callback);
+ ctx.menu_left_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
}
-} // namespace gamepad_config_menu
} // namespace state
} // namespace game
diff --git a/src/game/states/gamepad-config-menu.hpp b/src/game/state/gamepad-config-menu.hpp
similarity index 75%
rename from src/game/states/gamepad-config-menu.hpp
rename to src/game/state/gamepad-config-menu.hpp
index dc5f56b..f7e337f 100644
--- a/src/game/states/gamepad-config-menu.hpp
+++ b/src/game/state/gamepad-config-menu.hpp
@@ -20,18 +20,22 @@
#ifndef ANTKEEPER_GAME_STATE_GAMEPAD_CONFIG_MENU_HPP
#define ANTKEEPER_GAME_STATE_GAMEPAD_CONFIG_MENU_HPP
-#include "game/context.hpp"
+#include "game/state/base.hpp"
+#include "input/control.hpp"
namespace game {
namespace state {
-/// Gamepad config menu screen game state functions.
-namespace gamepad_config_menu {
-
-void enter(game::context* ctx);
-void exit(game::context* ctx);
-
-} // namespace gamepad_config_menu
+class gamepad_config_menu: public game::state::base
+{
+public:
+ gamepad_config_menu(game::context& ctx);
+ virtual ~gamepad_config_menu();
+
+private:
+ std::string get_binding_string(input::control* control);
+ void add_control_item(const std::string& control_name);
+};
} // namespace state
} // namespace game
diff --git a/src/game/state/graphics-menu.cpp b/src/game/state/graphics-menu.cpp
new file mode 100644
index 0000000..47cac30
--- /dev/null
+++ b/src/game/state/graphics-menu.cpp
@@ -0,0 +1,357 @@
+/*
+ * Copyright (C) 2021 Christopher J. Howard
+ *
+ * This file is part of Antkeeper source code.
+ *
+ * Antkeeper source code is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Antkeeper source code is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Antkeeper source code. If not, see .
+ */
+
+#include "game/state/graphics-menu.hpp"
+#include "game/state/options-menu.hpp"
+#include "application.hpp"
+#include "scene/text.hpp"
+#include "debug/logger.hpp"
+#include "game/fonts.hpp"
+#include "game/menu.hpp"
+#include "game/graphics.hpp"
+#include "animation/timeline.hpp"
+
+namespace game {
+namespace state {
+
+static void update_value_text_content(game::context* ctx);
+
+graphics_menu::graphics_menu(game::context& ctx):
+ game::state::base(ctx)
+{
+ ctx.logger->push_task("Entering graphics menu state");
+
+ // Construct menu item texts
+ scene::text* fullscreen_name_text = new scene::text();
+ scene::text* fullscreen_value_text = new scene::text();
+ scene::text* resolution_name_text = new scene::text();
+ scene::text* resolution_value_text = new scene::text();
+ scene::text* v_sync_name_text = new scene::text();
+ scene::text* v_sync_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* dyslexia_font_name_text = new scene::text();
+ scene::text* dyslexia_font_value_text = new scene::text();
+ scene::text* back_text = new scene::text();
+
+ // Build list of menu item texts
+ ctx.menu_item_texts.push_back({fullscreen_name_text, fullscreen_value_text});
+ 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({font_size_name_text, font_size_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"]);
+ 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"]);
+ update_value_text_content();
+
+ // Init menu item index
+ game::menu::init_menu_item_index(ctx, "graphics");
+
+ game::menu::update_text_color(ctx);
+ game::menu::update_text_font(ctx);
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+ game::menu::add_text_to_ui(ctx);
+ game::menu::setup_animations(ctx);
+
+ // Construct menu item callbacks
+ auto toggle_fullscreen_callback = [this, &ctx]()
+ {
+ bool fullscreen = !ctx.app->is_fullscreen();
+
+ ctx.app->set_fullscreen(fullscreen);
+
+ if (!fullscreen)
+ {
+ int2 resolution;
+ resolution.x = (*ctx.config)["windowed_resolution"][0].get();
+ resolution.y = (*ctx.config)["windowed_resolution"][1].get();
+
+ 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;
+ };
+
+ auto increase_resolution_callback = [this, &ctx]()
+ {
+ // Increase resolution
+ if (ctx.controls["menu_modifier"]->is_active())
+ ctx.render_resolution_scale += 0.05f;
+ else
+ ctx.render_resolution_scale += 0.25f;
+
+ // Limit resolution
+ if (ctx.render_resolution_scale > 2.0f)
+ ctx.render_resolution_scale = 2.0f;
+
+ // Resize framebuffers
+ game::graphics::change_render_resolution(ctx, ctx.render_resolution_scale);
+
+ // Update text
+ this->update_value_text_content();
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+
+ // Update config
+ (*ctx.config)["render_resolution"] = ctx.render_resolution_scale;
+ };
+
+ auto decrease_resolution_callback = [this, &ctx]()
+ {
+ // Increase resolution
+ if (ctx.controls["menu_modifier"]->is_active())
+ ctx.render_resolution_scale -= 0.05f;
+ else
+ ctx.render_resolution_scale -= 0.25f;
+
+ // Limit resolution
+ if (ctx.render_resolution_scale < 0.25f)
+ ctx.render_resolution_scale = 0.25f;
+
+ // Resize framebuffers
+ game::graphics::change_render_resolution(ctx, ctx.render_resolution_scale);
+
+ // Update text
+ this->update_value_text_content();
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+
+ // Update config
+ (*ctx.config)["render_resolution"] = ctx.render_resolution_scale;
+ };
+
+ auto toggle_v_sync_callback = [this, &ctx]()
+ {
+ bool v_sync = !ctx.app->get_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 increase_font_size_callback = [this, &ctx]()
+ {
+ // Increase font size
+ if (ctx.controls["menu_modifier"]->is_active())
+ ctx.font_size += 0.01f;
+ else
+ ctx.font_size += 0.1f;
+
+ // Limit font size
+ if (ctx.font_size > 2.0f)
+ ctx.font_size = 2.0f;
+
+ // Update value text
+ this->update_value_text_content();
+
+ // Update config
+ (*ctx.config)["font_size"] = ctx.font_size;
+
+ // Reload fonts
+ ctx.logger->push_task("Reloading fonts");
+ try
+ {
+ game::load_fonts(ctx);
+ }
+ catch (...)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+ }
+ ctx.logger->pop_task(EXIT_SUCCESS);
+
+ // Refresh and realign text
+ game::menu::refresh_text(ctx);
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+ };
+
+ auto decrease_font_size_callback = [this, &ctx]()
+ {
+ // Decrease font size
+ if (ctx.controls["menu_modifier"]->is_active())
+ ctx.font_size -= 0.01f;
+ else
+ ctx.font_size -= 0.1f;
+
+ // Limit font size
+ if (ctx.font_size < 0.1f)
+ ctx.font_size = 0.1f;
+
+ // Update value text
+ this->update_value_text_content();
+
+ // Update config
+ (*ctx.config)["font_size"] = ctx.font_size;
+
+ // Reload fonts
+ ctx.logger->push_task("Reloading fonts");
+ try
+ {
+ game::load_fonts(ctx);
+ }
+ catch (...)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+ }
+ ctx.logger->pop_task(EXIT_SUCCESS);
+
+ // Refresh and realign text
+ game::menu::refresh_text(ctx);
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+ };
+
+ auto toggle_dyslexia_font_callback = [this, &ctx]()
+ {
+ ctx.dyslexia_font = !ctx.dyslexia_font;
+
+ // Update value text
+ this->update_value_text_content();
+
+ // Save dyslexia font config
+ (*ctx.config)["dyslexia_font"] = ctx.dyslexia_font;
+
+ // Reload fonts
+ ctx.logger->push_task("Reloading fonts");
+ try
+ {
+ game::load_fonts(ctx);
+ }
+ catch (...)
+ {
+ ctx.logger->pop_task(EXIT_FAILURE);
+ }
+ ctx.logger->pop_task(EXIT_SUCCESS);
+
+ // Refresh and realign text
+ game::menu::refresh_text(ctx);
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+ };
+ auto select_back_callback = [&ctx]()
+ {
+ // Disable controls
+ game::menu::clear_controls(ctx);
+
+ game::menu::fade_out
+ (
+ ctx,
+ [&ctx]()
+ {
+ // Queue change to options menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::options_menu(ctx));
+ }
+ );
+ }
+ );
+ };
+
+ // Build list of menu select callbacks
+ ctx.menu_select_callbacks.push_back(toggle_fullscreen_callback);
+ 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(increase_font_size_callback);
+ ctx.menu_select_callbacks.push_back(toggle_dyslexia_font_callback);
+ ctx.menu_select_callbacks.push_back(select_back_callback);
+
+ // Build list of menu left callbacks
+ ctx.menu_left_callbacks.push_back(toggle_fullscreen_callback);
+ 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(decrease_font_size_callback);
+ ctx.menu_left_callbacks.push_back(toggle_dyslexia_font_callback);
+ ctx.menu_left_callbacks.push_back(nullptr);
+
+ // Build list of menu right callbacks
+ ctx.menu_right_callbacks.push_back(toggle_fullscreen_callback);
+ 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(increase_font_size_callback);
+ ctx.menu_right_callbacks.push_back(toggle_dyslexia_font_callback);
+ ctx.menu_right_callbacks.push_back(nullptr);
+
+ // Set menu back callback
+ ctx.menu_back_callback = select_back_callback;
+
+ // Queue menu control setup
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
+
+ // Fade in menu
+ game::menu::fade_in(ctx, nullptr);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+graphics_menu::~graphics_menu()
+{
+ ctx.logger->push_task("Exiting graphics menu state");
+
+ // Destruct menu
+ game::menu::clear_controls(ctx);
+ game::menu::clear_callbacks(ctx);
+ game::menu::delete_animations(ctx);
+ game::menu::remove_text_from_ui(ctx);
+ game::menu::delete_text(ctx);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+void graphics_menu::update_value_text_content()
+{
+ bool fullscreen = ctx.app->is_fullscreen();
+ float resolution = ctx.render_resolution_scale;
+ bool v_sync = ctx.app->get_v_sync();
+ float font_size = ctx.font_size;
+ bool dyslexia_font = ctx.dyslexia_font;
+
+ const std::string string_on = (*ctx.strings)["on"];
+ const std::string string_off = (*ctx.strings)["off"];
+
+ 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(std::round(resolution * 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(std::to_string(static_cast(std::round(font_size * 100.0f))) + "%");
+ std::get<1>(ctx.menu_item_texts[4])->set_content((dyslexia_font) ? string_on : string_off);
+}
+
+} // namespace state
+} // namespace game
diff --git a/src/game/states/graphics-menu.hpp b/src/game/state/graphics-menu.hpp
similarity index 83%
rename from src/game/states/graphics-menu.hpp
rename to src/game/state/graphics-menu.hpp
index 8a6c246..02db3f4 100644
--- a/src/game/states/graphics-menu.hpp
+++ b/src/game/state/graphics-menu.hpp
@@ -20,18 +20,20 @@
#ifndef ANTKEEPER_GAME_STATE_GRAPHICS_MENU_HPP
#define ANTKEEPER_GAME_STATE_GRAPHICS_MENU_HPP
-#include "game/context.hpp"
+#include "game/state/base.hpp"
namespace game {
namespace state {
-/// Sound menu screen game state functions.
-namespace graphics_menu {
-
-void enter(game::context* ctx);
-void exit(game::context* ctx);
-
-} // namespace graphics_menu
+class graphics_menu: public game::state::base
+{
+public:
+ graphics_menu(game::context& ctx);
+ virtual ~graphics_menu();
+
+private:
+ void update_value_text_content();
+};
} // namespace state
} // namespace game
diff --git a/src/game/states/keyboard-config-menu.cpp b/src/game/state/keyboard-config-menu.cpp
similarity index 62%
rename from src/game/states/keyboard-config-menu.cpp
rename to src/game/state/keyboard-config-menu.cpp
index a496b5b..d01b1f0 100644
--- a/src/game/states/keyboard-config-menu.cpp
+++ b/src/game/state/keyboard-config-menu.cpp
@@ -17,8 +17,8 @@
* along with Antkeeper source code. If not, see .
*/
-#include "game/states/keyboard-config-menu.hpp"
-#include "game/states/controls-menu.hpp"
+#include "game/state/keyboard-config-menu.hpp"
+#include "game/state/controls-menu.hpp"
#include "application.hpp"
#include "scene/text.hpp"
#include "debug/logger.hpp"
@@ -28,13 +28,110 @@
namespace game {
namespace state {
-namespace keyboard_config_menu {
-static std::string get_binding_string(game::context* ctx, input::control* control)
+keyboard_config_menu::keyboard_config_menu(game::context& ctx):
+ game::state::base(ctx)
+{
+ ctx.logger->push_task("Entering keyboard config menu state");
+
+ // Add camera control menu items
+ add_control_item("move_forward");
+ add_control_item("move_back");
+ add_control_item("move_left");
+ add_control_item("move_right");
+ add_control_item("move_up");
+ add_control_item("move_down");
+
+ // Add application control menu items
+ add_control_item("toggle_fullscreen");
+ add_control_item("screenshot");
+
+ // Construct menu item texts
+ scene::text* back_text = new scene::text();
+
+ // Build list of menu item texts
+ ctx.menu_item_texts.push_back({back_text, nullptr});
+
+ // Set content of menu item texts
+ back_text->set_content((*ctx.strings)["back"]);
+
+ // Init menu item index
+ game::menu::init_menu_item_index(ctx, "keyboard_config");
+
+ game::menu::update_text_color(ctx);
+ game::menu::update_text_font(ctx);
+ game::menu::align_text(ctx);
+ game::menu::update_text_tweens(ctx);
+ game::menu::add_text_to_ui(ctx);
+ game::menu::setup_animations(ctx);
+
+ // Construct menu item callbacks
+ auto select_back_callback = [&ctx]()
+ {
+ // Disable controls
+ game::menu::clear_controls(ctx);
+
+ game::menu::fade_out
+ (
+ ctx,
+ [&ctx]()
+ {
+ // Queue change to controls menu state
+ ctx.function_queue.push
+ (
+ [&ctx]()
+ {
+ ctx.state_machine.pop();
+ ctx.state_machine.emplace(new game::state::controls_menu(ctx));
+ }
+ );
+ }
+ );
+ };
+
+ // Build list of menu select callbacks
+ ctx.menu_select_callbacks.push_back(select_back_callback);
+
+ // Build list of menu left callbacks
+ ctx.menu_left_callbacks.push_back(nullptr);
+
+ // Build list of menu right callbacks
+ ctx.menu_right_callbacks.push_back(nullptr);
+
+ // Set menu back callback
+ ctx.menu_back_callback = select_back_callback;
+
+ // Queue menu control setup
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
+
+ // Fade in menu
+ game::menu::fade_in(ctx, nullptr);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+keyboard_config_menu::~keyboard_config_menu()
+{
+ ctx.logger->push_task("Exiting keyboard config menu state");
+
+ // Destruct menu
+ game::menu::clear_controls(ctx);
+ game::menu::clear_callbacks(ctx);
+ game::menu::delete_animations(ctx);
+ game::menu::remove_text_from_ui(ctx);
+ game::menu::delete_text(ctx);
+
+ // Save control profile
+ game::save_control_profile(ctx);
+
+ ctx.logger->pop_task(EXIT_SUCCESS);
+}
+
+std::string keyboard_config_menu::get_binding_string(input::control* control)
{
std::string binding_string;
- auto mappings = ctx->input_event_router->get_mappings(control);
+ auto mappings = ctx.input_event_router->get_mappings(control);
for (input::mapping* mapping: *mappings)
{
std::string mapping_string;
@@ -57,19 +154,19 @@ static std::string get_binding_string(game::context* ctx, input::control* contro
switch (wheel_mapping->axis)
{
case input::mouse_wheel_axis::negative_x:
- mapping_string = (*ctx->strings)["mouse_wheel_left"];
+ mapping_string = (*ctx.strings)["mouse_wheel_left"];
break;
case input::mouse_wheel_axis::positive_x:
- mapping_string = (*ctx->strings)["mouse_wheel_right"];
+ mapping_string = (*ctx.strings)["mouse_wheel_right"];
break;
case input::mouse_wheel_axis::negative_y:
- mapping_string = (*ctx->strings)["mouse_wheel_down"];
+ mapping_string = (*ctx.strings)["mouse_wheel_down"];
break;
case input::mouse_wheel_axis::positive_y:
- mapping_string = (*ctx->strings)["mouse_wheel_up"];
+ mapping_string = (*ctx.strings)["mouse_wheel_up"];
break;
default:
@@ -84,19 +181,19 @@ static std::string get_binding_string(game::context* ctx, input::control* contro
if (button_mapping->button == 1)
{
- mapping_string = (*ctx->strings)["mouse_button_left"];
+ mapping_string = (*ctx.strings)["mouse_button_left"];
}
else if (button_mapping->button == 2)
{
- mapping_string = (*ctx->strings)["mouse_button_middle"];
+ mapping_string = (*ctx.strings)["mouse_button_middle"];
}
else if (button_mapping->button == 3)
{
- mapping_string = (*ctx->strings)["mouse_button_right"];
+ mapping_string = (*ctx.strings)["mouse_button_right"];
}
else
{
- const std::string& format = (*ctx->strings)["mouse_button_n"];
+ const std::string& format = (*ctx.strings)["mouse_button_n"];
char buffer[64];
std::snprintf(buffer, 64, format.c_str(), button_mapping->button);
mapping_string = buffer;
@@ -124,32 +221,32 @@ static std::string get_binding_string(game::context* ctx, input::control* contro
return binding_string;
}
-static void add_control_item(game::context* ctx, const std::string& control_name)
+void keyboard_config_menu::add_control_item(const std::string& control_name)
{
// Get pointer to control
- input::control* control = ctx->controls[control_name];
+ input::control* control = ctx.controls[control_name];
// Construct texts
scene::text* name_text = new scene::text();
scene::text* value_text = new scene::text();
// Add texts to list of menu item texts
- ctx->menu_item_texts.push_back({name_text, value_text});
+ 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())
+ 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);
// Set content of value text
- value_text->set_content(get_binding_string(ctx, control));
+ value_text->set_content(get_binding_string( control));
- auto select_callback = [ctx, control, value_text]()
+ auto select_callback = [this, &ctx = this->ctx, control, value_text]()
{
// Clear binding string from value text
- value_text->set_content((*ctx->strings)["ellipsis"]);
+ value_text->set_content((*ctx.strings)["ellipsis"]);
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
@@ -157,15 +254,15 @@ static void add_control_item(game::context* ctx, const std::string& control_name
game::menu::clear_controls(ctx);
// Remove keyboard and mouse event mappings from control
- ctx->input_event_router->remove_mappings(control, input::mapping_type::key);
- ctx->input_event_router->remove_mappings(control, input::mapping_type::mouse_motion);
- ctx->input_event_router->remove_mappings(control, input::mapping_type::mouse_wheel);
- ctx->input_event_router->remove_mappings(control, input::mapping_type::mouse_button);
+ ctx.input_event_router->remove_mappings(control, input::mapping_type::key);
+ ctx.input_event_router->remove_mappings(control, input::mapping_type::mouse_motion);
+ ctx.input_event_router->remove_mappings(control, input::mapping_type::mouse_wheel);
+ ctx.input_event_router->remove_mappings(control, input::mapping_type::mouse_button);
// Setup input binding listener
- ctx->input_listener->set_callback
+ ctx.input_listener->set_callback
(
- [ctx, control, value_text](const event_base& event)
+ [this, &ctx, control, value_text](const event_base& event)
{
auto id = event.get_event_type_id();
if (id == key_pressed_event::event_type_id)
@@ -174,7 +271,7 @@ static void add_control_item(game::context* ctx, const std::string& control_name
const key_pressed_event& key_event = static_cast(event);
if (key_event.scancode != input::scancode::escape && key_event.scancode != input::scancode::backspace)
- ctx->input_event_router->add_mapping(input::key_mapping(control, key_event.keyboard, key_event.scancode));
+ ctx.input_event_router->add_mapping(input::key_mapping(control, key_event.keyboard, key_event.scancode));
}
else if (id == mouse_wheel_scrolled_event::event_type_id)
{
@@ -193,13 +290,13 @@ static void add_control_item(game::context* ctx, const std::string& control_name
else
return;
- ctx->input_event_router->add_mapping(input::mouse_wheel_mapping(control, wheel_event.mouse, axis));
+ ctx.input_event_router->add_mapping(input::mouse_wheel_mapping(control, wheel_event.mouse, axis));
}
else if (id == mouse_button_pressed_event::event_type_id)
{
// Map mouse button pressed event to control
const mouse_button_pressed_event& button_event = static_cast(event);
- ctx->input_event_router->add_mapping(input::mouse_button_mapping(control, button_event.mouse, button_event.button));
+ ctx.input_event_router->add_mapping(input::mouse_button_mapping(control, button_event.mouse, button_event.button));
}
else
{
@@ -207,112 +304,26 @@ static void add_control_item(game::context* ctx, const std::string& control_name
}
// Update menu text
- value_text->set_content(get_binding_string(ctx, control));
+ value_text->set_content(this->get_binding_string(control));
game::menu::align_text(ctx);
game::menu::update_text_tweens(ctx);
// Disable input listener
- ctx->input_listener->set_enabled(false);
- ctx->input_listener->set_callback(nullptr);
+ ctx.input_listener->set_enabled(false);
+ ctx.input_listener->set_callback(nullptr);
// Queue menu control setup
- ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx));
+ ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx)));
}
);
- ctx->input_listener->set_enabled(true);
+ ctx.input_listener->set_enabled(true);
};
// Register menu item callbacks
- ctx->menu_select_callbacks.push_back(select_callback);
- ctx->menu_left_callbacks.push_back(nullptr);
- ctx->menu_right_callbacks.push_back(nullptr);
-}
-
-void enter(game::context* ctx)
-{
- // Add camera control menu items
- add_control_item(ctx, "move_forward");
- add_control_item(ctx, "move_back");
- add_control_item(ctx, "move_left");
- add_control_item(ctx, "move_right");
- add_control_item(ctx, "move_up");
- add_control_item(ctx, "move_down");
-
- // Add application control menu items
- add_control_item(ctx, "toggle_fullscreen");
- add_control_item(ctx, "screenshot");
-
- // Construct menu item texts
- scene::text* back_text = new scene::text();
-
- // Build list of menu item texts
- ctx->menu_item_texts.push_back({back_text, nullptr});
-
- // Set content of menu item texts
- back_text->set_content((*ctx->strings)["back"]);
-
- // Init menu item index
- game::menu::init_menu_item_index(ctx, "keyboard_config");
-
- game::menu::update_text_color(ctx);
- game::menu::update_text_font(ctx);
- game::menu::align_text(ctx);
- game::menu::update_text_tweens(ctx);
- game::menu::add_text_to_ui(ctx);
- game::menu::setup_animations(ctx);
-
- // Construct menu item callbacks
- auto select_back_callback = [ctx]()
- {
- // Disable controls
- game::menu::clear_controls(ctx);
-
- game::menu::fade_out
- (
- ctx,
- [ctx]()
- {
- application::state next_state;
- next_state.name = "controls_menu";
- next_state.enter = std::bind(game::state::controls_menu::enter, ctx);
- next_state.exit = std::bind(game::state::controls_menu::exit, ctx);
- ctx->app->queue_state(next_state);
- }
- );
- };
-
- // Build list of menu select callbacks
- ctx->menu_select_callbacks.push_back(select_back_callback);
-
- // Build list of menu left callbacks
- ctx->menu_left_callbacks.push_back(nullptr);
-
- // Build list of menu right callbacks
- ctx->menu_right_callbacks.push_back(nullptr);
-
- // Set menu back callback
- ctx->menu_back_callback = select_back_callback;
-
- // Queue menu control setup
- ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx));
-
- // Fade in menu
- game::menu::fade_in(ctx, nullptr);
-}
-
-void exit(game::context* ctx)
-{
- // Destruct menu
- game::menu::clear_controls(ctx);
- game::menu::clear_callbacks(ctx);
- game::menu::delete_animations(ctx);
- game::menu::remove_text_from_ui(ctx);
- game::menu::delete_text(ctx);
-
- // Save control profile
- game::save_control_profile(ctx);
+ ctx.menu_select_callbacks.push_back(select_callback);
+ ctx.menu_left_callbacks.push_back(nullptr);
+ ctx.menu_right_callbacks.push_back(nullptr);
}
-} // namespace keyboard_config_menu
} // namespace state
} // namespace game
diff --git a/src/game/states/keyboard-config-menu.hpp b/src/game/state/keyboard-config-menu.hpp
similarity index 74%
rename from src/game/states/keyboard-config-menu.hpp
rename to src/game/state/keyboard-config-menu.hpp
index b180e9e..c823930 100644
--- a/src/game/states/keyboard-config-menu.hpp
+++ b/src/game/state/keyboard-config-menu.hpp
@@ -20,18 +20,23 @@
#ifndef ANTKEEPER_GAME_STATE_KEYBOARD_CONFIG_MENU_HPP
#define ANTKEEPER_GAME_STATE_KEYBOARD_CONFIG_MENU_HPP
-#include "game/context.hpp"
+#include "game/state/base.hpp"
+#include "input/control.hpp"
+#include