new settings stuff that i need to commit because main broke

This commit is contained in:
HJfod 2024-08-13 13:34:33 +03:00
parent 677bd762de
commit 89d1a5140f
11 changed files with 1623 additions and 247 deletions

View file

@ -184,7 +184,12 @@ namespace geode {
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[nodiscard]] std::vector<std::pair<std::string, Setting>> getSettings() const;
[[nodiscard, deprecated("Use getSettingsV3")]] std::vector<std::pair<std::string, Setting>> getSettings() const;
/**
* Mod settings
* @note Not a map because insertion order must be preserved
*/
[[nodiscard]] std::vector<std::pair<std::string, matjson::Value>> getSettingsV3() const;
/**
* Get the tags for this mod
*/
@ -232,7 +237,9 @@ namespace geode {
void setDependencies(std::vector<Dependency> const& value);
void setIncompatibilities(std::vector<Incompatibility> const& value);
void setSpritesheets(std::vector<std::string> const& value);
[[deprecated("This function does NOTHING")]]
void setSettings(std::vector<std::pair<std::string, Setting>> const& value);
void setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value);
void setTags(std::unordered_set<std::string> const& value);
void setNeedsEarlyLoad(bool const& value);
void setIsAPI(bool const& value);

View file

@ -0,0 +1,325 @@
#pragma once
#include "../DefaultInclude.hpp"
#include <optional>
#include <cocos2d.h>
// todo: remove this header in 4.0.0
#include "Setting.hpp"
namespace geode {
class SettingNodeV3;
class JsonExpectedValue;
class GEODE_DLL SettingV3 {
private:
class GeodeImpl;
std::shared_ptr<GeodeImpl> m_impl;
public:
SettingV3(std::string const& key, std::string const& modID);
virtual ~SettingV3();
/**
* Get the key of this setting
*/
std::string getKey() const;
/**
* Get the mod ID this setting is for
*/
std::string getModID() const;
/**
* Get the mod this setting is for. Note that this may return null
* while the mod is still being initialized
*/
Mod* getMod() const;
virtual Result<> parse(std::string const& modID, matjson::Value const& json) = 0;
virtual bool load(matjson::Value const& json) = 0;
virtual bool save(matjson::Value& json) const = 0;
virtual SettingNodeV3* createNode(float width) = 0;
virtual bool isDefaultValue() const = 0;
/**
* Reset this setting's value back to its original value
*/
virtual void reset() = 0;
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
virtual std::optional<Setting> convertToLegacy() const;
[[deprecated("This function will be removed alongside legacy settings in 4.0.0!")]]
virtual std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const;
static Result<std::shared_ptr<SettingV3>> parseBuiltin(std::string const& modID, matjson::Value const& json);
};
namespace detail {
class GEODE_DLL GeodeSettingBaseV3 : public SettingV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
protected:
Result<> parseShared(JsonExpectedValue& json);
public:
std::string getName() const;
std::optional<std::string> getDescription() const;
std::optional<std::string> getEnableIf() const;
};
}
class GEODE_DLL TitleSettingV3 final : public SettingV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
std::string getTitle() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
};
class GEODE_DLL UnresolvedCustomSettingV3 final : public SettingV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL BoolSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
bool getValue() const;
void setValue(bool value);
Result<> isValid(bool value) const;
bool getDefaultValue() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL IntSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
int64_t getValue() const;
void setValue(int64_t value);
Result<> isValid(int64_t value) const;
int64_t getDefaultValue() const;
std::optional<int64_t> getMinValue() const;
std::optional<int64_t> getMaxValue() const;
bool isArrowsEnabled() const;
bool isBigArrowsEnabled() const;
size_t getArrowStepSize() const;
size_t getBigArrowStepSize() const;
bool isSliderEnabled() const;
std::optional<int64_t> getSliderSnap() const;
bool isInputEnabled() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FloatSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
double getValue() const;
void setValue(double value);
Result<> isValid(double value) const;
double getDefaultValue() const;
std::optional<double> getMinValue() const;
std::optional<double> getMaxValue() const;
bool isArrowsEnabled() const;
bool isBigArrowsEnabled() const;
size_t getArrowStepSize() const;
size_t getBigArrowStepSize() const;
bool isSliderEnabled() const;
std::optional<double> getSliderSnap() const;
bool isInputEnabled() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL StringSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
std::string getValue() const;
void setValue(std::string_view value);
Result<> isValid(std::string_view value) const;
std::string getDefaultValue() const;
std::optional<std::string> getRegexValidator() const;
std::optional<std::string> getAllowedCharacters() const;
std::optional<std::vector<std::string>> getEnumOptions() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL FileSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
std::filesystem::path getValue() const;
void setValue(std::filesystem::path const& value);
Result<> isValid(std::filesystem::path value) const;
std::filesystem::path getDefaultValue() const;
std::optional<std::vector<utils::file::FilePickOptions::Filter>> getFilters() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color3BSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
cocos2d::ccColor3B getValue() const;
void setValue(cocos2d::ccColor3B value);
Result<> isValid(cocos2d::ccColor3B value) const;
cocos2d::ccColor3B getDefaultValue() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL Color4BSettingV3 final : public detail::GeodeSettingBaseV3 {
private:
class Impl;
std::shared_ptr<Impl> m_impl;
public:
cocos2d::ccColor4B getValue() const;
void setValue(cocos2d::ccColor4B value);
Result<> isValid(cocos2d::ccColor4B value) const;
cocos2d::ccColor4B getDefaultValue() const;
Result<> parse(std::string const& modID, matjson::Value const& json) override;
bool load(matjson::Value const& json) override;
bool save(matjson::Value& json) const override;
SettingNodeV3* createNode(float width) override;
bool isDefaultValue() const override;
void reset() override;
std::optional<Setting> convertToLegacy() const override;
std::optional<std::unique_ptr<SettingValue>> convertToLegacyValue() const override;
};
class GEODE_DLL SettingNodeV3 : public cocos2d::CCNode {
protected:
bool init();
/**
* Mark this setting as changed. This updates the UI for committing
* the value
*/
void markChanged();
/**
* When the setting value is committed (aka can't be undone), this
* function will be called. This should take care of actually saving
* the value in some sort of global manager
*/
virtual void onCommit() = 0;
void dispatchChanged();
void dispatchCommitted();
public:
virtual void commit() = 0;
virtual bool hasUncommittedChanges() = 0;
virtual bool hasNonDefaultValue() = 0;
virtual void resetToDefault() = 0;
};
}

View file

