/* +------------------------------------------------------+ ____/ \____ /| - Open source game framework licensed to freely use, | \ / / | copy, modify and sell without restriction | +--\ ^__^ /--+ | | | ~/ \~ | | - created for | | ~~~~~~~~~~~~ | +------------------------------------------------------+ | SPACE ~~~~~ | / | ~~~~~~~ BOX |/ +-------------*/ #include "Configuration.hpp" Configuration::Configuration() { set_defaults(); } void Configuration::set_defaults() { config["keys"] = { {"record", {"CTRL", "SHIFT", "i"}}, {"save current stash", {"CTRL", "SHIFT", "v"}}, {"screenshot", {"CTRL", "i"}}, {"action", "space"}, {"up", "up"}, {"right", "right"}, {"down", "down"}, {"left", "left"}, {"fullscreen", {"ALT", "enter"}}, {"reset", {"CTRL", "r"}} }; config["input"] = { {"suppress any key on mods", true}, {"system any key ignore commands", {"fullscreen", "screenshot", "record", "quit"}}, {"any key ignore commands", nlohmann::json::array()}, {"default-unsuppress-delay", 0.7}, {"ignore repeat keypress", true} }; config["display"] = { {"dimensions", {960, 540}}, {"max framerate", -1}, {"vsync", false}, {"sdl delay", 6}, {"title", "[SPACEBOX]"}, {"debug", false}, {"show cursor", false}, {"render-test-spacing", 2}, {"render driver", "opengl"}, {"fluid resize", false}, {"default font path", "BPmono.ttf"}, {"default font size", 16}, {"use play button", false}, {"fullscreen", false}, {"fullscreen enabled", true} }; config["audio"] = { {"default-sfx-root", "resource/sfx"}, {"default-bgm-root", "resource/bgm"}, {"frequency", 48000}, {"chunk size", 2048} }; config["gl"] = { {"depth size", 16}, {"red size", 8}, {"green size", 8}, {"blue size", 8}, {"share with current context", true}, {"double buffer", true}, {"major version", 3}, {"minor version", 2} }, config["recording"] = { {"enabled", false}, {"video frame length", 1.0f / 60.0f}, {"screenshot prefix", "screenshot-"}, {"screenshot extension", ".png"}, {"screenshot zfill", 5}, {"screenshot directory", "."}, {"gif frame length", 0.1f}, {"video directory", "."}, {"write mp4", false}, {"max stash length", 5.0f}, {"max in game stashes", 3}, {"max video stashes", 40}, {"max video memory", 1000}, {"mp4 pixel format", "yuv444p"} }; config["animation"] = { {"all frames frameset name", "all"} }; config["log"] = { {"enabled", false}, {"debug-to-stdout", true}, {"verbose to stdout", false}, {"debug-to-file", false}, {"output-directory", "."}, {"info-file-name", "space_box_log.txt"}, {"debug-file-name", "space_box_debug_log.txt"}, {"short-name", "spacebox"} }; config["configuration"] = { {"auto refresh", false}, {"auto refresh interval", 5.0}, {"android config path", "config_android.json"}, {"wasm config path", "config_wasm.json"} }; } nlohmann::json& Configuration::operator[](const std::string& key) { return config[key]; } const nlohmann::json& Configuration::operator[](const std::string& key) const { return config[key]; } const nlohmann::json& Configuration::operator()() const { return config; } void Configuration::merge(const nlohmann::json& incoming) { if (!incoming.empty()) { /* loop over first level key/value pairs */ for (auto& item: incoming.items()) { /* if the value is an object (dict), merge it into the config, overwriting keys already in the config */ if (item.value().is_object()) { config[item.key()].update(item.value()); } /* otherwise just assign config key to this value */ else { config[item.key()] = item.value(); } } } else { sb::Log::log("Attempted to merge empty JSON into configuration", sb::Log::WARN); } } void Configuration::merge(const fs::path& path) { merge(json_from_file(path)); } void Configuration::merge(const std::string& path) { merge(fs::path(path)); } void Configuration::merge(const char* path) { merge(fs::path(path)); } nlohmann::json Configuration::json_from_file(const fs::path& path) { nlohmann::json json; #ifndef __ANDROID__ /* Can't check for file existence in an Android APK */ if (fs::exists(path)) { #endif /* Load JSON to a string and check for validity. */ std::string contents = sb::file_to_string(path); if (nlohmann::json::accept(contents)) { json = nlohmann::json::parse(contents); } else { std::ostringstream message; message << "Invalid JSON at " << path; sb::Log::log(message, sb::Log::WARN); } #ifndef __ANDROID__ } else { std::ostringstream message; message << "File not found: " << path; sb::Log::log(message, sb::Log::WARN); } #endif return json; } nlohmann::json Configuration::json_from_file(const std::string& path) { return json_from_file(fs::path(path)); } nlohmann::json Configuration::json_from_file(const char* path) { return json_from_file(fs::path(path)); } void Configuration::enable_auto_refresh(const fs::path& file_to_refresh) { #ifndef __ANDROID__ /* Warn user if the file does not exist */ if (!fs::exists(file_to_refresh)) { std::ostringstream message; message << "File to auto-refresh does not exist: " << file_to_refresh; sb::Log::log(message, sb::Log::WARN); } #endif files_to_refresh.push_back(file_to_refresh); auto_refresher.frame_length(config.at("configuration").at("auto refresh interval").get()); auto_refresher.play(); } void Configuration::disable_auto_refresh() { auto_refresher.pause(); } void Configuration::refresh() { #if !defined(__ANDROID__) for (const fs::path& path : files_to_refresh) { if (fs::exists(path) && fs::last_write_time(path) > config_modification_time) { std::ostringstream message; message << "config file modified, reloading " << path; sb::Log::log(message, sb::Log::DEBUG); merge(path); config_modification_time = fs::file_time_type::clock::now(); sb::Delegate::post("reconfig"); } } #else /* Warn user file modification check doesn't work on Android */ sb::Log::log("File modification time can't be checked on Android, so file cannot be reloaded automatically", sb::Log::WARN); #endif } void Configuration::update(float timestamp) { auto_refresher.update(timestamp); } std::ostream& std::operator<<(std::ostream& out, const Configuration& configuration) { out << configuration(); return out; }