--- > [!WARNING] > Some part is still **work in progress** and only provide a brief and short explanation. > [!NOTE] > Reflecting Utilitary 1.0.0 and Minecraft 26.1 release for the documentation Utilitary config system utilizes Minecraft codec to serialize and deserialize values. In addition, the config has their own unique structure of separation. ```json5 { // The metadata of the config "config": {}, // The main content of the config "data": {}, } ``` --- # Setting Up Config Make sure Utilitary is ready to use, if not you can go back to [Installation]() and come back here ## Preparing A Config Class The class of the config can be either record or regular class depending on preferences ```java public record Config() {} ``` ```java public class Config { public Config() {} } ``` And that's pretty much it. No need for an interface to implements. ## Creating Codec Use `RecordCodecBuilder.create()` to create a config codec Since the config class above is empty and can't serve as a good example, we're going to use Skinmatics 2.0.0 config as an example. ```java public record SkinmaticsConfig( String profile, int updateRange, boolean forceStrongerEmissiveGlow, GUI gui, PaperDoll paperDoll, LinkedHashMap autoAssigns ) { } ``` ```java public static final Codec CODEC = RecordCodecBuilder.create( inst -> inst.group( Codec.STRING.fieldOf("profile").forGetter(SkinmaticsConfig::profile), Codec.INT.fieldOf("updateRange").forGetter(SkinmaticsConfig::updateRange), Codec.BOOL.fieldOf("forceStrongerEmissiveGlow").forGetter(SkinmaticsConfig::forceStrongerEmissiveGlow), GUI.CODEC.fieldOf("gui").forGetter(SkinmaticsConfig::gui), PaperDoll.CODEC.fieldOf("paperDoll").forGetter(SkinmaticsConfig::paperDoll), Codec.unboundedMap(UUIDUtil.CODEC, Codec.STRING).xmap(LinkedHashMap::new, (map) -> map) .fieldOf("autoAssigns").forGetter(SkinmaticsConfig::autoAssigns) ).apply(inst, SkinmaticsConfig::new) ); ``` > [!TIP] > You can directly store it in `ConfigCodec` using `ConfigCodec.of()`. ## Creating Config Type The last step is creating a config type. ```java public static final ConfigType TYPE = new ConfigType<>( Identifier.fromNamespaceAndPath("skinmatics", "config"), // Config type id 1, // config version ConfigCodec.of(CODEC), (_) -> new SkinmaticsConfig(...), null, SimpleMetadataType.INSTANCE, JsonConfigFormat.INSTANCE ); ``` And all set! Now you only have to load and save the config. --- # Loading, Saving, and Deleting Config > [!NOTE] > The file name of the config always use [SimpleIdentifier](). ## Loading Config ```java ConfigManager.Result result = ConfigManager.load(, ); Config config = result.config(); ConfigMetadata metadata = result.metadata(); ProfileManager.Status status = result.status(); ``` ## Saving Config ```java ProfileManager.Status status = ConfigManager.save(, , ) ``` ## Deleting Config ```java boolean deleted = ConfigManager.delete(, ) ``` --- # Adding Comments To Config `config/utilitary/utilitary.json5` ```json5 { // Config version config: 2, // Utilitary Configuration data: { // Disable saving and loading config file. Loading config file always returns default safeMode: false, // Prints additional information to the console debugPrinting: false, }, } ``` Before you start adding comments, make sure to change your `ConfigType`'s `ConfigFormat` to `Json5ConfigFormat` ```java new ConfigType<>( ..., Json5ConfigFormat.INSTANCE ); ``` ## Preparing Comments Use `Comment.of()` to start making comments. > [!NOTE] > This comment only apply to `data` field. `config` (metadata) field has their own Comment provided by the metadata type. ```java Comment comment = Comment.of("Utilitary Configuration"); comment.addComment("safeMode", "Disable saving and loading config file. Loading config file always returns default"); comment.addComment("debugPrinting", "Prints additional information to the console"); ``` > [!TIP] > You can use `Component` for comments. However, the language file might not be loaded when you load it. Use `Component.translatableWithFallback()` if your config load before the language load. ## Creating FormatSettings The final step is to create a `FormatSettings` ```java // Returns a FormatSettings with both metadata comments and the supplied comments Json5ConfigFormat.withComment(, ) ``` Then change the argument of how you load and save from ```java ConfigManager.load(, ); ConfigManager.save(, , ); ``` to ```java ConfigManager.load(, , ); ConfigManager.save(, , , ); ``` --- # Config Metadata Config metadata serves as a way to store constant values or be a clear separation between data and metadata values ## SimpleMetadataType This is the only metadata type Utilitary provided by default. Only serializes the mandatory version number ```java public class SimpleMetadataType implements ConfigMetadataType { public static final Codec CODEC = Codec.INT.comapFlatMap(SimpleMetadataType::read, Metadata::version); public static final SimpleMetadataType INSTANCE = new SimpleMetadataType(); private SimpleMetadataType() {} @Override public @NonNull Codec codec() { return CODEC; } @Override public SimpleMetadataType.@NonNull Metadata create(Context context) { return new Metadata(context.version()); } @Override public JsonElement migrate(JsonElement original) { if (original.isJsonObject()) { return original.getAsJsonObject().get("version"); } return original; } @Override public Comment comment() { return Comment.of("Config version"); } public static DataResult read(Integer version) { return DataResult.success(new Metadata(version)); } public record Metadata(int version) implements ConfigMetadata {} } ``` ```json { "config": 1 } ``` ## Custom Config Metadata > [!IMPORTANT] > It's recommended to use "version" as the config version, and always convert a number only `config` field as the config version. We'll be using Skinmatics `ProfileMetadataType` as the example of this part ### Record Metadata Prepare a metadata record class by implementing `ConfigMetadata` ```java public record Metadata( int version, String skinmatics, String utilitary ) implements ConfigMetadata {} ``` ### Preparing Codec ```java public static final Codec CODEC = RecordCodecBuilder.create( inst -> inst.group( Codec.INT.fieldOf("version").forGetter(Metadata::version), Codec.STRING.fieldOf("skinmatics").forGetter(Metadata::skinmatics), Codec.STRING.fieldOf("utilitary").forGetter(Metadata::utilitary) ).apply(inst, Metadata::new) ); ``` ### The Main Class ```java public class ProfileMetadataType implements ConfigMetadataType { @Override public @NonNull Codec codec() { return CODEC; } @Override public @NonNull Metadata create(Context context) { return new Metadata(context.version(), SkinmaticsMod.getVersion(), SkinmaticsMod.getVersion(Utilitary.ID)); } } ``` ### Using It Just plug it in the last second argument of `new ConfigType<>()` and test it with `ConfigManager.save()` ```java new ConfigType<>( , , , , , new ProfileMetadataType(), ); ``` ```json { "config": { "version": 1, "skinmatics": "2.0.0", "utilitary": "1.0.0" } } ``` --- # Migrating Config Be sure to change the version value of your config and add migrations. For each version or change you made, add one migration to your config. ```java public static final ConfigType TYPE; static { NavigableMap migrations = new TreeMap<>(); migrations.put(1, (context) -> { JsonObject object = context.data().getAsJsonObject(); object.add("debugPrinting", object.remove("debug")); return object; }); TYPE = new ConfigType<>( Identifier.fromNamespaceAndPath(Utilitary.ID, "config_type"), 2, CODEC, (_) -> new UtilitaryConfig(false, false), migrations, new SimpleMetadataType() ); } ``` > [!WARNING] > Missing migration version will cause an error ## Migrating Metadata You only need to override `.migrate()` method of `ConfigMetadataType` `SimpleMetadataType` ```java @Override public JsonElement migrate(JsonElement original) { if (original.isJsonObject()) { return original.getAsJsonObject().get("version"); } return original; } ``` --- # Syncing Config Current Utilitary version does not have their own config sync system. You have to create your own network payload and stream codec for this. --- # Config Format Config format (`ConfigFormat`) handles the writing and reading of your config file ```java public interface ConfigFormat { @NonNull JsonElement read(Path path, S formatSettings) throws IOException; void write(Path path, JsonElement element, S formatSettings) throws IOException; @NonNull S create(SimpleIdentifier fileName, ConfigType type); @NonNull String suffix(); } ``` Utilitary only provides two `ConfigFormat`, `JsonConfigFormat` and `Json5ConfigFormat` ## Custom Config Format You can add your own custom config format for other file type by implementing `ConfigFormat`. As long as your implementation is able to handle `JsonElement` classes then you're good to go. ```java public class CustomConfigFormat implements ConfigFormat { public static final CustomConfigFormat INSTANCE = new CustomConfigFormat(); private CustomConfigFormat() {} @Override public @NonNull JsonElement read(Path path, FormatSettings formatSettings) throws IOException { // Read from file } @Override public void write(Path path, JsonElement element, FormatSettings formatSettings) throws IOException { // Write to file } @Override public @NonNull FormatSettings create(SimpleIdentifier fileName, ConfigType type) { // Default FormatSettings return new FormatSettings(); } @Override public @NonNull String suffix() { // File format (.) return ""; } public record FormatSettings() {} } ``` >[!IMPORTANT] >`ConfigFormat.save()` always get the complete `JsonElement` (`data` and `config` already stored) >[!IMPORTANT] >Be sure to return `JsonElement` as `JsonObject` that has `data` and `config`