make custom settings be based on custom setting types

This commit is contained in:
HJfod 2024-08-21 21:41:44 +03:00
parent a5f56cb7cb
commit 40a28eec7b
12 changed files with 462 additions and 379 deletions

View file

@ -191,11 +191,6 @@ namespace geode {
* @see addCustomSetting * @see addCustomSetting
*/ */
void registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value); void registerCustomSetting(std::string_view const key, std::unique_ptr<SettingValue> value);
/**
* Register a custom setting
*/
Result<> registerCustomSettingV3(std::string_view const key, std::shared_ptr<SettingV3> value);
/** /**
* Register a custom setting's value class. The new SettingValue class * Register a custom setting's value class. The new SettingValue class
* will be created in-place using `std::make_unique`. See * will be created in-place using `std::make_unique`. See
@ -213,6 +208,11 @@ namespace geode {
this->registerCustomSetting(key, std::make_unique<T>(std::string(key), this->getID(), value)); this->registerCustomSetting(key, std::make_unique<T>(std::string(key), this->getID(), value));
} }
/**
* Register a custom setting type
*/
Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator);
/** /**
* Returns a prefixed launch argument name. See `Mod::getLaunchArgument` * Returns a prefixed launch argument name. See `Mod::getLaunchArgument`
* for details about mod-specific launch arguments. * for details about mod-specific launch arguments.

View file

@ -0,0 +1,46 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include "SettingV3.hpp"
namespace geode {
class GEODE_DLL ModSettingsManager final {
private:
class Impl;
std::unique_ptr<Impl> m_impl;
public:
static ModSettingsManager* from(Mod* mod);
ModSettingsManager(ModMetadata const& metadata);
~ModSettingsManager();
ModSettingsManager(ModSettingsManager&&);
ModSettingsManager(ModSettingsManager const&) = delete;
/**
* Load setting values from savedata.
* The format of the savedata should be an object with the keys being
* setting IDs and then the values the values of the saved settings
* @returns Ok if no horrible errors happened. Note that a setting value
* missing is not considered a horrible error, but will instead just log a
* warning into the console!
*/
Result<> load(matjson::Value const& json);
/**
* Save setting values to savedata.
* The format of the savedata will be an object with the keys being
* setting IDs and then the values the values of the saved settings
* @note If saving a setting fails, it will log a warning to the console
*/
void save(matjson::Value& json);
Result<> registerCustomSettingType(std::string_view type, SettingGenerator generator);
// todo in v4: remove this
Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr);
std::shared_ptr<SettingV3> get(std::string_view key);
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
std::optional<Setting> getLegacyDefinition(std::string_view key);
};
}

View file

