Skip to content

Commit

Permalink
Implement custom chat representations for configs (#16)
Browse files Browse the repository at this point in the history
* Implement custom chat representations for configs

* Make JavaDoc clearer

* Implement Component type in both fabric and paper modules to avoid accidental mistakes

* Actually implement platform-specific API interface

* Make string argument nullable for stringToComponent

* Tighten type bound

* Use correct Nullable type
  • Loading branch information
xpple authored Jan 15, 2025
1 parent 6ec39dd commit 5a318b5
Show file tree
Hide file tree
Showing 28 changed files with 334 additions and 101 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.xpple.betterconfig.api;

import org.jetbrains.annotations.ApiStatus;

/**
* @param <P> the chat component type: {@link net.minecraft.network.chat.Component} on Fabric
* and {@link net.kyori.adventure.text.Component} on Paper
*/
@ApiStatus.Internal
public interface AbstractBetterConfigAPI<P> {
/**
* Get the configurations for the specified mod.
* @param modId the mod's identifier
* @return the configurations for the specified mod
*/
ModConfig<P> getModConfig(String modId);
}

This file was deleted.

2 changes: 2 additions & 0 deletions common/src/main/java/dev/xpple/betterconfig/api/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@
public @interface Config {
String comment() default "";

String chatRepresentation() default "";

Setter setter() default @Setter;
Adder adder() default @Adder;
Putter putter() default @Putter;
Expand Down
18 changes: 16 additions & 2 deletions common/src/main/java/dev/xpple/betterconfig/api/ModConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@

import java.nio.file.Path;

public interface ModConfig {
/**
* @param <P> the chat component type: {@link net.minecraft.network.chat.Component} on Fabric
* and {@link net.kyori.adventure.text.Component} on Paper
*/
public interface ModConfig<P> {
/**
* Get the identifier of the mod of this configuration.
* @return the mod's identifier
Expand All @@ -27,16 +31,26 @@ public interface ModConfig {
* Get a config value based on the key.
* @param config the config's key
* @return the config value
* @throws IllegalArgumentException when there is no config associated to this key
*/
Object get(String config);

/**
* Get the string representation for this config key.
* Get the JSON serialised string representation for this config key.
* @param config the config's key
* @return the string representation
* @throws IllegalArgumentException when there is no config associated to this key
*/
String asString(String config);

/**
* Get the chat component representation for this config key.
* @param config the config's key
* @return the chat component representation
* @throws IllegalArgumentException when there is no config associated to this key
*/
P asComponent(String config);

/**
* Reset the value for the config associated with this config key.
* @param config the config's key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
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.AbstractBetterConfigImpl;
import dev.xpple.betterconfig.impl.BetterConfigInternals;
import dev.xpple.betterconfig.impl.ModConfigImpl;

Expand All @@ -13,6 +13,13 @@
import java.util.function.Function;
import java.util.function.Supplier;

/**
* @param <S> the command source type: {@link net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource}
* on Fabric clients, {@link net.minecraft.commands.CommandSourceStack} on Fabric servers
* and {@link io.papermc.paper.command.brigadier.CommandSourceStack} on Paper.
* @param <C> the command build context: {@link net.minecraft.commands.CommandBuildContext} on Fabric
* and unused on Paper.
*/
public final class ModConfigBuilder<S, C> {

private final String modId;
Expand Down Expand Up @@ -117,8 +124,8 @@ public ModConfigBuilder<S, C> registerGlobalChangeHook(Consumer<GlobalChangeEven
* @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.globalChangeHook);
if (BetterConfigImpl.getModConfigs().putIfAbsent(this.modId, modConfig) == null) {
ModConfigImpl<?, ?, ?> modConfig = new ModConfigImpl<>(this.modId, this.configsClass, this.builder.create(), this.arguments, this.globalChangeHook);
if (AbstractBetterConfigImpl.getModConfigs().putIfAbsent(this.modId, modConfig) == null) {
BetterConfigInternals.init(modConfig);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,17 @@

import static com.mojang.brigadier.arguments.StringArgumentType.*;

public abstract class AbstractConfigCommand<S, C> {
public abstract class AbstractConfigCommand<S, C, P> {

private final String rootLiteral;

protected AbstractConfigCommand(String rootLiteral) {
this.rootLiteral = rootLiteral;
}

protected final LiteralArgumentBuilder<S> create(Collection<? extends ModConfigImpl<S, C>> modConfigs, C buildContext) {
protected final LiteralArgumentBuilder<S> create(Collection<? extends ModConfigImpl<S, C, P>> modConfigs, C buildContext) {
LiteralArgumentBuilder<S> root = LiteralArgumentBuilder.literal(this.rootLiteral);
for (ModConfigImpl<S, C> modConfig : modConfigs) {
for (ModConfigImpl<S, C, P> modConfig : modConfigs) {
LiteralArgumentBuilder<S> identifierLiteral = LiteralArgumentBuilder.literal(modConfig.getModId());
for (String config : modConfig.getConfigs().keySet()) {
Predicate<S> condition = modConfig.getConditions().get(config);
Expand Down Expand Up @@ -150,15 +150,15 @@ protected final LiteralArgumentBuilder<S> create(Collection<? extends ModConfigI

protected abstract int comment(S source, String config, String comment);

protected abstract int get(S source, ModConfigImpl<S, C> modConfig, String config);
protected abstract int get(S source, ModConfigImpl<S, C, P> modConfig, String config);

protected abstract int reset(S source, ModConfigImpl<S, C> modConfig, String config);
protected abstract int reset(S source, ModConfigImpl<S, C, P> modConfig, String config);

protected abstract int set(S source, ModConfigImpl<S, C> modConfig, String config, Object value) throws CommandSyntaxException;
protected abstract int set(S source, ModConfigImpl<S, C, P> modConfig, String config, Object value) throws CommandSyntaxException;

protected abstract int add(S source, ModConfigImpl<S, C> modConfig, String config, Object value) throws CommandSyntaxException;
protected abstract int add(S source, ModConfigImpl<S, C, P> modConfig, String config, Object value) throws CommandSyntaxException;

protected abstract int put(S source, ModConfigImpl<S, C> modConfig, String config, Object key, Object value) throws CommandSyntaxException;
protected abstract int put(S source, ModConfigImpl<S, C, P> modConfig, String config, Object key, Object value) throws CommandSyntaxException;

protected abstract int remove(S source, ModConfigImpl<S, C> modConfig, String config, Object value) throws CommandSyntaxException;
protected abstract int remove(S source, ModConfigImpl<S, C, P> modConfig, String config, Object value) throws CommandSyntaxException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.xpple.betterconfig.impl;

import dev.xpple.betterconfig.api.AbstractBetterConfigAPI;
import dev.xpple.betterconfig.api.ModConfig;

import java.util.HashMap;
import java.util.Map;

public abstract class AbstractBetterConfigImpl<P> implements AbstractBetterConfigAPI<P> {

private static final Map<String, ModConfigImpl<?, ?, ?>> modConfigs = new HashMap<>();

@SuppressWarnings("unchecked")
@Override
public ModConfig<P> getModConfig(String modId) {
ModConfig<P> modConfig = (ModConfig<P>) modConfigs.get(modId);
if (modConfig == null) {
throw new IllegalArgumentException(modId);
}
return modConfig;
}

public static Map<String, ModConfigImpl<?, ?, ?>> getModConfigs() {
return modConfigs;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@
import java.util.Map;
import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Supplier;

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();
Expand Down Expand Up @@ -79,6 +80,8 @@ public static void init(ModConfigImpl<?, ?> modConfig) {

initCondition(modConfig, annotation.condition(), fieldName);

initChatRepresentation(modConfig, field, annotation.chatRepresentation());

if (annotation.readOnly()) {
continue;
}
Expand All @@ -104,7 +107,36 @@ public static void init(ModConfigImpl<?, ?> modConfig) {
}
}

private static void initCondition(ModConfigImpl<?, ?> modConfig, String condition, String fieldName) {
private static void initChatRepresentation(ModConfigImpl<?, ?, ?> modConfig, Field field, String chatRepresentationMethodName) {
if (chatRepresentationMethodName.isEmpty()) {
return;
}
Method chatRepresentationMethod;
try {
chatRepresentationMethod = modConfig.getConfigsClass().getDeclaredMethod(chatRepresentationMethodName);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
Class<?> componentClass = Platform.current.getComponentClass();
if (chatRepresentationMethod.getReturnType() != componentClass) {
throw new AssertionError("Chat representation method '" + chatRepresentationMethodName + "' does not return Component");
}
if (!Modifier.isStatic(chatRepresentationMethod.getModifiers())) {
throw new AssertionError("Chat representation method '" + chatRepresentationMethodName + "' is not static");
}
chatRepresentationMethod.setAccessible(true);

//noinspection rawtypes, unchecked
modConfig.getChatRepresentations().put(field.getName(), (Supplier) () -> {
try {
return chatRepresentationMethod.invoke(null);
} catch (ReflectiveOperationException e) {
throw new AssertionError(e);
}
});
}

private static void initCondition(ModConfigImpl<?, ?, ?> modConfig, String condition, String fieldName) {
if (condition.isEmpty()) {
modConfig.getConditions().put(fieldName, source -> true);
return;
Expand Down Expand Up @@ -151,7 +183,7 @@ private static void initCondition(ModConfigImpl<?, ?> modConfig, String conditio
}
}

private static BiConsumer<Object, Object> initOnChange(ModConfigImpl<?, ?> modConfig, Field field, String onChangeMethodName) {
private static BiConsumer<Object, Object> initOnChange(ModConfigImpl<?, ?, ?> modConfig, Field field, String onChangeMethodName) {
if (onChangeMethodName.isEmpty()) {
BiConsumer<Object, Object> onChange = (oldValue, newValue) -> {};
modConfig.getOnChangeCallbacks().put(field.getName(), onChange);
Expand All @@ -176,7 +208,7 @@ private static BiConsumer<Object, Object> initOnChange(ModConfigImpl<?, ?> modCo
return onChange;
}

private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
private static void initCollection(ModConfigImpl<?, ?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
String fieldName = field.getName();
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
Config.Adder adder = annotation.adder();
Expand Down Expand Up @@ -257,7 +289,7 @@ private static void initCollection(ModConfigImpl<?, ?> modConfig, Field field, C
}
}

private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
private static void initMap(ModConfigImpl<?, ?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
String fieldName = field.getName();
Type[] types = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
Config.Adder adder = annotation.adder();
Expand Down Expand Up @@ -363,7 +395,7 @@ private static void initMap(ModConfigImpl<?, ?> modConfig, Field field, Config a
}
}

private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
private static void initObject(ModConfigImpl<?, ?, ?> modConfig, Field field, Config annotation, BiConsumer<Object, Object> onChange) {
String fieldName = field.getName();
Config.Setter setter = annotation.setter();
String setterMethodName = setter.value();
Expand Down Expand Up @@ -399,7 +431,7 @@ private static void initObject(ModConfigImpl<?, ?> modConfig, Field field, Confi
}
}

static void onChange(ModConfigImpl<?, ?> modConfig, Field field, CheckedRunnable<ReflectiveOperationException> updater, BiConsumer<Object, Object> onChange) throws ReflectiveOperationException {
static void onChange(ModConfigImpl<?, ?, ?> modConfig, Field field, CheckedRunnable<ReflectiveOperationException> updater, BiConsumer<Object, Object> onChange) throws ReflectiveOperationException {
Object oldValue = modConfig.deepCopy(field.get(null), field.getGenericType());
updater.run();
Object newValue = modConfig.deepCopy(field.get(null), field.getGenericType());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class ModConfigImpl<S, C> implements ModConfig {
public class ModConfigImpl<S, C, P> implements ModConfig<P> {

private static final Map<Class<?>, Function<?, ArgumentType<?>>> defaultArguments = ImmutableMap.<Class<?>, Function<?, ArgumentType<?>>>builder()
.put(boolean.class, buildContext -> BoolArgumentType.bool())
Expand All @@ -53,6 +54,7 @@ public class ModConfigImpl<S, C> implements ModConfig {
private final Map<String, Object> defaults = new HashMap<>();
private final Map<String, String> comments = new HashMap<>();
private final Map<String, Predicate<S>> conditions = new HashMap<>();
private final Map<String, Supplier<P>> chatRepresentations = new HashMap<>();
private final Map<String, CheckedConsumer<Object, CommandSyntaxException>> setters = new HashMap<>();
private final Map<String, CheckedConsumer<Object, CommandSyntaxException>> adders = new HashMap<>();
private final Map<String, CheckedBiConsumer<Object, Object, CommandSyntaxException>> putters = new HashMap<>();
Expand Down Expand Up @@ -111,6 +113,10 @@ public Map<String, Predicate<S>> getConditions() {
return this.conditions;
}

public Map<String, Supplier<P>> getChatRepresentations() {
return this.chatRepresentations;
}

public Map<String, CheckedConsumer<Object, CommandSyntaxException>> getSetters() {
return this.setters;
}
Expand Down Expand Up @@ -168,6 +174,16 @@ public String asString(Object value) {
return this.inlineGson.toJson(value);
}

@Override
public P asComponent(String config) {
Object value = this.get(config);
Supplier<P> chatRepresentation = this.chatRepresentations.get(config);
if (chatRepresentation == null) {
return Platform.current.stringToComponent(this.asString(value));
}
return chatRepresentation.get();
}

@Override
public void reset(String config) {
Field field = this.configs.get(config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.mojang.brigadier.exceptions.DynamicCommandExceptionType;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import org.jetbrains.annotations.Nullable;

import java.nio.file.Path;
import java.util.ServiceLoader;
Expand All @@ -15,5 +16,9 @@ public interface Platform {

<S, T extends Enum<T>> SuggestionProvider<S> enumSuggestionProvider(Class<T> type);

Class<?> getComponentClass();

<P> P stringToComponent(@Nullable String string);

Platform current = ServiceLoader.load(Platform.class, Platform.class.getClassLoader()).iterator().next();
}
Loading

0 comments on commit 5a318b5

Please sign in to comment.