@ -77,7 +77,9 @@ namespace geode {
struct JsonMaybeObject;
struct JsonMaybeValue;
struct GEODE_DLL JsonMaybeSomething {
struct GEODE_DLL
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
JsonMaybeSomething {
protected:
JsonChecker& m_checker;
matjson::Value& m_json;
@ -102,7 +104,9 @@ namespace geode {
operator bool() const;
};
struct GEODE_DLL JsonMaybeValue : public JsonMaybeSomething {
struct GEODE_DLL
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
JsonMaybeValue : public JsonMaybeSomething {
bool m_inferType = true;
JsonMaybeValue(
@ -254,7 +258,9 @@ namespace geode {
Iterator<std::pair<std::string, JsonMaybeValue>> items();
};
struct GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
struct
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
GEODE_DLL JsonMaybeObject : JsonMaybeSomething {
std::set<std::string> m_knownKeys;
JsonMaybeObject(
@ -276,7 +282,9 @@ namespace geode {
void checkUnknownKeys();
};
struct GEODE_DLL JsonChecker {
struct
[[deprecated("Use JsonExpectedValue via the checkJson function instead")]]
GEODE_DLL JsonChecker {
std::variant<std::monostate, std::string> m_result;
matjson::Value& m_json;
@ -289,4 +297,179 @@ namespace geode {
JsonMaybeValue root(std::string const& hierarchy);
};
class GEODE_DLL JsonExpectedValue final {
protected:
class Impl;
std::unique_ptr<Impl> m_impl;
JsonExpectedValue();
JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key);
bool hasError() const;
void setError(std::string_view error);
matjson::Value const& getJSONRef() const;
template <class... Args>
void setError(fmt::format_string<Args...> error, Args&&... args) {
this->setError(fmt::format(error, std::forward<Args>(args)...));
}
template <class T>
std::optional<T> tryGet() {
if (this->hasError()) return std::nullopt;
try {
return this->getJSONRef().template as<T>();
}
catch(matjson::JsonException const& e) {
this->setError("invalid json type: {}", e);
}
return std::nullopt;
}
public:
JsonExpectedValue(matjson::Value const& value, std::string_view rootScopeName);
~JsonExpectedValue();
JsonExpectedValue(JsonExpectedValue&&);
JsonExpectedValue& operator=(JsonExpectedValue&&);
JsonExpectedValue(JsonExpectedValue const&) = delete;
JsonExpectedValue& operator=(JsonExpectedValue const&) = delete;
/**
* Get a copy of the underlying raw JSON value
*/
matjson::Value json() const;
/**
* Get the key name of this JSON value. If this is an array index,
* returns the index as a string. If this is the root object,
* returns the root scope name.
*/
std::string key() const;
/**
* Check the type of this JSON value. Does not set an error. If an
* error is already set, always returns false
*/
bool is(matjson::Type type) const;
bool isNull() const;
bool isBool() const;
bool isNumber() const;
bool isString() const;
bool isArray() const;
bool isObject() const;
/**
* Asserts that this JSON value is of the specified type. If it is
* not, an error is set and all subsequent operations are no-ops
* @returns Itself
*/
JsonExpectedValue& assertIs(matjson::Type type);
JsonExpectedValue& assertIsNull();
JsonExpectedValue& assertIsBool();
JsonExpectedValue& assertIsNumber();
JsonExpectedValue& assertIsString();
JsonExpectedValue& assertIsArray();
JsonExpectedValue& assertIsObject();
/**
* Asserts that this JSON value is one of a list of specified types
* @returns Itself
*/
JsonExpectedValue& assertIs(std::initializer_list<matjson::Type> type);
// -- Dealing with values --
template <class T>
T get() {
if (auto v = this->template tryGet<T>()) {
return *std::move(v);
}
return T();
}
template <class T>
JsonExpectedValue& into(T& value) {
if (auto v = this->template tryGet<T>()) {
value = *std::move(v);
}
return *this;
}
template <class T>
JsonExpectedValue& into(std::optional<T>& value) {
if (auto v = this->template tryGet<T>()) {
value.emplace(*std::move(v));
}
return *this;
}
template <class T>
JsonExpectedValue& mustBe(std::string_view name, auto predicate) requires requires {
{ predicate(std::declval<T>()) } -> std::convertible_to<bool>;
} {
if (this->hasError()) return *this;
if (auto v = this->template tryGet<T>()) {
if (!predicate(v)) {
this->setError("json value is not {}", name);
}
}
return *this;
}
// -- Dealing with objects --
/**
* Check if this object has an optional key. Asserts that this JSON
* value is an object. If the key doesn't exist, returns a
* `JsonExpectValue` that does nothing
* @returns The key, which is a no-op value if it didn't exist
*/
JsonExpectedValue has(std::string_view key);
/**
* Check if this object has an optional key. Asserts that this JSON
* value is an object. If the key doesn't exist, sets an error and
* returns a `JsonExpectValue` that does nothing
* @returns The key, which is a no-op value if it didn't exist
*/
JsonExpectedValue needs(std::string_view key);
/**
* Asserts that this JSON value is an object. Get all object
* properties
*/
std::vector<std::pair<std::string, JsonExpectedValue>> properties();
/**
* Asserts that this JSON value is an object. Logs unknown keys to
* the console as warnings
*/
void checkUnknownKeys();
// -- Dealing with arrays --
/**
* Asserts that this JSON value is an array. Returns the length of
* the array, or 0 on error
*/
size_t length();
/**
* Asserts that this JSON value is an array. Returns the value at
* the specified index. If there is no value at that index, sets an
* error
*/
JsonExpectedValue at(size_t index);
/**
* Asserts that this JSON value is an array. Returns the array items
* @warning The old JsonChecker used `items` for iterating object
* properties - on this new API that function is called `properties`!
*/
std::vector<JsonExpectedValue> items();
operator bool() const;
Result<> ok();
template <class T>
Result<T> ok(T&& value) {
auto ok = this->ok();
if (!ok) {
return Err(ok.unwrapErr());
}
return Ok(std::move(value));
}
};
GEODE_DLL JsonExpectedValue checkJson(matjson::Value const& json, std::string_view rootScopeName);
}

View file

@ -341,9 +341,10 @@ std::optional<Setting> Mod::Impl::getSettingDefinition(std::string_view const ke
SettingValue* Mod::Impl::getSetting(std::string_view const key) const {
auto keystr = std::string(key);
if (m_settings.count(keystr)) {
return m_settings.at(keystr).get();
if (auto value = m_settings.at(keystr)->convertToLegacyValue()) {
return value->get();
}
}
return nullptr;
}

View file

@ -4,6 +4,7 @@
#include "ModPatch.hpp"
#include <Geode/loader/Loader.hpp>
#include <string_view>
#include "ModSettingsManager.hpp"
namespace geode {
class Mod::Impl {
@ -48,9 +49,9 @@ namespace geode {
*/
matjson::Value m_saved = matjson::Object();
/**
* Setting values
* Setting values. This is behind unique_ptr for interior mutability
*/
std::unordered_map<std::string, std::unique_ptr<SettingValue>> m_settings;
std::unique_ptr<ModSettingsManager> m_settings = std::make_unique<ModSettingsManager>();
/**
* Settings save data. Stored for efficient loading of custom settings
*/

View file

@ -125,45 +125,28 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
}
catch (...) { }
JsonChecker checker(impl->m_rawJSON);
auto root = checker.root(checkerRoot).obj();
auto root = checkJson(impl->m_rawJSON, checkerRoot);
root.needs("geode").into(impl->m_geodeVersion);
root.addKnownKey("gd");
// Check GD version
// (use rawJson because i dont like JsonMaybeValue)
if (rawJson.contains("gd")) {
std::string ver;
if (rawJson["gd"].is_object()) {
auto key = PlatformID::toShortString(GEODE_PLATFORM_TARGET, true);
if (rawJson["gd"].contains(key) && rawJson["gd"][key].is_string())
ver = rawJson["gd"][key].as_string();
} else if (rawJson["gd"].is_string()) {
if (auto gd = root.needs("gd")) {
// In the future when we get rid of support for string format just
// change all of this to the gd.needs(...) stuff
gd.assertIs({ matjson::Type::Object, matjson::Type::String });
if (gd.isObject()) {
gd.needs(GEODE_PLATFORM_SHORT_IDENTIFIER)
.mustBe<std::string>("a valid gd version", [](auto const& str) {
return str == "*" || numFromString<double>(str).isOk();
})
.into(impl->m_gdVersion);
}
else if (gd.isString()) {
impl->m_softInvalidReason = "mod.json uses old syntax";
goto dontCheckVersion;
} else {
return Err("[mod.json] has invalid target GD version");
}
if (ver.empty()) {
// this will show an error later on, but will at least load the rest of the metadata
ver = "0.000";
}
if (ver != "*") {
auto res = numFromString<double>(ver);
if (res.isErr()) {
return Err("[mod.json] has invalid target GD version");
}
impl->m_gdVersion = ver;
}
} else {
return Err("[mod.json] is missing target GD version");
}
dontCheckVersion:
constexpr auto ID_REGEX = "[a-z0-9\\-_]+\\.[a-z0-9\\-_]+";
root.needs("id")
// todo: make this use validateID in full 2.0.0 release
.validate(MiniFunction<bool(std::string const&)>(&ModMetadata::Impl::validateOldID))
.mustBe<std::string>(ID_REGEX, &ModMetadata::Impl::validateOldID)
.into(impl->m_id);
// if (!isDeprecatedIDForm(impl->m_id)) {
@ -180,7 +163,7 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
if (root.has("developer")) {
return Err("[mod.json] can not have both \"developer\" and \"developers\" specified");
}
for (auto& dev : root.needs("developers").iterate()) {
for (auto& dev : root.needs("developers").items()) {
impl->m_developers.push_back(dev.template get<std::string>());
}
}
@ -205,11 +188,9 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
});
}
for (auto& dep : root.has("dependencies").iterate()) {
auto obj = dep.obj();
bool onThisPlatform = !obj.has("platforms");
for (auto& plat : obj.has("platforms").iterate()) {
for (auto& dep : root.has("dependencies").items()) {
bool onThisPlatform = !dep.has("platforms");
for (auto& plat : dep.has("platforms").items()) {
if (PlatformID::coveredBy(plat.get<std::string>(), GEODE_PLATFORM_TARGET)) {
onThisPlatform = true;
}
@ -219,11 +200,10 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
}
Dependency dependency;
// todo: make this use validateID in full 2.0.0 release
obj.needs("id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::Impl::validateOldID)).into(dependency.id);
obj.needs("version").into(dependency.version);
obj.has("importance").into(dependency.importance);
obj.checkUnknownKeys();
dep.needs("id").mustBe<std::string>(ID_REGEX, &ModMetadata::Impl::validateOldID).into(dependency.id);
dep.needs("version").into(dependency.version);
dep.has("importance").into(dependency.importance);
dep.checkUnknownKeys();
if (
dependency.version.getComparison() != VersionCompare::MoreEq &&
@ -247,24 +227,20 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
impl->m_dependencies.push_back(dependency);
}
for (auto& incompat : root.has("incompatibilities").iterate()) {
auto obj = incompat.obj();
for (auto& incompat : root.has("incompatibilities").items()) {
Incompatibility incompatibility;
obj.needs("id").validate(MiniFunction<bool(std::string const&)>(&ModMetadata::Impl::validateOldID)).into(incompatibility.id);
obj.needs("version").into(incompatibility.version);
obj.has("importance").into(incompatibility.importance);
obj.checkUnknownKeys();
incompat.needs("id").mustBe<std::string>(ID_REGEX, &ModMetadata::Impl::validateOldID).into(incompatibility.id);
incompat.needs("version").into(incompatibility.version);
incompat.has("importance").into(incompatibility.importance);
incompat.checkUnknownKeys();
impl->m_incompatibilities.push_back(incompatibility);
}
for (auto& [key, value] : root.has("settings").items()) {
for (auto& [key, value] : root.has("settings").properties()) {
// Skip settings not on this platform
if (value.template is<matjson::Object>()) {
auto obj = value.obj();
bool onThisPlatform = !obj.has("platforms");
for (auto& plat : obj.has("platforms").iterate()) {
if (value.is(matjson::Type::Object)) {
bool onThisPlatform = !value.has("platforms");
for (auto& plat : value.has("platforms").items()) {
if (PlatformID::coveredBy(plat.get<std::string>(), GEODE_PLATFORM_TARGET)) {
onThisPlatform = true;
}
@ -274,24 +250,24 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
}
}
GEODE_UNWRAP_INTO(auto sett, Setting::parse(key, impl->m_id, value));
GEODE_UNWRAP_INTO(auto sett, SettingV3::parseBuiltin(impl->m_id, value.json()));
impl->m_settings.emplace_back(key, sett);
}
if (auto resources = root.has("resources").obj()) {
for (auto& [key, _] : resources.has("spritesheets").items()) {
if (auto resources = root.has("resources")) {
for (auto& [key, _] : resources.has("spritesheets").properties()) {
impl->m_spritesheets.push_back(impl->m_id + "/" + key);
}
}
if (auto issues = root.has("issues").obj()) {
if (auto issues = root.has("issues")) {
IssuesInfo issuesInfo;
issues.needs("info").into(issuesInfo.info);
issues.has("url").intoAs<std::string>(issuesInfo.url);
issues.has("url").into(issuesInfo.url);
impl->m_issues = issuesInfo;
}
if (auto links = root.has("links").obj()) {
if (auto links = root.has("links")) {
links.has("homepage").into(info.getLinksMut().getImpl()->m_homepage);
links.has("source").into(info.getLinksMut().getImpl()->m_source);
links.has("community").into(info.getLinksMut().getImpl()->m_community);
@ -299,19 +275,15 @@ Result<ModMetadata> ModMetadata::Impl::createFromSchemaV010(ModJson const& rawJs
}
// Tags. Actual validation is done when interacting with the server in the UI
for (auto& tag : root.has("tags").iterate()) {
for (auto& tag : root.has("tags").items()) {
impl->m_tags.insert(tag.template get<std::string>());
}
// with new cli, binary name is always mod id
impl->m_binaryName = impl->m_id + GEODE_PLATFORM_EXTENSION;
if (checker.isError()) {
return Err(checker.getError());
}
root.checkUnknownKeys();
return Ok(info);
return root.ok(info);
}
Result<ModMetadata> ModMetadata::Impl::create(ModJson const& json) {
@ -542,6 +514,18 @@ std::vector<std::string> ModMetadata::getSpritesheets() const {
return m_impl->m_spritesheets;
}
std::vector<std::pair<std::string, Setting>> ModMetadata::getSettings() const {
std::vector<std::pair<std::string, Setting>> res;
for (auto [key, sett] : m_impl->m_settings) {
auto checker = JsonChecker(sett);
auto value = checker.root("");
auto legacy = Setting::parse(key, m_impl->m_id, value);
if (!checker.isError() && legacy.isOk()) {
res.push_back(std::make_pair(key, *legacy));
}
}
return res;
}
std::vector<std::pair<std::string, matjson::Value>> ModMetadata::getSettingsV3() const {
return m_impl->m_settings;
}
std::unordered_set<std::string> ModMetadata::getTags() const {
@ -643,6 +627,10 @@ void ModMetadata::setSpritesheets(std::vector<std::string> const& value) {
m_impl->m_spritesheets = value;
}
void ModMetadata::setSettings(std::vector<std::pair<std::string, Setting>> const& value) {
// intentionally no-op because no one is supposed to be using this
// without subscribing to "internals are not stable" mentality
}
void ModMetadata::setSettings(std::vector<std::pair<std::string, matjson::Value>> const& value) {
m_impl->m_settings = value;
}
void ModMetadata::setTags(std::unordered_set<std::string> const& value) {

View file

@ -4,6 +4,7 @@
#include <Geode/loader/Mod.hpp>
#include <Geode/utils/JsonValidation.hpp>
#include <Geode/utils/VersionInfo.hpp>
#include <Geode/loader/SettingV3.hpp>
using namespace geode::prelude;
@ -36,7 +37,7 @@ namespace geode {
std::vector<Dependency> m_dependencies;
std::vector<Incompatibility> m_incompatibilities;
std::vector<std::string> m_spritesheets;
std::vector<std::pair<std::string, Setting>> m_settings;
std::vector<std::pair<std::string, matjson::Value>> m_settings;
std::unordered_set<std::string> m_tags;
bool m_needsEarlyLoad = false;
bool m_isAPI = false;

View file

@ -0,0 +1,11 @@
#include "ModSettingsManager.hpp"
SettingV3* ModSettingsManager::get(std::string const& id) {}
SettingValue* ModSettingsManager::getLegacy(std::string const& id) {
// If this setting has alreay been given a legacy interface, give that
if (m_legacy.count(id)) {
return m_legacy.at(id).get();
}
if (m_v3.count(id)) {}
}

View file

@ -0,0 +1,25 @@
#pragma once
#include <Geode/DefaultInclude.hpp>
#include <Geode/loader/SettingV3.hpp>
using namespace geode::prelude;
// This class should NEVER be exposed in a header!!!
// It is an implementation detail!!!
class ModSettingsManager final {
private:
struct SettingInfo final {
std::unique_ptr<SettingV3> v3;
std::unique_ptr<SettingValue> legacy;
};
std::unordered_map<std::string, std::unique_ptr<SettingV3>> m_v3;
// todo: remove in v4
std::unordered_map<std::string, std::unique_ptr<SettingValue>> m_legacy;
public:
SettingV3* get(std::string const& id);
SettingValue* getLegacy(std::string const& id);
};

View file

@ -0,0 +1,746 @@
#include <Geode/loader/SettingV3.hpp>
#include <Geode/utils/JsonValidation.hpp>
using namespace geode::prelude;
class SettingV3::GeodeImpl {
public:
std::string modID;
std::string key;
};
SettingV3::~SettingV3() = default;
SettingV3::SettingV3(std::string const& key, std::string const& modID)
: m_impl(std::make_shared<GeodeImpl>())
{
m_impl->key = key;
m_impl->modID = modID;
}
std::string SettingV3::getKey() const {
return m_impl->key;
}
std::string SettingV3::getModID() const {
return m_impl->modID;
}
Mod* SettingV3::getMod() const {
return Loader::get()->getInstalledMod(m_impl->modID);
}
Result<std::shared_ptr<SettingV3>> SettingV3::parseBuiltin(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>(); break;
case hash("int"): ret = std::make_shared<IntSettingV3>(); break;
case hash("float"): ret = std::make_shared<FloatSettingV3>(); break;
case hash("string"): ret = std::make_shared<StringSettingV3>(); break;
case hash("rgb"): case hash("color"): ret = std::make_shared<Color3BSettingV3>(); break;
case hash("rgba"): ret = std::make_shared<Color4BSettingV3>(); break;
case hash("path"): case hash("file"): ret = std::make_shared<FileSettingV3>(); break;
case hash("custom"): ret = std::make_shared<UnresolvedCustomSettingV3>(); break;
case hash("title"): ret = std::make_shared<TitleSettingV3>(); break;
}
GEODE_UNWRAP(ret->parse(modID, json));
return root.ok(ret);
}
std::optional<Setting> SettingV3::convertToLegacy() const {
return std::nullopt;
}
std::optional<std::unique_ptr<SettingValue>> SettingV3::convertToLegacyValue() const {
return std::nullopt;
}
class geode::detail::GeodeSettingBaseV3::Impl final {
public:
std::string name;
std::optional<std::string> description;
std::optional<std::string> enableIf;
};
std::string geode::detail::GeodeSettingBaseV3::getName() const {
return m_impl->name;
}
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::parseShared(JsonExpectedValue& json) {
json.needs("name").into(m_impl->name);
json.needs("description").into(m_impl->description);
json.needs("enable-if").into(m_impl->enableIf);
return Ok();
}
class TitleSettingV3::Impl final {
public:
std::string title;
};
std::string TitleSettingV3::getTitle() const {
return m_impl->title;
}
Result<> TitleSettingV3::parse(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) {
return true;
}
bool TitleSettingV3::save(matjson::Value&) const {
return true;
}
SettingNodeV3* TitleSettingV3::createNode(float width) {
// todo
}
bool TitleSettingV3::isDefaultValue() const {
return true;
}
void TitleSettingV3::reset() {}
class UnresolvedCustomSettingV3::Impl final {
public:
matjson::Value json;
};
Result<> UnresolvedCustomSettingV3::parse(std::string const& modID, matjson::Value const& json) {
m_impl->json = json;
return Ok();
}
bool UnresolvedCustomSettingV3::load(matjson::Value const& json) {
return true;
}
bool UnresolvedCustomSettingV3::save(matjson::Value& json) const {
return true;
}
SettingNodeV3* UnresolvedCustomSettingV3::createNode(float width) {
// todo
}
bool UnresolvedCustomSettingV3::isDefaultValue() const {
return true;
}
void UnresolvedCustomSettingV3::reset() {}
std::optional<Setting> UnresolvedCustomSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(CustomSetting {
.json = std::make_shared<ModJson>(m_impl->json)
}));
}
std::optional<std::unique_ptr<SettingValue>> UnresolvedCustomSettingV3::convertToLegacyValue() const {
return std::nullopt;
}
class BoolSettingV3::Impl final {
public:
bool value;
bool defaultValue;
};
bool BoolSettingV3::getValue() const {
return m_impl->value;
}
void BoolSettingV3::setValue(bool value) {
m_impl->value = value;
}
Result<> BoolSettingV3::isValid(bool value) const {}
bool BoolSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
Result<> BoolSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "BoolSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(m_impl->defaultValue);
m_impl->value = m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok();
}
bool BoolSettingV3::load(matjson::Value const& json) {
if (json.is_bool()) {
m_impl->value = json.as_bool();
return true;
}
return false;
}
bool BoolSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* BoolSettingV3::createNode(float width) {
// todo
}
bool BoolSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void BoolSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> BoolSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(BoolSetting {
.name = this->getName(),
.description = this->getDescription(),
.defaultValue = this->getDefaultValue(),
}));
}
std::optional<std::unique_ptr<SettingValue>> BoolSettingV3::convertToLegacyValue() const {
return std::make_unique<BoolSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}
class IntSettingV3::Impl final {
public:
int64_t value;
int64_t defaultValue;
std::optional<int64_t> minValue;
std::optional<int64_t> maxValue;
struct {
// 0 means not enabled
size_t arrowStepSize;
size_t bigArrowStepSize;
bool sliderEnabled;
std::optional<int64_t> sliderSnap;
bool textInputEnabled;
} controls;
};
bool IntSettingV3::isArrowsEnabled() const {
return m_impl->controls.arrowStepSize > 0;
}
bool IntSettingV3::isBigArrowsEnabled() const {
return m_impl->controls.bigArrowStepSize > 0;
}
size_t IntSettingV3::getArrowStepSize() const {
return m_impl->controls.arrowStepSize;
}
size_t IntSettingV3::getBigArrowStepSize() const {
return m_impl->controls.bigArrowStepSize;
}
bool IntSettingV3::isSliderEnabled() const {
return m_impl->controls.sliderEnabled;
}
std::optional<int64_t> IntSettingV3::getSliderSnap() const {
return m_impl->controls.sliderSnap;
}
bool IntSettingV3::isInputEnabled() const {
return m_impl->controls.textInputEnabled;
}
int64_t IntSettingV3::getValue() const {
return m_impl->value;
}
void IntSettingV3::setValue(int64_t value) {
m_impl->value = clamp(
value,
m_impl->minValue.value_or(std::numeric_limits<int64_t>::min()),
m_impl->maxValue.value_or(std::numeric_limits<int64_t>::max())
);
}
int64_t IntSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
std::optional<int64_t> IntSettingV3::getMinValue() const {
return m_impl->minValue;
}
std::optional<int64_t> IntSettingV3::getMaxValue() const {
return m_impl->maxValue;
}
Result<> IntSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "IntSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(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) {
if (json.is_number()) {
m_impl->value = json.as_int();
return true;
}
return false;
}
bool IntSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* IntSettingV3::createNode(float width) {
// todo
}
bool IntSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void IntSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> IntSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(IntSetting {
.name = this->getName(),
.description = this->getDescription(),
.defaultValue = this->getDefaultValue(),
.min = this->getMinValue(),
.max = this->getMaxValue(),
.controls = {
.arrows = this->isArrowsEnabled(),
.bigArrows = this->isBigArrowsEnabled(),
.arrowStep = this->getArrowStepSize(),
.bigArrowStep = this->getBigArrowStepSize(),
.slider = this->isSliderEnabled(),
.sliderStep = this->getSliderSnap(),
.input = this->isInputEnabled(),
},
}));
}
std::optional<std::unique_ptr<SettingValue>> IntSettingV3::convertToLegacyValue() const {
return std::make_unique<IntSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}
class FloatSettingV3::Impl final {
public:
double value;
double defaultValue;
std::optional<double> minValue;
std::optional<double> maxValue;
struct {
// 0 means not enabled
size_t arrowStepSize;
size_t bigArrowStepSize;
bool sliderEnabled;
std::optional<double> sliderSnap;
bool textInputEnabled;
} controls;
};
bool FloatSettingV3::isArrowsEnabled() const {
return m_impl->controls.arrowStepSize > 0;
}
bool FloatSettingV3::isBigArrowsEnabled() const {
return m_impl->controls.bigArrowStepSize > 0;
}
size_t FloatSettingV3::getArrowStepSize() const {
return m_impl->controls.arrowStepSize;
}
size_t FloatSettingV3::getBigArrowStepSize() const {
return m_impl->controls.bigArrowStepSize;
}
bool FloatSettingV3::isSliderEnabled() const {
return m_impl->controls.sliderEnabled;
}
std::optional<double> FloatSettingV3::getSliderSnap() const {
return m_impl->controls.sliderSnap;
}
bool FloatSettingV3::isInputEnabled() const {
return m_impl->controls.textInputEnabled;
}
double FloatSettingV3::getValue() const {
return m_impl->value;
}
Result<> FloatSettingV3::setValue(double value) {
if (m_impl->minValue && value < *m_impl->minValue) {
return Err("Value must be under ");
}
m_impl->value = clamp(
value,
m_impl->minValue.value_or(std::numeric_limits<double>::min()),
m_impl->maxValue.value_or(std::numeric_limits<double>::max())
);
}
double FloatSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
std::optional<double> FloatSettingV3::getMinValue() const {
return m_impl->minValue;
}
std::optional<double> FloatSettingV3::getMaxValue() const {
return m_impl->maxValue;
}
Result<> FloatSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "FloatSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(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) {
if (json.is_number()) {
m_impl->value = json.as_double();
return true;
}
return false;
}
bool FloatSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* FloatSettingV3::createNode(float width) {
// todo
}
bool FloatSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void FloatSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> FloatSettingV3::convertToLegacy() const {
return Setting(this->getKey(), this->getModID(), SettingKind(FloatSetting {
.name = this->getName(),
.description = this->getDescription(),
.defaultValue = this->getDefaultValue(),
.min = this->getMinValue(),
.max = this->getMaxValue(),
.controls = {
.arrows = this->isArrowsEnabled(),
.bigArrows = this->isBigArrowsEnabled(),
.arrowStep = this->getArrowStepSize(),
.bigArrowStep = this->getBigArrowStepSize(),
.slider = this->isSliderEnabled(),
.sliderStep = this->getSliderSnap(),
.input = this->isInputEnabled(),
},
}));
}
std::optional<std::unique_ptr<SettingValue>> FloatSettingV3::convertToLegacyValue() const {
return std::make_unique<FloatSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}
class StringSettingV3::Impl final {
public:
std::string value;
std::string defaultValue;
std::optional<std::string> match;
std::optional<std::string> filter;
std::optional<std::vector<std::string>> oneOf;
};
std::string StringSettingV3::getValue() const {
return m_impl->value;
}
Result<> StringSettingV3::setValue(std::string_view value) {
m_impl->value = value;
}
std::string StringSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
std::optional<std::string> StringSettingV3::getRegexValidator() const {
return m_impl->match;
}
std::optional<std::string> StringSettingV3::getAllowedCharacters() const {
return m_impl->filter;
}
std::optional<std::vector<std::string>> StringSettingV3::getEnumOptions() const {
return m_impl->oneOf;
}
Result<> StringSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "StringSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(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) {
if (json.is_string()) {
m_impl->value = json.as_string();
return true;
}
return false;
}
bool StringSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* StringSettingV3::createNode(float width) {
// todo
}
bool StringSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void StringSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> StringSettingV3::convertToLegacy() const {
auto setting = StringSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
setting.controls->filter = this->getAllowedCharacters();
setting.controls->match = this->getRegexValidator();
setting.controls->options = this->getEnumOptions();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> StringSettingV3::convertToLegacyValue() const {
return std::make_unique<StringSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}
class FileSettingV3::Impl final {
public:
std::filesystem::path value;
std::filesystem::path defaultValue;
std::optional<std::vector<utils::file::FilePickOptions::Filter>> filters;
};
std::filesystem::path FileSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
std::filesystem::path FileSettingV3::getValue() const {
return m_impl->value;
}
std::optional<std::vector<utils::file::FilePickOptions::Filter>> FileSettingV3::getFilters() const {
return m_impl->filters;
}
Result<> FileSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "FileSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(m_impl->defaultValue);
// Replace known paths like `{gd-save-dir}/`
try {
m_impl->defaultValue = fmt::format(
fmt::runtime(m_impl->defaultValue.string()),
fmt::arg("gd-save-dir", dirs::getSaveDir()),
fmt::arg("gd-game-dir", dirs::getGameDir()),
fmt::arg("mod-config-dir", dirs::getModConfigDir() / modID),
fmt::arg("mod-save-dir", dirs::getModsSaveDir() / modID),
fmt::arg("temp-dir", dirs::getTempDir())
);
}
catch(fmt::format_error const&) {
return Err("Invalid format string for file setting path");
}
m_impl->value = m_impl->defaultValue;
if (auto controls = root.has("control")) {
auto filters = std::vector<file::FilePickOptions::Filter>();
for (auto& item : controls.has("filters").items()) {
utils::file::FilePickOptions::Filter filter;
item.has("description").into(filter.description);
item.has("files").into(filter.files);
filters.push_back(filter);
}
if (!filters.empty()) {
m_impl->filters.emplace(filters);
}
}
root.checkUnknownKeys();
return root.ok();
}
bool FileSettingV3::load(matjson::Value const& json) {
if (json.is_string()) {
m_impl->value = json.as_string();
return true;
}
return false;
}
bool FileSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* createNode(float width) {
// todo
}
bool FileSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void FileSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> FileSettingV3::convertToLegacy() const {
auto setting = FileSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
setting.controls.filters = this->getFilters().value_or(std::vector<utils::file::FilePickOptions::Filter>());
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> FileSettingV3::convertToLegacyValue() const {
return std::make_unique<FileSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}
class Color3BSettingV3::Impl final {
public:
ccColor3B value;
ccColor3B defaultValue;
};
ccColor3B Color3BSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
ccColor3B Color3BSettingV3::getValue() const {
return m_impl->value;
}
Result<> Color3BSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "Color3BSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(m_impl->defaultValue);
m_impl->value = m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok();
}
bool Color3BSettingV3::load(matjson::Value const& json) {
if (json.template is<ccColor3B>()) {
m_impl->value = json.template as<ccColor3B>();
return true;
}
return false;
}
bool Color3BSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* Color3BSettingV3::createNode(float width) {
// todo
}
bool Color3BSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void Color3BSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> Color3BSettingV3::convertToLegacy() const {
auto setting = ColorSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> Color3BSettingV3::convertToLegacyValue() const {
return std::make_unique<ColorSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}
class Color4BSettingV3::Impl final {
public:
ccColor4B value;
ccColor4B defaultValue;
};
ccColor4B Color4BSettingV3::getDefaultValue() const {
return m_impl->defaultValue;
}
ccColor4B Color4BSettingV3::getValue() const {
return m_impl->value;
}
Result<> Color4BSettingV3::parse(std::string const& modID, matjson::Value const& json) {
auto root = checkJson(json, "Color4BSettingV3");
GEODE_UNWRAP(this->parseShared(root));
root.needs("default").into(m_impl->defaultValue);
m_impl->value = m_impl->defaultValue;
root.checkUnknownKeys();
return root.ok();
}
bool Color4BSettingV3::load(matjson::Value const& json) {
if (json.template is<ccColor4B>()) {
m_impl->value = json.template as<ccColor4B>();
return true;
}
return false;
}
bool Color4BSettingV3::save(matjson::Value& json) const {
json = m_impl->value;
return true;
}
SettingNodeV3* Color4BSettingV3::createNode(float width) {
// todo
}
bool Color4BSettingV3::isDefaultValue() const {
return m_impl->value == m_impl->defaultValue;
}
void Color4BSettingV3::reset() {
m_impl->value = m_impl->defaultValue;
}
std::optional<Setting> Color4BSettingV3::convertToLegacy() const {
auto setting = ColorAlphaSetting();
setting.name = this->getName();
setting.description = this->getDescription();
setting.defaultValue = this->getDefaultValue();
return Setting(this->getKey(), this->getModID(), SettingKind(setting));
}
std::optional<std::unique_ptr<SettingValue>> Color4BSettingV3::convertToLegacyValue() const {
return std::make_unique<ColorAlphaSettingValue>(this->getKey(), this->getModID(), *this->convertToLegacy());
}

View file

@ -2,201 +2,50 @@
using namespace geode::prelude;
matjson::Value& JsonMaybeSomething::json() {
return m_json;
}
JsonMaybeSomething::JsonMaybeSomething(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
) :
m_checker(checker),
m_json(json), m_hierarchy(hierarchy), m_hasValue(hasValue) {}
bool JsonMaybeSomething::isError() const {
return m_checker.isError() || !m_hasValue;
}
std::string JsonMaybeSomething::getError() const {
return m_checker.getError();
}
JsonMaybeSomething::operator bool() const {
return !isError();
}
void JsonMaybeSomething::setError(std::string const& error) {
m_checker.m_result = error;
}
JsonMaybeValue::JsonMaybeValue(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
) :
JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
JsonMaybeSomething& JsonMaybeValue::self() {
return *static_cast<JsonMaybeSomething*>(this);
}
// template<class Json>
// template<nlohmann::detail::value_t T>
// JsonMaybeValue& JsonMaybeValue::as() {
// if (this->isError()) return *this;
// if (!jsonConvertibleTo(self().m_json.type(), T)) {
// this->setError(
// self().m_hierarchy + ": Invalid type \"" +
// self().m_json.type_name() + "\", expected \"" +
// jsonValueTypeToString(T) + "\""
// );
// }
// m_inferType = false;
// return *this;
// }
JsonMaybeValue& JsonMaybeValue::array() {
this->as<value_t::Array>();
return *this;
}
// template<class Json>
// template<nlohmann::detail::value_t... T>
// JsonMaybeValue JsonMaybeValue::asOneOf() {
// if (this->isError()) return *this;
// bool isOneOf = (... || jsonConvertibleTo(self().m_json.type(), T));
// if (!isOneOf) {
// this->setError(
// self().m_hierarchy + ": Invalid type \"" +
// self().m_json.type_name() + "\", expected one of \"" +
// (jsonValueTypeToString(T), ...) + "\""
// );
// }
// m_inferType = false;
// return *this;
// }
// template<class Json>
// template<nlohmann::detail::value_t T>
// JsonMaybeValue JsonMaybeValue::is() {
// if (this->isError()) return *this;
// self().m_hasValue = jsonConvertibleTo(self().m_json.type(), T);
// m_inferType = false;
// return *this;
// }
// template<class Json>
// template<class T>
// JsonMaybeValue JsonMaybeValue::validate(JsonValueValidator<T> validator) {
// if (this->isError()) return *this;
// try {
// if (!validator(self().m_json.template get<T>())) {
// this->setError(self().m_hierarchy + ": Invalid value format");
// }
// } catch(...) {
// this->setError(
// self().m_hierarchy + ": Invalid type \"" +
// std::string(self().m_json.type_name()) + "\""
// );
// }
// return *this;
// }
// template<class Json>
// template<class T>
// JsonMaybeValue JsonMaybeValue::inferType() {
// if (this->isError() || !m_inferType) return *this;
// return this->as<getJsonType<T>()>();
// }
// template<class Json>
// template<class T>
// JsonMaybeValue JsonMaybeValue::intoRaw(T& target) {
// if (this->isError()) return *this;
// target = self().m_json;
// return *this;
// }
// template<class Json>
// template<class T>
// JsonMaybeValue JsonMaybeValue::into(T& target) {
// return this->intoAs<T, T>(target);
// }
// template<class Json>
// template<class T>
// JsonMaybeValue JsonMaybeValue::into(std::optional<T>& target) {
// return this->intoAs<T, std::optional<T>>(target);
// }
// template<class Json>
// template<class A, class T>
// JsonMaybeValue JsonMaybeValue::intoAs(T& target) {
// this->inferType<A>();
// if (this->isError()) return *this;
// try {
// target = self().m_json.template get<A>();
// } catch(...) {
// this->setError(
// self().m_hierarchy + ": Invalid type \"" +
// std::string(self().m_json.type_name()) + "\""
// );
// }
// return *this;
// }
// template<class Json>
// template<class T>
// T JsonMaybeValue::get() {
// this->inferType<T>();
// if (this->isError()) return T();
// try {
// return self().m_json.template get<T>();
// } catch(...) {
// this->setError(
// self().m_hierarchy + ": Invalid type to get \"" +
// std::string(self().m_json.type_name()) + "\""
// );
// }
// return T();
// }
JsonMaybeObject JsonMaybeValue::obj() {
this->as<value_t::Object>();
return JsonMaybeObject(self().m_checker, self().m_json, self().m_hierarchy, self().m_hasValue);
}
// template<class Json>
// template<class T>
// struct JsonMaybeValue::Iterator {
// std::vector<T> m_values;
// using iterator = typename std::vector<T>::iterator;
// using const_iterator = typename std::vector<T>::const_iterator;
// iterator begin() {
// return m_values.begin();
// }
// iterator end() {
// return m_values.end();
// }
// const_iterator begin() const {
// return m_values.begin();
// }
// const_iterator end() const {
// return m_values.end();
// }
// };
JsonMaybeValue JsonMaybeValue::at(size_t i) {
this->as<value_t::Array>();
if (this->isError()) return *this;
@ -216,7 +65,6 @@ JsonMaybeValue JsonMaybeValue::at(size_t i) {
);
}
typename JsonMaybeValue::template Iterator<JsonMaybeValue> JsonMaybeValue::iterate() {
this->as<value_t::Array>();
Iterator<JsonMaybeValue> iter;
@ -232,7 +80,6 @@ typename JsonMaybeValue::template Iterator<JsonMaybeValue> JsonMaybeValue::itera
return iter;
}
typename JsonMaybeValue::template Iterator<std::pair<std::string, JsonMaybeValue>> JsonMaybeValue::items() {
this->as<value_t::Object>();
Iterator<std::pair<std::string, JsonMaybeValue>> iter;
@ -248,33 +95,26 @@ typename JsonMaybeValue::template Iterator<std::pair<std::string, JsonMaybeValue
return iter;
}
JsonMaybeObject::JsonMaybeObject(
JsonChecker& checker, matjson::Value& json, std::string const& hierarchy, bool hasValue
) :
JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
) : JsonMaybeSomething(checker, json, hierarchy, hasValue) {}
JsonMaybeSomething& JsonMaybeObject::self() {
return *static_cast<JsonMaybeSomething*>(this);
}
void JsonMaybeObject::addKnownKey(std::string const& key) {
m_knownKeys.insert(key);
}
matjson::Value& JsonMaybeObject::json() {
return self().m_json;
}
JsonMaybeValue JsonMaybeObject::emptyValue() {
return JsonMaybeValue(self().m_checker, self().m_json, "", false);
}
JsonMaybeValue JsonMaybeObject::has(std::string const& key) {
this->addKnownKey(key);
if (this->isError()) return emptyValue();
@ -284,7 +124,6 @@ JsonMaybeValue JsonMaybeObject::has(std::string const& key) {
return JsonMaybeValue(self().m_checker, self().m_json[key], key, true);
}
JsonMaybeValue JsonMaybeObject::needs(std::string const& key) {
this->addKnownKey(key);
if (this->isError()) return emptyValue();
@ -295,7 +134,6 @@ JsonMaybeValue JsonMaybeObject::needs(std::string const& key) {
return JsonMaybeValue(self().m_checker, self().m_json[key], key, true);
}
// TODO: gross hack :3 (ctrl+f this comment to find the other part)
extern bool s_jsonCheckerShouldCheckUnknownKeys;
bool s_jsonCheckerShouldCheckUnknownKeys = true;
@ -309,20 +147,270 @@ void JsonMaybeObject::checkUnknownKeys() {
}
}
JsonChecker::JsonChecker(matjson::Value& json) : m_json(json), m_result(std::monostate()) {}
bool JsonChecker::isError() const {
return std::holds_alternative<std::string>(m_result);
}
std::string JsonChecker::getError() const {
return std::get<std::string>(m_result);
}
JsonMaybeValue JsonChecker::root(std::string const& hierarchy) {
return JsonMaybeValue(*this, m_json, hierarchy, true);
}
static const char* matJsonTypeToString(matjson::Type ty) {
switch (ty) {
case matjson::Type::Null: return "null";
case matjson::Type::Bool: return "bool";
case matjson::Type::Number: return "number";
case matjson::Type::String: return "string";
case matjson::Type::Array: return "array";
case matjson::Type::Object: return "object";
default: return "unknown";
}
}
// This is used for null JsonExpectedValues (for example when doing
// `json.has("key")` where "key" doesn't exist)
static matjson::Value NULL_SCOPED_VALUE = nullptr;
class JsonExpectedValue::Impl final {
public:
// Values shared between JsonExpectedValues related to the same JSON
struct Shared final {
matjson::Value originalJson;
std::optional<std::string> error;
std::string rootScopeName;
Shared(matjson::Value const& json, std::string_view rootScopeName)
: originalJson(json), rootScopeName(rootScopeName) {}
};
// this may be null if the JsonExpectedValue is a "null" value
std::shared_ptr<Shared> shared;
matjson::Value& scope;
std::string scopeName;
std::string key;
std::unordered_set<std::string> knownKeys;
Impl()
: shared(nullptr),
scope(NULL_SCOPED_VALUE)
{}
// Create a root Impl
Impl(std::shared_ptr<Shared> shared)
: shared(shared),
scope(shared->originalJson),
scopeName(shared->rootScopeName)
{}
// Create a derived Impl
Impl(Impl* from, matjson::Value& scope, std::string_view key)
: shared(from->shared),
scope(scope),
scopeName(fmt::format("{}.{}", from->scopeName, key)),
key(key)
{}
};
JsonExpectedValue::JsonExpectedValue()
: m_impl(std::make_unique<Impl>())
{}
JsonExpectedValue::JsonExpectedValue(Impl* from, matjson::Value& scope, std::string_view key)
: m_impl(std::make_unique<Impl>(from, scope, key))
{}
JsonExpectedValue::JsonExpectedValue(matjson::Value const& json, std::string_view rootScopeName)
: m_impl(std::make_unique<Impl>(std::make_shared<matjson::Value>(json, rootScopeName)))
{}
JsonExpectedValue::~JsonExpectedValue() {}
JsonExpectedValue::JsonExpectedValue(JsonExpectedValue&&) = default;
JsonExpectedValue& JsonExpectedValue::operator=(JsonExpectedValue&&) = default;
matjson::Value const& JsonExpectedValue::getJSONRef() const {
return m_impl->scope;
}
matjson::Value JsonExpectedValue::json() const {
return m_impl->scope;
}
std::string JsonExpectedValue::key() const {
return m_impl->key;
}
bool JsonExpectedValue::hasError() const {
return !m_impl->shared || m_impl->shared->error.has_value();
}
void JsonExpectedValue::setError(std::string_view error) {
m_impl->shared->error.emplace(fmt::format("[{}]: {}", m_impl->scopeName, error));
}
bool JsonExpectedValue::is(matjson::Type type) const {
if (this->hasError()) return false;
return m_impl->scope.type() == type;
}
bool JsonExpectedValue::isNull() const {
return this->is(matjson::Type::Null);
}
bool JsonExpectedValue::isBool() const {
return this->is(matjson::Type::Bool);
}
bool JsonExpectedValue::isNumber() const {
return this->is(matjson::Type::Number);
}
bool JsonExpectedValue::isString() const {
return this->is(matjson::Type::String);
}
bool JsonExpectedValue::isArray() const {
return this->is(matjson::Type::Array);
}
bool JsonExpectedValue::isObject() const {
return this->is(matjson::Type::Object);
}
JsonExpectedValue& JsonExpectedValue::assertIs(matjson::Type type) {
if (this->hasError()) return *this;
if (m_impl->scope.type() != type) {
this->setError(
"invalid type {}, expected {}",
matJsonTypeToString(m_impl->scope.type()),
matJsonTypeToString(type)
);
}
return *this;
}
JsonExpectedValue& JsonExpectedValue::assertIsNull() {
return this->assertIs(matjson::Type::Null);
}
JsonExpectedValue& JsonExpectedValue::assertIsBool() {
return this->assertIs(matjson::Type::Bool);
}
JsonExpectedValue& JsonExpectedValue::assertIsNumber() {
return this->assertIs(matjson::Type::Number);
}
JsonExpectedValue& JsonExpectedValue::assertIsString() {
return this->assertIs(matjson::Type::String);
}
JsonExpectedValue& JsonExpectedValue::assertIsArray() {
return this->assertIs(matjson::Type::Array);
}
JsonExpectedValue& JsonExpectedValue::assertIsObject() {
return this->assertIs(matjson::Type::Object);
}
JsonExpectedValue& JsonExpectedValue::assertIs(std::initializer_list<matjson::Type> types) {
if (this->hasError()) return *this;
if (!std::any_of(types.begin(), types.end(), [this](matjson::Type t) { return t == m_impl->scope.type(); })) {
this->setError(
"invalid type {}, expected either {}",
matJsonTypeToString(m_impl->scope.type()),
ranges::join(ranges::map<std::vector<std::string>>(types, [](matjson::Type t) {
return matJsonTypeToString(t);
}), " or ")
);
}
return *this;
}
JsonExpectedValue JsonExpectedValue::has(std::string_view key) {
if (this->hasError()) {
return JsonExpectedValue();
}
if (!this->assertIs(matjson::Type::Object)) {
return JsonExpectedValue();
}
if (!m_impl->scope.contains(key)) {
return JsonExpectedValue();
}
return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key);
}
JsonExpectedValue JsonExpectedValue::needs(std::string_view key) {
if (this->hasError()) {
return JsonExpectedValue();
}
if (!this->assertIs(matjson::Type::Object)) {
return JsonExpectedValue();
}
if (!m_impl->scope.contains(key)) {
this->setError("missing required key {}", key);
return JsonExpectedValue();
}
return JsonExpectedValue(m_impl.get(), m_impl->scope[key], key);
}
std::vector<std::pair<std::string, JsonExpectedValue>> JsonExpectedValue::properties() {
if (this->hasError()) {
return std::vector<std::pair<std::string, JsonExpectedValue>>();
}
if (!this->assertIs(matjson::Type::Object)) {
return std::vector<std::pair<std::string, JsonExpectedValue>>();
}
std::vector<std::pair<std::string, JsonExpectedValue>> res;
for (auto& [k, v] : m_impl->scope.as_object()) {
res.push_back(std::make_pair(k, JsonExpectedValue(m_impl.get(), v, k)));
}
return res;
}
void JsonExpectedValue::checkUnknownKeys() {
for (auto& [key, _] : this->properties()) {
if (!m_impl->knownKeys.count(key)) {
log::warn("{} contains unknown key \"{}\"", m_impl->scopeName, key);
}
}
}
size_t JsonExpectedValue::length() {
if (this->hasError()) {
return 0;
}
if (!this->assertIs(matjson::Type::Array)) {
return 0;
}
return m_impl->scope.as_array().size();
}
JsonExpectedValue JsonExpectedValue::at(size_t index) {
if (this->hasError()) {
return JsonExpectedValue();
}
if (!this->assertIs(matjson::Type::Array)) {
return JsonExpectedValue();
}
auto& arr = m_impl->scope.as_array();
if (arr.size() <= index) {
this->setError(
"array expected to have at least size {}, but its size was only {}",
index + 1, arr.size()
);
return JsonExpectedValue();
}
return JsonExpectedValue(m_impl.get(), arr.at(index), std::to_string(index));
}
std::vector<JsonExpectedValue> JsonExpectedValue::items() {
if (this->hasError()) {
return std::vector<JsonExpectedValue>();
}
if (!this->assertIs(matjson::Type::Object)) {
return std::vector<JsonExpectedValue>();
}
std::vector<JsonExpectedValue> res;
size_t i = 0;
for (auto& v : m_impl->scope.as_array()) {
res.push_back(JsonExpectedValue(m_impl.get(), v, std::to_string(i++)));
}
return res;
}
JsonExpectedValue::operator bool() const {
return !this->hasError();
}
Result<> JsonExpectedValue::ok() {
if (m_impl->shared && m_impl->shared->error) {
return Err(*m_impl->shared->error);
}
return Ok();
}
JsonExpectedValue geode::checkJson(matjson::Value const& json, std::string_view rootScopeName) {
return JsonExpectedValue(json, rootScopeName);
}