diff --git a/README.md b/README.md index c97ca5e..763ed9e 100644 --- a/README.md +++ b/README.md @@ -12,33 +12,47 @@ public class Configs { public static String exampleString = "default"; } ``` -Finally, in your mod's `onInitialize(Client)` method, register the `Configs` class. Replace `` with your mod's -id. -```java -new ModConfigBuilder(, Configs.class).build(); -``` -That's it! Now you can access `exampleString` through `Configs.exampleString`. You can edit `exampleString` by executing -the following command. -``` -/(c)config exampleString set -``` +Finally, register your `Configs` class. +- For Fabric users, register the `Configs` class in your mod's `onInitialize(Client)` method. Replace `` with your +mod's id. Sometimes you may omit the generics and just do `<>` instead. + - On clients: + ```java + new ModConfigBuilder(, Configs.class).build(); + ``` + - On servers: + ```java + new ModConfigBuilder(, Configs.class).build(); + ``` +- For Paper users, register the `Configs` class in your plugin's `onEnable` method. Replace `` with your +plugin's name. + ```java + new ModConfigBuilder<>(, Configs.class).build(); + ``` +That's it! Now you can access `exampleString` through `Configs.exampleString`. You can edit `exampleString` by using the +config command. +- On Fabric there are different commands for the client and server. For both, replace `` with your mod's id. + - On clients, execute `/cconfig exampleString set `. + - On servers, execute `/config exampleString set `. +- On Paper servers, execute `/config exampleString set `. Replace `` with your +plugin's name. ## That's not all! -This mod also supports the use of `Collection`s and `Map`s as variable types. These configurations will have the options -`add`, `put` and `remove` available. Moreover, you can define your own (de)serialisers to create configurations with -arbitrary types. To do this, all you have to do is register the (de)serialiser when you build your config. For instance, -to create a variable with type `Block` you can do +This mod also natively supports the use of `Collection`s and `Map`s as variable types. These configurations will have +the options `add`, `put` and `remove` available. Moreover, you can define your own (de)serialisers to create +configurations with arbitrary types. To do this, all you have to do is register the (de)serialiser when you build your +config. For instance, to create configurations with type `Block` you can do ```java -new ModConfigBuilder(, Configs.class) +new ModConfigBuilder<>(, Configs.class) .registerTypeHierarchy(Block.class, new BlockAdapter(), BlockArgumentType::block) .build(); ``` -where `BlockAdapter` extends `TypeAdapter` and `BlockArgumentType` implements `ArgumentType`. See -[these tests](src/testmod/java/dev/xpple/betterconfig) for a complete picture. +where `BlockAdapter` extends `TypeAdapter` and `BlockArgumentType` implements `ArgumentType`. See +[these tests](fabric/src/testmod/java/dev/xpple/betterconfig) for a complete picture. An identical setup for Paper can +be found [here](paper/src/testplugin/java/dev/xpple/betterconfig). Furthermore, you can completely change the behaviour of updating your config values by creating your own methods. Simply add one or more of `setter`, `adder`, `putter` or `remover` as attribute to your `@Config` annotation. A great use for -this would be adding key-pair entries to a `Map` based on a single value. Consider the following configuration. +this would be adding key-value entries to a `Map` based on a single value. Consider the following configuration. ```java @Config(putter = @Config.Putter("none"), adder = @Config.Adder("customMapAdder")) public static Map exampleMapAdder = new HashMap<>(Map.of("a", "A", "b", "B")); @@ -48,7 +62,7 @@ public static void customMapAdder(String string) { ``` The value of `"none"` for the putter indicates that no putter will be available. This way, you can use this `Map` in your code like usual, and add values to it using `/(c)config exampleMapAdder add `. For more details, see -[the JavaDocs for `@Config`](src/main/java/dev/xpple/betterconfig/api/Config.java). +[the JavaDocs for `@Config`](common/src/main/java/dev/xpple/betterconfig/api/Config.java). The parameters of the update method can also be customised. ```java @@ -60,8 +74,13 @@ public static void customTypeAdder(int codepoint) { ``` For putters, there are separate key and value type attributes. +And many more things! For some illustrative examples, see the `Configs` class for both +[Fabric](fabric/src/testmod/java/dev/xpple/betterconfig/Configs.java) and +[Paper](paper/src/testplugin/java/dev/xpple/betterconfig/Configs.java). + ## Installation -Replace `${version}` with the artifact version. +Replace `${version}` with the artifact version. Append `-fabric` for Fabric and `-paper` for Paper to the base artifact +name. You may choose between my own maven repository and GitHub's package repository. ### My own @@ -87,6 +106,9 @@ repositories { Import it: ```gradle dependencies { - include modImplementation('dev.xpple:betterconfig:${betterconfig_version}') + // Fabric + include modImplementation('dev.xpple:betterconfig-fabric:${betterconfig-version}') + // Paper (also include the JAR in the plugins folder) + compileOnly 'dev.xpple:betterconfig-paper:${betterconfig_version}' } ``` diff --git a/build.gradle b/build.gradle deleted file mode 100644 index a06f0f4..0000000 --- a/build.gradle +++ /dev/null @@ -1,132 +0,0 @@ -plugins { - id 'fabric-loom' version '1.6-SNAPSHOT' - id 'maven-publish' -} - -sourceCompatibility = JavaVersion.VERSION_21 -targetCompatibility = JavaVersion.VERSION_21 - -archivesBaseName = project.archives_base_name -version = project.mod_version -group = project.maven_group - -loom { - splitEnvironmentSourceSets() -} - -sourceSets { - testmod { - compileClasspath += main.compileClasspath - runtimeClasspath += main.runtimeClasspath - - compileClasspath += client.compileClasspath - runtimeClasspath += client.runtimeClasspath - } -} - -loom { - mods { - betterconfig { - sourceSet sourceSets.main - sourceSet sourceSets.client - } - testmod { - sourceSet sourceSets.testmod - } - } - - runs { - testmodClient { - client() - name = "Test Mod Client" - source sourceSets.testmod - } - testmodServer { - server() - name = "Test Mod Server" - source sourceSets.testmod - } - } -} - -repositories { - // Add repositories to retrieve artifacts from in here. - // You should only use this when depending on other mods because - // Loom adds the essential maven repositories to download Minecraft and libraries from automatically. - // See https://docs.gradle.org/current/userguide/declaring_repositories.html - // for more information about repositories. -} - -dependencies { - // To change the versions see the gradle.properties file - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings "net.fabricmc:yarn:${project.yarn_mappings}:v2" - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - // Fabric API. This is technically optional, but you probably want it anyway. - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_api_version}" - - // Uncomment the following line to enable the deprecated Fabric API modules. - // These are included in the Fabric API production distribution and allow you to update your mod to the latest modules at a later more convenient time. - - // modImplementation "net.fabricmc.fabric-api:fabric-api-deprecated:${project.fabric_api_version}" -} - -processResources { - inputs.property "version", project.version - - filesMatching("fabric.mod.json") { - expand "version": project.version - } -} - -tasks.withType(JavaCompile).configureEach { - it.options.release = 21 -} - -java { - // Loom will automatically attach sourcesJar to a RemapSourcesJar task and to the "build" task - // if it is present. - // If you remove this line, sources will not be generated. - withSourcesJar() -} - -jar { - from("LICENSE") { - rename {"${it}_${project.archivesBaseName}"} - } -} - -// configure the maven publication -publishing { - publications { - mavenJava(MavenPublication) { - artifactId project.archivesBaseName - from components.java - } - } - - // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. - repositories { - // Add repositories to publish to here. - // Notice: This block does NOT have the same function as the block in the top level. - // The repositories here will be used for publishing your artifact, not for - // retrieving dependencies. - maven { - name = "xpple" - url = "sftp://xpple.dev:22/maven.xpple.dev/httpdocs/maven2" - credentials { - username = System.getenv("MAVEN_USER") - password = System.getenv("MAVEN_PASS") - } - } - maven { - name = "GitHubPackages" - url = "https://maven.pkg.github.com/xpple/BetterConfig" - credentials { - username = System.getenv("GITHUB_ACTOR") - password = System.getenv("GITHUB_TOKEN") - } - } - } -} diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..8ae0d73 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,40 @@ +plugins { + id 'fabric-loom' version '1.7-SNAPSHOT' +} + +base { + archivesName = project.archives_base_name + version = project.mod_version + group = project.maven_group +} + +repositories { + maven { + url = 'https://maven.parchmentmc.org' + } +} + +dependencies { + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.layered { + officialMojangMappings { + nameSyntheticMembers = true + } + parchment "org.parchmentmc.data:${project.parchment_mappings}" + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 21 +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +jar { + from("LICENSE") { + rename {"${it}_${project.base.archivesName.get()}"} + } +} diff --git a/common/src/main/java/dev/xpple/betterconfig/BetterConfigCommon.java b/common/src/main/java/dev/xpple/betterconfig/BetterConfigCommon.java new file mode 100644 index 0000000..607d5a9 --- /dev/null +++ b/common/src/main/java/dev/xpple/betterconfig/BetterConfigCommon.java @@ -0,0 +1,11 @@ +package dev.xpple.betterconfig; + +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApiStatus.Internal +public class BetterConfigCommon { + public static final String MOD_ID = "betterconfig"; + public static final Logger LOGGER = LoggerFactory.getLogger(MOD_ID); +} diff --git a/src/main/java/dev/xpple/betterconfig/api/BetterConfigAPI.java b/common/src/main/java/dev/xpple/betterconfig/api/BetterConfigAPI.java similarity index 99% rename from src/main/java/dev/xpple/betterconfig/api/BetterConfigAPI.java rename to common/src/main/java/dev/xpple/betterconfig/api/BetterConfigAPI.java index decb3eb..f44b54f 100644 --- a/src/main/java/dev/xpple/betterconfig/api/BetterConfigAPI.java +++ b/common/src/main/java/dev/xpple/betterconfig/api/BetterConfigAPI.java @@ -3,7 +3,6 @@ import dev.xpple.betterconfig.impl.BetterConfigImpl; public interface BetterConfigAPI { - static BetterConfigAPI getInstance() { return BetterConfigImpl.INSTANCE; } diff --git a/src/main/java/dev/xpple/betterconfig/api/Config.java b/common/src/main/java/dev/xpple/betterconfig/api/Config.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/api/Config.java rename to common/src/main/java/dev/xpple/betterconfig/api/Config.java diff --git a/src/main/java/dev/xpple/betterconfig/api/ModConfig.java b/common/src/main/java/dev/xpple/betterconfig/api/ModConfig.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/api/ModConfig.java rename to common/src/main/java/dev/xpple/betterconfig/api/ModConfig.java diff --git a/common/src/main/java/dev/xpple/betterconfig/api/ModConfigBuilder.java b/common/src/main/java/dev/xpple/betterconfig/api/ModConfigBuilder.java new file mode 100644 index 0000000..6fa5bbb --- /dev/null +++ b/common/src/main/java/dev/xpple/betterconfig/api/ModConfigBuilder.java @@ -0,0 +1,113 @@ +package dev.xpple.betterconfig.api; + +import com.google.gson.GsonBuilder; +import com.google.gson.TypeAdapter; +import com.mojang.brigadier.arguments.ArgumentType; +import dev.xpple.betterconfig.impl.BetterConfigImpl; +import dev.xpple.betterconfig.impl.BetterConfigInternals; +import dev.xpple.betterconfig.impl.ModConfigImpl; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; + +public final class ModConfigBuilder { + + private final String modId; + + private final Class configsClass; + + private final GsonBuilder builder = new GsonBuilder().serializeNulls().enableComplexMapKeySerialization(); + private final Map, Function>> arguments = new HashMap<>(); + + public ModConfigBuilder(String modId, Class configsClass) { + this.modId = modId; + this.configsClass = configsClass; + } + + /** + * Register a new type adapter and argument type for the specified type. + * @param type the type's class + * @param adapter the type adapter + * @param argumentTypeSupplier a supplier for the argument type + * @param the type + * @return the current builder instance + * @implNote On servers, this requires that the argument type is known to the client. This holds + * true for all argument types that natively exist in the game. Any custom argument types must be + * converted, however. For this, use {@link dev.xpple.betterconfig.util.WrappedArgumentType} on + * Fabric or {@link io.papermc.paper.command.brigadier.argument.CustomArgumentType} on Paper. + * @see ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, Supplier) + */ + public ModConfigBuilder registerType(Class type, TypeAdapter adapter, Supplier> argumentTypeSupplier) { + return this.registerType(type, adapter, buildContext -> argumentTypeSupplier.get()); + } + + /** + * Register a new type adapter and argument type for the specified type. + * @param type the type's class + * @param adapter the type adapter + * @param argumentTypeFunction a function for the argument type needing build context + * @param the type + * @return the current builder instance + * @implNote On servers, this requires that the argument type is known to the client. This holds + * true for all argument types that natively exist in the game. Any custom argument types must be + * converted, however. For this, use {@link dev.xpple.betterconfig.util.WrappedArgumentType} on + * Fabric or {@link io.papermc.paper.command.brigadier.argument.CustomArgumentType} on Paper. + * @see ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, Function) + */ + public ModConfigBuilder registerType(Class type, TypeAdapter adapter, Function> argumentTypeFunction) { + this.builder.registerTypeAdapter(type, adapter); + this.arguments.put(type, argumentTypeFunction); + return this; + } + + /** + * Register a new type adapter and argument type for the specified type and all subclasses. + * @param type the type's class + * @param adapter the type adapter + * @param argumentTypeSupplier a supplier for the argument type + * @param the type + * @return the current builder instance + * @implNote On servers, this requires that the argument type is known to the client. This holds + * true for all argument types that natively exist in the game. Any custom argument types must be + * converted, however. For this, use {@link dev.xpple.betterconfig.util.WrappedArgumentType} on + * Fabric or {@link io.papermc.paper.command.brigadier.argument.CustomArgumentType} on Paper. + * @see ModConfigBuilder#registerType(Class, TypeAdapter, Supplier) + */ + public ModConfigBuilder registerTypeHierarchy(Class type, TypeAdapter adapter, Supplier> argumentTypeSupplier) { + return this.registerTypeHierarchy(type, adapter, buildContext -> argumentTypeSupplier.get()); + } + + /** + * Register a new type adapter and argument type for the specified type and all subclasses. + * @param type the type's class + * @param adapter the type adapter + * @param argumentTypeFunction a function for the argument type needing build context + * @param the type + * @return the current builder instance + * @implNote On servers, this requires that the argument type is known to the client. This holds + * true for all argument types that natively exist in the game. Any custom argument types must be + * converted, however. For this, use {@link dev.xpple.betterconfig.util.WrappedArgumentType} on + * Fabric or {@link io.papermc.paper.command.brigadier.argument.CustomArgumentType} on Paper. + * @see ModConfigBuilder#registerType(Class, TypeAdapter, Function) + */ + public ModConfigBuilder registerTypeHierarchy(Class type, TypeAdapter adapter, Function> argumentTypeFunction) { + this.builder.registerTypeHierarchyAdapter(type, adapter); + this.arguments.put(type, argumentTypeFunction); + return this; + } + + /** + * Finalise the registration process. + * @throws IllegalArgumentException when a configuration already exists for this mod + */ + public void build() { + ModConfigImpl modConfig = new ModConfigImpl<>(this.modId, this.configsClass, this.builder.create(), this.arguments); + if (BetterConfigImpl.getModConfigs().putIfAbsent(this.modId, modConfig) == null) { + BetterConfigInternals.init(modConfig); + return; + } + throw new IllegalArgumentException(this.modId); + } +} diff --git a/common/src/main/java/dev/xpple/betterconfig/command/AbstractConfigCommand.java b/common/src/main/java/dev/xpple/betterconfig/command/AbstractConfigCommand.java new file mode 100644 index 0000000..8f5f65d --- /dev/null +++ b/common/src/main/java/dev/xpple/betterconfig/command/AbstractConfigCommand.java @@ -0,0 +1,165 @@ +package dev.xpple.betterconfig.command; + +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.xpple.betterconfig.api.Config; +import dev.xpple.betterconfig.impl.ModConfigImpl; +import dev.xpple.betterconfig.impl.Platform; +import dev.xpple.betterconfig.util.CheckedFunction; + +import java.lang.reflect.Type; +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Predicate; + +import static com.mojang.brigadier.arguments.StringArgumentType.*; + +public abstract class AbstractConfigCommand { + + private final String rootLiteral; + + protected AbstractConfigCommand(String rootLiteral) { + this.rootLiteral = rootLiteral; + } + + protected final LiteralArgumentBuilder create(Collection> modConfigs, C buildContext) { + LiteralArgumentBuilder root = LiteralArgumentBuilder.literal(this.rootLiteral); + for (ModConfigImpl modConfig : modConfigs) { + LiteralArgumentBuilder identifierLiteral = LiteralArgumentBuilder.literal(modConfig.getModId()); + for (String config : modConfig.getConfigs().keySet()) { + Predicate condition = modConfig.getConditions().get(config); + LiteralArgumentBuilder configLiteral = LiteralArgumentBuilder.literal(config).requires(condition); + + configLiteral.then(LiteralArgumentBuilder.literal("get").executes(ctx -> get(ctx.getSource(), modConfig, config))); + configLiteral.then(LiteralArgumentBuilder.literal("reset").executes(ctx -> reset(ctx.getSource(), modConfig, config))); + + String comment = modConfig.getComments().get(config); + if (comment != null) { + configLiteral.then(LiteralArgumentBuilder.literal("comment").executes(ctx -> comment(ctx.getSource(), config, comment))); + } + + if (modConfig.getSetters().containsKey(config)) { + Config annotation = modConfig.getAnnotations().get(config); + Config.Setter setter = annotation.setter(); + Class type = setter.type() == Config.EMPTY.class ? modConfig.getType(config) : setter.type(); + var argumentFunction = modConfig.getArgument(type); + if (argumentFunction != null) { + RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", argumentFunction.apply(buildContext)); + subCommand.executes(ctx -> set(ctx.getSource(), modConfig, config, ctx.getArgument("value", type))); + configLiteral.then(LiteralArgumentBuilder.literal("set").then(subCommand)); + } else if (type.isEnum()) { + //noinspection rawtypes, unchecked + RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", string()).suggests(Platform.current.enumSuggestionProvider((Class) type)); + subCommand.executes(ctx -> { + String value = getString(ctx, "value"); + return set(ctx.getSource(), modConfig, config, Arrays.stream(type.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> Platform.current.invalidEnumException().create(value))); + }); + configLiteral.then(LiteralArgumentBuilder.literal("set").then(subCommand)); + } + } + + if (modConfig.getAdders().containsKey(config)) { + Config annotation = modConfig.getAnnotations().get(config); + Config.Adder adder = annotation.adder(); + Class type = adder.type() == Config.EMPTY.class ? (Class) modConfig.getParameterTypes(config)[0] : adder.type(); + var argumentFunction = modConfig.getArgument(type); + if (argumentFunction != null) { + RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", argumentFunction.apply(buildContext)); + subCommand.executes(ctx -> add(ctx.getSource(), modConfig, config, ctx.getArgument("value", type))); + configLiteral.then(LiteralArgumentBuilder.literal("add").then(subCommand)); + } else if (type.isEnum()) { + // noinspection rawtypes, unchecked + RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", string()).suggests(Platform.current.enumSuggestionProvider((Class) type)); + subCommand.executes(ctx -> { + String value = getString(ctx, "value"); + return add(ctx.getSource(), modConfig, config, Arrays.stream(type.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> Platform.current.invalidEnumException().create(value))); + }); + configLiteral.then(LiteralArgumentBuilder.literal("add").then(subCommand)); + } + } + + if (modConfig.getPutters().containsKey(config)) { + Config annotation = modConfig.getAnnotations().get(config); + Config.Putter putter = annotation.putter(); + Type[] types = modConfig.getParameterTypes(config); + Class keyType = putter.keyType() == Config.EMPTY.class ? (Class) types[0] : putter.keyType(); + RequiredArgumentBuilder subCommand; + CheckedFunction, ?, CommandSyntaxException> getKey; + var keyArgumentFunction = modConfig.getArgument(keyType); + if (keyArgumentFunction != null) { + subCommand = RequiredArgumentBuilder.argument("key", keyArgumentFunction.apply(buildContext)); + getKey = ctx -> ctx.getArgument("key", keyType); + } else if (keyType.isEnum()) { + //noinspection rawtypes, unchecked + subCommand = RequiredArgumentBuilder.argument("key", string()).suggests(Platform.current.enumSuggestionProvider((Class) keyType)); + getKey = ctx -> { + String value = getString(ctx, "key"); + return Arrays.stream(keyType.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> Platform.current.invalidEnumException().create(value)); + }; + } else { + subCommand = null; + getKey = null; + } + if (subCommand != null) { + Class valueType = putter.valueType() == Config.EMPTY.class ? (Class) types[1] : putter.valueType(); + var valueArgumentFunction = modConfig.getArgument(valueType); + if (valueArgumentFunction != null) { + RequiredArgumentBuilder subSubCommand = RequiredArgumentBuilder.argument("value", valueArgumentFunction.apply(buildContext)); + subSubCommand.executes(ctx -> put(ctx.getSource(), modConfig, config, getKey.apply(ctx), ctx.getArgument("value", valueType))); + configLiteral.then(LiteralArgumentBuilder.literal("put").then(subCommand.then(subSubCommand))); + } else if (valueType.isEnum()) { + //noinspection rawtypes, unchecked + RequiredArgumentBuilder subSubCommand = RequiredArgumentBuilder.argument("value", string()).suggests(Platform.current.enumSuggestionProvider((Class) valueType)); + subCommand.executes(ctx -> { + String value = getString(ctx, "value"); + return put(ctx.getSource(), modConfig, config, getKey.apply(ctx), Arrays.stream(valueType.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> Platform.current.invalidEnumException().create(value))); + }); + configLiteral.then(LiteralArgumentBuilder.literal("put").then(subCommand.then(subSubCommand))); + } + } + } + + if (modConfig.getRemovers().containsKey(config)) { + Config annotation = modConfig.getAnnotations().get(config); + Config.Remover remover = annotation.remover(); + Class type = remover.type() == Config.EMPTY.class ? (Class) modConfig.getParameterTypes(config)[0] : remover.type(); + var argumentFunction = modConfig.getArgument(type); + if (argumentFunction != null) { + RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", argumentFunction.apply(buildContext)); + subCommand.executes(ctx -> remove(ctx.getSource(), modConfig, config, ctx.getArgument("value", type))); + configLiteral.then(LiteralArgumentBuilder.literal("remove").then(subCommand)); + } else if (type.isEnum()) { + //noinspection rawtypes, unchecked + RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", string()).suggests(Platform.current.enumSuggestionProvider((Class) type)); + subCommand.executes(ctx -> { + String value = getString(ctx, "value"); + return remove(ctx.getSource(), modConfig, config, Arrays.stream(type.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> Platform.current.invalidEnumException().create(value))); + }); + configLiteral.then(LiteralArgumentBuilder.literal("remove").then(subCommand)); + } + } + + identifierLiteral.then(configLiteral); + } + + root.then(identifierLiteral); + } + return root; + } + + protected abstract int comment(S source, String config, String comment); + + protected abstract int get(S source, ModConfigImpl modConfig, String config); + + protected abstract int reset(S source, ModConfigImpl modConfig, String config); + + protected abstract int set(S source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException; + + protected abstract int add(S source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException; + + protected abstract int put(S source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException; + + protected abstract int remove(S source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException; +} diff --git a/src/main/java/dev/xpple/betterconfig/command/package-info.java b/common/src/main/java/dev/xpple/betterconfig/command/package-info.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/command/package-info.java rename to common/src/main/java/dev/xpple/betterconfig/command/package-info.java diff --git a/src/main/java/dev/xpple/betterconfig/impl/BetterConfigImpl.java b/common/src/main/java/dev/xpple/betterconfig/impl/BetterConfigImpl.java similarity index 79% rename from src/main/java/dev/xpple/betterconfig/impl/BetterConfigImpl.java rename to common/src/main/java/dev/xpple/betterconfig/impl/BetterConfigImpl.java index 9e83d6f..e5d7835 100644 --- a/src/main/java/dev/xpple/betterconfig/impl/BetterConfigImpl.java +++ b/common/src/main/java/dev/xpple/betterconfig/impl/BetterConfigImpl.java @@ -8,7 +8,7 @@ public class BetterConfigImpl implements BetterConfigAPI { - private static final Map modConfigs = new HashMap<>(); + private static final Map> modConfigs = new HashMap<>(); @Override public ModConfig getModConfig(String modId) { @@ -19,7 +19,7 @@ public ModConfig getModConfig(String modId) { return modConfig; } - public static Map getModConfigs() { + public static Map> getModConfigs() { return modConfigs; } diff --git a/src/main/java/dev/xpple/betterconfig/impl/BetterConfigInternals.java b/common/src/main/java/dev/xpple/betterconfig/impl/BetterConfigInternals.java similarity index 93% rename from src/main/java/dev/xpple/betterconfig/impl/BetterConfigInternals.java rename to common/src/main/java/dev/xpple/betterconfig/impl/BetterConfigInternals.java index 7b1773b..73cdf3b 100644 --- a/src/main/java/dev/xpple/betterconfig/impl/BetterConfigInternals.java +++ b/common/src/main/java/dev/xpple/betterconfig/impl/BetterConfigInternals.java @@ -3,31 +3,32 @@ import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.xpple.betterconfig.BetterConfigCommon; import dev.xpple.betterconfig.api.Config; -import net.minecraft.command.CommandSource; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; -import java.lang.reflect.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Collection; import java.util.Map; import java.util.Objects; -import static dev.xpple.betterconfig.BetterConfig.LOGGER; - public class BetterConfigInternals { - public static void init(ModConfigImpl modConfig) { + public static void init(ModConfigImpl modConfig) { JsonObject root = null; try (BufferedReader reader = Files.newBufferedReader(modConfig.getConfigsPath())) { root = JsonParser.parseReader(reader).getAsJsonObject(); } catch (IOException ignored) { } catch (Exception e) { - LOGGER.warn("Could not read config file, default values will be used."); - LOGGER.warn("The old config file will be renamed."); + BetterConfigCommon.LOGGER.warn("Could not read config file, default values will be used.\nThe old config file will be renamed.", e); try { Files.move(modConfig.getConfigsPath(), modConfig.getConfigsPath().resolveSibling("config_old.json"), StandardCopyOption.REPLACE_EXISTING); } catch (IOException ignored) { @@ -83,7 +84,8 @@ public static void init(ModConfigImpl modConfig) { } catch (ReflectiveOperationException e) { hasParameter = true; try { - predicateMethod = modConfig.getConfigsClass().getDeclaredMethod(annotation.condition(), CommandSource.class); + Class commandSourceClass = Platform.current.getCommandSourceClass(); + predicateMethod = modConfig.getConfigsClass().getDeclaredMethod(annotation.condition(), commandSourceClass); } catch (ReflectiveOperationException e1) { throw new AssertionError(e1); } @@ -135,12 +137,11 @@ public static void init(ModConfigImpl modConfig) { try (BufferedWriter writer = Files.newBufferedWriter(modConfig.getConfigsPath())) { writer.write(modConfig.getGson().toJson(root)); } catch (IOException e) { - LOGGER.error("Could not save config file."); - e.printStackTrace(); + BetterConfigCommon.LOGGER.error("Could not save config file.", e); } } - private static void initCollection(ModConfigImpl modConfig, Field field, Config annotation) { + private static void initCollection(ModConfigImpl modConfig, Field field, Config annotation) { String fieldName = field.getName(); Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments(); Config.Adder adder = annotation.adder(); @@ -221,7 +222,7 @@ private static void initCollection(ModConfigImpl modConfig, Field field, Config } } - private static void initMap(ModConfigImpl modConfig, Field field, Config annotation) { + private static void initMap(ModConfigImpl modConfig, Field field, Config annotation) { String fieldName = field.getName(); Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments(); Config.Adder adder = annotation.adder(); @@ -327,7 +328,7 @@ private static void initMap(ModConfigImpl modConfig, Field field, Config annotat } } - private static void initObject(ModConfigImpl modConfig, Field field, Config annotation) { + private static void initObject(ModConfigImpl modConfig, Field field, Config annotation) { String fieldName = field.getName(); Config.Setter setter = annotation.setter(); String setterMethodName = setter.value(); diff --git a/src/main/java/dev/xpple/betterconfig/impl/ModConfigImpl.java b/common/src/main/java/dev/xpple/betterconfig/impl/ModConfigImpl.java similarity index 70% rename from src/main/java/dev/xpple/betterconfig/impl/ModConfigImpl.java rename to common/src/main/java/dev/xpple/betterconfig/impl/ModConfigImpl.java index 24435b8..0010f66 100644 --- a/src/main/java/dev/xpple/betterconfig/impl/ModConfigImpl.java +++ b/common/src/main/java/dev/xpple/betterconfig/impl/ModConfigImpl.java @@ -3,18 +3,19 @@ import com.google.common.collect.ImmutableMap; import com.google.gson.Gson; import com.google.gson.JsonObject; -import com.mojang.brigadier.arguments.*; -import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.BoolArgumentType; +import com.mojang.brigadier.arguments.DoubleArgumentType; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.LongArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import dev.xpple.betterconfig.api.Config; +import dev.xpple.betterconfig.BetterConfigCommon; import dev.xpple.betterconfig.api.ModConfig; +import dev.xpple.betterconfig.api.Config; import dev.xpple.betterconfig.util.CheckedBiConsumer; -import dev.xpple.betterconfig.util.CheckedBiFunction; import dev.xpple.betterconfig.util.CheckedConsumer; -import dev.xpple.betterconfig.util.Pair; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.CommandSource; import java.io.BufferedWriter; import java.io.IOException; @@ -28,23 +29,20 @@ import java.util.function.Function; import java.util.function.Predicate; -import static dev.xpple.betterconfig.BetterConfig.LOGGER; -import static dev.xpple.betterconfig.BetterConfig.MOD_PATH; - -public class ModConfigImpl implements ModConfig { - - private static final Map, Function>> defaultArguments = ImmutableMap., Function>>builder() - .put(boolean.class, registryAccess -> BoolArgumentType.bool()) - .put(Boolean.class, registryAccess -> BoolArgumentType.bool()) - .put(double.class, registryAccess -> DoubleArgumentType.doubleArg()) - .put(Double.class, registryAccess -> DoubleArgumentType.doubleArg()) - .put(float.class, registryAccess -> FloatArgumentType.floatArg()) - .put(Float.class, registryAccess -> FloatArgumentType.floatArg()) - .put(int.class, registryAccess -> IntegerArgumentType.integer()) - .put(Integer.class, registryAccess -> IntegerArgumentType.integer()) - .put(long.class, registryAccess -> LongArgumentType.longArg()) - .put(Long.class, registryAccess -> LongArgumentType.longArg()) - .put(String.class, registryAccess -> StringArgumentType.string()) +public class ModConfigImpl implements ModConfig { + + private static final Map, Function>> defaultArguments = ImmutableMap., Function>>builder() + .put(boolean.class, buildContext -> BoolArgumentType.bool()) + .put(Boolean.class, buildContext -> BoolArgumentType.bool()) + .put(double.class, buildContext -> DoubleArgumentType.doubleArg()) + .put(Double.class, buildContext -> DoubleArgumentType.doubleArg()) + .put(float.class, buildContext -> FloatArgumentType.floatArg()) + .put(Float.class, buildContext -> FloatArgumentType.floatArg()) + .put(int.class, buildContext -> IntegerArgumentType.integer()) + .put(Integer.class, buildContext -> IntegerArgumentType.integer()) + .put(long.class, buildContext -> LongArgumentType.longArg()) + .put(Long.class, buildContext -> LongArgumentType.longArg()) + .put(String.class, buildContext -> StringArgumentType.string()) .build(); private final String modId; @@ -52,16 +50,14 @@ public class ModConfigImpl implements ModConfig { private final Gson gson; private final Gson inlineGson; - private final Map, Function>> arguments; - private final Map, Pair, CheckedBiFunction, String, ?, CommandSyntaxException>>> suggestors; + private final Map, Function>> arguments; - public ModConfigImpl(String modId, Class configsClass, Gson gson, Map, Function>> arguments, Map, Pair, CheckedBiFunction, String, ?, CommandSyntaxException>>> suggestors) { + public ModConfigImpl(String modId, Class configsClass, Gson gson, Map, Function>> arguments) { this.modId = modId; this.configsClass = configsClass; this.gson = gson.newBuilder().setPrettyPrinting().create(); this.inlineGson = gson; this.arguments = arguments; - this.suggestors = suggestors; } @Override @@ -78,17 +74,14 @@ public Gson getGson() { return this.gson; } - public Function> getArgument(Class type) { - return this.arguments.getOrDefault(type, defaultArguments.get(type)); - } - - public Pair, CheckedBiFunction, String, ?, CommandSyntaxException>> getSuggestor(Class type) { - return this.suggestors.get(type); + @SuppressWarnings("unchecked") + public Function> getArgument(Class type) { + return this.arguments.getOrDefault(type, (Function>) defaultArguments.get(type)); } @Override public Path getConfigsPath() { - return MOD_PATH.resolve(this.modId).resolve("config.json"); + return Platform.current.getConfigsPath(this.modId); } @Override @@ -206,8 +199,7 @@ public boolean save() { }); writer.write(this.gson.toJson(root)); } catch (IOException e) { - LOGGER.error("Could not save config file."); - e.printStackTrace(); + BetterConfigCommon.LOGGER.error("Could not save config file.", e); return false; } return true; @@ -241,7 +233,7 @@ public Map> getRemovers( return this.removers; } - public Map> getConditions() { + public Map> getConditions() { return this.conditions; } @@ -256,6 +248,6 @@ public Map getAnnotations() { private final Map> adders = new HashMap<>(); private final Map> putters = new HashMap<>(); private final Map> removers = new HashMap<>(); - private final Map> conditions = new HashMap<>(); + private final Map> conditions = new HashMap<>(); private final Map annotations = new HashMap<>(); } diff --git a/common/src/main/java/dev/xpple/betterconfig/impl/Platform.java b/common/src/main/java/dev/xpple/betterconfig/impl/Platform.java new file mode 100644 index 0000000..67ecb14 --- /dev/null +++ b/common/src/main/java/dev/xpple/betterconfig/impl/Platform.java @@ -0,0 +1,19 @@ +package dev.xpple.betterconfig.impl; + +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.SuggestionProvider; + +import java.nio.file.Path; +import java.util.ServiceLoader; + +public interface Platform { + Path getConfigsPath(String modId); + + Class getCommandSourceClass(); + + DynamicCommandExceptionType invalidEnumException(); + + > SuggestionProvider enumSuggestionProvider(Class type); + + Platform current = ServiceLoader.load(Platform.class, Platform.class.getClassLoader()).iterator().next(); +} diff --git a/src/main/java/dev/xpple/betterconfig/impl/package-info.java b/common/src/main/java/dev/xpple/betterconfig/impl/package-info.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/impl/package-info.java rename to common/src/main/java/dev/xpple/betterconfig/impl/package-info.java diff --git a/src/main/java/dev/xpple/betterconfig/package-info.java b/common/src/main/java/dev/xpple/betterconfig/package-info.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/package-info.java rename to common/src/main/java/dev/xpple/betterconfig/package-info.java diff --git a/src/main/java/dev/xpple/betterconfig/util/CheckedBiConsumer.java b/common/src/main/java/dev/xpple/betterconfig/util/CheckedBiConsumer.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/util/CheckedBiConsumer.java rename to common/src/main/java/dev/xpple/betterconfig/util/CheckedBiConsumer.java diff --git a/src/main/java/dev/xpple/betterconfig/util/CheckedBiFunction.java b/common/src/main/java/dev/xpple/betterconfig/util/CheckedBiFunction.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/util/CheckedBiFunction.java rename to common/src/main/java/dev/xpple/betterconfig/util/CheckedBiFunction.java diff --git a/src/main/java/dev/xpple/betterconfig/util/CheckedConsumer.java b/common/src/main/java/dev/xpple/betterconfig/util/CheckedConsumer.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/util/CheckedConsumer.java rename to common/src/main/java/dev/xpple/betterconfig/util/CheckedConsumer.java diff --git a/src/main/java/dev/xpple/betterconfig/util/CheckedFunction.java b/common/src/main/java/dev/xpple/betterconfig/util/CheckedFunction.java similarity index 100% rename from src/main/java/dev/xpple/betterconfig/util/CheckedFunction.java rename to common/src/main/java/dev/xpple/betterconfig/util/CheckedFunction.java diff --git a/fabric/build.gradle b/fabric/build.gradle new file mode 100644 index 0000000..0d0cc69 --- /dev/null +++ b/fabric/build.gradle @@ -0,0 +1,144 @@ +plugins { + id 'fabric-loom' version '1.7-SNAPSHOT' + id 'maven-publish' +} + +base { + archivesName = "${project.archives_base_name}-fabric" + version = project.mod_version + group = project.maven_group +} + +loom { + splitEnvironmentSourceSets() +} + +sourceSets { + testmod { + compileClasspath += main.compileClasspath + runtimeClasspath += main.runtimeClasspath + + compileClasspath += client.compileClasspath + runtimeClasspath += client.runtimeClasspath + } +} + +loom { + mods { + betterconfig { + sourceSet sourceSets.main + sourceSet sourceSets.client + } + testmod { + sourceSet sourceSets.testmod + } + } + + runs { + testmodClient { + client() + name = "Test Mod Client" + source sourceSets.testmod + } + testmodServer { + server() + name = "Test Mod Server" + source sourceSets.testmod + } + } + + runConfigs.configureEach { + ideConfigGenerated = true + } +} + +repositories { + maven { + url = 'https://maven.parchmentmc.org' + } +} + +configurations { + includeInJar +} + +dependencies { + configurations.compileOnly.extendsFrom(configurations.includeInJar) + configurations.localRuntime.extendsFrom(configurations.includeInJar) + includeInJar project(':common') + + // To change the versions see the gradle.properties file + minecraft "com.mojang:minecraft:${project.minecraft_version}" + mappings loom.layered { + officialMojangMappings { + nameSyntheticMembers = true + } + parchment "org.parchmentmc.data:${project.parchment_mappings}" + } + modImplementation "net.fabricmc:fabric-loader:${project.fabric_loader_version}" + + modImplementation fabricApi.module("fabric-command-api-v2", project.fabric_api_version) +} + +processResources { + def props = [version: project.mod_version, minecraft_version: project.minecraft_version, fabric_loader_version: project.fabric_loader_version] + inputs.properties props + filesMatching("fabric.mod.json") { + expand props + } +} + +tasks.withType(JavaCompile).configureEach { + it.options.release = 21 +} + +java { + sourceCompatibility = JavaVersion.VERSION_21 + targetCompatibility = JavaVersion.VERSION_21 +} + +jar { + from("LICENSE") { + rename {"${it}_${project.base.archivesName.get()}"} + } + dependsOn ':common:remapJar' + from { + configurations.includeInJar.collect { + it.isDirectory() ? it : zipTree(it) + } + } +} + +// configure the maven publication +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.base.archivesName.get() + from components.java + } + } + + // See https://docs.gradle.org/current/userguide/publishing_maven.html for information on how to set up publishing. + repositories { + // Add repositories to publish to here. + // Notice: This block does NOT have the same function as the block in the top level. + // The repositories here will be used for publishing your artifact, not for + // retrieving dependencies. + maven { + name = "xpple" + url = "sftp://xpple.dev:22/maven.xpple.dev/httpdocs/maven2" + credentials { + username = System.getenv("MAVEN_USER") + password = System.getenv("MAVEN_PASS") + } + } + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/xpple/BetterConfig" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} diff --git a/src/client/java/dev/xpple/betterconfig/BetterConfigClient.java b/fabric/src/client/java/dev/xpple/betterconfig/BetterConfigClient.java similarity index 77% rename from src/client/java/dev/xpple/betterconfig/BetterConfigClient.java rename to fabric/src/client/java/dev/xpple/betterconfig/BetterConfigClient.java index 286172b..349a5b2 100644 --- a/src/client/java/dev/xpple/betterconfig/BetterConfigClient.java +++ b/fabric/src/client/java/dev/xpple/betterconfig/BetterConfigClient.java @@ -5,7 +5,7 @@ import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.command.CommandRegistryAccess; +import net.minecraft.commands.CommandBuildContext; public class BetterConfigClient implements ClientModInitializer { @@ -14,7 +14,7 @@ public void onInitializeClient() { ClientCommandRegistrationCallback.EVENT.register(BetterConfigClient::registerCommands); } - private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { - ConfigCommandClient.register(dispatcher, registryAccess); + private static void registerCommands(CommandDispatcher dispatcher, CommandBuildContext buildContext) { + ConfigCommandClient.register(dispatcher, buildContext); } } diff --git a/fabric/src/client/java/dev/xpple/betterconfig/command/client/ConfigCommandClient.java b/fabric/src/client/java/dev/xpple/betterconfig/command/client/ConfigCommandClient.java new file mode 100644 index 0000000..48653d2 --- /dev/null +++ b/fabric/src/client/java/dev/xpple/betterconfig/command/client/ConfigCommandClient.java @@ -0,0 +1,71 @@ +package dev.xpple.betterconfig.command.client; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.xpple.betterconfig.command.AbstractConfigCommand; +import dev.xpple.betterconfig.impl.BetterConfigImpl; +import dev.xpple.betterconfig.impl.ModConfigImpl; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.network.chat.Component; + +public class ConfigCommandClient extends AbstractConfigCommand { + + private ConfigCommandClient() { + super("cconfig"); + } + + @SuppressWarnings("unchecked") + public static void register(CommandDispatcher dispatcher, CommandBuildContext buildContext) { + dispatcher.register(new ConfigCommandClient().create(BetterConfigImpl.getModConfigs().values().stream().map(modConfig -> (ModConfigImpl) modConfig).toList(), buildContext)); + } + + @Override + protected int comment(FabricClientCommandSource source, String config, String comment) { + source.sendFeedback(Component.translatable("betterconfig.commands.config.comment", config)); + source.sendFeedback(Component.literal(comment)); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int get(FabricClientCommandSource source, ModConfigImpl modConfig, String config) { + source.sendFeedback(Component.translatable("betterconfig.commands.config.get", config, modConfig.asString(config))); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int reset(FabricClientCommandSource source, ModConfigImpl modConfig, String config) { + modConfig.reset(config); + source.sendFeedback(Component.translatable("betterconfig.commands.config.reset", config, modConfig.asString(config))); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int set(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.set(config, value); + source.sendFeedback(Component.translatable("betterconfig.commands.config.set", config, modConfig.asString(config))); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int add(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.add(config, value); + source.sendFeedback(Component.translatable("betterconfig.commands.config.add", modConfig.asString(value), config)); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int put(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException { + modConfig.put(config, key, value); + source.sendFeedback(Component.translatable("betterconfig.commands.config.put", modConfig.asString(key), modConfig.asString(value), config)); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int remove(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.remove(config, value); + source.sendFeedback(Component.translatable("betterconfig.commands.config.remove", modConfig.asString(value), config)); + return Command.SINGLE_SUCCESS; + } +} diff --git a/src/client/resources/betterconfig.mixins.client.json b/fabric/src/client/resources/betterconfig.mixins.client.json similarity index 100% rename from src/client/resources/betterconfig.mixins.client.json rename to fabric/src/client/resources/betterconfig.mixins.client.json diff --git a/src/main/java/dev/xpple/betterconfig/BetterConfig.java b/fabric/src/main/java/dev/xpple/betterconfig/BetterConfig.java similarity index 50% rename from src/main/java/dev/xpple/betterconfig/BetterConfig.java rename to fabric/src/main/java/dev/xpple/betterconfig/BetterConfig.java index 7fc3c9c..db9675e 100644 --- a/src/main/java/dev/xpple/betterconfig/BetterConfig.java +++ b/fabric/src/main/java/dev/xpple/betterconfig/BetterConfig.java @@ -5,27 +5,22 @@ import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.fabric.api.command.v2.CommandRegistrationCallback; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.server.command.CommandManager; -import net.minecraft.server.command.ServerCommandSource; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; import java.nio.file.Path; public class BetterConfig implements DedicatedServerModInitializer { - public static final String MOD_ID = "betterconfig"; public static final Path MOD_PATH = FabricLoader.getInstance().getConfigDir(); - public static final Logger LOGGER = LogManager.getLogger(MOD_ID); - @Override public void onInitializeServer() { CommandRegistrationCallback.EVENT.register(BetterConfig::registerCommands); } - private static void registerCommands(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess, CommandManager.RegistrationEnvironment environment) { - ConfigCommand.register(dispatcher, registryAccess); + private static void registerCommands(CommandDispatcher dispatcher, CommandBuildContext buildContext, Commands.CommandSelection selection) { + ConfigCommand.register(dispatcher, buildContext); } } diff --git a/fabric/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java b/fabric/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java new file mode 100644 index 0000000..e7b0509 --- /dev/null +++ b/fabric/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java @@ -0,0 +1,70 @@ +package dev.xpple.betterconfig.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.xpple.betterconfig.impl.BetterConfigImpl; +import dev.xpple.betterconfig.impl.ModConfigImpl; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; + +public class ConfigCommand extends AbstractConfigCommand { + + private ConfigCommand() { + super("config"); + } + + @SuppressWarnings("unchecked") + public static void register(CommandDispatcher dispatcher, CommandBuildContext buildContext) { + dispatcher.register(new ConfigCommand().create(BetterConfigImpl.getModConfigs().values().stream().map(modConfig -> (ModConfigImpl) modConfig).toList(), buildContext).requires(source -> source.hasPermission(4))); + } + + @Override + protected int comment(CommandSourceStack source, String config, String comment) { + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.comment", "Comment for %s:", config), false); + source.sendSuccess(() -> Component.literal(comment), false); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int get(CommandSourceStack source, ModConfigImpl modConfig, String config) { + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.get", "%s is currently set to %s.", config, modConfig.asString(config)), false); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int reset(CommandSourceStack source, ModConfigImpl modConfig, String config) { + modConfig.reset(config); + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.reset", "%s has been reset to %s.", config, modConfig.asString(config)), true); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int set(CommandSourceStack source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.set(config, value); + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.set", "%s has been set to %s.", config, modConfig.asString(config)), true); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int add(CommandSourceStack source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.add(config, value); + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.add", "%s has been added to %s.", modConfig.asString(value), config), true); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int put(CommandSourceStack source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException { + modConfig.put(config, key, value); + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.put", "The mapping %s=%s has been added to %s.", modConfig.asString(key), modConfig.asString(value), config), true); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int remove(CommandSourceStack source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.remove(config, value); + source.sendSuccess(() -> Component.translatableWithFallback("betterconfig.commands.config.remove", "%s has been removed from %s.", modConfig.asString(value), config), true); + return Command.SINGLE_SUCCESS; + } +} diff --git a/fabric/src/main/java/dev/xpple/betterconfig/impl/FabricPlatform.java b/fabric/src/main/java/dev/xpple/betterconfig/impl/FabricPlatform.java new file mode 100644 index 0000000..1f40b95 --- /dev/null +++ b/fabric/src/main/java/dev/xpple/betterconfig/impl/FabricPlatform.java @@ -0,0 +1,32 @@ +package dev.xpple.betterconfig.impl; + +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.xpple.betterconfig.BetterConfig; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.chat.Component; + +import java.nio.file.Path; +import java.util.Arrays; + +public class FabricPlatform implements Platform { + @Override + public Path getConfigsPath(String modId) { + return BetterConfig.MOD_PATH.resolve(modId).resolve("config.json"); + } + + @Override + public Class getCommandSourceClass() { + return SharedSuggestionProvider.class; + } + + @Override + public DynamicCommandExceptionType invalidEnumException() { + return new DynamicCommandExceptionType(value -> Component.translatable("argument.enum.invalid", value)); + } + + @Override + public > SuggestionProvider enumSuggestionProvider(Class type) { + return (context, builder) -> SharedSuggestionProvider.suggest(Arrays.stream(type.getEnumConstants()).map(Enum::name), builder); + } +} diff --git a/fabric/src/main/java/dev/xpple/betterconfig/mixin/MixinCommands.java b/fabric/src/main/java/dev/xpple/betterconfig/mixin/MixinCommands.java new file mode 100644 index 0000000..724a438 --- /dev/null +++ b/fabric/src/main/java/dev/xpple/betterconfig/mixin/MixinCommands.java @@ -0,0 +1,26 @@ +package dev.xpple.betterconfig.mixin; + +import com.llamalad7.mixinextras.sugar.Local; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.tree.CommandNode; +import dev.xpple.betterconfig.util.WrappedArgumentType; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.Commands; +import net.minecraft.commands.SharedSuggestionProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.util.Map; + +@Mixin(Commands.class) +public abstract class MixinCommands { + @Inject(method = "fillUsableCommands", at = @At(value = "INVOKE", target = "Lcom/mojang/brigadier/builder/RequiredArgumentBuilder;getSuggestionsProvider()Lcom/mojang/brigadier/suggestion/SuggestionProvider;", ordinal = 0), remap = false) + private void replace(CommandNode rootCommandSource, CommandNode rootSuggestion, CommandSourceStack source, Map, CommandNode> commandNodeToSuggestionNode, CallbackInfo ci, @Local RequiredArgumentBuilder requiredArgumentBuilder) { + if (requiredArgumentBuilder.getType() instanceof WrappedArgumentType wrappedArgumentType) { + ((RequiredArgumentBuilderAccessor) requiredArgumentBuilder).setSuggestionsProvider(wrappedArgumentType::listSuggestions); + ((RequiredArgumentBuilderAccessor) requiredArgumentBuilder).setType(wrappedArgumentType.getNativeType()); + } + } +} diff --git a/fabric/src/main/java/dev/xpple/betterconfig/mixin/RequiredArgumentBuilderAccessor.java b/fabric/src/main/java/dev/xpple/betterconfig/mixin/RequiredArgumentBuilderAccessor.java new file mode 100644 index 0000000..a6368b1 --- /dev/null +++ b/fabric/src/main/java/dev/xpple/betterconfig/mixin/RequiredArgumentBuilderAccessor.java @@ -0,0 +1,18 @@ +package dev.xpple.betterconfig.mixin; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Mutable; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(value = RequiredArgumentBuilder.class, remap = false) +public interface RequiredArgumentBuilderAccessor { + @Mutable + @Accessor + void setType(ArgumentType type); + + @Accessor + void setSuggestionsProvider(SuggestionProvider suggestionsProvider); +} diff --git a/src/main/java/dev/xpple/betterconfig/command/suggestion/package-info.java b/fabric/src/main/java/dev/xpple/betterconfig/mixin/package-info.java similarity index 56% rename from src/main/java/dev/xpple/betterconfig/command/suggestion/package-info.java rename to fabric/src/main/java/dev/xpple/betterconfig/mixin/package-info.java index 91f7912..96b320a 100644 --- a/src/main/java/dev/xpple/betterconfig/command/suggestion/package-info.java +++ b/fabric/src/main/java/dev/xpple/betterconfig/mixin/package-info.java @@ -1,4 +1,4 @@ @ApiStatus.Internal -package dev.xpple.betterconfig.command.suggestion; +package dev.xpple.betterconfig.mixin; import org.jetbrains.annotations.ApiStatus; diff --git a/fabric/src/main/java/dev/xpple/betterconfig/util/WrappedArgumentType.java b/fabric/src/main/java/dev/xpple/betterconfig/util/WrappedArgumentType.java new file mode 100644 index 0000000..3bc1c36 --- /dev/null +++ b/fabric/src/main/java/dev/xpple/betterconfig/util/WrappedArgumentType.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2024 Owen1212055 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package dev.xpple.betterconfig.util; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; + +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +public abstract class WrappedArgumentType implements ArgumentType { + private final ArgumentType nativeType; + + /** + * @param nativeType the native argument type, the client needs + * to understand this! + */ + protected WrappedArgumentType(ArgumentType nativeType) { + this.nativeType = nativeType; + } + + public final ArgumentType getNativeType() { + return this.nativeType; + } + + @Override + public abstract T parse(StringReader reader) throws CommandSyntaxException; + + @Override + public Collection getExamples() { + return this.nativeType.getExamples(); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return this.nativeType.listSuggestions(context, builder); + } + + public abstract static class Converted extends WrappedArgumentType { + protected Converted(ArgumentType nativeType) { + super(nativeType); + } + + @Override + public final T parse(StringReader reader) throws CommandSyntaxException { + return this.convert(this.getNativeType().parse(reader)); + } + + public abstract T convert(N nativeType) throws CommandSyntaxException; + } +} diff --git a/fabric/src/main/resources/META-INF/services/dev.xpple.betterconfig.impl.Platform b/fabric/src/main/resources/META-INF/services/dev.xpple.betterconfig.impl.Platform new file mode 100644 index 0000000..260c578 --- /dev/null +++ b/fabric/src/main/resources/META-INF/services/dev.xpple.betterconfig.impl.Platform @@ -0,0 +1 @@ +dev.xpple.betterconfig.impl.FabricPlatform diff --git a/src/main/resources/assets/betterconfig/icon.png b/fabric/src/main/resources/assets/betterconfig/icon.png similarity index 100% rename from src/main/resources/assets/betterconfig/icon.png rename to fabric/src/main/resources/assets/betterconfig/icon.png diff --git a/src/main/resources/assets/betterconfig/lang/en_us.json b/fabric/src/main/resources/assets/betterconfig/lang/en_us.json similarity index 100% rename from src/main/resources/assets/betterconfig/lang/en_us.json rename to fabric/src/main/resources/assets/betterconfig/lang/en_us.json diff --git a/src/main/resources/assets/betterconfig/lang/fr_fr.json b/fabric/src/main/resources/assets/betterconfig/lang/fr_fr.json similarity index 100% rename from src/main/resources/assets/betterconfig/lang/fr_fr.json rename to fabric/src/main/resources/assets/betterconfig/lang/fr_fr.json diff --git a/src/main/resources/betterconfig.mixins.json b/fabric/src/main/resources/betterconfig.mixins.json similarity index 67% rename from src/main/resources/betterconfig.mixins.json rename to fabric/src/main/resources/betterconfig.mixins.json index f30c545..9fa2ea6 100644 --- a/src/main/resources/betterconfig.mixins.json +++ b/fabric/src/main/resources/betterconfig.mixins.json @@ -2,7 +2,9 @@ "required": true, "package": "dev.xpple.betterconfig.mixin", "compatibilityLevel": "JAVA_17", - "mixins": [ + "server": [ + "MixinCommands", + "RequiredArgumentBuilderAccessor" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/fabric.mod.json b/fabric/src/main/resources/fabric.mod.json similarity index 87% rename from src/main/resources/fabric.mod.json rename to fabric/src/main/resources/fabric.mod.json index c65115d..60e9b8c 100644 --- a/src/main/resources/fabric.mod.json +++ b/fabric/src/main/resources/fabric.mod.json @@ -30,10 +30,10 @@ } ], "depends": { - "fabricloader": ">=0.15.11", - "minecraft": "1.21.x", + "fabricloader": ">=${fabric_loader_version}", + "minecraft": "${minecraft_version}.x", "java": ">=21", - "fabric-api": "*" + "fabric-command-api-v2": "*" }, "custom": { "modmenu": { diff --git a/src/testmod/java/dev/xpple/betterconfig/BlockAdapter.java b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockAdapter.java similarity index 56% rename from src/testmod/java/dev/xpple/betterconfig/BlockAdapter.java rename to fabric/src/testmod/java/dev/xpple/betterconfig/BlockAdapter.java index 18ac027..57af865 100644 --- a/src/testmod/java/dev/xpple/betterconfig/BlockAdapter.java +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockAdapter.java @@ -3,20 +3,20 @@ import com.google.gson.TypeAdapter; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; -import net.minecraft.block.Block; -import net.minecraft.registry.Registries; -import net.minecraft.util.Identifier; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; import java.io.IOException; class BlockAdapter extends TypeAdapter { @Override public void write(JsonWriter writer, Block block) throws IOException { - writer.value(Registries.BLOCK.getId(block).toString()); + writer.value(BuiltInRegistries.BLOCK.getKey(block).toString()); } @Override public Block read(JsonReader reader) throws IOException { - return Registries.BLOCK.get(Identifier.of(reader.nextString())); + return BuiltInRegistries.BLOCK.get(ResourceLocation.parse(reader.nextString())); } } diff --git a/src/testmod/java/dev/xpple/betterconfig/BlockArgumentType.java b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockArgumentType.java similarity index 69% rename from src/testmod/java/dev/xpple/betterconfig/BlockArgumentType.java rename to fabric/src/testmod/java/dev/xpple/betterconfig/BlockArgumentType.java index dd3e73a..219e0f1 100644 --- a/src/testmod/java/dev/xpple/betterconfig/BlockArgumentType.java +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockArgumentType.java @@ -6,10 +6,10 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.minecraft.block.Block; -import net.minecraft.command.CommandSource; -import net.minecraft.registry.Registries; -import net.minecraft.util.Identifier; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; import java.util.Arrays; import java.util.Collection; @@ -26,24 +26,24 @@ public static BlockArgumentType block() { return new BlockArgumentType(); } - public static Block getBlock(CommandContext context, String name) { + public static Block getBlock(CommandContext context, String name) { return context.getArgument(name, Block.class); } @Override public Block parse(StringReader reader) throws CommandSyntaxException { int cursor = reader.getCursor(); - Identifier identifier = Identifier.fromCommandInput(reader); - if (!Registries.BLOCK.containsId(identifier)) { + ResourceLocation key = ResourceLocation.read(reader); + if (!BuiltInRegistries.BLOCK.containsKey(key)) { reader.setCursor(cursor); throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); } - return Registries.BLOCK.get(identifier); + return BuiltInRegistries.BLOCK.get(key); } @Override public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return CommandSource.suggestIdentifiers(Registries.BLOCK.getIds(), builder); + return SharedSuggestionProvider.suggestResource(BuiltInRegistries.BLOCK.keySet(), builder); } @Override diff --git a/src/testmod/java/dev/xpple/betterconfig/BlockStateAdapter.java b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockStateAdapter.java similarity index 63% rename from src/testmod/java/dev/xpple/betterconfig/BlockStateAdapter.java rename to fabric/src/testmod/java/dev/xpple/betterconfig/BlockStateAdapter.java index d10e453..0e5b938 100644 --- a/src/testmod/java/dev/xpple/betterconfig/BlockStateAdapter.java +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockStateAdapter.java @@ -7,17 +7,17 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonWriter; import com.mojang.serialization.JsonOps; -import net.minecraft.block.BlockState; -import net.minecraft.command.argument.BlockStateArgument; +import net.minecraft.commands.arguments.blocks.BlockInput; +import net.minecraft.world.level.block.state.BlockState; import java.io.IOException; import java.util.Collections; import java.util.Optional; -class BlockStateAdapter extends TypeAdapter { +class BlockStateAdapter extends TypeAdapter { @Override - public void write(JsonWriter writer, BlockStateArgument blockState) throws IOException { - Optional jsonElement = BlockState.CODEC.encodeStart(JsonOps.INSTANCE, blockState.getBlockState()).result(); + public void write(JsonWriter writer, BlockInput blockState) throws IOException { + Optional jsonElement = BlockState.CODEC.encodeStart(JsonOps.INSTANCE, blockState.getState()).result(); if (jsonElement.isEmpty()) { throw new IOException(); } @@ -25,11 +25,11 @@ public void write(JsonWriter writer, BlockStateArgument blockState) throws IOExc } @Override - public BlockStateArgument read(JsonReader reader) throws IOException { + public BlockInput read(JsonReader reader) throws IOException { Optional blockState = BlockState.CODEC.parse(JsonOps.INSTANCE, JsonParser.parseReader(reader)).result(); if (blockState.isEmpty()) { throw new IOException(); } - return new BlockStateArgument(blockState.get(), Collections.emptySet(), null); + return new BlockInput(blockState.get(), Collections.emptySet(), null); } } diff --git a/fabric/src/testmod/java/dev/xpple/betterconfig/BlockWrappedArgumentType.java b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockWrappedArgumentType.java new file mode 100644 index 0000000..6f09fd4 --- /dev/null +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/BlockWrappedArgumentType.java @@ -0,0 +1,47 @@ +package dev.xpple.betterconfig; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dev.xpple.betterconfig.util.WrappedArgumentType; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.blocks.BlockPredicateArgument; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; + +import java.util.concurrent.CompletableFuture; + +public class BlockWrappedArgumentType extends WrappedArgumentType { + + private static final DynamicCommandExceptionType INVALID_BLOCK_ID_EXCEPTION = new DynamicCommandExceptionType(id -> Component.translatable("argument.block.id.invalid", id)); + + private BlockWrappedArgumentType(CommandBuildContext buildContext) { + super(BlockPredicateArgument.blockPredicate(buildContext)); + } + + public static BlockWrappedArgumentType block(CommandBuildContext buildContext) { + return new BlockWrappedArgumentType(buildContext); + } + + @Override + public Block parse(StringReader reader) throws CommandSyntaxException { + int cursor = reader.getCursor(); + ResourceLocation key = ResourceLocation.read(reader); + if (!BuiltInRegistries.BLOCK.containsKey(key)) { + reader.setCursor(cursor); + throw INVALID_BLOCK_ID_EXCEPTION.create(Component.translationArg(key)); + } + return BuiltInRegistries.BLOCK.get(key); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggestResource(BuiltInRegistries.BLOCK.keySet(), builder); + } +} diff --git a/src/testmod/java/dev/xpple/betterconfig/Configs.java b/fabric/src/testmod/java/dev/xpple/betterconfig/Configs.java similarity index 74% rename from src/testmod/java/dev/xpple/betterconfig/Configs.java rename to fabric/src/testmod/java/dev/xpple/betterconfig/Configs.java index f2f20a0..6f8e7e1 100644 --- a/src/testmod/java/dev/xpple/betterconfig/Configs.java +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/Configs.java @@ -2,13 +2,20 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import dev.xpple.betterconfig.api.Config; -import net.minecraft.block.Block; -import net.minecraft.block.Blocks; -import net.minecraft.command.CommandSource; -import net.minecraft.command.argument.BlockStateArgument; -import net.minecraft.server.command.ServerCommandSource; - -import java.util.*; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.blocks.BlockInput; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraft.world.level.levelgen.structure.StructureType; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; @SuppressWarnings({"FieldMayBeFinal", "unused", "MismatchedQueryAndUpdateOfCollection"}) public class Configs { @@ -61,8 +68,8 @@ public static void customTypeAdder(int codepoint) { @Config(condition = "isServer") public static boolean exampleServerOnlyConfig = true; - public static boolean isServer(CommandSource source) { - return source instanceof ServerCommandSource; + public static boolean isServer(SharedSuggestionProvider source) { + return source instanceof CommandSourceStack; } @Config(setter = @Config.Setter("privateSetter")) @@ -75,5 +82,8 @@ private static void privateSetter(String string) { public static Object exampleComment = null; @Config - public static BlockStateArgument exampleRegistryAccess = new BlockStateArgument(Blocks.COMPOSTER.getDefaultState(), Collections.emptySet(), null); + public static BlockInput exampleRegistryAccess = new BlockInput(Blocks.COMPOSTER.defaultBlockState(), Collections.emptySet(), null); + + @Config + public static StructureType exampleConvertedArgumentType = StructureType.WOODLAND_MANSION; } diff --git a/fabric/src/testmod/java/dev/xpple/betterconfig/StructureAdapter.java b/fabric/src/testmod/java/dev/xpple/betterconfig/StructureAdapter.java new file mode 100644 index 0000000..646e71c --- /dev/null +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/StructureAdapter.java @@ -0,0 +1,22 @@ +package dev.xpple.betterconfig; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.StructureType; + +import java.io.IOException; + +class StructureAdapter extends TypeAdapter> { + @Override + public void write(JsonWriter writer, StructureType structure) throws IOException { + writer.value(BuiltInRegistries.STRUCTURE_TYPE.getKey(structure).toString()); + } + + @Override + public StructureType read(JsonReader reader) throws IOException { + return BuiltInRegistries.STRUCTURE_TYPE.get(ResourceLocation.parse(reader.nextString())); + } +} diff --git a/fabric/src/testmod/java/dev/xpple/betterconfig/StructureArgumentType.java b/fabric/src/testmod/java/dev/xpple/betterconfig/StructureArgumentType.java new file mode 100644 index 0000000..d5c6e4b --- /dev/null +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/StructureArgumentType.java @@ -0,0 +1,42 @@ +package dev.xpple.betterconfig; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dev.xpple.betterconfig.util.WrappedArgumentType; +import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.commands.arguments.ResourceLocationArgument; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.StructureType; + +import java.util.concurrent.CompletableFuture; + +public class StructureArgumentType extends WrappedArgumentType.Converted, ResourceLocation> { + + private static final DynamicCommandExceptionType INVALID_STRUCTURE_ID_EXCEPTION = new DynamicCommandExceptionType(id -> Component.translatable("structure_block.invalid_structure_name", id)); + + private StructureArgumentType() { + super(ResourceLocationArgument.id()); + } + + public static StructureArgumentType structure() { + return new StructureArgumentType(); + } + + @Override + public StructureType convert(ResourceLocation key) throws CommandSyntaxException { + if (!BuiltInRegistries.STRUCTURE_TYPE.containsKey(key)) { + throw INVALID_STRUCTURE_ID_EXCEPTION.create(Component.translationArg(key)); + } + return BuiltInRegistries.STRUCTURE_TYPE.get(key); + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return SharedSuggestionProvider.suggestResource(BuiltInRegistries.STRUCTURE_TYPE.keySet(), builder); + } +} diff --git a/src/testmod/java/dev/xpple/betterconfig/TestEnum.java b/fabric/src/testmod/java/dev/xpple/betterconfig/TestEnum.java similarity index 100% rename from src/testmod/java/dev/xpple/betterconfig/TestEnum.java rename to fabric/src/testmod/java/dev/xpple/betterconfig/TestEnum.java diff --git a/fabric/src/testmod/java/dev/xpple/betterconfig/TestMod.java b/fabric/src/testmod/java/dev/xpple/betterconfig/TestMod.java new file mode 100644 index 0000000..5a5650f --- /dev/null +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/TestMod.java @@ -0,0 +1,25 @@ +package dev.xpple.betterconfig; + +import dev.xpple.betterconfig.api.ModConfigBuilder; +import net.fabricmc.api.DedicatedServerModInitializer; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.CommandSourceStack; +import net.minecraft.commands.arguments.blocks.BlockInput; +import net.minecraft.commands.arguments.blocks.BlockStateArgument; +import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.structure.StructureType; + +public class TestMod implements DedicatedServerModInitializer { + @Override + public void onInitializeServer() { + //ArgumentTypeRegistry.registerArgumentType(new ResourceLocation("testmod", "block"), BlockArgumentType.class, SingletonArgumentInfo.contextFree(BlockArgumentType::block)); + + new ModConfigBuilder("testmod", Configs.class) + .registerTypeHierarchy(Block.class, new BlockAdapter(), BlockWrappedArgumentType::block) + .registerTypeHierarchy(BlockInput.class, new BlockStateAdapter(), BlockStateArgument::block) + .registerTypeHierarchy((Class>) (Class) StructureType.class, new StructureAdapter(), StructureArgumentType::structure) + .build(); + } +} diff --git a/fabric/src/testmod/java/dev/xpple/betterconfig/TestModClient.java b/fabric/src/testmod/java/dev/xpple/betterconfig/TestModClient.java new file mode 100644 index 0000000..6f878a6 --- /dev/null +++ b/fabric/src/testmod/java/dev/xpple/betterconfig/TestModClient.java @@ -0,0 +1,21 @@ +package dev.xpple.betterconfig; + +import dev.xpple.betterconfig.api.ModConfigBuilder; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.commands.CommandBuildContext; +import net.minecraft.commands.arguments.blocks.BlockInput; +import net.minecraft.commands.arguments.blocks.BlockStateArgument; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.levelgen.structure.StructureType; + +public class TestModClient implements ClientModInitializer { + @Override + public void onInitializeClient() { + new ModConfigBuilder("testmodclient", Configs.class) + .registerTypeHierarchy(Block.class, new BlockAdapter(), BlockArgumentType::block) + .registerTypeHierarchy(BlockInput.class, new BlockStateAdapter(), BlockStateArgument::block) + .registerTypeHierarchy((Class>) (Class) StructureType.class, new StructureAdapter(), StructureArgumentType::structure) + .build(); + } +} diff --git a/src/testmod/resources/fabric.mod.json b/fabric/src/testmod/resources/fabric.mod.json similarity index 100% rename from src/testmod/resources/fabric.mod.json rename to fabric/src/testmod/resources/fabric.mod.json diff --git a/gradle.properties b/gradle.properties index 1aeefb6..dd5b4de 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,16 +2,21 @@ org.gradle.jvmargs=-Xmx1G org.gradle.parallel=true -# Fabric Properties -# check these on https://fabricmc.net/develop -minecraft_version=1.21 -yarn_mappings=1.21+build.2 -loader_version=0.15.11 - -# Mod Properties +# BetterConfig Properties mod_version=1.3 maven_group=dev.xpple archives_base_name=betterconfig -# Dependencies -fabric_api_version=0.100.1+1.21 +# Minecraft properties +minecraft_version=1.21 + +# Fabric Properties +# check these on https://fabricmc.net/develop +# https://ldtteam.jfrog.io/ui/native/parchmentmc-public/org/parchmentmc/data/ +fabric_api_version=0.101.2+1.21 +parchment_mappings=parchment-1.21:2024.07.28@zip +fabric_loader_version=0.15.11 + +# Paper Properties +paper_api_version=1.21-R0.1-SNAPSHOT +plugin_yml_paper_api_version=1.21 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e644113..2c35211 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a441313..09523c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index b740cf1..f5feea6 100644 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 25da30d..9d21a21 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## diff --git a/paper/build.gradle b/paper/build.gradle new file mode 100644 index 0000000..aea6c83 --- /dev/null +++ b/paper/build.gradle @@ -0,0 +1,142 @@ +plugins { + id 'java' + id 'maven-publish' +} + +base { + archivesName = "${project.archives_base_name}-paper" + group = project.maven_group + version = project.mod_version +} + +sourceSets { + testplugin { + compileClasspath += main.compileClasspath + runtimeClasspath += main.runtimeClasspath + compileClasspath += main.output + runtimeClasspath += main.output + } +} + +repositories { + mavenCentral() + maven { + name = "papermc-repo" + url = "https://repo.papermc.io/repository/maven-public/" + } +} + +configurations { + includeInJar +} + +dependencies { + configurations.compileOnly.extendsFrom(configurations.includeInJar) + includeInJar project(':common') + + compileOnly "io.papermc.paper:paper-api:${paper_api_version}" +} + +processResources { + def props = [version: project.mod_version, plugin_yml_paper_api_version: project.plugin_yml_paper_api_version] + inputs.properties props + filteringCharset 'UTF-8' + filesMatching('plugin.yml') { + expand props + } +} + +def targetJavaVersion = 21 +java { + def javaVersion = JavaVersion.toVersion(targetJavaVersion) + sourceCompatibility = javaVersion + targetCompatibility = javaVersion + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } +} + +tasks.withType(JavaCompile).configureEach { + if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { + options.release = targetJavaVersion + } +} + +tasks.register('buildTestplugin', Jar) { + it.group = 'paper' + + it.archiveBaseName = 'testplugin' + it.archiveVersion = '1.0.0' + from sourceSets.testplugin.output +} + +tasks.register('copyJarsToServer') { + it.group = 'paper' + + doLast { + copy { + from jar + into "${projectDir}/run/plugins" + } + + copy { + from buildTestplugin + into "${projectDir}/run/plugins" + } + } +} + +copyJarsToServer.dependsOn(build, buildTestplugin) + +tasks.register('runTestpluginServer') { + it.group = 'paper' + + doLast { + javaexec { + workingDir = "${projectDir}/run" + main = '-jar' + args = ["${projectDir}/run/server.jar", '--nogui'] + jvmArgs = ['-Dcom.mojang.eula.agree=true'] + standardInput = System.in + } + } +} + +runTestpluginServer.dependsOn(copyJarsToServer) + +jar { + dependsOn ':common:remapJar' + from { + configurations.includeInJar.collect { + it.isDirectory() ? it : zipTree(it) + } + } +} + +publishing { + publications { + mavenJava(MavenPublication) { + artifactId project.base.archivesName.get() + from components.java + } + } + + repositories { + maven { + name = "xpple" + url = "sftp://xpple.dev:22/maven.xpple.dev/httpdocs/maven2" + credentials { + username = System.getenv("MAVEN_USER") + password = System.getenv("MAVEN_PASS") + } + } + maven { + name = "GitHubPackages" + url = "https://maven.pkg.github.com/xpple/BetterConfig" + credentials { + username = System.getenv("GITHUB_ACTOR") + password = System.getenv("GITHUB_TOKEN") + } + } + } +} diff --git a/paper/src/main/java/dev/xpple/betterconfig/BetterConfig.java b/paper/src/main/java/dev/xpple/betterconfig/BetterConfig.java new file mode 100644 index 0000000..cc8a230 --- /dev/null +++ b/paper/src/main/java/dev/xpple/betterconfig/BetterConfig.java @@ -0,0 +1,18 @@ +package dev.xpple.betterconfig; + +import dev.xpple.betterconfig.command.ConfigCommand; +import io.papermc.paper.plugin.lifecycle.event.types.LifecycleEvents; +import org.bukkit.Bukkit; +import org.bukkit.plugin.java.JavaPlugin; + +import java.nio.file.Path; + +public final class BetterConfig extends JavaPlugin { + + public static final Path PLUGIN_PATH = Bukkit.getPluginsFolder().toPath(); + + @Override + public void onEnable() { + this.getLifecycleManager().registerEventHandler(LifecycleEvents.COMMANDS, event -> event.registrar().register(ConfigCommand.build())); + } +} diff --git a/paper/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java b/paper/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java new file mode 100644 index 0000000..9c12f7c --- /dev/null +++ b/paper/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java @@ -0,0 +1,76 @@ +package dev.xpple.betterconfig.command; + +import com.mojang.brigadier.Command; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.tree.LiteralCommandNode; +import dev.xpple.betterconfig.impl.BetterConfigImpl; +import dev.xpple.betterconfig.impl.ModConfigImpl; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import net.kyori.adventure.text.Component; +import org.bukkit.Server; + +public class ConfigCommand extends AbstractConfigCommand { + + private ConfigCommand() { + super("config"); + } + + @SuppressWarnings("unchecked") + public static LiteralCommandNode build() { + return new ConfigCommand().create(BetterConfigImpl.getModConfigs().values().stream().map(modConfig -> (ModConfigImpl) modConfig).toList(), null).requires(source -> source.getSender().hasPermission("betterconfig.config")).build(); + } + + @Override + protected int comment(CommandSourceStack source, String config, String comment) { + source.getSender().sendMessage(Component.translatable("betterconfig.commands.config.comment", "Comment for %s:", Component.text(config))); + source.getSender().sendMessage(Component.text(comment)); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int get(CommandSourceStack source, ModConfigImpl modConfig, String config) { + Component component = Component.translatable("betterconfig.commands.config.get", "%s is currently set to %s.", Component.text(config), Component.text(modConfig.asString(config))); + source.getSender().sendMessage(component); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int reset(CommandSourceStack source, ModConfigImpl modConfig, String config) { + modConfig.reset(config); + Component component = Component.translatable("betterconfig.commands.config.reset", "%s has been reset to %s.", Component.text(config), Component.text(modConfig.asString(config))); + source.getSender().getServer().broadcast(component, Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int set(CommandSourceStack source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.set(config, value); + Component component = Component.translatable("betterconfig.commands.config.set", "%s has been set to %s.", Component.text(config), Component.text(modConfig.asString(config))); + source.getSender().getServer().broadcast(component, Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int add(CommandSourceStack source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.add(config, value); + Component component = Component.translatable("betterconfig.commands.config.add", "%s has been added to %s.", Component.text(modConfig.asString(value)), Component.text(config)); + source.getSender().getServer().broadcast(component, Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int put(CommandSourceStack source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException { + modConfig.put(config, key, value); + Component component = Component.translatable("betterconfig.commands.config.put", "The mapping %s=%s has been added to %s.", Component.text(modConfig.asString(key)), Component.text(modConfig.asString(value)), Component.text(config)); + source.getSender().getServer().broadcast(component, Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + return Command.SINGLE_SUCCESS; + } + + @Override + protected int remove(CommandSourceStack source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { + modConfig.remove(config, value); + Component component = Component.translatable("betterconfig.commands.config.remove", "%s has been removed from %s.", Component.text(modConfig.asString(value)), Component.text(config)); + source.getSender().getServer().broadcast(component, Server.BROADCAST_CHANNEL_ADMINISTRATIVE); + return Command.SINGLE_SUCCESS; + } +} diff --git a/paper/src/main/java/dev/xpple/betterconfig/command/suggestion/SuggestionProviderHelper.java b/paper/src/main/java/dev/xpple/betterconfig/command/suggestion/SuggestionProviderHelper.java new file mode 100644 index 0000000..13f3ed3 --- /dev/null +++ b/paper/src/main/java/dev/xpple/betterconfig/command/suggestion/SuggestionProviderHelper.java @@ -0,0 +1,56 @@ +package dev.xpple.betterconfig.command.suggestion; + +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import org.bukkit.NamespacedKey; + +import java.util.Locale; +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Stream; + +public class SuggestionProviderHelper { + public static CompletableFuture suggestMatching(Stream candidates, SuggestionsBuilder builder) { + String string = builder.getRemaining().toLowerCase(Locale.ROOT); + Stream suggestions = candidates.filter(candidate -> shouldSuggest(string, candidate.toLowerCase(Locale.ROOT))); + suggestions.forEach(builder::suggest); + return builder.buildFuture(); + } + + public static CompletableFuture suggestNamespacedKeys(Stream candidates, SuggestionsBuilder builder) { + return suggestNamespacedKeys(candidates::iterator, builder); + } + + public static CompletableFuture suggestNamespacedKeys(Iterable candidates, SuggestionsBuilder builder) { + String string = builder.getRemaining().toLowerCase(Locale.ROOT); + forEachMatching(candidates, string, key -> key, key -> builder.suggest(key.toString())); + return builder.buildFuture(); + } + + private static void forEachMatching(Iterable candidates, String remaining, Function namespacedKeyFunction, Consumer action) { + boolean hasNamespace = remaining.indexOf(':') > -1; + + for (T candidate : candidates) { + NamespacedKey namespacedKey = namespacedKeyFunction.apply(candidate); + if (hasNamespace) { + String string = namespacedKey.toString(); + if (shouldSuggest(remaining, string)) { + action.accept(candidate); + } + } else if (shouldSuggest(remaining, namespacedKey.getNamespace()) || namespacedKey.getNamespace().equals("minecraft") && shouldSuggest(remaining, namespacedKey.getKey())) { + action.accept(candidate); + } + } + } + + private static boolean shouldSuggest(String remaining, String candidate) { + for (int i = 0; !candidate.startsWith(remaining, i); ++i) { + i = candidate.indexOf('_', i); + if (i < 0) { + return false; + } + } + return true; + } +} diff --git a/paper/src/main/java/dev/xpple/betterconfig/impl/PaperPlatform.java b/paper/src/main/java/dev/xpple/betterconfig/impl/PaperPlatform.java new file mode 100644 index 0000000..618f75a --- /dev/null +++ b/paper/src/main/java/dev/xpple/betterconfig/impl/PaperPlatform.java @@ -0,0 +1,34 @@ +package dev.xpple.betterconfig.impl; + +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import dev.xpple.betterconfig.BetterConfig; +import dev.xpple.betterconfig.command.suggestion.SuggestionProviderHelper; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import net.kyori.adventure.text.Component; + +import java.nio.file.Path; +import java.util.Arrays; + +public class PaperPlatform implements Platform { + @Override + public Path getConfigsPath(String modId) { + return BetterConfig.PLUGIN_PATH.resolve(modId).resolve("config.json"); + } + + @Override + public Class getCommandSourceClass() { + return CommandSourceStack.class; + } + + @Override + public DynamicCommandExceptionType invalidEnumException() { + return new DynamicCommandExceptionType(value -> MessageComponentSerializer.message().serialize(Component.translatable("argument.enum.invalid").arguments(Component.text(String.valueOf(value))))); + } + + @Override + public > SuggestionProvider enumSuggestionProvider(Class type) { + return (context, builder) -> SuggestionProviderHelper.suggestMatching(Arrays.stream(type.getEnumConstants()).map(Enum::name), builder); + } +} diff --git a/paper/src/main/resources/META-INF/services/dev.xpple.betterconfig.impl.Platform b/paper/src/main/resources/META-INF/services/dev.xpple.betterconfig.impl.Platform new file mode 100644 index 0000000..b688434 --- /dev/null +++ b/paper/src/main/resources/META-INF/services/dev.xpple.betterconfig.impl.Platform @@ -0,0 +1 @@ +dev.xpple.betterconfig.impl.PaperPlatform diff --git a/paper/src/main/resources/plugin.yml b/paper/src/main/resources/plugin.yml new file mode 100644 index 0000000..7f7334a --- /dev/null +++ b/paper/src/main/resources/plugin.yml @@ -0,0 +1,17 @@ +name: BetterConfig +version: '${version}' +description: A very powerful and easy to use command based configuration library for servers and clients. +author: xpple +website: https://xpple.dev/ +main: dev.xpple.betterconfig.BetterConfig +api-version: '${plugin_yml_paper_api_version}' +load: STARTUP +permissions: + betterconfig.config: + description: Allows the use of the config command + default: op + betterconfig.*: + description: Wildcard permission + default: op + children: + - betterconfig.config diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/BlockMaterialArgumentType.java b/paper/src/testplugin/java/dev/xpple/betterconfig/BlockMaterialArgumentType.java new file mode 100644 index 0000000..1934911 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/BlockMaterialArgumentType.java @@ -0,0 +1,45 @@ +package dev.xpple.betterconfig; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dev.xpple.betterconfig.command.suggestion.SuggestionProviderHelper; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.block.BlockState; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class BlockMaterialArgumentType implements CustomArgumentType.Converted { + + private static final Set BLOCKS = Arrays.stream(Material.values()) + .filter(Material::isBlock) + .map(Material::getKey) + .collect(Collectors.toUnmodifiableSet()); + + @Override + public @NotNull ArgumentType getNativeType() { + return ArgumentTypes.blockState(); + } + + public static BlockMaterialArgumentType block() { + return new BlockMaterialArgumentType(); + } + + @Override + public @NotNull Material convert(@NotNull BlockState state) { + return state.getType(); + } + + @Override + public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { + return SuggestionProviderHelper.suggestNamespacedKeys(BLOCKS, builder); + } +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/BlockStateAdapter.java b/paper/src/testplugin/java/dev/xpple/betterconfig/BlockStateAdapter.java new file mode 100644 index 0000000..f010078 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/BlockStateAdapter.java @@ -0,0 +1,21 @@ +package dev.xpple.betterconfig; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import org.bukkit.Bukkit; +import org.bukkit.block.BlockState; + +import java.io.IOException; + +class BlockStateAdapter extends TypeAdapter { + @Override + public void write(JsonWriter writer, BlockState blockState) throws IOException { + writer.value(blockState.getBlockData().getAsString()); + } + + @Override + public BlockState read(JsonReader reader) throws IOException { + return Bukkit.getServer().createBlockData(reader.nextString()).createBlockState(); + } +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/Configs.java b/paper/src/testplugin/java/dev/xpple/betterconfig/Configs.java new file mode 100644 index 0000000..b860580 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/Configs.java @@ -0,0 +1,86 @@ +package dev.xpple.betterconfig; + +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import dev.xpple.betterconfig.api.Config; +import io.papermc.paper.command.brigadier.CommandSourceStack; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.generator.structure.Structure; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +@SuppressWarnings({"FieldMayBeFinal", "unused", "MismatchedQueryAndUpdateOfCollection"}) +public class Configs { + @Config + public static Material exampleMaterial = Material.STONE; + + @Config + public static Collection exampleMaterialList = new ArrayList<>(List.of(Material.PISTON, Material.STICKY_PISTON)); + + @Config + public static Map exampleStringMaterialMap = new HashMap<>(Map.of("sand", Material.SAND, "red_sand", Material.RED_SAND)); + + @Config + public static Map exampleMaterialStringMap = new HashMap<>(Map.of(Material.NETHERRACK, "nether", Material.END_STONE, "end")); + + @Config(adder = @Config.Adder("customAdder")) + public static Collection exampleCustomAdder = new ArrayList<>(List.of("1", "2")); + public static void customAdder(String string) throws CommandSyntaxException { + try { + Integer.parseInt(string); + } catch (NumberFormatException e) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherParseException().create(string); + } + exampleCustomAdder.add(string); + } + + @Config(putter = @Config.Putter("none"), adder = @Config.Adder("customMapAdder")) + public static Map exampleMapAdder = new HashMap<>(Map.of("a", "A", "b", "B")); + public static void customMapAdder(String string) { + exampleMapAdder.put(string.toLowerCase(Locale.ROOT), string.toUpperCase(Locale.ROOT)); + } + + @Config(adder = @Config.Adder(value = "customTypeAdder", type = int.class)) + public static Collection exampleCustomType = new ArrayList<>(List.of("%", "@")); + public static void customTypeAdder(int codepoint) { + exampleCustomType.add(Character.toString(codepoint)); + } + + @Config + public static TestEnum exampleEnum = TestEnum.ONE; + + @Config(readOnly = true) + public static double exampleReadOnly = Math.PI; + + @Config(temporary = true) + public static double exampleTemporary = Math.random(); + + @Config + private static boolean examplePrivate = false; + + @Config(condition = "isXpple") + public static boolean exampleCondition = true; + public static boolean isXpple(CommandSourceStack source) { + return source.getSender().getName().equals("xpple"); + } + + @Config(setter = @Config.Setter("privateSetter")) + public static String examplePrivateSetter = "nice"; + private static void privateSetter(String string) { + examplePrivateSetter = string + '!'; + } + + @Config(comment = "This is a mysterious object") + public static Object exampleComment = null; + + @Config + public static BlockState exampleNativeArgumentType = Material.COMPOSTER.createBlockData().createBlockState(); + + @Config + public static Structure exampleCustomArgumentType = Structure.MANSION; +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/MaterialAdapter.java b/paper/src/testplugin/java/dev/xpple/betterconfig/MaterialAdapter.java new file mode 100644 index 0000000..8d9bf67 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/MaterialAdapter.java @@ -0,0 +1,22 @@ +package dev.xpple.betterconfig; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.Registry; + +import java.io.IOException; + +class MaterialAdapter extends TypeAdapter { + @Override + public void write(JsonWriter writer, Material value) throws IOException { + writer.value(value.getKey().toString()); + } + + @Override + public Material read(JsonReader reader) throws IOException { + return Registry.MATERIAL.get(NamespacedKey.fromString(reader.nextString())); + } +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/StructureAdapter.java b/paper/src/testplugin/java/dev/xpple/betterconfig/StructureAdapter.java new file mode 100644 index 0000000..023f46a --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/StructureAdapter.java @@ -0,0 +1,23 @@ +package dev.xpple.betterconfig; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import org.bukkit.NamespacedKey; +import org.bukkit.generator.structure.Structure; + +import java.io.IOException; + +class StructureAdapter extends TypeAdapter { + @Override + public void write(JsonWriter writer, Structure value) throws IOException { + writer.value(RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE).getKey(value).toString()); + } + + @Override + public Structure read(JsonReader reader) throws IOException { + return RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE).get(NamespacedKey.fromString(reader.nextString())); + } +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/StructureArgumentType.java b/paper/src/testplugin/java/dev/xpple/betterconfig/StructureArgumentType.java new file mode 100644 index 0000000..90cdbd4 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/StructureArgumentType.java @@ -0,0 +1,56 @@ +package dev.xpple.betterconfig; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import dev.xpple.betterconfig.command.suggestion.SuggestionProviderHelper; +import io.papermc.paper.command.brigadier.MessageComponentSerializer; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import io.papermc.paper.command.brigadier.argument.CustomArgumentType; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import net.kyori.adventure.text.Component; +import org.bukkit.Keyed; +import org.bukkit.NamespacedKey; +import org.bukkit.generator.structure.Structure; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +class StructureArgumentType implements CustomArgumentType.Converted { + + private static final DynamicCommandExceptionType INVALID_STRUCTURE_ID_EXCEPTION = new DynamicCommandExceptionType(id -> MessageComponentSerializer.message().serialize(Component.translatable("structure_block.invalid_structure_name").arguments(Component.text(id.toString())))); + + private static final Set STRUCTURES = StreamSupport.stream(RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE).spliterator(), false) + .map(Keyed::getKey) + .collect(Collectors.toUnmodifiableSet()); + + @Override + public @NotNull ArgumentType getNativeType() { + return ArgumentTypes.namespacedKey(); + } + + public static StructureArgumentType structure() { + return new StructureArgumentType(); + } + + @Override + public @NotNull Structure convert(@NotNull NamespacedKey key) throws CommandSyntaxException { + Structure structure = RegistryAccess.registryAccess().getRegistry(RegistryKey.STRUCTURE).get(key); + if (structure == null) { + throw INVALID_STRUCTURE_ID_EXCEPTION.create(key); + } + return structure; + } + + @Override + public @NotNull CompletableFuture listSuggestions(@NotNull CommandContext context, @NotNull SuggestionsBuilder builder) { + return SuggestionProviderHelper.suggestNamespacedKeys(STRUCTURES, builder); + } +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/TestEnum.java b/paper/src/testplugin/java/dev/xpple/betterconfig/TestEnum.java new file mode 100644 index 0000000..fe145f6 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/TestEnum.java @@ -0,0 +1,7 @@ +package dev.xpple.betterconfig; + +enum TestEnum { + ONE, + TWO, + THREE; +} diff --git a/paper/src/testplugin/java/dev/xpple/betterconfig/TestPlugin.java b/paper/src/testplugin/java/dev/xpple/betterconfig/TestPlugin.java new file mode 100644 index 0000000..aa548b5 --- /dev/null +++ b/paper/src/testplugin/java/dev/xpple/betterconfig/TestPlugin.java @@ -0,0 +1,22 @@ +package dev.xpple.betterconfig; + +import dev.xpple.betterconfig.api.ModConfigBuilder; +import io.papermc.paper.command.brigadier.argument.ArgumentTypes; +import org.bukkit.Material; +import org.bukkit.block.BlockState; +import org.bukkit.generator.structure.Structure; +import org.bukkit.plugin.java.JavaPlugin; + +public class TestPlugin extends JavaPlugin { + + private static final String PLUGIN_NAME = "TestPlugin"; + + @Override + public void onEnable() { + new ModConfigBuilder<>(PLUGIN_NAME, Configs.class) + .registerType(Material.class, new MaterialAdapter(), BlockMaterialArgumentType::block) + .registerTypeHierarchy(BlockState.class, new BlockStateAdapter(), ArgumentTypes::blockState) + .registerTypeHierarchy(Structure.class, new StructureAdapter(), StructureArgumentType::structure) + .build(); + } +} diff --git a/paper/src/testplugin/resources/plugin.yml b/paper/src/testplugin/resources/plugin.yml new file mode 100644 index 0000000..ca4c530 --- /dev/null +++ b/paper/src/testplugin/resources/plugin.yml @@ -0,0 +1,6 @@ +name: TestPlugin +version: '1.0.0' +main: dev.xpple.betterconfig.TestPlugin +api-version: '1.20.6' +load: STARTUP +authors: [xpple] diff --git a/settings.gradle b/settings.gradle index b02216b..2f7d774 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,3 +8,7 @@ pluginManagement { gradlePluginPortal() } } + +include 'common' +include 'fabric' +include 'paper' diff --git a/src/client/java/dev/xpple/betterconfig/command/client/ConfigCommandClient.java b/src/client/java/dev/xpple/betterconfig/command/client/ConfigCommandClient.java deleted file mode 100644 index 827b471..0000000 --- a/src/client/java/dev/xpple/betterconfig/command/client/ConfigCommandClient.java +++ /dev/null @@ -1,68 +0,0 @@ -package dev.xpple.betterconfig.command.client; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import dev.xpple.betterconfig.command.AbstractConfigCommand; -import dev.xpple.betterconfig.impl.ModConfigImpl; -import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.text.Text; - -public class ConfigCommandClient extends AbstractConfigCommand { - private ConfigCommandClient() { - super("cconfig"); - } - - public static void register(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { - dispatcher.register(new ConfigCommandClient().create(registryAccess)); - } - - @Override - protected int comment(FabricClientCommandSource source, String config, String comment) { - source.sendFeedback(Text.translatable("betterconfig.commands.config.comment", config)); - source.sendFeedback(Text.of(comment)); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int get(FabricClientCommandSource source, ModConfigImpl modConfig, String config) { - source.sendFeedback(Text.translatable("betterconfig.commands.config.get", config, modConfig.asString(config))); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int reset(FabricClientCommandSource source, ModConfigImpl modConfig, String config) { - modConfig.reset(config); - source.sendFeedback(Text.translatable("betterconfig.commands.config.reset", config, modConfig.asString(config))); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int set(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { - modConfig.set(config, value); - source.sendFeedback(Text.translatable("betterconfig.commands.config.set", config, modConfig.asString(config))); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int add(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { - modConfig.add(config, value); - source.sendFeedback(Text.translatable("betterconfig.commands.config.add", modConfig.asString(value), config)); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int put(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException { - modConfig.put(config, key, value); - source.sendFeedback(Text.translatable("betterconfig.commands.config.put", modConfig.asString(key), modConfig.asString(value), config)); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int remove(FabricClientCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { - modConfig.remove(config, value); - source.sendFeedback(Text.translatable("betterconfig.commands.config.remove", modConfig.asString(value), config)); - return Command.SINGLE_SUCCESS; - } -} diff --git a/src/main/java/dev/xpple/betterconfig/api/ModConfigBuilder.java b/src/main/java/dev/xpple/betterconfig/api/ModConfigBuilder.java deleted file mode 100644 index 2f1609e..0000000 --- a/src/main/java/dev/xpple/betterconfig/api/ModConfigBuilder.java +++ /dev/null @@ -1,150 +0,0 @@ -package dev.xpple.betterconfig.api; - -import com.google.gson.GsonBuilder; -import com.google.gson.TypeAdapter; -import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import dev.xpple.betterconfig.impl.BetterConfigImpl; -import dev.xpple.betterconfig.impl.BetterConfigInternals; -import dev.xpple.betterconfig.impl.ModConfigImpl; -import dev.xpple.betterconfig.util.CheckedBiFunction; -import dev.xpple.betterconfig.util.Pair; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.CommandSource; - -import java.util.HashMap; -import java.util.Map; -import java.util.function.Function; -import java.util.function.Supplier; - -public class ModConfigBuilder { - - final String modId; - final Class configsClass; - - final GsonBuilder builder = new GsonBuilder().serializeNulls().enableComplexMapKeySerialization(); - final Map, Function>> arguments = new HashMap<>(); - final Map, Pair, CheckedBiFunction, String, ?, CommandSyntaxException>>> suggestors = new HashMap<>(); - - public ModConfigBuilder(String modId, Class configsClass) { - this.modId = modId; - this.configsClass = configsClass; - } - - /** - * Register a new type adapter and argument type for the specified type. - * @param type the type's class - * @param adapter the type adapter - * @param argumentTypeSupplier a supplier for the argument type - * @param the type - * @return the current builder instance - * @implNote On servers, consider using {@link ModConfigBuilder#registerType(Class, TypeAdapter, SuggestionProvider, CheckedBiFunction)} - * instead. To use this method on servers, operators need to register the brigadier argument type - * as well. - * @see ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, Supplier) - */ - public ModConfigBuilder registerType(Class type, TypeAdapter adapter, Supplier> argumentTypeSupplier) { - return this.registerType(type, adapter, registryAccess -> argumentTypeSupplier.get()); - } - - /** - * Register a new type adapter and argument type for the specified type. - * @param type the type's class - * @param adapter the type adapter - * @param argumentTypeFunction a function for the argument type needing registry access - * @param the type - * @return the current builder instance - * @implNote On servers, consider using {@link ModConfigBuilder#registerType(Class, TypeAdapter, SuggestionProvider, CheckedBiFunction)} - * instead. To use this method on servers, operators need to register the brigadier argument type - * as well. - * @see ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, Function) - */ - public ModConfigBuilder registerType(Class type, TypeAdapter adapter, Function> argumentTypeFunction) { - this.builder.registerTypeAdapter(type, adapter); - this.arguments.put(type, argumentTypeFunction); - return this; - } - - /** - * Register a new type adapter and argument type for the specified type and all subclasses. - * @param type the type's class - * @param adapter the type adapter - * @param argumentTypeSupplier a supplier for the argument type - * @param the type - * @return the current builder instance - * @implNote On servers, consider using {@link ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, SuggestionProvider, CheckedBiFunction)} - * instead. To use this method on servers, operators need to register the brigadier argument type - * as well. - * @see ModConfigBuilder#registerType(Class, TypeAdapter, Supplier) - */ - public ModConfigBuilder registerTypeHierarchy(Class type, TypeAdapter adapter, Supplier> argumentTypeSupplier) { - return this.registerTypeHierarchy(type, adapter, registryAccess -> argumentTypeSupplier.get()); - } - - /** - * Register a new type adapter and argument type for the specified type and all subclasses. - * @param type the type's class - * @param adapter the type adapter - * @param argumentTypeFunction a function for the argument type needing registry access - * @param the type - * @return the current builder instance - * @implNote On servers, consider using {@link ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, SuggestionProvider, CheckedBiFunction)} - * instead. To use this method on servers, operators need to register the brigadier argument type - * as well. - * @see ModConfigBuilder#registerType(Class, TypeAdapter, Function) - */ - public ModConfigBuilder registerTypeHierarchy(Class type, TypeAdapter adapter, Function> argumentTypeFunction) { - this.builder.registerTypeHierarchyAdapter(type, adapter); - this.arguments.put(type, argumentTypeFunction); - return this; - } - - /** - * Register a new type adapter and suggestor for the specified type. - * @param type the type's class - * @param adapter the type adapter - * @param suggestionProvider a suggestion provider for the type - * @param argumentParser a parser for the argument - * @param the type - * @return the current builder instance - * @implNote On clients, consider using {@link ModConfigBuilder#registerType(Class, TypeAdapter, Supplier)} instead. - * @see ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, SuggestionProvider, CheckedBiFunction) - */ - public ModConfigBuilder registerType(Class type, TypeAdapter adapter, SuggestionProvider suggestionProvider, CheckedBiFunction, String, T, CommandSyntaxException> argumentParser) { - this.builder.registerTypeAdapter(type, adapter); - this.suggestors.put(type, new Pair<>(suggestionProvider, argumentParser)); - return this; - } - - /** - * Register a new type adapter and suggestor for the specified type and all subclasses. - * @param type the type's class - * @param adapter the type adapter - * @param suggestionProvider a suggestion provider for the type - * @param argumentParser a parser for the argument - * @param the type - * @return the current builder instance - * @implNote On clients, consider using {@link ModConfigBuilder#registerTypeHierarchy(Class, TypeAdapter, Supplier)} instead. - * @see ModConfigBuilder#registerType(Class, TypeAdapter, SuggestionProvider, CheckedBiFunction) - */ - public ModConfigBuilder registerTypeHierarchy(Class type, TypeAdapter adapter, SuggestionProvider suggestionProvider, CheckedBiFunction, String, T, CommandSyntaxException> argumentParser) { - this.builder.registerTypeHierarchyAdapter(type, adapter); - this.suggestors.put(type, new Pair<>(suggestionProvider, argumentParser)); - return this; - } - - /** - * Finalise the registration process. - * @throws IllegalArgumentException when a configuration already exists for this mod - */ - public void build() { - ModConfigImpl modConfig = new ModConfigImpl(this.modId, this.configsClass, this.builder.create(), this.arguments, this.suggestors); - if (BetterConfigImpl.getModConfigs().putIfAbsent(this.modId, modConfig) == null) { - BetterConfigInternals.init(modConfig); - return; - } - throw new IllegalArgumentException(this.modId); - } -} diff --git a/src/main/java/dev/xpple/betterconfig/command/AbstractConfigCommand.java b/src/main/java/dev/xpple/betterconfig/command/AbstractConfigCommand.java deleted file mode 100644 index 916b432..0000000 --- a/src/main/java/dev/xpple/betterconfig/command/AbstractConfigCommand.java +++ /dev/null @@ -1,193 +0,0 @@ -package dev.xpple.betterconfig.command; - -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import dev.xpple.betterconfig.api.Config; -import dev.xpple.betterconfig.command.suggestion.EnumSuggestionProvider; -import dev.xpple.betterconfig.impl.BetterConfigImpl; -import dev.xpple.betterconfig.impl.ModConfigImpl; -import dev.xpple.betterconfig.util.CheckedFunction; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.command.CommandSource; -import net.minecraft.text.Text; - -import java.lang.reflect.Type; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.function.Predicate; - -import static com.mojang.brigadier.arguments.StringArgumentType.*; - -public abstract class AbstractConfigCommand { - - private static final DynamicCommandExceptionType INVALID_ENUM_EXCEPTION = new DynamicCommandExceptionType(value -> Text.translatable("argument.enum.invalid", value)); - - private final String rootLiteral; - - protected AbstractConfigCommand(String rootLiteral) { - this.rootLiteral = rootLiteral; - } - - protected LiteralArgumentBuilder create(CommandRegistryAccess registryAccess) { - LiteralArgumentBuilder root = LiteralArgumentBuilder.literal(this.rootLiteral); - for (ModConfigImpl modConfig : BetterConfigImpl.getModConfigs().values()) { - Map> literals = new HashMap<>(); - for (String config : modConfig.getConfigs().keySet()) { - @SuppressWarnings("unchecked") - Predicate condition = (Predicate) modConfig.getConditions().get(config); - LiteralArgumentBuilder configLiteral = LiteralArgumentBuilder.literal(config).requires(condition); - literals.put(config, configLiteral); - - configLiteral.then(LiteralArgumentBuilder.literal("get").executes(ctx -> get(ctx.getSource(), modConfig, config))); - configLiteral.then(LiteralArgumentBuilder.literal("reset").executes(ctx -> reset(ctx.getSource(), modConfig, config))); - } - - modConfig.getComments().forEach((config, comment) -> literals.get(config).then(LiteralArgumentBuilder.literal("comment").executes(ctx -> comment(ctx.getSource(), config, comment)))); - modConfig.getSetters().keySet().forEach(config -> { - Config annotation = modConfig.getAnnotations().get(config); - Config.Setter setter = annotation.setter(); - Class type = setter.type() == Config.EMPTY.class ? modConfig.getType(config) : setter.type(); - var argumentFunction = modConfig.getArgument(type); - var suggestorPair = modConfig.getSuggestor(type); - if (argumentFunction != null) { - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", argumentFunction.apply(registryAccess)); - subCommand.executes(ctx -> set(ctx.getSource(), modConfig, config, ctx.getArgument("value", type))); - literals.get(config).then(LiteralArgumentBuilder.literal("set").then(subCommand)); - } else if (suggestorPair != null) { - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", greedyString()); - //noinspection unchecked - subCommand.suggests((SuggestionProvider) suggestorPair.left()).executes(ctx -> set(ctx.getSource(), modConfig, config, suggestorPair.right().apply(ctx, "value"))); - literals.get(config).then(LiteralArgumentBuilder.literal("set").then(subCommand)); - } else if (type.isEnum()) { - //noinspection rawtypes, unchecked - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", string()).suggests(new EnumSuggestionProvider<>((Class) type)); - subCommand.executes(ctx -> { - String value = getString(ctx, "value"); - return set(ctx.getSource(), modConfig, config, Arrays.stream(type.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> INVALID_ENUM_EXCEPTION.create(value))); - }); - literals.get(config).then(LiteralArgumentBuilder.literal("set").then(subCommand)); - } - }); - modConfig.getAdders().keySet().forEach(config -> { - Config annotation = modConfig.getAnnotations().get(config); - Config.Adder adder = annotation.adder(); - Class type = adder.type() == Config.EMPTY.class ? (Class) modConfig.getParameterTypes(config)[0] : adder.type(); - var argumentFunction = modConfig.getArgument(type); - var suggestorPair = modConfig.getSuggestor(type); - if (argumentFunction != null) { - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", argumentFunction.apply(registryAccess)); - subCommand.executes(ctx -> add(ctx.getSource(), modConfig, config, ctx.getArgument("value", type))); - literals.get(config).then(LiteralArgumentBuilder.literal("add").then(subCommand)); - } else if (suggestorPair != null) { - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", greedyString()); - //noinspection unchecked - subCommand.suggests((SuggestionProvider) suggestorPair.left()).executes(ctx -> add(ctx.getSource(), modConfig, config, suggestorPair.right().apply(ctx, "value"))); - literals.get(config).then(LiteralArgumentBuilder.literal("add").then(subCommand)); - } else if (type.isEnum()) { - //noinspection rawtypes, unchecked - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", string()).suggests(new EnumSuggestionProvider<>((Class) type)); - subCommand.executes(ctx -> { - String value = getString(ctx, "value"); - return add(ctx.getSource(), modConfig, config, Arrays.stream(type.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> INVALID_ENUM_EXCEPTION.create(value))); - }); - literals.get(config).then(LiteralArgumentBuilder.literal("add").then(subCommand)); - } - }); - modConfig.getPutters().keySet().forEach(config -> { - Config annotation = modConfig.getAnnotations().get(config); - Config.Putter putter = annotation.putter(); - Type[] types = modConfig.getParameterTypes(config); - Class keyType = putter.keyType() == Config.EMPTY.class ? (Class) types[0] : putter.keyType(); - RequiredArgumentBuilder subCommand; - CheckedFunction, ?, CommandSyntaxException> getKey; - var keyArgumentFunction = modConfig.getArgument(keyType); - var keySuggestorPair = modConfig.getSuggestor(keyType); - if (keyArgumentFunction != null) { - subCommand = RequiredArgumentBuilder.argument("key", keyArgumentFunction.apply(registryAccess)); - getKey = ctx -> ctx.getArgument("key", keyType); - } else if (keySuggestorPair != null) { - subCommand = RequiredArgumentBuilder.argument("key", string()); - //noinspection unchecked - subCommand.suggests((SuggestionProvider) keySuggestorPair.left()); - getKey = ctx -> keySuggestorPair.right().apply(ctx, "key"); - } else if (keyType.isEnum()) { - //noinspection rawtypes, unchecked - subCommand = RequiredArgumentBuilder.argument("key", string()).suggests(new EnumSuggestionProvider<>((Class) keyType)); - getKey = ctx -> { - String value = getString(ctx, "key"); - return Arrays.stream(keyType.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> INVALID_ENUM_EXCEPTION.create(value)); - }; - } else { - return; - } - Class valueType = putter.valueType() == Config.EMPTY.class ? (Class) types[1] : putter.valueType(); - var valueArgumentFunction = modConfig.getArgument(valueType); - var valueSuggestorPair = modConfig.getSuggestor(valueType); - if (valueArgumentFunction != null) { - RequiredArgumentBuilder subSubCommand = RequiredArgumentBuilder.argument("value", valueArgumentFunction.apply(registryAccess)); - subSubCommand.executes(ctx -> put(ctx.getSource(), modConfig, config, getKey.apply(ctx), ctx.getArgument("value", valueType))); - literals.get(config).then(LiteralArgumentBuilder.literal("put").then(subCommand.then(subSubCommand))); - } else if (valueSuggestorPair != null) { - RequiredArgumentBuilder subSubCommand = RequiredArgumentBuilder.argument("value", greedyString()); - //noinspection unchecked - subSubCommand.suggests((SuggestionProvider) valueSuggestorPair.left()).executes(ctx -> put(ctx.getSource(), modConfig, config, getKey.apply(ctx), valueSuggestorPair.right().apply(ctx, "value"))); - literals.get(config).then(LiteralArgumentBuilder.literal("put").then(subCommand.then(subSubCommand))); - } else if (valueType.isEnum()) { - //noinspection rawtypes, unchecked - RequiredArgumentBuilder subSubCommand = RequiredArgumentBuilder.argument("value", string()).suggests(new EnumSuggestionProvider<>((Class) valueType)); - subCommand.executes(ctx -> { - String value = getString(ctx, "value"); - return put(ctx.getSource(), modConfig, config, getKey.apply(ctx), Arrays.stream(valueType.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> INVALID_ENUM_EXCEPTION.create(value))); - }); - literals.get(config).then(LiteralArgumentBuilder.literal("put").then(subCommand.then(subSubCommand))); - } - }); - modConfig.getRemovers().keySet().forEach(config -> { - Config annotation = modConfig.getAnnotations().get(config); - Config.Remover remover = annotation.remover(); - Class type = remover.type() == Config.EMPTY.class ? (Class) modConfig.getParameterTypes(config)[0] : remover.type(); - var argumentFunction = modConfig.getArgument(type); - var suggestorPair = modConfig.getSuggestor(type); - if (argumentFunction != null) { - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", argumentFunction.apply(registryAccess)); - subCommand.executes(ctx -> remove(ctx.getSource(), modConfig, config, ctx.getArgument("value", type))); - literals.get(config).then(LiteralArgumentBuilder.literal("remove").then(subCommand)); - } else if (suggestorPair != null) { - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", greedyString()); - //noinspection unchecked - subCommand.suggests((SuggestionProvider) suggestorPair.left()).executes(ctx -> remove(ctx.getSource(), modConfig, config, suggestorPair.right().apply(ctx, "value"))); - literals.get(config).then(LiteralArgumentBuilder.literal("remove").then(subCommand)); - } else if (type.isEnum()) { - //noinspection rawtypes, unchecked - RequiredArgumentBuilder subCommand = RequiredArgumentBuilder.argument("value", string()).suggests(new EnumSuggestionProvider<>((Class) type)); - subCommand.executes(ctx -> { - String value = getString(ctx, "value"); - return remove(ctx.getSource(), modConfig, config, Arrays.stream(type.getEnumConstants()).filter(c -> ((Enum) c).name().equals(value)).findAny().orElseThrow(() -> INVALID_ENUM_EXCEPTION.create(value))); - }); - literals.get(config).then(LiteralArgumentBuilder.literal("remove").then(subCommand)); - } - }); - literals.values().forEach(literal -> root.then(LiteralArgumentBuilder.literal(modConfig.getModId()).then(literal))); - } - return root; - } - - protected abstract int comment(S source, String config, String comment); - - protected abstract int get(S source, ModConfigImpl modConfig, String config); - - protected abstract int reset(S source, ModConfigImpl modConfig, String config); - - protected abstract int set(S source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException; - - protected abstract int add(S source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException; - - protected abstract int put(S source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException; - - protected abstract int remove(S source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException; -} diff --git a/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java b/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java deleted file mode 100644 index 45330df..0000000 --- a/src/main/java/dev/xpple/betterconfig/command/ConfigCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package dev.xpple.betterconfig.command; - -import com.mojang.brigadier.Command; -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import dev.xpple.betterconfig.impl.ModConfigImpl; -import net.minecraft.command.CommandRegistryAccess; -import net.minecraft.server.command.ServerCommandSource; -import net.minecraft.text.Text; - -public class ConfigCommand extends AbstractConfigCommand { - private ConfigCommand() { - super("config"); - } - - public static void register(CommandDispatcher dispatcher, CommandRegistryAccess registryAccess) { - dispatcher.register(new ConfigCommand().create(registryAccess).requires(source -> source.hasPermissionLevel(4))); - } - - @Override - protected int comment(ServerCommandSource source, String config, String comment) { - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.comment", "Comment for %s:", config), false); - source.sendFeedback(() -> Text.of(comment), false); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int get(ServerCommandSource source, ModConfigImpl modConfig, String config) { - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.get", "%s is currently set to %s.", config, modConfig.asString(config)), false); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int reset(ServerCommandSource source, ModConfigImpl modConfig, String config) { - modConfig.reset(config); - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.reset", "%s has been reset to %s.", config, modConfig.asString(config)), true); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int set(ServerCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { - modConfig.set(config, value); - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.set", "%s has been set to %s.", config, modConfig.asString(config)), true); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int add(ServerCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { - modConfig.add(config, value); - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.add", "%s has been added to %s.", modConfig.asString(value), config), true); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int put(ServerCommandSource source, ModConfigImpl modConfig, String config, Object key, Object value) throws CommandSyntaxException { - modConfig.put(config, key, value); - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.put", "The mapping %s=%s has been added to %s.", modConfig.asString(key), modConfig.asString(value), config), true); - return Command.SINGLE_SUCCESS; - } - - @Override - protected int remove(ServerCommandSource source, ModConfigImpl modConfig, String config, Object value) throws CommandSyntaxException { - modConfig.remove(config, value); - source.sendFeedback(() -> Text.translatableWithFallback("betterconfig.commands.config.remove", "%s has been removed from %s.", modConfig.asString(value), config), true); - return Command.SINGLE_SUCCESS; - } -} diff --git a/src/main/java/dev/xpple/betterconfig/command/suggestion/EnumSuggestionProvider.java b/src/main/java/dev/xpple/betterconfig/command/suggestion/EnumSuggestionProvider.java deleted file mode 100644 index fcd87d3..0000000 --- a/src/main/java/dev/xpple/betterconfig/command/suggestion/EnumSuggestionProvider.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.xpple.betterconfig.command.suggestion; - -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.minecraft.command.CommandSource; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -public class EnumSuggestionProvider> implements SuggestionProvider { - - private final Class enumClass; - - public EnumSuggestionProvider(Class enumClass) { - this.enumClass = enumClass; - } - - @Override - public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { - return CommandSource.suggestMatching(Arrays.stream(this.enumClass.getEnumConstants()).map(Enum::name), builder); - } -} diff --git a/src/main/java/dev/xpple/betterconfig/util/Pair.java b/src/main/java/dev/xpple/betterconfig/util/Pair.java deleted file mode 100644 index d63c249..0000000 --- a/src/main/java/dev/xpple/betterconfig/util/Pair.java +++ /dev/null @@ -1,4 +0,0 @@ -package dev.xpple.betterconfig.util; - -public record Pair(L left, R right) { -} diff --git a/src/testmod/java/dev/xpple/betterconfig/BlockSuggestionProvider.java b/src/testmod/java/dev/xpple/betterconfig/BlockSuggestionProvider.java deleted file mode 100644 index 7e608c1..0000000 --- a/src/testmod/java/dev/xpple/betterconfig/BlockSuggestionProvider.java +++ /dev/null @@ -1,18 +0,0 @@ -package dev.xpple.betterconfig; - -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.minecraft.command.CommandSource; -import net.minecraft.registry.Registries; -import net.minecraft.server.command.ServerCommandSource; - -import java.util.concurrent.CompletableFuture; - -class BlockSuggestionProvider implements SuggestionProvider { - @Override - public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) { - return CommandSource.suggestIdentifiers(Registries.BLOCK.getIds(), builder); - } -} diff --git a/src/testmod/java/dev/xpple/betterconfig/TestMod.java b/src/testmod/java/dev/xpple/betterconfig/TestMod.java deleted file mode 100644 index 531d1dc..0000000 --- a/src/testmod/java/dev/xpple/betterconfig/TestMod.java +++ /dev/null @@ -1,34 +0,0 @@ -package dev.xpple.betterconfig; - -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import dev.xpple.betterconfig.api.ModConfigBuilder; -import net.fabricmc.api.DedicatedServerModInitializer; -import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; -import net.minecraft.block.Block; -import net.minecraft.command.argument.BlockStateArgument; -import net.minecraft.command.argument.BlockStateArgumentType; -import net.minecraft.command.argument.serialize.ConstantArgumentSerializer; -import net.minecraft.registry.Registries; -import net.minecraft.util.Identifier; - -public class TestMod implements DedicatedServerModInitializer { - @Override - public void onInitializeServer() { - //ArgumentTypeRegistry.registerArgumentType(new Identifier("testmod", "block"), BlockArgumentType.class, ConstantArgumentSerializer.of(BlockArgumentType::block)); - - new ModConfigBuilder("testmod", Configs.class) - .registerTypeHierarchy(Block.class, new BlockAdapter(), new BlockSuggestionProvider(), (ctx, name) -> { - String blockString = ctx.getArgument(name, String.class); - Identifier blockId = Identifier.tryParse(blockString); - if (blockId == null) { - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); - } - if (Registries.BLOCK.containsId(blockId)) { - return Registries.BLOCK.get(blockId); - } - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); - }) - .registerTypeHierarchy(BlockStateArgument.class, new BlockStateAdapter(), BlockStateArgumentType::blockState) - .build(); - } -} diff --git a/src/testmod/java/dev/xpple/betterconfig/TestModClient.java b/src/testmod/java/dev/xpple/betterconfig/TestModClient.java deleted file mode 100644 index 0faf195..0000000 --- a/src/testmod/java/dev/xpple/betterconfig/TestModClient.java +++ /dev/null @@ -1,17 +0,0 @@ -package dev.xpple.betterconfig; - -import dev.xpple.betterconfig.api.ModConfigBuilder; -import net.fabricmc.api.ClientModInitializer; -import net.minecraft.block.Block; -import net.minecraft.command.argument.BlockStateArgument; -import net.minecraft.command.argument.BlockStateArgumentType; - -public class TestModClient implements ClientModInitializer { - @Override - public void onInitializeClient() { - new ModConfigBuilder("testmodclient", Configs.class) - .registerTypeHierarchy(Block.class, new BlockAdapter(), BlockArgumentType::block) - .registerTypeHierarchy(BlockStateArgument.class, new BlockStateAdapter(), BlockStateArgumentType::blockState) - .build(); - } -}