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 namespace game { namespace state { -/// Keyboard/mouse config menu screen game state functions. -namespace keyboard_config_menu { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace keyboard_config_menu +class keyboard_config_menu: public game::state::base +{ +public: + keyboard_config_menu(game::context& ctx); + virtual ~keyboard_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/states/language-menu.cpp b/src/game/state/language-menu.cpp similarity index 51% rename from src/game/states/language-menu.cpp rename to src/game/state/language-menu.cpp index 75423f6..2a3799f 100644 --- a/src/game/states/language-menu.cpp +++ b/src/game/state/language-menu.cpp @@ -17,8 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#include "game/states/language-menu.hpp" -#include "game/states/options-menu.hpp" +#include "game/state/language-menu.hpp" +#include "game/state/options-menu.hpp" #include "application.hpp" #include "scene/text.hpp" #include "debug/logger.hpp" @@ -27,31 +27,23 @@ namespace game { namespace state { -namespace language_menu { -static void update_text_content(game::context* ctx) +language_menu::language_menu(game::context& ctx): + game::state::base(ctx) { - auto [language_name, language_value] = ctx->menu_item_texts[0]; - auto [back_name, back_value] = ctx->menu_item_texts[1]; + ctx.logger->push_task("Entering language menu state"); - language_name->set_content((*ctx->strings)["language_menu_language"]); - language_value->set_content((*ctx->strings)["language_name"]); - back_name->set_content((*ctx->strings)["back"]); -} - -void enter(game::context* ctx) -{ // Construct menu item texts scene::text* language_name_text = new scene::text(); scene::text* language_value_text = new scene::text(); scene::text* back_text = new scene::text(); // Build list of menu item texts - ctx->menu_item_texts.push_back({language_name_text, language_value_text}); - ctx->menu_item_texts.push_back({back_text, nullptr}); + ctx.menu_item_texts.push_back({language_name_text, language_value_text}); + ctx.menu_item_texts.push_back({back_text, nullptr}); // Set content of menu item texts - update_text_content(ctx); + update_text_content(); // Init menu item index game::menu::init_menu_item_index(ctx, "language"); @@ -64,75 +56,75 @@ void enter(game::context* ctx) game::menu::setup_animations(ctx); // Construct menu item callbacks - auto next_language_callback = [ctx]() + auto next_language_callback = [this, &ctx]() { // Increment language index - ++ctx->language_index; - if (ctx->language_index >= ctx->language_count) - ctx->language_index = 0; + ++ctx.language_index; + if (ctx.language_index >= ctx.language_count) + ctx.language_index = 0; // Find corresponding language code and strings - ctx->language_code = (*ctx->string_table)[0][ctx->language_index + 2]; - ctx->strings = &ctx->string_table_map[ctx->language_code]; + ctx.language_code = (*ctx.string_table)[0][ctx.language_index + 2]; + ctx.strings = &ctx.string_table_map[ctx.language_code]; // Update language in config - (*ctx->config)["language"] = ctx->language_code; + (*ctx.config)["language"] = ctx.language_code; - ctx->logger->log("Language changed to \"" + ctx->language_code + "\""); + ctx.logger->log("Language changed to \"" + ctx.language_code + "\""); // Reload fonts - ctx->logger->push_task("Reloading fonts"); + ctx.logger->push_task("Reloading fonts"); try { game::load_fonts(ctx); } 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); game::menu::update_text_font(ctx); - update_text_content(ctx); + this->update_text_content(); game::menu::refresh_text(ctx); game::menu::align_text(ctx); game::menu::update_text_tweens(ctx); }; - auto previous_language_callback = [ctx]() + auto previous_language_callback = [this, &ctx]() { // Increment language index - --ctx->language_index; - if (ctx->language_index < 0) - ctx->language_index = ctx->language_count - 1; + --ctx.language_index; + if (ctx.language_index < 0) + ctx.language_index = ctx.language_count - 1; // Find corresponding language code and strings - ctx->language_code = (*ctx->string_table)[0][ctx->language_index + 2]; - ctx->strings = &ctx->string_table_map[ctx->language_code]; + ctx.language_code = (*ctx.string_table)[0][ctx.language_index + 2]; + ctx.strings = &ctx.string_table_map[ctx.language_code]; // Update language in config - (*ctx->config)["language"] = ctx->language_code; + (*ctx.config)["language"] = ctx.language_code; - ctx->logger->log("Language changed to \"" + ctx->language_code + "\""); + ctx.logger->log("Language changed to \"" + ctx.language_code + "\""); // Reload fonts - ctx->logger->push_task("Reloading fonts"); + ctx.logger->push_task("Reloading fonts"); try { game::load_fonts(ctx); } 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); game::menu::update_text_font(ctx); - update_text_content(ctx); + this->update_text_content(); game::menu::refresh_text(ctx); game::menu::align_text(ctx); game::menu::update_text_tweens(ctx); }; - auto select_back_callback = [ctx]() + auto select_back_callback = [&ctx]() { // Disable controls game::menu::clear_controls(ctx); @@ -140,49 +132,68 @@ 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(next_language_callback); - ctx->menu_select_callbacks.push_back(select_back_callback); + ctx.menu_select_callbacks.push_back(next_language_callback); + ctx.menu_select_callbacks.push_back(select_back_callback); // Build list of menu left callbacks - ctx->menu_left_callbacks.push_back(previous_language_callback); - ctx->menu_left_callbacks.push_back(nullptr); + ctx.menu_left_callbacks.push_back(previous_language_callback); + ctx.menu_left_callbacks.push_back(nullptr); // Build list of menu right callbacks - ctx->menu_right_callbacks.push_back(next_language_callback); - ctx->menu_right_callbacks.push_back(nullptr); + ctx.menu_right_callbacks.push_back(next_language_callback); + 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) +language_menu::~language_menu() { + ctx.logger->push_task("Exiting language 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 language_menu::update_text_content() +{ + auto [language_name, language_value] = ctx.menu_item_texts[0]; + auto [back_name, back_value] = ctx.menu_item_texts[1]; + + language_name->set_content((*ctx.strings)["language_menu_language"]); + language_value->set_content((*ctx.strings)["language_name"]); + back_name->set_content((*ctx.strings)["back"]); } -} // namespace language_menu } // namespace state } // namespace game diff --git a/src/game/states/language-menu.hpp b/src/game/state/language-menu.hpp similarity index 83% rename from src/game/states/language-menu.hpp rename to src/game/state/language-menu.hpp index b247c98..cec610a 100644 --- a/src/game/states/language-menu.hpp +++ b/src/game/state/language-menu.hpp @@ -20,18 +20,21 @@ #ifndef ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP #define ANTKEEPER_GAME_STATE_LANGUAGE_MENU_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Language menu screen game state functions. -namespace language_menu { +class language_menu: public game::state::base +{ +public: + language_menu(game::context& ctx); + virtual ~language_menu(); + +private: + void update_text_content(); +}; -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace language_menu } // namespace state } // namespace game diff --git a/src/game/state/main-menu.cpp b/src/game/state/main-menu.cpp new file mode 100644 index 0000000..d188aec --- /dev/null +++ b/src/game/state/main-menu.cpp @@ -0,0 +1,284 @@ +/* + * 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/main-menu.hpp" +#include "game/state/options-menu.hpp" +#include "game/state/extras-menu.hpp" +#include "game/state/forage.hpp" +#include "game/state/nuptial-flight.hpp" +#include "game/menu.hpp" +#include "render/passes/clear-pass.hpp" +#include "resources/resource-manager.hpp" +#include "render/model.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" +#include "animation/screen-transition.hpp" +#include "animation/ease.hpp" +#include "animation/timeline.hpp" +#include "application.hpp" +#include + +namespace game { +namespace state { + +main_menu::main_menu(game::context& ctx, bool fade_in): + game::state::base(ctx) +{ + ctx.logger->push_task("Entering main menu state"); + + ctx.ui_clear_pass->set_cleared_buffers(true, true, false); + + // Construct title text + ctx.title_text = new scene::text(); + ctx.title_text->set_material(&ctx.title_font_material); + ctx.title_text->set_font(&ctx.title_font); + ctx.title_text->set_color({1.0f, 1.0f, 1.0f, 1.0f}); + ctx.title_text->set_content((*ctx.strings)["title_antkeeper"]); + + // Align title text + const auto& title_aabb = static_cast&>(ctx.title_text->get_local_bounds()); + float title_w = title_aabb.max_point.x - title_aabb.min_point.x; + float title_h = title_aabb.max_point.y - title_aabb.min_point.y; + ctx.title_text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (ctx.app->get_viewport_dimensions().y / 3.0f) / 2.0f), 0.0f}); + ctx.title_text->update_tweens(); + + // Add title text to UI + ctx.ui_scene->add_object(ctx.title_text); + + // Construct title fade animation + ctx.title_fade_animation = new animation(); + animation_channel* opacity_channel = ctx.title_fade_animation->add_channel(0); + + ctx.title_fade_animation->set_frame_callback + ( + [&ctx](int channel, const float& opacity) + { + float4 color = ctx.title_text->get_color(); + color[3] = opacity; + ctx.title_text->set_color(color); + } + ); + ctx.animator->add_animation(ctx.title_fade_animation); + + // Construct menu item texts + scene::text* start_text = new scene::text(); + scene::text* options_text = new scene::text(); + scene::text* extras_text = new scene::text(); + scene::text* quit_text = new scene::text(); + + // Build list of menu item texts + ctx.menu_item_texts.push_back({start_text, nullptr}); + ctx.menu_item_texts.push_back({options_text, nullptr}); + ctx.menu_item_texts.push_back({extras_text, nullptr}); + ctx.menu_item_texts.push_back({quit_text, nullptr}); + + // Set content of menu item texts + start_text->set_content((*ctx.strings)["main_menu_start"]); + options_text->set_content((*ctx.strings)["main_menu_options"]); + extras_text->set_content((*ctx.strings)["main_menu_extras"]); + quit_text->set_content((*ctx.strings)["main_menu_quit"]); + + // Init menu item index + game::menu::init_menu_item_index(ctx, "main"); + + game::menu::update_text_color(ctx); + game::menu::update_text_font(ctx); + game::menu::align_text(ctx, true, false, (-ctx.app->get_viewport_dimensions().y / 3.0f) / 2.0f); + game::menu::update_text_tweens(ctx); + game::menu::add_text_to_ui(ctx); + game::menu::setup_animations(ctx); + + auto select_start_callback = [&ctx]() + { + // Disable controls and menu callbacks + game::menu::clear_controls(ctx); + game::menu::clear_callbacks(ctx); + + // Create change state function + auto change_state_nuptial_flight = [&ctx]() + { + // Queue change to nuptial state + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + ctx.state_machine.emplace(new game::state::nuptial_flight(ctx)); + } + ); + }; + + // Set up timing + const float fade_out_duration = 1.0f; + + // Schedule state change + timeline* timeline = ctx.timeline; + float t = timeline->get_position(); + timeline->add_sequence({{t + fade_out_duration, change_state_nuptial_flight}}); + + // Start fade out to white + ctx.fade_transition_color->set_value({1, 1, 1}); + ctx.fade_transition->transition(fade_out_duration, false, ease::out_cubic, false); + }; + auto select_options_callback = [this, &ctx]() + { + game::menu::clear_controls(ctx); + + // Fade out title + this->fade_out_title(); + + // Fade out menu + 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)); + } + ); + } + ); + }; + auto select_extras_callback = [this, &ctx]() + { + // Disable controls + game::menu::clear_controls(ctx); + + // Fade out title + this->fade_out_title(); + + // Fade out menu + game::menu::fade_out + ( + ctx, + [&ctx]() + { + // Queue change to extras menu state + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + ctx.state_machine.emplace(new game::state::extras_menu(ctx)); + } + ); + } + ); + }; + auto select_quit_callback = [&ctx]() + { + ctx.app->close(); + }; + + // Build list of menu select callbacks + ctx.menu_select_callbacks.push_back(select_start_callback); + ctx.menu_select_callbacks.push_back(select_options_callback); + ctx.menu_select_callbacks.push_back(select_extras_callback); + ctx.menu_select_callbacks.push_back(select_quit_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_quit_callback; + + // Queue menu control setup + ctx.function_queue.push(std::bind(game::menu::setup_controls, std::ref(ctx))); + + if (fade_in) + { + ctx.fade_transition->transition(0.5f, true, ease::out_cubic); + } + else + { + // Fade in title + ctx.title_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); + ctx.title_text->update_tweens(); + fade_in_title(); + + // Fade in menu + game::menu::fade_in(ctx, nullptr); + } + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +main_menu::~main_menu() +{ + ctx.logger->push_task("Exiting main 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); + + // Destruct title animation + ctx.animator->remove_animation(ctx.title_fade_animation); + delete ctx.title_fade_animation; + ctx.title_fade_animation = nullptr; + + // Destruct title text + ctx.ui_scene->remove_object(ctx.title_text); + delete ctx.title_text; + ctx.title_text = nullptr; + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +void main_menu::fade_in_title() +{ + ctx.title_fade_animation->set_interpolator(ease::out_cubic); + animation_channel* opacity_channel = ctx.title_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.title_fade_animation->stop(); + ctx.title_fade_animation->play(); +} + +void main_menu::fade_out_title() +{ + ctx.title_fade_animation->set_interpolator(ease::out_cubic); + animation_channel* opacity_channel = ctx.title_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.title_fade_animation->stop(); + ctx.title_fade_animation->play(); +} + +} // namespace state +} // namespace game diff --git a/src/game/states/main-menu.hpp b/src/game/state/main-menu.hpp similarity index 82% rename from src/game/states/main-menu.hpp rename to src/game/state/main-menu.hpp index da607cd..1b0b59a 100644 --- a/src/game/states/main-menu.hpp +++ b/src/game/state/main-menu.hpp @@ -20,18 +20,21 @@ #ifndef ANTKEEPER_GAME_STATE_MAIN_MENU_HPP #define ANTKEEPER_GAME_STATE_MAIN_MENU_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Main menu screen game state functions. -namespace main_menu { - -void enter(game::context* ctx, bool fade_in); -void exit(game::context* ctx); - -} // namespace main_menu +class main_menu: public game::state::base +{ +public: + main_menu(game::context& ctx, bool fade_in); + virtual ~main_menu(); + +private: + void fade_in_title(); + void fade_out_title(); +}; } // namespace state } // namespace game diff --git a/src/game/state/nuptial-flight.cpp b/src/game/state/nuptial-flight.cpp new file mode 100644 index 0000000..fb1999b --- /dev/null +++ b/src/game/state/nuptial-flight.cpp @@ -0,0 +1,544 @@ +/* + * 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/nuptial-flight.hpp" +#include "game/state/pause-menu.hpp" +#include "entity/archetype.hpp" +#include "entity/systems/astronomy.hpp" +#include "entity/systems/orbit.hpp" +#include "entity/systems/camera.hpp" +#include "entity/components/observer.hpp" +#include "entity/components/transform.hpp" +#include "entity/components/terrain.hpp" +#include "entity/components/camera.hpp" +#include "entity/components/constraints/spring-to.hpp" +#include "entity/components/constraints/three-dof.hpp" +#include "entity/components/constraint-stack.hpp" +#include "entity/commands.hpp" +#include "animation/screen-transition.hpp" +#include "animation/ease.hpp" +#include "resources/resource-manager.hpp" +#include "game/world.hpp" +#include "application.hpp" +#include "render/passes/clear-pass.hpp" +#include +#include +#include "state-machine.hpp" + +namespace game { +namespace state { + +nuptial_flight::nuptial_flight(game::context& ctx): + game::state::base(ctx) +{ + ctx.logger->push_task("Entering nuptial flight state"); + + // Disable UI color clear + ctx.ui_clear_pass->set_cleared_buffers(false, true, false); + + // Create world + game::world::create_stars(ctx); + game::world::create_sun(ctx); + game::world::create_planet(ctx); + game::world::create_moon(ctx); + + // Set time to solar noon + game::world::set_time(ctx, 0.0); + + // Freeze time + game::world::set_time_scale(ctx, 0.0); + + // Switch to surface camera + ctx.underground_camera->set_active(false); + ctx.surface_camera->set_active(true); + + // Find planet EID by name + entity::id planet_eid = ctx.entities["planet"]; + + // Remove terrain component from planet (if any) + //if (ctx.entity_registry->has(planet_eid)) + // ctx.entity_registry->remove(planet_eid); + + // Enable clouds in sky pass + //ctx.surface_sky_pass->set_clouds_model(ctx.resource_manager->load("cloud-plane.mdl")); + + // Create observer + entity::id observer_eid = ctx.entity_registry->create(); + { + entity::component::observer observer; + observer.reference_body_eid = planet_eid; + observer.elevation = 2000.0; + observer.latitude = 0.0; + observer.longitude = 0.0; + observer.camera = ctx.surface_camera; + ctx.entity_registry->assign(observer_eid, observer); + + // Set reference location of astronomy system + ctx.astronomy_system->set_reference_body(planet_eid); + ctx.astronomy_system->set_observer_location(double3{observer.elevation, observer.latitude, observer.longitude}); + } + + // Setup camera + setup_camera(); + /* + ctx.surface_camera->look_at({0, 0, 1}, {0, 0, 0}, {0, 1, 0}); + ctx.surface_camera->set_exposure(-14.5f); + ctx.surface_scene->update_tweens(); + */ + + // Queue fade in + ctx.fade_transition_color->set_value({1, 1, 1}); + ctx.function_queue.push(std::bind(&screen_transition::transition, ctx.fade_transition, 5.0f, true, math::lerp, true)); + + // Queue control setup + ctx.function_queue.push(std::bind(&nuptial_flight::enable_controls, this)); + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +nuptial_flight::~nuptial_flight() +{ + ctx.logger->push_task("Exiting nuptial flight state"); + + // Resume time + //const double time_scale = (*ctx.config)["time_scale"].get(); + //game::world::set_time_scale(ctx, time_scale); + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +void nuptial_flight::setup_camera() +{ + // Switch to surface camera + ctx.underground_camera->set_active(false); + ctx.surface_camera->set_active(true); + + // Create surface camera entity + if (!ctx.entities.count("surface_cam")) + { + // Create camera target entity + entity::id target_eid = ctx.entity_registry->create(); + ctx.entities["surface_cam_target"] = target_eid; + { + // Transform + entity::component::transform target_transform; + target_transform.local = math::identity_transform; + target_transform.world = target_transform.local; + target_transform.warp = true; + ctx.entity_registry->assign(target_eid, target_transform); + } + + // Create camera entity + entity::id camera_eid = ctx.entity_registry->create(); + ctx.entities["surface_cam"] = camera_eid; + + // Create camera transform component + entity::component::transform transform; + transform.local = math::identity_transform; + transform.world = transform.local; + transform.warp = true; + ctx.entity_registry->assign(camera_eid, transform); + + // Create camera camera component + entity::component::camera camera; + camera.object = ctx.surface_camera; + ctx.entity_registry->assign(camera_eid, camera); + + // Create camera 3DOF constraint entity + entity::id three_dof_constraint_eid = ctx.entity_registry->create(); + ctx.entities["surface_cam_3dof"] = three_dof_constraint_eid; + { + // Create 3DOF to constraint + entity::component::constraint::three_dof three_dof; + three_dof.yaw = 0.0f; + three_dof.pitch = 0.0f; + three_dof.roll = 0.0f; + ctx.entity_registry->assign(three_dof_constraint_eid, three_dof); + + // Create constraint stack node component + entity::component::constraint_stack_node node; + node.active = true; + node.weight = 1.0f; + node.next = entt::null; + ctx.entity_registry->assign(three_dof_constraint_eid, node); + } + + // Create camera spring to constraint entity + entity::id spring_constraint_eid = ctx.entity_registry->create(); + { + // Create spring to constraint + entity::component::constraint::spring_to spring; + spring.target = target_eid; + spring.translation = {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, 1.0f, math::two_pi}; + spring.translation.w = hz_to_rads(8.0f); + + spring.spring_translation = true; + spring.spring_rotation = false; + ctx.entity_registry->assign(spring_constraint_eid, spring); + + // Create constraint stack node component + entity::component::constraint_stack_node node; + node.active = true; + node.weight = 1.0f; + node.next = three_dof_constraint_eid; + ctx.entity_registry->assign(spring_constraint_eid, node); + } + + // Create camera constraint stack component + entity::component::constraint_stack constraint_stack; + constraint_stack.head = spring_constraint_eid; + ctx.entity_registry->assign(camera_eid, constraint_stack); + } + + ctx.surface_camera->set_exposure(-12.0f); +} + +void nuptial_flight::enable_controls() +{ + // Get camera entities + entity::id camera_eid = ctx.entities["surface_cam"]; + entity::id target_eid = ctx.entities["surface_cam_target"]; + entity::id three_dof_eid = ctx.entities["surface_cam_3dof"]; + + const float slow_modifier = 0.25f; + const float fast_modifier = 4.0f; + const float dolly_speed = 20.0f; + const float truck_speed = dolly_speed; + const float pedestal_speed = 30.0f; + float mouse_tilt_sensitivity = 1.0f; + float mouse_pan_sensitivity = 1.0f; + bool mouse_invert_tilt = false; + bool mouse_invert_pan = false; + float gamepad_tilt_sensitivity = 1.0f; + float gamepad_pan_sensitivity = 1.0f; + bool gamepad_invert_tilt = false; + bool gamepad_invert_pan = false; + bool mouse_look_toggle = false; + ctx.mouse_look = false; + + if (ctx.config->contains("mouse_tilt_sensitivity")) + mouse_tilt_sensitivity = math::radians((*ctx.config)["mouse_tilt_sensitivity"].get()); + if (ctx.config->contains("mouse_pan_sensitivity")) + mouse_pan_sensitivity = math::radians((*ctx.config)["mouse_pan_sensitivity"].get()); + if (ctx.config->contains("mouse_invert_tilt")) + mouse_invert_tilt = math::radians((*ctx.config)["mouse_invert_tilt"].get()); + if (ctx.config->contains("mouse_invert_pan")) + mouse_invert_pan = math::radians((*ctx.config)["mouse_invert_pan"].get()); + if (ctx.config->contains("mouse_look_toggle")) + mouse_look_toggle = math::radians((*ctx.config)["mouse_look_toggle"].get()); + + if (ctx.config->contains("gamepad_tilt_sensitivity")) + gamepad_tilt_sensitivity = math::radians((*ctx.config)["gamepad_tilt_sensitivity"].get()); + if (ctx.config->contains("gamepad_pan_sensitivity")) + gamepad_pan_sensitivity = math::radians((*ctx.config)["gamepad_pan_sensitivity"].get()); + if (ctx.config->contains("gamepad_invert_tilt")) + gamepad_invert_tilt = math::radians((*ctx.config)["gamepad_invert_tilt"].get()); + if (ctx.config->contains("gamepad_invert_pan")) + gamepad_invert_pan = math::radians((*ctx.config)["gamepad_invert_pan"].get()); + + const input::control* move_slow = ctx.controls["move_slow"]; + const input::control* move_fast = ctx.controls["move_fast"]; + const input::control* mouse_look = ctx.controls["mouse_look"]; + + float mouse_tilt_factor = mouse_tilt_sensitivity * (mouse_invert_tilt ? -1.0f : 1.0f); + float mouse_pan_factor = mouse_pan_sensitivity * (mouse_invert_pan ? -1.0f : 1.0f); + float gamepad_tilt_factor = gamepad_tilt_sensitivity * (gamepad_invert_tilt ? -1.0f : 1.0f); + float gamepad_pan_factor = gamepad_pan_sensitivity * (gamepad_invert_pan ? -1.0f : 1.0f); + + ctx.controls["move_forward"]->set_active_callback + ( + [&ctx = this->ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) + { + if (move_slow->is_active()) + value *= slow_modifier; + if (move_fast->is_active()) + value *= fast_modifier; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); + + const float3 movement = {0.0f, 0.0f, -truck_speed * value * (1.0f / 60.0f)}; + entity::command::translate(*ctx.entity_registry, target_eid, yaw * movement); + } + ); + + // Dolly backward + ctx.controls["move_back"]->set_active_callback + ( + [&ctx = this->ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) + { + if (move_slow->is_active()) + value *= slow_modifier; + if (move_fast->is_active()) + value *= fast_modifier; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); + + const float3 movement = {0.0f, 0.0f, truck_speed * value * (1.0f / 60.0f)}; + entity::command::translate(*ctx.entity_registry, target_eid, yaw * movement); + } + ); + + // Truck right + ctx.controls["move_right"]->set_active_callback + ( + [&ctx = this->ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) + { + if (move_slow->is_active()) + value *= slow_modifier; + if (move_fast->is_active()) + value *= fast_modifier; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); + + const float3 movement = {truck_speed * value * (1.0f / 60.0f), 0.0f, 0.0f}; + entity::command::translate(*ctx.entity_registry, target_eid, yaw * movement); + } + ); + + // Truck left + ctx.controls["move_left"]->set_active_callback + ( + [&ctx = this->ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) + { + if (move_slow->is_active()) + value *= slow_modifier; + if (move_fast->is_active()) + value *= fast_modifier; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); + + const float3 movement = {-truck_speed * value * (1.0f / 60.0f), 0.0f, 0.0f}; + entity::command::translate(*ctx.entity_registry, target_eid, yaw * movement); + } + ); + + // Pedestal up + ctx.controls["move_up"]->set_active_callback + ( + [&ctx = this->ctx, target_eid, pedestal_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) + { + if (move_slow->is_active()) + value *= slow_modifier; + if (move_fast->is_active()) + value *= fast_modifier; + + const float3 movement = {0.0f, pedestal_speed * value * (1.0f / 60.0f), 0.0f}; + entity::command::translate(*ctx.entity_registry, target_eid, movement); + } + ); + + // Pedestal down + ctx.controls["move_down"]->set_active_callback + ( + [&ctx = this->ctx, target_eid, pedestal_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) + { + if (move_slow->is_active()) + value *= slow_modifier; + if (move_fast->is_active()) + value *= fast_modifier; + + const float3 movement = {0.0f, -pedestal_speed * value * (1.0f / 60.0f), 0.0f}; + entity::command::translate(*ctx.entity_registry, target_eid, movement); + } + ); + + // Mouse rotate + ctx.controls["mouse_look"]->set_activated_callback + ( + [&ctx = this->ctx, mouse_look_toggle]() + { + if (mouse_look_toggle) + ctx.mouse_look = !ctx.mouse_look; + else + ctx.mouse_look = true; + + ctx.app->set_relative_mouse_mode(ctx.mouse_look); + } + ); + ctx.controls["mouse_look"]->set_deactivated_callback + ( + [&ctx = this->ctx, mouse_look_toggle]() + { + if (!mouse_look_toggle) + { + ctx.mouse_look = false; + ctx.app->set_relative_mouse_mode(false); + } + } + ); + // Pan left + ctx.controls["look_left_gamepad"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, gamepad_pan_factor](float value) + { + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.yaw += gamepad_pan_factor * value * (1.0f / 60.0f); + } + ); + ctx.controls["look_left_mouse"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, mouse_pan_factor](float value) + { + if (!ctx.mouse_look) + return; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.yaw += mouse_pan_factor * value * (1.0f / 60.0f); + } + ); + + // Pan right + ctx.controls["look_right_gamepad"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, gamepad_pan_factor](float value) + { + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.yaw -= gamepad_pan_factor * value * (1.0f / 60.0f); + } + ); + ctx.controls["look_right_mouse"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, mouse_pan_factor](float value) + { + if (!ctx.mouse_look) + return; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.yaw -= mouse_pan_factor * value * (1.0f / 60.0f); + } + ); + // Tilt up + ctx.controls["look_up_gamepad"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, gamepad_tilt_factor](float value) + { + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.pitch -= gamepad_tilt_factor * value * (1.0f / 60.0f); + three_dof.pitch = std::max(math::radians(-90.0f), three_dof.pitch); + } + ); + ctx.controls["look_up_mouse"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, mouse_tilt_factor](float value) + { + if (!ctx.mouse_look) + return; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.pitch -= mouse_tilt_factor * value * (1.0f / 60.0f); + three_dof.pitch = std::max(math::radians(-90.0f), three_dof.pitch); + } + ); + // Tilt down + ctx.controls["look_down_gamepad"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, gamepad_tilt_factor](float value) + { + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.pitch += gamepad_tilt_factor * value * (1.0f / 60.0f); + three_dof.pitch = std::min(math::radians(90.0f), three_dof.pitch); + } + ); + ctx.controls["look_down_mouse"]->set_active_callback + ( + [&ctx = this->ctx, three_dof_eid, mouse_tilt_factor](float value) + { + if (!ctx.mouse_look) + return; + + auto& three_dof = ctx.entity_registry->get(three_dof_eid); + three_dof.pitch += mouse_tilt_factor * value * (1.0f / 60.0f); + three_dof.pitch = std::min(math::radians(90.0f), three_dof.pitch); + } + ); + /* + // Use tool + ctx.controls["use_tool"]->set_activated_callback + ( + [&ctx]() + { + if (ctx.entities.count("active_tool")) + { + entity::id tool_eid = ctx.entities["active_tool"]; + const auto& tool = ctx.entity_registry->get(tool_eid); + if (tool.activated) + tool.activated(); + } + } + ); + ctx.controls["use_tool"]->set_deactivated_callback + ( + [&ctx]() + { + if (ctx.entities.count("active_tool")) + { + entity::id tool_eid = ctx.entities["active_tool"]; + const auto& tool = ctx.entity_registry->get(tool_eid); + if (tool.deactivated) + tool.deactivated(); + } + } + ); + ctx.controls["use_tool"]->set_active_callback + ( + [&ctx](float value) + { + if (ctx.entities.count("active_tool")) + { + entity::id tool_eid = ctx.entities["active_tool"]; + const auto& tool = ctx.entity_registry->get(tool_eid); + if (tool.active) + tool.active(); + } + } + ); + */ + + // Setup pause control + ctx.controls["pause"]->set_activated_callback + ( + [this, &ctx = this->ctx]() + { + // Disable controls + this->disable_controls(); + + // Set resume callback + ctx.resume_callback = [this, &ctx]() + { + this->enable_controls(); + ctx.resume_callback = nullptr; + }; + + // Push pause menu state + ctx.state_machine.emplace(new game::state::pause_menu(ctx)); + } + ); +} + +void nuptial_flight::disable_controls() +{ + ctx.controls["pause"]->set_activated_callback(nullptr); +} + +} // namespace state +} // namespace game diff --git a/src/game/states/nuptial-flight.hpp b/src/game/state/nuptial-flight.hpp similarity index 80% rename from src/game/states/nuptial-flight.hpp rename to src/game/state/nuptial-flight.hpp index a03660b..60b412e 100644 --- a/src/game/states/nuptial-flight.hpp +++ b/src/game/state/nuptial-flight.hpp @@ -20,18 +20,22 @@ #ifndef ANTKEEPER_GAME_STATE_NUPTIAL_FLIGHT_HPP #define ANTKEEPER_GAME_STATE_NUPTIAL_FLIGHT_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Nuptial flight game state functions. -namespace nuptial_flight { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace nuptial_flight +class nuptial_flight: public game::state::base +{ +public: + nuptial_flight(game::context& ctx); + virtual ~nuptial_flight(); + +private: + void setup_camera(); + void enable_controls(); + void disable_controls(); +}; } // namespace state } // namespace game diff --git a/src/game/state/options-menu.cpp b/src/game/state/options-menu.cpp new file mode 100644 index 0000000..e02e36f --- /dev/null +++ b/src/game/state/options-menu.cpp @@ -0,0 +1,237 @@ +/* + * 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/options-menu.hpp" +#include "game/state/main-menu.hpp" +#include "game/state/controls-menu.hpp" +#include "game/state/graphics-menu.hpp" +#include "game/state/sound-menu.hpp" +#include "game/state/language-menu.hpp" +#include "game/state/pause-menu.hpp" +#include "game/save.hpp" +#include "game/menu.hpp" +#include "animation/ease.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" +#include "application.hpp" +#include "scene/text.hpp" +#include "debug/logger.hpp" + +namespace game { +namespace state { + +options_menu::options_menu(game::context& ctx): + game::state::base(ctx) +{ + ctx.logger->push_task("Entering options menu state"); + + // Construct menu item texts + scene::text* controls_text = new scene::text(); + scene::text* graphics_text = new scene::text(); + scene::text* sound_text = new scene::text(); + scene::text* language_text = new scene::text(); + scene::text* back_text = new scene::text(); + + // Set content of menu item texts + controls_text->set_content((*ctx.strings)["options_menu_controls"]); + graphics_text->set_content((*ctx.strings)["options_menu_graphics"]); + sound_text->set_content((*ctx.strings)["options_menu_sound"]); + language_text->set_content((*ctx.strings)["options_menu_language"]); + back_text->set_content((*ctx.strings)["back"]); + + // Build list of menu item texts + ctx.menu_item_texts.push_back({controls_text, nullptr}); + ctx.menu_item_texts.push_back({graphics_text, nullptr}); + ctx.menu_item_texts.push_back({sound_text, nullptr}); + ctx.menu_item_texts.push_back({language_text, nullptr}); + ctx.menu_item_texts.push_back({back_text, nullptr}); + + // Init menu item index + game::menu::init_menu_item_index(ctx, "options"); + + game::menu::update_text_color(ctx); + game::menu::update_text_font(ctx); + game::menu::align_text(ctx, true); + game::menu::update_text_tweens(ctx); + game::menu::add_text_to_ui(ctx); + game::menu::setup_animations(ctx); + + // Construct menu item callbacks + auto select_controls_callback = [&ctx]() + { + // Disable controls + game::menu::clear_controls(ctx); + + // Return to main menu + 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)); + } + ); + } + ); + }; + auto select_graphics_callback = [&ctx]() + { + // Disable controls + game::menu::clear_controls(ctx); + + // Return to main menu + game::menu::fade_out + ( + ctx, + [&ctx]() + { + // Queue change to graphics menu state + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + ctx.state_machine.emplace(new game::state::graphics_menu(ctx)); + } + ); + } + ); + }; + auto select_sound_callback = [&ctx]() + { + // Disable controls + game::menu::clear_controls(ctx); + + // Return to main menu + game::menu::fade_out + ( + ctx, + [&ctx]() + { + // Queue change to sound menu state + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + ctx.state_machine.emplace(new game::state::sound_menu(ctx)); + } + ); + } + ); + }; + auto select_language_callback = [&ctx]() + { + // Disable controls + game::menu::clear_controls(ctx); + + // Return to main menu + game::menu::fade_out + ( + ctx, + [&ctx]() + { + // Queue change to language menu state + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + ctx.state_machine.emplace(new game::state::language_menu(ctx)); + } + ); + } + ); + }; + auto select_back_callback = [&ctx]() + { + // Disable controls + game::menu::clear_controls(ctx); + + // Save config + game::save_config(ctx); + + game::menu::fade_out + ( + ctx, + [&ctx]() + { + // Queue change to main menu state + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + if (ctx.resume_callback) + ctx.state_machine.emplace(new game::state::pause_menu(ctx)); + else + ctx.state_machine.emplace(new game::state::main_menu(ctx, false)); + } + ); + } + ); + }; + + // Build list of menu select callbacks + ctx.menu_select_callbacks.push_back(select_controls_callback); + ctx.menu_select_callbacks.push_back(select_graphics_callback); + ctx.menu_select_callbacks.push_back(select_sound_callback); + ctx.menu_select_callbacks.push_back(select_language_callback); + ctx.menu_select_callbacks.push_back(select_back_callback); + + // Build list of menu right callbacks + ctx.menu_right_callbacks.resize(5, nullptr); + + // Build list of menu left callbacks + ctx.menu_left_callbacks.resize(5, 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); +} + +options_menu::~options_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 state +} // namespace game diff --git a/src/game/states/options-menu.hpp b/src/game/state/options-menu.hpp similarity index 83% rename from src/game/states/options-menu.hpp rename to src/game/state/options-menu.hpp index 0ad2f0c..603085b 100644 --- a/src/game/states/options-menu.hpp +++ b/src/game/state/options-menu.hpp @@ -20,18 +20,17 @@ #ifndef ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP #define ANTKEEPER_GAME_STATE_OPTIONS_MENU_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Options menu screen game state functions. -namespace options_menu { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace options_menu +class options_menu: public game::state::base +{ +public: + options_menu(game::context& ctx); + virtual ~options_menu(); +}; } // namespace state } // namespace game diff --git a/src/game/states/pause-menu.cpp b/src/game/state/pause-menu.cpp similarity index 53% rename from src/game/states/pause-menu.cpp rename to src/game/state/pause-menu.cpp index 98a81c0..e532432 100644 --- a/src/game/states/pause-menu.cpp +++ b/src/game/state/pause-menu.cpp @@ -17,22 +17,26 @@ * along with Antkeeper source code. If not, see . */ -#include "game/states/pause-menu.hpp" -#include "game/states/main-menu.hpp" -#include "game/states/options-menu.hpp" +#include "game/state/pause-menu.hpp" +#include "game/state/main-menu.hpp" +#include "game/state/options-menu.hpp" +#include "game/state/nuptial-flight.hpp" #include "game/menu.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 { -namespace pause_menu { -void enter(game::context* ctx) +pause_menu::pause_menu(game::context& ctx): + game::state::base(ctx) { + ctx.logger->push_task("Entering pause menu state"); + // Construct menu item texts scene::text* resume_text = new scene::text(); scene::text* options_text = new scene::text(); @@ -40,16 +44,16 @@ void enter(game::context* ctx) scene::text* quit_text = new scene::text(); // Set content of menu item texts - resume_text->set_content((*ctx->strings)["pause_menu_resume"]); - options_text->set_content((*ctx->strings)["pause_menu_options"]); - main_menu_text->set_content((*ctx->strings)["pause_menu_main_menu"]); - quit_text->set_content((*ctx->strings)["pause_menu_quit"]); + resume_text->set_content((*ctx.strings)["pause_menu_resume"]); + options_text->set_content((*ctx.strings)["pause_menu_options"]); + main_menu_text->set_content((*ctx.strings)["pause_menu_main_menu"]); + quit_text->set_content((*ctx.strings)["pause_menu_quit"]); // Build list of menu item texts - ctx->menu_item_texts.push_back({resume_text, nullptr}); - ctx->menu_item_texts.push_back({options_text, nullptr}); - ctx->menu_item_texts.push_back({main_menu_text, nullptr}); - ctx->menu_item_texts.push_back({quit_text, nullptr}); + ctx.menu_item_texts.push_back({resume_text, nullptr}); + ctx.menu_item_texts.push_back({options_text, nullptr}); + ctx.menu_item_texts.push_back({main_menu_text, nullptr}); + ctx.menu_item_texts.push_back({quit_text, nullptr}); // Init menu item index game::menu::init_menu_item_index(ctx, "pause"); @@ -62,27 +66,34 @@ void enter(game::context* ctx) game::menu::setup_animations(ctx); // Construct menu item callbacks - auto select_resume_callback = [ctx]() + auto select_resume_callback = [&ctx]() { // Disable unpause control - ctx->controls["pause"]->set_activated_callback(nullptr); + ctx.controls["pause"]->set_activated_callback(nullptr); // Disable menu controls game::menu::clear_controls(ctx); - auto resume_paused_state = [ctx]() + auto resume_paused_state = [&ctx]() { - ctx->app->queue_state(*ctx->paused_state); + ctx.function_queue.push + ( + [&ctx]() + { + ctx.state_machine.pop(); + ctx.resume_callback(); + } + ); }; // Fade out pause menu then resume paused state game::menu::fade_out(ctx, resume_paused_state); game::menu::fade_out_bg(ctx); }; - auto select_options_callback = [ctx]() + auto select_options_callback = [&ctx]() { // Disable unpause control - ctx->controls["pause"]->set_activated_callback(nullptr); + ctx.controls["pause"]->set_activated_callback(nullptr); // Disable menu controls game::menu::clear_controls(ctx); @@ -91,78 +102,86 @@ 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)); + } + ); } ); }; - auto select_main_menu_callback = [ctx]() + auto select_main_menu_callback = [&ctx]() { // Disable unpause control - ctx->controls["pause"]->set_activated_callback(nullptr); + ctx.controls["pause"]->set_activated_callback(nullptr); // Disable menu controls game::menu::clear_controls(ctx); // Clear paused state - ctx->paused_state.reset(); + //ctx.paused_state.reset(); // Fade out pause menu then return to main menu 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, true); - 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, true)); + } + ); } ); }; - auto select_quit_callback = [ctx]() + auto select_quit_callback = [&ctx]() { // Disable unpause control - ctx->controls["pause"]->set_activated_callback(nullptr); + ctx.controls["pause"]->set_activated_callback(nullptr); // Disable menu controls game::menu::clear_controls(ctx); // Clear paused state - ctx->paused_state.reset(); + //ctx.paused_state.reset(); // Fade out then quit - game::menu::fade_out(ctx, std::bind(&application::close, ctx->app, EXIT_SUCCESS)); + game::menu::fade_out(ctx, std::bind(&application::close, ctx.app)); }; // Build list of menu select callbacks - ctx->menu_select_callbacks.push_back(select_resume_callback); - ctx->menu_select_callbacks.push_back(select_options_callback); - ctx->menu_select_callbacks.push_back(select_main_menu_callback); - ctx->menu_select_callbacks.push_back(select_quit_callback); + ctx.menu_select_callbacks.push_back(select_resume_callback); + ctx.menu_select_callbacks.push_back(select_options_callback); + ctx.menu_select_callbacks.push_back(select_main_menu_callback); + ctx.menu_select_callbacks.push_back(select_quit_callback); // Build list of menu right callbacks - ctx->menu_right_callbacks.resize(4, nullptr); + ctx.menu_right_callbacks.resize(4, nullptr); // Build list of menu left callbacks - ctx->menu_left_callbacks.resize(4, nullptr); + ctx.menu_left_callbacks.resize(4, nullptr); // Set menu back callback - ctx->menu_back_callback = select_resume_callback; + ctx.menu_back_callback = select_resume_callback; // Queue control setup - ctx->function_queue.push + ctx.function_queue.push ( - [ctx, select_resume_callback]() + [&ctx, select_resume_callback]() { // Enable unpause control - ctx->controls["pause"]->set_activated_callback(select_resume_callback); + ctx.controls["pause"]->set_activated_callback(select_resume_callback); // Enable menu controls game::menu::setup_controls(ctx); @@ -171,20 +190,25 @@ void enter(game::context* ctx) // Fade in menu and menu BG game::menu::fade_in(ctx, nullptr); - if (!ctx->menu_bg_billboard->is_active()) + if (!ctx.menu_bg_billboard->is_active()) game::menu::fade_in_bg(ctx); + + ctx.logger->pop_task(EXIT_SUCCESS); } -void exit(game::context* ctx) +pause_menu::~pause_menu() { + ctx.logger->push_task("Exiting pause 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 pause_menu } // namespace state } // namespace game diff --git a/src/game/states/pause-menu.hpp b/src/game/state/pause-menu.hpp similarity index 84% rename from src/game/states/pause-menu.hpp rename to src/game/state/pause-menu.hpp index 585046f..fe1b350 100644 --- a/src/game/states/pause-menu.hpp +++ b/src/game/state/pause-menu.hpp @@ -20,18 +20,17 @@ #ifndef ANTKEEPER_GAME_STATE_PAUSE_MENU_HPP #define ANTKEEPER_GAME_STATE_PAUSE_MENU_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Pause menu screen game state functions. -namespace pause_menu { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace pause_menu +class pause_menu: public game::state::base +{ +public: + pause_menu(game::context& ctx); + virtual ~pause_menu(); +}; } // namespace state } // namespace game diff --git a/src/game/state/sound-menu.cpp b/src/game/state/sound-menu.cpp new file mode 100644 index 0000000..80761f2 --- /dev/null +++ b/src/game/state/sound-menu.cpp @@ -0,0 +1,262 @@ +/* + * 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/sound-menu.hpp" +#include "game/state/options-menu.hpp" +#include "application.hpp" +#include "scene/text.hpp" +#include "debug/logger.hpp" +#include "game/menu.hpp" + +namespace game { +namespace state { + +sound_menu::sound_menu(game::context& ctx): + game::state::base(ctx) +{ + ctx.logger->push_task("Entering sound menu state"); + + // Construct menu item texts + scene::text* master_volume_name_text = new scene::text(); + scene::text* master_volume_value_text = new scene::text(); + scene::text* ambience_volume_name_text = new scene::text(); + scene::text* ambience_volume_value_text = new scene::text(); + scene::text* effects_volume_name_text = new scene::text(); + scene::text* effects_volume_value_text = new scene::text(); + scene::text* mono_audio_name_text = new scene::text(); + scene::text* mono_audio_value_text = new scene::text(); + scene::text* captions_name_text = new scene::text(); + scene::text* captions_value_text = new scene::text(); + scene::text* captions_size_name_text = new scene::text(); + scene::text* captions_size_value_text = new scene::text(); + scene::text* back_text = new scene::text(); + + // Build list of menu item texts + ctx.menu_item_texts.push_back({master_volume_name_text, master_volume_value_text}); + ctx.menu_item_texts.push_back({ambience_volume_name_text, ambience_volume_value_text}); + ctx.menu_item_texts.push_back({effects_volume_name_text, effects_volume_value_text}); + ctx.menu_item_texts.push_back({mono_audio_name_text, mono_audio_value_text}); + ctx.menu_item_texts.push_back({captions_name_text, captions_value_text}); + ctx.menu_item_texts.push_back({captions_size_name_text, captions_size_value_text}); + ctx.menu_item_texts.push_back({back_text, nullptr}); + + // Set content of menu item texts + master_volume_name_text->set_content((*ctx.strings)["sound_menu_master_volume"]); + ambience_volume_name_text->set_content((*ctx.strings)["sound_menu_ambience_volume"]); + effects_volume_name_text->set_content((*ctx.strings)["sound_menu_effects_volume"]); + mono_audio_name_text->set_content((*ctx.strings)["sound_menu_mono_audio"]); + captions_name_text->set_content((*ctx.strings)["sound_menu_captions"]); + captions_size_name_text->set_content((*ctx.strings)["sound_menu_captions_size"]); + back_text->set_content((*ctx.strings)["back"]); + update_value_text_content(); + + // Init menu item index + game::menu::init_menu_item_index(ctx, "sound"); + + 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 increase_volume_callback = [this, &ctx](float* volume) + { + // Increase volume + if (ctx.controls["menu_modifier"]->is_active()) + *volume += 0.01f; + else + *volume += 0.1f; + + // Limit volume + if (*volume > 1.0f) + *volume = 1.0f; + + this->update_value_text_content(); + game::menu::align_text(ctx); + game::menu::update_text_tweens(ctx); + }; + auto decrease_volume_callback = [this, &ctx](float* volume) + { + // Decrease volume + if (ctx.controls["menu_modifier"]->is_active()) + *volume -= 0.01f; + else + *volume -= 0.1f; + + // Limit volume + if (*volume < 0.0f) + *volume = 0.0f; + + this->update_value_text_content(); + game::menu::align_text(ctx); + game::menu::update_text_tweens(ctx); + }; + + auto toggle_mono_audio_callback = [this, &ctx]() + { + ctx.mono_audio = !ctx.mono_audio; + + this->update_value_text_content(); + game::menu::align_text(ctx); + game::menu::update_text_tweens(ctx); + }; + + auto toggle_captions_callback = [this, &ctx]() + { + ctx.captions = !ctx.captions; + + this->update_value_text_content(); + game::menu::align_text(ctx); + game::menu::update_text_tweens(ctx); + }; + + auto increase_captions_size_callback = [this, &ctx]() + { + // Increase size + if (ctx.controls["menu_modifier"]->is_active()) + ctx.captions_size += 0.01f; + else + ctx.captions_size += 0.1f; + + // Limit size + if (ctx.captions_size > 2.0f) + ctx.captions_size = 2.0f; + + this->update_value_text_content(); + game::menu::align_text(ctx); + game::menu::update_text_tweens(ctx); + }; + + auto decrease_captions_size_callback = [this, &ctx]() + { + // Decrease size + if (ctx.controls["menu_modifier"]->is_active()) + ctx.captions_size -= 0.01f; + else + ctx.captions_size -= 0.1f; + + // Limit size + if (ctx.captions_size < 0.1f) + ctx.captions_size = 0.1f; + + this->update_value_text_content(); + 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(std::bind(increase_volume_callback, &ctx.master_volume)); + ctx.menu_select_callbacks.push_back(std::bind(increase_volume_callback, &ctx.ambience_volume)); + ctx.menu_select_callbacks.push_back(std::bind(increase_volume_callback, &ctx.effects_volume)); + ctx.menu_select_callbacks.push_back(toggle_mono_audio_callback); + ctx.menu_select_callbacks.push_back(toggle_captions_callback); + ctx.menu_select_callbacks.push_back(increase_captions_size_callback); + ctx.menu_select_callbacks.push_back(select_back_callback); + + // Build list of menu left callbacks + ctx.menu_left_callbacks.push_back(std::bind(decrease_volume_callback, &ctx.master_volume)); + ctx.menu_left_callbacks.push_back(std::bind(decrease_volume_callback, &ctx.ambience_volume)); + ctx.menu_left_callbacks.push_back(std::bind(decrease_volume_callback, &ctx.effects_volume)); + ctx.menu_left_callbacks.push_back(toggle_mono_audio_callback); + ctx.menu_left_callbacks.push_back(toggle_captions_callback); + ctx.menu_left_callbacks.push_back(decrease_captions_size_callback); + ctx.menu_left_callbacks.push_back(nullptr); + + // Build list of menu right callbacks + ctx.menu_right_callbacks.push_back(std::bind(increase_volume_callback, &ctx.master_volume)); + ctx.menu_right_callbacks.push_back(std::bind(increase_volume_callback, &ctx.ambience_volume)); + ctx.menu_right_callbacks.push_back(std::bind(increase_volume_callback, &ctx.effects_volume)); + ctx.menu_right_callbacks.push_back(toggle_mono_audio_callback); + ctx.menu_right_callbacks.push_back(toggle_captions_callback); + ctx.menu_right_callbacks.push_back(increase_captions_size_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); +} + +sound_menu::~sound_menu() +{ + ctx.logger->push_task("Exiting sound 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); + + // Update config + (*ctx.config)["master_volume"] = ctx.master_volume; + (*ctx.config)["ambience_volume"] = ctx.ambience_volume; + (*ctx.config)["effects_volume"] = ctx.effects_volume; + (*ctx.config)["mono_audio"] = ctx.mono_audio; + (*ctx.config)["captions"] = ctx.captions; + (*ctx.config)["captions_size"] = ctx.captions_size; + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +void sound_menu::update_value_text_content() +{ + 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(std::to_string(static_cast(std::round(ctx.master_volume * 100.0f))) + "%"); + std::get<1>(ctx.menu_item_texts[1])->set_content(std::to_string(static_cast(std::round(ctx.ambience_volume * 100.0f))) + "%"); + std::get<1>(ctx.menu_item_texts[2])->set_content(std::to_string(static_cast(std::round(ctx.effects_volume * 100.0f))) + "%"); + std::get<1>(ctx.menu_item_texts[3])->set_content((ctx.mono_audio) ? string_on : string_off); + std::get<1>(ctx.menu_item_texts[4])->set_content((ctx.captions) ? string_on : string_off); + std::get<1>(ctx.menu_item_texts[5])->set_content(std::to_string(static_cast(std::round(ctx.captions_size * 100.0f))) + "%"); +} + +} // namespace state +} // namespace game diff --git a/src/game/states/sound-menu.hpp b/src/game/state/sound-menu.hpp similarity index 83% rename from src/game/states/sound-menu.hpp rename to src/game/state/sound-menu.hpp index d2d55e6..c3b4de0 100644 --- a/src/game/states/sound-menu.hpp +++ b/src/game/state/sound-menu.hpp @@ -20,18 +20,20 @@ #ifndef ANTKEEPER_GAME_STATE_SOUND_MENU_HPP #define ANTKEEPER_GAME_STATE_SOUND_MENU_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Sound menu screen game state functions. -namespace sound_menu { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace sound_menu +class sound_menu: public game::state::base +{ +public: + sound_menu(game::context& ctx); + virtual ~sound_menu(); + +private: + void update_value_text_content(); +}; } // namespace state } // namespace game diff --git a/src/game/state/splash.cpp b/src/game/state/splash.cpp new file mode 100644 index 0000000..572ebb4 --- /dev/null +++ b/src/game/state/splash.cpp @@ -0,0 +1,170 @@ +/* + * 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/splash.hpp" +#include "game/state/main-menu.hpp" +#include "animation/screen-transition.hpp" +#include "animation/animation.hpp" +#include "animation/animator.hpp" +#include "animation/ease.hpp" +#include "application.hpp" +#include "render/passes/clear-pass.hpp" +#include "game/context.hpp" +#include "debug/logger.hpp" + +namespace game { +namespace state { + +splash::splash(game::context& ctx): + game::state::base(ctx) +{ + ctx.logger->push_task("Entering splash state"); + + ctx.ui_clear_pass->set_cleared_buffers(true, true, false); + + // Load animation timing configuration + double splash_fade_in_duration = 0.0; + double splash_duration = 0.0; + double splash_fade_out_duration = 0.0; + if (ctx.config->contains("splash_fade_in_duration")) + splash_fade_in_duration = (*ctx.config)["splash_fade_in_duration"].get(); + if (ctx.config->contains("splash_duration")) + splash_duration = (*ctx.config)["splash_duration"].get(); + if (ctx.config->contains("splash_fade_out_duration")) + splash_fade_out_duration = (*ctx.config)["splash_fade_out_duration"].get(); + + // Build splash fade in animation + ctx.splash_fade_in_animation = new animation(); + animation_channel* splash_fade_in_opacity_channel = ctx.splash_fade_in_animation->add_channel(0); + ctx.splash_fade_in_animation->set_interpolator(ease::out_cubic); + splash_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); + splash_fade_in_opacity_channel->insert_keyframe({splash_fade_in_duration, 1.0f}); + splash_fade_in_opacity_channel->insert_keyframe({splash_fade_in_duration + splash_duration, 1.0f}); + + // Build splash fade out animation + ctx.splash_fade_out_animation = new animation(); + animation_channel* splash_fade_out_opacity_channel = ctx.splash_fade_out_animation->add_channel(0); + ctx.splash_fade_out_animation->set_interpolator(ease::out_cubic); + splash_fade_out_opacity_channel->insert_keyframe({0.0, 1.0f}); + splash_fade_out_opacity_channel->insert_keyframe({splash_fade_out_duration, 0.0f}); + + // Setup animation frame callbacks + auto set_splash_opacity = [&ctx](int channel, const float& opacity) + { + static_cast*>(ctx.splash_billboard_material->get_property("tint"))->set_value(float4{1, 1, 1, opacity}); + }; + ctx.splash_fade_in_animation->set_frame_callback(set_splash_opacity); + ctx.splash_fade_out_animation->set_frame_callback(set_splash_opacity); + + // Reset splash color when animation starts + ctx.splash_fade_in_animation->set_start_callback + ( + [&ctx]() + { + static_cast*>(ctx.splash_billboard_material->get_property("tint"))->set_value(float4{1, 1, 1, 0}); + ctx.splash_billboard_material->update_tweens(); + } + ); + + // Trigger splash fade out animation when splash fade in animation ends + ctx.splash_fade_in_animation->set_end_callback + ( + [&ctx]() + { + ctx.splash_fade_out_animation->play(); + } + ); + + // Trigger a state change when the splash fade out animation ends + ctx.splash_fade_out_animation->set_end_callback + ( + [&ctx]() + { + // 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, true)); + } + ); + } + ); + + // Add splash fade animations to animator + ctx.animator->add_animation(ctx.splash_fade_in_animation); + ctx.animator->add_animation(ctx.splash_fade_out_animation); + + // Start splash fade in animation + ctx.splash_fade_in_animation->play(); + + // Set up splash 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) + { + // Black out screen + ctx.rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); + ctx.rasterizer->clear_framebuffer(true, false, false); + ctx.app->swap_buffers(); + + // Change to main menu state + ctx.state_machine.pop(); + ctx.state_machine.emplace(new game::state::main_menu(ctx, true)); + } + } + ); + ctx.input_listener->set_enabled(true); + + // Add splash billboard to UI scene + ctx.ui_scene->add_object(ctx.splash_billboard); + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +splash::~splash() +{ + ctx.logger->push_task("Exiting splash state"); + + // Remove splash billboard from UI scene + ctx.ui_scene->remove_object(ctx.splash_billboard); + + // Disable splash skipper + ctx.input_listener->set_enabled(false); + ctx.input_listener->set_callback(nullptr); + + // Destruct splash fade animations + ctx.animator->remove_animation(ctx.splash_fade_in_animation); + ctx.animator->remove_animation(ctx.splash_fade_out_animation); + delete ctx.splash_fade_in_animation; + delete ctx.splash_fade_out_animation; + ctx.splash_fade_in_animation = nullptr; + ctx.splash_fade_out_animation = nullptr; + + ctx.ui_clear_pass->set_cleared_buffers(false, true, false); + + ctx.logger->pop_task(EXIT_SUCCESS); +} + +} // namespace state +} // namespace game diff --git a/src/game/states/splash.hpp b/src/game/state/splash.hpp similarity index 84% rename from src/game/states/splash.hpp rename to src/game/state/splash.hpp index bfb8999..4fc0e4c 100644 --- a/src/game/states/splash.hpp +++ b/src/game/state/splash.hpp @@ -20,18 +20,17 @@ #ifndef ANTKEEPER_GAME_STATE_SPLASH_HPP #define ANTKEEPER_GAME_STATE_SPLASH_HPP -#include "game/context.hpp" +#include "game/state/base.hpp" namespace game { namespace state { -/// Splash screen game state functions. -namespace splash { - -void enter(game::context* ctx); -void exit(game::context* ctx); - -} // namespace splash +class splash: public game::state::base +{ +public: + splash(game::context& ctx); + virtual ~splash(); +}; } // namespace state } // namespace game diff --git a/src/game/states/boot.cpp b/src/game/states/boot.cpp deleted file mode 100644 index 5a7601a..0000000 --- a/src/game/states/boot.cpp +++ /dev/null @@ -1,1230 +0,0 @@ -/* - * 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/states/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/states/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 -#include - -namespace game { -namespace state { -namespace boot { - -static constexpr double seconds_per_day = 24.0 * 60.0 * 60.0; - -static void parse_options(game::context* ctx, int argc, char** argv); -static void setup_resources(game::context* ctx); -static void load_config(game::context* ctx); -static void load_strings(game::context* ctx); -static void setup_window(game::context* ctx); -static void setup_rendering(game::context* ctx); -static void setup_sound(game::context* ctx); -static void setup_scenes(game::context* ctx); -static void setup_animation(game::context* ctx); -static void setup_entities(game::context* ctx); -static void setup_systems(game::context* ctx); -static void setup_controls(game::context* ctx); -static void setup_ui(game::context* ctx); -static void setup_cli(game::context* ctx); -static void setup_callbacks(game::context* ctx); - -void enter(application* app, int argc, char** argv) -{ - // Get application logger - debug::logger* logger = app->get_logger(); - - // Allocate game context - game::context* ctx = new game::context(); - ctx->app = app; - ctx->logger = logger; - - // Init game context - try - { - parse_options(ctx, argc, argv); - setup_resources(ctx); - load_config(ctx); - load_strings(ctx); - setup_window(ctx); - setup_rendering(ctx); - setup_sound(ctx); - setup_scenes(ctx); - setup_animation(ctx); - setup_entities(ctx); - setup_systems(ctx); - setup_controls(ctx); - setup_ui(ctx); - setup_cli(ctx); - setup_callbacks(ctx); - } - catch (const std::exception& e) - { - logger->error("Caught exception: \"" + std::string(e.what()) + "\""); - logger->pop_task(EXIT_FAILURE); - return; - } - - // Set update rate - if (ctx->config->contains("update_rate")) - { - app->set_update_rate((*ctx->config)["update_rate"].get()); - } - - // Clear paused state - - // Queue next application state - application::state next_state; - next_state.name = "splash"; - next_state.enter = std::bind(game::state::splash::enter, ctx); - next_state.exit = std::bind(game::state::splash::exit, ctx); - app->queue_state(next_state); -} - -void exit(application* app) -{} - -void parse_options(game::context* ctx, 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")) - ctx->option_continue = true; - - // --data - if (result.count("data")) - ctx->option_data = result["data"].as(); - - // --fullscreen - if (result.count("fullscreen")) - ctx->option_fullscreen = true; - - // --new-game - if (result.count("new-game")) - ctx->option_new_game = true; - - // --quick-start - if (result.count("quick-start")) - ctx->option_quick_start = true; - - // --reset - if (result.count("reset")) - ctx->option_reset = true; - - // --vsync - if (result.count("vsync")) - ctx->option_v_sync = (result["vsync"].as()) ? true : false; - - // --windowed - if (result.count("windowed")) - ctx->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 setup_resources(game::context* ctx) -{ - 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 + "\""); - logger->log("Detected config path as \"" + ctx->config_path + "\""); - - // 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::string& path: config_paths) - { - if (!path_exists(path)) - { - logger->push_task("Creating directory \"" + path + "\""); - if (create_directory(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::string log_filename = ctx->config_path + "log.txt"; - ctx->log_filestream.open(log_filename.c_str()); - ctx->log_filestream << logger->get_history(); - logger->redirect(&ctx->log_filestream); - #endif - - // Scan for mods - std::vector mods; - struct dirent** files = nullptr; - if (int n = scandir(ctx->mods_path.c_str(), &files, NULL, alphasort); n >= 0) - { - for (int i = 0; i < n; ++i) - { - struct dirent* file = files[i]; - - switch (file->d_type) - { - case DT_REG: - case DT_DIR: - { - std::string mod_name = file->d_name; - - // Skip hidden files and directories - if (mod_name.front() == '.') - break; - - mods.push_back(mod_name); - } - - default: - break; - } - } - } - - // Determine data package path - if (ctx->option_data.has_value()) - { - ctx->data_package_path = ctx->option_data.value(); - if (std::filesystem::path(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::string& mod_name: mods) - ctx->resource_manager->mount(ctx->mods_path + mod_name); - - // Mount config path - ctx->resource_manager->mount(ctx->config_path); - - // Mount data package - ctx->resource_manager->mount(ctx->data_package_path); - - // 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 load_config(game::context* ctx) -{ - 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 load_strings(game::context* ctx) -{ - 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 setup_window(game::context* ctx) -{ - 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 (ctx->option_fullscreen.has_value()) - fullscreen = true; - else if (ctx->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 (ctx->option_v_sync.has_value()) - v_sync = (ctx->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 setup_rendering(game::context* ctx) -{ - 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 setup_sound(game::context* ctx) -{ - 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 setup_scenes(game::context* ctx) -{ - 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 setup_animation(game::context* ctx) -{ - // 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]() - { - 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]() - { - 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 setup_entities(game::context* ctx) -{ - // Create entity registry - ctx->entity_registry = new entt::registry(); -} - -void setup_systems(game::context* ctx) -{ - 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); - - // Set time scale - double time_scale = 60.0; - if (ctx->config->contains("time_scale")) - { - time_scale = (*ctx->config)["time_scale"].get(); - } - - ctx->orbit_system->set_time_scale(time_scale / seconds_per_day); - ctx->astronomy_system->set_time_scale(time_scale / seconds_per_day); - - // 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 setup_controls(game::context* ctx) -{ - 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]() - { - 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 - ( - [ctx]() - { - std::string path = ctx->screenshots_path + "antkeeper-" + timestamp() + ".png"; - ctx->app->save_frame(path); - } - ); - - // 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 setup_ui(game::context* ctx) -{ - // 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 setup_cli(game::context* ctx) -{ - 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 setup_callbacks(game::context* ctx) -{ - // Set update callback - ctx->app->set_update_callback - ( - [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 function queue - while (!ctx->function_queue.empty()) - { - ctx->function_queue.front()(); - ctx->function_queue.pop(); - } - - // Advance timeline - ctx->timeline->advance(dt); - - // Update controls - for (const auto& control: ctx->controls) - control.second->update(); - - // Update processes - std::for_each - ( - std::execution::par, - ctx->processes.begin(), - ctx->processes.end(), - [t, dt](const auto& process) - { - process.second(t, dt); - } - ); - - 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->app->set_render_callback - ( - [ctx](double alpha) - { - ctx->render_system->draw(alpha); - } - ); -} - -} // namespace boot -} // namespace state -} // namespace game diff --git a/src/game/states/credits.cpp b/src/game/states/credits.cpp deleted file mode 100644 index bda9bbf..0000000 --- a/src/game/states/credits.cpp +++ /dev/null @@ -1,137 +0,0 @@ -/* - * 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/states/credits.hpp" -#include "game/states/extras-menu.hpp" -#include "animation/ease.hpp" -#include "animation/animation.hpp" -#include "animation/animator.hpp" -#include "application.hpp" -#include "scene/text.hpp" - -namespace game { -namespace state { -namespace credits { - -void enter(game::context* ctx) -{ - // 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 - application::state next_state; - next_state.name = "extras_menu"; - next_state.enter = std::bind(game::state::extras_menu::enter, ctx); - next_state.exit = std::bind(game::state::extras_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - } - } - ); - ctx->input_listener->set_enabled(true); - - ctx->ui_scene->add_object(ctx->credits_text); - ctx->credits_text->update_tweens(); -} - -void exit(game::context* ctx) -{ - // 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; -} - -} // namespace credits -} // namespace state -} // namespace game diff --git a/src/game/states/graphics-menu.cpp b/src/game/states/graphics-menu.cpp deleted file mode 100644 index 626325f..0000000 --- a/src/game/states/graphics-menu.cpp +++ /dev/null @@ -1,346 +0,0 @@ -/* - * 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/states/graphics-menu.hpp" -#include "game/states/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 { -namespace graphics_menu { - -static void update_value_text_content(game::context* ctx); - -void enter(game::context* ctx) -{ - // 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(ctx); - - // 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 = [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); - } - - update_value_text_content(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - - // Save display mode config - (*ctx->config)["fullscreen"] = fullscreen; - }; - - auto increase_resolution_callback = [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 - update_value_text_content(ctx); - 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 = [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 - update_value_text_content(ctx); - 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 = [ctx]() - { - bool v_sync = !ctx->app->get_v_sync(); - - ctx->app->set_v_sync(v_sync); - - update_value_text_content(ctx); - 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 = [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 - update_value_text_content(ctx); - - // 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 = [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 - update_value_text_content(ctx); - - // 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 = [ctx]() - { - ctx->dyslexia_font = !ctx->dyslexia_font; - - // Update value text - update_value_text_content(ctx); - - // 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]() - { - 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); - } - ); - }; - - // 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, 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); -} - -static void update_value_text_content(game::context* ctx) -{ - 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 graphics_menu -} // namespace state -} // namespace game diff --git a/src/game/states/main-menu.cpp b/src/game/states/main-menu.cpp deleted file mode 100644 index 15b8097..0000000 --- a/src/game/states/main-menu.cpp +++ /dev/null @@ -1,265 +0,0 @@ -/* - * 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/states/main-menu.hpp" -#include "game/states/options-menu.hpp" -#include "game/states/extras-menu.hpp" -#include "game/states/forage.hpp" -#include "game/states/nuptial-flight.hpp" -#include "game/menu.hpp" -#include "render/passes/clear-pass.hpp" -#include "resources/resource-manager.hpp" -#include "render/model.hpp" -#include "animation/animation.hpp" -#include "animation/animator.hpp" -#include "animation/screen-transition.hpp" -#include "animation/ease.hpp" -#include "animation/timeline.hpp" -#include "application.hpp" -#include - -namespace game { -namespace state { -namespace main_menu { - -static void fade_in_title(game::context* ctx) -{ - ctx->title_fade_animation->set_interpolator(ease::out_cubic); - animation_channel* opacity_channel = ctx->title_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->title_fade_animation->stop(); - ctx->title_fade_animation->play(); -} - -static void fade_out_title(game::context* ctx) -{ - ctx->title_fade_animation->set_interpolator(ease::out_cubic); - animation_channel* opacity_channel = ctx->title_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->title_fade_animation->stop(); - ctx->title_fade_animation->play(); -} - -void enter(game::context* ctx, bool fade_in) -{ - ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - - // Construct title text - ctx->title_text = new scene::text(); - ctx->title_text->set_material(&ctx->title_font_material); - ctx->title_text->set_font(&ctx->title_font); - ctx->title_text->set_color({1.0f, 1.0f, 1.0f, 1.0f}); - ctx->title_text->set_content((*ctx->strings)["title_antkeeper"]); - - // Align title text - const auto& title_aabb = static_cast&>(ctx->title_text->get_local_bounds()); - float title_w = title_aabb.max_point.x - title_aabb.min_point.x; - float title_h = title_aabb.max_point.y - title_aabb.min_point.y; - ctx->title_text->set_translation({std::round(-title_w * 0.5f), std::round(-title_h * 0.5f + (ctx->app->get_viewport_dimensions().y / 3.0f) / 2.0f), 0.0f}); - ctx->title_text->update_tweens(); - - // Add title text to UI - ctx->ui_scene->add_object(ctx->title_text); - - // Construct title fade animation - ctx->title_fade_animation = new animation(); - animation_channel* opacity_channel = ctx->title_fade_animation->add_channel(0); - - ctx->title_fade_animation->set_frame_callback - ( - [ctx](int channel, const float& opacity) - { - float4 color = ctx->title_text->get_color(); - color[3] = opacity; - ctx->title_text->set_color(color); - } - ); - ctx->animator->add_animation(ctx->title_fade_animation); - - // Construct menu item texts - scene::text* start_text = new scene::text(); - scene::text* options_text = new scene::text(); - scene::text* extras_text = new scene::text(); - scene::text* quit_text = new scene::text(); - - // Build list of menu item texts - ctx->menu_item_texts.push_back({start_text, nullptr}); - ctx->menu_item_texts.push_back({options_text, nullptr}); - ctx->menu_item_texts.push_back({extras_text, nullptr}); - ctx->menu_item_texts.push_back({quit_text, nullptr}); - - // Set content of menu item texts - start_text->set_content((*ctx->strings)["main_menu_start"]); - options_text->set_content((*ctx->strings)["main_menu_options"]); - extras_text->set_content((*ctx->strings)["main_menu_extras"]); - quit_text->set_content((*ctx->strings)["main_menu_quit"]); - - // Init menu item index - game::menu::init_menu_item_index(ctx, "main"); - - game::menu::update_text_color(ctx); - game::menu::update_text_font(ctx); - game::menu::align_text(ctx, true, false, (-ctx->app->get_viewport_dimensions().y / 3.0f) / 2.0f); - game::menu::update_text_tweens(ctx); - game::menu::add_text_to_ui(ctx); - game::menu::setup_animations(ctx); - - auto select_start_callback = [ctx]() - { - // Disable controls and menu callbacks - game::menu::clear_controls(ctx); - game::menu::clear_callbacks(ctx); - - // Create change state function - auto change_state_nuptial_flight = [ctx]() - { - application::state next_state; - next_state.name = "nuptial_flight"; - next_state.enter = std::bind(game::state::nuptial_flight::enter, ctx); - next_state.exit = std::bind(game::state::nuptial_flight::exit, ctx); - ctx->app->change_state(next_state); - }; - - // Set up timing - const float fade_out_duration = 1.0f; - - // Schedule state change - timeline* timeline = ctx->timeline; - float t = timeline->get_position(); - timeline->add_sequence({{t + fade_out_duration, change_state_nuptial_flight}}); - - // Start fade out to white - ctx->fade_transition_color->set_value({1, 1, 1}); - ctx->fade_transition->transition(fade_out_duration, false, ease::out_cubic, false); - }; - auto select_options_callback = [ctx]() - { - game::menu::clear_controls(ctx); - - // Fade out title - fade_out_title(ctx); - - // Fade out menu - game::menu::fade_out - ( - 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); - } - ); - }; - auto select_extras_callback = [ctx]() - { - // Disable controls - game::menu::clear_controls(ctx); - - // Fade out title - fade_out_title(ctx); - - // Fade out menu - game::menu::fade_out - ( - ctx, - [ctx]() - { - application::state next_state; - next_state.name = "extras_menu"; - next_state.enter = std::bind(game::state::extras_menu::enter, ctx); - next_state.exit = std::bind(game::state::extras_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - ); - }; - auto select_quit_callback = [ctx]() - { - ctx->app->close(EXIT_SUCCESS); - }; - - // Build list of menu select callbacks - ctx->menu_select_callbacks.push_back(select_start_callback); - ctx->menu_select_callbacks.push_back(select_options_callback); - ctx->menu_select_callbacks.push_back(select_extras_callback); - ctx->menu_select_callbacks.push_back(select_quit_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_quit_callback; - - // Queue menu control setup - ctx->function_queue.push(std::bind(game::menu::setup_controls, ctx)); - - if (fade_in) - { - ctx->fade_transition->transition(0.5f, true, ease::out_cubic); - } - else - { - // Fade in title - ctx->title_text->set_color({1.0f, 1.0f, 1.0f, 0.0f}); - ctx->title_text->update_tweens(); - fade_in_title(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); - - // Destruct title animation - ctx->animator->remove_animation(ctx->title_fade_animation); - delete ctx->title_fade_animation; - ctx->title_fade_animation = nullptr; - - // Destruct title text - ctx->ui_scene->remove_object(ctx->title_text); - delete ctx->title_text; - ctx->title_text = nullptr; -} - -} // namespace main_menu -} // namespace state -} // namespace game diff --git a/src/game/states/nuptial-flight.cpp b/src/game/states/nuptial-flight.cpp deleted file mode 100644 index 4f69728..0000000 --- a/src/game/states/nuptial-flight.cpp +++ /dev/null @@ -1,557 +0,0 @@ -/* - * 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/states/nuptial-flight.hpp" -#include "game/states/pause-menu.hpp" -#include "entity/archetype.hpp" -#include "entity/systems/astronomy.hpp" -#include "entity/systems/orbit.hpp" -#include "entity/systems/camera.hpp" -#include "entity/components/observer.hpp" -#include "entity/components/transform.hpp" -#include "entity/components/terrain.hpp" -#include "entity/components/camera.hpp" -#include "entity/components/constraints/spring-to.hpp" -#include "entity/components/constraints/three-dof.hpp" -#include "entity/components/constraint-stack.hpp" -#include "entity/commands.hpp" -#include "animation/screen-transition.hpp" -#include "animation/ease.hpp" -#include "resources/resource-manager.hpp" -#include "game/world.hpp" -#include "application.hpp" -#include "render/passes/clear-pass.hpp" -#include -#include -#include "state-machine.hpp" - -namespace game { -namespace state { -namespace nuptial_flight { - -static void setup_camera(game::context* ctx); -static void enable_controls(game::context* ctx); -static void disable_controls(game::context* ctx); - -void enter(game::context* ctx) -{ - // Resume if paused - if (ctx->paused_state) - { - // Clear paused state - ctx->paused_state.reset(); - - // Enable controls - enable_controls(ctx); - return; - } - - // Disable UI color clear - ctx->ui_clear_pass->set_cleared_buffers(false, true, false); - - // Create world - game::world::create_stars(ctx); - game::world::create_sun(ctx); - game::world::create_planet(ctx); - game::world::create_moon(ctx); - - // Set time to solar noon - game::world::set_time(ctx, 0.0); - - // Freeze time - game::world::set_time_scale(ctx, 0.0); - - // Switch to surface camera - ctx->underground_camera->set_active(false); - ctx->surface_camera->set_active(true); - - // Find planet EID by name - entity::id planet_eid = ctx->entities["planet"]; - - // Remove terrain component from planet (if any) - //if (ctx->entity_registry->has(planet_eid)) - // ctx->entity_registry->remove(planet_eid); - - // Enable clouds in sky pass - //ctx->surface_sky_pass->set_clouds_model(ctx->resource_manager->load("cloud-plane.mdl")); - - // Create observer - entity::id observer_eid = ctx->entity_registry->create(); - { - entity::component::observer observer; - observer.reference_body_eid = planet_eid; - observer.elevation = 2000.0; - observer.latitude = 0.0; - observer.longitude = 0.0; - observer.camera = ctx->surface_camera; - ctx->entity_registry->assign(observer_eid, observer); - - // Set reference location of astronomy system - ctx->astronomy_system->set_reference_body(planet_eid); - ctx->astronomy_system->set_observer_location(double3{observer.elevation, observer.latitude, observer.longitude}); - } - - // Setup camera - setup_camera(ctx); - /* - ctx->surface_camera->look_at({0, 0, 1}, {0, 0, 0}, {0, 1, 0}); - ctx->surface_camera->set_exposure(-14.5f); - ctx->surface_scene->update_tweens(); - */ - - // Queue fade in - ctx->fade_transition_color->set_value({1, 1, 1}); - ctx->function_queue.push(std::bind(&screen_transition::transition, ctx->fade_transition, 5.0f, true, math::lerp, true)); - - // Queue control setup - ctx->function_queue.push(std::bind(enable_controls, ctx)); -} - -void exit(game::context* ctx) -{ - // Resume time - //const double time_scale = (*ctx->config)["time_scale"].get(); - //game::world::set_time_scale(ctx, time_scale); -} - -void setup_camera(game::context* ctx) -{ - // Switch to surface camera - ctx->underground_camera->set_active(false); - ctx->surface_camera->set_active(true); - - // Create surface camera entity - if (!ctx->entities.count("surface_cam")) - { - // Create camera target entity - entity::id target_eid = ctx->entity_registry->create(); - ctx->entities["surface_cam_target"] = target_eid; - { - // Transform - entity::component::transform target_transform; - target_transform.local = math::identity_transform; - target_transform.world = target_transform.local; - target_transform.warp = true; - ctx->entity_registry->assign(target_eid, target_transform); - } - - // Create camera entity - entity::id camera_eid = ctx->entity_registry->create(); - ctx->entities["surface_cam"] = camera_eid; - - // Create camera transform component - entity::component::transform transform; - transform.local = math::identity_transform; - transform.world = transform.local; - transform.warp = true; - ctx->entity_registry->assign(camera_eid, transform); - - // Create camera camera component - entity::component::camera camera; - camera.object = ctx->surface_camera; - ctx->entity_registry->assign(camera_eid, camera); - - // Create camera 3DOF constraint entity - entity::id three_dof_constraint_eid = ctx->entity_registry->create(); - ctx->entities["surface_cam_3dof"] = three_dof_constraint_eid; - { - // Create 3DOF to constraint - entity::component::constraint::three_dof three_dof; - three_dof.yaw = 0.0f; - three_dof.pitch = 0.0f; - three_dof.roll = 0.0f; - ctx->entity_registry->assign(three_dof_constraint_eid, three_dof); - - // Create constraint stack node component - entity::component::constraint_stack_node node; - node.active = true; - node.weight = 1.0f; - node.next = entt::null; - ctx->entity_registry->assign(three_dof_constraint_eid, node); - } - - // Create camera spring to constraint entity - entity::id spring_constraint_eid = ctx->entity_registry->create(); - { - // Create spring to constraint - entity::component::constraint::spring_to spring; - spring.target = target_eid; - spring.translation = {{0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 0.0f}, 1.0f, math::two_pi}; - spring.translation.w = hz_to_rads(8.0f); - - spring.spring_translation = true; - spring.spring_rotation = false; - ctx->entity_registry->assign(spring_constraint_eid, spring); - - // Create constraint stack node component - entity::component::constraint_stack_node node; - node.active = true; - node.weight = 1.0f; - node.next = three_dof_constraint_eid; - ctx->entity_registry->assign(spring_constraint_eid, node); - } - - // Create camera constraint stack component - entity::component::constraint_stack constraint_stack; - constraint_stack.head = spring_constraint_eid; - ctx->entity_registry->assign(camera_eid, constraint_stack); - } - - ctx->surface_camera->set_exposure(-12.0f); -} - -void enable_controls(game::context* ctx) -{ - // Get camera entities - entity::id camera_eid = ctx->entities["surface_cam"]; - entity::id target_eid = ctx->entities["surface_cam_target"]; - entity::id three_dof_eid = ctx->entities["surface_cam_3dof"]; - - const float slow_modifier = 0.25f; - const float fast_modifier = 4.0f; - const float dolly_speed = 20.0f; - const float truck_speed = dolly_speed; - const float pedestal_speed = 30.0f; - float mouse_tilt_sensitivity = 1.0f; - float mouse_pan_sensitivity = 1.0f; - bool mouse_invert_tilt = false; - bool mouse_invert_pan = false; - float gamepad_tilt_sensitivity = 1.0f; - float gamepad_pan_sensitivity = 1.0f; - bool gamepad_invert_tilt = false; - bool gamepad_invert_pan = false; - bool mouse_look_toggle = false; - ctx->mouse_look = false; - - if (ctx->config->contains("mouse_tilt_sensitivity")) - mouse_tilt_sensitivity = math::radians((*ctx->config)["mouse_tilt_sensitivity"].get()); - if (ctx->config->contains("mouse_pan_sensitivity")) - mouse_pan_sensitivity = math::radians((*ctx->config)["mouse_pan_sensitivity"].get()); - if (ctx->config->contains("mouse_invert_tilt")) - mouse_invert_tilt = math::radians((*ctx->config)["mouse_invert_tilt"].get()); - if (ctx->config->contains("mouse_invert_pan")) - mouse_invert_pan = math::radians((*ctx->config)["mouse_invert_pan"].get()); - if (ctx->config->contains("mouse_look_toggle")) - mouse_look_toggle = math::radians((*ctx->config)["mouse_look_toggle"].get()); - - if (ctx->config->contains("gamepad_tilt_sensitivity")) - gamepad_tilt_sensitivity = math::radians((*ctx->config)["gamepad_tilt_sensitivity"].get()); - if (ctx->config->contains("gamepad_pan_sensitivity")) - gamepad_pan_sensitivity = math::radians((*ctx->config)["gamepad_pan_sensitivity"].get()); - if (ctx->config->contains("gamepad_invert_tilt")) - gamepad_invert_tilt = math::radians((*ctx->config)["gamepad_invert_tilt"].get()); - if (ctx->config->contains("gamepad_invert_pan")) - gamepad_invert_pan = math::radians((*ctx->config)["gamepad_invert_pan"].get()); - - const input::control* move_slow = ctx->controls["move_slow"]; - const input::control* move_fast = ctx->controls["move_fast"]; - const input::control* mouse_look = ctx->controls["mouse_look"]; - - float mouse_tilt_factor = mouse_tilt_sensitivity * (mouse_invert_tilt ? -1.0f : 1.0f); - float mouse_pan_factor = mouse_pan_sensitivity * (mouse_invert_pan ? -1.0f : 1.0f); - float gamepad_tilt_factor = gamepad_tilt_sensitivity * (gamepad_invert_tilt ? -1.0f : 1.0f); - float gamepad_pan_factor = gamepad_pan_sensitivity * (gamepad_invert_pan ? -1.0f : 1.0f); - - ctx->controls["move_forward"]->set_active_callback - ( - [ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) - { - if (move_slow->is_active()) - value *= slow_modifier; - if (move_fast->is_active()) - value *= fast_modifier; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); - - const float3 movement = {0.0f, 0.0f, -truck_speed * value * (1.0f / 60.0f)}; - entity::command::translate(*ctx->entity_registry, target_eid, yaw * movement); - } - ); - - // Dolly backward - ctx->controls["move_back"]->set_active_callback - ( - [ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) - { - if (move_slow->is_active()) - value *= slow_modifier; - if (move_fast->is_active()) - value *= fast_modifier; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); - - const float3 movement = {0.0f, 0.0f, truck_speed * value * (1.0f / 60.0f)}; - entity::command::translate(*ctx->entity_registry, target_eid, yaw * movement); - } - ); - - // Truck right - ctx->controls["move_right"]->set_active_callback - ( - [ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) - { - if (move_slow->is_active()) - value *= slow_modifier; - if (move_fast->is_active()) - value *= fast_modifier; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); - - const float3 movement = {truck_speed * value * (1.0f / 60.0f), 0.0f, 0.0f}; - entity::command::translate(*ctx->entity_registry, target_eid, yaw * movement); - } - ); - - // Truck left - ctx->controls["move_left"]->set_active_callback - ( - [ctx, target_eid, three_dof_eid, truck_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) - { - if (move_slow->is_active()) - value *= slow_modifier; - if (move_fast->is_active()) - value *= fast_modifier; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - const math::quaternion yaw = math::angle_axis(three_dof.yaw, {0.0f, 1.0f, 0.0f}); - - const float3 movement = {-truck_speed * value * (1.0f / 60.0f), 0.0f, 0.0f}; - entity::command::translate(*ctx->entity_registry, target_eid, yaw * movement); - } - ); - - // Pedestal up - ctx->controls["move_up"]->set_active_callback - ( - [ctx, target_eid, pedestal_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) - { - if (move_slow->is_active()) - value *= slow_modifier; - if (move_fast->is_active()) - value *= fast_modifier; - - const float3 movement = {0.0f, pedestal_speed * value * (1.0f / 60.0f), 0.0f}; - entity::command::translate(*ctx->entity_registry, target_eid, movement); - } - ); - - // Pedestal down - ctx->controls["move_down"]->set_active_callback - ( - [ctx, target_eid, pedestal_speed, move_slow, move_fast, slow_modifier, fast_modifier](float value) - { - if (move_slow->is_active()) - value *= slow_modifier; - if (move_fast->is_active()) - value *= fast_modifier; - - const float3 movement = {0.0f, -pedestal_speed * value * (1.0f / 60.0f), 0.0f}; - entity::command::translate(*ctx->entity_registry, target_eid, movement); - } - ); - - // Mouse rotate - ctx->controls["mouse_look"]->set_activated_callback - ( - [ctx, mouse_look_toggle]() - { - if (mouse_look_toggle) - ctx->mouse_look = !ctx->mouse_look; - else - ctx->mouse_look = true; - - ctx->app->set_relative_mouse_mode(ctx->mouse_look); - } - ); - ctx->controls["mouse_look"]->set_deactivated_callback - ( - [ctx, mouse_look_toggle]() - { - if (!mouse_look_toggle) - { - ctx->mouse_look = false; - ctx->app->set_relative_mouse_mode(false); - } - } - ); - // Pan left - ctx->controls["look_left_gamepad"]->set_active_callback - ( - [ctx, three_dof_eid, gamepad_pan_factor](float value) - { - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.yaw += gamepad_pan_factor * value * (1.0f / 60.0f); - } - ); - ctx->controls["look_left_mouse"]->set_active_callback - ( - [ctx, three_dof_eid, mouse_pan_factor](float value) - { - if (!ctx->mouse_look) - return; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.yaw += mouse_pan_factor * value * (1.0f / 60.0f); - } - ); - - // Pan right - ctx->controls["look_right_gamepad"]->set_active_callback - ( - [ctx, three_dof_eid, gamepad_pan_factor](float value) - { - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.yaw -= gamepad_pan_factor * value * (1.0f / 60.0f); - } - ); - ctx->controls["look_right_mouse"]->set_active_callback - ( - [ctx, three_dof_eid, mouse_pan_factor](float value) - { - if (!ctx->mouse_look) - return; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.yaw -= mouse_pan_factor * value * (1.0f / 60.0f); - } - ); - // Tilt up - ctx->controls["look_up_gamepad"]->set_active_callback - ( - [ctx, three_dof_eid, gamepad_tilt_factor](float value) - { - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.pitch -= gamepad_tilt_factor * value * (1.0f / 60.0f); - three_dof.pitch = std::max(math::radians(-90.0f), three_dof.pitch); - } - ); - ctx->controls["look_up_mouse"]->set_active_callback - ( - [ctx, three_dof_eid, mouse_tilt_factor](float value) - { - if (!ctx->mouse_look) - return; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.pitch -= mouse_tilt_factor * value * (1.0f / 60.0f); - three_dof.pitch = std::max(math::radians(-90.0f), three_dof.pitch); - } - ); - // Tilt down - ctx->controls["look_down_gamepad"]->set_active_callback - ( - [ctx, three_dof_eid, gamepad_tilt_factor](float value) - { - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.pitch += gamepad_tilt_factor * value * (1.0f / 60.0f); - three_dof.pitch = std::min(math::radians(90.0f), three_dof.pitch); - } - ); - ctx->controls["look_down_mouse"]->set_active_callback - ( - [ctx, three_dof_eid, mouse_tilt_factor](float value) - { - if (!ctx->mouse_look) - return; - - auto& three_dof = ctx->entity_registry->get(three_dof_eid); - three_dof.pitch += mouse_tilt_factor * value * (1.0f / 60.0f); - three_dof.pitch = std::min(math::radians(90.0f), three_dof.pitch); - } - ); - /* - // Use tool - ctx->controls["use_tool"]->set_activated_callback - ( - [ctx]() - { - if (ctx->entities.count("active_tool")) - { - entity::id tool_eid = ctx->entities["active_tool"]; - const auto& tool = ctx->entity_registry->get(tool_eid); - if (tool.activated) - tool.activated(); - } - } - ); - ctx->controls["use_tool"]->set_deactivated_callback - ( - [ctx]() - { - if (ctx->entities.count("active_tool")) - { - entity::id tool_eid = ctx->entities["active_tool"]; - const auto& tool = ctx->entity_registry->get(tool_eid); - if (tool.deactivated) - tool.deactivated(); - } - } - ); - ctx->controls["use_tool"]->set_active_callback - ( - [ctx](float value) - { - if (ctx->entities.count("active_tool")) - { - entity::id tool_eid = ctx->entities["active_tool"]; - const auto& tool = ctx->entity_registry->get(tool_eid); - if (tool.active) - tool.active(); - } - } - ); - */ - - // Setup pause control - ctx->controls["pause"]->set_activated_callback - ( - [ctx]() - { - // Disable controls - disable_controls(ctx); - - // Store paused state - ctx->paused_state = - { - "nuptial_flight", - std::bind(game::state::nuptial_flight::enter, ctx), - std::bind(game::state::nuptial_flight::exit, ctx) - }; - - // Change to pause menu state - application::state next_state; - next_state.name = "pause_menu"; - next_state.enter = std::bind(game::state::pause_menu::enter, ctx); - next_state.exit = std::bind(game::state::pause_menu::exit, ctx); - ctx->app->change_state(next_state); - } - ); -} - -void disable_controls(game::context* ctx) -{ - ctx->controls["pause"]->set_activated_callback(nullptr); -} - -} // namespace nuptial_flight -} // namespace state -} // namespace game diff --git a/src/game/states/options-menu.cpp b/src/game/states/options-menu.cpp deleted file mode 100644 index c2ae8f8..0000000 --- a/src/game/states/options-menu.cpp +++ /dev/null @@ -1,214 +0,0 @@ -/* - * 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/states/options-menu.hpp" -#include "game/states/main-menu.hpp" -#include "game/states/controls-menu.hpp" -#include "game/states/graphics-menu.hpp" -#include "game/states/sound-menu.hpp" -#include "game/states/language-menu.hpp" -#include "game/states/pause-menu.hpp" -#include "game/save.hpp" -#include "game/menu.hpp" -#include "animation/ease.hpp" -#include "animation/animation.hpp" -#include "animation/animator.hpp" -#include "application.hpp" -#include "scene/text.hpp" - -namespace game { -namespace state { -namespace options_menu { - -void enter(game::context* ctx) -{ - // Construct menu item texts - scene::text* controls_text = new scene::text(); - scene::text* graphics_text = new scene::text(); - scene::text* sound_text = new scene::text(); - scene::text* language_text = new scene::text(); - scene::text* back_text = new scene::text(); - - // Set content of menu item texts - controls_text->set_content((*ctx->strings)["options_menu_controls"]); - graphics_text->set_content((*ctx->strings)["options_menu_graphics"]); - sound_text->set_content((*ctx->strings)["options_menu_sound"]); - language_text->set_content((*ctx->strings)["options_menu_language"]); - back_text->set_content((*ctx->strings)["back"]); - - // Build list of menu item texts - ctx->menu_item_texts.push_back({controls_text, nullptr}); - ctx->menu_item_texts.push_back({graphics_text, nullptr}); - ctx->menu_item_texts.push_back({sound_text, nullptr}); - ctx->menu_item_texts.push_back({language_text, nullptr}); - ctx->menu_item_texts.push_back({back_text, nullptr}); - - // Init menu item index - game::menu::init_menu_item_index(ctx, "options"); - - game::menu::update_text_color(ctx); - game::menu::update_text_font(ctx); - game::menu::align_text(ctx, true); - game::menu::update_text_tweens(ctx); - game::menu::add_text_to_ui(ctx); - game::menu::setup_animations(ctx); - - // Construct menu item callbacks - auto select_controls_callback = [ctx]() - { - // Disable controls - game::menu::clear_controls(ctx); - - // Return to main menu - 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); - } - ); - }; - auto select_graphics_callback = [ctx]() - { - // Disable controls - game::menu::clear_controls(ctx); - - // Return to main menu - game::menu::fade_out - ( - ctx, - [ctx]() - { - application::state next_state; - next_state.name = "graphics_menu"; - next_state.enter = std::bind(game::state::graphics_menu::enter, ctx); - next_state.exit = std::bind(game::state::graphics_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - ); - }; - auto select_sound_callback = [ctx]() - { - // Disable controls - game::menu::clear_controls(ctx); - - // Return to main menu - game::menu::fade_out - ( - ctx, - [ctx]() - { - application::state next_state; - next_state.name = "sound_menu"; - next_state.enter = std::bind(game::state::sound_menu::enter, ctx); - next_state.exit = std::bind(game::state::sound_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - ); - }; - auto select_language_callback = [ctx]() - { - // Disable controls - game::menu::clear_controls(ctx); - - // Return to main menu - game::menu::fade_out - ( - ctx, - [ctx]() - { - application::state next_state; - next_state.name = "language_menu"; - next_state.enter = std::bind(game::state::language_menu::enter, ctx); - next_state.exit = std::bind(game::state::language_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - ); - }; - auto select_back_callback = [ctx]() - { - // Disable controls - game::menu::clear_controls(ctx); - - // Save config - game::save_config(ctx); - - application::state next_state; - if (ctx->paused_state) - { - // Return to pause menu - next_state.name = "pause_menu"; - next_state.enter = std::bind(game::state::pause_menu::enter, ctx); - next_state.exit = std::bind(game::state::pause_menu::exit, ctx); - } - else - { - // Return to main menu - 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); - } - - game::menu::fade_out(ctx, std::bind(&application::queue_state, ctx->app, next_state)); - }; - - // Build list of menu select callbacks - ctx->menu_select_callbacks.push_back(select_controls_callback); - ctx->menu_select_callbacks.push_back(select_graphics_callback); - ctx->menu_select_callbacks.push_back(select_sound_callback); - ctx->menu_select_callbacks.push_back(select_language_callback); - ctx->menu_select_callbacks.push_back(select_back_callback); - - // Build list of menu right callbacks - ctx->menu_right_callbacks.resize(5, nullptr); - - // Build list of menu left callbacks - ctx->menu_left_callbacks.resize(5, 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 config - game::save_config(ctx); -} - -} // namespace options_menu -} // namespace state -} // namespace game diff --git a/src/game/states/sound-menu.cpp b/src/game/states/sound-menu.cpp deleted file mode 100644 index 5861690..0000000 --- a/src/game/states/sound-menu.cpp +++ /dev/null @@ -1,254 +0,0 @@ -/* - * 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/states/sound-menu.hpp" -#include "game/states/options-menu.hpp" -#include "application.hpp" -#include "scene/text.hpp" -#include "debug/logger.hpp" -#include "game/menu.hpp" -#include "animation/timeline.hpp" - -namespace game { -namespace state { -namespace sound_menu { - -static void update_value_text_content(game::context* ctx) -{ - 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(std::to_string(static_cast(std::round(ctx->master_volume * 100.0f))) + "%"); - std::get<1>(ctx->menu_item_texts[1])->set_content(std::to_string(static_cast(std::round(ctx->ambience_volume * 100.0f))) + "%"); - std::get<1>(ctx->menu_item_texts[2])->set_content(std::to_string(static_cast(std::round(ctx->effects_volume * 100.0f))) + "%"); - std::get<1>(ctx->menu_item_texts[3])->set_content((ctx->mono_audio) ? string_on : string_off); - std::get<1>(ctx->menu_item_texts[4])->set_content((ctx->captions) ? string_on : string_off); - std::get<1>(ctx->menu_item_texts[5])->set_content(std::to_string(static_cast(std::round(ctx->captions_size * 100.0f))) + "%"); -} - -void enter(game::context* ctx) -{ - // Construct menu item texts - scene::text* master_volume_name_text = new scene::text(); - scene::text* master_volume_value_text = new scene::text(); - scene::text* ambience_volume_name_text = new scene::text(); - scene::text* ambience_volume_value_text = new scene::text(); - scene::text* effects_volume_name_text = new scene::text(); - scene::text* effects_volume_value_text = new scene::text(); - scene::text* mono_audio_name_text = new scene::text(); - scene::text* mono_audio_value_text = new scene::text(); - scene::text* captions_name_text = new scene::text(); - scene::text* captions_value_text = new scene::text(); - scene::text* captions_size_name_text = new scene::text(); - scene::text* captions_size_value_text = new scene::text(); - scene::text* back_text = new scene::text(); - - // Build list of menu item texts - ctx->menu_item_texts.push_back({master_volume_name_text, master_volume_value_text}); - ctx->menu_item_texts.push_back({ambience_volume_name_text, ambience_volume_value_text}); - ctx->menu_item_texts.push_back({effects_volume_name_text, effects_volume_value_text}); - ctx->menu_item_texts.push_back({mono_audio_name_text, mono_audio_value_text}); - ctx->menu_item_texts.push_back({captions_name_text, captions_value_text}); - ctx->menu_item_texts.push_back({captions_size_name_text, captions_size_value_text}); - ctx->menu_item_texts.push_back({back_text, nullptr}); - - // Set content of menu item texts - master_volume_name_text->set_content((*ctx->strings)["sound_menu_master_volume"]); - ambience_volume_name_text->set_content((*ctx->strings)["sound_menu_ambience_volume"]); - effects_volume_name_text->set_content((*ctx->strings)["sound_menu_effects_volume"]); - mono_audio_name_text->set_content((*ctx->strings)["sound_menu_mono_audio"]); - captions_name_text->set_content((*ctx->strings)["sound_menu_captions"]); - captions_size_name_text->set_content((*ctx->strings)["sound_menu_captions_size"]); - back_text->set_content((*ctx->strings)["back"]); - update_value_text_content(ctx); - - // Init menu item index - game::menu::init_menu_item_index(ctx, "sound"); - - 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 increase_volume_callback = [ctx](float* volume) - { - // Increase volume - if (ctx->controls["menu_modifier"]->is_active()) - *volume += 0.01f; - else - *volume += 0.1f; - - // Limit volume - if (*volume > 1.0f) - *volume = 1.0f; - - update_value_text_content(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - }; - auto decrease_volume_callback = [ctx](float* volume) - { - // Decrease volume - if (ctx->controls["menu_modifier"]->is_active()) - *volume -= 0.01f; - else - *volume -= 0.1f; - - // Limit volume - if (*volume < 0.0f) - *volume = 0.0f; - - update_value_text_content(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - }; - - auto toggle_mono_audio_callback = [ctx]() - { - ctx->mono_audio = !ctx->mono_audio; - - update_value_text_content(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - }; - - auto toggle_captions_callback = [ctx]() - { - ctx->captions = !ctx->captions; - - update_value_text_content(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - }; - - auto increase_captions_size_callback = [ctx]() - { - // Increase size - if (ctx->controls["menu_modifier"]->is_active()) - ctx->captions_size += 0.01f; - else - ctx->captions_size += 0.1f; - - // Limit size - if (ctx->captions_size > 2.0f) - ctx->captions_size = 2.0f; - - update_value_text_content(ctx); - game::menu::align_text(ctx); - game::menu::update_text_tweens(ctx); - }; - - auto decrease_captions_size_callback = [ctx]() - { - // Decrease size - if (ctx->controls["menu_modifier"]->is_active()) - ctx->captions_size -= 0.01f; - else - ctx->captions_size -= 0.1f; - - // Limit size - if (ctx->captions_size < 0.1f) - ctx->captions_size = 0.1f; - - update_value_text_content(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]() - { - 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); - } - ); - }; - - // Build list of menu select callbacks - ctx->menu_select_callbacks.push_back(std::bind(increase_volume_callback, &ctx->master_volume)); - ctx->menu_select_callbacks.push_back(std::bind(increase_volume_callback, &ctx->ambience_volume)); - ctx->menu_select_callbacks.push_back(std::bind(increase_volume_callback, &ctx->effects_volume)); - ctx->menu_select_callbacks.push_back(toggle_mono_audio_callback); - ctx->menu_select_callbacks.push_back(toggle_captions_callback); - ctx->menu_select_callbacks.push_back(increase_captions_size_callback); - ctx->menu_select_callbacks.push_back(select_back_callback); - - // Build list of menu left callbacks - ctx->menu_left_callbacks.push_back(std::bind(decrease_volume_callback, &ctx->master_volume)); - ctx->menu_left_callbacks.push_back(std::bind(decrease_volume_callback, &ctx->ambience_volume)); - ctx->menu_left_callbacks.push_back(std::bind(decrease_volume_callback, &ctx->effects_volume)); - ctx->menu_left_callbacks.push_back(toggle_mono_audio_callback); - ctx->menu_left_callbacks.push_back(toggle_captions_callback); - ctx->menu_left_callbacks.push_back(decrease_captions_size_callback); - ctx->menu_left_callbacks.push_back(nullptr); - - // Build list of menu right callbacks - ctx->menu_right_callbacks.push_back(std::bind(increase_volume_callback, &ctx->master_volume)); - ctx->menu_right_callbacks.push_back(std::bind(increase_volume_callback, &ctx->ambience_volume)); - ctx->menu_right_callbacks.push_back(std::bind(increase_volume_callback, &ctx->effects_volume)); - ctx->menu_right_callbacks.push_back(toggle_mono_audio_callback); - ctx->menu_right_callbacks.push_back(toggle_captions_callback); - ctx->menu_right_callbacks.push_back(increase_captions_size_callback); - ctx->menu_right_callbacks.push_back(nullptr); - - // Set menu back callback - ctx->menu_back_callback = select_back_callback; - - // Schedule menu control setup - timeline* timeline = ctx->timeline; - float t = timeline->get_position(); - timeline->add_sequence({{t + game::menu::input_delay, 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); - - // Update config - (*ctx->config)["master_volume"] = ctx->master_volume; - (*ctx->config)["ambience_volume"] = ctx->ambience_volume; - (*ctx->config)["effects_volume"] = ctx->effects_volume; - (*ctx->config)["mono_audio"] = ctx->mono_audio; - (*ctx->config)["captions"] = ctx->captions; - (*ctx->config)["captions_size"] = ctx->captions_size; -} - -} // namespace sound_menu -} // namespace state -} // namespace game diff --git a/src/game/states/splash.cpp b/src/game/states/splash.cpp deleted file mode 100644 index afe599a..0000000 --- a/src/game/states/splash.cpp +++ /dev/null @@ -1,160 +0,0 @@ -/* - * 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/states/splash.hpp" -#include "game/states/main-menu.hpp" -#include "animation/screen-transition.hpp" -#include "animation/animation.hpp" -#include "animation/animator.hpp" -#include "animation/ease.hpp" -#include "application.hpp" -#include "render/passes/clear-pass.hpp" - -namespace game { -namespace state { -namespace splash { - -void enter(game::context* ctx) -{ - ctx->ui_clear_pass->set_cleared_buffers(true, true, false); - - // Load animation timing configuration - double splash_fade_in_duration = 0.0; - double splash_duration = 0.0; - double splash_fade_out_duration = 0.0; - if (ctx->config->contains("splash_fade_in_duration")) - splash_fade_in_duration = (*ctx->config)["splash_fade_in_duration"].get(); - if (ctx->config->contains("splash_duration")) - splash_duration = (*ctx->config)["splash_duration"].get(); - if (ctx->config->contains("splash_fade_out_duration")) - splash_fade_out_duration = (*ctx->config)["splash_fade_out_duration"].get(); - - // Build splash fade in animation - ctx->splash_fade_in_animation = new animation(); - animation_channel* splash_fade_in_opacity_channel = ctx->splash_fade_in_animation->add_channel(0); - ctx->splash_fade_in_animation->set_interpolator(ease::out_cubic); - splash_fade_in_opacity_channel->insert_keyframe({0.0, 0.0f}); - splash_fade_in_opacity_channel->insert_keyframe({splash_fade_in_duration, 1.0f}); - splash_fade_in_opacity_channel->insert_keyframe({splash_fade_in_duration + splash_duration, 1.0f}); - - // Build splash fade out animation - ctx->splash_fade_out_animation = new animation(); - animation_channel* splash_fade_out_opacity_channel = ctx->splash_fade_out_animation->add_channel(0); - ctx->splash_fade_out_animation->set_interpolator(ease::out_cubic); - splash_fade_out_opacity_channel->insert_keyframe({0.0, 1.0f}); - splash_fade_out_opacity_channel->insert_keyframe({splash_fade_out_duration, 0.0f}); - - // Setup animation frame callbacks - auto set_splash_opacity = [ctx](int channel, const float& opacity) - { - static_cast*>(ctx->splash_billboard_material->get_property("tint"))->set_value(float4{1, 1, 1, opacity}); - }; - ctx->splash_fade_in_animation->set_frame_callback(set_splash_opacity); - ctx->splash_fade_out_animation->set_frame_callback(set_splash_opacity); - - // Reset splash color when animation starts - ctx->splash_fade_in_animation->set_start_callback - ( - [ctx]() - { - static_cast*>(ctx->splash_billboard_material->get_property("tint"))->set_value(float4{1, 1, 1, 0}); - ctx->splash_billboard_material->update_tweens(); - } - ); - - // Trigger splash fade out animation when splash fade in animation ends - ctx->splash_fade_in_animation->set_end_callback - ( - [ctx]() - { - ctx->splash_fade_out_animation->play(); - } - ); - - // Trigger a state change when the splash fade out animation ends - ctx->splash_fade_out_animation->set_end_callback - ( - [ctx]() - { - application::state next_state; - next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, true); - next_state.exit = std::bind(game::state::main_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - ); - - // Add splash fade animations to animator - ctx->animator->add_animation(ctx->splash_fade_in_animation); - ctx->animator->add_animation(ctx->splash_fade_out_animation); - - // Start splash fade in animation - ctx->splash_fade_in_animation->play(); - - // Set up splash 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) - { - // Black out screen - ctx->rasterizer->set_clear_color(0.0f, 0.0f, 0.0f, 1.0f); - ctx->rasterizer->clear_framebuffer(true, false, false); - ctx->app->swap_buffers(); - - // Change state - application::state next_state; - next_state.name = "main_menu"; - next_state.enter = std::bind(game::state::main_menu::enter, ctx, true); - next_state.exit = std::bind(game::state::main_menu::exit, ctx); - ctx->app->queue_state(next_state); - } - } - ); - ctx->input_listener->set_enabled(true); - - // Add splash billboard to UI scene - ctx->ui_scene->add_object(ctx->splash_billboard); -} - -void exit(game::context* ctx) -{ - // Remove splash billboard from UI scene - ctx->ui_scene->remove_object(ctx->splash_billboard); - - // Disable splash skipper - ctx->input_listener->set_enabled(false); - ctx->input_listener->set_callback(nullptr); - - // Destruct splash fade animations - ctx->animator->remove_animation(ctx->splash_fade_in_animation); - ctx->animator->remove_animation(ctx->splash_fade_out_animation); - delete ctx->splash_fade_in_animation; - delete ctx->splash_fade_out_animation; - ctx->splash_fade_in_animation = nullptr; - ctx->splash_fade_out_animation = nullptr; - - ctx->ui_clear_pass->set_cleared_buffers(false, true, false); -} - -} // namespace splash -} // namespace state -} // namespace game diff --git a/src/game/tools.cpp b/src/game/tools.cpp index 67d2b6e..ae92534 100644 --- a/src/game/tools.cpp +++ b/src/game/tools.cpp @@ -25,82 +25,82 @@ #include "entity/components/celestial-body.hpp" #include "utility/timestamp.hpp" #include "render/material.hpp" +#include "game/graphics.hpp" namespace game { -entity::id build_camera_tool(game::context* ctx) +entity::id build_camera_tool(game::context& ctx) { // Create camera tool entity - entity::id tool_eid = ctx->entity_registry->create(); + entity::id tool_eid = ctx.entity_registry->create(); // Create tool component entity::component::tool tool; // Setup tool activated callback - tool.activated = [ctx]() + tool.activated = [&ctx]() { - if (!ctx->camera_flash_animation->is_stopped()) + if (!ctx.camera_flash_animation->is_stopped()) return; - std::string path = ctx->screenshots_path + "antkeeper-" + timestamp() + ".png"; - ctx->app->save_frame(path); + game::graphics::save_screenshot(ctx); - render::material_property* tint = static_cast*>(ctx->camera_flash_billboard->get_material()->get_property("tint")); + render::material_property* tint = static_cast*>(ctx.camera_flash_billboard->get_material()->get_property("tint")); tint->set_value({1.0f, 1.0f, 1.0f, 1.0f}); - ctx->camera_flash_billboard->get_material()->update_tweens(); - ctx->ui_scene->add_object(ctx->camera_flash_billboard); + ctx.camera_flash_billboard->get_material()->update_tweens(); + ctx.ui_scene->add_object(ctx.camera_flash_billboard); - ctx->camera_flash_animation->set_end_callback + ctx.camera_flash_animation->set_end_callback ( - [ctx]() + [&ctx]() { - ctx->ui_scene->remove_object(ctx->camera_flash_billboard); + ctx.ui_scene->remove_object(ctx.camera_flash_billboard); } ); - ctx->camera_flash_animation->set_frame_callback + ctx.camera_flash_animation->set_frame_callback ( - [ctx, tint](int channel, const float& opacity) + [&ctx, tint](int channel, const float& opacity) { tint->set_value({1.0f, 1.0f, 1.0f, opacity}); } ); - ctx->animator->remove_animation(ctx->camera_flash_animation); - ctx->animator->add_animation(ctx->camera_flash_animation); - ctx->camera_flash_animation->rewind(); - ctx->camera_flash_animation->play(); + ctx.animator->remove_animation(ctx.camera_flash_animation); + ctx.animator->add_animation(ctx.camera_flash_animation); + ctx.camera_flash_animation->rewind(); + ctx.camera_flash_animation->play(); }; // Add tool component to camera tool entity - ctx->entity_registry->assign(tool_eid, tool); + ctx.entity_registry->assign(tool_eid, tool); return tool_eid; } -entity::id build_time_tool(game::context* ctx) +entity::id build_time_tool(game::context& ctx) { // Create time tool entity - entity::id tool_eid = ctx->entity_registry->create(); + entity::id tool_eid = ctx.entity_registry->create(); // Create tool component entity::component::tool tool; // Setup tool active calback - tool.active = [ctx]() + tool.active = [&ctx]() { - auto [mouse_x, mouse_y] = ctx->app->get_mouse()->get_current_position(); - auto [window_w, window_h] = ctx->app->get_viewport_dimensions(); + auto [mouse_x, mouse_y] = ctx.app->get_mouse()->get_current_position(); + auto [window_w, window_h] = ctx.app->get_viewport_dimensions(); - entity::id planet_eid = ctx->entities["planet"]; - entity::component::celestial_body body = ctx->entity_registry->get(planet_eid); + entity::id planet_eid = ctx.entities["planet"]; + entity::component::celestial_body body = ctx.entity_registry->get(planet_eid); body.axial_rotation = math::radians(360.0f) * ((float)mouse_x / (float)window_w); - ctx->entity_registry->replace(planet_eid, body); + ctx.entity_registry->replace(planet_eid, body); }; // Add tool component to time tool entity - ctx->entity_registry->assign(tool_eid, tool); + ctx.entity_registry->assign(tool_eid, tool); return tool_eid; } diff --git a/src/game/tools.hpp b/src/game/tools.hpp index 335a393..006e918 100644 --- a/src/game/tools.hpp +++ b/src/game/tools.hpp @@ -25,8 +25,8 @@ namespace game { -entity::id build_camera_tool(game::context* ctx); -entity::id build_time_tool(game::context* ctx); +entity::id build_camera_tool(game::context& ctx); +entity::id build_time_tool(game::context& ctx); } // namespace game diff --git a/src/game/world.cpp b/src/game/world.cpp index 3c7bb7a..69969c2 100644 --- a/src/game/world.cpp +++ b/src/game/world.cpp @@ -53,10 +53,10 @@ namespace game { namespace world { -void create_stars(game::context* ctx) +void create_stars(game::context& ctx) { // Load star catalog - string_table* star_catalog = ctx->resource_manager->load("stars.csv"); + string_table* star_catalog = ctx.resource_manager->load("stars.csv"); // Allocate star catalog vertex data std::size_t star_count = 0; @@ -130,7 +130,7 @@ void create_stars(game::context* ctx) } // Unload star catalog - ctx->resource_manager->unload("stars.csv"); + ctx.resource_manager->unload("stars.csv"); // Allocate stars model render::model* stars_model = new render::model(); @@ -170,7 +170,7 @@ void create_stars(game::context* ctx) vao->bind(render::vertex_attribute::color, color_attribute); // Load star material - render::material* star_material = ctx->resource_manager->load("fixed-star.mtl"); + render::material* star_material = ctx.resource_manager->load("fixed-star.mtl"); // Create model group render::model_group* stars_model_group = stars_model->add_group("stars"); @@ -180,15 +180,15 @@ void create_stars(game::context* ctx) stars_model_group->set_index_count(star_count); // Pass stars model to sky pass - ctx->surface_sky_pass->set_stars_model(stars_model); + ctx.surface_sky_pass->set_stars_model(stars_model); } -void create_sun(game::context* ctx) +void create_sun(game::context& ctx) { // Create sun entity - entity::archetype* sun_archetype = ctx->resource_manager->load("sun.ent"); - entity::id sun_eid = sun_archetype->create(*ctx->entity_registry); - ctx->entities["sun"] = sun_eid; + entity::archetype* sun_archetype = ctx.resource_manager->load("sun.ent"); + entity::id sun_eid = sun_archetype->create(*ctx.entity_registry); + ctx.entities["sun"] = sun_eid; // Create direct sun light scene object scene::directional_light* sun_direct = new scene::directional_light(); @@ -200,20 +200,20 @@ void create_sun(game::context* ctx) sun_ambient->update_tweens(); // Add sun light scene objects to surface scene - ctx->surface_scene->add_object(sun_direct); - ctx->surface_scene->add_object(sun_ambient); + ctx.surface_scene->add_object(sun_direct); + ctx.surface_scene->add_object(sun_ambient); // Pass direct sun light scene object to shadow map pass and astronomy system - ctx->surface_shadow_map_pass->set_light(sun_direct); - ctx->astronomy_system->set_sun_light(sun_direct); + ctx.surface_shadow_map_pass->set_light(sun_direct); + ctx.astronomy_system->set_sun_light(sun_direct); } -void create_planet(game::context* ctx) +void create_planet(game::context& ctx) { // Create planet entity - entity::archetype* planet_archetype = ctx->resource_manager->load("planet.ent"); - entity::id planet_eid = planet_archetype->create(*ctx->entity_registry); - ctx->entities["planet"] = planet_eid; + entity::archetype* planet_archetype = ctx.resource_manager->load("planet.ent"); + entity::id planet_eid = planet_archetype->create(*ctx.entity_registry); + ctx.entities["planet"] = planet_eid; // Assign planetary terrain component entity::component::terrain terrain; @@ -224,38 +224,38 @@ void create_planet(game::context* ctx) }; terrain.max_lod = 0; terrain.patch_material = nullptr; - ctx->entity_registry->assign(planet_eid, terrain); + ctx.entity_registry->assign(planet_eid, terrain); // Pass planet to astronomy system as reference body - ctx->astronomy_system->set_reference_body(planet_eid); + ctx.astronomy_system->set_reference_body(planet_eid); // Load sky model - ctx->surface_sky_pass->set_sky_model(ctx->resource_manager->load("sky-dome.mdl")); + ctx.surface_sky_pass->set_sky_model(ctx.resource_manager->load("sky-dome.mdl")); } -void create_moon(game::context* ctx) +void create_moon(game::context& ctx) { // Create lunar entity - entity::id moon_eid = ctx->entity_registry->create(); - ctx->entities["moon"] = moon_eid; + entity::id moon_eid = ctx.entity_registry->create(); + ctx.entities["moon"] = moon_eid; // Pass moon model to sky pass - ctx->surface_sky_pass->set_moon_model(ctx->resource_manager->load("moon.mdl")); + ctx.surface_sky_pass->set_moon_model(ctx.resource_manager->load("moon.mdl")); } -void set_time(game::context* ctx, double t) +void set_time(game::context& ctx, double t) { - ctx->astronomy_system->set_universal_time(t); - ctx->orbit_system->set_universal_time(t); + ctx.astronomy_system->set_universal_time(t); + ctx.orbit_system->set_universal_time(t); } -void set_time_scale(game::context* ctx, double scale) +void set_time_scale(game::context& ctx, double scale) { static constexpr double seconds_per_day = 24.0 * 60.0 * 60.0; scale /= seconds_per_day; - ctx->orbit_system->set_time_scale(scale); - ctx->astronomy_system->set_time_scale(scale); + ctx.orbit_system->set_time_scale(scale); + ctx.astronomy_system->set_time_scale(scale); } } // namespace world diff --git a/src/game/world.hpp b/src/game/world.hpp index 8b409b5..1ecf4f3 100644 --- a/src/game/world.hpp +++ b/src/game/world.hpp @@ -28,22 +28,22 @@ namespace game { namespace world { /// Creates the fixed stars. -void create_stars(game::context* ctx); +void create_stars(game::context& ctx); /// Creates the sun. -void create_sun(game::context* ctx); +void create_sun(game::context& ctx); /// Creates the planet. -void create_planet(game::context* ctx); +void create_planet(game::context& ctx); /// Creates the moon. -void create_moon(game::context* ctx); +void create_moon(game::context& ctx); /// Sets the current time. -void set_time(game::context* ctx, double t); +void set_time(game::context& ctx, double t); /// Sets rate at which time passes. -void set_time_scale(game::context* ctx, double scale); +void set_time_scale(game::context& ctx, double scale); } // namespace menu } // namespace game diff --git a/src/main.cpp b/src/main.cpp index 5eacd0e..828184f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,9 +17,8 @@ * along with Antkeeper source code. If not, see . */ -#include "application.hpp" -#include "game/states/boot.hpp" -#include +#include "game/context.hpp" +#include "game/state/boot.hpp" #include #include @@ -27,22 +26,17 @@ int main(int argc, char* argv[]) { try { - // Construct application - application app; + // Allocate game context + game::context ctx; - // Setup initial application state - application::state boot_state; - boot_state.name = "boot"; - boot_state.enter = std::bind(game::state::boot::enter, &app, argc, argv); - boot_state.exit = std::bind(game::state::boot::exit, &app); - - // Execute application then return the exit status code - return app.execute(boot_state); + // Enter boot state + ctx.state_machine.emplace(new game::state::boot(ctx, argc, argv)); } catch (const std::exception& e) { std::cerr << "Unhandled exception: \"" << e.what() << "\"" << std::endl; + return EXIT_FAILURE; } - return EXIT_FAILURE; + return EXIT_SUCCESS; } diff --git a/src/state-machine.hpp b/src/state-machine.hpp new file mode 100644 index 0000000..9b7cc53 --- /dev/null +++ b/src/state-machine.hpp @@ -0,0 +1,39 @@ +/* + * 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_HSM_STATE_MACHINE_HPP +#define ANTKEEPER_HSM_STATE_MACHINE_HPP + +#include +#include + +/// Hierarchical State Machine (HSM) +namespace hsm { + +/** + * Stack-based hierarchical state machine. + * + * @tparam T State type. + */ +template +using state_machine = std::stack>; + +} // namespace hsm + +#endif // ANTKEEPER_HSM_STATE_MACHINE_HPP diff --git a/src/utility/paths.cpp b/src/utility/paths.cpp index 0ea96c6..8360384 100644 --- a/src/utility/paths.cpp +++ b/src/utility/paths.cpp @@ -48,9 +48,9 @@ } #endif -std::string get_executable_path() +std::filesystem::path get_executable_path() { - std::string executable_path; + std::filesystem::path executable_path; #if defined(_WIN32) // Get executable path on Windows @@ -73,38 +73,29 @@ std::string get_executable_path() return executable_path; } -std::string get_data_path(const std::string& application_name) +std::filesystem::path get_data_path(const std::string& application_name) { - std::string data_path; - #if defined(_WIN32) - std::string executable_path = get_executable_path(); - std::size_t delimeter = executable_path.find_last_of("\\/") + 1; - data_path = executable_path.substr(0, delimeter); + return get_executable_path().parent_path(); #else - std::string executable_path = get_executable_path(); - std::size_t delimeter = executable_path.find_last_of("\\/") + 1; - data_path = executable_path.substr(0, delimeter) + std::string("../share/") + application_name + std::string("/"); + return get_executable_path().parent_path().parent_path() / "share" / application_name; #endif - - return data_path; } -std::string get_config_path(const std::string& application_name) +std::filesystem::path get_config_path(const std::string& application_name) { - std::string config_path; + std::filesystem::path config_path; #if defined(_WIN32) std::wstring wpath(MAX_PATH, L'\0'); if (SHGetSpecialFolderPathW(nullptr, &wpath[0], CSIDL_LOCAL_APPDATA, FALSE)) { wpath.erase(std::find(wpath.begin(), wpath.end(), L'\0'), wpath.end()); - config_path = narrow(wpath); - config_path += std::string("\\") + application_name + std::string("\\"); + config_path = std::filesystem::path(narrow(wpath)) / application_name; } #else // Determine home path - std::string home_path = std::string(getpwuid(getuid())->pw_dir); + std::filesystem::path home_path = getpwuid(getuid())->pw_dir; // Determine config path char* xdgConfigHome = std::getenv("XDG_CONFIG_HOME"); @@ -112,36 +103,13 @@ std::string get_config_path(const std::string& application_name) { // Default to $HOME/.config/ as per: // https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html#variables - config_path = home_path + std::string("/.config/") + application_name + std::string("/"); + config_path = home_path / ".config/" / application_name; } else { - config_path = xdgConfigHome + std::string("/") + application_name + std::string("/"); + config_path = std::filesystem::path(xdgConfigHome) / application_name; } #endif return config_path; } - -bool path_exists(const std::string& path) -{ - #if defined(_WIN32) - std::wstring wpath = widen(path); - DWORD attributes = GetFileAttributesW(wpath.c_str()); - return (attributes != INVALID_FILE_ATTRIBUTES); - #else - struct stat info; - return (stat(path.c_str(), &info) == 0); - #endif -} - -bool create_directory(const std::string& path) -{ - #if defined(_WIN32) - std::wstring wpath = widen(path); - return (CreateDirectoryW(wpath.c_str(), nullptr) != 0); - #else - return (mkdir(path.c_str(), 0777) == 0); - #endif -} - diff --git a/src/utility/paths.hpp b/src/utility/paths.hpp index afc9e35..b0678dc 100644 --- a/src/utility/paths.hpp +++ b/src/utility/paths.hpp @@ -20,6 +20,7 @@ #ifndef ANTKEEPER_PATHS_HPP #define ANTKEEPER_PATHS_HPP +#include #include /** @@ -27,7 +28,7 @@ * * @return Path to the application's executable. */ -std::string get_executable_path(); +std::filesystem::path get_executable_path(); /** * Returns the absolute path to the directory containing application data. @@ -38,7 +39,7 @@ std::string get_executable_path(); * @param application_name Name of the application. * @return Path to the application's data directory. */ -std::string get_data_path(const std::string& application_name); +std::filesystem::path get_data_path(const std::string& application_name); /** * Returns the absolute path to the directory containing user-specific application data. @@ -49,13 +50,7 @@ std::string get_data_path(const std::string& application_name); * @param application_name Name of the application. * @return Path to the application's config directory. */ -std::string get_config_path(const std::string& application_name); - -/// Checks if a file or directory exists -bool path_exists(const std::string& path); - -/// Creates a directory -bool create_directory(const std::string& path); +std::filesystem::path get_config_path(const std::string& application_name); #endif // ANTKEEPER_PATHS_HPP