From 7c625e6ccafc81eef81b5c292c2e1b632f062f20 Mon Sep 17 00:00:00 2001 From: Siphalor Date: Fri, 31 Oct 2025 14:07:38 +0100 Subject: [PATCH] [coat-bridge] Implement first version of Tweed Coat bridge --- tweed5-minecraft/coat-bridge/build.gradle.kts | 15 +- .../coat-bridge/gradle.properties | 2 + .../bridge/api/ConfigScreenCreateParams.java | 19 + .../coat/bridge/api/TweedCoatAttributes.java | 11 + .../bridge/api/TweedCoatBridgeExtension.java | 26 ++ .../coat/bridge/api/TweedCoatMappers.java | 69 ++++ .../bridge/api/TweedCoatMappingUtils.java | 22 ++ .../TweedCoatEntryCreationContext.java | 20 + .../mapping/TweedCoatEntryMappingContext.java | 50 +++ .../mapping/TweedCoatEntryMappingResult.java | 61 +++ .../bridge/api/mapping/TweedCoatMapper.java | 8 + .../handler/BasicTweedCoatEntryHandler.java | 84 ++++ .../ConvertingTweedCoatEntryHandler.java | 65 +++ .../api/mapping/handler/package-info.java | 4 + .../coat/bridge/api/mapping/package-info.java | 4 + .../tweed5/coat/bridge/api/package-info.java | 4 + .../coat/bridge/impl/CoatEnumMaterial.java | 28 ++ .../impl/TweedCoatBridgeExtensionImpl.java | 83 ++++ .../bridge/impl/TweedCoatMappersImpl.java | 370 ++++++++++++++++++ .../tweed5/coat/bridge/impl/package-info.java | 6 + .../assets/tweed5_coat_bridge/lang/en_us.json | 3 + .../testmod/TweedCoatBridgeTestMod.java | 89 +++++ .../testmod/TweedCoatBridgeTestModConfig.java | 54 +++ .../lang/en_us.json | 8 + .../src/testmod/resources/fabric.mod.json | 10 + .../gradle/mc-1.21.10/mcLibs.versions.toml | 4 +- tweed5-minecraft/settings.gradle.kts | 1 + 27 files changed, 1118 insertions(+), 2 deletions(-) create mode 100644 tweed5-minecraft/coat-bridge/gradle.properties create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/ConfigScreenCreateParams.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatAttributes.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatBridgeExtension.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappers.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappingUtils.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryCreationContext.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingContext.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingResult.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatMapper.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/BasicTweedCoatEntryHandler.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/ConvertingTweedCoatEntryHandler.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/package-info.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/package-info.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/package-info.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/CoatEnumMaterial.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatBridgeExtensionImpl.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/package-info.java create mode 100644 tweed5-minecraft/coat-bridge/src/main/resources/assets/tweed5_coat_bridge/lang/en_us.json create mode 100644 tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestMod.java create mode 100644 tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestModConfig.java create mode 100644 tweed5-minecraft/coat-bridge/src/testmod/resources/assets/tweed5_coat_bridge_testmod/lang/en_us.json create mode 100644 tweed5-minecraft/coat-bridge/src/testmod/resources/fabric.mod.json diff --git a/tweed5-minecraft/coat-bridge/build.gradle.kts b/tweed5-minecraft/coat-bridge/build.gradle.kts index bda8516..1c5167f 100644 --- a/tweed5-minecraft/coat-bridge/build.gradle.kts +++ b/tweed5-minecraft/coat-bridge/build.gradle.kts @@ -4,5 +4,18 @@ plugins { } dependencies { - modImplementation(mcLibs.coat) + compileOnly("de.siphalor.tweed5:tweed5-core") + compileOnly("de.siphalor.tweed5:tweed5-attributes-extension") + compileOnly("de.siphalor.tweed5:tweed5-default-extensions") + compileOnly("de.siphalor.tweed5:tweed5-weaver-pojo") + modCompileOnly(mcLibs.coat) + + listOf("fabric-key-binding-api-v1", "fabric-resource-loader-v0").forEach { + modTestmodImplementation(fabricApi.module(it, mcLibs.versions.fabric.api.get())) + } + testmodImplementation(project(":tweed5-bundle", configuration = "minecraftModElements")) + modTestmodImplementation(mcLibs.coat) + modTestmodImplementation(mcLibs.amecs.api) + testmodImplementation(project(":tweed5-fabric-helper")) + testmodImplementation("de.siphalor.tweed5:tweed5-serde-hjson") } diff --git a/tweed5-minecraft/coat-bridge/gradle.properties b/tweed5-minecraft/coat-bridge/gradle.properties new file mode 100644 index 0000000..207effe --- /dev/null +++ b/tweed5-minecraft/coat-bridge/gradle.properties @@ -0,0 +1,2 @@ +module.name = Tweed 5 Coat Bridge +module.description = Provides a system that allows to generate a Coat config screen for a Tweed config. diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/ConfigScreenCreateParams.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/ConfigScreenCreateParams.java new file mode 100644 index 0000000..ee5667c --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/ConfigScreenCreateParams.java @@ -0,0 +1,19 @@ +package de.siphalor.tweed5.coat.bridge.api; + +import de.siphalor.tweed5.core.api.entry.ConfigEntry; +import lombok.Builder; +import lombok.Getter; +import net.minecraft.network.chat.Component; + +import java.util.function.Consumer; + +@Builder +@Getter +public class ConfigScreenCreateParams { + private final ConfigEntry rootEntry; + private final T currentValue; + private final T defaultValue; + private final Component title; + private final String translationKeyPrefix; + private final Consumer saveHandler; +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatAttributes.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatAttributes.java new file mode 100644 index 0000000..28501a7 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatAttributes.java @@ -0,0 +1,11 @@ +package de.siphalor.tweed5.coat.bridge.api; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TweedCoatAttributes { + public static final String BACKGROUND_TEXTURE = "backgroundTexture"; + public static final String TRANSLATION_KEY = "translationKey"; + public static final String ENUM_TRANSLATION_KEY = "enumTranslationKey"; +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatBridgeExtension.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatBridgeExtension.java new file mode 100644 index 0000000..ed058f5 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatBridgeExtension.java @@ -0,0 +1,26 @@ +package de.siphalor.tweed5.coat.bridge.api; + +import de.siphalor.coat.screen.ConfigScreen; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatMapper; +import de.siphalor.tweed5.coat.bridge.impl.TweedCoatBridgeExtensionImpl; +import de.siphalor.tweed5.core.api.extension.TweedExtension; + +public interface TweedCoatBridgeExtension extends TweedExtension { + Class DEFAULT = TweedCoatBridgeExtensionImpl.class; + String EXTENSION_ID = "coat-bridge"; + + //static Function, ConfigScreen> createConfigScreen(T value) { + // return configEntry -> configEntry.container().extension(TweedCoatBridgeExtension.class) + // .map(extension -> extension.createConfigScreen(configEntry, value)) + // .orElseThrow(() -> new IllegalStateException("No TweedCoatBridgeExtension present")); + //} + + void addMapper(TweedCoatMapper mapper); + + ConfigScreen createConfigScreen(ConfigScreenCreateParams params); + + @Override + default String getId() { + return EXTENSION_ID; + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappers.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappers.java new file mode 100644 index 0000000..71d1b12 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappers.java @@ -0,0 +1,69 @@ +package de.siphalor.tweed5.coat.bridge.api; + +import de.siphalor.coat.util.EnumeratedMaterial; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatMapper; +import de.siphalor.tweed5.coat.bridge.impl.TweedCoatMappersImpl; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import java.util.function.Function; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TweedCoatMappers { + public static TweedCoatMapper byteTextMapper() { + return TweedCoatMappersImpl.BYTE_TEXT_MAPPER; + } + + public static TweedCoatMapper shortTextMapper() { + return TweedCoatMappersImpl.SHORT_TEXT_MAPPER; + } + + public static TweedCoatMapper integerTextMapper() { + return TweedCoatMappersImpl.INTEGER_TEXT_MAPPER; + } + + public static TweedCoatMapper longTextMapper() { + return TweedCoatMappersImpl.LONG_TEXT_MAPPER; + } + + public static TweedCoatMapper floatTextMapper() { + return TweedCoatMappersImpl.FLOAT_TEXT_MAPPER; + } + + public static TweedCoatMapper doubleTextMapper() { + return TweedCoatMappersImpl.DOUBLE_TEXT_MAPPER; + } + + public static TweedCoatMapper booleanCheckboxMapper() { + return TweedCoatMappersImpl.BOOLEAN_CHECKBOX_MAPPER; + } + + public static TweedCoatMapper stringTextMapper() { + return TweedCoatMappersImpl.STRING_TEXT_MAPPER; + } + + public static > TweedCoatMapper enumCycleButtonMapper() { + //noinspection unchecked + return (TweedCoatMapper) TweedCoatMappersImpl.ENUM_CYCLE_BUTTON_MAPPER; + } + + public static TweedCoatMapper enumeratedMaterialCycleButtonMapper( + Class valueClass, + EnumeratedMaterial material + ) { + return new TweedCoatMappersImpl.EnumeratedMaterialCycleButtonMapper<>(valueClass, material); + } + + public static TweedCoatMapper compoundCategoryMapper() { + return TweedCoatMappersImpl.COMPOUND_CATEGORY_MAPPER; + } + + public static TweedCoatMapper convertingTextMapper( + Class valueClass, + Function textMapper, + Function textParser + ) { + //noinspection unchecked + return TweedCoatMappersImpl.convertingTextMapper(new Class[]{valueClass}, textMapper, textParser); + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappingUtils.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappingUtils.java new file mode 100644 index 0000000..6714bc6 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/TweedCoatMappingUtils.java @@ -0,0 +1,22 @@ +package de.siphalor.tweed5.coat.bridge.api; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.MutableComponent; +import org.jspecify.annotations.Nullable; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class TweedCoatMappingUtils { + public static MutableComponent translatableComponentWithFallback(String translationKey, @Nullable String fallback) { + return Component.translatableWithFallback(translationKey, fallback == null ? "" : fallback); + // FIXME + //if (I18n.exists(translationKey)) { + // return Component.translatable(translationKey); + //} else if (fallback != null) { + // return Component.literal(fallback); + //} else { + // return Component.empty(); + //} + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryCreationContext.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryCreationContext.java new file mode 100644 index 0000000..519bad7 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryCreationContext.java @@ -0,0 +1,20 @@ +package de.siphalor.tweed5.coat.bridge.api.mapping; + +import de.siphalor.tweed5.core.api.entry.ConfigEntry; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.jspecify.annotations.Nullable; + +import java.util.function.Consumer; + +@Builder +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +public class TweedCoatEntryCreationContext { + private final ConfigEntry entry; + private final T currentValue; + private final T defaultValue; + private final @Nullable Consumer parentSaveHandler; +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingContext.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingContext.java new file mode 100644 index 0000000..f84b2e4 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingContext.java @@ -0,0 +1,50 @@ +package de.siphalor.tweed5.coat.bridge.api.mapping; + +import de.siphalor.tweed5.core.api.entry.ConfigEntry; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import org.jspecify.annotations.Nullable; + +@RequiredArgsConstructor +@Getter +public +class TweedCoatEntryMappingContext { + @Getter(AccessLevel.NONE) + private final TweedCoatMapper mappingDelegate; + private final String entryName; + private final String translationKeyPrefix; + private final @Nullable Class parentWidgetClass; + + public static Builder rootBuilder(TweedCoatMapper mappingDelegate, String translationKeyPrefix) { + return new Builder(mappingDelegate, "root", translationKeyPrefix); + } + + public TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext context) { + //noinspection unchecked,rawtypes + return (TweedCoatEntryMappingResult) mappingDelegate.mapEntry((ConfigEntry) entry, context); + } + + public Builder subContextBuilder(String entryName) { + return new Builder(mappingDelegate, entryName, translationKeyPrefix + "." + entryName); + } + + @Setter + public static class Builder { + private final TweedCoatMapper mappingDelegate; + private final String entryName; + private String translationKeyPrefix; + private @Nullable Class parentWidgetClass; + + private Builder(TweedCoatMapper mappingDelegate, String entryName, String translationKeyPrefix) { + this.mappingDelegate = mappingDelegate; + this.entryName = entryName; + this.translationKeyPrefix = translationKeyPrefix; + } + + public TweedCoatEntryMappingContext build() { + return new TweedCoatEntryMappingContext(mappingDelegate, entryName, translationKeyPrefix, parentWidgetClass); + } + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingResult.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingResult.java new file mode 100644 index 0000000..756e121 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatEntryMappingResult.java @@ -0,0 +1,61 @@ +package de.siphalor.tweed5.coat.bridge.api.mapping; + +import de.siphalor.coat.handler.ConfigEntryHandler; +import de.siphalor.coat.input.ConfigInput; +import de.siphalor.coat.screen.ConfigContentWidget; +import org.jspecify.annotations.Nullable; + +import java.util.function.Consumer; + +/** + * The result of a {@link TweedCoatMapper}. + *
+ * There are three types of results: + *
    + *
  1. The mapper isn't applicable to the entry.
  2. + *
  3. The mapper is applicable and provides methods for creating + * {@link ConfigInput} and {@link ConfigEntryHandler} instances for the config entry.
  4. + * FIXME + *
+ * + * + * @param the actual (Tweed) type of the entry + * @param the UI (coat) type of the entry + */ +public interface TweedCoatEntryMappingResult { + TweedCoatEntryMappingResult NOT_APPLICABLE = new TweedCoatEntryMappingResult() { + @Override + public boolean isApplicable() { + return false; + } + + @Override + public @Nullable ConfigInput createInput(TweedCoatEntryCreationContext context) { + return null; + } + + @Override + public @Nullable ConfigEntryHandler createHandler(TweedCoatEntryCreationContext context) { + return null; + } + + @Override + public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext context) { + return null; + } + + }; + + static TweedCoatEntryMappingResult notApplicable() { + //noinspection unchecked + return (TweedCoatEntryMappingResult) NOT_APPLICABLE; + } + + boolean isApplicable(); + + @Nullable ConfigInput createInput(TweedCoatEntryCreationContext context); + + @Nullable ConfigEntryHandler createHandler(TweedCoatEntryCreationContext context); + + @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext context); +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatMapper.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatMapper.java new file mode 100644 index 0000000..35d36a2 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/TweedCoatMapper.java @@ -0,0 +1,8 @@ +package de.siphalor.tweed5.coat.bridge.api.mapping; + +import de.siphalor.tweed5.core.api.entry.ConfigEntry; + +public interface TweedCoatMapper { + TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext context); + +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/BasicTweedCoatEntryHandler.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/BasicTweedCoatEntryHandler.java new file mode 100644 index 0000000..253f08b --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/BasicTweedCoatEntryHandler.java @@ -0,0 +1,84 @@ +package de.siphalor.tweed5.coat.bridge.api.mapping.handler; + +import de.siphalor.coat.handler.ConfigEntryHandler; +import de.siphalor.coat.handler.Message; +import de.siphalor.tweed5.coat.bridge.impl.TweedCoatMappersImpl; +import de.siphalor.tweed5.core.api.entry.ConfigEntry; +import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; +import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssueLevel; +import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationIssues; +import de.siphalor.tweed5.defaultextensions.validation.api.result.ValidationResult; +import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.Objects; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +public class BasicTweedCoatEntryHandler implements ConfigEntryHandler { + protected final ConfigEntry configEntry; + protected final T defaultValue; + protected final Consumer parentSaveHandler; + protected final ValidationExtension validationExtension; + + public BasicTweedCoatEntryHandler(ConfigEntry configEntry, T defaultValue, Consumer parentSaveHandler) { + this.configEntry = configEntry; + this.defaultValue = defaultValue; + this.parentSaveHandler = parentSaveHandler; + this.validationExtension = configEntry.container().extension(ValidationExtension.class) + .orElseThrow(() -> new IllegalStateException("No validation extension registered")); + } + + @Override + public T getDefault() { + return defaultValue; + } + + @Override + public Collection getMessages(T value) { + ValidationIssues issues = validationExtension.validate(configEntry, value); + return issues.issuesByPath().values().stream() + .flatMap(entryIssues -> entryIssues.issues().stream()) + .map(issue -> new Message( + mapLevel(issue.level()), + Component.literal(issue.message()) + )) + .collect(Collectors.toList()); + } + + private static Message.Level mapLevel(ValidationIssueLevel level) { + switch (level) { + case INFO: + return Message.Level.INFO; + case WARN: + return Message.Level.WARNING; + case ERROR: + return Message.Level.ERROR; + default: + throw new IllegalStateException("Unknown validation issue level " + level); + } + } + + @Override + public void save(T value) { + parentSaveHandler.accept(processSaveValue(value)); + } + + @Override + public Component asText(T value) { + return Component.literal(Objects.toString(value)); + } + + protected T processSaveValue(T value) { + ValidationResult validationResult = validationExtension.validateValueFlat(configEntry, value); + if (validationResult.hasError()) { + TweedCoatMappersImpl.log.warn( + "Failed to save value " + value + " because of issues: " + validationResult.issues() + + "; using default: " + defaultValue + " instead" + ); + return defaultValue; + } + return validationResult.value(); + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/ConvertingTweedCoatEntryHandler.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/ConvertingTweedCoatEntryHandler.java new file mode 100644 index 0000000..c87191f --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/ConvertingTweedCoatEntryHandler.java @@ -0,0 +1,65 @@ +package de.siphalor.tweed5.coat.bridge.api.mapping.handler; + +import de.siphalor.coat.handler.ConfigEntryHandler; +import de.siphalor.coat.handler.Message; +import lombok.RequiredArgsConstructor; +import lombok.extern.apachecommons.CommonsLog; +import net.minecraft.network.chat.Component; +import org.jspecify.annotations.Nullable; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; +import java.util.function.Function; + +@RequiredArgsConstructor +@CommonsLog +public class ConvertingTweedCoatEntryHandler implements ConfigEntryHandler { + private static final String CONVERSION_EXCEPTION_TEXT_KEY = "tweed5_coat_bridge.handler.conversion.exception"; + + private final ConfigEntryHandler inner; + private final Function toCoatMapper; + private final Function fromCoatMapper; + + @Override + public C getDefault() { + return toCoatMapper.apply(inner.getDefault()); + } + + @Override + public Collection getMessages(C value) { + try { + T innerValue = fromCoatMapper.apply(value); + return inner.getMessages(innerValue); + } catch (Exception e) { + return Collections.singletonList(new Message( + Message.Level.ERROR, + Component.translatable(CONVERSION_EXCEPTION_TEXT_KEY, e.getMessage()) + )); + } + } + + @Override + public void save(C value) { + inner.save(convertSaveValue(value)); + } + + protected T convertSaveValue(C value) { + try { + return fromCoatMapper.apply(value); + } catch (Exception e) { + log.warn( + "Failed to convert value " + + value + + " for saving, using default: " + + inner.getDefault(), e + ); + return inner.getDefault(); + } + } + + @Override + public Component asText(C value) { + return Component.literal(Objects.toString(value)); + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/package-info.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/package-info.java new file mode 100644 index 0000000..0953b9b --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/handler/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.siphalor.tweed5.coat.bridge.api.mapping.handler; + +import org.jspecify.annotations.NullMarked; diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/package-info.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/package-info.java new file mode 100644 index 0000000..34495ef --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/mapping/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.siphalor.tweed5.coat.bridge.api.mapping; + +import org.jspecify.annotations.NullMarked; diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/package-info.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/package-info.java new file mode 100644 index 0000000..342459a --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/api/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package de.siphalor.tweed5.coat.bridge.api; + +import org.jspecify.annotations.NullMarked; diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/CoatEnumMaterial.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/CoatEnumMaterial.java new file mode 100644 index 0000000..437ded9 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/CoatEnumMaterial.java @@ -0,0 +1,28 @@ +package de.siphalor.tweed5.coat.bridge.impl; + +import de.siphalor.coat.util.EnumeratedMaterial; +import lombok.RequiredArgsConstructor; +import net.minecraft.network.chat.Component; + +import java.util.Locale; + +import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponentWithFallback; + +@RequiredArgsConstructor +public class CoatEnumMaterial implements EnumeratedMaterial { + private final Class enumClass; + private final String textTranslationKeyPrefix; + + @Override + public T[] values() { + return enumClass.getEnumConstants(); + } + + @Override + public Component asText(T value) { + return translatableComponentWithFallback( + textTranslationKeyPrefix + "." + value.toString().toLowerCase(Locale.ROOT), + value.toString() + ); + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatBridgeExtensionImpl.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatBridgeExtensionImpl.java new file mode 100644 index 0000000..a4d9a3f --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatBridgeExtensionImpl.java @@ -0,0 +1,83 @@ +package de.siphalor.tweed5.coat.bridge.impl; + +import de.siphalor.coat.screen.ConfigContentWidget; +import de.siphalor.coat.screen.ConfigScreen; +import de.siphalor.tweed5.coat.bridge.api.ConfigScreenCreateParams; +import de.siphalor.tweed5.coat.bridge.api.TweedCoatBridgeExtension; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatEntryCreationContext; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatEntryMappingContext; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatEntryMappingResult; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatMapper; +import de.siphalor.tweed5.core.api.entry.ConfigEntry; +import de.siphalor.tweed5.core.api.extension.TweedExtensionSetupContext; +import de.siphalor.tweed5.patchwork.api.PatchworkPartAccess; +import net.minecraft.client.Minecraft; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TweedCoatBridgeExtensionImpl implements TweedCoatBridgeExtension { + private final PatchworkPartAccess customDataAccess; + private final List> mappers = new ArrayList<>(); + + public TweedCoatBridgeExtensionImpl(TweedExtensionSetupContext setupContext) { + customDataAccess = setupContext.registerEntryExtensionData(CustomData.class); + } + + @Override + public void addMapper(TweedCoatMapper mapper) { + mappers.add(mapper); + } + + @Override + public ConfigScreen createConfigScreen(ConfigScreenCreateParams params) { + Minecraft minecraft = Minecraft.getInstance(); + + TweedCoatEntryMappingContext mappingContext = TweedCoatEntryMappingContext.rootBuilder( + this::mapEntry, + params.translationKeyPrefix() + ).parentWidgetClass(ConfigScreen.class).build(); + + TweedCoatEntryMappingResult rootResult = mapEntry(params.rootEntry(), mappingContext); + if (!rootResult.isApplicable()) { + throw new IllegalStateException("Failed to map root entry"); + } + + T value = params.rootEntry().deepCopy(params.currentValue()); + TweedCoatEntryCreationContext creationContext = TweedCoatEntryCreationContext.builder() + .entry(params.rootEntry()) + .currentValue(value) + .defaultValue(params.defaultValue()) + .build(); + + ConfigContentWidget contentWidget = rootResult.createContentWidget(creationContext); + if (contentWidget == null) { + throw new IllegalStateException("Failed to create root content widget"); + } + + ConfigScreen configScreen = new ConfigScreen( + minecraft.screen, params.title(), Collections.singletonList(contentWidget) + ); + configScreen.setOnSave(() -> params.saveHandler().accept(value)); + return configScreen; + } + + private TweedCoatEntryMappingResult mapEntry( + ConfigEntry entry, + TweedCoatEntryMappingContext context + ) { + for (TweedCoatMapper mapper : mappers) { + //noinspection rawtypes,unchecked + TweedCoatEntryMappingResult result = mapper.mapEntry((ConfigEntry) entry, context); + if (result.isApplicable()) { + return result; + } + } + return TweedCoatEntryMappingResult.notApplicable(); + } + + private static class CustomData { + + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java new file mode 100644 index 0000000..6fb9853 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/TweedCoatMappersImpl.java @@ -0,0 +1,370 @@ +package de.siphalor.tweed5.coat.bridge.impl; + +import de.siphalor.coat.handler.ConfigEntryHandler; +import de.siphalor.coat.input.CheckBoxConfigInput; +import de.siphalor.coat.input.ConfigInput; +import de.siphalor.coat.input.CycleButtonConfigInput; +import de.siphalor.coat.input.TextConfigInput; +import de.siphalor.coat.list.complex.ConfigCategoryWidget; +import de.siphalor.coat.list.entry.ConfigCategoryConfigEntry; +import de.siphalor.coat.screen.ConfigContentWidget; +import de.siphalor.coat.util.EnumeratedMaterial; +import de.siphalor.tweed5.attributesextension.api.AttributesExtension; +import de.siphalor.tweed5.coat.bridge.api.TweedCoatAttributes; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatEntryCreationContext; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatEntryMappingContext; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatEntryMappingResult; +import de.siphalor.tweed5.coat.bridge.api.mapping.TweedCoatMapper; +import de.siphalor.tweed5.coat.bridge.api.mapping.handler.BasicTweedCoatEntryHandler; +import de.siphalor.tweed5.coat.bridge.api.mapping.handler.ConvertingTweedCoatEntryHandler; +import de.siphalor.tweed5.core.api.entry.CompoundConfigEntry; +import de.siphalor.tweed5.core.api.entry.ConfigEntry; +import lombok.RequiredArgsConstructor; +import lombok.Value; +import lombok.extern.apachecommons.CommonsLog; +import net.minecraft.client.Minecraft; +import net.minecraft.resources.ResourceLocation; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static de.siphalor.tweed5.coat.bridge.api.TweedCoatMappingUtils.translatableComponentWithFallback; + +@CommonsLog +@SuppressWarnings("unchecked") +public class TweedCoatMappersImpl { + public static TweedCoatMapper BYTE_TEXT_MAPPER = convertingTextMapper( + new Class[]{Byte.class, byte.class}, + value -> Byte.toString(value), + Byte::parseByte + ); + public static TweedCoatMapper SHORT_TEXT_MAPPER = convertingTextMapper( + new Class[]{Short.class, short.class}, + value -> Short.toString(value), + Short::parseShort + ); + public static TweedCoatMapper INTEGER_TEXT_MAPPER = convertingTextMapper( + new Class[]{Integer.class, int.class}, + value -> Integer.toString(value), + Integer::parseInt + ); + public static TweedCoatMapper LONG_TEXT_MAPPER = convertingTextMapper( + new Class[]{Long.class, long.class}, + value -> Long.toString(value), + Long::parseLong + ); + public static TweedCoatMapper FLOAT_TEXT_MAPPER = convertingTextMapper( + new Class[]{Float.class, float.class}, + value -> Float.toString(value), + Float::parseFloat + ); + public static TweedCoatMapper DOUBLE_TEXT_MAPPER = convertingTextMapper( + new Class[]{Double.class, double.class}, + value -> Double.toString(value), + Double::parseDouble + ); + public static TweedCoatMapper BOOLEAN_CHECKBOX_MAPPER + = new SimpleMapper(new Class[]{Boolean.class, boolean.class}, CheckBoxConfigInput::new); + + public static TweedCoatMapper STRING_TEXT_MAPPER = new SimpleMapper( + new Class[]{String.class}, + TextConfigInput::new + ); + + public static TweedCoatMapper> ENUM_CYCLE_BUTTON_MAPPER = new EnumCycleButtonMapper<>(); + + public static TweedCoatMapper COMPOUND_CATEGORY_MAPPER = new CompoundCategoryMapper<>(); + + public static TweedCoatMapper convertingTextMapper( + Class[] valueClasses, + Function textMapper, + Function textParser + ) { + return new ConvertingTextMapper<>(valueClasses, textMapper, textParser); + } + + @RequiredArgsConstructor + public static class ConvertingTextMapper implements TweedCoatMapper { + private final Class[] valueClasses; + private final Function textMapper; + private final Function textParser; + + @Override + public TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext context) { + boolean applicable = anyClassMatches(entry.valueClass(), valueClasses); + + return new TweedCoatEntryMappingResult() { + @Override + public boolean isApplicable() { + return applicable; + } + + @Override + public ConfigInput createInput(TweedCoatEntryCreationContext context) { + return new TextConfigInput(textMapper.apply(context.currentValue())); + } + + @Override + public ConfigEntryHandler createHandler(TweedCoatEntryCreationContext context) { + if (context.parentSaveHandler() == null) { + throw new IllegalArgumentException("No parent save handler provided"); + } + return new ConvertingTweedCoatEntryHandler<>( + new BasicTweedCoatEntryHandler<>( + context.entry(), context.defaultValue(), context.parentSaveHandler() + ), + textMapper, + textParser + ); + } + + @Override + public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext context) { + return null; + } + }; + } + } + + @RequiredArgsConstructor + public static class EnumeratedMaterialCycleButtonMapper implements TweedCoatMapper { + private final Class valueClass; + private final EnumeratedMaterial material; + + @Override + public TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext context) { + if (!valueClass.isAssignableFrom(entry.valueClass())) { + return TweedCoatEntryMappingResult.notApplicable(); + } + + return new CycleButtonMappingResult<>(material); + } + } + + private static class EnumCycleButtonMapper> implements TweedCoatMapper { + @Override + public TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext context) { + if (!Enum.class.isAssignableFrom(entry.valueClass())) { + return TweedCoatEntryMappingResult.notApplicable(); + } + Class enumClass = entry.valueClass(); + + String translationKeyPrefix = entry.container().extension(AttributesExtension.class) + .map(extension -> extension.getAttributeValue(entry, TweedCoatAttributes.ENUM_TRANSLATION_KEY)) + .orElse(enumClass.getPackage().getName()); + + CoatEnumMaterial material = new CoatEnumMaterial<>(enumClass, translationKeyPrefix + "."); + return new CycleButtonMappingResult<>(material); + } + } + + @RequiredArgsConstructor + private static class CycleButtonMappingResult implements TweedCoatEntryMappingResult { + private final EnumeratedMaterial material; + + @Override + public boolean isApplicable() { + return true; + } + + @Override + public ConfigInput createInput(TweedCoatEntryCreationContext context) { + return new CycleButtonConfigInput<>(material, false, context.currentValue()); + } + + @Override + public ConfigEntryHandler createHandler(TweedCoatEntryCreationContext context) { + if (context.parentSaveHandler() == null) { + throw new IllegalArgumentException("No parent save handler provided"); + } + return new BasicTweedCoatEntryHandler<>( + context.entry(), context.defaultValue(), context.parentSaveHandler() + ); + } + + @Override + public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext context) { + return null; + } + } + + @RequiredArgsConstructor + private static class SimpleMapper implements TweedCoatMapper { + private final Class[] valueClasses; + private final Function> inputFactory; + + @Override + public TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext context) { + matchingClass: { + for (Class valueClass : valueClasses) { + if (entry.valueClass() == valueClass) { + break matchingClass; + } + } + return TweedCoatEntryMappingResult.notApplicable(); + } + + return new TweedCoatEntryMappingResult() { + @Override + public boolean isApplicable() { + return true; + } + + @Override + public ConfigInput createInput(TweedCoatEntryCreationContext context) { + return inputFactory.apply(context.currentValue()); + } + + @Override + public ConfigEntryHandler createHandler(TweedCoatEntryCreationContext context) { + if (context.parentSaveHandler() == null) { + throw new IllegalArgumentException("No parent save handler provided"); + } + return new BasicTweedCoatEntryHandler<>( + context.entry(), context.defaultValue(), context.parentSaveHandler() + ); + } + + @Override + public @Nullable ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext context) { + return null; + } + }; + } + } + + private static class CompoundCategoryMapper implements TweedCoatMapper { + @Override + public TweedCoatEntryMappingResult mapEntry(ConfigEntry entry, TweedCoatEntryMappingContext mappingContext) { + @Value + class MappedEntry { + String name; + String translationKeyPrefix; + ConfigEntry entry; + TweedCoatEntryMappingContext mappingContext; + TweedCoatEntryMappingResult mappingResult; + } + + if (!(entry instanceof CompoundConfigEntry)) { + return TweedCoatEntryMappingResult.notApplicable(); + } + CompoundConfigEntry compoundEntry = (CompoundConfigEntry) entry; + + Optional attributesExtension = entry.container().extension(AttributesExtension.class); + ResourceLocation backgroundTexture = attributesExtension + .map(extension -> extension.getAttributeValue( + entry, + TweedCoatAttributes.BACKGROUND_TEXTURE + )) + .map(ResourceLocation::tryParse) + .orElse(null); + String translationKey = attributesExtension + .map(extension -> extension.getAttributeValue( + entry, + TweedCoatAttributes.TRANSLATION_KEY + )) + .orElse(mappingContext.translationKeyPrefix()); + + List> mappedEntries = compoundEntry.subEntries().entrySet().stream() + .map(mapEntry -> { + String subTranslationKeyPrefix = translationKey + "." + mapEntry.getKey(); + TweedCoatEntryMappingContext subMappingContext = mappingContext.subContextBuilder(mapEntry.getKey()) + .translationKeyPrefix(subTranslationKeyPrefix) + .parentWidgetClass(ConfigCategoryWidget.class) + .build(); + return new MappedEntry<>( + mapEntry.getKey(), + subTranslationKeyPrefix, + (ConfigEntry) mapEntry.getValue(), + subMappingContext, + (TweedCoatEntryMappingResult<@NonNull Object, ?>) subMappingContext.mapEntry( + mapEntry.getValue(), + subMappingContext + ) + ); + }) + .filter(mappedEntry -> mappedEntry.mappingResult.isApplicable()) + .collect(Collectors.toList()); + + return new TweedCoatEntryMappingResult() { + @Override + public boolean isApplicable() { + return true; + } + + @Override + public @Nullable ConfigInput createInput(TweedCoatEntryCreationContext context) { + return null; + } + + @Override + public @Nullable ConfigEntryHandler createHandler(TweedCoatEntryCreationContext context) { + return null; + } + + @Override + public ConfigContentWidget createContentWidget(TweedCoatEntryCreationContext context) { + ConfigCategoryWidget categoryWidget = new ConfigCategoryWidget( + Minecraft.getInstance(), + translatableComponentWithFallback( + translationKey, + mappingContext.entryName() + ), + Collections.emptyList(), + backgroundTexture + ); + for (MappedEntry mappedEntry : mappedEntries) { + TweedCoatEntryMappingResult mappingResult = mappedEntry.mappingResult(); + if (!mappingResult.isApplicable()) { + log.warn( + "Failed to resolve mapping for entry \"" + mappedEntry.name() + "\" at \"" + + translationKey + "\". Entry will be ignored in UI." + ); + continue; + } + + Object subEntryValue = compoundEntry.get(context.currentValue(), mappedEntry.name()); + Object subEntryDefaultValue = compoundEntry.get(context.defaultValue(), mappedEntry.name()); + TweedCoatEntryCreationContext creationContext = TweedCoatEntryCreationContext.builder() + .entry(mappedEntry.entry()) + .currentValue(subEntryValue) + .defaultValue(subEntryDefaultValue) + .parentSaveHandler(value -> compoundEntry.set(context.currentValue(), mappedEntry.name(), value)) + .build(); + + ConfigInput input = mappingResult.createInput(creationContext); + if (input != null) { + ConfigCategoryConfigEntry entry = new ConfigCategoryConfigEntry<>( + translatableComponentWithFallback(mappedEntry.translationKeyPrefix(), mappedEntry.name()), + translatableComponentWithFallback(mappedEntry.translationKeyPrefix() + ".description", null), + (ConfigEntryHandler) mappingResult + .createHandler(creationContext), + (ConfigInput) input + ); + categoryWidget.addEntry(entry); + continue; + } + + ConfigContentWidget contentWidget = mappingResult.createContentWidget(creationContext); + if (contentWidget != null) { + categoryWidget.addSubTree(contentWidget); + } + } + return categoryWidget; + } + }; + } + } + + private static boolean anyClassMatches(Object value, Class... classes) { + for (Class clazz : classes) { + if (clazz.isInstance(value)) { + return true; + } + } + return false; + } +} diff --git a/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/package-info.java b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/package-info.java new file mode 100644 index 0000000..c29cb90 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/java/de/siphalor/tweed5/coat/bridge/impl/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@ApiStatus.Internal +package de.siphalor.tweed5.coat.bridge.impl; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/tweed5-minecraft/coat-bridge/src/main/resources/assets/tweed5_coat_bridge/lang/en_us.json b/tweed5-minecraft/coat-bridge/src/main/resources/assets/tweed5_coat_bridge/lang/en_us.json new file mode 100644 index 0000000..adba4f0 --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/main/resources/assets/tweed5_coat_bridge/lang/en_us.json @@ -0,0 +1,3 @@ +{ + "tweed5_coat_bridge.handler.conversion.exception": "Failed to convert: %s" +} diff --git a/tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestMod.java b/tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestMod.java new file mode 100644 index 0000000..88b89cf --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestMod.java @@ -0,0 +1,89 @@ +package de.siphalor.tweed5.coat.bridge.testmod; + +import de.siphalor.amecs.api.PriorityKeyBinding; +import de.siphalor.coat.screen.ConfigScreen; +import de.siphalor.tweed5.coat.bridge.api.ConfigScreenCreateParams; +import de.siphalor.tweed5.coat.bridge.api.TweedCoatBridgeExtension; +import de.siphalor.tweed5.coat.bridge.api.TweedCoatMappers; +import de.siphalor.tweed5.core.api.container.ConfigContainer; +import de.siphalor.tweed5.data.hjson.HjsonSerde; +import de.siphalor.tweed5.data.hjson.HjsonWriter; +import de.siphalor.tweed5.fabric.helper.api.FabricConfigContainerHelper; +import de.siphalor.tweed5.weaver.pojo.impl.weaving.TweedPojoWeaverBootstrapper; +import lombok.extern.apachecommons.CommonsLog; +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper; +import net.minecraft.client.KeyMapping; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.TitleScreen; +import net.minecraft.network.chat.Component; + +import java.util.Arrays; + +@CommonsLog +public class TweedCoatBridgeTestMod implements ClientModInitializer { + public static final String MOD_ID = "tweed5_coat_bridge_testmod"; + + private static final TweedCoatBridgeTestModConfig DEFAULT_CONFIG_VALUE = new TweedCoatBridgeTestModConfig(); + + private ConfigContainer configContainer; + private TweedCoatBridgeExtension configCoatBridgeExtension; + private FabricConfigContainerHelper configContainerHelper; + private TweedCoatBridgeTestModConfig config; + + @Override + public void onInitializeClient() { + configContainer = TweedPojoWeaverBootstrapper.create(TweedCoatBridgeTestModConfig.class).weave(); + configCoatBridgeExtension = configContainer.extension(TweedCoatBridgeExtension.class) + .orElseThrow(() -> new IllegalStateException("TweedCoatBridgeExtension not found")); + Arrays.asList( + TweedCoatMappers.compoundCategoryMapper(), + TweedCoatMappers.stringTextMapper(), + TweedCoatMappers.integerTextMapper() + ).forEach(configCoatBridgeExtension::addMapper); + + configContainer.initialize(); + + configContainerHelper = FabricConfigContainerHelper.create( + configContainer, + new HjsonSerde(new HjsonWriter.Options()), + MOD_ID + ); + + config = configContainerHelper.loadAndUpdateInConfigDirectory(() -> DEFAULT_CONFIG_VALUE); + + KeyBindingHelper.registerKeyBinding(new ScreenKeyBinding(MOD_ID + ".config", 84, KeyMapping.Category.MISC)); + + log.info("current config"); + } + + private class ScreenKeyBinding extends KeyMapping implements PriorityKeyBinding { + public ScreenKeyBinding(String name, int key, Category category) { + super(name, key, category); + } + + @Override + public boolean onPressedPriority() { + if (!(Minecraft.getInstance().screen instanceof TitleScreen)) { + return false; + } + + ConfigScreen configScreen = configCoatBridgeExtension.createConfigScreen( + ConfigScreenCreateParams.builder() + .translationKeyPrefix(MOD_ID + ".config") + .title(Component.translatable(MOD_ID + ".title")) + .rootEntry(configContainer.rootEntry()) + .currentValue(config) + .defaultValue(DEFAULT_CONFIG_VALUE) + .saveHandler(value -> { + config = value; + log.info("Updated config: " + config); + configContainerHelper.writeConfigInConfigDirectory(config); + }) + .build() + ); + Minecraft.getInstance().setScreen(configScreen); + return true; + } + } +} diff --git a/tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestModConfig.java b/tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestModConfig.java new file mode 100644 index 0000000..1ef03db --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/testmod/java/de/siphalor/tweed5/coat/bridge/testmod/TweedCoatBridgeTestModConfig.java @@ -0,0 +1,54 @@ +package de.siphalor.tweed5.coat.bridge.testmod; + +import de.siphalor.tweed5.attributesextension.api.AttributesExtension; +import de.siphalor.tweed5.coat.bridge.api.TweedCoatAttributes; +import de.siphalor.tweed5.coat.bridge.api.TweedCoatBridgeExtension; +import de.siphalor.tweed5.data.extension.api.ReadWriteExtension; +import de.siphalor.tweed5.defaultextensions.validation.api.ValidationExtension; +import de.siphalor.tweed5.weaver.pojo.api.annotation.CompoundWeaving; +import de.siphalor.tweed5.weaver.pojo.api.annotation.DefaultWeavingExtensions; +import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeaving; +import de.siphalor.tweed5.weaver.pojo.api.annotation.PojoWeavingExtension; +import de.siphalor.tweed5.weaver.pojoext.attributes.api.Attribute; +import de.siphalor.tweed5.weaver.pojoext.attributes.api.AttributesPojoWeavingProcessor; +import de.siphalor.tweed5.weaver.pojoext.serde.api.auto.AutoReadWritePojoWeavingProcessor; +import de.siphalor.tweed5.weaver.pojoext.serde.api.auto.DefaultReadWriteMappings; +import de.siphalor.tweed5.weaver.pojoext.validation.api.Validator; +import de.siphalor.tweed5.weaver.pojoext.validation.api.ValidatorsPojoWeavingProcessor; +import de.siphalor.tweed5.weaver.pojoext.validation.api.validators.WeavableNumberRangeValidator; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@PojoWeaving(extensions = { + ReadWriteExtension.class, + TweedCoatBridgeExtension.class, + ValidationExtension.class, + AttributesExtension.class, +}) +@PojoWeavingExtension(AutoReadWritePojoWeavingProcessor.class) +@PojoWeavingExtension(ValidatorsPojoWeavingProcessor.class) +@PojoWeavingExtension(AttributesPojoWeavingProcessor.class) +@DefaultWeavingExtensions +@DefaultReadWriteMappings +@CompoundWeaving(namingFormat = "kebab_case") +@Data +public class TweedCoatBridgeTestModConfig { + private String test = "hello world"; + private int someInteger = 123; + @Validator(value = WeavableNumberRangeValidator.class, config = "-10=..=10") + private int integerInRange = -5; + + @Attribute(key = TweedCoatAttributes.BACKGROUND_TEXTURE, values = "textures/block/green_terracotta.png") + private Greeting serverGreeting = new Greeting("Hello server!", "Server"); + @Attribute(key = TweedCoatAttributes.BACKGROUND_TEXTURE, values = "textures/block/red_terracotta.png") + private Greeting clientGreeting = new Greeting("Hello client!", "Client"); + + @NoArgsConstructor + @AllArgsConstructor + @CompoundWeaving + public static class Greeting { + public String greeting; + public String greeter; + } +} diff --git a/tweed5-minecraft/coat-bridge/src/testmod/resources/assets/tweed5_coat_bridge_testmod/lang/en_us.json b/tweed5-minecraft/coat-bridge/src/testmod/resources/assets/tweed5_coat_bridge_testmod/lang/en_us.json new file mode 100644 index 0000000..d0a1f6d --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/testmod/resources/assets/tweed5_coat_bridge_testmod/lang/en_us.json @@ -0,0 +1,8 @@ +{ + "tweed5_coat_bridge_testmod.title": "Test Mod", + "tweed5_coat_bridge_testmod.config": "Test Mod config", + "tweed5_coat_bridge_testmod.config.test": "Test entry", + "tweed5_coat_bridge_testmod.config.test.description": "Just a simple string entry", + "tweed5_coat_bridge_testmod.config.some-integer": "Some integer", + "tweed5_coat_bridge_testmod.config.integer-in-range": "Integer in range" +} diff --git a/tweed5-minecraft/coat-bridge/src/testmod/resources/fabric.mod.json b/tweed5-minecraft/coat-bridge/src/testmod/resources/fabric.mod.json new file mode 100644 index 0000000..70deb4d --- /dev/null +++ b/tweed5-minecraft/coat-bridge/src/testmod/resources/fabric.mod.json @@ -0,0 +1,10 @@ +{ + "schemaVersion": 1, + "id": "tweed5_coat_bridge_testmod", + "version": "0.1.0", + "entrypoints": { + "client": [ + "de.siphalor.tweed5.coat.bridge.testmod.TweedCoatBridgeTestMod" + ] + } +} diff --git a/tweed5-minecraft/gradle/mc-1.21.10/mcLibs.versions.toml b/tweed5-minecraft/gradle/mc-1.21.10/mcLibs.versions.toml index 6f89465..80cbf42 100644 --- a/tweed5-minecraft/gradle/mc-1.21.10/mcLibs.versions.toml +++ b/tweed5-minecraft/gradle/mc-1.21.10/mcLibs.versions.toml @@ -1,10 +1,12 @@ [versions] -coat = "1.0.0-beta.23" +amecs-api = "1.6.2" +coat = "1.0.0-beta.24.local.3" fabric-api = "0.136.0+1.21.10" minecraft = "1.21.10" parchment = "2025.10.12" [libraries] +amecs-api = { group = "de.siphalor.amecs-api", name = "amecs-api-mc1.21.9", version.ref = "amecs-api" } coat = { group = "de.siphalor.coat", name = "coat-mc1.21.10", version.ref = "coat" } fabric-api = { group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api" } minecraft = { group = "com.mojang", name = "minecraft", version.ref = "minecraft" } diff --git a/tweed5-minecraft/settings.gradle.kts b/tweed5-minecraft/settings.gradle.kts index 18e4c5f..0cb65ec 100644 --- a/tweed5-minecraft/settings.gradle.kts +++ b/tweed5-minecraft/settings.gradle.kts @@ -52,6 +52,7 @@ dependencyResolutionManagement { includeBuild("../tweed5") includeNormalModule("bundle") +includeNormalModule("coat-bridge") includeNormalModule("fabric-helper") fun includeNormalModule(name: String) {