@ -9,22 +9,22 @@
// this unfortunately has to be included because of C++ templates // this unfortunately has to be included because of C++ templates
#include "../utils/JsonValidation.hpp" #include "../utils/JsonValidation.hpp"
// todo in v4: these can be removed as well as the friend decl in UnresolvedCustomSettingV3 // todo in v4: these can be removed as well as the friend decl in LegacyCustomSettingV3
class ModSettingsManager;
class LegacyCustomSettingToV3Node; class LegacyCustomSettingToV3Node;
namespace geode { namespace geode {
class ModSettingsManager;
class SettingNodeV3; class SettingNodeV3;
class GEODE_DLL SettingV3 : public std::enable_shared_from_this<SettingV3> { class GEODE_DLL SettingV3 : public std::enable_shared_from_this<SettingV3> {
private: private:
class GeodeImpl; class GeodeImpl;
std::shared_ptr<GeodeImpl> m_impl; std::shared_ptr<GeodeImpl> m_impl;
protected: protected:
virtual Result<> onParse( void init(std::string const& key, std::string const& modID);
std::string const& key, std::string const& modID, matjson::Value const& json Result<> parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value);
) = 0; void parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value);
public: public:
SettingV3(); SettingV3();
@ -43,8 +43,23 @@ namespace geode {
* while the mod is still being initialized * while the mod is still being initialized
*/ */
Mod* getMod() const; Mod* getMod() const;
/**
* Get the name of this setting
*/
std::string getName() const;
/**
* Get the description of this setting
*/
std::optional<std::string> getDescription() const;
/**
* Get the "enable-if" scheme for this setting
*/
std::optional<std::string> getEnableIf() const;
/**
* Whether this setting requires a restart on change
*/
bool requiresRestart() const;
Result<> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
virtual bool load(matjson::Value const& json) = 0; virtual bool load(matjson::Value const& json) = 0;
virtual bool save(matjson::Value& json) const = 0; virtual bool save(matjson::Value& json) const = 0;
virtual SettingNodeV3* createNode(float width) = 0; virtual SettingNodeV3* createNode(float width) = 0;
@ -59,26 +74,22 @@ namespace geode {
virtual std::optional<Setting> convertToLegacy() const; virtual std::optional<Setting> convertToLegacy() const;
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]] [[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
virtual std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const; virtual std::optional<std::shared_ptr<SettingValue>> convertToLegacyValue() const;
static Result<std::shared_ptr<SettingV3>> parseBuiltin(
std::string const& key, std::string const& modID, matjson::Value const& json
);
}; };
using SettingGenerator = std::function<Result<std::shared_ptr<SettingV3>>(
std::string const& key,
std::string const& modID,
matjson::Value const& json
)>;
namespace detail { namespace detail {
class GEODE_DLL GeodeSettingBaseV3 : public SettingV3 { template <class T, class V = T>
private: class GeodeSettingBaseValueV3 : public SettingV3 {
class Impl;
std::shared_ptr<Impl> m_impl;
Result<> parseSharedBase(JsonExpectedValue& json);
protected: protected:
Result<> isValidShared() const; virtual T& getValueMut() const = 0;
template <class T> template <class T>
Result<> parseShared(JsonExpectedValue& json, T& defaultValue) { void parseDefaultValue(JsonExpectedValue& json, T& defaultValue) {
GEODE_UNWRAP(this->parseSharedBase(json));
auto value = json.needs("default"); auto value = json.needs("default");
// Check if this is a platform-specific default value // Check if this is a platform-specific default value
if (value.isObject() && value.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) { if (value.isObject() && value.has(GEODE_PLATFORM_SHORT_IDENTIFIER_NOARCH)) {
@ -87,22 +98,8 @@ namespace geode {
else { else {
value.into(defaultValue); value.into(defaultValue);
} }
return Ok();
} }
public:
GeodeSettingBaseV3();
std::string getName() const;
std::optional<std::string> getDescription() const;
std::optional<std::string> getEnableIf() const;
};
template <class T, class V = T>
class GeodeSettingBaseValueV3 : public GeodeSettingBaseV3 {
protected:
virtual T& getValueMut() const = 0;
public: public:
using ValueType = T; using ValueType = T;
@ -130,15 +127,13 @@ namespace geode {
class Impl; class Impl;
std::shared_ptr<Impl> m_impl; std::shared_ptr<Impl> m_impl;
protected:
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
friend class SettingV3; friend class SettingV3;
public: public:
TitleSettingV3(PrivateMarker); TitleSettingV3(PrivateMarker);
static Result<std::shared_ptr<TitleSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::string getTitle() const; std::string getTitle() const;
@ -150,23 +145,25 @@ namespace geode {
void reset() override; void reset() override;
}; };
class GEODE_DLL UnresolvedCustomSettingV3 final : public SettingV3 { // todo in v4: remove this class completely
class GEODE_DLL LegacyCustomSettingV3 final : public SettingV3 {
private: private:
class Impl; class Impl;
std::shared_ptr<Impl> m_impl; std::shared_ptr<Impl> m_impl;
friend class ::ModSettingsManager; friend class ::geode::ModSettingsManager;
friend class ::LegacyCustomSettingToV3Node; friend class ::LegacyCustomSettingToV3Node;
protected:
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
friend class SettingV3; friend class SettingV3;
public: public:
UnresolvedCustomSettingV3(PrivateMarker); LegacyCustomSettingV3(PrivateMarker);
static Result<std::shared_ptr<LegacyCustomSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::shared_ptr<SettingValue> getValue() const;
void setValue(std::shared_ptr<SettingValue> value);
bool load(matjson::Value const& json) override; bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override; bool save(matjson::Value& json) const override;
@ -186,7 +183,6 @@ namespace geode {
protected: protected:
bool& getValueMut() const override; bool& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -194,6 +190,7 @@ namespace geode {
public: public:
BoolSettingV3(PrivateMarker); BoolSettingV3(PrivateMarker);
static Result<std::shared_ptr<BoolSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
bool getDefaultValue() const override; bool getDefaultValue() const override;
Result<> isValid(bool value) const override; Result<> isValid(bool value) const override;
@ -213,7 +210,6 @@ namespace geode {
protected: protected:
int64_t& getValueMut() const override; int64_t& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -221,6 +217,7 @@ namespace geode {
public: public:
IntSettingV3(PrivateMarker); IntSettingV3(PrivateMarker);
static Result<std::shared_ptr<IntSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
int64_t getDefaultValue() const override; int64_t getDefaultValue() const override;
Result<> isValid(int64_t value) const override; Result<> isValid(int64_t value) const override;
@ -251,7 +248,6 @@ namespace geode {
protected: protected:
double& getValueMut() const override; double& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -259,6 +255,7 @@ namespace geode {
public: public:
FloatSettingV3(PrivateMarker); FloatSettingV3(PrivateMarker);
static Result<std::shared_ptr<FloatSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
double getDefaultValue() const override; double getDefaultValue() const override;
Result<> isValid(double value) const override; Result<> isValid(double value) const override;
@ -289,7 +286,6 @@ namespace geode {
protected: protected:
std::string& getValueMut() const override; std::string& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -297,6 +293,7 @@ namespace geode {
public: public:
StringSettingV3(PrivateMarker); StringSettingV3(PrivateMarker);
static Result<std::shared_ptr<StringSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::string getDefaultValue() const override; std::string getDefaultValue() const override;
Result<> isValid(std::string_view value) const override; Result<> isValid(std::string_view value) const override;
@ -320,7 +317,6 @@ namespace geode {
protected: protected:
std::filesystem::path& getValueMut() const override; std::filesystem::path& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -328,6 +324,7 @@ namespace geode {
public: public:
FileSettingV3(PrivateMarker); FileSettingV3(PrivateMarker);
static Result<std::shared_ptr<FileSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
std::filesystem::path getDefaultValue() const override; std::filesystem::path getDefaultValue() const override;
Result<> isValid(std::filesystem::path const& value) const override; Result<> isValid(std::filesystem::path const& value) const override;
@ -349,7 +346,6 @@ namespace geode {
protected: protected:
cocos2d::ccColor3B& getValueMut() const override; cocos2d::ccColor3B& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -357,6 +353,7 @@ namespace geode {
public: public:
Color3BSettingV3(PrivateMarker); Color3BSettingV3(PrivateMarker);
static Result<std::shared_ptr<Color3BSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
cocos2d::ccColor3B getDefaultValue() const override; cocos2d::ccColor3B getDefaultValue() const override;
Result<> isValid(cocos2d::ccColor3B value) const override; Result<> isValid(cocos2d::ccColor3B value) const override;
@ -376,7 +373,6 @@ namespace geode {
protected: protected:
cocos2d::ccColor4B& getValueMut() const override; cocos2d::ccColor4B& getValueMut() const override;
Result<> onParse(std::string const& key, std::string const& modID, matjson::Value const& json) override;
private: private:
class PrivateMarker {}; class PrivateMarker {};
@ -384,6 +380,7 @@ namespace geode {
public: public:
Color4BSettingV3(PrivateMarker); Color4BSettingV3(PrivateMarker);
static Result<std::shared_ptr<Color4BSettingV3>> parse(std::string const& key, std::string const& modID, matjson::Value const& json);
cocos2d::ccColor4B getDefaultValue() const override; cocos2d::ccColor4B getDefaultValue() const override;
Result<> isValid(cocos2d::ccColor4B value) const override; Result<> isValid(cocos2d::ccColor4B value) const override;

View file

@ -318,12 +318,17 @@ namespace geode {
template <class T> template <class T>
std::optional<T> tryGet() { std::optional<T> tryGet() {
if (this->hasError()) return std::nullopt; if (this->hasError()) return std::nullopt;
try { if constexpr (std::is_same_v<T, matjson::Value>) {
return this->getJSONRef().template as<T>(); return this->getJSONRef();
} }
// matjson can throw variant exceptions too so you need to do this else {
catch(std::exception const& e) { try {
this->setError("invalid json type: {}", e); return this->getJSONRef().template as<T>();
}
// matjson can throw variant exceptions too so you need to do this
catch(std::exception const& e) {
this->setError("invalid json type: {}", e);
}
} }
return std::nullopt; return std::nullopt;
} }

View file

@ -173,8 +173,8 @@ void Mod::registerCustomSetting(std::string_view const key, std::unique_ptr<Sett
log::error("Unable to register custom setting: {}", reg.unwrapErr()); log::error("Unable to register custom setting: {}", reg.unwrapErr());
} }
} }
Result<> Mod::registerCustomSettingV3(std::string_view const key, std::shared_ptr<SettingV3> value) { Result<> Mod::registerCustomSettingType(std::string_view type, SettingGenerator generator) {
return m_impl->m_settings->registerCustomSetting(key, value); return m_impl->m_settings->registerCustomSettingType(type, generator);
} }
std::vector<std::string> Mod::getLaunchArgumentNames() const { std::vector<std::string> Mod::getLaunchArgumentNames() const {

View file

@ -4,7 +4,7 @@
#include "ModPatch.hpp" #include "ModPatch.hpp"
#include <Geode/loader/Loader.hpp> #include <Geode/loader/Loader.hpp>
#include <string_view> #include <string_view>
#include "ModSettingsManager.hpp" #include <Geode/loader/ModSettingsManager.hpp>
namespace geode { namespace geode {
class Mod::Impl { class Mod::Impl {

View file

@ -1,65 +1,161 @@
#include "ModSettingsManager.hpp" #include <Geode/loader/ModSettingsManager.hpp>
#include "SettingV3Impl.hpp"
#include <Geode/utils/JsonValidation.hpp> #include <Geode/utils/JsonValidation.hpp>
#include "ModImpl.hpp"
using namespace geode::prelude;
// All setting type generators are put in a shared pool for two reasons:
// #1 no need to duplicate the built-in settings between all mods
// #2 easier lookup of custom settings if a mod uses another mod's custom setting type
class SharedSettingTypesPool final {
private:
std::unordered_map<std::string, SettingGenerator> m_types;
SharedSettingTypesPool() : m_types({
// todo in v4: remove this
{ "custom", &LegacyCustomSettingV3::parse },
{ "title", &TitleSettingV3::parse },
{ "bool", &BoolSettingV3::parse },
{ "int", &IntSettingV3::parse },
{ "float", &FloatSettingV3::parse },
{ "string", &StringSettingV3::parse },
{ "file", &FileSettingV3::parse },
{ "path", &FileSettingV3::parse },
{ "rgb", &Color3BSettingV3::parse },
{ "color", &Color3BSettingV3::parse },
{ "rgba", &Color4BSettingV3::parse },
}) {}
public:
static SharedSettingTypesPool& get() {
static auto inst = SharedSettingTypesPool();
return inst;
}
Result<> add(std::string_view modID, std::string_view type, SettingGenerator generator) {
// Limit type to just [a-z0-9\-]+
if (type.empty() || !std::all_of(type.begin(), type.end(), +[](char c) {
return
('a' <= c && c <= 'z') ||
('0' <= c && c <= '9') ||
(c == '-');
})) {
return Err("Custom setting types must match the regex [a-z0-9\\-]+");
}
auto full = fmt::format("{}/{}", modID, type);
if (m_types.contains(full)) {
return Err("Type \"{}\" has already been registered for mod {}", type, modID);
}
m_types.emplace(full, generator);
return Ok();
}
std::optional<SettingGenerator> find(std::string_view modID, std::string_view fullType) {
auto full = std::string(
fullType.starts_with("custom:") ?
fullType.substr(fullType.find(':') + 1) :
fullType
);
if (!full.find('/')) {
full = fmt::format("{}/{}", modID, full);
}
if (m_types.contains(full)) {
return m_types.at(full);
}
return std::nullopt;
}
};
class ModSettingsManager::Impl final { class ModSettingsManager::Impl final {
public: public:
struct SettingInfo final { struct SettingInfo final {
std::shared_ptr<SettingV3> v3; std::string type;
matjson::Value json;
std::shared_ptr<SettingV3> v3 = nullptr;
// todo: remove in v4 // todo: remove in v4
std::shared_ptr<SettingValue> legacy = nullptr; std::shared_ptr<SettingValue> legacy = nullptr;
}; };
std::string modID; std::string modID;
std::unordered_map<std::string, SettingInfo> list; std::unordered_map<std::string, SettingInfo> settings;
void createSettings() {
for (auto& [key, setting] : settings) {
if (setting.v3) {
continue;
}
auto gen = SharedSettingTypesPool::get().find(modID, setting.type);
// The type was not found, meaning it probably hasn't been registered yet
if (!gen) {
continue;
}
if (auto v3 = (*gen)(key, modID, setting.json)) {
setting.v3 = *v3;
}
else {
log::error(
"Unable to parse setting '{}' for mod {}: {}",
key, modID, v3.unwrapErr()
);
}
}
}
}; };
ModSettingsManager* ModSettingsManager::from(Mod* mod) {
return ModImpl::getImpl(mod)->m_settings.get();
}
ModSettingsManager::ModSettingsManager(ModMetadata const& metadata) ModSettingsManager::ModSettingsManager(ModMetadata const& metadata)
: m_impl(std::make_unique<Impl>()) : m_impl(std::make_unique<Impl>())
{ {
m_impl->modID = metadata.getID(); m_impl->modID = metadata.getID();
for (auto const& [key, json] : metadata.getSettingsV3()) { for (auto const& [key, json] : metadata.getSettingsV3()) {
if (auto v3 = SettingV3::parseBuiltin(key, m_impl->modID, json)) { auto setting = Impl::SettingInfo();
auto setting = Impl::SettingInfo(); setting.json = json;
setting.v3.swap(*v3); auto root = checkJson(json, "setting");
m_impl->list.emplace(key, setting); root.needs("type").into(setting.type);
if (root) {
if (setting.type == "custom") {
log::warn(
"Setting \"{}\" in mod {} has the old \"custom\" type - "
"this type has been deprecated and will be removed in Geode v4.0.0. "
"Use the new \"custom:type-name-here\" syntax for defining custom "
"setting types - see more in INSERT TUTORIAL HERE",
key, m_impl->modID
);
}
m_impl->settings.emplace(key, setting);
} }
else { else {
log::error("Unable to parse setting '{}' for mod {}: {}", key, m_impl->modID, v3.unwrapErr()); log::error("Setting '{}' in mod {} is missing type", key, m_impl->modID);
} }
} }
m_impl->createSettings();
} }
ModSettingsManager::~ModSettingsManager() {} ModSettingsManager::~ModSettingsManager() {}
ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default; ModSettingsManager::ModSettingsManager(ModSettingsManager&&) = default;
Result<> ModSettingsManager::registerCustomSetting(std::string_view key, std::shared_ptr<SettingV3> ptr) { Result<> ModSettingsManager::registerCustomSettingType(std::string_view type, SettingGenerator generator) {
if (!ptr) { GEODE_UNWRAP(SharedSettingTypesPool::get().add(m_impl->modID, type, generator));
return Err("Custom settings must not be null!"); m_impl->createSettings();
}
auto id = std::string(key);
if (!m_impl->list.count(id)) {
return Err("No such setting '{}' in mod {}", id, m_impl->modID);
}
auto& sett = m_impl->list.at(id);
sett.v3.swap(ptr);
return Ok(); return Ok();
} }
Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr) { Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr) {
auto id = std::string(key); auto id = std::string(key);
if (!m_impl->list.count(id)) { if (!m_impl->settings.count(id)) {
return Err("No such setting '{}' in mod {}", id, m_impl->modID); return Err("No such setting '{}' in mod {}", id, m_impl->modID);
} }
auto& sett = m_impl->list.at(id); auto& sett = m_impl->settings.at(id);
if (auto custom = typeinfo_pointer_cast<UnresolvedCustomSettingV3>(sett.v3)) { if (auto custom = typeinfo_pointer_cast<LegacyCustomSettingV3>(sett.v3)) {
if (!custom->m_impl->legacyValue) { if (!custom->getValue()) {
custom->m_impl->legacyValue = std::move(ptr); custom->setValue(std::move(ptr));
} }
else { else {
return Err("Setting '{}' in mod {} has already been registed", id, m_impl->modID); return Err("Setting '{}' in mod {} has already been registed", id, m_impl->modID);
} }
} }
else { else {
return Err("Setting '{}' in mod {} is not a custom setting", id, m_impl->modID); return Err("Setting '{}' in mod {} is not a legacy custom setting", id, m_impl->modID);
} }
return Ok(); return Ok();
} }
@ -67,9 +163,9 @@ Result<> ModSettingsManager::registerLegacyCustomSetting(std::string_view key, s
Result<> ModSettingsManager::load(matjson::Value const& json) { Result<> ModSettingsManager::load(matjson::Value const& json) {
auto root = checkJson(json, "Settings"); auto root = checkJson(json, "Settings");
for (auto const& [key, value] : root.properties()) { for (auto const& [key, value] : root.properties()) {
if (m_impl->list.contains(key)) { if (m_impl->settings.contains(key)) {
try { try {
if (!m_impl->list.at(key).v3->load(value.json())) { if (!m_impl->settings.at(key).v3->load(value.json())) {
log::error("Unable to load setting '{}' for mod {}", key, m_impl->modID); log::error("Unable to load setting '{}' for mod {}", key, m_impl->modID);
} }
} }
@ -81,7 +177,7 @@ Result<> ModSettingsManager::load(matjson::Value const& json) {
return Ok(); return Ok();
} }
void ModSettingsManager::save(matjson::Value& json) { void ModSettingsManager::save(matjson::Value& json) {
for (auto& [key, sett] : m_impl->list) { for (auto& [key, sett] : m_impl->settings) {
// Store the value in an intermediary so if `save` fails the existing // Store the value in an intermediary so if `save` fails the existing
// value loaded from disk isn't overwritten // value loaded from disk isn't overwritten
matjson::Value value; matjson::Value value;
@ -101,18 +197,22 @@ void ModSettingsManager::save(matjson::Value& json) {
std::shared_ptr<SettingV3> ModSettingsManager::get(std::string_view key) { std::shared_ptr<SettingV3> ModSettingsManager::get(std::string_view key) {
auto id = std::string(key); auto id = std::string(key);
return m_impl->list.count(id) ? m_impl->list.at(id).v3 : nullptr; return m_impl->settings.count(id) ? m_impl->settings.at(id).v3 : nullptr;
} }
std::shared_ptr<SettingValue> ModSettingsManager::getLegacy(std::string_view key) { std::shared_ptr<SettingValue> ModSettingsManager::getLegacy(std::string_view key) {
auto id = std::string(key); auto id = std::string(key);
if (!m_impl->list.count(id)) { if (!m_impl->settings.count(id)) {
return nullptr; return nullptr;
} }
auto& info = m_impl->list.at(id); auto& info = m_impl->settings.at(id);
// If this setting has alreay been given a legacy interface, give that // If this setting has alreay been given a legacy interface, give that
if (info.legacy) { if (info.legacy) {
return info.legacy; return info.legacy;
} }
// Uninitialized settings are null
if (!info.v3) {
return nullptr;
}
// Generate new legacy interface // Generate new legacy interface
if (auto legacy = info.v3->convertToLegacyValue()) { if (auto legacy = info.v3->convertToLegacyValue()) {
info.legacy.swap(*legacy); info.legacy.swap(*legacy);

View file

@ -1,44 +0,0 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <Geode/loader/SettingV3.hpp>
using namespace geode::prelude;
class ModSettingsManager final {
private:
class Impl;
std::unique_ptr<Impl> m_impl;
public:
ModSettingsManager(ModMetadata const& metadata);
~ModSettingsManager();
ModSettingsManager(ModSettingsManager&&);
ModSettingsManager(ModSettingsManager const&) = delete;
/**
* Load setting values from savedata.
* The format of the savedata should be an object with the keys being
* setting IDs and then the values the values of the saved settings
* @returns Ok if no horrible errors happened. Note that a setting value
* missing is not considered a horrible error, but will instead just log a
* warning into the console!
*/
Result<> load(matjson::Value const& json);
/**
* Save setting values to savedata.
* The format of the savedata will be an object with the keys being
* setting IDs and then the values the values of the saved settings
* @note If saving a setting fails, it will log a warning to the console
*/
void save(matjson::Value& json);
Result<> registerCustomSetting(std::string_view key, std::shared_ptr<SettingV3> ptr);
// remove in v4
Result<> registerLegacyCustomSetting(std::string_view key, std::unique_ptr<SettingValue>&& ptr);
std::shared_ptr<SettingV3> get(std::string_view key);
std::shared_ptr<SettingValue> getLegacy(std::string_view key);
std::optional<Setting> getLegacyDefinition(std::string_view key);
};

View file

@ -1,5 +1,4 @@
#include "SettingNodeV3.hpp" #include "SettingNodeV3.hpp"
#include "SettingV3Impl.hpp"
#include <Geode/loader/SettingNode.hpp> #include <Geode/loader/SettingNode.hpp>
class SettingNodeSizeChangeEventV3::Impl final { class SettingNodeSizeChangeEventV3::Impl final {
@ -367,7 +366,7 @@ std::shared_ptr<Color4BSettingV3> Color4BSettingNodeV3::getSetting() const {
// UnresolvedCustomSettingNodeV3 // UnresolvedCustomSettingNodeV3
bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width) { bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<LegacyCustomSettingV3> setting, float width) {
if (!SettingNodeV3::init(setting, width)) if (!SettingNodeV3::init(setting, width))
return false; return false;
@ -385,7 +384,7 @@ bool UnresolvedCustomSettingNodeV3::init(std::shared_ptr<UnresolvedCustomSetting
void UnresolvedCustomSettingNodeV3::onCommit() {} void UnresolvedCustomSettingNodeV3::onCommit() {}
UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width) { UnresolvedCustomSettingNodeV3* UnresolvedCustomSettingNodeV3::create(std::shared_ptr<LegacyCustomSettingV3> setting, float width) {
auto ret = new UnresolvedCustomSettingNodeV3(); auto ret = new UnresolvedCustomSettingNodeV3();
if (ret && ret->init(setting, width)) { if (ret && ret->init(setting, width)) {
ret->autorelease(); ret->autorelease();
@ -403,17 +402,17 @@ bool UnresolvedCustomSettingNodeV3::hasNonDefaultValue() const {
} }
void UnresolvedCustomSettingNodeV3::resetToDefault() {} void UnresolvedCustomSettingNodeV3::resetToDefault() {}
std::shared_ptr<UnresolvedCustomSettingV3> UnresolvedCustomSettingNodeV3::getSetting() const { std::shared_ptr<LegacyCustomSettingV3> UnresolvedCustomSettingNodeV3::getSetting() const {
return std::static_pointer_cast<UnresolvedCustomSettingV3>(SettingNodeV3::getSetting()); return std::static_pointer_cast<LegacyCustomSettingV3>(SettingNodeV3::getSetting());
} }
// LegacyCustomSettingToV3Node // LegacyCustomSettingToV3Node
bool LegacyCustomSettingToV3Node::init(std::shared_ptr<UnresolvedCustomSettingV3> original, float width) { bool LegacyCustomSettingToV3Node::init(std::shared_ptr<LegacyCustomSettingV3> original, float width) {
if (!SettingNodeV3::init(original, width)) if (!SettingNodeV3::init(original, width))
return false; return false;
m_original = original->m_impl->legacyValue->createNode(width); m_original = original->getValue()->createNode(width);
this->setContentSize({ width, m_original->getContentHeight() }); this->setContentSize({ width, m_original->getContentHeight() });
this->addChildAtPosition(m_original, Anchor::Center); this->addChildAtPosition(m_original, Anchor::Center);
@ -424,7 +423,7 @@ void LegacyCustomSettingToV3Node::onCommit() {
m_original->commit(); m_original->commit();
} }
LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr<UnresolvedCustomSettingV3> original, float width) { LegacyCustomSettingToV3Node* LegacyCustomSettingToV3Node::create(std::shared_ptr<LegacyCustomSettingV3> original, float width) {
auto ret = new LegacyCustomSettingToV3Node(); auto ret = new LegacyCustomSettingToV3Node();
if (ret && ret->init(original, width)) { if (ret && ret->init(original, width)) {
ret->autorelease(); ret->autorelease();

View file

@ -141,18 +141,18 @@ public:
class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 { class UnresolvedCustomSettingNodeV3 : public SettingNodeV3 {
protected: protected:
bool init(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width); bool init(std::shared_ptr<LegacyCustomSettingV3> setting, float width);
void onCommit() override; void onCommit() override;
public: public:
static UnresolvedCustomSettingNodeV3* create(std::shared_ptr<UnresolvedCustomSettingV3> setting, float width); static UnresolvedCustomSettingNodeV3* create(std::shared_ptr<LegacyCustomSettingV3> setting, float width);
bool hasUncommittedChanges() const override; bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override; bool hasNonDefaultValue() const override;
void resetToDefault() override; void resetToDefault() override;
std::shared_ptr<UnresolvedCustomSettingV3> getSetting() const; std::shared_ptr<LegacyCustomSettingV3> getSetting() const;
}; };
// If these classes do get exposed in headers, this SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!! // If these classes do get exposed in headers, this SHOULD NOT BE EXPOSED!!!!!! DO NOT DO THAT!!!!
@ -161,12 +161,12 @@ class LegacyCustomSettingToV3Node : public SettingNodeV3 {
protected: protected:
SettingNode* m_original; SettingNode* m_original;
bool init(std::shared_ptr<UnresolvedCustomSettingV3> original, float width); bool init(std::shared_ptr<LegacyCustomSettingV3> original, float width);
void onCommit() override; void onCommit() override;
public: public:
static LegacyCustomSettingToV3Node* create(std::shared_ptr<UnresolvedCustomSettingV3> original, float width); static LegacyCustomSettingToV3Node* create(std::shared_ptr<LegacyCustomSettingV3> original, float width);
bool hasUncommittedChanges() const override; bool hasUncommittedChanges() const override;
bool hasNonDefaultValue() const override; bool hasNonDefaultValue() const override;

View file

@ -1,7 +1,6 @@
#include <Geode/loader/SettingV3.hpp> #include <Geode/loader/SettingV3.hpp>
#include <Geode/utils/JsonValidation.hpp> #include <Geode/utils/JsonValidation.hpp>
#include <regex> #include <regex>
#include "SettingV3Impl.hpp"
#include "SettingNodeV3.hpp" #include "SettingNodeV3.hpp"
using namespace geode::prelude; using namespace geode::prelude;
@ -10,11 +9,32 @@ class SettingV3::GeodeImpl {
public: public:
std::string modID; std::string modID;
std::string key; std::string key;
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
bool requiresRestart = false;
}; };
SettingV3::SettingV3() : m_impl(std::make_shared<GeodeImpl>()) {}
SettingV3::~SettingV3() = default; SettingV3::~SettingV3() = default;
SettingV3::SettingV3() : m_impl(std::make_shared<GeodeImpl>()) {} Result<> SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, matjson::Value const& value) {
auto json = checkJson(value, "SettingV3");
this->parseSharedProperties(key, modID, json);
return json.ok();
}
void SettingV3::parseSharedProperties(std::string const& key, std::string const& modID, JsonExpectedValue& value) {
this->init(key, modID);
value.needs("type");
value.has("name").into(m_impl->name);
value.has("description").into(m_impl->description);
value.has("enable-if").into(m_impl->enableIf);
value.has("requires-restart").into(m_impl->requiresRestart);
}
void SettingV3::init(std::string const& key, std::string const& modID) {
m_impl->key = key;
m_impl->modID = modID;
}
std::string SettingV3::getKey() const { std::string SettingV3::getKey() const {
return m_impl->key; return m_impl->key;
@ -22,37 +42,22 @@ std::string SettingV3::getKey() const {
std::string SettingV3::getModID() const { std::string SettingV3::getModID() const {
return m_impl->modID; return m_impl->modID;
} }
std::string SettingV3::getName() const {
return m_impl->name.value_or(m_impl->key);
}
std::optional<std::string> SettingV3::getDescription() const {
return m_impl->description;
}
std::optional<std::string> SettingV3::getEnableIf() const {
return m_impl->enableIf;
}
bool SettingV3::requiresRestart() const {
return m_impl->requiresRestart;
}
Mod* SettingV3::getMod() const { Mod* SettingV3::getMod() const {
return Loader::get()->getInstalledMod(m_impl->modID); return Loader::get()->getInstalledMod(m_impl->modID);
} }
Result<> SettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
m_impl->key = key;
m_impl->modID = modID;
return this->onParse(key, modID, json);
}
Result<std::shared_ptr<SettingV3>> SettingV3::parseBuiltin(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "SettingV3");
std::string type;
root.needs("type").into(type);
std::shared_ptr<SettingV3> ret;
switch (hash(type)) {
case hash("bool"): ret = std::make_shared<BoolSettingV3>(BoolSettingV3::PrivateMarker()); break;
case hash("int"): ret = std::make_shared<IntSettingV3>(IntSettingV3::PrivateMarker()); break;
case hash("float"): ret = std::make_shared<FloatSettingV3>(FloatSettingV3::PrivateMarker()); break;
case hash("string"): ret = std::make_shared<StringSettingV3>(StringSettingV3::PrivateMarker()); break;
case hash("rgb"): case hash("color"): ret = std::make_shared<Color3BSettingV3>(Color3BSettingV3::PrivateMarker()); break;
case hash("rgba"): ret = std::make_shared<Color4BSettingV3>(Color4BSettingV3::PrivateMarker()); break;
case hash("path"): case hash("file"): ret = std::make_shared<FileSettingV3>(FileSettingV3::PrivateMarker()); break;
case hash("title"): ret = std::make_shared<TitleSettingV3>(TitleSettingV3::PrivateMarker()); break;
default:
case hash("custom"): ret = std::make_shared<UnresolvedCustomSettingV3>(UnresolvedCustomSettingV3::PrivateMarker()); break;
}
GEODE_UNWRAP(ret->parse(key, modID, json));
return root.ok(std::move(ret));
}
std::optional<Setting> SettingV3::convertToLegacy() const { std::optional<Setting> SettingV3::convertToLegacy() const {
return std::nullopt; return std::nullopt;
} }
@ -60,41 +65,6 @@ std::optional<std::shared_ptr<SettingValue>> SettingV3::convertToLegacyValue() c
return std::nullopt; return std::nullopt;
} }
class geode::detail::GeodeSettingBaseV3::Impl final {
public:
std::optional<std::string> name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
};
geode::detail::GeodeSettingBaseV3::GeodeSettingBaseV3() : m_impl(std::make_shared<Impl>()) {}
std::string geode::detail::GeodeSettingBaseV3::getName() const {
return m_impl->name.value_or(this->getKey());
}
std::optional<std::string> geode::detail::GeodeSettingBaseV3::getDescription() const {
return m_impl->description;
}
std::optional<std::string> geode::detail::GeodeSettingBaseV3::getEnableIf() const {
return m_impl->enableIf;
}
Result<> geode::detail::GeodeSettingBaseV3::parseSharedBase(JsonExpectedValue& json) {
// Mark keys that have been checked before-hand
json.needs("type");
json.has("platforms");
json.has("name").into(m_impl->name);
json.has("description").into(m_impl->description);
json.has("enable-if").into(m_impl->enableIf);
return Ok();
}
Result<> geode::detail::GeodeSettingBaseV3::isValidShared() const {
// In the future if something like `enable-if` preventing
// programmatic modification of settings it should be added here
return Ok();
}
class TitleSettingV3::Impl final { class TitleSettingV3::Impl final {
public: public:
std::string title; std::string title;
@ -102,16 +72,19 @@ public:
TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} TitleSettingV3::TitleSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<TitleSettingV3>> TitleSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<TitleSettingV3>(PrivateMarker());
auto root = checkJson(json, "TitleSettingV3");
ret->init(key, modID);
root.needs("title").into(ret->m_impl->title);
root.checkUnknownKeys();
return root.ok(ret);
}
std::string TitleSettingV3::getTitle() const { std::string TitleSettingV3::getTitle() const {
return m_impl->title; return m_impl->title;
} }
Result<> TitleSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "TitleSettingV3");
root.needs("title").into(m_impl->title);
root.checkUnknownKeys();
return root.ok();
}
bool TitleSettingV3::load(matjson::Value const& json) { bool TitleSettingV3::load(matjson::Value const& json) {
return true; return true;
} }
@ -128,46 +101,56 @@ bool TitleSettingV3::isDefaultValue() const {
} }
void TitleSettingV3::reset() {} void TitleSettingV3::reset() {}
// todo in v4: move the UnresolvedCustomSettingV3::Impl definition from SettingV3Impl.hpp to here class LegacyCustomSettingV3::Impl final {
// on this line in particular public:
// right here matjson::Value json;
// replace this comment with it std::shared_ptr<SettingValue> legacyValue = nullptr;
// put it riiiiiight here };
UnresolvedCustomSettingV3::UnresolvedCustomSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} LegacyCustomSettingV3::LegacyCustomSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<> UnresolvedCustomSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) { Result<std::shared_ptr<LegacyCustomSettingV3>> LegacyCustomSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
m_impl->json = json; auto ret = std::make_shared<LegacyCustomSettingV3>(PrivateMarker());
return Ok(); ret->init(key, modID);
ret->m_impl->json = json;
return Ok(ret);
} }
bool UnresolvedCustomSettingV3::load(matjson::Value const& json) {
std::shared_ptr<SettingValue> LegacyCustomSettingV3::getValue() const {
return m_impl->legacyValue;
}
void LegacyCustomSettingV3::setValue(std::shared_ptr<SettingValue> value) {
m_impl->legacyValue = value;
}
bool LegacyCustomSettingV3::load(matjson::Value const& json) {
return true; return true;
} }
bool UnresolvedCustomSettingV3::save(matjson::Value& json) const { bool LegacyCustomSettingV3::save(matjson::Value& json) const {
return true; return true;
} }
SettingNodeV3* UnresolvedCustomSettingV3::createNode(float width) { SettingNodeV3* LegacyCustomSettingV3::createNode(float width) {
if (m_impl->legacyValue) { if (m_impl->legacyValue) {
return LegacyCustomSettingToV3Node::create( return LegacyCustomSettingToV3Node::create(
std::static_pointer_cast<UnresolvedCustomSettingV3>(shared_from_this()), width std::static_pointer_cast<LegacyCustomSettingV3>(shared_from_this()), width
); );
} }
return UnresolvedCustomSettingNodeV3::create( return UnresolvedCustomSettingNodeV3::create(
std::static_pointer_cast<UnresolvedCustomSettingV3>(shared_from_this()), width std::static_pointer_cast<LegacyCustomSettingV3>(shared_from_this()), width
); );
} }
bool UnresolvedCustomSettingV3::isDefaultValue() const { bool LegacyCustomSettingV3::isDefaultValue() const {
return true; return true;
} }
void UnresolvedCustomSettingV3::reset() {} void LegacyCustomSettingV3::reset() {}
std::optional<Setting> UnresolvedCustomSettingV3::convertToLegacy() const { std::optional<Setting> LegacyCustomSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting { return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting {
.json = std::make_shared<ModJson>(m_impl->json) .json = std::make_shared<ModJson>(m_impl->json)
})); }));
} }
std::optional<std::shared_ptr<SettingValue>> UnresolvedCustomSettingV3::convertToLegacyValue() const { std::optional<std::shared_ptr<SettingValue>> LegacyCustomSettingV3::convertToLegacyValue() const {
return m_impl->legacyValue ? std::optional(m_impl->legacyValue) : std::nullopt; return m_impl->legacyValue ? std::optional(m_impl->legacyValue) : std::nullopt;
} }
@ -179,6 +162,18 @@ public:
BoolSettingV3::BoolSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} BoolSettingV3::BoolSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<BoolSettingV3>> BoolSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<BoolSettingV3>(PrivateMarker());
auto root = checkJson(json, "BoolSettingV3");
ret->parseSharedProperties(key, modID, root);
ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok(ret);
}
bool& BoolSettingV3::getValueMut() const { bool& BoolSettingV3::getValueMut() const {
return m_impl->value; return m_impl->value;
} }
@ -186,19 +181,9 @@ bool BoolSettingV3::getDefaultValue() const {
return m_impl->defaultValue; return m_impl->defaultValue;
} }
Result<> BoolSettingV3::isValid(bool value) const { Result<> BoolSettingV3::isValid(bool value) const {
GEODE_UNWRAP(this->isValidShared());
return Ok(); return Ok();
} }
Result<> BoolSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "BoolSettingV3");
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue));
m_impl->value = m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok();
}
bool BoolSettingV3::load(matjson::Value const& json) { bool BoolSettingV3::load(matjson::Value const& json) {
if (json.is_bool()) { if (json.is_bool()) {
m_impl->value = json.as_bool(); m_impl->value = json.as_bool();
@ -244,6 +229,35 @@ public:
} controls; } controls;
}; };
Result<std::shared_ptr<IntSettingV3>> IntSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<IntSettingV3>(PrivateMarker());
auto root = checkJson(json, "IntSettingV3");
ret->parseSharedProperties(key, modID, root);
ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
root.has("min").into(ret->m_impl->minValue);
root.has("max").into(ret->m_impl->maxValue);
if (auto controls = root.has("control")) {
controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize);
if (!controls.has("arrows").template get<bool>()) {
ret->m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
ret->m_impl->controls.bigArrowStepSize = 0;
}
controls.has("slider").into(ret->m_impl->controls.sliderEnabled);
controls.has("slider-step").into(ret->m_impl->controls.sliderSnap);
controls.has("input").into(ret->m_impl->controls.textInputEnabled);
controls.checkUnknownKeys();
}
root.checkUnknownKeys();
return root.ok(ret);
}
IntSettingV3::IntSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} IntSettingV3::IntSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
int64_t& IntSettingV3::getValueMut() const { int64_t& IntSettingV3::getValueMut() const {
@ -253,7 +267,6 @@ int64_t IntSettingV3::getDefaultValue() const {
return m_impl->defaultValue; return m_impl->defaultValue;
} }
Result<> IntSettingV3::isValid(int64_t value) const { Result<> IntSettingV3::isValid(int64_t value) const {
GEODE_UNWRAP(this->isValidShared());
if (m_impl->minValue && value < *m_impl->minValue) { if (m_impl->minValue && value < *m_impl->minValue) {
return Err("value must be at least {}", *m_impl->minValue); return Err("value must be at least {}", *m_impl->minValue);
} }
@ -292,32 +305,6 @@ bool IntSettingV3::isInputEnabled() const {
return m_impl->controls.textInputEnabled; return m_impl->controls.textInputEnabled;
} }
Result<> IntSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "IntSettingV3");
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue));
m_impl->value = m_impl->defaultValue;
root.has("min").into(m_impl->minValue);
root.has("max").into(m_impl->maxValue);
if (auto controls = root.has("control")) {
controls.has("arrow-step").into(m_impl->controls.arrowStepSize);
if (!controls.has("arrows").template get<bool>()) {
m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
m_impl->controls.bigArrowStepSize = 0;
}
controls.has("slider").into(m_impl->controls.sliderEnabled);
controls.has("slider-step").into(m_impl->controls.sliderSnap);
controls.has("input").into(m_impl->controls.textInputEnabled);
controls.checkUnknownKeys();
}
root.checkUnknownKeys();
return root.ok();
}
bool IntSettingV3::load(matjson::Value const& json) { bool IntSettingV3::load(matjson::Value const& json) {
if (json.is_number()) { if (json.is_number()) {
m_impl->value = json.as_int(); m_impl->value = json.as_int();
@ -376,6 +363,35 @@ public:
FloatSettingV3::FloatSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} FloatSettingV3::FloatSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<FloatSettingV3>> FloatSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<FloatSettingV3>(PrivateMarker());
auto root = checkJson(json, "FloatSettingV3");
ret->parseSharedProperties(key, modID, root);
ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
root.has("min").into(ret->m_impl->minValue);
root.has("max").into(ret->m_impl->maxValue);
if (auto controls = root.has("control")) {
controls.has("arrow-step").into(ret->m_impl->controls.arrowStepSize);
if (!controls.has("arrows").template get<bool>()) {
ret->m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(ret->m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
ret->m_impl->controls.bigArrowStepSize = 0;
}
controls.has("slider").into(ret->m_impl->controls.sliderEnabled);
controls.has("slider-step").into(ret->m_impl->controls.sliderSnap);
controls.has("input").into(ret->m_impl->controls.textInputEnabled);
controls.checkUnknownKeys();
}
root.checkUnknownKeys();
return root.ok(ret);
}
double& FloatSettingV3::getValueMut() const { double& FloatSettingV3::getValueMut() const {
return m_impl->value; return m_impl->value;
} }
@ -383,7 +399,6 @@ double FloatSettingV3::getDefaultValue() const {
return m_impl->defaultValue; return m_impl->defaultValue;
} }
Result<> FloatSettingV3::isValid(double value) const { Result<> FloatSettingV3::isValid(double value) const {
GEODE_UNWRAP(this->isValidShared());
if (m_impl->minValue && value < *m_impl->minValue) { if (m_impl->minValue && value < *m_impl->minValue) {
return Err("value must be at least {}", *m_impl->minValue); return Err("value must be at least {}", *m_impl->minValue);
} }
@ -422,32 +437,6 @@ bool FloatSettingV3::isInputEnabled() const {
return m_impl->controls.textInputEnabled; return m_impl->controls.textInputEnabled;
} }
Result<> FloatSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "FloatSettingV3");
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue));
m_impl->value = m_impl->defaultValue;
root.has("min").into(m_impl->minValue);
root.has("max").into(m_impl->maxValue);
if (auto controls = root.has("control")) {
controls.has("arrow-step").into(m_impl->controls.arrowStepSize);
if (!controls.has("arrows").template get<bool>()) {
m_impl->controls.arrowStepSize = 0;
}
controls.has("big-arrow-step").into(m_impl->controls.bigArrowStepSize);
if (!controls.has("big-arrows").template get<bool>()) {
m_impl->controls.bigArrowStepSize = 0;
}
controls.has("slider").into(m_impl->controls.sliderEnabled);
controls.has("slider-step").into(m_impl->controls.sliderSnap);
controls.has("input").into(m_impl->controls.textInputEnabled);
controls.checkUnknownKeys();
}
root.checkUnknownKeys();
return root.ok();
}
bool FloatSettingV3::load(matjson::Value const& json) { bool FloatSettingV3::load(matjson::Value const& json) {
if (json.is_number()) { if (json.is_number()) {
m_impl->value = json.as_double(); m_impl->value = json.as_double();
@ -498,6 +487,22 @@ public:
StringSettingV3::StringSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} StringSettingV3::StringSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<StringSettingV3>> StringSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<StringSettingV3>(PrivateMarker());
auto root = checkJson(json, "StringSettingV3");
ret->parseSharedProperties(key, modID, root);
ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
root.has("match").into(ret->m_impl->match);
root.has("filter").into(ret->m_impl->filter);
root.has("one-of").into(ret->m_impl->oneOf);
root.checkUnknownKeys();
return root.ok(ret);
}
std::string& StringSettingV3::getValueMut() const { std::string& StringSettingV3::getValueMut() const {
return m_impl->value; return m_impl->value;
} }
@ -505,7 +510,6 @@ std::string StringSettingV3::getDefaultValue() const {
return m_impl->defaultValue; return m_impl->defaultValue;
} }
Result<> StringSettingV3::isValid(std::string_view value) const { Result<> StringSettingV3::isValid(std::string_view value) const {
GEODE_UNWRAP(this->isValidShared());
if (m_impl->match) { if (m_impl->match) {
if (!std::regex_match(std::string(value), std::regex(*m_impl->match))) { if (!std::regex_match(std::string(value), std::regex(*m_impl->match))) {
return Err("value must match regex {}", *m_impl->match); return Err("value must match regex {}", *m_impl->match);
@ -529,19 +533,6 @@ std::optional<std::vector<std::string>> StringSettingV3::getEnumOptions() const
return m_impl->oneOf; return m_impl->oneOf;
} }
Result<> StringSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "StringSettingV3");
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue));
m_impl->value = m_impl->defaultValue;
root.has("match").into(m_impl->match);
root.has("filter").into(m_impl->filter);
root.has("one-of").into(m_impl->oneOf);
root.checkUnknownKeys();
return root.ok();
}
bool StringSettingV3::load(matjson::Value const& json) { bool StringSettingV3::load(matjson::Value const& json) {
if (json.is_string()) { if (json.is_string()) {
m_impl->value = json.as_string(); m_impl->value = json.as_string();
@ -582,30 +573,18 @@ public:
FileSettingV3::FileSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} FileSettingV3::FileSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
std::filesystem::path& FileSettingV3::getValueMut() const { Result<std::shared_ptr<FileSettingV3>> FileSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
return m_impl->value; auto ret = std::make_shared<FileSettingV3>(PrivateMarker());
}
std::filesystem::path FileSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
Result<> FileSettingV3::isValid(std::filesystem::path const& value) const {
GEODE_UNWRAP(this->isValidShared());
return Ok();
}
std::optional<std::vector<utils::file::FilePickOptions::Filter>> FileSettingV3::getFilters() const {
return m_impl->filters;
}
Result<> FileSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "FileSettingV3"); auto root = checkJson(json, "FileSettingV3");
ret->parseSharedProperties(key, modID, root);
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue)); ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
// Replace known paths like `{gd-save-dir}/` // Replace known paths like `{gd-save-dir}/`
try { try {
m_impl->defaultValue = fmt::format( ret->m_impl->defaultValue = fmt::format(
fmt::runtime(m_impl->defaultValue.string()), fmt::runtime(ret->m_impl->defaultValue.string()),
fmt::arg("gd-save-dir", dirs::getSaveDir()), fmt::arg("gd-save-dir", dirs::getSaveDir()),
fmt::arg("gd-game-dir", dirs::getGameDir()), fmt::arg("gd-game-dir", dirs::getGameDir()),
fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID), fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID),
@ -616,7 +595,7 @@ Result<> FileSettingV3::onParse(std::string const& key, std::string const& modID
catch(fmt::format_error const&) { catch(fmt::format_error const&) {
return Err("Invalid format string for file setting path"); return Err("Invalid format string for file setting path");
} }
m_impl->value = m_impl->defaultValue; ret->m_impl->value = ret->m_impl->defaultValue;
if (auto controls = root.has("control")) { if (auto controls = root.has("control")) {
auto filters = std::vector<file::FilePickOptions::Filter>(); auto filters = std::vector<file::FilePickOptions::Filter>();
@ -627,13 +606,28 @@ Result<> FileSettingV3::onParse(std::string const& key, std::string const& modID
filters.push_back(filter); filters.push_back(filter);
} }
if (!filters.empty()) { if (!filters.empty()) {
m_impl->filters.emplace(filters); ret->m_impl->filters.emplace(filters);
} }
} }
root.checkUnknownKeys(); root.checkUnknownKeys();
return root.ok(); return root.ok(ret);
} }
std::filesystem::path& FileSettingV3::getValueMut() const {
return m_impl->value;
}
std::filesystem::path FileSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
Result<> FileSettingV3::isValid(std::filesystem::path const& value) const {
return Ok();
}
std::optional<std::vector<utils::file::FilePickOptions::Filter>> FileSettingV3::getFilters() const {
return m_impl->filters;
}
bool FileSettingV3::load(matjson::Value const& json) { bool FileSettingV3::load(matjson::Value const& json) {
if (json.is_string()) { if (json.is_string()) {
m_impl->value = json.as_string(); m_impl->value = json.as_string();
@ -671,6 +665,18 @@ public:
Color3BSettingV3::Color3BSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} Color3BSettingV3::Color3BSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<Color3BSettingV3>> Color3BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<Color3BSettingV3>(PrivateMarker());
auto root = checkJson(json, "Color3BSettingV3");
ret->parseSharedProperties(key, modID, root);
ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok(ret);
}
ccColor3B& Color3BSettingV3::getValueMut() const { ccColor3B& Color3BSettingV3::getValueMut() const {
return m_impl->value; return m_impl->value;
} }
@ -678,19 +684,9 @@ ccColor3B Color3BSettingV3::getDefaultValue() const {
return m_impl->defaultValue; return m_impl->defaultValue;
} }
Result<> Color3BSettingV3::isValid(ccColor3B value) const { Result<> Color3BSettingV3::isValid(ccColor3B value) const {
GEODE_UNWRAP(this->isValidShared());
return Ok(); return Ok();
} }
Result<> Color3BSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "Color3BSettingV3");
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue));
m_impl->value = m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok();
}
bool Color3BSettingV3::load(matjson::Value const& json) { bool Color3BSettingV3::load(matjson::Value const& json) {
if (json.template is<ccColor3B>()) { if (json.template is<ccColor3B>()) {
m_impl->value = json.template as<ccColor3B>(); m_impl->value = json.template as<ccColor3B>();
@ -727,6 +723,18 @@ public:
Color4BSettingV3::Color4BSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {} Color4BSettingV3::Color4BSettingV3(PrivateMarker) : m_impl(std::make_shared<Impl>()) {}
Result<std::shared_ptr<Color4BSettingV3>> Color4BSettingV3::parse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto ret = std::make_shared<Color4BSettingV3>(PrivateMarker());
auto root = checkJson(json, "Color4BSettingV3");
ret->parseSharedProperties(key, modID, root);
ret->parseDefaultValue(root, ret->m_impl->defaultValue);
ret->m_impl->value = ret->m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok(ret);
}
ccColor4B& Color4BSettingV3::getValueMut() const { ccColor4B& Color4BSettingV3::getValueMut() const {
return m_impl->value; return m_impl->value;
} }
@ -734,19 +742,9 @@ ccColor4B Color4BSettingV3::getDefaultValue() const {
return m_impl->defaultValue; return m_impl->defaultValue;
} }
Result<> Color4BSettingV3::isValid(ccColor4B value) const { Result<> Color4BSettingV3::isValid(ccColor4B value) const {
GEODE_UNWRAP(this->isValidShared());
return Ok(); return Ok();
} }
Result<> Color4BSettingV3::onParse(std::string const& key, std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "Color4BSettingV3");
GEODE_UNWRAP(this->parseShared(root, m_impl->defaultValue));
m_impl->value = m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok();
}
bool Color4BSettingV3::load(matjson::Value const& json) { bool Color4BSettingV3::load(matjson::Value const& json) {
if (json.template is<ccColor4B>()) { if (json.template is<ccColor4B>()) {
m_impl->value = json.template as<ccColor4B>(); m_impl->value = json.template as<ccColor4B>();

View file

@ -1,18 +0,0 @@
#pragma once
#include <Geode/loader/SettingV3.hpp>
using namespace geode::prelude;
// todo in v4: this header can be fully removed and the impl moved back into SettingV3.cpp
// for now it has to be exposed for ModSettingsManager legacy compatibility
class UnresolvedCustomSettingV3::Impl final {
public:
matjson::Value json;
// todo: remove in v4
// this is for compatability with legacy custom settings
// in v3 settings custom settings just replace the definition fully like a normal person
std::shared_ptr<SettingValue> legacyValue = nullptr;
};