From 31acd52acbd2c7fb2d61d4021eac1d3a142d0635 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 6 Sep 2025 06:17:04 +0000 Subject: [PATCH 01/37] Migrate build scripts to kotlin, update to Gradle 9.0.0 --- build.gradle | 76 ----------------------- build.gradle.kts | 79 ++++++++++++++++++++++++ gradle/wrapper/gradle-wrapper.properties | 2 +- settings.gradle => settings.gradle.kts | 4 +- 4 files changed, 82 insertions(+), 79 deletions(-) delete mode 100644 build.gradle create mode 100644 build.gradle.kts rename settings.gradle => settings.gradle.kts (55%) diff --git a/build.gradle b/build.gradle deleted file mode 100644 index 3912291..0000000 --- a/build.gradle +++ /dev/null @@ -1,76 +0,0 @@ -plugins { - id 'fabric-loom' version '1.11-SNAPSHOT' - id 'maven-publish' -} - -repositories { - maven { - url = "https://maven.parchmentmc.org" - } -} - -dependencies { - minecraft "com.mojang:minecraft:${project.minecraft_version}" - mappings(loom.layered { - officialMojangMappings() - parchment("org.parchmentmc.data:parchment-${project.parchment_version}@zip") - }) - - modImplementation "net.fabricmc:fabric-loader:${project.loader_version}" - - modImplementation "net.fabricmc.fabric-api:fabric-api:${project.fabric_version}" -} - -processResources { - inputs.property "version", project.mod_version - inputs.property "supported_versions", project.supported_versions - inputs.property "loader_version", project.loader_version - filteringCharset "UTF-8" - - filesMatching("fabric.mod.json") { - expand "version": project.mod_version, - "supported_versions": project.supported_versions, - "loader_version": project.loader_version - } -} - -def targetJavaVersion = 21 -tasks.withType(JavaCompile).configureEach { - it.options.encoding = "UTF-8" - if (targetJavaVersion >= 10 || JavaVersion.current().isJava10Compatible()) { - it.options.release.set(targetJavaVersion) - } -} - -java { - def javaVersion = JavaVersion.toVersion(targetJavaVersion) - if (JavaVersion.current() < javaVersion) { - toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) - } - withSourcesJar() -} - -jar { - from("LICENSE") { - rename { "${it}_${project.archivesBaseName}" } - } -} - -publishing { - publications { - create("mavenJava", MavenPublication) { - artifactId = project.archives_base_name - from components.java - } - } - - repositories {} -} - -loom { - runs { - named("server") { - runDir = "run-server" - } - } -} diff --git a/build.gradle.kts b/build.gradle.kts new file mode 100644 index 0000000..716b807 --- /dev/null +++ b/build.gradle.kts @@ -0,0 +1,79 @@ +plugins { + id("fabric-loom") version "1.11-SNAPSHOT" +} + +val minecraftVersion = properties["minecraft_version"]!! as String +val parchmentVersion = properties["parchment_version"]!! as String +val loaderVersion = properties["loader_version"]!! as String + +val modVersion = properties["mod_version"]!! as String +val supportedVersions = properties["supported_versions"]!! as String +val archivesBaseName = properties["archives_base_name"]!! as String +val fabricVersion = properties["fabric_version"]!! as String + +val targetJavaVersion = 21 + +repositories { + maven { + name = "ParchmentMC" + url = uri("https://maven.parchmentmc.org") + } +} + +dependencies { + minecraft("com.mojang:minecraft:${minecraftVersion}") + mappings(loom.layered { + officialMojangMappings() + parchment("org.parchmentmc.data:parchment-${parchmentVersion}@zip") + }) + + modImplementation("net.fabricmc:fabric-loader:${loaderVersion}") + + modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricVersion}") +} + +tasks { + processResources { + inputs.property("version", modVersion) + inputs.property("supported_versions", supportedVersions) + inputs.property("loader_version", loaderVersion) + filteringCharset = "UTF-8" + + filesMatching("fabric.mod.json") { + expand( + mapOf( + "version" to modVersion, + "supported_versions" to supportedVersions, + "loader_version" to loaderVersion + ) + ) + } + } + + jar { + from("LICENSE") { + rename { "${it}_${archivesBaseName}" } + } + } + + withType().configureEach { + options.encoding = "UTF-8" + options.release = targetJavaVersion + } +} + +java { + val javaVersion = JavaVersion.toVersion(targetJavaVersion) + if (JavaVersion.current() < javaVersion) { + toolchain.languageVersion = JavaLanguageVersion.of(targetJavaVersion) + } + withSourcesJar() +} + +loom { + runs { + named("server") { + runDir = "run-server" + } + } +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ff23a68..2a84e18 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.14.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle.kts similarity index 55% rename from settings.gradle rename to settings.gradle.kts index f91a4fe..e7b2814 100644 --- a/settings.gradle +++ b/settings.gradle.kts @@ -1,8 +1,8 @@ pluginManagement { repositories { maven { - name = 'Fabric' - url = 'https://maven.fabricmc.net/' + name = "Fabric" + url = uri("https://maven.fabricmc.net/") } gradlePluginPortal() } From e57460b76ba072a971736bb0e1e788f18a81fc20 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Sat, 6 Sep 2025 08:50:15 +0000 Subject: [PATCH 02/37] Include PackConverter & Creative, create Creative pack wrapper for Minecraft's ResourceManager --- build.gradle.kts | 16 ++ gradle.properties | 1 + .../java/org/geysermc/rainbow/KeyUtil.java | 16 ++ .../java/org/geysermc/rainbow/Rainbow.java | 75 ++++++ .../CachingStreamResourceContainer.java | 42 +++ .../creative/ImmutableResourceContainer.java | 140 ++++++++++ .../creative/ImmutableResourcePack.java | 37 +++ .../MinecraftCreativeResourcePack.java | 70 +++++ .../creative/StreamResourceContainer.java | 254 ++++++++++++++++++ 9 files changed, 651 insertions(+) create mode 100644 src/main/java/org/geysermc/rainbow/KeyUtil.java create mode 100644 src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java create mode 100644 src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java create mode 100644 src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java create mode 100644 src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java create mode 100644 src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java diff --git a/build.gradle.kts b/build.gradle.kts index 716b807..bf3e7a2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -9,7 +9,9 @@ val loaderVersion = properties["loader_version"]!! as String val modVersion = properties["mod_version"]!! as String val supportedVersions = properties["supported_versions"]!! as String val archivesBaseName = properties["archives_base_name"]!! as String + val fabricVersion = properties["fabric_version"]!! as String +val packConverterVersion = properties["pack_converter_version"]!! as String val targetJavaVersion = 21 @@ -18,6 +20,16 @@ repositories { name = "ParchmentMC" url = uri("https://maven.parchmentmc.org") } + + maven { + name = "Jitpack" + url = uri("https://jitpack.io") + } + + maven { + name = "Open Collaboration" + url = uri("https://repo.opencollab.dev/main") + } } dependencies { @@ -30,6 +42,10 @@ dependencies { modImplementation("net.fabricmc:fabric-loader:${loaderVersion}") modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricVersion}") + + include(implementation("com.github.GeyserMC.unnamed-creative:creative-api:817fa982c4")!!) + include(implementation("com.github.GeyserMC.unnamed-creative:creative-serializer-minecraft:817fa982c4")!!) + include(implementation("org.geysermc.pack:converter:${packConverterVersion}")!!) } tasks { diff --git a/gradle.properties b/gradle.properties index 79e8f2f..4dca00d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,3 +13,4 @@ archives_base_name=rainbow # Dependencies fabric_version=0.135.0+1.21.10 +pack_converter_version=3.4.1-SNAPSHOT diff --git a/src/main/java/org/geysermc/rainbow/KeyUtil.java b/src/main/java/org/geysermc/rainbow/KeyUtil.java new file mode 100644 index 0000000..5811cfc --- /dev/null +++ b/src/main/java/org/geysermc/rainbow/KeyUtil.java @@ -0,0 +1,16 @@ +package org.geysermc.rainbow; + +import net.kyori.adventure.key.Key; +import net.minecraft.resources.ResourceLocation; + +@SuppressWarnings("PatternValidation") +public interface KeyUtil { + + static Key resourceLocationToKey(ResourceLocation location) { + return Key.key(location.getNamespace(), location.getPath()); + } + + static ResourceLocation keyToResourceLocation(Key key) { + return ResourceLocation.fromNamespaceAndPath(key.namespace(), key.value()); + } +} diff --git a/src/main/java/org/geysermc/rainbow/Rainbow.java b/src/main/java/org/geysermc/rainbow/Rainbow.java index f0f4eba..d14f252 100644 --- a/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -2,16 +2,39 @@ package org.geysermc.rainbow; import com.mojang.logging.LogUtils; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; +import org.geysermc.pack.bedrock.resource.BedrockResourcePack; +import org.geysermc.pack.converter.PackConversionContext; +import org.geysermc.pack.converter.PackConverter; +import org.geysermc.pack.converter.converter.ActionListener; +import org.geysermc.pack.converter.converter.Converter; +import org.geysermc.pack.converter.converter.base.PackManifestConverter; +import org.geysermc.pack.converter.converter.lang.LangConverter; +import org.geysermc.pack.converter.converter.misc.SplashTextConverter; +import org.geysermc.pack.converter.converter.model.ModelConverter; +import org.geysermc.pack.converter.data.ConversionData; +import org.geysermc.pack.converter.util.DefaultLogListener; +import org.geysermc.pack.converter.util.LogListener; import org.geysermc.rainbow.command.CommandSuggestionsArgumentType; import org.geysermc.rainbow.command.PackGeneratorCommand; +import org.geysermc.rainbow.creative.MinecraftCreativeResourcePack; import org.geysermc.rainbow.mapper.PackMapper; import org.slf4j.Logger; +import javax.imageio.ImageIO; +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + public class Rainbow implements ClientModInitializer { public static final String MOD_ID = "rainbow"; @@ -29,6 +52,58 @@ public class Rainbow implements ClientModInitializer { ArgumentTypeRegistry.registerArgumentType(getModdedLocation("command_suggestions"), CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); + + ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> { + dispatcher.register(ClientCommandManager.literal("debugtest") + .executes(context -> { + + PackConverter packConverter = new PackConverter() + .packName("RAINBOW-TEST") + .output(FabricLoader.getInstance().getGameDir().resolve("pack-convert-out")); + Path tmpDir = FabricLoader.getInstance().getGameDir().resolve("pack-convert-temp"); + + ImageIO.scanForPlugins(); + MinecraftCreativeResourcePack resourcePack = new MinecraftCreativeResourcePack(Minecraft.getInstance().getResourceManager()); + BedrockResourcePack bedrockResourcePack = new BedrockResourcePack(tmpDir); + + LogListener logListener = new DefaultLogListener(); + final Converter.ConversionDataCreationContext conversionDataCreationContext = new Converter.ConversionDataCreationContext( + packConverter, logListener, null, tmpDir, resourcePack, resourcePack + ); + + + List> converters = new ArrayList<>(); + converters.add(new PackManifestConverter()); + converters.add(new LangConverter()); + converters.add(new ModelConverter()); + + int errors = 0; + for (Converter converter : converters) { + ConversionData data = converter.createConversionData(conversionDataCreationContext); + PackConversionContext conversionContext = new PackConversionContext<>(data, packConverter, resourcePack, bedrockResourcePack, logListener); + + List> actionListeners = List.of(); + try { + actionListeners.forEach(actionListener -> actionListener.preConvert((PackConversionContext) conversionContext)); + converter.convert(conversionContext); + actionListeners.forEach(actionListener -> actionListener.postConvert((PackConversionContext) conversionContext)); + } catch (Throwable t) { + logListener.error("Error converting pack!", t); + errors++; + } + } + + try { + bedrockResourcePack.export(); + } catch (IOException e) { + throw new RuntimeException(e); + } + context.getSource().sendFeedback(Component.literal("exporting, " + errors + " errors")); + + return 0; + }) + ); + }); } public static ResourceLocation getModdedLocation(String path) { diff --git a/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java b/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java new file mode 100644 index 0000000..1beb8c5 --- /dev/null +++ b/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java @@ -0,0 +1,42 @@ +package org.geysermc.rainbow.creative; + +import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; +import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import team.unnamed.creative.part.ResourcePackPart; +import team.unnamed.creative.serialize.minecraft.ResourceCategory; +import team.unnamed.creative.sound.SoundRegistry; +import team.unnamed.creative.texture.Texture; + +@SuppressWarnings("UnstableApiUsage") +public abstract class CachingStreamResourceContainer implements StreamResourceContainer { + private final Reference2ObjectMap, Object2ObjectMap> cache = new Reference2ObjectOpenHashMap<>(); + private final Object2ObjectMap soundRegistryCache = new Object2ObjectOpenHashMap<>(); + private final Object2ObjectMap textureCache = new Object2ObjectOpenHashMap<>(); + + @SuppressWarnings("unchecked") + private T cacheOrDeserialize(ResourceCategory deserializer, Key key) { + Object2ObjectMap deserializerCache = cache.computeIfAbsent(deserializer, cacheKey -> new Object2ObjectOpenHashMap<>()); + return (T) deserializerCache.computeIfAbsent(key, cacheKey -> StreamResourceContainer.super.deserialize(deserializer, key)); + } + + @Override + public @Nullable T deserialize(ResourceCategory deserializer, Key key) { + return cacheOrDeserialize(deserializer, key); + } + + @Override + public @Nullable SoundRegistry soundRegistry(@NotNull String namespace) { + return soundRegistryCache.computeIfAbsent(namespace, cacheNamespace -> StreamResourceContainer.super.soundRegistry(namespace)); + } + + @Override + public @Nullable Texture texture(@NotNull Key key) { + return textureCache.computeIfAbsent(key, cacheKey -> StreamResourceContainer.super.texture(key)); + } +} diff --git a/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java b/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java new file mode 100644 index 0000000..4859eb4 --- /dev/null +++ b/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java @@ -0,0 +1,140 @@ +package org.geysermc.rainbow.creative; + +import net.kyori.adventure.key.Key; +import org.jetbrains.annotations.NotNull; +import team.unnamed.creative.atlas.Atlas; +import team.unnamed.creative.base.Writable; +import team.unnamed.creative.blockstate.BlockState; +import team.unnamed.creative.equipment.Equipment; +import team.unnamed.creative.font.Font; +import team.unnamed.creative.item.Item; +import team.unnamed.creative.lang.Language; +import team.unnamed.creative.model.Model; +import team.unnamed.creative.overlay.ResourceContainer; +import team.unnamed.creative.resources.MergeStrategy; +import team.unnamed.creative.sound.Sound; +import team.unnamed.creative.sound.SoundRegistry; +import team.unnamed.creative.texture.Texture; + +@SuppressWarnings("NonExtendableApiUsage") +public interface ImmutableResourceContainer extends ResourceContainer { + + private static T thr() { + throw new UnsupportedOperationException("ResourceContainer is immutable"); + } + + @Override + default void atlas(@NotNull Atlas atlas) { + thr(); + } + + @Override + default boolean removeAtlas(@NotNull Key key) { + return thr(); + } + + @Override + default void blockState(@NotNull BlockState state) { + thr(); + } + + @Override + default boolean removeBlockState(@NotNull Key key) { + return thr(); + } + + @Override + default void equipment(@NotNull Equipment equipment) { + thr(); + } + + @Override + default boolean removeEquipment(@NotNull Key key) { + return thr(); + } + + @Override + default void font(@NotNull Font font) { + thr(); + } + + @Override + default boolean removeFont(@NotNull Key key) { + return thr(); + } + + @Override + default void item(@NotNull Item item) { + thr(); + } + + @Override + default boolean removeItem(@NotNull Key key) { + return thr(); + } + + @Override + default void language(@NotNull Language language) { + thr(); + } + + @Override + default boolean removeLanguage(@NotNull Key key) { + return thr(); + } + + @Override + default void model(@NotNull Model model) { + thr(); + } + + @Override + default boolean removeModel(@NotNull Key key) { + return thr(); + } + + @Override + default void soundRegistry(@NotNull SoundRegistry soundRegistry) { + thr(); + } + + @Override + default boolean removeSoundRegistry(@NotNull String namespace) { + return thr(); + } + + @Override + default void sound(@NotNull Sound sound) { + thr(); + } + + @Override + default boolean removeSound(@NotNull Key key) { + return thr(); + } + + @Override + default void texture(@NotNull Texture texture) { + thr(); + } + + @Override + default boolean removeTexture(@NotNull Key key) { + return thr(); + } + + @Override + default void unknownFile(@NotNull String path, @NotNull Writable data) { + thr(); + } + + @Override + default boolean removeUnknownFile(@NotNull String path) { + return thr(); + } + + @Override + default void merge(@NotNull ResourceContainer other, @NotNull MergeStrategy strategy) { + thr(); + } +} diff --git a/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java b/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java new file mode 100644 index 0000000..37edc55 --- /dev/null +++ b/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java @@ -0,0 +1,37 @@ +package org.geysermc.rainbow.creative; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import team.unnamed.creative.ResourcePack; +import team.unnamed.creative.base.Writable; +import team.unnamed.creative.metadata.Metadata; +import team.unnamed.creative.metadata.sodium.SodiumMeta; +import team.unnamed.creative.overlay.Overlay; + +@SuppressWarnings("NonExtendableApiUsage") +public interface ImmutableResourcePack extends ResourcePack, ImmutableResourceContainer { + + private static void thr() { + throw new UnsupportedOperationException("ResourcePack is immutable"); + } + + @Override + default void icon(@Nullable Writable icon) { + thr(); + } + + @Override + default void metadata(@NotNull Metadata metadata) { + thr(); + } + + @Override + default void overlay(@NotNull Overlay overlay) { + thr(); + } + + @Override + default void sodiumMeta(@NotNull SodiumMeta sodiumMeta) { + thr(); + } +} diff --git a/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java b/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java new file mode 100644 index 0000000..833769a --- /dev/null +++ b/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java @@ -0,0 +1,70 @@ +package org.geysermc.rainbow.creative; + +import net.kyori.adventure.key.Key; +import net.minecraft.server.packs.resources.Resource; +import net.minecraft.server.packs.resources.ResourceManager; +import org.geysermc.rainbow.KeyUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import team.unnamed.creative.base.Writable; +import team.unnamed.creative.metadata.Metadata; +import team.unnamed.creative.overlay.Overlay; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class MinecraftCreativeResourcePack extends CachingStreamResourceContainer implements ImmutableResourcePack { + private static final Pattern PATH_SANITIZE_REGEX = Pattern.compile("(^\\w+/)(.*)(\\.\\w+$)"); + + private final ResourceManager resourceManager; + + public MinecraftCreativeResourcePack(ResourceManager resourceManager) { + this.resourceManager = resourceManager; + } + + @Override + public @Nullable InputStream open(Key key) throws IOException { + Optional resource = resourceManager.getResource(KeyUtil.keyToResourceLocation(key)); + if (resource.isPresent()) { + return resource.get().open(); + } + return null; + } + + @Override + public List assets(String category) { + return resourceManager.listResources(category, resource -> true).keySet().stream() + .map(location -> location.withPath(path -> PATH_SANITIZE_REGEX.matcher(path).replaceAll("$2"))) + .map(KeyUtil::resourceLocationToKey) + .toList(); + } + + @Override + public Collection namespaces() { + return resourceManager.getNamespaces(); + } + + @Override + public @Nullable Writable icon() { + return null; + } + + @Override + public @NotNull Metadata metadata() { + return Metadata.empty(); + } + + @Override + public @Nullable Overlay overlay(@NotNull String directory) { + return null; + } + + @Override + public @NotNull Collection overlays() { + return List.of(); + } +} diff --git a/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java b/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java new file mode 100644 index 0000000..f260518 --- /dev/null +++ b/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java @@ -0,0 +1,254 @@ +package org.geysermc.rainbow.creative; + +import com.google.gson.JsonElement; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.Keyed; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import team.unnamed.creative.atlas.Atlas; +import team.unnamed.creative.base.Writable; +import team.unnamed.creative.blockstate.BlockState; +import team.unnamed.creative.equipment.Equipment; +import team.unnamed.creative.font.Font; +import team.unnamed.creative.item.Item; +import team.unnamed.creative.lang.Language; +import team.unnamed.creative.metadata.Metadata; +import team.unnamed.creative.model.Model; +import team.unnamed.creative.part.ResourcePackPart; +import team.unnamed.creative.serialize.minecraft.GsonUtil; +import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackStructure; +import team.unnamed.creative.serialize.minecraft.ResourceCategory; +import team.unnamed.creative.serialize.minecraft.atlas.AtlasSerializer; +import team.unnamed.creative.serialize.minecraft.blockstate.BlockStateSerializer; +import team.unnamed.creative.serialize.minecraft.equipment.EquipmentCategory; +import team.unnamed.creative.serialize.minecraft.font.FontSerializer; +import team.unnamed.creative.serialize.minecraft.item.ItemSerializer; +import team.unnamed.creative.serialize.minecraft.language.LanguageSerializer; +import team.unnamed.creative.serialize.minecraft.metadata.MetadataSerializer; +import team.unnamed.creative.serialize.minecraft.model.ModelSerializer; +import team.unnamed.creative.serialize.minecraft.sound.SoundRegistrySerializer; +import team.unnamed.creative.serialize.minecraft.sound.SoundSerializer; +import team.unnamed.creative.sound.Sound; +import team.unnamed.creative.sound.SoundRegistry; +import team.unnamed.creative.texture.Texture; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; + +@SuppressWarnings({"UnstableApiUsage", "PatternValidation"}) +public interface StreamResourceContainer extends ImmutableResourceContainer { + + @Nullable + InputStream open(Key key) throws IOException; + + List assets(String category); + + Collection namespaces(); + + String METADATA_EXTENSION = MinecraftResourcePackStructure.METADATA_EXTENSION; + String TEXTURE_EXTENSION = MinecraftResourcePackStructure.TEXTURE_EXTENSION; + String TEXTURE_CATEGORY = MinecraftResourcePackStructure.TEXTURES_FOLDER; + String SOUNDS_FILE = MinecraftResourcePackStructure.SOUNDS_FILE; + + private static Key addExtensionAndCategory(Key key, String category, String extension) { + return Key.key(key.namespace(), category + "/" + key.value() + extension); + } + + private static JsonElement parseJson(InputStream input) throws IOException { + try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { + return GsonUtil.parseReader(reader); + } + } + + @Nullable + private static Writable writable(Key key, String category, String extension, ResourceOpener opener) throws IOException { + Key withExtension = addExtensionAndCategory(key, category, extension); + try (InputStream input = opener.open(withExtension)) { + if (input != null) { + return Writable.inputStream(() -> opener.open(withExtension)); + } + } + return null; + } + + private static List collect(ResourceCategory category, Function deserializer, + Function> assetLookup) { + return assetLookup.apply(category.folder(-1)).stream() + .map(deserializer) + .filter(Objects::nonNull) + .toList(); + } + + @Nullable + default T deserialize(ResourceCategory category, Key key) { + Key withExtensionAndCategory = addExtensionAndCategory(key, category.folder(-1), category.extension(-1)); + try (InputStream input = open(withExtensionAndCategory)) { + if (input != null) { + return category.deserializer().deserialize(input, key); + } + } catch (IOException exception) { + throw new RuntimeException(exception); + } + return null; + } + + @Override + default @Nullable Atlas atlas(@NotNull Key key) { + return deserialize(AtlasSerializer.CATEGORY, key); + } + + @Override + default @NotNull Collection atlases() { + return collect(AtlasSerializer.CATEGORY, this::atlas, this::assets); + } + + @Override + default @Nullable BlockState blockState(@NotNull Key key) { + return deserialize(BlockStateSerializer.CATEGORY, key); + } + + @Override + @NotNull + default Collection blockStates() { + return collect(BlockStateSerializer.CATEGORY, this::blockState, this::assets); + } + + @Override + default @Nullable Equipment equipment(@NotNull Key key) { + return deserialize(EquipmentCategory.INSTANCE, key); + } + + @Override + @NotNull + default Collection equipment() { + return collect(EquipmentCategory.INSTANCE, this::equipment, this::assets); + } + + @Override + default @Nullable Font font(@NotNull Key key) { + return deserialize(FontSerializer.CATEGORY, key); + } + + @Override + @NotNull + default Collection fonts() { + return collect(FontSerializer.CATEGORY, this::font, this::assets); + } + + @Override + default @Nullable Item item(@NotNull Key key) { + return deserialize(ItemSerializer.CATEGORY, key); + } + + @Override + @NotNull + default Collection items() { + return collect(ItemSerializer.CATEGORY, this::item, this::assets); + } + + @Override + default @Nullable Language language(@NotNull Key key) { + return deserialize(LanguageSerializer.CATEGORY, key); + } + + @Override + @NotNull + default Collection languages() { + return collect(LanguageSerializer.CATEGORY, this::language, this::assets); + } + + @Override + default @Nullable Model model(@NotNull Key key) { + return deserialize(ModelSerializer.CATEGORY, key); + } + + @Override + @NotNull + default Collection models() { + return collect(ModelSerializer.CATEGORY, this::model, this::assets); + } + + @Override + default @Nullable SoundRegistry soundRegistry(@NotNull String namespace) { + try (InputStream input = open(Key.key(namespace, SOUNDS_FILE))) { + if (input != null) { + return SoundRegistrySerializer.INSTANCE.readFromTree(parseJson(input), namespace); + } + } catch (IOException exception) { + throw new RuntimeException(exception); + } + return null; + } + + @Override + @NotNull + default Collection soundRegistries() { + return namespaces().stream() + .map(this::soundRegistry) + .filter(Objects::nonNull) + .toList(); + } + + @Override + default @Nullable Sound sound(@NotNull Key key) { + return deserialize(SoundSerializer.CATEGORY, key); + } + + @Override + @NotNull + default Collection sounds() { + return collect(SoundSerializer.CATEGORY, this::sound, this::assets); + } + + @Override + default @Nullable Texture texture(@NotNull Key key) { + try { + Writable texture = writable(key, TEXTURE_CATEGORY, TEXTURE_EXTENSION, this::open); + if (texture != null) { + Metadata metadata = Metadata.empty(); + try (InputStream metadataStream = open(addExtensionAndCategory(key, TEXTURE_CATEGORY, METADATA_EXTENSION))) { + if (metadataStream != null) { + metadata = MetadataSerializer.INSTANCE.readFromTree(parseJson(metadataStream)); + } + } + return Texture.texture(key, texture, metadata); + } + } catch (IOException exception) { + throw new RuntimeException(exception); + } + return null; + } + + @Override + @NotNull + default Collection textures() { + return assets(TEXTURE_CATEGORY).stream() + .map(this::texture) + .filter(Objects::nonNull) + .toList(); + } + + @Override + default @Nullable Writable unknownFile(@NotNull String path) { + throw new UnsupportedOperationException("Unsupported by StreamResourceContainer"); + } + + @Override + default @NotNull Map unknownFiles() { + return Map.of(); + } + + @FunctionalInterface + interface ResourceOpener { + + InputStream open(Key key) throws IOException; + } +} From 954bb9b223c9d8c1ad68a71c4ae81fa7b38b6cd3 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Oct 2025 16:10:04 +0000 Subject: [PATCH 03/37] Create version catalog --- build.gradle.kts | 43 ++++++++----------- gradle.properties | 10 ----- gradle/libs.versions.toml | 25 +++++++++++ settings.gradle.kts | 2 + .../MinecraftCreativeResourcePack.java | 1 + 5 files changed, 47 insertions(+), 34 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/build.gradle.kts b/build.gradle.kts index bf3e7a2..53d18b5 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,18 +1,11 @@ plugins { - id("fabric-loom") version "1.11-SNAPSHOT" + alias(libs.plugins.fabric.loom) } -val minecraftVersion = properties["minecraft_version"]!! as String -val parchmentVersion = properties["parchment_version"]!! as String -val loaderVersion = properties["loader_version"]!! as String +version = properties["mod_version"]!! as String +group = properties["maven_group"]!! as String -val modVersion = properties["mod_version"]!! as String -val supportedVersions = properties["supported_versions"]!! as String val archivesBaseName = properties["archives_base_name"]!! as String - -val fabricVersion = properties["fabric_version"]!! as String -val packConverterVersion = properties["pack_converter_version"]!! as String - val targetJavaVersion = 21 repositories { @@ -33,34 +26,36 @@ repositories { } dependencies { - minecraft("com.mojang:minecraft:${minecraftVersion}") + minecraft(libs.minecraft) mappings(loom.layered { officialMojangMappings() - parchment("org.parchmentmc.data:parchment-${parchmentVersion}@zip") + parchment(libs.parchment) }) - modImplementation("net.fabricmc:fabric-loader:${loaderVersion}") + modImplementation(libs.fabric.loader) + modImplementation(libs.fabric.api) - modImplementation("net.fabricmc.fabric-api:fabric-api:${fabricVersion}") - - include(implementation("com.github.GeyserMC.unnamed-creative:creative-api:817fa982c4")!!) - include(implementation("com.github.GeyserMC.unnamed-creative:creative-serializer-minecraft:817fa982c4")!!) - include(implementation("org.geysermc.pack:converter:${packConverterVersion}")!!) + implementation(libs.creative.api) + implementation(libs.creative.serializer.minecraft) + implementation(libs.packconverter) + include(libs.creative.api) + include(libs.creative.serializer.minecraft) + include(libs.packconverter) } tasks { processResources { - inputs.property("version", modVersion) - inputs.property("supported_versions", supportedVersions) - inputs.property("loader_version", loaderVersion) + inputs.property("version", version) + inputs.property("supported_versions", libs.versions.minecraft.supported.get()) + inputs.property("loader_version", libs.versions.fabric.loader.get()) filteringCharset = "UTF-8" filesMatching("fabric.mod.json") { expand( mapOf( - "version" to modVersion, - "supported_versions" to supportedVersions, - "loader_version" to loaderVersion + "version" to version, + "supported_versions" to libs.versions.minecraft.supported.get(), + "loader_version" to libs.versions.fabric.loader.get() ) ) } diff --git a/gradle.properties b/gradle.properties index 4dca00d..7831827 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,16 +1,6 @@ org.gradle.jvmargs=-Xmx1G -# Fabric Properties -minecraft_version=1.21.10 -parchment_version=1.21.10:2025.10.12 -loader_version=0.17.3 - # Mod Properties mod_version=0.1.0-1.21.10 -supported_versions=>=1.21.9 <=1.21.10 maven_group=org.geysermc archives_base_name=rainbow - -# Dependencies -fabric_version=0.135.0+1.21.10 -pack_converter_version=3.4.1-SNAPSHOT diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..2fae03f --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,25 @@ +[versions] +minecraft = "1.21.10" +minecraft-supported = ">=1.21.9 <=1.21.10" +parchment = "2025.10.12" + +fabric-loom = "1.11-SNAPSHOT" +fabric-loader = "0.17.3" +fabric-api = "0.135.0+1.21.10" + +creative = "817fa982c4" +packconverter = "3.4.1-SNAPSHOT" + +[libraries] +minecraft = {group = "com.mojang", name = "minecraft", version.ref = "minecraft"} +parchment = {group = "org.parchmentmc.data", name = "parchment-1.21.10", version.ref = "parchment"} + +fabric-loader = {group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader"} +fabric-api = {group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api"} + +creative-api = {group = "com.github.GeyserMC.unnamed-creative", name = "creative-api", version.ref = "creative"} +creative-serializer-minecraft = {group = "com.github.GeyserMC.unnamed-creative", name = "creative-serializer-minecraft", version.ref = "creative"} +packconverter = {group = "org.geysermc.pack", name = "converter", version.ref = "packconverter"} + +[plugins] +fabric-loom = {id = "fabric-loom", version.ref = "fabric-loom"} diff --git a/settings.gradle.kts b/settings.gradle.kts index e7b2814..22a0042 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,3 +7,5 @@ pluginManagement { gradlePluginPortal() } } + +rootProject.name = "Rainbow" diff --git a/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java b/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java index 833769a..09e3314 100644 --- a/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java +++ b/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java @@ -18,6 +18,7 @@ import java.util.Optional; import java.util.regex.Pattern; public class MinecraftCreativeResourcePack extends CachingStreamResourceContainer implements ImmutableResourcePack { + // Matches a path in 3 groups: the first directory, the rest of the path, and the file extension (e.g. .json) private static final Pattern PATH_SANITIZE_REGEX = Pattern.compile("(^\\w+/)(.*)(\\.\\w+$)"); private final ResourceManager resourceManager; From b9ea28ee8fef035574a98469317f59f2439c8e3b Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 13 Oct 2025 17:33:03 +0000 Subject: [PATCH 04/37] Pin packconverter version, gradle to 9.1 --- gradle/libs.versions.toml | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 5 +---- gradlew.bat | 3 +-- 5 files changed, 4 insertions(+), 8 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2fae03f..2c56843 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ fabric-loader = "0.17.3" fabric-api = "0.135.0+1.21.10" creative = "817fa982c4" -packconverter = "3.4.1-SNAPSHOT" +packconverter = "3.4.1-20251013.173215-13" [libraries] minecraft = {group = "com.mojang", name = "minecraft", version.ref = "minecraft"} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37256 zcmXVXV`E)y({>tT2aRppNn_h+Y}>|ev}4@T^BTF zt*UbFk22?fVj8UBV<>NN?oj)e%q3;ANZn%w$&6vqe{^I;QY|jWDMG5ZEZRBH(B?s8 z#P8OsAZjB^hSJcmj0htMiurSj*&pTVc4Q?J8pM$O*6ZGZT*uaKX|LW}Zf>VRnC5;1 zSCWN+wVs*KP6h)5YXeKX;l)oxK^6fH2%+TI+348tQ+wXDQZ>noe$eDa5Q{7FH|_d$ zq!-(Ga2avI1+K!}Fz~?<`hpS3Wc|u#W4`{F+&Nx(g8|DLU<^u~GRNe<35m05WFc~C zJM?2zO{8IPPG0XVWI?@BD!7)~mw6VdR;u4HGN~g^lH|h}=DgO$ec8G3#Dt?Lfc6k3v*{%viJm3wtS3c`aA;J< z(RqusS%t%}c#2l@(X#MCoIQR?Y3d#=zx#Htg_B4Z`ziM-Yui|#6&+YD^=T?@ZJ=Q! z7X;7vYNp%yy01j=nt5jfk%Ab9gFk=quaas)6_6)er_Ks2Qh&>!>f&1U`fyq-TmJot z_`m-)A=X+#_6-coG4Yz0AhDL2FcBpe18AnYp@620t{2)2unUz%5Wf!O*0+?E{bOwx z&NPT1{oMo(@?he0(ujvS+seFH%;Zq;9>!Ol43(Wl;Emujm}x&JU>#L|x_ffl=Az*- z-2mA00ap9V4D*kZ+!4FEEERo9KUG6hZNzZpu`xR zCT(HG$m%9BO;66C-({?7Y(ECD43@i3C=ZbhpaT+{3$R>6ZHlQ&i3pzF>(4O}8@gYB&wID6mkHHFf2O_edpaHIMV3E)&;(0bLUyGf(6&=B*)37Tubx zHB;CkwoF#&_%LCS1Z*Zb3L|n5dIIY!N;GMpEC7OFUVdYiJc=!tt2vh+nB)X?L(Oa@nCM zl-Bb`R~({aYF$Ra(UKd97mfin1l~*Gb=WWk^92POcsy+`D=Z~3OIqqKV5^))b_q;? zWBLW8oTQ)h>o_oRyIm3jvoS(7PH0%~HTbc)qm&v@^@;bii|1$&9ivbs@f*{wQd-OVj> zEX>{AAD?oGdcgR^a`qPH<|g)G3i_)cNbF38YRiWMjiCIe9y|}B=kFnO;`HDYua)9l zVnd68O;nXZwU?p8GRZ!9n#|TQr*|2roF-~1si~E3v9J{pCGXZ-ccUnmPA=iiB0SaT zB5m^|Hln3*&hcHX&xUoD>-k2$_~0h9EkW(|gP=1wXf`E4^2MK3TArmO)3vjy^OzgoV}n6JNYQbgAZF~MYA}XYKgLN~(fx3`trMC7 z+h#$&mI0I*fticKJhCd$0Y_X>DN2^G?;zz|qMwk-1^JIZuqo?{{I++YVr5He2{?S3 zGd9eykq!l0w+LGaCofT%nhOc8bxls9V&CfZCm?V-6R}2dDY3$wk@te znGy2pS$=3|wz!fmujPu+FRUD+c7r}#duG$YH>n$rKZ|}O1#y=(+3kdF`bP3J{+iAM zmK@PKt=WU}a%@pgV3y3-#+%I@(1sQDOqF5K#L+mDe_JDc*p<%i$FU_c#BG;9B9v-8 zhtRMK^5##f*yb&Vr6Lon$;53^+*QMDjeeQZ8pLE1vwa~J7|gv7pY$w#Gn3*JhNzn% z*x_dM@O4QdmT*3#qMUd!iJI=2%H92&`g0n;3NE4S=ci5UHpw4eEw&d{mKZ0CPu`>L zEGO4nq=X#uG3`AVlsAO`HQvhWL9gz=#%qTB?{&c=p-5E3qynmL{6yi$(uItGt%;M& zq?CXHG>1Tt$Mjj@64xL>@;LQJoyxJT+z$Pm9UvQu_ zOgARy33XHSDAhd8-{CQHxxFO#)$ND8OWSSc`FXxJ&_81xa)#GmUEWaMU2U$uRfh{2 z^Bbt+m?(qq*8>{CU&3iux+pH3iR@fwq?AloyDXq-H7PI9Z_h^cN>b$JE|ye(Utu_3 zui=tU1gn{DlJ-V-pQ;UUMC_0_DR$&vkG$?5ycZL$h>(9sRbYm0J7m|>+vJezi}Tpj zu0Fagr*Uq#I>f}E*mrje=kpuUQ*0f$Gv0Cvzwq`i(*jym$x1Qn#y06$L3$rIw{D2Y z2t0)ZBY}{5>^%oGuosKCxx|fkm~97o#vC2!bNu7J_b>5x?mw3YD!97su~EaDW+jm9 zv5U5ts0LRP4NcW@Hs2>X+-8kkXjdP?lra!W44a5rQy42ENhP|AR9IrceE`Z5hZ=A# zdB{w_f`EXrRy*=6lM|=@uFjWSQYrvM{6VopTHD)Zh2U;L8Jq!Y z<4W)hb34~;^0;c=TT-!TT;PP%cx!N;$wAaD@g7}7L}qcr!|HZzHUn=zKXh}kA!LED zDGexnb?~xbXC?grP;wvpPPTsM$VD?sydh3d2xJK>phZ6;=?-{oR#4l?ief)`Hx;ns zJzma8sr}#;{F|TLPXpQxGK+IeHY!a{G?nc#PY5zy#28x)OU*bD^UuApH^4mcoDZwz zUh+GFec2(}foDhw)Iv9#+=U+4{jN_s$7LpWkeL{jGo*;_8M7z;4p{TJkD*f>e9M*T z1QMGNw&0*5uwPs8%w=>7!(4o?fo$lYV%E3U#@GYFzFOu;-{Ts0`Sp1g0PPI_ec$xF zd1BpP!DZUBUJ$p^&pEyINuKZXQmexrV0hww?-0%NVpB80R5sMiec)m>^oV{S4E%us zn(z>anDpcWVNO~3& zrdL}9J$`}x4{=FZ?eJ<4U|@+b{~>MyM-FJCgKvS;ZJ>#*Su9OLHJZ0(t5AC`;$kWD z%_N}MZXBG2xYf#*_Z(>=crE*4l0JBua>;s8J9dfo#&%&)w8|=EC`0ywO7L0l>zDo~ zSk1&)d1%BFZwCV2s?_zwB=5`{-;9solZ)pu^4H6Q!#8|Mh26hJvKG8K$T2oIH2lD9 zSa;|Hv_3~>`yy6QSsN%hrm!+tp{**j{pe&fYcWg8S0z^Q$66BFdDg6)Br*)!n3T+f z7~s_8eK4HtrT|%K<&t_`(NsPW+(IQ1f3GA*0oO{eCE7J%-fGL;6Y~#&-N-r*DV!hA zvj}4FFW~Cd9z#EaR@nx`bW z48Tg|k5nzV-I*vIoC0a)@?_;DtZk(JY;n_LrA^uee{j#$h3}fNY*15` zl2wj>M{PmUHB3KRXBP2GWW|B7RZW({nuZJGN2O-u=#BA(@vG^ow3n$e7u=+dSJo%+ zF)UA%K8xA+r94&p-?FYx+LqfW)RrjSnFBj{B;6(5co4rV6V#XI75BFVh*?at%%o6j$5)u2|TE&BCB`euH0!jNz z5(Lf$;>D3VQP||uintqX8WPrn*?+)6mD`K=Txz+5gD>2GE zk!IdlA{A#%`Ll-BJj08U>fA!r6S02S^dX(izeGM4LcY>~g^U$)vw% zdV@b2g#?}*)+*iDWmOHR`-VCd(rD_1PSCs(b~8Qr69bhp8>?*1qdrRZCA|m@3{+tW zQyre2^zuuMI6PZ0R9!Ql_Aws+fjw68TGiR%jK(IzwVTEvUZ`9~SQ_RVJiVHHcO_mgr5 z9H|@8GY4tUvG3DNTjSb~kv-P$F03=Cz+u6nW_AlsxpZ4xg~w3!#g}`r_j0 z13GpvKRIs?B&h=op~7Uj?qKy19pd+{>E+8^0+v2g1$NZ-xTn zJ4$dp9pdQ7%qaPC?N<1@tQC+7uL#of)%e3l>Yx4D5#Cl6XQNp9h0XZDULW-sj`9-D z3CtoYO*jY0X-GVdAz1}9N%DcyYnA(fSSQO zK{a}k4~XXsiA^I#~52amxe4@gMu*wKLS>TvYXUagd*_35z z>6%E?8_dAs2hN;s-nHDRO?Cgg5)aebjwl7r`)r{!~?JECl!xiYr+P}B4Zwr zdOmbCd<-2k`nIs9F#}u;+-FE0a&2T;YbUu)1S^!r3)DNr(+8fvzuzy2oJlVtLnEdF zE8NQJ0W#O+F<$|RG3pNI1V1a*r_M&b`pi2HLJ)v|s;GTci%_ItdssFmUAmPi<9zLCJR60QB!W zv+(O(NpSnRy_Uh2#;ko|eWNWMk1Dhm7xV7q!=uPIT+hO2+2KU*-#)1itWE(L6tH&A zGhHP!cUcQA(;qKqZ^&S>%-90>_??#B3+tPkX!G+a94?X-R>fCt_^FaHOo%frkS`E> z@PzQMtrMaHn;1v>s}CYTJFn1=yizNIjcd;lN8@Psf;vOSZ3^4j^E;3BYS|daR6GP% z^m+F}lmIfj+sjDeLd`>m>78^3+?3Uo?btw;L#_{d!w9MvI&55j!1ZJGwz+UsAo^BQo?GdP^G*6=p&BL-`U1i#!DO>F=UztubL7A~l6wQKufoz!z|qq>)y!yvC?!cww9 zsN?(kvGVUGnGzaPX0c`^uk05P+fog+pTv9A0&jevIjlNrP}1MQHo{^-N^cJB22-tk z`5~#kg~Buvol0Nfve2_7ZDcNiqKt+#S);@IaC1w69Z4GR0lxxV6?~3BgH2>aAxTI|0-FcbzV01b9Ppiur#_!#Y zjY<41$oTWx?dbfsvix`{xE$*OVqrf=%ay$&4J}yK2<{S|6|=SC6bhJk)j_eLZgIEi zEH1*&%$`YPSzHsJoq@YFLK#k{s`2@fVD^0%vz1duXAirWESQ}jXjYU&FGAeY+S8Z2 z=+9u@YuUFbl143hX}wNPhCXJ!B#HSrK8x@|`}DD*d^;Da78#i{-F6YAN`mJfC4!D# z;kMqJXz_P<{=fWLnk0$BMypYBtXR*ZyGH|R5=mbzCY+&I@jo67#GS_jm?fkPa)JpGZ5&uc^>dPC^oW@oY zaxVTa-6P{GoTQU{yamt!qNk953k|$?n6XRjQ6J&~NxR62I1#X^`ouJ1I{CTcZLs2} z?+0J0*2mIcjoF!5`WU{kg?Z|={u^D|O4Rnl^q;H@6oUF3dJc>LjF~{sh;N`rA6WPt zHb_rKj|w)MHU2!G#dPNUu#jtTQ4h8b)$l;b5G|b@ZLNuO^Ld9#*1 zv{4vY`NUnYD>ZP)h&*VP*}32*8Gs(e!j9dqQ{O79-YjXdQcoX5&Kxj?GR!jcTiwo` zM^Tv$=7?5`1+bky_D01RwT5CYM5WdtrjeaD#APPq{&SQerwMYaizh?qH}rQPY`}7u zU`a4!?`Ti>a%$t5CQ2}!kkk?-}8_CjS|b3n7IoVIft*o$!U~yM&_@FToop( zr8!`nZ>CgUP{J8yVGll;5+l_$*8dv5a3(%}`Cr4!K>asPsi-7@@``vYC3 zS*?}cQYaIc>-n%KsKg|+;=iPZ0y0;4*RVUclP{uaNuEhQu(D_$dXZ0JMWRG$y+t4T zX708p?)DY%(m?5y?7zo;uYWGL zS&B^c=(JH19VlFfZg9~ADPAaCEpdKY8HSpVawMnVSdZ-f-tsvuzIq3D|JjG#RrNdhlof{loQVHL~Nt5_OJhCO6z)h z%}+h1yoKLmTolWBVht(^hv^z?fj|NiHL z`z6MU5+ow>A^*=^Ody9&G@-!;I-m-p^FzR*W6{h;G+VprFeqWF2;$D;64~ynHc7}K zcBdKPq}V;tH6Snzehvmlssi z8y{UmbEFNwe-Qg4C3P-ITAE>sRRpVrlLcJbJA83gcg020 zEylMTgg5^SQl#5eZsc$;s3=9ob<{>x$?FDG4P2FUi@L}k+=1)5MVe3Tb-CBoOax?` z+xlo{I%+m}4sRR$Mbz=`tvwPXe>JVe=-lMi1lE(hmAmWO>(;Ny&V9Jhda;wVi!GoC zr9%LJhlho2y$YF8WT0UvrCVb%#9jyNBHaHhHL~UyeILeAWAw^}i8$ltMr2Yp6{lvV zK9^=_@Plr%z5x2-QX1Anic_;-*AT8u%f@;5Q|x_-kS9$kbl9T;Fw3Wq_32zfcdGQ5 zsqsFFE{(;u!m_6vYVP3QUCZ>KRV8wyg@_%Ds`oA$S%wPo65gLLYhLnyP zhK{0!Ha52RV4CQ^+&a3%%Ob};CA+=XzwNEcPnc3ZouzDBxHb#WSWog z6vF+G-6b?>jfUO8f%*V2oSPN_!R6?kzr8|c+Fo*tt-C&MyzV zT>M65Pa)4#)7ao^6Jj_{`^jb;T@hb{neRGTuMwj~SD9U}q;=niF!g78n!Y0jEXRlT zrSw;qZiU2rtnnEMvN);}=q2Ww&2bA5PV9^W|0f30Zk7Ust-%Q#F!V~jy33y^($hsQ zh@n}s$T7sZUzn69tccDf-a;lg4UWYYI|2?*Lms2$ZW)GI-yaymOBZq!&aOm4 zg4iuvQM|}-y=U>fOaLFvu(`K}T5BANqjBpqrY+RxviWLz<wNld3Q zOBi{x%;Dka>Yc!KK(3mP@37jmo@Mz0cH(Rqg|+z2!Th&@QRP$Zlhz@#qUVwNe+&<| z*r@@F%Q4dEBnm;=G#@xvANE`CUE53}ZBNBrRuqYi#x%afta6su7&}a?a=G)rKmkK) zfjZ$n!{l&|aa2~)$69+Gbq!LA1^Pti_X2wMfoZ6VO{Rm1AT#$uuVZ(BazVh&l@OW- zT&hmX+Zb!T-c3!_KhLAl`Sd4aJnvwWL)ATcbxTo)LJ8GZ-c{m0EPu+zW~Ir!S2p^R z)7utF6qj3+BpAq8RU~RXZ#vwr6fQzM@c$4CPixQ3Z%q~(Alx$As{Y5{Cbp0;11^${C_}W!KX=~W!zReTO z?aa+Pn73jCR%p?&9s643`gJ$-OuXOBFgbk78U`PTq*5GyBOEGeW2FOdY!hji?{7H` zRjP4h^JZ8T0%?nBNA2PC9Cc=m(>G{}=##WMe%2j)u<5pldvt2csC#l0wc#&V%;cyk zWRp}bwR8iEi_c7JC-~eFiuoiUu+mE;l12%pk|UO09_2 z>eE1B&MK95QzvySEAf?itp=4n5RZtQ$!2{B1<9x*@cLWsfmJqMk*oh}fD%5O4^GCN z37Y83rWzv~4>w0jdKxzV49lPdpX1creItd8F$w=Lfu!az*ai2r-M*`MZH*OY?sCX@ z?U*kR}2ccC4KCV_h!awS%0cY($fD>sPlU`(3S4OKo!ffovsG`JkUc7-2 z+}NOCASI}n03S7Dz*1Nh^82}i7z7eqFyri!Um!##*VNy`%3$mPBlXn`ip9zHJE%}z zjt$;Rdq|?+3{hmT35bHJV`Xj#uR;re^f zVF>~hbu#vv>)49SP@HCVD>4wm#-7fGzH~Z-9-*WcYooVzz{or zHO^zLrYU#h5{)1kv@V6piPMn0s+=lG*1O{VbBXjx5ulO4{>LN16ph1ywnupD^sa3h z{9pWV8PrlGDV-}pwGz5rxpW)Z(q30FkGDvx1W6VP!)@%IFF_mSnV1O`ZQ$AS zV)FekW4=%FoffthfbITk2Cog9DeIOG7_#t?iBD)|IpeTaI7hjKs;ifz&LZkngi5Wr zq)SCWvFU4}GhS1suQ|iWl!Y^~AE{Q=B1LN-Yso3?Mq1awyiJKEQNP)DY_us6|1NE7 z@F1QJFadv}7N2~GY3Sm`2%flyD#nF-`4clNI)PeTwqS{Fc$tuL_Pdys03a zLfHbhkh#b2K=}JRhlBUBrTb(i5Ms{M31^PWk_L(CKf4i|xOFA=L1 z2SGxSA@2%mUXb(@mx-R_4nKMaa&=-!aEDk2@CjeWjUNVuFxPho4@zMH-fnRE*kiq| z7W?IE;$LX@ZJBKX5xaxurB-HUadHl%5+u|?J5D^3F-7gEyPIBZuNqHJhp&W_b9eBC zJ#)RQwBB6^@slM1%ggGG#<9WBa0k7#8Q-rdGsMQE@7z%_x3TZ;k?!c2MQ7u^jDu4ZI;T9Fnv^rB~;`xB+I-fZa&&=T>N@GuNZd-jiU%R`> zdg41iOzr9Z`rfOKj-A8r=gst5Bv@tY-j?$)^TPH6IGW1>FRrd?y9AsafFhfac5sfS z!z_v2h`^Y(y_>97r`7yy%gWc{J7hW2&B`p#p}HXCVi*^HJvp2-WzYKK^I4;72ymXKPRH?=UE&U!VZMv+EHmXG9J91O ztTxu>>##+KkI0EuT}Sq zm1AnDS6&3GWLaQSXKe1bcPXaJ;Cpn1(2ZpSgh-+t8pu7ACtHW-w z<%tjAl1TPw3()A?%a1aRDEusI&LO}cTlZJv#_Wah0tMU9+=ab6I>onMsi!pR?C8Qi5hBK zz~WZrR}JHGK$y_~ryEaJGbP-M9fs{8KKm|Oo5bMEcgeL%l-iZiSFYCuq@`3!w!#Yr zyuV`jA#slqYf5hz*}vq-Jjk;>@MVJEG$gD>268u)mQ?UX5_cq>+I9Gg=_XKP8SSI# zm9^(40#wZfS(o{m6fCDHa@iWB9K#B^&xd3Yd%)Z;i8n9=i54mA7VAyT<~E*Q{aT*% z>qGD?#Y6ot;FivJ6HSn$Px^aWo!iJ*j@fA8l#tVL{}|ZWe)`UXEmhPU<5(Wmr}hqO z5x8Si8g(bqEp+Rc$fq(aPVy$*?HhLEd5uAd1MD6Ghg$&DI5kDBsqMpF5gO+JmIpY3 z#vKA2w~URZy?*7nOwW>Fa^-6H1BJ1%*}Y?Wm4yL%!Ls>9fr5L9%(BKIDLKy%@Q+J- zK+!+kCvuSEn$lGSdns&>@c#nqJf7k*gglAyXSUIASL-C4oMoCYoJ4-@)SNK9mW)SsFda!>q`@Vq;j9o6kQcuH( z41;6DW{~4lbk1Ug=5gfQLld^uo+$*@YA}!bN}ekTEtA3B=6-ztZ9^KDzT#S7BUr#& zYXGhILp+T`lKFHBX7me|SCAm+5~iY87Hb=_z8oEE5o+W=4-*xQBPrada%)U72lD)Fm8Xpm0}{*^f>JwiSpjvoLD#q#n@nTuW!I4?JUPJ1AjXgc!au&1fu zo+XX`WjA*dTfSjj)_M5wrVFz?6r2)$`Hr){4FK{m7Eh1Mm<=PBV3=*yl_^UNfO z6)R`HRf7)be9|yAPbcC5(Q*gZm#o zt7hlICpCLq(o&n`0gy2Qnt->2DdUH$g*Zcp^05HspJd7idiX14g>j&@ROzf%K=6EGx<> z%L$cau&Jb&x^VE1z}9jo{_lJ$L1I59^a$x#uI>l4``?WWR>Z$t(*p+*j0#c^W}pw`7oI1R9MI?&A37S03`}wlOp_CBmD~javahP%)DcMTJMSDph`RPAvUaWgQo-L;&Ag)hZsl zl;s>Lq?@9lJI=cSo(K)Y^Z7{cQAo0GXA+zc0iwhzC07UV^X_0(CRx|h96VB!R3e+B z0g(jHwBdryOVB5jtt>yrYsRdLU-%G_vUv1JU>Z)CKUNy&7lyb#bDn&t{_KJx+H*i)ia<4j*Tru1+K zHg8V11BJ*|KFH>(B&-T&fc>~VYEE#1>W<%1amEqb;Cx7lTKzpD1Ltn_;l1=%z>2OyrQ=%ByoQnP`;Y zP?U`ye<0gnxlJ~8ulNd&7IC%B6y_+)3TZi+BD2+0PjA0V7J<>wYjxO#bM8kp!qfOy zZ|e$u8^hUt8J6Z7f`)!#Ad7Cn6ZiPSNC`GYMq>`S-JwwZ4Yn1-9@020LZ#Ya>i-!O zG4rl1X#e(NTK_Ll@f1`9D$6UP3#0f=U9z6nlhIReA4B4S;HWbZvC%~D$yp-$TofHH zY#aEAPIK0T!roE7epx6;AmQ^r7c6GL4F~y^UV2|GRmeQd{M!r#%Q-0PP0h?iJ~$&z zu~t|k=Z0ToUqw{Q!CW6zIo3)$LNne>AUO>iOLxu7h|lPtb?ci0s^Lm@2*(GP(TnK$ z3>M6F^KhG15qwqU{v2lBHD}#CPO2BP5c_EXSAb9-s^2dhkwi&j!H)bBF#=VWwXksQH>v4%Bsp=NgY>HV9E&8kcoFGVNHb7LbeNdKxm7L zkFWH_GKiz)r$?X%_ROX;8o)O;drZG+3b()@^9Kmi))@1!v=uxh7tia$+1mBk$+;48 z1V`@<9-9K>&np9#xsaOg` z>wl~mcXr=877@BzV*93nP^h^U0@UwC@K8%jIAe_IctQCA3zYNWWSLTET@9=gqXH{! z4ek8YxI1;`Wb)i>s(eY1M;?EaBqS)E?#sJmf#Y6jsG2G!^E73>AAgVPgi4f^yXsza zwq3<{qW`cY#YMU|8*oCt3z{IC1(Z?o%w3iV6}=*V=nx5*Po(u_^{%DqCLXU_6htol z={XfRa_S~F;4Zsw;6RSl-A(OGkDu48`uD*3(noV(L0!J@%sPptPL%FO^cKplLC;iq zTaTB<+O+D&*~2DrK6^u%XT})Jrc7>+Hj@xOlJlVxz4fy*1?b@Oi^8FG!bqlBH8o!n z>~F#%7}Poj%beNU1S&5x!B+k`Ca=z5lnsMj@seyz#H( zBmYWn0(6TaaS}moWyC)pJxlfy`-$oV7Oskdn!-)Yc;V#3KYe*_ZGMhVdQ0L9fyF4c z-wSiCOl=1PDWzMyw4}bo!6xYM|Aw?nLrCr0-s!v16Bb%Hvl_Espc#9hP&tv$`U6UJ zy^vaxzV#q$tN}oEh{kW^cVrO~8#|ojb2+G<0z_A%FyCY0<2yecnF&67?RhxR%0bwr zO1dvJ%fy*DkD7waZn&$Lz4m{SZpn@EBm`Cp(=5XLnY8jZbN*?W$|%bwS@18_msB5O z^ixjhgR#<2tP2uito2!ptSztQDEd+KV~yUAEvp{s`!dF3N-51kNJ)|L9zzB!N5})3 z2~gg%x^~{W$L4p;hMSn>=&!~jT53Mq?9VDefsY0g6wH<%_B|S_J#guV>7?S+x6XC>d?#MLnx+j~p-a?O2PWCkw%M$X&jl*xmluhFy(z79P;5Y|x!^O`&yOpw?&mCBxakmlR07DAM zRKSK)gruDZtjP-;Vx;=Gn^iT?OiB&G4uqX;G{a(>XF9;n%3+=X3NV{`kG@klzsL`M zWx^4-d7^~n9gOVl;0ud;e}}M95=h0L2^TQr*7uYZ8A1f9<+bLS;AnnuDu$&T@j{>!r3Ytg>hxTM*Uy13Vi)!1oH?iC1C2m=wdh8b%2p`n&3zYo) z4OH-=jYTC1udKOaeuVSp#60OwD!vyCRY{Fk?2`xa9NN<_w%%DGfe5?g#KahJyn6?%AwY{L&=pPJZj?FaEXqYa29=8TUx^^gTZ_L0x2tI&!QN-Jy^qVvtg z98&rSm50IM)&OVeW7$c1)yh7`RPp(`f~=Z@M9T;!`J~BnlcYPzzXHC$1~A>FOYZD0 z%s+A8EeGmXA&j-+NVD;*hLrAb&m><5a1r^wEEPV~O{9&oT&XQFn* zSI0G0vXOaD`|zKYld3NhDff?|p#EP1E+#Ds)cN0A_iy7vCxro14W*N*bVEc(xzAa- zk5s=`2rN1p*?bl0V%)uD+Ftm7=NY>NGnS2F@==Nz|2Rs6uAGisqqK*`^vm>*oga5o zpU*F+2*2pk%siXg+T#54m|R@cxqtYnacSIt+j5Phm^kYG!xNsLiDsJGkGY9Ql)DSIe$RC;4mV*-foNZg$JC$AX`+)tBlw zp|Eva!~!~Uny7m}0}x1LGd;$Um<|$JE9I3bq0FI3$RcDohUM`xy?b4HomEe&Cl_<# zct@|E6X^qCl>bnhX`;-G_mlO@;!$M$QYO$`P%=PtmK!j_hvOzNJ9*26h0+58UYc zChyB)J`r^Y>V3XqNQ?_W?_oRBY+@RYXAOZCAa-&H9>VfzCc%Ls&)0{~dXtWEQFS;qps^H_eaWb63T%Jmdq=132qfOJj; z^o!D$8dRA3XPaeB3}}qvc%-aXuob>UCE)F6P5ro3cb!#ay8C7=2MI0M<@Spslua!Y zfH*S;lhxG@Wof;QAa_?t7?03?HrKqeQ}NtxoW(0tgJ!6g%uz&UZQvZiZ*_<&^~U)- z!V4a&9U%vfoGl5RFBq{M(&r|a^e5(;xiFM2v(CV25AGXix*J<43);ewr!ap|`~|Q+ zS`#Wf2A!X__5S-QwC|AR<0n_t;F<7&+wb%%%ga`QI~+7ES{4qW)(xE-yUne2BLUGF zLiYE5v|w~x`RfrTF`QoXzl=h`?yvA4(EnqD8EIz(F#ixD{C@~ZmSX~H!g=bdV|+TW zB|h;G$gmZKoUwdtC5;IqG(~hz_Q#1&Af@26lr)YiCcPcwmxS+8ZxE$V%bPuiBw zA~$U}Fp1)kwt;jZ{+_Zrt|`kt6?#^q+=mSgS7BK4EI~GblcEW9r_8B)a7`JJwB^q| zcK7Y#Fg9o4uj(DCHB1$#9BF7z4>w?~jV#fHY63KA(IxJ2j(Mmn&r(orNO3#p;AHYD zr0%tDqJtl6piy77+VT@EB51Y9Jx!xv(Pp!}PR{}0+MzwL70welF?GrCu9oi_ExX6I zzE5m#Ssb>iJJJAY2>?_j^ogDOl;$*+)|Io4uK9LeP(BTp0I%^ga~6!?QHo=n;ywLd zrG-{s8x$%dWiW)gw7o*>c8sk4-_8q7BdA$`N}I~fC`~)ztO$y4!A`gXa0|ugSqk-_ z3A?SP(W1zbG54hBLZN|)<2|!d3)ra~joK(-lEa5y+08P57Aaw*;FsN-whG_mRCX_AxC%{gOp!hzWL&%q_W2e#Y<$R!6rv^!siuqhAa@0It`#*?lO zbBF~rIau~T>n$sgYaKlMkd8b@bvT6s>v*YIq!F@9D|}ZuJFIfX37Sb#-wB-92wI zp6&n&FXp-hxYAVVf@P!=P**GZyQ#!Mg3g+ z^51krxe`VAv-L}OC9J&}ndx%_-ek%vwpfAk&fgfw-Ao%jMm104avlW`Z}&9^IqCI{7K>-}u>Hat;!vgwmJ9T3l$o@^nn>Ua`9s;MQ`(w-+g10mim*e5 zxlQXo{h%Vfx^0A{E!?>xTlB>8Z04xGDa?68hp-sQOkWQA-p(Wt#tUIN5Q<&B(d-VC zRg|2etlG(wZ<_M+>&m!qCmX-I?*cH?hiINamr#w|+kms1= zgoZbkmpe<=OGI%2@TC1rTW9{Rdh;E04XjLu7mz3|*)|&vr>%cIXr=qr^(;p5Tr4cq zx0NKfuash^OEFWpuX;##)kymY2e|{J$a=>aPb$c4w17i_zbv{ZpOGz(M54{ezi!;9 zHIB&tIp_%n<7jaD7#Xe>KBw>dK#TFTAY2Yl`;4z{z9%(iYWd7mnlNG60du1ShP-Pe z!(8til%B7jxcdQBGwtER!)bJ%PrKecGyk(}=O{?a*>H0~2#-Hda;S~agxd^w)RrP| z_eSB2nJQ*b=B9MRJ&<*AhVI)$t|i|SSfeTia9LfKm%q%QJ=yZl62HQGHV0GO)k(to z@WU%$pv}3hE_O4iJ|V!;xI1&VhUgBuidgh)-y|J_!Z7=K17xIOM@Jvk*L@q18(BW9 zzKr?f)v;0v5A*&@dw`F|jeiDM$tJf&sCq+IE~56;tmN-J!qAj#0GupAa%ucNK)@p*ffr-`???~*)~kK<6qjrpyNjhUvc+9h;xo!t{&Y<( zKwnT7J*x=^wfL26KtPUTCO_!2eo=c+1{n*ZhtW*YmfIugMdvRDJ(W4|?~m&JCrB02 zV#==*`M>VgQbW1o8YGHr`TI5ZklZ>$J151Kj{Ar)%d5MMV?BQ`a%n$>OK}>{vo5EF zO=nnE~;1JIL)smt2q ztjvq09vBFtO5B2}3sjcZ+Hyg$!A24`+wyS|X($ZaA_(Wia@uR|N{khIjMoOGo^V0$ zkc*@h80LxC3EJT+qiD=>N;g0AF)H7~;8S8gJhhgZ{yzYFK!m^G*<`RVa9MvOxnsvT z);1kLd-DNon82oFXVW+?jvPSO(gWxz;?n&P|K?%~5+&)Ii4tzPa02~Fp`nP&I$2i{ z+q;X{c|j2at-d07tG|e$*4ju@^U|;{><`zDWB0z!30TR{m636{4@o8S=zWnRFV@L1 zghg^(Om8ePF2U(?)NqCz8?b*uj-CsGV3S0WM-<}KiRQUvVuB*TXl#nyiw&XSgLw5E z@@t)>_DJe6)J@>pq~MI>_4na=an3nXZ7t@Uc7(z^N#6nDEhAND(O8GK;H};U>}gt6 zOXGa0@@-P(!)QzPNctURy4Cj>8p8CWP2k34bmutURm3d|T8p?XOg?|QrHI>m_Cjqc z;{83*L-6gVuggLo*jdDfZ%2@HwTC`h#3w_a?iBJ}q5b3dY>51NFqv%ig(iyleCUfc z58yx%hg$uiFAMrBKBAK~p|2%~8TK=pR*HC%xJoiwv)Ui}b`jrOt z-if>AxS#wY#z(1s&!O=ts=8u)2G7dzIXo{%FBW}JU%-YJ1)$pq?~4R%72G3HJ&DUv zBO!hxu>=SR`!(=SvE;`CV&a)2h)>Fl6@-lJVoGlDUqijLlTCkOhv8!+Oi}&?R+V6M zD*_UvHwcuA!2YTn*iJ$Hrc8AS>UU+TTTp)}Q$2$E(@{VO@-I`Qe}O8zOzL;E*4Bic zPxwNAPxzyW+ORL7g#8IMl2}mNlvtoNCqjqAwfEu0eKH@ZWs-QU`8QBY2MFdV&OX@* z008C^002-+0|b-zI~J2vdKZ(=rv{U7Rw92<5IvUy-F~20QBYKLRVWGD4StXYi3v)9 zhZ;<4O?+x@cc`<1)9HN?md@n0AdG@AGW{87f)qA`jOzT7)=X3or+x%b=m&tCyN zz_P%*ikOEuZ)UCe0rdy#Oxt>hiFfjbkCdL(cBxB;>K*okOAZr+>eyo3Q z_N5oonjSfZFC)XvYVJ6)}Y z>+B`rX{x|n^`Fg`a5H1xDnmn|fGOM-n0(5Q&AXpMoKq$e8j2|KeV4rzOt1wk ze!OhyP@r)+S3lBd^ zM5~n>nC`mirk!hFQ_*2We~y@m&Wd0~q^qL3B4WjRqcI~LwGx52)oEfqX~s+=Wn#0( zNChH2X5>gJ6HiqHyNp=Mtgh(o4#bV#KvdA^sHuo9nU zqC1)}&15vujn$)OGKI6SzP9GdnzeyW^JvBEG-4*b-O3~*=B8-Oe`H#0CA(|8lSXIE ztUZ=AdV9@e?PmG8*ZyiXq6w9pOw(^LjvBQwBhg*Ez2gQml2*yhsz@8brWilV#JWs9a{#NSTpLGMetI9S^hKLmrx< zQz=blT5xe#m8LUIf5AbGP?jw*)BFiXjP8QCm&$aSK{J`=Oa`UWET&SB4OtOsOeiK# zG-0M|ckc{=&>ZsVG@Ir!dB*OjG@r?pws!AqnSj;;v<0+Kr_0D+h}NP~1yc#mY=@7; zA;!!+>R4@iXfZ9(X%Srkt8~G*8dVlp&4yEHIg{JGF#{iCe=4sGjW_H1W&1o-O#z*% zs0OyOIf+`ef@bXwBi#cdu3&P2A^1;ap%8hQ#=?WORdl6JD`_>8cjCTEbzmuN*&aEf z7l4QrV6UZhrL=~E;HHS1sdRPT8{~4EB|WXl?Al~y5}nP-q?J@@V_vB_vMOE6qzXp_ z2Oes$b=L?+f3A)uqUnv}bTi`89%`mdI@Qx=+a^1Vq?t&2s6`N{r>!>8HY09&C}gj- zg6M&o8;s;)jkd#kYI>6vA}bv=QyRSrd?n4^m?0uEnSx5!7CE;FC&fIVopuSc?Pgkf zX+)$rdj*r%+0kN)BNXJJeY8&O>}T?i$r6!R6!8#`e;bL;5b_NWQYQ3!5FSx!(>tWo z^>i4YbOE;E~MM*G! zqed{8f9u9f)J$u16e~>{9fyfieW|n=4+ukR^lGN5l1wHYjn#&tDWuNVLa25#?Y9B_ zIgjY`TV4KikLlmKr`2C+)^ykS15NQhvAZGOchrbw%w;ti-Gmc5%~T{A&FRNm%o%Q` zTLhoC=97Rty*`;V`Vhcxgm#UT;Du>Pfp+s*e;`!IG6=qj-mKFJx^1E^r4w|H(Wpvq zh4MxzY%x+j5LczQp(NN=O*Qn{tin-3g^;aAFOGXVy+b(3J0}prwo3m60i;6UQgbTD za@%OdVs<3}kvr+#I-R8VF!?Hr!`MFiKArBMQ=*WCCUBhtdB0A#)7?yUuM`Z68_X^% ze`$wvd!{3|uhIvZHdkK6X>IKF;~^#}H^yT?f?9IxP|wHd6Q%Sq>SwBcMXBsZd)i2Y{-^Ti7En~_)5w45X4=f-X_*iZ?4P0g zOX)s(0A(p5mkY~R&fh%rIeJjQeIEWAe>eI%Oq`TVZ_jyn(PRwbXDF-Fy)?k21Ogg8 z#1wc%LF&7}ZZ03GG$aDxQg!}_PG6u$A!8u0|N0FFt2BBHA8{j%%AE4hmjpLe^ktNW zRHh@9bMNxXmZI7Et8`94KaR|6B?_e7cZnt76-BiPjR(`ZiP=O>~;ax1%yRp}ZCk zeV4u`boG7V%Po_s^M?ZDN9b^^M13xeGc^?Rod1;DAJemf+y6m++gr{_g$;ug(&0tGfuRQyTEK+-?ap9P7( zAb+GSd(%TNibm#n`WuXe9sy}FuU-%RgYFla`KQ!6)Yuy{)94*uvd#N4e>jO@FiH2w zYyd+J1CXj1b4aO`XtQ#CfrlMJ!}qcnG$ft8Ihqrl9(IeK;$Bt@`&n5!RW8YOE+b9V z_<}IHv);p{?9o~0DMF!8^wpQ*9TT#_XnVoaQ5ARw(-oJ7qjDJ%LTFq;&K1}@xx9pD z@~nKSO4$ykjeLd3xxyi(+cRCByH-RI#e;eYI7Ocu^m^wp+^F-wSre>D^G?nt3o#p?tF z#)*YvN+%kEZX+fGzWI2>%vlSg#XOr;Kgyavo{6QSaB;ugdemsVQRfXJ;1=efIxREh zPgrSyA2t0(qR$2eWIej_NvG}I$OBu@_l7L%NTye13?g%ynm5(&4(&R$d1rl7sQJ+D z_U4_3wrp>0_HZ*=e>-mCO(TtSjcA-}WaG?R>;X0B8GUfgOG*Jy`c~d1Vj~2y=^P(OPz7>}GN5xN9VS3%^yE<#rgUR^vO6e-1FYrd#Ze%ERxlivZ>-MpnWc zrKXH7b9XYzv|y6koDtG@^1FqCF-}cMTlMXYEiJhgf!`-DP#7bWqqXTOjo%LsEWAW( zHB%|0+iZ$nw{r3{Rh$O+`4E3t=MOTbAlL3)n*wV!7K0DSHuR;1 z_suFse{+9>hd<7r5K2HXb!U1zk@G>Ja({!URiEN}1nytap4x_JcS|B|$^`Kl zAazO(M5d7B9^lUkoX=sWvPF`Cy*{t={d`(bkHj*m=uvs& zTOWx)g{?*cT0~fH80&jc2$)P5G5cmNW<`!bUA4`VqC@|W^Aja-%C9lapFH3euT&Y+ zM)IP;ROo5NLLx`4=w8umXj|bMI-ln!ZLg45IH(^518DAEhrh|+(n;l~Vbq#f;Xad-!{H-pBk=8bz0%L?>Y-(SH2UUdPZeca-AJOd^duIi`*HF=nJjD--LK ztwAJd!sGnC@~+L_nWyIOvXXwGcE2!yUt^3L)4+9oN6Lz2(xz?MpUO)`{+Z6tioQcj z7zs;cW!YeF_3$tGSE4rm+C}2uw1#UPf5hK;EI)NX-8)f9t+;JTc@xSQEG`?lmW}in ziG&$TNwYNCA1ePoFW>}_5ExeZ4;a9c$29(<&d-U0t_yA3U`&@+j=2^tMjzV$3;$K1 zz6d8yC;J3Zk&Y(A6Z=5=JO4xH=NZGt`u~R?tNaog8F}Z>7_(C5tHgC)tZy`Xf8cbv zAx1md&R*bQonKa{U>@1k1G9Fjih@*u&gw)h0!a1v616Brr4FL z;?UA`;j$}ISsGCMzf=6=hNQ4>P>g8mer zxF`1Ke%lCnl=qr+jW=Gu9O$bhV3%p#eROpIdS>&M>`)!Gk zWq;w%FOy))Y@jUFmAOhK$`=ZXh(6nB&Nm8*mv>NE^= z^7n{VGu>lBplgc|*gt{5SdvMzOWcXp+7v*0of6ckR9RneV^IjDDjSd_qlu%|5hS2> zMFz>qua*mjGUXcOT3y+we_%**MMSK5lt%bHjMc={JeoRV;%7Hg-jUnd^XIkc-&()Z zA5G+!$Cgh2(j}>-HJXBX$&DO~fDlnFMi)RlB#k+gemG-1yfXY zuI&0pr$4)N34M=F!g6-PK^UwyHX?~*sS|@_G9FEs{)q6yUQ{+Ie=eE%w;D-*SJI06 zBUY!`0ip9IJe+SUe{-EedtV}L93LZZhq(Q@2=ASOclfGP{HBXMfJ_-Vf&pTefI+<# zS2b;!c!!ykD@gG!Qe`Pce36F#Sm`F3au{!=L|VDmm8EG}D$mlqEL|QBWofB*S(a)~ zsn1jm(p3);;wRKk-n~OqA8xJ6Qqur!sSYi#%71Uee{J3!f8L#0+A~1mEFG}_LPKSWr%JM2c1K7M>uer-j${I4$xf#^noGzP&nuc_?!cD&qMS{rl8yBeuzHHbc)aU zT;lyS(_k&J#ZMP?pYT z>FJ=WfA~J^e@E`ui2dmsvh;&G0ay;uXKc`Nm-DcEdm>9e5lF{?^fQU%7f8-gP@n1^ z1>5l;{qioF1K?jvV0S;24$*JJ1N6UV13&|0P=nMye=SSTouZk7mUz$eHa(D|9V`)0 zB@*flKGzUEANG|T^1d)Yf6UTfv-EedcOF7#>0hU)EH9|d#)Yr>@NpsNa@A?&norHL za?gb`K3BQsJS-$F*QBUHO_J3L$lAitsI{r3z}98FAj_AB>$JORhM-r*i?Y0Q zZ~ySqJ}HV%b(CvD8r69?XKK0qd7m>J5Jy&dyM>_NeC=8LwL!c-$eZ_;amygL z;;eI2EOTe`Y~d*iSpnLm&jz$~>U^T)~olxCvGs5i81_ zRl$;gPxF-sN&!LWG(R>%3(hHtL8pRR$!Y#_IH>2TmH1pCA*G%tc15+Xq-qSIbA^O* zukI0=r}^tcd_ElVK~kTy8Y+D%%ioq+INU1Y+Oev&pIqEpeU93Pl)2#pAwbN_DhpbjkI-ddM|Jz4vN)?; zF`z6PR0248WtnniR#}7H(s0P(-Oyg9ti|%xSWvOByq)pYus5qTe@>`Pe=cuxQ~_-B z@bclf=lcOJrbnou!#*7^Z5aN`&UoVydKToDVq9 zs81@_IR~BR=_91tAM)>dm2Ow*UX|`6dWq^(s#>`Eied7Ke+Fq7jgnRr7GMH= zF`mP;sR+=Md7xpmRV9BE_lA& zI4Q}#Oe+L~f2Re*v_~jIA10k#@tDJ)NC8QAYpQOJ;Gg;`O zIE>`-WlCty7o|$4e~gGb0ZxKQLv9oY7XVRSXZ4z^Nz(kM;QKam2t7%p`8H)fFTcgV z+(x-=Cb^;Vb1FaYRQZMcZUZ`H0n5*e|2+r4Qc8x&U4Zj~jq_X{M4D-NjNTa+D=M-cednUESgQS3}zW!9}%Ytwo*z)e>a5nN@?WZh}Y;7mq<{) z?gDuvF>$hBVv)^++>9tuJZos1oFdj?e+NX{M@}*!a};{%1IFvY@w;I1dvFLESNaqv z-Urh@fOve0rqRuu+!to+4ayn?SQ>7)&X>^6tOG}-VROzgyWzN;K z+_{FTob^=gyp96SgH+>;P_6R>t#E#fRyzA>mGc3*()lA=?R=50a{i0zTuf_Ri)pPZ zK=2Pz^UisA!x zyaW`6iVE1Jh4K(}o1mg7_(a7Az7R!3MMUcVd`Z@{w1xhD>AC0o&UfD5Ip=%qwfi3e zaI9)qxc<^hH?4g~eXkX}$WDL7>m&8CzWS#6n427Q5|-zMzGKIO@tsPcN!bC0`4I2+LCnHz`8qU+IhZS7 zhbj0Qykl|r)Hf*+)f*43}A(bH^{EjO4^e($di*<7|p`0g`O54q~Z$UhSw9m z{%k=MS**fpk#-D?Z+0&-u|~o4+&onf$BBRySgUa4lo6aDMY}E{3Q1l%8D=CM<)$yu zjy*q!ldw*9Po{smPDZ!{u|B_as=^!^yS_K$CbFJ=w&e{3u_15WX$p&`PYDBW;f1tf zF+0PIT*;j5Z4lgahHYqgpT|3?y!09+c;pjJc$iSJ@HcxoEo1_EIl7#HU z*%Qh{*CiRxP8!%m&)I3->)L~ApG_@2>S|j_YOonwD$#$1b9u-6EGLmo+h@`bRzFjw zda8su4^feJJ}bo(3=M2!(hbT&f)$~5s#Ic-FGNoO7vOCSW1I!pqZPgRFvgfX3}aiu z%48^FLelC*s$io}Zdd=*PMhj78*r#hX;teQuvV{W?aC&DxJWG8jzsY~7OIGW)I^VJ z^$iTt{e6F~6mQ#$4JaHwWm*?Ykyx8XMuP0oT6-6D$ON$?Z|zQMHD1Kq+(d%uPVF)V znDUi&a?rb^gC`h^q9-(^tkDtgz&itYJKjao1Xn~noi?vw`PRubH>D?O-j2SH&ikjH`3}2l6wqlUA$Ol>P*}$HK<2w)-4L5X*n6Vjh>;%AU-GL zpT&Re3`0Jfbt9cODKErVdvK>@!snT4rO6n?7p0YK$6agyp1Z!Qt-ZZiKff#`%*9ve zKaLYl-z6K|ovDOt#oG$Aio%*HZrPhDwfEp&(dMg6=xplk&R~bk3DYI?K{I%8FLH8l zm}PZ5U}Vt3A>*`NF?%q7=kCk*pL{7E&D($R0N0u``tq50h)CLI!QR1YQ$Ky%DPE=^ zzJ^DH%h&0RqE@G7`}*v(9p7YIy7hgNQ7i7Xrv|fy%2eFmUu>HNgGxvYd~1rZ>7Mjh z0FUC^3gufiZw#+B@m+<+al#TF({{D*1#kf0my&kySYD;V{tp7!had97kW0LSLu7vt zPl?O+;YSo3OSl=X{6yx8efVkd#%eJo9{>4-jm-mTcV~VS`~{uT=4KP|x|HkH^-1Nb zky-jZe^UD7bA#!ZgWZ}GbTeuHNx%@W0;G2<-p z2f2BFR8Y+({!Dk!Nf|d4p^|@*zGr`Xh4vK0U&TGY#NVizn`usQ$}#bGjt!D>X_xwY ztf5D}sbPka|AChR?1TR-*8F@KlN&+z{aeAerR!ivEZO79|KOEMyo~=+wC8rXJK1~q zq8JxlN?#_&<_(m`}UVE04Vo5)=)QYwNE8S&ZoV9;bF=PfjXnPr5~^sRiLD1XZn?FO&;-(O$Q0sF1k8a=eYw zFF5hF2i2i!aX>9n9Ian^0 zvn*w*qu4z9^sd5*QzXpRX_I&&V@hsN%gI|c@|KLBX-{!8ogMV-`1oa2O(i2#`&lI$ z&7$4f3Bw1kGRuOYRmxTx;P^hj&dE@pI=(EOcpck`-fK411_r8)&uuEvdW8?Ra!!V{8Rc{5$)gP*3>F|CY#Q>prXinq0DPpc!6AH> zZzR^p^A&_k8l&5`h069~{))X=*t8dm!h5keRK6EWhH=C_kiU7T$C3GS=5op;cmK7G zqgWR0XdJ@A9F~t_MYOSJ7)=^onZvQwt^Ak6@xwTA2#az!WjBA;tjM8lH=227K7Wg% zIcyw3NA%1goD=QbkBUA1IVRTR6b_Z;kPVgRu zU`P}jp&5Jd+wR)Rid*r$kZ}NyHEF77#L(;vac~X~ig$k>E^_=v#2nR9LuM!tE`%bS zr(9V=$vDsA4kj_eikw##vXKv!zx3v@NiSK zXpzxV{R}M{!S8eUQ}uHP%_{DjJ=M=^i(fdnr6NXIt65v=dt0=%@@92Ht$F=x-Nh8( zZ?R@}cS(ODs4CfxM#?0>)h~|VU-#nG9Ftf1a;joCV~3}-&E?@5WzsO!IjREDiU)CV zG#V=JiTZ0)u&b;_&F(61t;nf)wG};G!|ITnTFA7?sU^FS5l3{28zM%COZC-{_t0lg zgbX@jR4paluv$iU{+I;&(GaSrQAbD2vIk*ABb9&tkkLhVSLW0T2J`98J($biB4M;7sqLVLmW{BejNuid<>6k_%jYf z0%d=M5%@0+SLG=utRu`+QG`w0}qv5sc z1`TgiBN{%Sp3v|K^`v?hP(M;X)%dgOIf1@weAoGBs}>CdD(t(_cZ`1^Q z^1ZBafr9_nU!ie<#QoL&1%hix96t3Hmfb5+_dlF#V3~o=S1@~wb6>zfxn4M3|9AEO z?FNS%1&pzZPfNfWjtavVV~wAd#=zyIdJS_8T%pwBG4_h8>G_dJWcp{~XK1y|nMi*= zu1SucS@ZJ^+&_jZrzLVpM1`InL)r8+2KH&HUy5NfP(7_RI(cS|#@IC9AR4F1Zl0hs zPbRBz7$vLw3Wqt+aPKIFsJMsx4i#46Hbb?%3O}jDnd3CvDo{ZJTe{IQzEM`XAui8v zyo@8p*rChVrwfD}DdoE}pGpTe6!mH5+k27t7-w)C=qBA(?q5hhUdCbI3etUyirv8$ z|0)7%J*w0O1XVv~sU&9m)?tosGv@j(z&u|J)xLhz_%6jE{w~z|FT{L*91Hvo7Wxwi z`3JQezaBgM{|8V@2MF_%Q9{HF006QWlkqzolT>;|e_B^->*2<`Rq)hx@kmkeMi2!> zP!POKx6^Gjdm!1?3$YL4TX-RY7e0UwCC*kwLlJ}3-Hvn6h6?p9RF6#Gg zLk71LH{D$~Xt^~vNTO6}nW-f9qNGWz8`2~#@n&0EFKAP6Ydev3cUw|hs<~5z*XmxAy6(dWgh1&s z>6n0ylqP}2#DsomWK)xWXJnd^@lRr#Nv#*Y^I?9mA_fH}Z)8{cTE?M&-ngM4D`J@a zzQ&J}i2Wu``;1Eb+<%XSmQ=c9=!~qDArsZpZeN$nEWa&N!}}^$*@3|P(qDuB@bZ;F zVQKlwfrE(>iYPl6!RRQ4P;pSgSYAyD3?A|;p~6j(e`bIyrnsu)3}?aNV4T+(?&eV7 z0Lm-Z*Dsh{eMYtRjOiz!j~4nCg-=jR2MDI8gO6$f008Hc@H-uoBYZD^3w&GWRX?94 z`N}uS!*=Y%c{I0n+{lt;=dswS(wFU|tz+fsJfgBf1?)j2Ma2b}nT%Mu+sIZL~IKh9fCG6ERuFKu5=>#OAG7o84C0Ka@)* zF<_7Akxl3t>0vW%7+EttjL|bj*2Y;F-`2LJZChl}IMet6KM6s9YQL4sCX74Hq#f`kHr03aTWQfK0tn|;;)qfQfU!?t%5ssxoiE# zjT;3G&wIh5L$}AIGfk_V4=eVhYx^BW&Gwe-Y+he%dl;sF?Au|(=}GD~0ACwyDU&4! zw+HA3TE|w<1O>{ERj3gTG0vH`V@rb_4bXaOR;h_@ngKUgCxwE7>f~t7F_Y~*Rx$|` z0@=1gAwg9}D&vgCAWcwBNe{V_$Dl?lMN|q?8R`*UnbruJ3l^qSx&F+PwxS&1=^w$Mrv*TzxU;Gxj zmG=XgOJ*vr&>eyl)85Iq3s5&TFQP8$5p?fe(mUE97G=$W99u%$&}?te1}($Z(w3to zthA$>X-!X$VwtOxY1nPr&T|=bj6uz@v>`J+s2S&f^n{Zf)izD78*TH`PWWfY%BFOf z^yc7PlpLGqE^}7}=q|cjr55THwBd(@l|p@jnu6~MQyF8sRf^FbL0;Ru-;hY^4bVQ? z&xSgHP+!ncMf=z=gQcbZuU0yUBM}1Z+uoMB775T{I>M^FAM29lfS-;sBA{=}JjUp@ zEC*_T>Y3e8tl!bIpo;aI6uL*H6O68wnKnu5Ddr1@S!W&?-^(ZIf_A+(R`_^5%U7L3 zjW*9N+&3Yp9y!Gv8ZB{RPcdN$+By$P-rI=)c>mp9k{4|VIBA3`kB9}Ft(e~Zo zG|=DsH7q@d4J%*nS3p#1~@T7d+O@kUU4DDxIbK5mmX&pzc6-1yjAf zEcQp}1FX@5C2{gL2S>8jS$%-H@}IfL>-I0-D)9iWHl$5_aJ zkC(1hW|HolnH=O?@{=k(!bqx~UeSw$B=gKq!M2Wdw{gzhGY8UB5&bjt5tV+LewGUW zR2$AnfIde1ImkbbA;wY~7he{lLp>FsrpAv2rOoDto@kD+ZS-`qc!Zs?or#an~aNv-#VXZiE*tAVY8*!YB9c?dCWE-<(u~42a zk=vQETsD%bPff6QtReWy#0lkp<^!?!4!PDEU_fa(8|Klq1TKl|mM?A9Y{QUF(M-o? zYo9RzKycu%piZ5}+JRi!F;fOAI3vUR6#BJUnSMsT`ix4?(eo%nT=1b`cn6eI0$eiYO&qsrQu&ZUg3bUT!rq%ZLL-Y>7g@gHXe3XSbC#b|#G! zq#`nZm&=v~kWUPRx$&sm%H%`aNF$3Nq3ht#?ArQH8z?jS8oIz1?zE+`GZ-VUroAyTZ}L>ehtN|tq(~?U|E80`k^=rO8yc3u}XhPf5IoD4y;U_ zM)iQZ{<%vze*vB>IiWi@G{i)(H|LaPlD`tPvfNEGXa8EI*V!)()1EC~P{iEdsPr2B zEvieII;Um@wFhJKo33=3nRyNOd4s;muKhcBWxfLy`g_3bEYdE24E~Rt)&7CL%|9RJ zT}WE0gd$T!GC-fBD~!;8DbJ#N%L3_N@e=5Q1PKJ? zf58X~KI#;DhwCqEI6(iy5%}NqePoXVU=yY(KNX-DY*Q>00(cz*Di4VY45I|bBiV2g zBMZe(+Hl$r9q5&R@v|6G_JLK?j{B}&7HpYSn2AcE!1Kb-?gtiqZ5h;gez6D`+fhcv zez6$E&~@ITidYJCGb|5fQ5M}0oTbgoZa`Fv8dWS4wX+iLf~9*|!WDHexu`Ea;fgX9 zu@dS#)}aHjvWvQtF&wx`tX4&XSTl25Oc6H#iAYVH>C*0hBMyW*Yyb2dBx&MCRjdi`xeXzJ9Ahx?xx1cr* zE*RS4HePc(oH;DdaB%OKTi}T<6nL2Ip7AzEg=#PmcL4aPwHfyA&}`0jN8!mk#a*h{ zDelGw)8@)Eo6TiV9R$QK5F%#!e8m5j5#c1{+~F*LVv?W2MtaVlfM!R;`W?oQo=ZBV z{=Qk;asFPhkL|dB=HF!gw}KSWkJMHwobXU{a(2%ME^5evf7dSd#vyT76$ix;(8d&O z`Yj}slHaC@PQ*c8Q}xqX-PX)$)3o`;F_qq;=b<a&fg1oZw`FGF?2%YnMlNbOt z$_Ye&)^C0RjcSTjX;gFEleM5<3~_}%Pkmn=_9Gnj;1*BHZt;uLfU*viPO9F%t2m*3Ls{tjXk;4fRU9WRE=by!22G2`KbzD)%+JO*#>Aa zS_QCJLQ6@A40;=|-ivm1D1LmLYOc`oc;7gG)rDT572y}Cq4fn?eM!Qpiq_Ctca!)M zwp5~B6b|L-#v^&!aFNsrYVRAP+rxR<67PGND#r@n4PBwmcx;@uUAxWG;jQzoeVW#W z>b#rdQD2_6Um!KyfREdcocD^c!W-ef(2ImPxImisDkbp`mQ z0wXbaBnt&XaCjv)?!)K^gq?x6J_4~%U~~-Y-T*M(!kz-wRgpnMMX&NaL+2~4FO&CD z&Bz3$_gtY&Jn9XPlU==xKJSnE8ocbX2jU%-Pf$&y!RM)~%+m+Q;BNYOU1i08lkE4` zBMsg>ozK%xVE-f7KTeN&I(&7$$hD`bEmG&(QcZ;iC+MT`C^kO^gD-0EF58%=Pac7I z3_X72ybp-@S}V(WGQKBIPhWsa;dq{&0otC8DeRT_@u=4m>i35GeXaeKk^Y)rZScA- zdM*wJ{raTTViFdpqg60D0l`gwvTecd)+vX5j8xydRIkt}g)$1|3bc|Wg`!JBp@#}= zURd09;?z30>uvHEAic6|GN&Nm2{jUTiw-VMLf|9p(!}gGb2~kH#0y%=_1;+1s&#i01u<{y)d?>tTGY~&PFJ2^npXa&r6|m_y zvGSScuv5spFDB3TsYao3vGQ$*tm1mI2#05jO!D*9;vXU*;G+kB{FM z2(MS;d-yP*B$B5;n4mwELH1`CXerzOFOQ5BzB)$7S|eBJHD398oIx~BUvKb@(>L<; zt*E!!I}2Km)6x>OzB5*T_;w^-#M7JjKUVlqUkE3?IoX=0f4am!lVCFySLv2UTQ1ub zq{+6Cnq?cL4%yyJx5;)V?UHSb_R97E9hdEKIthal=?DvMN63=uee1Eugg1&nxz9$sFObr}{;gdE0K2G05_#nV) z{u4i~#qYQAgE-66yTzrElPGa{t?*1uP2w;DBr3rjE_T2%cPi*r3$O6G$9oNJJnL)&cya?5b){}X$`LgK9i>Um)H81Xn z`l^G#-tN5U>F`!{`l~wC24AZLVE|m_Oo-mRh+U+6>(zRHe_i0=eP>fqJ#h`|x8IX+@--2aQhuWpMyQ^=e+czd>pB)Zx0{VF{gTr+=*QR9}M<^^TEU zY@=7`t$3|CJ}&N=3^ynZzQ|>9qE_6C>z7cEl;sbzsX{Pk;>aZ=+O2)OjqL`z)(Qg_ z1$BxQwPF~5pAmV*Q?(-LS~@f?tjTi8FOi?4?RC>{$E%%?L&&WQv+<%@f$v(H-e~~6-pIh#~L|>MDZn^&r z`j+f-%YD2tWuII0g$Hji^kvKaR#fcV=a%~k@tD+q(+$h-(UJm=Qe}8GF*l=d(nR&OQ{7OL_2E=Vm2~MJX9`-SZSXeEFD}Wr5B5U8nD2AgzO2JB1RsOKwrp| zQ9+&%9{^BG2MBjW_x58D003kklkqzolXHtTe}Te6DU?D%5Kvqd+tTd+0E=b=XuYWoSE;xzkUO- ziY11l!^7w0w`!dmd%|s~>#DJ%7FEM@e9PvM<++;UH3aE_umukVEjD?m8BJmAg|QQ= zf9pHk4n|^y zT)JB-YYlOrz8e5zNY=bKFvKIv77Wu~VCrVT8@AA22i*5XpjSQ96oG;S!{{zQ;JVFS zQ-50D6-K0>pCNmuJ|x0z@VYG&3^4TVf5(=H7}z#L|9#7~q6Z9#+;)D8p*NS`N+E@j zBow4mNMdLZeaO&??U@V{x$2p3Et31FNbXz>wKriT90e1^croRfXd#xTKco1FD8Zdd z3Rf^Sh)GN{jCTl7FvFnuQn1|==8#Qd7T2g`ezF~grSr9HG}8hQOQ?3e{H_P zpkIdkQ{+5UnfE5cN>_GsvuncT%b^Y_7i7vi)cD*+SLdm}YaI*<(qNIgxCMQd(>>{iBFSw8J6KV=ooCr>Y&{ zbUK#D6MxFu;BS6WYE8f;!W)xC6Dxygm5GV2(K>pIcrZE{1zv<}{@ez}p!1NGR^qkN z$lx%uu^(FzY4jhh$aA#*ohXt^=P(U5+7{Fq>@USy_*$6QzYUitixxB)G|!b$#RY?d z{>@K7Wq!5w?7th#8PxiNc^BHy=|Bs17}T%m3o6iq2HC0@oi=P!-zC>0t&uj4-k|&X z8>qk*)V={wO9u$HjWB8?0RRAMlkhtolZKB&e-2P4PC`p5lv2gUpcq0zq!*0Pi!D;Y z2B-v!sTZ6~PLhGi%y?!7%2K=92Y*ESppSj+Q_{*>_Q5yb{SE#GUyS<2}pIOwBWFD^<0NoaBO= ze_V4pDJzw?!{iKcTa?pfp%qP@-V~bS zaFM<%YAoUf2mpJ^kQL+>z;y6hBIaE<+fapSDT&;7vkB# z+OX3SW@=>T=zE5lp4XfyhDfVkfy&TnxI1aJ$4Bl*5J8uUFitY`HGQXT)1=5$o2#Ik zA;hbWw?&8yr{jl%M9_mXDo&%9p|`1O=BeN;g}rK6hIc&(doO}>7*NrV^9=p1e;LkM zj_>6>!L_P_H)OO!1qQBfsu;uth7Qx#iVWwPMlJqe5_&yvkb4f ze!<;Mp)WpnY!08`j^c}0f;a2U(H!(9PtC~579LsrF zLUeP0&xd)~lsq;NIVi^14|c^ac}6=}p5!k~Q2%v}7lsErGUTnvA$f5&XasePPJ_sg z6hwO2?$YipnbOVRboPAd-8-(a?jjcxrEaP=73lUf=x_LpwkWxrOtgUq2iuJf27CDI z$Zo!&;JFpGF;C}KyUq56H9w}UsDoGCm~uO-bmp~{q}<>S6#vc^sy<<)K_NX?&~$+# zSpV|%XBcFILUM~0EhMqI6MYf0HD`iqU8Mrn0^)^REIRsgKJYE%DE&TzM-V{|BR5(o-FtXIUIdAvAp_2i%4*$iNCzjVTipiOx8IZ6E?+t$V#^sGm;;^uj zWpcCr=t@o85&cLcr`~n_G8R`gHLdoW15WR=V+IriwkY!f;}gQ}^mt6qnyH>1LFMr-$to}%T!%YB^nUi- zk0IWBMZdM27T5(8(V^vBtn5beZtk-T#2}wu zwXtVIXPL+5JVO?DGbgg&?X3UmF$bNGGNs6smHpPp;+AyU>&)@kzIGhdER2 zUn9LuaFny*!&Q#r0h*&$wdn@Z|^T$|5vZPCZGYKVMbd-*A-OTE2$aT zvElV9QO9#Wb-!~c>Ro$^i1^IP>tk_F$`b2aCqAlbefKEalH)n0E_>0zY@?%Kd8!Vb z)eh6~UhMYI;pL5&H(fQ*-vU?Ogn$gF!R_& zG*`?yg&5hECwPSDBgezFU0OYchl>aZ_O#1As$3DLs?6DVQ{+Bgf)qXOt?i!a-QsZ%Qyak$I+*LVKW3LN868lw&Abn1?M8woaWLO$jR z$1o+N+loH#L^Er>=GCPgsT1^R0=X}s#h!PvnZFcfc zPt^$bFspHAPSw5*d+fTlT0DcKG-OCmeGp&5%#xVc(qXh_!{LV4Fy&pGr2278^s7Hd zG0OA~n))|Zn3$VO=t^_#qRjpIIm&kCB^Mks z5%5*{`o~*6j@yuj;WK9LU!7(f7@qD&a9f}U_ezFf?*k~2TwalyDA{Me7+?!XX85W8~2Gkn7tkMi(Y#9wua=HjEN6b!4F;~fq2 zN+=n_OYt$sP&~H8bAIx}a8=fAeC)y3XSNNE)@wvGrmw_A2?_6(5dH4Ay$$3eKnpls zQ9p2NjNR;IS2XA*j@uavp?DKu^d$E794+V23Ft`Vk@33@+vnrt10H+~EM|8CvEjZ0 zsbjngycb@L8_MfVT`Xnnuk>x^`U%`CUB!Uzxi*3x3TY=eP}a67_st`3LM%MRB2@IF z--lqT%Cn#eoc*(yV-@o_=s>T9rI^|8Sn#Mxp@^^<0&VtemQx&)8jQ7o21p%?cZhY= z2$L+PviXU>b&m1-87KE7;kWh`u#fdL$UD*xi>MUO^=5ux-13*`xP76LtA@2zUB^ms zSP{pq)Oc4=?5KT7jGFsk9qwwUux!x@N8#C3{jzMRcrJ}`@d6sRivaGYm`CCXmL6|fuFcBWxDev6Dq94<*BsW}T zUkMa>wwY(#q>&x))jD6u=f}0nXH*SBq(iHCV2gJ)&{Y3)R1aG6HdSi6xrrL+dp_=o zTnPHdBA;++kh;9JI$dVv-Z^nm2UM>VT`TKi3#7P}DGpQ3hHyot_%Ga5v(0Q0Xw^BQ zrB9sE+=kH-nx;d_Bwn5&zP(`iND^1RUcgx6*Ieq^p5Ygbprub6b$UW5=&;iph_RJX zv<=!^MO&MGLRP?LAeXM#O}yx{*)e_8fczM2xhtfJUEEenScK&7Hm`>;^Z!hT>)+_| zotD^E!|*`-9xk8Mw9oTqyVn;=CubXG)F|FKXuGWzYg<+^{7hV|$;^Yn&0ElR`rJL} z@vE~it;yE0dG*)jM%UBw6e>Tu^*xu9&HUkCUX1ntJ{WCAJasOvA3ufatZs5*DI-p- zxNA`D)n(2siM^MSVtP0)tHIk@)Xyyz(ho#&Rr)o@W(78Dad7&wf4-@MOtE?N z?#5=EP9XfsK%DG|mFk0QoA#XR{LtbZ@XFbt-?!L<9(NTEGPBG}T`ZcX-L#^jM zq2;S+?;XXN4s!~p7D#pnf~~zMgH`2|dUL}P=UuB`{<@O=I98hMSI++L66r4FY2r<< z%0Bf0xHUihoNG6;)RcCV(`@{S-4gawQv?%S?=6Wh<;jH!587HZv1BDpGAo@Ha#KkB zjix+Lg`FvSr!`ja1%F;iIbo1XspRa=d+)|5G{2lHURUXkxe35IPELIvv7a zc|*l*t#Q=As}vi>RC7aRxdsm%)g@4h`#6*)7T$V$Dlxt=ej+c%c-+ArC9|ex{2@7| zu4c+$vYSIihTmODqeJ{JH$%> z-CFQ!lh+{2vP;+tewX9brpOL9Ne7)_0gn)ROwklwW4VTNQqE#prrjg3HjNst&{(RS| zGk*}mpX;P2#HZfT)Hx8EbQ~u0Zdek{Znhq#>yfJt;^%*@YT~1O1FKn5tErRueVR-L@n%;Fhr|EP^GW)F`mDjn z=f0ShV<4J&+CF9AoFQJ zAblnPmu*LPX`s(O6$An`00LxqfK$b-aNX%sw zpzWo1N+A9djuA~ekCB0ytR#>%SDb(3=lj+RM5vxPT~s84Fn~p_xj;(RQ+jKn06+}e zhLfE?!%Y+s1X%=LHV4X#WPK~b_KXgOb1;2;_b{P*DdDF8YJI?#iBmj46lRX{+Svix3yprmvW z;urmpc*u~|x~H*62?NkVap+;Z!rxsq(F6gka7~idft^3G?K)&yFSPe4J|I;~fiw&U zF7QP16d5_83uqVFK}lZZ#3mgj0&-*k3;_aa^iGlr9(pSOT~O3;kKzR6iw&WNzOo>Y z5}DTG=|2=5;9)FG()?c!GGQ{>&g>5j2KY+^srL=5v`V-r2#k#CzWIj&1J}a%NtF+GV?iJxGCC#V z4^0cKl?p-+x6(i$K{C=TX`hV4l76?)gN-9%3&=0^U0|OSNDv@ZKU^AuK(b_-5vluR tb|UG5rrMiG19Iiulsp;xC-#?+`!a`jC=f`JOy*MdA6k~?a^c>+=|A-;lequ@ delta 35551 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2a84e18..2e11132 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-9.0.0-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a9..adff685 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac..c4bdd3a 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 16db0fe788a254465992dff91dc26f6e03a2f211 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 08:03:42 +0000 Subject: [PATCH 05/37] Create build-logic, main rainbow and client module --- build-logic/build.gradle.kts | 18 ++++++++++++++++++ build-logic/settings.gradle.kts | 7 +++++++ build-logic/src/main/kotlin/libs.kt | 6 ++++++ .../rainbow.base-conventions.gradle.kts | 2 +- .../rainbow.publish-conventions.gradle.kts | 0 client/build.gradle.kts | 3 +++ gradle/libs.versions.toml | 1 + rainbow/build.gradle.kts | 3 +++ .../java/org/geysermc/rainbow/CodecUtil.java | 0 .../java/org/geysermc/rainbow/KeyUtil.java | 0 .../org/geysermc/rainbow/PackConstants.java | 0 .../org/geysermc/rainbow/PackManager.java | 0 .../java/org/geysermc/rainbow/Rainbow.java | 0 .../accessor/ResolvedModelAccessor.java | 0 .../CommandSuggestionsArgumentType.java | 0 .../rainbow/command/PackGeneratorCommand.java | 0 .../CachingStreamResourceContainer.java | 0 .../creative/ImmutableResourceContainer.java | 0 .../creative/ImmutableResourcePack.java | 0 .../MinecraftCreativeResourcePack.java | 0 .../creative/StreamResourceContainer.java | 0 .../rainbow/mapper/CustomItemProvider.java | 0 .../rainbow/mapper/InventoryMapper.java | 0 .../mapper/ItemSuggestionProvider.java | 0 .../geysermc/rainbow/mapper/PackMapper.java | 0 .../rainbow/mapping/BedrockItemConsumer.java | 0 .../rainbow/mapping/BedrockItemMapper.java | 0 .../geysermc/rainbow/mapping/PackContext.java | 0 .../mapping/animation/AnimationMapper.java | 0 .../animation/BedrockAnimationContext.java | 0 .../mapping/attachable/AttachableMapper.java | 0 .../geometry/BedrockGeometryContext.java | 0 .../mapping/geometry/GeometryMapper.java | 0 .../mapping/geometry/GeometryRenderer.java | 0 .../mapping/geyser/GeyserBaseDefinition.java | 0 .../mapping/geyser/GeyserGroupDefinition.java | 0 .../mapping/geyser/GeyserItemDefinition.java | 0 .../geyser/GeyserLegacyDefinition.java | 0 .../rainbow/mapping/geyser/GeyserMapping.java | 0 .../mapping/geyser/GeyserMappings.java | 0 .../geyser/GeyserSingleDefinition.java | 0 .../predicate/GeyserConditionPredicate.java | 0 .../predicate/GeyserMatchPredicate.java | 0 .../geyser/predicate/GeyserPredicate.java | 0 .../GeyserRangeDispatchPredicate.java | 0 .../mixin/EntityRenderDispatcherAccessor.java | 0 .../mixin/GuiItemRenderStateMixin.java | 0 .../mixin/LateBoundIdMapperAccessor.java | 0 .../rainbow/mixin/ModelManagerMixin.java | 0 .../PictureInPictureRendererAccessor.java | 0 .../mixin/PictureInPictureRendererMixin.java | 0 .../mixin/RangeSelectItemModelAccessor.java | 0 .../rainbow/mixin/SplashRendererAccessor.java | 0 .../rainbow/mixin/TextureSlotsAccessor.java | 0 .../geysermc/rainbow/pack/BedrockItem.java | 0 .../geysermc/rainbow/pack/BedrockPack.java | 0 .../rainbow/pack/BedrockTextureAtlas.java | 0 .../rainbow/pack/BedrockTextures.java | 0 .../geysermc/rainbow/pack/BedrockVersion.java | 0 .../geysermc/rainbow/pack/PackManifest.java | 0 .../pack/animation/BedrockAnimation.java | 0 .../pack/attachable/BedrockAttachable.java | 0 .../pack/attachable/VanillaGeometries.java | 0 .../pack/attachable/VanillaMaterials.java | 0 .../attachable/VanillaRenderControllers.java | 0 .../pack/attachable/VanillaTextures.java | 0 .../pack/geometry/BedrockGeometry.java | 0 .../render/PictureInPictureCopyRenderer.java | 0 .../main/resources/assets/rainbow/icon.png | Bin .../resources/assets/rainbow/lang/en_us.json | 0 .../src}/main/resources/fabric.mod.json | 0 .../src}/main/resources/rainbow.mixins.json | 0 settings.gradle.kts | 6 +++++- 73 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 build-logic/build.gradle.kts create mode 100644 build-logic/settings.gradle.kts create mode 100644 build-logic/src/main/kotlin/libs.kt rename build.gradle.kts => build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts (98%) create mode 100644 build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts create mode 100644 client/build.gradle.kts create mode 100644 rainbow/build.gradle.kts rename {src => rainbow/src}/main/java/org/geysermc/rainbow/CodecUtil.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/KeyUtil.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/PackConstants.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/PackManager.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/Rainbow.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapper/PackMapper.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/BedrockItemConsumer.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/PackContext.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/animation/BedrockAnimationContext.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/LateBoundIdMapperAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/RangeSelectItemModelAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/BedrockItem.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/BedrockPack.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/BedrockTextureAtlas.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/BedrockTextures.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/BedrockVersion.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/PackManifest.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/attachable/VanillaGeometries.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/attachable/VanillaMaterials.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/attachable/VanillaRenderControllers.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/attachable/VanillaTextures.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java (100%) rename {src => rainbow/src}/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java (100%) rename {src => rainbow/src}/main/resources/assets/rainbow/icon.png (100%) rename {src => rainbow/src}/main/resources/assets/rainbow/lang/en_us.json (100%) rename {src => rainbow/src}/main/resources/fabric.mod.json (100%) rename {src => rainbow/src}/main/resources/rainbow.mixins.json (100%) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts new file mode 100644 index 0000000..3c6afdc --- /dev/null +++ b/build-logic/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + `kotlin-dsl` +} + +repositories { + maven { + name = "Fabric" + url = uri("https://maven.fabricmc.net/") + } + gradlePluginPortal() +} + +dependencies { + // Very ugly... https://github.com/gradle/gradle/issues/15383 + implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) + + implementation(libs.fabric.loom) +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts new file mode 100644 index 0000000..b5a0fab --- /dev/null +++ b/build-logic/settings.gradle.kts @@ -0,0 +1,7 @@ +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/build-logic/src/main/kotlin/libs.kt b/build-logic/src/main/kotlin/libs.kt new file mode 100644 index 0000000..1214d49 --- /dev/null +++ b/build-logic/src/main/kotlin/libs.kt @@ -0,0 +1,6 @@ +import org.gradle.accessors.dm.LibrariesForLibs +import org.gradle.api.Project +import org.gradle.kotlin.dsl.getByType + +val Project.libs: LibrariesForLibs + get() = rootProject.extensions.getByType() diff --git a/build.gradle.kts b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts similarity index 98% rename from build.gradle.kts rename to build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts index 53d18b5..2eb4e5a 100644 --- a/build.gradle.kts +++ b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts @@ -1,5 +1,5 @@ plugins { - alias(libs.plugins.fabric.loom) + id("fabric-loom") } version = properties["mod_version"]!! as String diff --git a/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts new file mode 100644 index 0000000..e69de29 diff --git a/client/build.gradle.kts b/client/build.gradle.kts new file mode 100644 index 0000000..def7c69 --- /dev/null +++ b/client/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("rainbow.base-conventions") +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2c56843..cfbb800 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ packconverter = "3.4.1-20251013.173215-13" minecraft = {group = "com.mojang", name = "minecraft", version.ref = "minecraft"} parchment = {group = "org.parchmentmc.data", name = "parchment-1.21.10", version.ref = "parchment"} +fabric-loom = {group = "net.fabricmc", name = "fabric-loom", version.ref = "fabric-loom"} fabric-loader = {group = "net.fabricmc", name = "fabric-loader", version.ref = "fabric-loader"} fabric-api = {group = "net.fabricmc.fabric-api", name = "fabric-api", version.ref = "fabric-api"} diff --git a/rainbow/build.gradle.kts b/rainbow/build.gradle.kts new file mode 100644 index 0000000..def7c69 --- /dev/null +++ b/rainbow/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("rainbow.base-conventions") +} diff --git a/src/main/java/org/geysermc/rainbow/CodecUtil.java b/rainbow/src/main/java/org/geysermc/rainbow/CodecUtil.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/CodecUtil.java rename to rainbow/src/main/java/org/geysermc/rainbow/CodecUtil.java diff --git a/src/main/java/org/geysermc/rainbow/KeyUtil.java b/rainbow/src/main/java/org/geysermc/rainbow/KeyUtil.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/KeyUtil.java rename to rainbow/src/main/java/org/geysermc/rainbow/KeyUtil.java diff --git a/src/main/java/org/geysermc/rainbow/PackConstants.java b/rainbow/src/main/java/org/geysermc/rainbow/PackConstants.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/PackConstants.java rename to rainbow/src/main/java/org/geysermc/rainbow/PackConstants.java diff --git a/src/main/java/org/geysermc/rainbow/PackManager.java b/rainbow/src/main/java/org/geysermc/rainbow/PackManager.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/PackManager.java rename to rainbow/src/main/java/org/geysermc/rainbow/PackManager.java diff --git a/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/Rainbow.java rename to rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java diff --git a/src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java b/rainbow/src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java rename to rainbow/src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java diff --git a/src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java b/rainbow/src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java rename to rainbow/src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java diff --git a/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java rename to rainbow/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java diff --git a/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java rename to rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java diff --git a/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java rename to rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java diff --git a/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java rename to rainbow/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java diff --git a/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java rename to rainbow/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java diff --git a/src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java b/rainbow/src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java diff --git a/src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java diff --git a/src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java b/rainbow/src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java diff --git a/src/main/java/org/geysermc/rainbow/mapper/PackMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapper/PackMapper.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapper/PackMapper.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapper/PackMapper.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/BedrockItemConsumer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemConsumer.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/BedrockItemConsumer.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemConsumer.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/PackContext.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/animation/BedrockAnimationContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/BedrockAnimationContext.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/animation/BedrockAnimationContext.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/BedrockAnimationContext.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/LateBoundIdMapperAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/LateBoundIdMapperAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/LateBoundIdMapperAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/LateBoundIdMapperAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/RangeSelectItemModelAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/RangeSelectItemModelAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/RangeSelectItemModelAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/RangeSelectItemModelAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java rename to rainbow/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java diff --git a/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/BedrockItem.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java diff --git a/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/BedrockPack.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java diff --git a/src/main/java/org/geysermc/rainbow/pack/BedrockTextureAtlas.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextureAtlas.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/BedrockTextureAtlas.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextureAtlas.java diff --git a/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java diff --git a/src/main/java/org/geysermc/rainbow/pack/BedrockVersion.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockVersion.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/BedrockVersion.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockVersion.java diff --git a/src/main/java/org/geysermc/rainbow/pack/PackManifest.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/PackManifest.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/PackManifest.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/PackManifest.java diff --git a/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java diff --git a/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java diff --git a/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaGeometries.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaGeometries.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/attachable/VanillaGeometries.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaGeometries.java diff --git a/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaMaterials.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaMaterials.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/attachable/VanillaMaterials.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaMaterials.java diff --git a/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaRenderControllers.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaRenderControllers.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/attachable/VanillaRenderControllers.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaRenderControllers.java diff --git a/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaTextures.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/attachable/VanillaTextures.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/VanillaTextures.java diff --git a/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java rename to rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java diff --git a/src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java similarity index 100% rename from src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java rename to rainbow/src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java diff --git a/src/main/resources/assets/rainbow/icon.png b/rainbow/src/main/resources/assets/rainbow/icon.png similarity index 100% rename from src/main/resources/assets/rainbow/icon.png rename to rainbow/src/main/resources/assets/rainbow/icon.png diff --git a/src/main/resources/assets/rainbow/lang/en_us.json b/rainbow/src/main/resources/assets/rainbow/lang/en_us.json similarity index 100% rename from src/main/resources/assets/rainbow/lang/en_us.json rename to rainbow/src/main/resources/assets/rainbow/lang/en_us.json diff --git a/src/main/resources/fabric.mod.json b/rainbow/src/main/resources/fabric.mod.json similarity index 100% rename from src/main/resources/fabric.mod.json rename to rainbow/src/main/resources/fabric.mod.json diff --git a/src/main/resources/rainbow.mixins.json b/rainbow/src/main/resources/rainbow.mixins.json similarity index 100% rename from src/main/resources/rainbow.mixins.json rename to rainbow/src/main/resources/rainbow.mixins.json diff --git a/settings.gradle.kts b/settings.gradle.kts index 22a0042..bc119e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -6,6 +6,10 @@ pluginManagement { } gradlePluginPortal() } + includeBuild("build-logic") } -rootProject.name = "Rainbow" +include(":rainbow") +include(":client") + +rootProject.name = "rainbow-parent" From 73bd8222be658814ea4ce74468e35cc7d82425cf Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 08:06:36 +0000 Subject: [PATCH 06/37] Remove PackConverter for now --- .../rainbow.base-conventions.gradle.kts | 7 - .../java/org/geysermc/rainbow/KeyUtil.java | 16 -- .../java/org/geysermc/rainbow/Rainbow.java | 75 ------ .../CachingStreamResourceContainer.java | 42 --- .../creative/ImmutableResourceContainer.java | 140 ---------- .../creative/ImmutableResourcePack.java | 37 --- .../MinecraftCreativeResourcePack.java | 71 ----- .../creative/StreamResourceContainer.java | 254 ------------------ 8 files changed, 642 deletions(-) delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/KeyUtil.java delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java diff --git a/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts index 2eb4e5a..ee2f1cb 100644 --- a/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts @@ -34,13 +34,6 @@ dependencies { modImplementation(libs.fabric.loader) modImplementation(libs.fabric.api) - - implementation(libs.creative.api) - implementation(libs.creative.serializer.minecraft) - implementation(libs.packconverter) - include(libs.creative.api) - include(libs.creative.serializer.minecraft) - include(libs.packconverter) } tasks { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/KeyUtil.java b/rainbow/src/main/java/org/geysermc/rainbow/KeyUtil.java deleted file mode 100644 index 5811cfc..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/KeyUtil.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.geysermc.rainbow; - -import net.kyori.adventure.key.Key; -import net.minecraft.resources.ResourceLocation; - -@SuppressWarnings("PatternValidation") -public interface KeyUtil { - - static Key resourceLocationToKey(ResourceLocation location) { - return Key.key(location.getNamespace(), location.getPath()); - } - - static ResourceLocation keyToResourceLocation(Key key) { - return ResourceLocation.fromNamespaceAndPath(key.namespace(), key.value()); - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java index d14f252..f0f4eba 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -2,39 +2,16 @@ package org.geysermc.rainbow; import com.mojang.logging.LogUtils; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.Minecraft; import net.minecraft.commands.synchronization.SingletonArgumentInfo; -import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import org.geysermc.pack.bedrock.resource.BedrockResourcePack; -import org.geysermc.pack.converter.PackConversionContext; -import org.geysermc.pack.converter.PackConverter; -import org.geysermc.pack.converter.converter.ActionListener; -import org.geysermc.pack.converter.converter.Converter; -import org.geysermc.pack.converter.converter.base.PackManifestConverter; -import org.geysermc.pack.converter.converter.lang.LangConverter; -import org.geysermc.pack.converter.converter.misc.SplashTextConverter; -import org.geysermc.pack.converter.converter.model.ModelConverter; -import org.geysermc.pack.converter.data.ConversionData; -import org.geysermc.pack.converter.util.DefaultLogListener; -import org.geysermc.pack.converter.util.LogListener; import org.geysermc.rainbow.command.CommandSuggestionsArgumentType; import org.geysermc.rainbow.command.PackGeneratorCommand; -import org.geysermc.rainbow.creative.MinecraftCreativeResourcePack; import org.geysermc.rainbow.mapper.PackMapper; import org.slf4j.Logger; -import javax.imageio.ImageIO; -import java.io.IOException; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - public class Rainbow implements ClientModInitializer { public static final String MOD_ID = "rainbow"; @@ -52,58 +29,6 @@ public class Rainbow implements ClientModInitializer { ArgumentTypeRegistry.registerArgumentType(getModdedLocation("command_suggestions"), CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); - - ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> { - dispatcher.register(ClientCommandManager.literal("debugtest") - .executes(context -> { - - PackConverter packConverter = new PackConverter() - .packName("RAINBOW-TEST") - .output(FabricLoader.getInstance().getGameDir().resolve("pack-convert-out")); - Path tmpDir = FabricLoader.getInstance().getGameDir().resolve("pack-convert-temp"); - - ImageIO.scanForPlugins(); - MinecraftCreativeResourcePack resourcePack = new MinecraftCreativeResourcePack(Minecraft.getInstance().getResourceManager()); - BedrockResourcePack bedrockResourcePack = new BedrockResourcePack(tmpDir); - - LogListener logListener = new DefaultLogListener(); - final Converter.ConversionDataCreationContext conversionDataCreationContext = new Converter.ConversionDataCreationContext( - packConverter, logListener, null, tmpDir, resourcePack, resourcePack - ); - - - List> converters = new ArrayList<>(); - converters.add(new PackManifestConverter()); - converters.add(new LangConverter()); - converters.add(new ModelConverter()); - - int errors = 0; - for (Converter converter : converters) { - ConversionData data = converter.createConversionData(conversionDataCreationContext); - PackConversionContext conversionContext = new PackConversionContext<>(data, packConverter, resourcePack, bedrockResourcePack, logListener); - - List> actionListeners = List.of(); - try { - actionListeners.forEach(actionListener -> actionListener.preConvert((PackConversionContext) conversionContext)); - converter.convert(conversionContext); - actionListeners.forEach(actionListener -> actionListener.postConvert((PackConversionContext) conversionContext)); - } catch (Throwable t) { - logListener.error("Error converting pack!", t); - errors++; - } - } - - try { - bedrockResourcePack.export(); - } catch (IOException e) { - throw new RuntimeException(e); - } - context.getSource().sendFeedback(Component.literal("exporting, " + errors + " errors")); - - return 0; - }) - ); - }); } public static ResourceLocation getModdedLocation(String path) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java deleted file mode 100644 index 1beb8c5..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/creative/CachingStreamResourceContainer.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.geysermc.rainbow.creative; - -import it.unimi.dsi.fastutil.objects.Object2ObjectMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.key.Keyed; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import team.unnamed.creative.part.ResourcePackPart; -import team.unnamed.creative.serialize.minecraft.ResourceCategory; -import team.unnamed.creative.sound.SoundRegistry; -import team.unnamed.creative.texture.Texture; - -@SuppressWarnings("UnstableApiUsage") -public abstract class CachingStreamResourceContainer implements StreamResourceContainer { - private final Reference2ObjectMap, Object2ObjectMap> cache = new Reference2ObjectOpenHashMap<>(); - private final Object2ObjectMap soundRegistryCache = new Object2ObjectOpenHashMap<>(); - private final Object2ObjectMap textureCache = new Object2ObjectOpenHashMap<>(); - - @SuppressWarnings("unchecked") - private T cacheOrDeserialize(ResourceCategory deserializer, Key key) { - Object2ObjectMap deserializerCache = cache.computeIfAbsent(deserializer, cacheKey -> new Object2ObjectOpenHashMap<>()); - return (T) deserializerCache.computeIfAbsent(key, cacheKey -> StreamResourceContainer.super.deserialize(deserializer, key)); - } - - @Override - public @Nullable T deserialize(ResourceCategory deserializer, Key key) { - return cacheOrDeserialize(deserializer, key); - } - - @Override - public @Nullable SoundRegistry soundRegistry(@NotNull String namespace) { - return soundRegistryCache.computeIfAbsent(namespace, cacheNamespace -> StreamResourceContainer.super.soundRegistry(namespace)); - } - - @Override - public @Nullable Texture texture(@NotNull Key key) { - return textureCache.computeIfAbsent(key, cacheKey -> StreamResourceContainer.super.texture(key)); - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java deleted file mode 100644 index 4859eb4..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourceContainer.java +++ /dev/null @@ -1,140 +0,0 @@ -package org.geysermc.rainbow.creative; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import team.unnamed.creative.atlas.Atlas; -import team.unnamed.creative.base.Writable; -import team.unnamed.creative.blockstate.BlockState; -import team.unnamed.creative.equipment.Equipment; -import team.unnamed.creative.font.Font; -import team.unnamed.creative.item.Item; -import team.unnamed.creative.lang.Language; -import team.unnamed.creative.model.Model; -import team.unnamed.creative.overlay.ResourceContainer; -import team.unnamed.creative.resources.MergeStrategy; -import team.unnamed.creative.sound.Sound; -import team.unnamed.creative.sound.SoundRegistry; -import team.unnamed.creative.texture.Texture; - -@SuppressWarnings("NonExtendableApiUsage") -public interface ImmutableResourceContainer extends ResourceContainer { - - private static T thr() { - throw new UnsupportedOperationException("ResourceContainer is immutable"); - } - - @Override - default void atlas(@NotNull Atlas atlas) { - thr(); - } - - @Override - default boolean removeAtlas(@NotNull Key key) { - return thr(); - } - - @Override - default void blockState(@NotNull BlockState state) { - thr(); - } - - @Override - default boolean removeBlockState(@NotNull Key key) { - return thr(); - } - - @Override - default void equipment(@NotNull Equipment equipment) { - thr(); - } - - @Override - default boolean removeEquipment(@NotNull Key key) { - return thr(); - } - - @Override - default void font(@NotNull Font font) { - thr(); - } - - @Override - default boolean removeFont(@NotNull Key key) { - return thr(); - } - - @Override - default void item(@NotNull Item item) { - thr(); - } - - @Override - default boolean removeItem(@NotNull Key key) { - return thr(); - } - - @Override - default void language(@NotNull Language language) { - thr(); - } - - @Override - default boolean removeLanguage(@NotNull Key key) { - return thr(); - } - - @Override - default void model(@NotNull Model model) { - thr(); - } - - @Override - default boolean removeModel(@NotNull Key key) { - return thr(); - } - - @Override - default void soundRegistry(@NotNull SoundRegistry soundRegistry) { - thr(); - } - - @Override - default boolean removeSoundRegistry(@NotNull String namespace) { - return thr(); - } - - @Override - default void sound(@NotNull Sound sound) { - thr(); - } - - @Override - default boolean removeSound(@NotNull Key key) { - return thr(); - } - - @Override - default void texture(@NotNull Texture texture) { - thr(); - } - - @Override - default boolean removeTexture(@NotNull Key key) { - return thr(); - } - - @Override - default void unknownFile(@NotNull String path, @NotNull Writable data) { - thr(); - } - - @Override - default boolean removeUnknownFile(@NotNull String path) { - return thr(); - } - - @Override - default void merge(@NotNull ResourceContainer other, @NotNull MergeStrategy strategy) { - thr(); - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java deleted file mode 100644 index 37edc55..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/creative/ImmutableResourcePack.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.geysermc.rainbow.creative; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import team.unnamed.creative.ResourcePack; -import team.unnamed.creative.base.Writable; -import team.unnamed.creative.metadata.Metadata; -import team.unnamed.creative.metadata.sodium.SodiumMeta; -import team.unnamed.creative.overlay.Overlay; - -@SuppressWarnings("NonExtendableApiUsage") -public interface ImmutableResourcePack extends ResourcePack, ImmutableResourceContainer { - - private static void thr() { - throw new UnsupportedOperationException("ResourcePack is immutable"); - } - - @Override - default void icon(@Nullable Writable icon) { - thr(); - } - - @Override - default void metadata(@NotNull Metadata metadata) { - thr(); - } - - @Override - default void overlay(@NotNull Overlay overlay) { - thr(); - } - - @Override - default void sodiumMeta(@NotNull SodiumMeta sodiumMeta) { - thr(); - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java deleted file mode 100644 index 09e3314..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/creative/MinecraftCreativeResourcePack.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.geysermc.rainbow.creative; - -import net.kyori.adventure.key.Key; -import net.minecraft.server.packs.resources.Resource; -import net.minecraft.server.packs.resources.ResourceManager; -import org.geysermc.rainbow.KeyUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import team.unnamed.creative.base.Writable; -import team.unnamed.creative.metadata.Metadata; -import team.unnamed.creative.overlay.Overlay; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.regex.Pattern; - -public class MinecraftCreativeResourcePack extends CachingStreamResourceContainer implements ImmutableResourcePack { - // Matches a path in 3 groups: the first directory, the rest of the path, and the file extension (e.g. .json) - private static final Pattern PATH_SANITIZE_REGEX = Pattern.compile("(^\\w+/)(.*)(\\.\\w+$)"); - - private final ResourceManager resourceManager; - - public MinecraftCreativeResourcePack(ResourceManager resourceManager) { - this.resourceManager = resourceManager; - } - - @Override - public @Nullable InputStream open(Key key) throws IOException { - Optional resource = resourceManager.getResource(KeyUtil.keyToResourceLocation(key)); - if (resource.isPresent()) { - return resource.get().open(); - } - return null; - } - - @Override - public List assets(String category) { - return resourceManager.listResources(category, resource -> true).keySet().stream() - .map(location -> location.withPath(path -> PATH_SANITIZE_REGEX.matcher(path).replaceAll("$2"))) - .map(KeyUtil::resourceLocationToKey) - .toList(); - } - - @Override - public Collection namespaces() { - return resourceManager.getNamespaces(); - } - - @Override - public @Nullable Writable icon() { - return null; - } - - @Override - public @NotNull Metadata metadata() { - return Metadata.empty(); - } - - @Override - public @Nullable Overlay overlay(@NotNull String directory) { - return null; - } - - @Override - public @NotNull Collection overlays() { - return List.of(); - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java b/rainbow/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java deleted file mode 100644 index f260518..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/creative/StreamResourceContainer.java +++ /dev/null @@ -1,254 +0,0 @@ -package org.geysermc.rainbow.creative; - -import com.google.gson.JsonElement; -import net.kyori.adventure.key.Key; -import net.kyori.adventure.key.Keyed; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import team.unnamed.creative.atlas.Atlas; -import team.unnamed.creative.base.Writable; -import team.unnamed.creative.blockstate.BlockState; -import team.unnamed.creative.equipment.Equipment; -import team.unnamed.creative.font.Font; -import team.unnamed.creative.item.Item; -import team.unnamed.creative.lang.Language; -import team.unnamed.creative.metadata.Metadata; -import team.unnamed.creative.model.Model; -import team.unnamed.creative.part.ResourcePackPart; -import team.unnamed.creative.serialize.minecraft.GsonUtil; -import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackStructure; -import team.unnamed.creative.serialize.minecraft.ResourceCategory; -import team.unnamed.creative.serialize.minecraft.atlas.AtlasSerializer; -import team.unnamed.creative.serialize.minecraft.blockstate.BlockStateSerializer; -import team.unnamed.creative.serialize.minecraft.equipment.EquipmentCategory; -import team.unnamed.creative.serialize.minecraft.font.FontSerializer; -import team.unnamed.creative.serialize.minecraft.item.ItemSerializer; -import team.unnamed.creative.serialize.minecraft.language.LanguageSerializer; -import team.unnamed.creative.serialize.minecraft.metadata.MetadataSerializer; -import team.unnamed.creative.serialize.minecraft.model.ModelSerializer; -import team.unnamed.creative.serialize.minecraft.sound.SoundRegistrySerializer; -import team.unnamed.creative.serialize.minecraft.sound.SoundSerializer; -import team.unnamed.creative.sound.Sound; -import team.unnamed.creative.sound.SoundRegistry; -import team.unnamed.creative.texture.Texture; - -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; - -@SuppressWarnings({"UnstableApiUsage", "PatternValidation"}) -public interface StreamResourceContainer extends ImmutableResourceContainer { - - @Nullable - InputStream open(Key key) throws IOException; - - List assets(String category); - - Collection namespaces(); - - String METADATA_EXTENSION = MinecraftResourcePackStructure.METADATA_EXTENSION; - String TEXTURE_EXTENSION = MinecraftResourcePackStructure.TEXTURE_EXTENSION; - String TEXTURE_CATEGORY = MinecraftResourcePackStructure.TEXTURES_FOLDER; - String SOUNDS_FILE = MinecraftResourcePackStructure.SOUNDS_FILE; - - private static Key addExtensionAndCategory(Key key, String category, String extension) { - return Key.key(key.namespace(), category + "/" + key.value() + extension); - } - - private static JsonElement parseJson(InputStream input) throws IOException { - try (Reader reader = new InputStreamReader(input, StandardCharsets.UTF_8)) { - return GsonUtil.parseReader(reader); - } - } - - @Nullable - private static Writable writable(Key key, String category, String extension, ResourceOpener opener) throws IOException { - Key withExtension = addExtensionAndCategory(key, category, extension); - try (InputStream input = opener.open(withExtension)) { - if (input != null) { - return Writable.inputStream(() -> opener.open(withExtension)); - } - } - return null; - } - - private static List collect(ResourceCategory category, Function deserializer, - Function> assetLookup) { - return assetLookup.apply(category.folder(-1)).stream() - .map(deserializer) - .filter(Objects::nonNull) - .toList(); - } - - @Nullable - default T deserialize(ResourceCategory category, Key key) { - Key withExtensionAndCategory = addExtensionAndCategory(key, category.folder(-1), category.extension(-1)); - try (InputStream input = open(withExtensionAndCategory)) { - if (input != null) { - return category.deserializer().deserialize(input, key); - } - } catch (IOException exception) { - throw new RuntimeException(exception); - } - return null; - } - - @Override - default @Nullable Atlas atlas(@NotNull Key key) { - return deserialize(AtlasSerializer.CATEGORY, key); - } - - @Override - default @NotNull Collection atlases() { - return collect(AtlasSerializer.CATEGORY, this::atlas, this::assets); - } - - @Override - default @Nullable BlockState blockState(@NotNull Key key) { - return deserialize(BlockStateSerializer.CATEGORY, key); - } - - @Override - @NotNull - default Collection blockStates() { - return collect(BlockStateSerializer.CATEGORY, this::blockState, this::assets); - } - - @Override - default @Nullable Equipment equipment(@NotNull Key key) { - return deserialize(EquipmentCategory.INSTANCE, key); - } - - @Override - @NotNull - default Collection equipment() { - return collect(EquipmentCategory.INSTANCE, this::equipment, this::assets); - } - - @Override - default @Nullable Font font(@NotNull Key key) { - return deserialize(FontSerializer.CATEGORY, key); - } - - @Override - @NotNull - default Collection fonts() { - return collect(FontSerializer.CATEGORY, this::font, this::assets); - } - - @Override - default @Nullable Item item(@NotNull Key key) { - return deserialize(ItemSerializer.CATEGORY, key); - } - - @Override - @NotNull - default Collection items() { - return collect(ItemSerializer.CATEGORY, this::item, this::assets); - } - - @Override - default @Nullable Language language(@NotNull Key key) { - return deserialize(LanguageSerializer.CATEGORY, key); - } - - @Override - @NotNull - default Collection languages() { - return collect(LanguageSerializer.CATEGORY, this::language, this::assets); - } - - @Override - default @Nullable Model model(@NotNull Key key) { - return deserialize(ModelSerializer.CATEGORY, key); - } - - @Override - @NotNull - default Collection models() { - return collect(ModelSerializer.CATEGORY, this::model, this::assets); - } - - @Override - default @Nullable SoundRegistry soundRegistry(@NotNull String namespace) { - try (InputStream input = open(Key.key(namespace, SOUNDS_FILE))) { - if (input != null) { - return SoundRegistrySerializer.INSTANCE.readFromTree(parseJson(input), namespace); - } - } catch (IOException exception) { - throw new RuntimeException(exception); - } - return null; - } - - @Override - @NotNull - default Collection soundRegistries() { - return namespaces().stream() - .map(this::soundRegistry) - .filter(Objects::nonNull) - .toList(); - } - - @Override - default @Nullable Sound sound(@NotNull Key key) { - return deserialize(SoundSerializer.CATEGORY, key); - } - - @Override - @NotNull - default Collection sounds() { - return collect(SoundSerializer.CATEGORY, this::sound, this::assets); - } - - @Override - default @Nullable Texture texture(@NotNull Key key) { - try { - Writable texture = writable(key, TEXTURE_CATEGORY, TEXTURE_EXTENSION, this::open); - if (texture != null) { - Metadata metadata = Metadata.empty(); - try (InputStream metadataStream = open(addExtensionAndCategory(key, TEXTURE_CATEGORY, METADATA_EXTENSION))) { - if (metadataStream != null) { - metadata = MetadataSerializer.INSTANCE.readFromTree(parseJson(metadataStream)); - } - } - return Texture.texture(key, texture, metadata); - } - } catch (IOException exception) { - throw new RuntimeException(exception); - } - return null; - } - - @Override - @NotNull - default Collection textures() { - return assets(TEXTURE_CATEGORY).stream() - .map(this::texture) - .filter(Objects::nonNull) - .toList(); - } - - @Override - default @Nullable Writable unknownFile(@NotNull String path) { - throw new UnsupportedOperationException("Unsupported by StreamResourceContainer"); - } - - @Override - default @NotNull Map unknownFiles() { - return Map.of(); - } - - @FunctionalInterface - interface ResourceOpener { - - InputStream open(Key key) throws IOException; - } -} From 466427e9746029dfe8cd4273944a8e2fe4e8f57e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 08:33:38 +0000 Subject: [PATCH 07/37] Split the mod in half --- client/build.gradle.kts | 6 ++ .../client/MinecraftAssetResolver.java | 41 +++++++++ .../geysermc/rainbow/client}/PackManager.java | 5 +- .../rainbow/client/RainbowClient.java | 27 ++++++ .../accessor/ResolvedModelAccessor.java | 2 +- .../CommandSuggestionsArgumentType.java | 2 +- .../client}/command/PackGeneratorCommand.java | 8 +- .../client}/mapper/CustomItemProvider.java | 2 +- .../client}/mapper/InventoryMapper.java | 2 +- .../mapper/ItemSuggestionProvider.java | 2 +- .../rainbow/client}/mapper/PackMapper.java | 5 +- .../mixin/EntityRenderDispatcherAccessor.java | 2 +- .../mixin/GuiItemRenderStateMixin.java | 2 +- .../client}/mixin/ModelManagerMixin.java | 4 +- .../PictureInPictureRendererAccessor.java | 2 +- .../mixin/PictureInPictureRendererMixin.java | 4 +- .../client}/mixin/SplashRendererAccessor.java | 2 +- .../render/MinecraftGeometryRenderer.java | 81 ++++++++++++++++++ .../render/PictureInPictureCopyRenderer.java | 2 +- .../main/resources/assets/rainbow/icon.png | Bin .../resources/assets/rainbow/lang/en_us.json | 0 client/src/main/resources/fabric.mod.json | 38 ++++++++ .../main/resources/rainbow-client.mixins.json | 18 ++++ .../java/org/geysermc/rainbow/Rainbow.java | 23 +---- .../rainbow/mapping/AssetResolver.java | 25 ++++++ .../rainbow/mapping/BedrockItemMapper.java | 21 ++--- .../geysermc/rainbow/mapping/PackContext.java | 4 +- .../mapping/attachable/AttachableMapper.java | 10 +-- .../mapping/geometry/GeometryRenderer.java | 73 +--------------- .../geometry/NoopGeometryRenderer.java | 14 +++ .../geysermc/rainbow/pack/BedrockPack.java | 34 ++++---- .../src/main/resources/rainbow.mixins.json | 6 -- 32 files changed, 307 insertions(+), 160 deletions(-) create mode 100644 client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/PackManager.java (89%) create mode 100644 client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/accessor/ResolvedModelAccessor.java (93%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/command/CommandSuggestionsArgumentType.java (98%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/command/PackGeneratorCommand.java (97%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mapper/CustomItemProvider.java (88%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mapper/InventoryMapper.java (93%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mapper/ItemSuggestionProvider.java (97%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mapper/PackMapper.java (94%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mixin/EntityRenderDispatcherAccessor.java (89%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mixin/GuiItemRenderStateMixin.java (92%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mixin/ModelManagerMixin.java (96%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mixin/PictureInPictureRendererAccessor.java (88%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mixin/PictureInPictureRendererMixin.java (90%) rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/mixin/SplashRendererAccessor.java (85%) create mode 100644 client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java rename {rainbow/src/main/java/org/geysermc/rainbow => client/src/main/java/org/geysermc/rainbow/client}/render/PictureInPictureCopyRenderer.java (66%) rename {rainbow => client}/src/main/resources/assets/rainbow/icon.png (100%) rename {rainbow => client}/src/main/resources/assets/rainbow/lang/en_us.json (100%) create mode 100644 client/src/main/resources/fabric.mod.json create mode 100644 client/src/main/resources/rainbow-client.mixins.json create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java diff --git a/client/build.gradle.kts b/client/build.gradle.kts index def7c69..7ad5470 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -1,3 +1,9 @@ plugins { id("rainbow.base-conventions") } + +dependencies { + // Implement namedElements so IntelliJ can use it correctly, but include the remapped build + implementation(project(path = ":rainbow", configuration = "namedElements")) + include(project(":rainbow")) +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java new file mode 100644 index 0000000..1a1c392 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java @@ -0,0 +1,41 @@ +package org.geysermc.rainbow.client; + +import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.client.resources.model.EquipmentClientInfo; +import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.equipment.EquipmentAsset; +import org.geysermc.rainbow.mapping.AssetResolver; + +import java.io.InputStream; +import java.util.Optional; + +public class MinecraftAssetResolver implements AssetResolver { + + @Override + public Optional getResolvedModel(ResourceLocation location) { + return Optional.empty(); + } + + @Override + public Optional getClientItem(ResourceLocation location) { + return Optional.empty(); + } + + @Override + public EquipmentClientInfo getEquipmentInfo(ResourceKey key) { + return null; + } + + @Override + public InputStream getTexture(ResourceLocation location) { + return null; + } + + @Override + public HolderLookup.Provider registries() { + return null; + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java similarity index 89% rename from rainbow/src/main/java/org/geysermc/rainbow/PackManager.java rename to client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 8148d5d..458609d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow; +package org.geysermc.rainbow.client; import org.geysermc.rainbow.pack.BedrockPack; @@ -6,7 +6,6 @@ import java.io.IOException; import java.nio.file.Path; import java.util.Optional; import java.util.function.Consumer; -import java.util.function.Function; public final class PackManager { @@ -17,7 +16,7 @@ public final class PackManager { throw new IllegalStateException("Already started a pack (" + currentPack.get().name() + ")"); } - currentPack = Optional.of(new BedrockPack(name)); + currentPack = Optional.of(new BedrockPack(name, new MinecraftAssetResolver())); } public void run(Consumer consumer) { diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java new file mode 100644 index 0000000..1ef7cea --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java @@ -0,0 +1,27 @@ +package org.geysermc.rainbow.client; + +import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; +import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; +import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; +import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.client.command.CommandSuggestionsArgumentType; +import org.geysermc.rainbow.client.command.PackGeneratorCommand; +import org.geysermc.rainbow.client.mapper.PackMapper; + +public class RainbowClient implements ClientModInitializer { + + private final PackManager packManager = new PackManager(); + private final PackMapper packMapper = new PackMapper(packManager); + + // TODO export language overrides + @Override + public void onInitializeClient() { + ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper)); + ClientTickEvents.START_CLIENT_TICK.register(packMapper::tick); + + ArgumentTypeRegistry.registerArgumentType(Rainbow.getModdedLocation("command_suggestions"), + CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java b/client/src/main/java/org/geysermc/rainbow/client/accessor/ResolvedModelAccessor.java similarity index 93% rename from rainbow/src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java rename to client/src/main/java/org/geysermc/rainbow/client/accessor/ResolvedModelAccessor.java index c8f0aae..c3a2cd3 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/accessor/ResolvedModelAccessor.java +++ b/client/src/main/java/org/geysermc/rainbow/client/accessor/ResolvedModelAccessor.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.accessor; +package org.geysermc.rainbow.client.accessor; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.ResolvedModel; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java b/client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java similarity index 98% rename from rainbow/src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java rename to client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java index f1f812a..23add2a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/command/CommandSuggestionsArgumentType.java +++ b/client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.command; +package org.geysermc.rainbow.client.command; import com.mojang.brigadier.StringReader; import com.mojang.brigadier.arguments.ArgumentType; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java b/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java similarity index 97% rename from rainbow/src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java rename to client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java index 2743826..adcc51f 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/command/PackGeneratorCommand.java +++ b/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.command; +package org.geysermc.rainbow.client.command; import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; @@ -10,9 +10,9 @@ import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.player.Inventory; import net.minecraft.world.item.ItemStack; -import org.geysermc.rainbow.PackManager; -import org.geysermc.rainbow.mapper.InventoryMapper; -import org.geysermc.rainbow.mapper.PackMapper; +import org.geysermc.rainbow.client.PackManager; +import org.geysermc.rainbow.client.mapper.InventoryMapper; +import org.geysermc.rainbow.client.mapper.PackMapper; import org.geysermc.rainbow.pack.BedrockPack; import java.nio.file.Path; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java similarity index 88% rename from rainbow/src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java rename to client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java index 8a8c7ad..8708f5d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapper/CustomItemProvider.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapper; +package org.geysermc.rainbow.client.mapper; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java similarity index 93% rename from rainbow/src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java rename to client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java index 5cfde28..4bac740 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapper/InventoryMapper.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapper; +package org.geysermc.rainbow.client.mapper; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java similarity index 97% rename from rainbow/src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java rename to client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java index fea136a..33ef127 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapper/ItemSuggestionProvider.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapper; +package org.geysermc.rainbow.client.mapper; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapper/PackMapper.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java similarity index 94% rename from rainbow/src/main/java/org/geysermc/rainbow/mapper/PackMapper.java rename to client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java index ce9666a..cb56500 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapper/PackMapper.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java @@ -1,14 +1,13 @@ -package org.geysermc.rainbow.mapper; +package org.geysermc.rainbow.client.mapper; import net.minecraft.client.Minecraft; import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; import net.minecraft.network.chat.Component; -import org.geysermc.rainbow.PackManager; +import org.geysermc.rainbow.client.PackManager; import org.geysermc.rainbow.pack.BedrockPack; import java.util.Objects; -import java.util.Optional; public class PackMapper { private final PackManager packManager; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/EntityRenderDispatcherAccessor.java similarity index 89% rename from rainbow/src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java rename to client/src/main/java/org/geysermc/rainbow/client/mixin/EntityRenderDispatcherAccessor.java index 6dcc361..2267bd9 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/EntityRenderDispatcherAccessor.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/EntityRenderDispatcherAccessor.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mixin; +package org.geysermc.rainbow.client.mixin; import net.minecraft.client.renderer.entity.EntityRenderDispatcher; import net.minecraft.client.resources.model.EquipmentAssetManager; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java similarity index 92% rename from rainbow/src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java rename to client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java index 70e1b9d..8b76205 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/GuiItemRenderStateMixin.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mixin; +package org.geysermc.rainbow.client.mixin; import net.minecraft.client.gui.render.state.GuiItemRenderState; import net.minecraft.client.gui.render.state.ScreenArea; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java similarity index 96% rename from rainbow/src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java rename to client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java index f3d92dd..acd0a57 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/ModelManagerMixin.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mixin; +package org.geysermc.rainbow.client.mixin; import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; @@ -9,7 +9,7 @@ import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.PreparableReloadListener; -import org.geysermc.rainbow.accessor.ResolvedModelAccessor; +import org.geysermc.rainbow.client.accessor.ResolvedModelAccessor; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererAccessor.java similarity index 88% rename from rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java rename to client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererAccessor.java index 4c6d5e4..4a51697 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererAccessor.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererAccessor.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mixin; +package org.geysermc.rainbow.client.mixin; import com.mojang.blaze3d.textures.GpuTexture; import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java similarity index 90% rename from rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java rename to client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java index a665dee..3329a71 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/PictureInPictureRendererMixin.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java @@ -1,8 +1,8 @@ -package org.geysermc.rainbow.mixin; +package org.geysermc.rainbow.client.mixin; import com.mojang.blaze3d.textures.GpuTexture; import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; -import org.geysermc.rainbow.render.PictureInPictureCopyRenderer; +import org.geysermc.rainbow.client.render.PictureInPictureCopyRenderer; import org.jetbrains.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/SplashRendererAccessor.java similarity index 85% rename from rainbow/src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java rename to client/src/main/java/org/geysermc/rainbow/client/mixin/SplashRendererAccessor.java index 95fcb8e..0f5a4a3 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/SplashRendererAccessor.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/SplashRendererAccessor.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mixin; +package org.geysermc.rainbow.client.mixin; import net.minecraft.client.gui.components.SplashRenderer; import org.spongepowered.asm.mixin.Mixin; diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java new file mode 100644 index 0000000..b8243fb --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java @@ -0,0 +1,81 @@ +package org.geysermc.rainbow.client.render; + +import com.mojang.blaze3d.buffers.GpuBuffer; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.blaze3d.systems.CommandEncoder; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.textures.GpuTexture; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.navigation.ScreenRectangle; +import net.minecraft.client.gui.render.pip.OversizedItemRenderer; +import net.minecraft.client.gui.render.state.GuiItemRenderState; +import net.minecraft.client.gui.render.state.GuiRenderState; +import net.minecraft.client.gui.render.state.pip.OversizedItemRenderState; +import net.minecraft.client.renderer.item.TrackingItemStackRenderState; +import net.minecraft.world.item.ItemDisplayContext; +import net.minecraft.world.item.ItemStack; +import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; +import org.geysermc.rainbow.client.mixin.PictureInPictureRendererAccessor; +import org.joml.Matrix3x2fStack; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Objects; + +// TODO maybe just use this even for normal 2D items, not sure, could be useful for composite models and stuff +// TODO output in a size bedrock likes +public class MinecraftGeometryRenderer implements GeometryRenderer { + + @Override + public boolean render(ItemStack stack, Path path) { + TrackingItemStackRenderState itemRenderState = new TrackingItemStackRenderState(); + Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stack, ItemDisplayContext.GUI, null, null, 0); + itemRenderState.setOversizedInGui(true); + + GuiItemRenderState guiItemRenderState = new GuiItemRenderState("geometry_render", new Matrix3x2fStack(16), itemRenderState, 0, 0, null); + ScreenRectangle sizeBounds = guiItemRenderState.oversizedItemBounds(); + Objects.requireNonNull(sizeBounds); + OversizedItemRenderState oversizedRenderState = new OversizedItemRenderState(guiItemRenderState, sizeBounds.left(), sizeBounds.top(), sizeBounds.right() + 4, sizeBounds.bottom() + 4); + + try (OversizedItemRenderer itemRenderer = new OversizedItemRenderer(Minecraft.getInstance().renderBuffers().bufferSource())) { + //noinspection DataFlowIssue + ((PictureInPictureCopyRenderer) itemRenderer).rainbow$allowTextureCopy(); + itemRenderer.prepare(oversizedRenderState, new GuiRenderState(), 4); + writeAsPNG(path, ((PictureInPictureRendererAccessor) itemRenderer).getTexture()); + } + return true; + } + + // Simplified TextureUtil#writeAsPNG with some modifications to flip the image and just generate it at full size + private static void writeAsPNG(Path path, GpuTexture texture) { + RenderSystem.assertOnRenderThread(); + int width = texture.getWidth(0); + int height = texture.getHeight(0); + int bufferSize = texture.getFormat().pixelSize() * width * height; + + GpuBuffer buffer = RenderSystem.getDevice().createBuffer(() -> "Texture output buffer", GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_MAP_READ, bufferSize); + CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); + + Runnable writer = () -> { + try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(buffer, true, false)) { + try (NativeImage nativeImage = new NativeImage(width, height, false)) { + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int colour = mappedView.data().getInt((x + y * width) * texture.getFormat().pixelSize()); + nativeImage.setPixelABGR(x, height - y - 1, colour); + } + } + + CodecUtil.ensureDirectoryExists(path.getParent()); + nativeImage.writeToFile(path); + } catch (IOException var19) { + // TODO + } + } + + buffer.close(); + }; + commandEncoder.copyTextureToBuffer(texture, buffer, 0, writer, 0); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java b/client/src/main/java/org/geysermc/rainbow/client/render/PictureInPictureCopyRenderer.java similarity index 66% rename from rainbow/src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java rename to client/src/main/java/org/geysermc/rainbow/client/render/PictureInPictureCopyRenderer.java index df2a497..d4c6da8 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/render/PictureInPictureCopyRenderer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/render/PictureInPictureCopyRenderer.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.render; +package org.geysermc.rainbow.client.render; public interface PictureInPictureCopyRenderer { diff --git a/rainbow/src/main/resources/assets/rainbow/icon.png b/client/src/main/resources/assets/rainbow/icon.png similarity index 100% rename from rainbow/src/main/resources/assets/rainbow/icon.png rename to client/src/main/resources/assets/rainbow/icon.png diff --git a/rainbow/src/main/resources/assets/rainbow/lang/en_us.json b/client/src/main/resources/assets/rainbow/lang/en_us.json similarity index 100% rename from rainbow/src/main/resources/assets/rainbow/lang/en_us.json rename to client/src/main/resources/assets/rainbow/lang/en_us.json diff --git a/client/src/main/resources/fabric.mod.json b/client/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..0dd34a6 --- /dev/null +++ b/client/src/main/resources/fabric.mod.json @@ -0,0 +1,38 @@ +{ + "schemaVersion": 1, + "id": "rainbow-client", + "version": "${version}", + "name": "Rainbow", + "description": "Rainbow is a mod to generate Geyser item mappings and bedrock resourcepacks for use with Geyser's custom item API (v2)", + "authors": [ + "GeyserMC contributors" + ], + "contact": { + "homepage": "https://github.com/GeyserMC/rainbow", + "issues": "https://github.com/GeyserMC/rainbow/issues", + "sources": "https://github.com/GeyserMC/rainbow" + }, + "license": "MIT", + "icon": "assets/rainbow/icon.png", + "environment": "client", + "entrypoints": { + "client": [ + "org.geysermc.rainbow.client.RainbowClient" + ] + }, + "mixins": [ + "rainbow-client.mixins.json" + ], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric-api": "*", + "minecraft": "${supported_versions}" + }, + "custom": { + "modmenu": { + "links": { + "modmenu.discord": "https://discord.gg/GeyserMC" + } + } + } +} diff --git a/client/src/main/resources/rainbow-client.mixins.json b/client/src/main/resources/rainbow-client.mixins.json new file mode 100644 index 0000000..bc5f880 --- /dev/null +++ b/client/src/main/resources/rainbow-client.mixins.json @@ -0,0 +1,18 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.rainbow.client.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [], + "client": [ + "EntityRenderDispatcherAccessor", + "GuiItemRenderStateMixin", + "ModelManagerMixin", + "PictureInPictureRendererAccessor", + "PictureInPictureRendererMixin", + "SplashRendererAccessor" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java index f0f4eba..3aa247c 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -1,36 +1,15 @@ package org.geysermc.rainbow; import com.mojang.logging.LogUtils; -import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; -import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; -import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; -import net.minecraft.commands.synchronization.SingletonArgumentInfo; import net.minecraft.resources.ResourceLocation; -import org.geysermc.rainbow.command.CommandSuggestionsArgumentType; -import org.geysermc.rainbow.command.PackGeneratorCommand; -import org.geysermc.rainbow.mapper.PackMapper; import org.slf4j.Logger; -public class Rainbow implements ClientModInitializer { +public class Rainbow { public static final String MOD_ID = "rainbow"; public static final String MOD_NAME = "Rainbow"; public static final Logger LOGGER = LogUtils.getLogger(); - private final PackManager packManager = new PackManager(); - private final PackMapper packMapper = new PackMapper(packManager); - - // TODO export language overrides - @Override - public void onInitializeClient() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper)); - ClientTickEvents.START_CLIENT_TICK.register(packMapper::tick); - - ArgumentTypeRegistry.registerArgumentType(getModdedLocation("command_suggestions"), - CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); - } - public static ResourceLocation getModdedLocation(String path) { return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java new file mode 100644 index 0000000..1ea9e60 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java @@ -0,0 +1,25 @@ +package org.geysermc.rainbow.mapping; + +import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.client.resources.model.EquipmentClientInfo; +import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.equipment.EquipmentAsset; + +import java.io.InputStream; +import java.util.Optional; + +public interface AssetResolver { + + Optional getResolvedModel(ResourceLocation location); + + Optional getClientItem(ResourceLocation location); + + EquipmentClientInfo getEquipmentInfo(ResourceKey key); + + InputStream getTexture(ResourceLocation location); + + HolderLookup.Provider registries(); +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 412d4e3..169bec7 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -1,6 +1,5 @@ package org.geysermc.rainbow.mapping; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.item.BlockModelWrapper; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.renderer.item.ConditionalItemModel; @@ -38,13 +37,11 @@ import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.equipment.trim.TrimMaterial; import net.minecraft.world.level.Level; import org.apache.commons.lang3.ArrayUtils; -import org.geysermc.rainbow.accessor.ResolvedModelAccessor; import org.geysermc.rainbow.mapping.animation.AnimationMapper; import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.mapping.attachable.AttachableMapper; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; import org.geysermc.rainbow.mapping.geometry.GeometryMapper; -import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.mapping.geyser.GeyserBaseDefinition; import org.geysermc.rainbow.mapping.geyser.GeyserItemDefinition; import org.geysermc.rainbow.mapping.geyser.GeyserLegacyDefinition; @@ -72,23 +69,19 @@ public class BedrockItemMapper { .map(ResourceLocation::withDefaultNamespace) .toList(); - private static ResolvedModelAccessor getModels() { - return (ResolvedModelAccessor) Minecraft.getInstance().getModelManager(); - } - private static ResourceLocation getModelId(ItemModel.Unbaked model) { //noinspection unchecked return ((LateBoundIdMapperAccessor) ItemModels.ID_MAPPER).getIdToValue().inverse().get(model.type()); } public static void tryMapStack(ItemStack stack, ResourceLocation modelLocation, ProblemReporter reporter, PackContext context) { - getModels().rainbow$getClientItem(modelLocation).map(ClientItem::model) + context.assetResolver().getClientItem(modelLocation).map(ClientItem::model) .ifPresentOrElse(model -> mapItem(model, stack, reporter.forChild(() -> "client item definition " + modelLocation + " "), base -> new GeyserSingleDefinition(base, Optional.of(modelLocation)), context), () -> reporter.report(() -> "missing client item definition " + modelLocation)); } public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) { - ItemModel.Unbaked vanillaModel = getModels().rainbow$getClientItem(stack.get(DataComponents.ITEM_MODEL)).map(ClientItem::model).orElseThrow(); + ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(stack.get(DataComponents.ITEM_MODEL)).map(ClientItem::model).orElseThrow(); ProblemReporter childReporter = reporter.forChild(() -> "item model " + vanillaModel + " with custom model data " + customModelData + " "); if (vanillaModel instanceof RangeSelectItemModel.Unbaked(RangeSelectItemModelProperty property, float scale, List entries, Optional fallback)) { // WHY, Mojang? @@ -130,7 +123,7 @@ public class BedrockItemMapper { private static void mapBlockModelWrapper(BlockModelWrapper.Unbaked model, MappingContext context) { ResourceLocation itemModelLocation = model.model(); - getModels().rainbow$getResolvedModel(itemModelLocation) + context.packContext().assetResolver().getResolvedModel(itemModelLocation) .ifPresentOrElse(itemModel -> { ResolvedModel parentModel = itemModel.parent(); // debugName() returns the resource location of the model as a string @@ -290,13 +283,15 @@ public class BedrockItemMapper { boolean exportTexture = true; if (customModel.isPresent()) { texture = texture.withPath(path -> path + "_icon"); - GeometryRenderer.render(stack, packContext.packPath().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png")); - exportTexture = false; + // FIXME Bit of a hack, preferably render geometry at a later stage + exportTexture = !packContext.geometryRenderer().render(stack, packContext.packPath().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png")); packContext.additionalTextureConsumer().accept(geometryTexture); } packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), texture, exportTexture, - AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, bedrockGeometry, bedrockAnimation, packContext.additionalTextureConsumer()), + AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, bedrockGeometry, bedrockAnimation, + packContext.assetResolver(), + packContext.additionalTextureConsumer()), bedrockGeometry.map(BedrockGeometryContext::geometry), bedrockAnimation.map(BedrockAnimationContext::animation))); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index b8a788a..f6276b6 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -1,10 +1,12 @@ package org.geysermc.rainbow.mapping; import net.minecraft.resources.ResourceLocation; +import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.mapping.geyser.GeyserMappings; import java.nio.file.Path; import java.util.function.Consumer; -public record PackContext(GeyserMappings mappings, Path packPath, BedrockItemConsumer itemConsumer, Consumer additionalTextureConsumer) { +public record PackContext(GeyserMappings mappings, Path packPath, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, + GeometryRenderer geometryRenderer, Consumer additionalTextureConsumer) { } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index 6995474..8801f98 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -1,7 +1,6 @@ package org.geysermc.rainbow.mapping.attachable; import com.mojang.datafixers.util.Pair; -import net.minecraft.client.Minecraft; import net.minecraft.client.resources.model.EquipmentAssetManager; import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.core.component.DataComponentPatch; @@ -9,9 +8,9 @@ import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.equipment.Equippable; +import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; -import org.geysermc.rainbow.mixin.EntityRenderDispatcherAccessor; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; import java.util.List; @@ -21,17 +20,14 @@ import java.util.function.Consumer; public class AttachableMapper { public static Optional mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, Optional customGeometry, - Optional customAnimation, Consumer textureConsumer) { + Optional customAnimation, AssetResolver assetResolver, Consumer textureConsumer) { // Crazy optional statement // Unfortunately we can't have both equippables and custom models, so we prefer the latter :( return customGeometry .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.geometry().definitions().getFirst(), geometry.texture().getPath())) .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) .flatMap(optional -> (Optional) optional) - .flatMap(equippable -> { - EquipmentAssetManager equipmentAssets = ((EntityRenderDispatcherAccessor) Minecraft.getInstance().getEntityRenderDispatcher()).getEquipmentAssets(); - return equippable.assetId().map(asset -> Pair.of(equippable.slot(), equipmentAssets.get(asset))); - }) + .flatMap(equippable -> equippable.assetId().map(asset -> Pair.of(equippable.slot(), assetResolver.getEquipmentInfo(asset)))) .filter(assetInfo -> assetInfo.getSecond() != EquipmentAssetManager.MISSING) .map(assetInfo -> assetInfo .mapSecond(info -> info.getLayers(getLayer(assetInfo.getFirst())))) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java index f215134..37799d7 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java @@ -1,79 +1,10 @@ package org.geysermc.rainbow.mapping.geometry; -import com.mojang.blaze3d.buffers.GpuBuffer; -import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.blaze3d.systems.CommandEncoder; -import com.mojang.blaze3d.systems.RenderSystem; -import com.mojang.blaze3d.textures.GpuTexture; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.navigation.ScreenRectangle; -import net.minecraft.client.gui.render.pip.OversizedItemRenderer; -import net.minecraft.client.gui.render.state.GuiItemRenderState; -import net.minecraft.client.gui.render.state.GuiRenderState; -import net.minecraft.client.gui.render.state.pip.OversizedItemRenderState; -import net.minecraft.client.renderer.item.TrackingItemStackRenderState; -import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; -import org.geysermc.rainbow.CodecUtil; -import org.geysermc.rainbow.mixin.PictureInPictureRendererAccessor; -import org.geysermc.rainbow.render.PictureInPictureCopyRenderer; -import org.joml.Matrix3x2fStack; -import java.io.IOException; import java.nio.file.Path; -import java.util.Objects; -// TODO maybe just use this even for normal 2D items, not sure, could be useful for composite models and stuff -// TODO output in a size bedrock likes -public class GeometryRenderer { +public interface GeometryRenderer { - public static void render(ItemStack stack, Path path) { - TrackingItemStackRenderState itemRenderState = new TrackingItemStackRenderState(); - Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stack, ItemDisplayContext.GUI, null, null, 0); - itemRenderState.setOversizedInGui(true); - - GuiItemRenderState guiItemRenderState = new GuiItemRenderState("geometry_render", new Matrix3x2fStack(16), itemRenderState, 0, 0, null); - ScreenRectangle sizeBounds = guiItemRenderState.oversizedItemBounds(); - Objects.requireNonNull(sizeBounds); - OversizedItemRenderState oversizedRenderState = new OversizedItemRenderState(guiItemRenderState, sizeBounds.left(), sizeBounds.top(), sizeBounds.right() + 4, sizeBounds.bottom() + 4); - - try (OversizedItemRenderer itemRenderer = new OversizedItemRenderer(Minecraft.getInstance().renderBuffers().bufferSource())) { - //noinspection DataFlowIssue - ((PictureInPictureCopyRenderer) itemRenderer).rainbow$allowTextureCopy(); - itemRenderer.prepare(oversizedRenderState, new GuiRenderState(), 4); - writeAsPNG(path, ((PictureInPictureRendererAccessor) itemRenderer).getTexture()); - } - } - - // Simplified TextureUtil#writeAsPNG with some modifications to flip the image and just generate it at full size - private static void writeAsPNG(Path path, GpuTexture texture) { - RenderSystem.assertOnRenderThread(); - int width = texture.getWidth(0); - int height = texture.getHeight(0); - int bufferSize = texture.getFormat().pixelSize() * width * height; - - GpuBuffer buffer = RenderSystem.getDevice().createBuffer(() -> "Texture output buffer", GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_MAP_READ, bufferSize); - CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); - - Runnable writer = () -> { - try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(buffer, true, false)) { - try (NativeImage nativeImage = new NativeImage(width, height, false)) { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int colour = mappedView.data().getInt((x + y * width) * texture.getFormat().pixelSize()); - nativeImage.setPixelABGR(x, height - y - 1, colour); - } - } - - CodecUtil.ensureDirectoryExists(path.getParent()); - nativeImage.writeToFile(path); - } catch (IOException var19) { - // TODO - } - } - - buffer.close(); - }; - commandEncoder.copyTextureToBuffer(texture, buffer, 0, writer, 0); - } + boolean render(ItemStack stack, Path path); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java new file mode 100644 index 0000000..9836042 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java @@ -0,0 +1,14 @@ +package org.geysermc.rainbow.mapping.geometry; + +import net.minecraft.world.item.ItemStack; + +import java.nio.file.Path; + +public class NoopGeometryRenderer implements GeometryRenderer { + public static final NoopGeometryRenderer INSTANCE = new NoopGeometryRenderer(); + + @Override + public boolean render(ItemStack stack, Path path) { + return false; + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 6aeade7..11716dc 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -4,8 +4,6 @@ import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.components.SplashRenderer; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceLocation; @@ -18,10 +16,11 @@ import org.apache.commons.io.IOUtils; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; +import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer; import org.geysermc.rainbow.mapping.geyser.GeyserMappings; -import org.geysermc.rainbow.mixin.SplashRendererAccessor; import org.jetbrains.annotations.NotNull; import java.io.FileOutputStream; @@ -74,7 +73,9 @@ public class BedrockPack { private final ProblemReporter.Collector reporter; - public BedrockPack(String name) throws IOException { + private final PackContext context; + + public BedrockPack(String name, AssetResolver assetResolver) throws IOException { this.name = name; // Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet @@ -88,6 +89,14 @@ public class BedrockPack { itemTextures = BedrockTextures.builder(); reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " "); + + context = new PackContext(mappings, packPath, item -> { + itemTextures.withItemTexture(item); + if (item.exportTexture()) { + texturesToExport.add(item.texture()); + } + bedrockItems.add(item); + }, assetResolver, NoopGeometryRenderer.INSTANCE, texturesToExport::add); } public String name() { @@ -113,13 +122,6 @@ public class BedrockPack { reporter.report(problem); } }; - PackContext context = new PackContext(mappings, packPath, item -> { - itemTextures.withItemTexture(item); - if (item.exportTexture()) { - texturesToExport.add(item.texture()); - } - bedrockItems.add(item); - }, texturesToExport::add); Optional patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL); //noinspection OptionalAssignedToNull - annoying Mojang @@ -148,7 +150,7 @@ public class BedrockPack { boolean success = true; try { - CodecUtil.trySaveJson(GeyserMappings.CODEC, mappings, exportPath.resolve(MAPPINGS_FILE), RegistryOps.create(JsonOps.INSTANCE, Minecraft.getInstance().level.registryAccess())); + CodecUtil.trySaveJson(GeyserMappings.CODEC, mappings, exportPath.resolve(MAPPINGS_FILE), RegistryOps.create(JsonOps.INSTANCE, context.assetResolver().registries())); CodecUtil.trySaveJson(PackManifest.CODEC, manifest, packPath.resolve(MANIFEST_FILE)); CodecUtil.trySaveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), packPath.resolve(ITEM_ATLAS_FILE)); } catch (IOException | NullPointerException exception) { @@ -165,8 +167,8 @@ public class BedrockPack { } for (ResourceLocation texture : texturesToExport) { - texture = texture.withPath(path -> "textures/" + path + ".png"); - try (InputStream inputTexture = Minecraft.getInstance().getResourceManager().open(texture)) { + texture = texture.withPath(path -> "textures/" + path + ".png"); // FIXME + try (InputStream inputTexture = context.assetResolver().getTexture(texture)) { Path texturePath = packPath.resolve(texture.getPath()); CodecUtil.ensureDirectoryExists(texturePath.getParent()); try (OutputStream outputTexture = new FileOutputStream(texturePath.toFile())) { @@ -227,11 +229,11 @@ Textures tried to export: %d private static String randomSummaryComment() { if (RANDOM.nextDouble() < 0.6) { - SplashRenderer splash = Minecraft.getInstance().getSplashManager().getSplash(); + /*SplashRenderer splash = Minecraft.getInstance().getSplashManager().getSplash(); if (splash == null) { return "Undefined Undefined :("; } - return ((SplashRendererAccessor) splash).getSplash(); + return ((SplashRendererAccessor) splash).getSplash();*/ // TODO } return randomBuiltinSummaryComment(); } diff --git a/rainbow/src/main/resources/rainbow.mixins.json b/rainbow/src/main/resources/rainbow.mixins.json index 2d5bb60..887a686 100644 --- a/rainbow/src/main/resources/rainbow.mixins.json +++ b/rainbow/src/main/resources/rainbow.mixins.json @@ -5,14 +5,8 @@ "compatibilityLevel": "JAVA_21", "mixins": [], "client": [ - "EntityRenderDispatcherAccessor", - "GuiItemRenderStateMixin", "LateBoundIdMapperAccessor", - "ModelManagerMixin", - "PictureInPictureRendererAccessor", - "PictureInPictureRendererMixin", "RangeSelectItemModelAccessor", - "SplashRendererAccessor", "TextureSlotsAccessor" ], "injectors": { From 7c4c10d9cb7b35297cd4c7f2c3de690fcbb073c0 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 08:44:33 +0000 Subject: [PATCH 08/37] Implement MinecraftAssetResolver --- .../client/MinecraftAssetResolver.java | 33 +++++++++++++++---- .../geysermc/rainbow/client/PackManager.java | 3 +- .../rainbow/mapping/AssetResolver.java | 5 +-- .../mapping/attachable/AttachableMapper.java | 2 +- .../geysermc/rainbow/pack/BedrockPack.java | 2 +- 5 files changed, 33 insertions(+), 12 deletions(-) diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java index 1a1c392..77a7b87 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java @@ -1,41 +1,60 @@ package org.geysermc.rainbow.client; +import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.client.resources.model.EquipmentAssetManager; import net.minecraft.client.resources.model.EquipmentClientInfo; +import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.core.HolderLookup; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.item.equipment.EquipmentAsset; +import org.geysermc.rainbow.client.accessor.ResolvedModelAccessor; +import org.geysermc.rainbow.client.mixin.EntityRenderDispatcherAccessor; import org.geysermc.rainbow.mapping.AssetResolver; +import java.io.IOException; import java.io.InputStream; +import java.util.Objects; import java.util.Optional; public class MinecraftAssetResolver implements AssetResolver { + private final ModelManager modelManager; + private final EquipmentAssetManager equipmentAssetManager; + private final ResourceManager resourceManager; + private final HolderLookup.Provider registries; + + public MinecraftAssetResolver(Minecraft minecraft) { + modelManager = minecraft.getModelManager(); + equipmentAssetManager = ((EntityRenderDispatcherAccessor) minecraft.getEntityRenderDispatcher()).getEquipmentAssets(); + resourceManager = minecraft.getResourceManager(); + registries = Objects.requireNonNull(minecraft.level).registryAccess(); + } @Override public Optional getResolvedModel(ResourceLocation location) { - return Optional.empty(); + return ((ResolvedModelAccessor) modelManager).rainbow$getResolvedModel(location); } @Override public Optional getClientItem(ResourceLocation location) { - return Optional.empty(); + return ((ResolvedModelAccessor) modelManager).rainbow$getClientItem(location); } @Override - public EquipmentClientInfo getEquipmentInfo(ResourceKey key) { - return null; + public Optional getEquipmentInfo(ResourceKey key) { + return Optional.of(equipmentAssetManager.get(key)); } @Override - public InputStream getTexture(ResourceLocation location) { - return null; + public InputStream getTexture(ResourceLocation location) throws IOException { + return resourceManager.open(location); } @Override public HolderLookup.Provider registries() { - return null; + return registries; } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 458609d..1b526b4 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -1,5 +1,6 @@ package org.geysermc.rainbow.client; +import net.minecraft.client.Minecraft; import org.geysermc.rainbow.pack.BedrockPack; import java.io.IOException; @@ -16,7 +17,7 @@ public final class PackManager { throw new IllegalStateException("Already started a pack (" + currentPack.get().name() + ")"); } - currentPack = Optional.of(new BedrockPack(name, new MinecraftAssetResolver())); + currentPack = Optional.of(new BedrockPack(name, new MinecraftAssetResolver(Minecraft.getInstance()))); } public void run(Consumer consumer) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java index 1ea9e60..b949268 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java @@ -8,6 +8,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.equipment.EquipmentAsset; +import java.io.IOException; import java.io.InputStream; import java.util.Optional; @@ -17,9 +18,9 @@ public interface AssetResolver { Optional getClientItem(ResourceLocation location); - EquipmentClientInfo getEquipmentInfo(ResourceKey key); + Optional getEquipmentInfo(ResourceKey key); - InputStream getTexture(ResourceLocation location); + InputStream getTexture(ResourceLocation location) throws IOException; HolderLookup.Provider registries(); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index 8801f98..85a1487 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -27,7 +27,7 @@ public class AttachableMapper { .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.geometry().definitions().getFirst(), geometry.texture().getPath())) .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) .flatMap(optional -> (Optional) optional) - .flatMap(equippable -> equippable.assetId().map(asset -> Pair.of(equippable.slot(), assetResolver.getEquipmentInfo(asset)))) + .flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info))) .filter(assetInfo -> assetInfo.getSecond() != EquipmentAssetManager.MISSING) .map(assetInfo -> assetInfo .mapSecond(info -> info.getLayers(getLayer(assetInfo.getFirst())))) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 11716dc..225ee78 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -167,7 +167,7 @@ public class BedrockPack { } for (ResourceLocation texture : texturesToExport) { - texture = texture.withPath(path -> "textures/" + path + ".png"); // FIXME + texture = texture.withPath(path -> "textures/" + path + ".png"); try (InputStream inputTexture = context.assetResolver().getTexture(texture)) { Path texturePath = packPath.resolve(texture.getPath()); CodecUtil.ensureDirectoryExists(texturePath.getParent()); From b741eac4c6a108826881e352222c53b8a1b4516e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 09:07:45 +0000 Subject: [PATCH 09/37] Relocate Geyser mapping codecs --- .../GeyserBaseDefinition.java | 4 ++-- .../GeyserGroupDefinition.java | 2 +- .../GeyserItemDefinition.java | 2 +- .../GeyserLegacyDefinition.java | 2 +- .../geyser => definition}/GeyserMapping.java | 2 +- .../geyser => definition}/GeyserMappings.java | 2 +- .../GeyserSingleDefinition.java | 2 +- .../predicate/GeyserConditionPredicate.java | 2 +- .../predicate/GeyserMatchPredicate.java | 2 +- .../predicate/GeyserPredicate.java | 2 +- .../predicate/GeyserRangeDispatchPredicate.java | 2 +- .../rainbow/mapping/BedrockItemMapper.java | 16 ++++++++-------- .../geysermc/rainbow/mapping/PackContext.java | 2 +- .../org/geysermc/rainbow/pack/BedrockPack.java | 2 +- rainbow/src/main/resources/fabric.mod.json | 5 ----- 15 files changed, 22 insertions(+), 27 deletions(-) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserBaseDefinition.java (97%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserGroupDefinition.java (97%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserItemDefinition.java (85%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserLegacyDefinition.java (96%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserMapping.java (97%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserMappings.java (98%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/GeyserSingleDefinition.java (96%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/predicate/GeyserConditionPredicate.java (98%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/predicate/GeyserMatchPredicate.java (98%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/predicate/GeyserPredicate.java (95%) rename rainbow/src/main/java/org/geysermc/rainbow/{mapping/geyser => definition}/predicate/GeyserRangeDispatchPredicate.java (98%) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java similarity index 97% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java index f00522c..304c011 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserBaseDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; @@ -8,7 +8,7 @@ import net.minecraft.core.component.DataComponentType; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; -import org.geysermc.rainbow.mapping.geyser.predicate.GeyserPredicate; +import org.geysermc.rainbow.definition.predicate.GeyserPredicate; import java.util.List; import java.util.Optional; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java similarity index 97% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java index 3199f47..20e1aa7 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserGroupDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java similarity index 85% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java index ee7ef16..d09894a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserItemDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import net.minecraft.resources.ResourceLocation; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java similarity index 96% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java index bff6ba8..0f79aae 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserLegacyDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java similarity index 97% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java index 14c8ffc..d3cc862 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMapping.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java similarity index 98% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java index 96fb307..5a887a2 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserMappings.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import com.google.common.collect.Multimap; import com.google.common.collect.MultimapBuilder; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserSingleDefinition.java similarity index 96% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserSingleDefinition.java index 85f02ce..b3e6066 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/GeyserSingleDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserSingleDefinition.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser; +package org.geysermc.rainbow.definition; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java similarity index 98% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java index 7e1d49c..79adca7 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserConditionPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser.predicate; +package org.geysermc.rainbow.definition.predicate; import com.google.common.base.Suppliers; import com.mojang.serialization.Codec; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java similarity index 98% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java index ecd558c..4e793a6 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserMatchPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser.predicate; +package org.geysermc.rainbow.definition.predicate; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java similarity index 95% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java index fb753ba..7c6b0f3 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser.predicate; +package org.geysermc.rainbow.definition.predicate; import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java similarity index 98% rename from rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java rename to rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java index 67f3643..537a3f7 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java @@ -1,4 +1,4 @@ -package org.geysermc.rainbow.mapping.geyser.predicate; +package org.geysermc.rainbow.definition.predicate; import com.google.common.base.Suppliers; import com.mojang.serialization.Codec; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 169bec7..e6a5967 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -42,14 +42,14 @@ import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.mapping.attachable.AttachableMapper; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; import org.geysermc.rainbow.mapping.geometry.GeometryMapper; -import org.geysermc.rainbow.mapping.geyser.GeyserBaseDefinition; -import org.geysermc.rainbow.mapping.geyser.GeyserItemDefinition; -import org.geysermc.rainbow.mapping.geyser.GeyserLegacyDefinition; -import org.geysermc.rainbow.mapping.geyser.GeyserSingleDefinition; -import org.geysermc.rainbow.mapping.geyser.predicate.GeyserConditionPredicate; -import org.geysermc.rainbow.mapping.geyser.predicate.GeyserMatchPredicate; -import org.geysermc.rainbow.mapping.geyser.predicate.GeyserPredicate; -import org.geysermc.rainbow.mapping.geyser.predicate.GeyserRangeDispatchPredicate; +import org.geysermc.rainbow.definition.GeyserBaseDefinition; +import org.geysermc.rainbow.definition.GeyserItemDefinition; +import org.geysermc.rainbow.definition.GeyserLegacyDefinition; +import org.geysermc.rainbow.definition.GeyserSingleDefinition; +import org.geysermc.rainbow.definition.predicate.GeyserConditionPredicate; +import org.geysermc.rainbow.definition.predicate.GeyserMatchPredicate; +import org.geysermc.rainbow.definition.predicate.GeyserPredicate; +import org.geysermc.rainbow.definition.predicate.GeyserRangeDispatchPredicate; import org.geysermc.rainbow.mixin.LateBoundIdMapperAccessor; import org.geysermc.rainbow.mixin.RangeSelectItemModelAccessor; import org.geysermc.rainbow.mixin.TextureSlotsAccessor; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index f6276b6..8bee7e0 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -2,7 +2,7 @@ package org.geysermc.rainbow.mapping; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; -import org.geysermc.rainbow.mapping.geyser.GeyserMappings; +import org.geysermc.rainbow.definition.GeyserMappings; import java.nio.file.Path; import java.util.function.Consumer; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 225ee78..5a765ab 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -20,7 +20,7 @@ import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer; -import org.geysermc.rainbow.mapping.geyser.GeyserMappings; +import org.geysermc.rainbow.definition.GeyserMappings; import org.jetbrains.annotations.NotNull; import java.io.FileOutputStream; diff --git a/rainbow/src/main/resources/fabric.mod.json b/rainbow/src/main/resources/fabric.mod.json index a3e78ce..1677e20 100644 --- a/rainbow/src/main/resources/fabric.mod.json +++ b/rainbow/src/main/resources/fabric.mod.json @@ -15,11 +15,6 @@ "license": "MIT", "icon": "assets/rainbow/icon.png", "environment": "client", - "entrypoints": { - "client": [ - "org.geysermc.rainbow.Rainbow" - ] - }, "mixins": [ "rainbow.mixins.json" ], From a38cbdf4136e38602c44c02bc5e4e2be348d8b9b Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 09:53:45 +0000 Subject: [PATCH 10/37] Make BedrockPack more modular --- .../geysermc/rainbow/client/PackManager.java | 95 ++++++- .../render/MinecraftGeometryRenderer.java | 1 + .../rainbow/mapping/BedrockItemMapper.java | 2 +- .../geysermc/rainbow/mapping/PackContext.java | 7 +- .../geysermc/rainbow/pack/BedrockPack.java | 260 +++++++++++------- .../org/geysermc/rainbow/pack/PackPaths.java | 8 + 6 files changed, 261 insertions(+), 112 deletions(-) create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/pack/PackPaths.java diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 1b526b4..46d56d1 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -1,14 +1,40 @@ package org.geysermc.rainbow.client; +import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.SplashRenderer; +import net.minecraft.util.RandomSource; +import net.minecraft.util.StringUtil; +import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.client.mixin.SplashRendererAccessor; +import org.geysermc.rainbow.client.render.MinecraftGeometryRenderer; +import org.geysermc.rainbow.pack.BedrockItem; import org.geysermc.rainbow.pack.BedrockPack; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.util.List; import java.util.Optional; +import java.util.Set; import java.util.function.Consumer; public final class PackManager { + private static final List PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY", + "use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!", + "eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually", + "you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.", + "meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!", + "any colour you like", "someone tell Mojang about this", "you can't unbake baked models, so we'll store the unbaked models", "soon fully datagen ready", + "packconverter when", "codecs ftw"); + private static final RandomSource RANDOM = RandomSource.create(); + + private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID); + private static final Path PACK_DIRECTORY = Path.of("pack"); + private static final Path MAPPINGS_FILE = Path.of("geyser_mappings.json"); + private static final Path PACK_ZIP_FILE = Path.of("pack.zip"); + private static final Path REPORT_FILE = Path.of("report.txt"); private Optional currentPack = Optional.empty(); @@ -17,7 +43,13 @@ public final class PackManager { throw new IllegalStateException("Already started a pack (" + currentPack.get().name() + ")"); } - currentPack = Optional.of(new BedrockPack(name, new MinecraftAssetResolver(Minecraft.getInstance()))); + Path packDirectory = createPackDirectory(name); + BedrockPack pack = BedrockPack.builder(name, packDirectory.resolve(MAPPINGS_FILE), packDirectory.resolve(PACK_DIRECTORY), new MinecraftAssetResolver(Minecraft.getInstance())) + .withPackZipFile(packDirectory.resolve(PACK_ZIP_FILE)) + .withGeometryRenderer(MinecraftGeometryRenderer.INSTANCE) + .reportSuccesses() + .build(); + currentPack = Optional.of(pack); } public void run(Consumer consumer) { @@ -29,12 +61,69 @@ public final class PackManager { } public Optional getExportPath() { - return currentPack.map(BedrockPack::getExportPath); + return currentPack.map(pack -> EXPORT_DIRECTORY.resolve(pack.name())); } public Optional finish() { - Optional success = currentPack.map(BedrockPack::save); + Optional success = currentPack.map(pack -> { + try { + Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack)); + } catch (IOException exception) { + // TODO log + } + return pack.save(); + }); currentPack = Optional.empty(); return success; } + + private static String createPackSummary(BedrockPack pack) { + String problems = pack.getReporter().getTreeReport(); + if (StringUtil.isBlank(problems)) { + problems = "Well that's odd... there's nothing here!"; + } + + Set bedrockItems = pack.getBedrockItems(); + long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count(); + long geometries = bedrockItems.stream().filter(item -> item.geometry().isPresent()).count(); + long animations = bedrockItems.stream().filter(item -> item.animation().isPresent()).count(); + + return """ +-- PACK GENERATION REPORT -- +// %s + +Generated pack: %s +Mappings written: %d +Item texture atlas size: %d +Attachables tried to export: %d +Geometry files tried to export: %d +Animations tried to export: %d +Textures tried to export: %d + +-- MAPPING TREE REPORT -- +%s +""".formatted(randomSummaryComment(), pack.name(), pack.getMappings(), pack.getItemTextureAtlasSize(), + attachables, geometries, animations, pack.getAdditionalExportedTextures(), problems); + } + + private static String randomSummaryComment() { + if (RANDOM.nextDouble() < 0.5) { + SplashRenderer splash = Minecraft.getInstance().getSplashManager().getSplash(); + if (splash == null) { + return "Undefined Undefined :("; + } + return ((SplashRendererAccessor) splash).getSplash(); + } + return randomBuiltinSummaryComment(); + } + + private static String randomBuiltinSummaryComment() { + return PACK_SUMMARY_COMMENTS.get(RANDOM.nextInt(PACK_SUMMARY_COMMENTS.size())); + } + + private static Path createPackDirectory(String name) throws IOException { + Path path = EXPORT_DIRECTORY.resolve(name); + CodecUtil.ensureDirectoryExists(path); + return path; + } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java index b8243fb..d7e8f19 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java @@ -26,6 +26,7 @@ import java.util.Objects; // TODO maybe just use this even for normal 2D items, not sure, could be useful for composite models and stuff // TODO output in a size bedrock likes public class MinecraftGeometryRenderer implements GeometryRenderer { + public static final MinecraftGeometryRenderer INSTANCE = new MinecraftGeometryRenderer(); @Override public boolean render(ItemStack stack, Path path) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index e6a5967..27c054d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -284,7 +284,7 @@ public class BedrockItemMapper { if (customModel.isPresent()) { texture = texture.withPath(path -> path + "_icon"); // FIXME Bit of a hack, preferably render geometry at a later stage - exportTexture = !packContext.geometryRenderer().render(stack, packContext.packPath().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png")); + exportTexture = !packContext.geometryRenderer().render(stack, packContext.paths().packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png")); packContext.additionalTextureConsumer().accept(geometryTexture); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index 8bee7e0..e32196a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -3,10 +3,11 @@ package org.geysermc.rainbow.mapping; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; +import org.geysermc.rainbow.pack.PackPaths; -import java.nio.file.Path; import java.util.function.Consumer; -public record PackContext(GeyserMappings mappings, Path packPath, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, - GeometryRenderer geometryRenderer, Consumer additionalTextureConsumer) { +public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, + GeometryRenderer geometryRenderer, Consumer additionalTextureConsumer, + boolean reportSuccesses) { } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 5a765ab..d2c2a9f 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -3,22 +3,22 @@ package org.geysermc.rainbow.pack; import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; -import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ProblemReporter; -import net.minecraft.util.RandomSource; -import net.minecraft.util.StringUtil; +import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.CustomModelData; import org.apache.commons.io.IOUtils; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; -import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; +import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; import org.jetbrains.annotations.NotNull; @@ -27,7 +27,6 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.nio.file.Files; import java.nio.file.Path; import java.util.HashSet; import java.util.List; @@ -35,68 +34,38 @@ import java.util.Optional; import java.util.Set; import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.UnaryOperator; public class BedrockPack { - private static final List PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY", - "use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!", - "eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually", - "you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.", - "meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!", - "any colour you like", "someone tell Mojang about this", "you can't unbake baked models, so we'll store the unbaked models", "soon fully datagen ready", - "packconverter when", "codecs ftw"); - private static final RandomSource RANDOM = RandomSource.create(); - - private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID); - private static final Path PACK_DIRECTORY = Path.of("pack"); - private static final Path ATTACHABLES_DIRECTORY = Path.of("attachables"); - private static final Path GEOMETRY_DIRECTORY = Path.of("models/entity"); - private static final Path ANIMATION_DIRECTORY = Path.of("animations"); - - private static final Path MAPPINGS_FILE = Path.of("geyser_mappings.json"); - private static final Path MANIFEST_FILE = Path.of("manifest.json"); - private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture.json"); - - private static final Path PACK_ZIP_FILE = Path.of("pack.zip"); - private static final Path REPORT_FILE = Path.of("report.txt"); - private final String name; - private final Path exportPath; - private final Path packPath; - private final PackManifest manifest; - private final GeyserMappings mappings; - private final BedrockTextures.Builder itemTextures; + private final PackPaths paths; + private final BedrockTextures.Builder itemTextures = BedrockTextures.builder(); private final Set bedrockItems = new HashSet<>(); private final Set texturesToExport = new HashSet<>(); private final Set modelsMapped = new HashSet<>(); private final IntSet customModelDataMapped = new IntOpenHashSet(); + private final PackContext context; + private final PackManifest manifest; private final ProblemReporter.Collector reporter; - private final PackContext context; - - public BedrockPack(String name, AssetResolver assetResolver) throws IOException { + public BedrockPack(String name, PackPaths paths, AssetResolver assetResolver, + GeometryRenderer geometryRenderer, ProblemReporter.Collector reporter, + boolean reportSuccesses) { this.name = name; + this.paths = paths; // Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet - exportPath = createPackDirectory(name); - packPath = exportPath.resolve(PACK_DIRECTORY); - //mappings = CodecUtil.readOrCompute(GeyserMappings.CODEC, exportPath.resolve(MAPPINGS_FILE), GeyserMappings::new); - mappings = new GeyserMappings(); - manifest = CodecUtil.readOrCompute(PackManifest.CODEC, packPath.resolve(MANIFEST_FILE), () -> defaultManifest(name)).increment(); - /*itemTextures = CodecUtil.readOrCompute(BedrockTextureAtlas.ITEM_ATLAS_CODEC, packPath.resolve(ITEM_ATLAS_FILE), - () -> BedrockTextureAtlas.itemAtlas(name, BedrockTextures.builder())).textures().toBuilder();*/ - itemTextures = BedrockTextures.builder(); - - reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " "); - - context = new PackContext(mappings, packPath, item -> { + this.context = new PackContext(new GeyserMappings(), paths, item -> { itemTextures.withItemTexture(item); if (item.exportTexture()) { texturesToExport.add(item.texture()); } bedrockItems.add(item); - }, assetResolver, NoopGeometryRenderer.INSTANCE, texturesToExport::add); + }, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses); + manifest = defaultManifest(name); + this.reporter = reporter; } public String name() { @@ -146,20 +115,26 @@ public class BedrockPack { return problems.get() ? MappingResult.PROBLEMS_OCCURRED : MappingResult.MAPPED_SUCCESSFULLY; } + public MappingResult map(Holder item, DataComponentPatch patch) { + ItemStack stack = new ItemStack(item); + stack.applyComponents(patch); + return map(stack); + } + public boolean save() { boolean success = true; try { - CodecUtil.trySaveJson(GeyserMappings.CODEC, mappings, exportPath.resolve(MAPPINGS_FILE), RegistryOps.create(JsonOps.INSTANCE, context.assetResolver().registries())); - CodecUtil.trySaveJson(PackManifest.CODEC, manifest, packPath.resolve(MANIFEST_FILE)); - CodecUtil.trySaveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), packPath.resolve(ITEM_ATLAS_FILE)); + CodecUtil.trySaveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings(), RegistryOps.create(JsonOps.INSTANCE, context.assetResolver().registries())); + CodecUtil.trySaveJson(PackManifest.CODEC, manifest, paths.manifest()); + CodecUtil.trySaveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas()); } catch (IOException | NullPointerException exception) { reporter.forChild(() -> "saving Geyser mappings, pack manifest, and texture atlas ").report(() -> "failed to save to pack: " + exception); success = false; } for (BedrockItem item : bedrockItems) { try { - item.save(packPath.resolve(ATTACHABLES_DIRECTORY), packPath.resolve(GEOMETRY_DIRECTORY), packPath.resolve(ANIMATION_DIRECTORY)); + item.save(paths.attachables(), paths.geometry(), paths.animation()); } catch (IOException exception) { reporter.forChild(() -> "files for bedrock item " + item.identifier() + " ").report(() -> "failed to save to pack: " + exception); success = false; @@ -169,7 +144,7 @@ public class BedrockPack { for (ResourceLocation texture : texturesToExport) { texture = texture.withPath(path -> "textures/" + path + ".png"); try (InputStream inputTexture = context.assetResolver().getTexture(texture)) { - Path texturePath = packPath.resolve(texture.getPath()); + Path texturePath = paths.packRoot().resolve(texture.getPath()); CodecUtil.ensureDirectoryExists(texturePath.getParent()); try (OutputStream outputTexture = new FileOutputStream(texturePath.toFile())) { IOUtils.copy(inputTexture, outputTexture); @@ -181,71 +156,35 @@ public class BedrockPack { } } - try { - CodecUtil.tryZipDirectory(packPath, exportPath.resolve(PACK_ZIP_FILE)); - } catch (IOException exception) { - success = false; + if (paths.zipOutput().isPresent()) { + try { + CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get()); + } catch (IOException exception) { + success = false; + } } - try { - Files.writeString(exportPath.resolve(REPORT_FILE), createPackSummary()); - } catch (IOException exception) { - // TODO log - } return success; } - public Path getExportPath() { - return exportPath; + public int getMappings() { + return context.mappings().size(); } - private String createPackSummary() { - String problems = reporter.getTreeReport(); - if (StringUtil.isBlank(problems)) { - problems = "Well that's odd... there's nothing here!"; - } - - long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count(); - long geometries = bedrockItems.stream().filter(item -> item.geometry().isPresent()).count(); - long animations = bedrockItems.stream().filter(item -> item.animation().isPresent()).count(); - - return """ --- PACK GENERATION REPORT -- -// %s - -Generated pack: %s -Mappings written: %d -Item texture atlas size: %d -Attachables tried to export: %d -Geometry files tried to export: %d -Animations tried to export: %d -Textures tried to export: %d - --- MAPPING TREE REPORT -- -%s -""".formatted(randomSummaryComment(), name, mappings.size(), itemTextures.build().size(), - attachables, geometries, animations, texturesToExport.size(), problems); + public Set getBedrockItems() { + return Set.copyOf(bedrockItems); } - private static String randomSummaryComment() { - if (RANDOM.nextDouble() < 0.6) { - /*SplashRenderer splash = Minecraft.getInstance().getSplashManager().getSplash(); - if (splash == null) { - return "Undefined Undefined :("; - } - return ((SplashRendererAccessor) splash).getSplash();*/ // TODO - } - return randomBuiltinSummaryComment(); + public int getItemTextureAtlasSize() { + return itemTextures.build().size(); } - private static String randomBuiltinSummaryComment() { - return PACK_SUMMARY_COMMENTS.get(RANDOM.nextInt(PACK_SUMMARY_COMMENTS.size())); + public int getAdditionalExportedTextures() { + return texturesToExport.size(); } - private static Path createPackDirectory(String name) throws IOException { - Path path = EXPORT_DIRECTORY.resolve(name); - CodecUtil.ensureDirectoryExists(path); - return path; + public ProblemReporter.Collector getReporter() { + return reporter; } private static PackManifest defaultManifest(String name) { @@ -253,6 +192,117 @@ Textures tried to export: %d List.of(new PackManifest.Module(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0)))); } + public static Builder builder(String name, Path mappingsPath, Path packRootPath, AssetResolver assetResolver) { + return new Builder(name, mappingsPath, packRootPath, assetResolver); + } + + public static class Builder { + private static final Path ATTACHABLES_DIRECTORY = Path.of("attachables"); + private static final Path GEOMETRY_DIRECTORY = Path.of("models/entity"); + private static final Path ANIMATION_DIRECTORY = Path.of("animations"); + + private static final Path MANIFEST_FILE = Path.of("manifest.json"); + private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture.json"); + + private final String name; + private final Path mappingsPath; + private final Path packRootPath; + private final AssetResolver assetResolver; + private UnaryOperator attachablesPath = resolve(ATTACHABLES_DIRECTORY); + private UnaryOperator geometryPath = resolve(GEOMETRY_DIRECTORY); + private UnaryOperator animationPath = resolve(ANIMATION_DIRECTORY); + private UnaryOperator manifestPath = resolve(MANIFEST_FILE); + private UnaryOperator itemAtlasPath = resolve(ITEM_ATLAS_FILE); + private Path packZipFile = null; + private GeometryRenderer geometryRenderer = NoopGeometryRenderer.INSTANCE; + private ProblemReporter.Collector reporter; + private boolean reportSuccesses = false; + + public Builder(String name, Path mappingsPath, Path packRootPath, AssetResolver assetResolver) { + this.name = name; + this.mappingsPath = mappingsPath; + this.packRootPath = packRootPath; + this.reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " "); + this.assetResolver = assetResolver; + } + + public Builder withAttachablesPath(Path absolute) { + return withAttachablesPath(path -> absolute); + } + + public Builder withAttachablesPath(UnaryOperator path) { + attachablesPath = path; + return this; + } + + public Builder withGeometryPath(Path absolute) { + return withGeometryPath(path -> absolute); + } + + public Builder withGeometryPath(UnaryOperator path) { + geometryPath = path; + return this; + } + + public Builder withAnimationPath(Path absolute) { + return withAnimationPath(path -> absolute); + } + + public Builder withAnimationPath(UnaryOperator path) { + animationPath = path; + return this; + } + + public Builder withManifestPath(Path absolute) { + return withManifestPath(path -> absolute); + } + + public Builder withManifestPath(UnaryOperator path) { + manifestPath = path; + return this; + } + + public Builder withItemAtlasPath(Path absolute) { + return withItemAtlasPath(path -> absolute); + } + + public Builder withItemAtlasPath(UnaryOperator path) { + itemAtlasPath = path; + return this; + } + + public Builder withPackZipFile(Path absolute) { + packZipFile = absolute; + return this; + } + + public Builder withGeometryRenderer(GeometryRenderer renderer) { + geometryRenderer = renderer; + return this; + } + + public Builder withReporter(ProblemReporter.Collector reporter) { + this.reporter = reporter; + return this; + } + + public Builder reportSuccesses() { + this.reportSuccesses = true; + return this; + } + + public BedrockPack build() { + PackPaths paths = new PackPaths(mappingsPath, packRootPath, attachablesPath.apply(packRootPath), + geometryPath.apply(packRootPath), animationPath.apply(packRootPath), manifestPath.apply(packRootPath), + itemAtlasPath.apply(packRootPath), Optional.ofNullable(packZipFile)); + return new BedrockPack(name, paths, assetResolver, geometryRenderer, reporter, reportSuccesses); + } + + private static UnaryOperator resolve(Path child) { + return root -> root.resolve(child); + } + } + public enum MappingResult { NONE_MAPPED, MAPPED_SUCCESSFULLY, diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/PackPaths.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/PackPaths.java new file mode 100644 index 0000000..16f101f --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/PackPaths.java @@ -0,0 +1,8 @@ +package org.geysermc.rainbow.pack; + +import java.nio.file.Path; +import java.util.Optional; + +public record PackPaths(Path mappings, Path packRoot, Path attachables, Path geometry, Path animation, + Path manifest, Path itemAtlas, Optional zipOutput) { +} From 6a1fe997ba8c25ec817ea7942db10ae78610a9d2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 10:59:57 +0000 Subject: [PATCH 11/37] Make pack saving abstract --- .../client/MinecraftAssetResolver.java | 19 ------ .../client/MinecraftPackSerializer.java | 60 +++++++++++++++++ .../geysermc/rainbow/client/PackManager.java | 15 +++-- .../client/command/PackGeneratorCommand.java | 15 ++--- .../rainbow/mapping/AssetResolver.java | 7 -- .../rainbow/mapping/PackSerializer.java | 14 ++++ .../geysermc/rainbow/pack/BedrockItem.java | 23 +++---- .../geysermc/rainbow/pack/BedrockPack.java | 66 +++++++------------ .../pack/animation/BedrockAnimation.java | 7 +- .../pack/attachable/BedrockAttachable.java | 8 +-- .../pack/geometry/BedrockGeometry.java | 7 +- 11 files changed, 137 insertions(+), 104 deletions(-) create mode 100644 client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java index 77a7b87..d3e2f7c 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java @@ -6,31 +6,22 @@ import net.minecraft.client.resources.model.EquipmentAssetManager; import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; -import net.minecraft.core.HolderLookup; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.client.accessor.ResolvedModelAccessor; import org.geysermc.rainbow.client.mixin.EntityRenderDispatcherAccessor; import org.geysermc.rainbow.mapping.AssetResolver; -import java.io.IOException; -import java.io.InputStream; -import java.util.Objects; import java.util.Optional; public class MinecraftAssetResolver implements AssetResolver { private final ModelManager modelManager; private final EquipmentAssetManager equipmentAssetManager; - private final ResourceManager resourceManager; - private final HolderLookup.Provider registries; public MinecraftAssetResolver(Minecraft minecraft) { modelManager = minecraft.getModelManager(); equipmentAssetManager = ((EntityRenderDispatcherAccessor) minecraft.getEntityRenderDispatcher()).getEquipmentAssets(); - resourceManager = minecraft.getResourceManager(); - registries = Objects.requireNonNull(minecraft.level).registryAccess(); } @Override @@ -47,14 +38,4 @@ public class MinecraftAssetResolver implements AssetResolver { public Optional getEquipmentInfo(ResourceKey key) { return Optional.of(equipmentAssetManager.get(key)); } - - @Override - public InputStream getTexture(ResourceLocation location) throws IOException { - return resourceManager.open(location); - } - - @Override - public HolderLookup.Provider registries() { - return registries; - } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java new file mode 100644 index 0000000..6ee5895 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java @@ -0,0 +1,60 @@ +package org.geysermc.rainbow.client; + +import com.google.gson.JsonElement; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.core.HolderLookup; +import net.minecraft.resources.RegistryOps; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import org.apache.commons.io.IOUtils; +import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.mapping.PackSerializer; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.file.Path; +import java.util.Objects; +import java.util.concurrent.CompletableFuture; + +public class MinecraftPackSerializer implements PackSerializer { + private final HolderLookup.Provider registries; + private final ResourceManager resourceManager; + + public MinecraftPackSerializer(Minecraft minecraft) { + registries = Objects.requireNonNull(minecraft.level).registryAccess(); + resourceManager = minecraft.getResourceManager(); + } + + @Override + public CompletableFuture saveJson(Codec codec, T object, Path path) { + DynamicOps ops = RegistryOps.create(JsonOps.INSTANCE, registries); + return CompletableFuture.runAsync(() -> { + try { + CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops); + } catch (IOException exception) { + // TODO log + } + }, Util.backgroundExecutor().forName("PackSerializer-saveJson")); + } + + @Override + public CompletableFuture saveTexture(ResourceLocation texture, Path path) { + return CompletableFuture.runAsync(() -> { + ResourceLocation texturePath = texture.withPath(p -> "textures/" + p + ".png"); + try (InputStream inputTexture = resourceManager.open(texturePath)) { + CodecUtil.ensureDirectoryExists(path.getParent()); + try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { + IOUtils.copy(inputTexture, outputTexture); + } + } catch (IOException exception) { + // TODO log + } + }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); + } +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 46d56d1..e81b348 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -18,6 +18,7 @@ import java.nio.file.Path; import java.util.List; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; public final class PackManager { @@ -32,7 +33,7 @@ public final class PackManager { private static final Path EXPORT_DIRECTORY = FabricLoader.getInstance().getGameDir().resolve(Rainbow.MOD_ID); private static final Path PACK_DIRECTORY = Path.of("pack"); - private static final Path MAPPINGS_FILE = Path.of("geyser_mappings.json"); + private static final Path MAPPINGS_FILE = Path.of("geyser_mappings"); private static final Path PACK_ZIP_FILE = Path.of("pack.zip"); private static final Path REPORT_FILE = Path.of("report.txt"); @@ -44,7 +45,8 @@ public final class PackManager { } Path packDirectory = createPackDirectory(name); - BedrockPack pack = BedrockPack.builder(name, packDirectory.resolve(MAPPINGS_FILE), packDirectory.resolve(PACK_DIRECTORY), new MinecraftAssetResolver(Minecraft.getInstance())) + BedrockPack pack = BedrockPack.builder(name, packDirectory.resolve(MAPPINGS_FILE), packDirectory.resolve(PACK_DIRECTORY), + new MinecraftPackSerializer(Minecraft.getInstance()), new MinecraftAssetResolver(Minecraft.getInstance())) .withPackZipFile(packDirectory.resolve(PACK_ZIP_FILE)) .withGeometryRenderer(MinecraftGeometryRenderer.INSTANCE) .reportSuccesses() @@ -64,17 +66,18 @@ public final class PackManager { return currentPack.map(pack -> EXPORT_DIRECTORY.resolve(pack.name())); } - public Optional finish() { - Optional success = currentPack.map(pack -> { + public boolean finish() { + currentPack.map(pack -> { try { Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack)); } catch (IOException exception) { // TODO log } return pack.save(); - }); + }).ifPresent(CompletableFuture::join); + boolean wasPresent = currentPack.isPresent(); currentPack = Optional.empty(); - return success; + return wasPresent; } private static String createPackSummary(BedrockPack pack) { diff --git a/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java b/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java index adcc51f..7b36272 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java +++ b/client/src/main/java/org/geysermc/rainbow/client/command/PackGeneratorCommand.java @@ -113,14 +113,13 @@ public class PackGeneratorCommand { .then(ClientCommandManager.literal("finish") .executes(context -> { Optional exportPath = packManager.getExportPath(); - packManager.finish().ifPresentOrElse(success -> { - if (!success) { - context.getSource().sendError(Component.translatable("commands.rainbow.pack_finished_error")); - } else { - context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_finished_successfully") - .withStyle(style -> style.withUnderlined(true).withClickEvent(new ClickEvent.OpenFile(exportPath.orElseThrow())))); - } - }, () -> context.getSource().sendError(NO_PACK_CREATED)); + if (packManager.finish()) { + // TODO error when exporting fails + context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_finished_successfully") + .withStyle(style -> style.withUnderlined(true).withClickEvent(new ClickEvent.OpenFile(exportPath.orElseThrow())))); + } else { + context.getSource().sendError(NO_PACK_CREATED); + } return 0; }) ) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java index b949268..53ee42b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java @@ -3,13 +3,10 @@ package org.geysermc.rainbow.mapping; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.client.resources.model.ResolvedModel; -import net.minecraft.core.HolderLookup; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.equipment.EquipmentAsset; -import java.io.IOException; -import java.io.InputStream; import java.util.Optional; public interface AssetResolver { @@ -19,8 +16,4 @@ public interface AssetResolver { Optional getClientItem(ResourceLocation location); Optional getEquipmentInfo(ResourceKey key); - - InputStream getTexture(ResourceLocation location) throws IOException; - - HolderLookup.Provider registries(); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java new file mode 100644 index 0000000..5f74525 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java @@ -0,0 +1,14 @@ +package org.geysermc.rainbow.mapping; + +import com.mojang.serialization.Codec; +import net.minecraft.resources.ResourceLocation; + +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; + +public interface PackSerializer { + + CompletableFuture saveJson(Codec codec, T object, Path path); + + CompletableFuture saveTexture(ResourceLocation texture, Path path); +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index f2403cc..929260a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -2,26 +2,27 @@ package org.geysermc.rainbow.pack; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.animation.BedrockAnimation; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; -import java.io.IOException; import java.nio.file.Path; +import java.util.List; import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Stream; public record BedrockItem(ResourceLocation identifier, String textureName, ResourceLocation texture, boolean exportTexture, Optional attachable, Optional geometry, Optional animation) { - public void save(Path attachableDirectory, Path geometryDirectory, Path animationDirectory) throws IOException { - if (attachable.isPresent()) { - attachable.get().save(attachableDirectory); - } - if (geometry.isPresent()) { - geometry.get().save(geometryDirectory); - } - if (animation.isPresent()) { - animation.get().save(animationDirectory, Rainbow.fileSafeResourceLocation(identifier)); - } + public List> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory) { + return Stream.concat( + attachable.stream().map(present -> present.save(serializer, attachableDirectory)), + Stream.concat( + geometry.stream().map(present -> present.save(serializer, geometryDirectory)), + animation.stream().map(present -> present.save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))) + ) + ).toList(); } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index d2c2a9f..51f6d0a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -1,44 +1,42 @@ package org.geysermc.rainbow.pack; -import com.mojang.serialization.JsonOps; import it.unimi.dsi.fastutil.ints.IntOpenHashSet; import it.unimi.dsi.fastutil.ints.IntSet; import net.minecraft.core.Holder; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.core.component.DataComponents; -import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ProblemReporter; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.CustomModelData; -import org.apache.commons.io.IOUtils; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; +import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; import org.jetbrains.annotations.NotNull; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.UnaryOperator; public class BedrockPack { private final String name; private final PackPaths paths; + private final PackSerializer serializer; private final BedrockTextures.Builder itemTextures = BedrockTextures.builder(); private final Set bedrockItems = new HashSet<>(); @@ -50,11 +48,12 @@ public class BedrockPack { private final PackManifest manifest; private final ProblemReporter.Collector reporter; - public BedrockPack(String name, PackPaths paths, AssetResolver assetResolver, + public BedrockPack(String name, PackPaths paths, PackSerializer serializer, AssetResolver assetResolver, GeometryRenderer geometryRenderer, ProblemReporter.Collector reporter, boolean reportSuccesses) { this.name = name; this.paths = paths; + this.serializer = serializer; // Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet this.context = new PackContext(new GeyserMappings(), paths, item -> { @@ -121,50 +120,29 @@ public class BedrockPack { return map(stack); } - public boolean save() { - boolean success = true; + public CompletableFuture save() { + List> futures = new ArrayList<>(); - try { - CodecUtil.trySaveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings(), RegistryOps.create(JsonOps.INSTANCE, context.assetResolver().registries())); - CodecUtil.trySaveJson(PackManifest.CODEC, manifest, paths.manifest()); - CodecUtil.trySaveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas()); - } catch (IOException | NullPointerException exception) { - reporter.forChild(() -> "saving Geyser mappings, pack manifest, and texture atlas ").report(() -> "failed to save to pack: " + exception); - success = false; - } + futures.add(serializer.saveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings())); + futures.add(serializer.saveJson(PackManifest.CODEC, manifest, paths.manifest())); + futures.add(serializer.saveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas())); for (BedrockItem item : bedrockItems) { - try { - item.save(paths.attachables(), paths.geometry(), paths.animation()); - } catch (IOException exception) { - reporter.forChild(() -> "files for bedrock item " + item.identifier() + " ").report(() -> "failed to save to pack: " + exception); - success = false; - } + futures.addAll(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation())); } for (ResourceLocation texture : texturesToExport) { - texture = texture.withPath(path -> "textures/" + path + ".png"); - try (InputStream inputTexture = context.assetResolver().getTexture(texture)) { - Path texturePath = paths.packRoot().resolve(texture.getPath()); - CodecUtil.ensureDirectoryExists(texturePath.getParent()); - try (OutputStream outputTexture = new FileOutputStream(texturePath.toFile())) { - IOUtils.copy(inputTexture, outputTexture); - } - } catch (IOException exception) { - ResourceLocation finalTexture = texture; - reporter.forChild(() -> "texture " + finalTexture + " ").report(() -> "failed to save to pack: " + exception); - success = false; - } + futures.add(serializer.saveTexture(texture, paths.packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"))); } if (paths.zipOutput().isPresent()) { try { CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get()); } catch (IOException exception) { - success = false; + // TODO log } } - return success; + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); } public int getMappings() { @@ -192,8 +170,8 @@ public class BedrockPack { List.of(new PackManifest.Module(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0)))); } - public static Builder builder(String name, Path mappingsPath, Path packRootPath, AssetResolver assetResolver) { - return new Builder(name, mappingsPath, packRootPath, assetResolver); + public static Builder builder(String name, Path mappingsPath, Path packRootPath, PackSerializer packSerializer, AssetResolver assetResolver) { + return new Builder(name, mappingsPath, packRootPath, packSerializer, assetResolver); } public static class Builder { @@ -201,12 +179,13 @@ public class BedrockPack { private static final Path GEOMETRY_DIRECTORY = Path.of("models/entity"); private static final Path ANIMATION_DIRECTORY = Path.of("animations"); - private static final Path MANIFEST_FILE = Path.of("manifest.json"); - private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture.json"); + private static final Path MANIFEST_FILE = Path.of("manifest"); + private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture"); private final String name; private final Path mappingsPath; private final Path packRootPath; + private final PackSerializer packSerializer; private final AssetResolver assetResolver; private UnaryOperator attachablesPath = resolve(ATTACHABLES_DIRECTORY); private UnaryOperator geometryPath = resolve(GEOMETRY_DIRECTORY); @@ -218,11 +197,12 @@ public class BedrockPack { private ProblemReporter.Collector reporter; private boolean reportSuccesses = false; - public Builder(String name, Path mappingsPath, Path packRootPath, AssetResolver assetResolver) { + public Builder(String name, Path mappingsPath, Path packRootPath, PackSerializer packSerializer, AssetResolver assetResolver) { this.name = name; this.mappingsPath = mappingsPath; this.packRootPath = packRootPath; this.reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " "); + this.packSerializer = packSerializer; this.assetResolver = assetResolver; } @@ -295,7 +275,7 @@ public class BedrockPack { PackPaths paths = new PackPaths(mappingsPath, packRootPath, attachablesPath.apply(packRootPath), geometryPath.apply(packRootPath), animationPath.apply(packRootPath), manifestPath.apply(packRootPath), itemAtlasPath.apply(packRootPath), Optional.ofNullable(packZipFile)); - return new BedrockPack(name, paths, assetResolver, geometryRenderer, reporter, reportSuccesses); + return new BedrockPack(name, paths, packSerializer, assetResolver, geometryRenderer, reporter, reportSuccesses); } private static UnaryOperator resolve(Path child) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java index dbdf1fc..9131dad 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java @@ -5,14 +5,15 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.codecs.RecordCodecBuilder; import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockVersion; import org.joml.Vector3fc; -import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; public record BedrockAnimation(BedrockVersion formatVersion, Map definitions) { public static final BedrockVersion FORMAT_VERSION = BedrockVersion.of(1, 8, 0); @@ -24,8 +25,8 @@ public record BedrockAnimation(BedrockVersion formatVersion, Map save(PackSerializer serializer, Path animationDirectory, String identifier) { + return serializer.saveJson(CODEC, this, animationDirectory.resolve(identifier + ".animation")); } public static Builder builder() { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java index a6e8b25..baf940f 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java @@ -9,15 +9,14 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import net.minecraft.world.entity.EquipmentSlot; -import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockTextures; import org.geysermc.rainbow.pack.BedrockVersion; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; import org.jetbrains.annotations.NotNull; -import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.EnumMap; @@ -25,6 +24,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo info) { @@ -35,9 +35,9 @@ public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo inf ).apply(instance, BedrockAttachable::new) ); - public void save(Path attachablesDirectory) throws IOException { + public CompletableFuture save(PackSerializer serializer, Path attachablesDirectory) { // Get a safe attachable path by using Geyser's way of getting icons - CodecUtil.trySaveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.fileSafeResourceLocation(info.identifier) + ".json")); + return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.fileSafeResourceLocation(info.identifier))); } public static Builder builder(ResourceLocation identifier) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java index 993abe0..665d445 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java @@ -5,12 +5,12 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.Direction; import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockVersion; import org.joml.Vector2fc; import org.joml.Vector3f; import org.joml.Vector3fc; -import java.io.IOException; import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; @@ -18,6 +18,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; public record BedrockGeometry(BedrockVersion formatVersion, List definitions) { public static final BedrockVersion FORMAT_VERSION = BedrockVersion.of(1, 21, 0); @@ -31,8 +32,8 @@ public record BedrockGeometry(BedrockVersion formatVersion, List save(PackSerializer serializer, Path geometryDirectory) { + return serializer.saveJson(CODEC, this, geometryDirectory.resolve(definitions.getFirst().info.identifier + ".geo")); } public static BedrockGeometry of(GeometryDefinition... definitions) { From 269a18b9dfa29e00571528a3b0e05d35fcc9165e Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 11:09:16 +0000 Subject: [PATCH 12/37] Allow custom pack manifests in BedrockPack --- .../geysermc/rainbow/pack/BedrockPack.java | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 51f6d0a..54c9442 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -35,6 +35,7 @@ import java.util.function.UnaryOperator; public class BedrockPack { private final String name; + private final PackManifest manifest; private final PackPaths paths; private final PackSerializer serializer; @@ -45,13 +46,13 @@ public class BedrockPack { private final IntSet customModelDataMapped = new IntOpenHashSet(); private final PackContext context; - private final PackManifest manifest; private final ProblemReporter.Collector reporter; - public BedrockPack(String name, PackPaths paths, PackSerializer serializer, AssetResolver assetResolver, + public BedrockPack(String name, PackManifest manifest, PackPaths paths, PackSerializer serializer, AssetResolver assetResolver, GeometryRenderer geometryRenderer, ProblemReporter.Collector reporter, boolean reportSuccesses) { this.name = name; + this.manifest = manifest; this.paths = paths; this.serializer = serializer; @@ -63,7 +64,6 @@ public class BedrockPack { } bedrockItems.add(item); }, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses); - manifest = defaultManifest(name); this.reporter = reporter; } @@ -165,11 +165,6 @@ public class BedrockPack { return reporter; } - private static PackManifest defaultManifest(String name) { - return new PackManifest(new PackManifest.Header(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0), PackConstants.ENGINE_VERSION), - List.of(new PackManifest.Module(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0)))); - } - public static Builder builder(String name, Path mappingsPath, Path packRootPath, PackSerializer packSerializer, AssetResolver assetResolver) { return new Builder(name, mappingsPath, packRootPath, packSerializer, assetResolver); } @@ -187,6 +182,7 @@ public class BedrockPack { private final Path packRootPath; private final PackSerializer packSerializer; private final AssetResolver assetResolver; + private PackManifest manifest; private UnaryOperator attachablesPath = resolve(ATTACHABLES_DIRECTORY); private UnaryOperator geometryPath = resolve(GEOMETRY_DIRECTORY); private UnaryOperator animationPath = resolve(ANIMATION_DIRECTORY); @@ -204,6 +200,12 @@ public class BedrockPack { this.reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " "); this.packSerializer = packSerializer; this.assetResolver = assetResolver; + manifest = defaultManifest(name); + } + + public Builder withManifest(PackManifest manifest) { + this.manifest = manifest; + return this; } public Builder withAttachablesPath(Path absolute) { @@ -275,12 +277,17 @@ public class BedrockPack { PackPaths paths = new PackPaths(mappingsPath, packRootPath, attachablesPath.apply(packRootPath), geometryPath.apply(packRootPath), animationPath.apply(packRootPath), manifestPath.apply(packRootPath), itemAtlasPath.apply(packRootPath), Optional.ofNullable(packZipFile)); - return new BedrockPack(name, paths, packSerializer, assetResolver, geometryRenderer, reporter, reportSuccesses); + return new BedrockPack(name, manifest, paths, packSerializer, assetResolver, geometryRenderer, reporter, reportSuccesses); } private static UnaryOperator resolve(Path child) { return root -> root.resolve(child); } + + private static PackManifest defaultManifest(String name) { + return new PackManifest(new PackManifest.Header(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0), PackConstants.ENGINE_VERSION), + List.of(new PackManifest.Module(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0)))); + } } public enum MappingResult { From e9e07016acd0e0f9d6b86bc80fa286ebb697547f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 11:53:47 +0000 Subject: [PATCH 13/37] Work on datagen module --- client/build.gradle.kts | 2 +- .../main/resources/rainbow-client.mixins.json | 1 - datagen/build.gradle.kts | 9 ++ .../rainbow/datagen/RainbowModelProvider.java | 113 ++++++++++++++++++ .../mixin/ItemInfoCollectorAccessor.java | 15 +++ .../datagen/mixin/ModelProviderMixin.java | 31 +++++ .../mixin/SimpleModelCollectorAccessor.java | 15 +++ datagen/src/main/resources/fabric.mod.json | 30 +++++ .../resources/rainbow-datagen.mixins.json | 14 +++ rainbow/src/main/resources/fabric.mod.json | 1 - .../src/main/resources/rainbow.mixins.json | 1 - settings.gradle.kts | 1 + 12 files changed, 229 insertions(+), 4 deletions(-) create mode 100644 datagen/build.gradle.kts create mode 100644 datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java create mode 100644 datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java create mode 100644 datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java create mode 100644 datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java create mode 100644 datagen/src/main/resources/fabric.mod.json create mode 100644 datagen/src/main/resources/rainbow-datagen.mixins.json diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 7ad5470..4b8e0ef 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } dependencies { - // Implement namedElements so IntelliJ can use it correctly, but include the remapped build + // Implement namedElements so IDEs can use it correctly, but include the remapped build implementation(project(path = ":rainbow", configuration = "namedElements")) include(project(":rainbow")) } diff --git a/client/src/main/resources/rainbow-client.mixins.json b/client/src/main/resources/rainbow-client.mixins.json index bc5f880..a55d09a 100644 --- a/client/src/main/resources/rainbow-client.mixins.json +++ b/client/src/main/resources/rainbow-client.mixins.json @@ -3,7 +3,6 @@ "minVersion": "0.8", "package": "org.geysermc.rainbow.client.mixin", "compatibilityLevel": "JAVA_21", - "mixins": [], "client": [ "EntityRenderDispatcherAccessor", "GuiItemRenderStateMixin", diff --git a/datagen/build.gradle.kts b/datagen/build.gradle.kts new file mode 100644 index 0000000..4b8e0ef --- /dev/null +++ b/datagen/build.gradle.kts @@ -0,0 +1,9 @@ +plugins { + id("rainbow.base-conventions") +} + +dependencies { + // Implement namedElements so IDEs can use it correctly, but include the remapped build + implementation(project(path = ":rainbow", configuration = "namedElements")) + include(project(":rainbow")) +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java new file mode 100644 index 0000000..a5cc5f4 --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -0,0 +1,113 @@ +package org.geysermc.rainbow.datagen; + +import com.mojang.serialization.Codec; +import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider; +import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.minecraft.client.data.models.model.ModelInstance; +import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.client.resources.model.EquipmentClientInfo; +import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataProvider; +import net.minecraft.data.PackOutput; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.equipment.EquipmentAsset; +import org.geysermc.rainbow.mapping.AssetResolver; +import org.geysermc.rainbow.mapping.PackSerializer; +import org.geysermc.rainbow.pack.BedrockPack; +import org.jetbrains.annotations.NotNull; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +public abstract class RainbowModelProvider extends FabricModelProvider { + private final HolderLookup.Provider registries; + private final PackOutput.PathProvider bedrockPackPathProvider; + private Map itemInfosMap; + private Map models; + + public RainbowModelProvider(FabricDataOutput output, HolderLookup.Provider registries) { + super(output); + this.registries = registries; + bedrockPackPathProvider = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "bedrock"); + } + + @Override + public @NotNull CompletableFuture run(CachedOutput output) { + CompletableFuture vanillaModels = super.run(output); + + BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), + new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(itemInfosMap)).build(); + + for (Item item : itemInfosMap.keySet()) { + pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); + } + + return CompletableFuture.allOf(vanillaModels, pack.save()); + } + + protected abstract Item getVanillaItem(Item modded); + + protected DataComponentPatch getVanillaDataComponentPatch(Item modded) { + DataComponentPatch.Builder builder = DataComponentPatch.builder(); + modded.components().forEach(builder::set); + return builder.build(); + } + + public void setItemInfosMap(Map itemInfosMap) { + this.itemInfosMap = itemInfosMap; + } + + public void setModels(Map models) { + this.models = models; + } + + private record Serializer(CachedOutput output, HolderLookup.Provider registries, PackOutput.PathProvider provider) implements PackSerializer { + + @Override + public CompletableFuture saveJson(Codec codec, T object, Path path) { + ResourceLocation location = ResourceLocation.withDefaultNamespace(path.toString()); + return DataProvider.saveStable(output, registries, codec, object, provider.json(location)); + } + + @Override + public CompletableFuture saveTexture(ResourceLocation texture, Path path) { + return CompletableFuture.completedFuture(null); + } + } + + private static class ModelResolver implements AssetResolver { + private final Map itemInfosMap; + private final Map models; + + private ModelResolver(Map itemInfosMap, Map models) { + this.itemInfosMap = new HashMap<>(); + for (Map.Entry entry : itemInfosMap.entrySet()) { + this.itemInfosMap.put(entry.getKey().builtInRegistryHolder().key().location(), entry.getValue()); + } + this.models = models; + } + + @Override + public Optional getResolvedModel(ResourceLocation location) { + return Optional.ofNullable(models.get(location)); + } + + @Override + public Optional getClientItem(ResourceLocation location) { + return Optional.ofNullable(itemInfosMap.get(location)); + } + + @Override + public Optional getEquipmentInfo(ResourceKey key) { + return Optional.empty(); // TODO + } + } +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java new file mode 100644 index 0000000..2afb1ff --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java @@ -0,0 +1,15 @@ +package org.geysermc.rainbow.datagen.mixin; + +import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.world.item.Item; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(targets = "net.minecraft.client.data.models.ModelProvider$ItemInfoCollector") +public interface ItemInfoCollectorAccessor { + + @Accessor + Map getItemInfos(); +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java new file mode 100644 index 0000000..2cf9664 --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java @@ -0,0 +1,31 @@ +package org.geysermc.rainbow.datagen.mixin; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import net.minecraft.client.data.models.ModelProvider; +import net.minecraft.data.DataProvider; +import org.geysermc.rainbow.datagen.RainbowModelProvider; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +@Mixin(ModelProvider.class) +public abstract class ModelProviderMixin implements DataProvider { + + @WrapOperation(method = "run", at = @At(value = "NEW", target = "Lnet/minecraft/client/data/models/ModelProvider$ItemInfoCollector;")) + public Object setItemInfosInRainbowModelProvider(Operation original) { + Object itemInfoCollector = original.call(); + if ((Object) this instanceof RainbowModelProvider rainbowModelProvider) { + rainbowModelProvider.setItemInfosMap(((ItemInfoCollectorAccessor) itemInfoCollector).getItemInfos()); + } + return itemInfoCollector; + } + + @WrapOperation(method = "run", at = @At(value = "NEW", target = "Lnet/minecraft/client/data/models/ModelProvider$SimpleModelCollector;")) + public Object setModelsInRainbowModelProvider(Operation original) { + Object simpleModelCollector = original.call(); + if ((Object) this instanceof RainbowModelProvider rainbowModelProvider) { + rainbowModelProvider.setModels(((SimpleModelCollectorAccessor) simpleModelCollector).getModels()); + } + return simpleModelCollector; + } +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java new file mode 100644 index 0000000..e21983c --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java @@ -0,0 +1,15 @@ +package org.geysermc.rainbow.datagen.mixin; + +import net.minecraft.client.data.models.model.ModelInstance; +import net.minecraft.resources.ResourceLocation; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.Map; + +@Mixin(targets = "net.minecraft.client.data.models.ModelProvider$SimpleModelCollector") +public interface SimpleModelCollectorAccessor { + + @Accessor + Map getModels(); +} diff --git a/datagen/src/main/resources/fabric.mod.json b/datagen/src/main/resources/fabric.mod.json new file mode 100644 index 0000000..ff57f94 --- /dev/null +++ b/datagen/src/main/resources/fabric.mod.json @@ -0,0 +1,30 @@ +{ + "schemaVersion": 1, + "id": "rainbow-datagen", + "version": "${version}", + "name": "Rainbow", + "description": "Rainbow is a mod to generate Geyser item mappings and bedrock resourcepacks for use with Geyser's custom item API (v2)", + "authors": [ + "GeyserMC contributors" + ], + "contact": { + "homepage": "https://github.com/GeyserMC/rainbow", + "issues": "https://github.com/GeyserMC/rainbow/issues", + "sources": "https://github.com/GeyserMC/rainbow" + }, + "license": "MIT", + "environment": "client", + "mixins": [], + "depends": { + "fabricloader": ">=${loader_version}", + "fabric-api": "*", + "minecraft": "${supported_versions}" + }, + "custom": { + "modmenu": { + "links": { + "modmenu.discord": "https://discord.gg/GeyserMC" + } + } + } +} diff --git a/datagen/src/main/resources/rainbow-datagen.mixins.json b/datagen/src/main/resources/rainbow-datagen.mixins.json new file mode 100644 index 0000000..574147d --- /dev/null +++ b/datagen/src/main/resources/rainbow-datagen.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "org.geysermc.rainbow.datagen.mixin", + "compatibilityLevel": "JAVA_21", + "client": [ + "ItemInfoCollectorAccessor", + "ModelProviderMixin", + "SimpleModelCollectorAccessor" + ], + "injectors": { + "defaultRequire": 1 + } +} diff --git a/rainbow/src/main/resources/fabric.mod.json b/rainbow/src/main/resources/fabric.mod.json index 1677e20..a672300 100644 --- a/rainbow/src/main/resources/fabric.mod.json +++ b/rainbow/src/main/resources/fabric.mod.json @@ -13,7 +13,6 @@ "sources": "https://github.com/GeyserMC/rainbow" }, "license": "MIT", - "icon": "assets/rainbow/icon.png", "environment": "client", "mixins": [ "rainbow.mixins.json" diff --git a/rainbow/src/main/resources/rainbow.mixins.json b/rainbow/src/main/resources/rainbow.mixins.json index 887a686..5e7f32d 100644 --- a/rainbow/src/main/resources/rainbow.mixins.json +++ b/rainbow/src/main/resources/rainbow.mixins.json @@ -3,7 +3,6 @@ "minVersion": "0.8", "package": "org.geysermc.rainbow.mixin", "compatibilityLevel": "JAVA_21", - "mixins": [], "client": [ "LateBoundIdMapperAccessor", "RangeSelectItemModelAccessor", diff --git a/settings.gradle.kts b/settings.gradle.kts index bc119e5..f44dcdb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,5 +11,6 @@ pluginManagement { include(":rainbow") include(":client") +include(":datagen") rootProject.name = "rainbow-parent" From 20ad28e81f6d8a8e25140ef84adc91d1e05036e8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 12:13:55 +0000 Subject: [PATCH 14/37] Fix models in datagen --- .../rainbow/datagen/RainbowModelProvider.java | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index a5cc5f4..8b6a938 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -4,9 +4,11 @@ import com.mojang.serialization.Codec; import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.minecraft.client.data.models.model.ModelInstance; +import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.core.HolderLookup; import net.minecraft.core.component.DataComponentPatch; import net.minecraft.data.CachedOutput; @@ -20,7 +22,9 @@ import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockPack; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import java.io.StringReader; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -44,7 +48,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { CompletableFuture vanillaModels = super.run(output); BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), - new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(itemInfosMap)).build(); + new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(itemInfosMap, models)).build(); for (Item item : itemInfosMap.keySet()) { pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); @@ -81,7 +85,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public CompletableFuture saveTexture(ResourceLocation texture, Path path) { return CompletableFuture.completedFuture(null); } - } + } private static class ModelResolver implements AssetResolver { private final Map itemInfosMap; @@ -97,7 +101,24 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public Optional getResolvedModel(ResourceLocation location) { - return Optional.ofNullable(models.get(location)); + return Optional.ofNullable(models.get(location)) + .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) + .map(model -> new ResolvedModel() { + @Override + public @NotNull UnbakedModel wrapped() { + return model; + } + + @Override + public @Nullable ResolvedModel parent() { + return null; + } + + @Override + public @NotNull String debugName() { + return location.toString(); + } + }); // Not perfect since we're not resolving parents, not sure how to manage that } @Override From 9d67610095eb73cb43f4a1fbdd7c04c178b92001 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 12:46:55 +0000 Subject: [PATCH 15/37] Setup publishing --- .../rainbow.base-conventions.gradle.kts | 4 +++ .../rainbow.publish-conventions.gradle.kts | 27 +++++++++++++++++++ client/build.gradle.kts | 1 + client/gradle.properties | 1 + datagen/build.gradle.kts | 1 + datagen/gradle.properties | 1 + gradle.properties | 5 ++-- rainbow/build.gradle.kts | 1 + rainbow/gradle.properties | 1 + 9 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 client/gradle.properties create mode 100644 datagen/gradle.properties create mode 100644 rainbow/gradle.properties diff --git a/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts index ee2f1cb..8717da3 100644 --- a/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts @@ -8,6 +8,10 @@ group = properties["maven_group"]!! as String val archivesBaseName = properties["archives_base_name"]!! as String val targetJavaVersion = 21 +base { + archivesName = archivesBaseName +} + repositories { maven { name = "ParchmentMC" diff --git a/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts index e69de29..b72e89f 100644 --- a/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts @@ -0,0 +1,27 @@ +plugins { + `maven-publish` +} + +val archivesBaseName = properties["archives_base_name"]!! as String + +publishing { + repositories { + maven { + name = "eclipseisoffline" + url = uri( + when { + version.toString().endsWith("-SNAPSHOT") -> "https://maven.eclipseisoffline.xyz/snapshots" + else -> "https://maven.eclipseisoffline.xyz/releases" + } + ) + credentials(PasswordCredentials::class) + } + } + + publications { + register("publish", MavenPublication::class) { + artifactId = archivesBaseName + from(project.components["java"]) + } + } +} diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 4b8e0ef..7fa829d 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("rainbow.base-conventions") + id("rainbow.publish-conventions") } dependencies { diff --git a/client/gradle.properties b/client/gradle.properties new file mode 100644 index 0000000..3e65201 --- /dev/null +++ b/client/gradle.properties @@ -0,0 +1 @@ +archives_base_name=rainbow-client diff --git a/datagen/build.gradle.kts b/datagen/build.gradle.kts index 4b8e0ef..7fa829d 100644 --- a/datagen/build.gradle.kts +++ b/datagen/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("rainbow.base-conventions") + id("rainbow.publish-conventions") } dependencies { diff --git a/datagen/gradle.properties b/datagen/gradle.properties new file mode 100644 index 0000000..6ea0225 --- /dev/null +++ b/datagen/gradle.properties @@ -0,0 +1 @@ +archives_base_name=rainbow-datagen diff --git a/gradle.properties b/gradle.properties index 7831827..12d685f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,5 @@ org.gradle.jvmargs=-Xmx1G # Mod Properties -mod_version=0.1.0-1.21.10 -maven_group=org.geysermc -archives_base_name=rainbow +mod_version=0.2.0-1.21.10-SNAPSHOT +maven_group=org.geysermc.rainbow diff --git a/rainbow/build.gradle.kts b/rainbow/build.gradle.kts index def7c69..f89f209 100644 --- a/rainbow/build.gradle.kts +++ b/rainbow/build.gradle.kts @@ -1,3 +1,4 @@ plugins { id("rainbow.base-conventions") + id("rainbow.publish-conventions") } diff --git a/rainbow/gradle.properties b/rainbow/gradle.properties new file mode 100644 index 0000000..19ae406 --- /dev/null +++ b/rainbow/gradle.properties @@ -0,0 +1 @@ +archives_base_name=rainbow From 3907c2f087a0755d9c907f6633b4b0f25284a9d3 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 12:54:23 +0000 Subject: [PATCH 16/37] Accept a CompletableFuture for registries --- .../rainbow/datagen/RainbowModelProvider.java | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index 8b6a938..68389a4 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -32,12 +32,12 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; public abstract class RainbowModelProvider extends FabricModelProvider { - private final HolderLookup.Provider registries; + private final CompletableFuture registries; private final PackOutput.PathProvider bedrockPackPathProvider; private Map itemInfosMap; private Map models; - public RainbowModelProvider(FabricDataOutput output, HolderLookup.Provider registries) { + public RainbowModelProvider(FabricDataOutput output, CompletableFuture registries) { super(output); this.registries = registries; bedrockPackPathProvider = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "bedrock"); @@ -47,14 +47,17 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public @NotNull CompletableFuture run(CachedOutput output) { CompletableFuture vanillaModels = super.run(output); - BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), - new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(itemInfosMap, models)).build(); + CompletableFuture bedrockPack = registries.thenApply(registries -> { + BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), + new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(itemInfosMap, models)).build(); - for (Item item : itemInfosMap.keySet()) { - pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); - } + for (Item item : itemInfosMap.keySet()) { + pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); + } + return pack; + }); - return CompletableFuture.allOf(vanillaModels, pack.save()); + return CompletableFuture.allOf(vanillaModels, bedrockPack.thenCompose(BedrockPack::save)); } protected abstract Item getVanillaItem(Item modded); From 834b9addce886b107bf33f25d33da9c4c075e817 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 13:49:24 +0000 Subject: [PATCH 17/37] Fix some stuff --- datagen/build.gradle.kts | 4 ++++ .../rainbow/datagen/RainbowModelProvider.java | 1 + .../datagen/mixin/ModelProviderMixin.java | 23 ++++++++----------- datagen/src/main/resources/fabric.mod.json | 5 +++- .../resources/rainbow-datagen.accesswidener | 3 +++ .../GeyserRangeDispatchPredicate.java | 2 +- .../rainbow/mapping/BedrockItemMapper.java | 2 +- .../mapping/geometry/GeometryMapper.java | 13 ++++++++--- 8 files changed, 33 insertions(+), 20 deletions(-) create mode 100644 datagen/src/main/resources/rainbow-datagen.accesswidener diff --git a/datagen/build.gradle.kts b/datagen/build.gradle.kts index 7fa829d..715fcb3 100644 --- a/datagen/build.gradle.kts +++ b/datagen/build.gradle.kts @@ -8,3 +8,7 @@ dependencies { implementation(project(path = ":rainbow", configuration = "namedElements")) include(project(":rainbow")) } + +loom { + accessWidenerPath = file("src/main/resources/rainbow-datagen.accesswidener") +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index 68389a4..9524f48 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -81,6 +81,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { ResourceLocation location = ResourceLocation.withDefaultNamespace(path.toString()); + System.out.println("saving bedrock " + location); return DataProvider.saveStable(output, registries, codec, object, provider.json(location)); } diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java index 2cf9664..678b22f 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java @@ -1,31 +1,26 @@ package org.geysermc.rainbow.datagen.mixin; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; import net.minecraft.client.data.models.ModelProvider; +import net.minecraft.data.CachedOutput; import net.minecraft.data.DataProvider; import org.geysermc.rainbow.datagen.RainbowModelProvider; 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.CallbackInfoReturnable; + +import java.util.concurrent.CompletableFuture; @Mixin(ModelProvider.class) public abstract class ModelProviderMixin implements DataProvider { - @WrapOperation(method = "run", at = @At(value = "NEW", target = "Lnet/minecraft/client/data/models/ModelProvider$ItemInfoCollector;")) - public Object setItemInfosInRainbowModelProvider(Operation original) { - Object itemInfoCollector = original.call(); + @Inject(method = "run", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/data/models/BlockModelGenerators;run()V")) + public void setItemInfosInRainbowModelProvider(CachedOutput output, CallbackInfoReturnable> callbackInfoReturnable, + @Local ModelProvider.ItemInfoCollector itemInfoCollector, @Local ModelProvider.SimpleModelCollector simpleModelCollector) { if ((Object) this instanceof RainbowModelProvider rainbowModelProvider) { rainbowModelProvider.setItemInfosMap(((ItemInfoCollectorAccessor) itemInfoCollector).getItemInfos()); - } - return itemInfoCollector; - } - - @WrapOperation(method = "run", at = @At(value = "NEW", target = "Lnet/minecraft/client/data/models/ModelProvider$SimpleModelCollector;")) - public Object setModelsInRainbowModelProvider(Operation original) { - Object simpleModelCollector = original.call(); - if ((Object) this instanceof RainbowModelProvider rainbowModelProvider) { rainbowModelProvider.setModels(((SimpleModelCollectorAccessor) simpleModelCollector).getModels()); } - return simpleModelCollector; } } diff --git a/datagen/src/main/resources/fabric.mod.json b/datagen/src/main/resources/fabric.mod.json index ff57f94..95fa4bd 100644 --- a/datagen/src/main/resources/fabric.mod.json +++ b/datagen/src/main/resources/fabric.mod.json @@ -14,7 +14,10 @@ }, "license": "MIT", "environment": "client", - "mixins": [], + "mixins": [ + "rainbow-datagen.mixins.json" + ], + "accessWidener": "rainbow-datagen.accesswidener", "depends": { "fabricloader": ">=${loader_version}", "fabric-api": "*", diff --git a/datagen/src/main/resources/rainbow-datagen.accesswidener b/datagen/src/main/resources/rainbow-datagen.accesswidener new file mode 100644 index 0000000..5ab114f --- /dev/null +++ b/datagen/src/main/resources/rainbow-datagen.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named +accessible class net/minecraft/client/data/models/ModelProvider$ItemInfoCollector +accessible class net/minecraft/client/data/models/ModelProvider$SimpleModelCollector diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java index 537a3f7..d58c7bb 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserRangeDispatchPredicate.java @@ -24,7 +24,7 @@ public record GeyserRangeDispatchPredicate(Property property, float threshold, f @Override public Type type() { - return null; + return Type.RANGE_DISPATCH; } public interface Property { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 27c054d..cf94d06 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -277,7 +277,7 @@ public class BedrockItemMapper { String safeIdentifier = base.textureName(); String bone = "bone"; ResourceLocation geometryTexture = texture; - Optional bedrockGeometry = customModel.map(model -> GeometryMapper.mapGeometry(safeIdentifier, bone, model, geometryTexture)); + Optional bedrockGeometry = customModel.flatMap(model -> GeometryMapper.mapGeometry(safeIdentifier, bone, model, geometryTexture)); Optional bedrockAnimation = customModel.map(model -> AnimationMapper.mapAnimation(safeIdentifier, bone, model.getTopTransforms())); boolean exportTexture = true; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java index 58eed4f..550095b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java @@ -5,6 +5,7 @@ import net.minecraft.client.renderer.block.model.BlockElementFace; import net.minecraft.client.renderer.block.model.BlockElementRotation; import net.minecraft.client.renderer.block.model.SimpleUnbakedGeometry; import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.UnbakedGeometry; import net.minecraft.core.Direction; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; @@ -13,11 +14,17 @@ import org.joml.Vector3f; import org.joml.Vector3fc; import java.util.Map; +import java.util.Optional; public class GeometryMapper { private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F); - public static BedrockGeometryContext mapGeometry(String identifier, String boneName, ResolvedModel model, ResourceLocation texture) { + public static Optional mapGeometry(String identifier, String boneName, ResolvedModel model, ResourceLocation texture) { + UnbakedGeometry top = model.getTopGeometry(); + if (top == UnbakedGeometry.EMPTY) { + return Optional.empty(); + } + BedrockGeometry.Builder builder = BedrockGeometry.builder(identifier); // Blockbench seems to always use these values TODO that's wrong builder.withVisibleBoundsWidth(4.0F); @@ -33,7 +40,7 @@ public class GeometryMapper { Vector3f min = new Vector3f(Float.MAX_VALUE); Vector3f max = new Vector3f(Float.MIN_VALUE); - SimpleUnbakedGeometry geometry = (SimpleUnbakedGeometry) model.getTopGeometry(); + SimpleUnbakedGeometry geometry = (SimpleUnbakedGeometry) top; for (BlockElement element : geometry.elements()) { // TODO the origin here is wrong, some models seem to be mirrored weirdly in blockbench BedrockGeometry.Cube cube = mapBlockElement(element).build(); @@ -48,7 +55,7 @@ public class GeometryMapper { // Bind to the bone of the current item slot bone.withBinding("q.item_slot_to_bone_name(context.item_slot)"); - return new BedrockGeometryContext(builder.withBone(bone).build(), texture); + return Optional.of(new BedrockGeometryContext(builder.withBone(bone).build(), texture)); } private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element) { From 6ae5358f730840fa4f442d66091ba69ef4b81835 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 13:50:33 +0000 Subject: [PATCH 18/37] Fix range dispatch predicate NPE --- README.md | 2 +- .../mapping/geyser/predicate/GeyserRangeDispatchPredicate.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f3998ce..4d0297e 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Discord](https://img.shields.io/discord/613163671870242838.svg?color=%237289da&label=discord)](https://discord.gg/geysermc) Rainbow is a client-side Minecraft mod for the Fabric modloader to generate Geyser item mappings and bedrock resourcepacks -for use with Geyser's [custom item API (v2)](https://github.com/geyserMC/geyser/pull/5189). Rainbow is available for Minecraft 1.21.7 and 1.21.8. +for use with Geyser's [custom item API (v2)](https://github.com/geyserMC/geyser/pull/5189). Rainbow is available for Minecraft 1.21.9 and 1.21.10. Rainbow is currently experimental and capable of the following: diff --git a/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java b/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java index 67f3643..4b3cc1c 100644 --- a/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java +++ b/src/main/java/org/geysermc/rainbow/mapping/geyser/predicate/GeyserRangeDispatchPredicate.java @@ -24,7 +24,7 @@ public record GeyserRangeDispatchPredicate(Property property, float threshold, f @Override public Type type() { - return null; + return Type.RANGE_DISPATCH; } public interface Property { From b19c319a18033a07a1425a4eab93e5723c51ebff Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 17:04:17 +0000 Subject: [PATCH 19/37] Perform some cursed magic to load client assets during datagen --- .../rainbow/datagen/ClientPackLoader.java | 67 +++++++++++++++++++ .../rainbow/datagen/RainbowModelProvider.java | 40 +++++++---- 2 files changed, 94 insertions(+), 13 deletions(-) create mode 100644 datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java new file mode 100644 index 0000000..2b62fa0 --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java @@ -0,0 +1,67 @@ +package org.geysermc.rainbow.datagen; + +import joptsimple.ArgumentAcceptingOptionSpec; +import joptsimple.OptionParser; +import joptsimple.OptionSet; +import joptsimple.OptionSpec; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.client.resources.ClientPackSource; +import net.minecraft.client.resources.IndexedAssetSource; +import net.minecraft.server.packs.PackType; +import net.minecraft.server.packs.repository.Pack; +import net.minecraft.server.packs.repository.PackRepository; +import net.minecraft.server.packs.resources.CloseableResourceManager; +import net.minecraft.server.packs.resources.MultiPackResourceManager; +import net.minecraft.world.level.validation.DirectoryValidator; +import org.jetbrains.annotations.Nullable; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +class ClientPackLoader { + + static ClientPackSource loadClientPackSource() { + OptionParser parser = new OptionParser(); + OptionSpec assetsDirSpec = parser.accepts("assetsDir").withRequiredArg().ofType(File.class); + OptionSpec assetIndexSpec = parser.accepts("assetIndex").withRequiredArg(); + parser.allowsUnrecognizedOptions(); + OptionSet parsed = parser.parse(FabricLoader.getInstance().getLaunchArguments(false)); + + return new ClientPackSource(getExternalAssetSource(parseArgument(parsed, assetIndexSpec), parseArgument(parsed, assetsDirSpec)), + new DirectoryValidator(path -> false)); + } + + static CompletableFuture openClientResources() { + return CompletableFuture.supplyAsync(() -> { + ClientPackSource packSource = loadClientPackSource(); + PackRepository repository = new PackRepository(packSource); + repository.reload(); + return new MultiPackResourceManager(PackType.CLIENT_RESOURCES, repository.getAvailablePacks().stream() + .map(Pack::open) + .toList()); + }); + } + + private static Path getExternalAssetSource(String assetIndex, File assetDirectory) { + return assetIndex == null ? assetDirectory.toPath() : IndexedAssetSource.createIndexFs(assetDirectory.toPath(), assetIndex); + } + + // From Mojang's client/Main.java + @Nullable + private static T parseArgument(OptionSet set, OptionSpec spec) { + try { + return set.valueOf(spec); + } catch (Throwable exception) { + if (spec instanceof ArgumentAcceptingOptionSpec argumentAccepting) { + List list = argumentAccepting.defaultValues(); + if (!list.isEmpty()) { + return list.getFirst(); + } + } + + throw exception; + } + } +} diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index 9524f48..b0925df 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -16,6 +16,7 @@ import net.minecraft.data.DataProvider; import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.item.Item; import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.mapping.AssetResolver; @@ -24,6 +25,8 @@ import org.geysermc.rainbow.pack.BedrockPack; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.BufferedReader; +import java.io.IOException; import java.io.StringReader; import java.nio.file.Path; import java.util.HashMap; @@ -47,15 +50,18 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public @NotNull CompletableFuture run(CachedOutput output) { CompletableFuture vanillaModels = super.run(output); - CompletableFuture bedrockPack = registries.thenApply(registries -> { - BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), - new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(itemInfosMap, models)).build(); + CompletableFuture bedrockPack = ClientPackLoader.openClientResources() + .thenCompose(resourceManager -> registries.thenApply(registries -> { + try (resourceManager) { + BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), + new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(resourceManager, itemInfosMap, models)).build(); - for (Item item : itemInfosMap.keySet()) { - pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); - } - return pack; - }); + for (Item item : itemInfosMap.keySet()) { + pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); + } + return pack; + } + })); return CompletableFuture.allOf(vanillaModels, bedrockPack.thenCompose(BedrockPack::save)); } @@ -81,7 +87,6 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { ResourceLocation location = ResourceLocation.withDefaultNamespace(path.toString()); - System.out.println("saving bedrock " + location); return DataProvider.saveStable(output, registries, codec, object, provider.json(location)); } @@ -92,10 +97,13 @@ public abstract class RainbowModelProvider extends FabricModelProvider { } private static class ModelResolver implements AssetResolver { + private final ResourceManager resourceManager; private final Map itemInfosMap; private final Map models; + private final Map> resolvedModelCache = new HashMap<>(); - private ModelResolver(Map itemInfosMap, Map models) { + private ModelResolver(ResourceManager resourceManager, Map itemInfosMap, Map models) { + this.resourceManager = resourceManager; this.itemInfosMap = new HashMap<>(); for (Map.Entry entry : itemInfosMap.entrySet()) { this.itemInfosMap.put(entry.getKey().builtInRegistryHolder().key().location(), entry.getValue()); @@ -105,8 +113,14 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public Optional getResolvedModel(ResourceLocation location) { - return Optional.ofNullable(models.get(location)) + return resolvedModelCache.computeIfAbsent(location, key -> Optional.ofNullable(models.get(location)) .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) + .or(() -> { + try (BufferedReader reader = resourceManager.openAsReader(location.withPrefix("models/").withSuffix(".json"))) { + return Optional.of(BlockModel.fromStream(reader)); + } catch (IOException ignored) {} + return Optional.empty(); + }) .map(model -> new ResolvedModel() { @Override public @NotNull UnbakedModel wrapped() { @@ -115,14 +129,14 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public @Nullable ResolvedModel parent() { - return null; + return Optional.ofNullable(model.parent()).flatMap(parent -> getResolvedModel(parent)).orElse(null); } @Override public @NotNull String debugName() { return location.toString(); } - }); // Not perfect since we're not resolving parents, not sure how to manage that + })); } @Override From c4653f5072f79b3b83e0b90b22e52ec64142077f Mon Sep 17 00:00:00 2001 From: Eclipse Date: Tue, 14 Oct 2025 17:34:39 +0000 Subject: [PATCH 20/37] Some tweaks, add equipment infos param to datagen provider --- datagen/build.gradle.kts | 2 - .../rainbow/datagen/RainbowModelProvider.java | 47 +++++++++++++------ .../mixin/ItemInfoCollectorAccessor.java | 3 +- .../datagen/mixin/ModelProviderMixin.java | 2 +- .../mixin/SimpleModelCollectorAccessor.java | 3 +- .../resources/rainbow-datagen.accesswidener | 4 +- 6 files changed, 39 insertions(+), 22 deletions(-) diff --git a/datagen/build.gradle.kts b/datagen/build.gradle.kts index 715fcb3..d0fae2f 100644 --- a/datagen/build.gradle.kts +++ b/datagen/build.gradle.kts @@ -4,9 +4,7 @@ plugins { } dependencies { - // Implement namedElements so IDEs can use it correctly, but include the remapped build implementation(project(path = ":rainbow", configuration = "namedElements")) - include(project(":rainbow")) } loom { diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index b0925df..9477500 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -22,6 +22,7 @@ import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockPack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,15 +38,22 @@ import java.util.concurrent.CompletableFuture; public abstract class RainbowModelProvider extends FabricModelProvider { private final CompletableFuture registries; private final PackOutput.PathProvider bedrockPackPathProvider; - private Map itemInfosMap; + private final Map, EquipmentClientInfo> equipmentInfos; + private Map itemInfos; private Map models; - public RainbowModelProvider(FabricDataOutput output, CompletableFuture registries) { + public RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, + Map, EquipmentClientInfo> equipmentInfos) { super(output); this.registries = registries; + this.equipmentInfos = equipmentInfos; bedrockPackPathProvider = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "bedrock"); } + public RainbowModelProvider(FabricDataOutput output, CompletableFuture registries) { + this(output, registries, Map.of()); + } + @Override public @NotNull CompletableFuture run(CachedOutput output) { CompletableFuture vanillaModels = super.run(output); @@ -53,10 +61,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider { CompletableFuture bedrockPack = ClientPackLoader.openClientResources() .thenCompose(resourceManager -> registries.thenApply(registries -> { try (resourceManager) { - BedrockPack pack = BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), - new Serializer(output, registries, bedrockPackPathProvider), new ModelResolver(resourceManager, itemInfosMap, models)).build(); + BedrockPack pack = createBedrockPack(new Serializer(output, registries, bedrockPackPathProvider), + new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build(); - for (Item item : itemInfosMap.keySet()) { + for (Item item : itemInfos.keySet()) { pack.map(getVanillaItem(item).builtInRegistryHolder(), getVanillaDataComponentPatch(item)); } return pack; @@ -66,6 +74,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider { return CompletableFuture.allOf(vanillaModels, bedrockPack.thenCompose(BedrockPack::save)); } + protected BedrockPack.Builder createBedrockPack(PackSerializer serializer, AssetResolver resolver) { + return BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), serializer, resolver); + } + protected abstract Item getVanillaItem(Item modded); protected DataComponentPatch getVanillaDataComponentPatch(Item modded) { @@ -74,10 +86,12 @@ public abstract class RainbowModelProvider extends FabricModelProvider { return builder.build(); } - public void setItemInfosMap(Map itemInfosMap) { - this.itemInfosMap = itemInfosMap; + @ApiStatus.Internal + public void setItemInfos(Map itemInfos) { + this.itemInfos = itemInfos; } + @ApiStatus.Internal public void setModels(Map models) { this.models = models; } @@ -96,17 +110,20 @@ public abstract class RainbowModelProvider extends FabricModelProvider { } } - private static class ModelResolver implements AssetResolver { + private static class DatagenResolver implements AssetResolver { private final ResourceManager resourceManager; - private final Map itemInfosMap; + private final Map, EquipmentClientInfo> equipmentInfos; + private final Map itemInfos; private final Map models; private final Map> resolvedModelCache = new HashMap<>(); - private ModelResolver(ResourceManager resourceManager, Map itemInfosMap, Map models) { + private DatagenResolver(ResourceManager resourceManager, Map, EquipmentClientInfo> equipmentInfos, + Map itemInfos, Map models) { this.resourceManager = resourceManager; - this.itemInfosMap = new HashMap<>(); - for (Map.Entry entry : itemInfosMap.entrySet()) { - this.itemInfosMap.put(entry.getKey().builtInRegistryHolder().key().location(), entry.getValue()); + this.equipmentInfos = equipmentInfos; + this.itemInfos = new HashMap<>(); + for (Map.Entry entry : itemInfos.entrySet()) { + this.itemInfos.put(entry.getKey().builtInRegistryHolder().key().location(), entry.getValue()); } this.models = models; } @@ -141,12 +158,12 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public Optional getClientItem(ResourceLocation location) { - return Optional.ofNullable(itemInfosMap.get(location)); + return Optional.ofNullable(itemInfos.get(location)); } @Override public Optional getEquipmentInfo(ResourceKey key) { - return Optional.empty(); // TODO + return Optional.ofNullable(equipmentInfos.get(key)); } } } diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java index 2afb1ff..b9450ae 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ItemInfoCollectorAccessor.java @@ -1,5 +1,6 @@ package org.geysermc.rainbow.datagen.mixin; +import net.minecraft.client.data.models.ModelProvider; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.world.item.Item; import org.spongepowered.asm.mixin.Mixin; @@ -7,7 +8,7 @@ import org.spongepowered.asm.mixin.gen.Accessor; import java.util.Map; -@Mixin(targets = "net.minecraft.client.data.models.ModelProvider$ItemInfoCollector") +@Mixin(ModelProvider.ItemInfoCollector.class) public interface ItemInfoCollectorAccessor { @Accessor diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java index 678b22f..69842c4 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/ModelProviderMixin.java @@ -19,7 +19,7 @@ public abstract class ModelProviderMixin implements DataProvider { public void setItemInfosInRainbowModelProvider(CachedOutput output, CallbackInfoReturnable> callbackInfoReturnable, @Local ModelProvider.ItemInfoCollector itemInfoCollector, @Local ModelProvider.SimpleModelCollector simpleModelCollector) { if ((Object) this instanceof RainbowModelProvider rainbowModelProvider) { - rainbowModelProvider.setItemInfosMap(((ItemInfoCollectorAccessor) itemInfoCollector).getItemInfos()); + rainbowModelProvider.setItemInfos(((ItemInfoCollectorAccessor) itemInfoCollector).getItemInfos()); rainbowModelProvider.setModels(((SimpleModelCollectorAccessor) simpleModelCollector).getModels()); } } diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java index e21983c..0f85752 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/SimpleModelCollectorAccessor.java @@ -1,5 +1,6 @@ package org.geysermc.rainbow.datagen.mixin; +import net.minecraft.client.data.models.ModelProvider; import net.minecraft.client.data.models.model.ModelInstance; import net.minecraft.resources.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; @@ -7,7 +8,7 @@ import org.spongepowered.asm.mixin.gen.Accessor; import java.util.Map; -@Mixin(targets = "net.minecraft.client.data.models.ModelProvider$SimpleModelCollector") +@Mixin(ModelProvider.SimpleModelCollector.class) public interface SimpleModelCollectorAccessor { @Accessor diff --git a/datagen/src/main/resources/rainbow-datagen.accesswidener b/datagen/src/main/resources/rainbow-datagen.accesswidener index 5ab114f..71454eb 100644 --- a/datagen/src/main/resources/rainbow-datagen.accesswidener +++ b/datagen/src/main/resources/rainbow-datagen.accesswidener @@ -1,3 +1,3 @@ accessWidener v2 named -accessible class net/minecraft/client/data/models/ModelProvider$ItemInfoCollector -accessible class net/minecraft/client/data/models/ModelProvider$SimpleModelCollector +transitive-accessible class net/minecraft/client/data/models/ModelProvider$ItemInfoCollector +transitive-accessible class net/minecraft/client/data/models/ModelProvider$SimpleModelCollector From 6af8203c26791d18bdf145dccb363a950a498504 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Wed, 15 Oct 2025 07:10:35 +0000 Subject: [PATCH 21/37] Allow using custom paths in datagen --- .../rainbow/datagen/RainbowModelProvider.java | 27 +++++++++++-------- .../rainbow/mapping/BedrockItemMapper.java | 1 + .../geysermc/rainbow/pack/BedrockPack.java | 4 +-- .../pack/animation/BedrockAnimation.java | 2 +- .../pack/attachable/BedrockAttachable.java | 2 +- .../pack/geometry/BedrockGeometry.java | 2 +- 6 files changed, 22 insertions(+), 16 deletions(-) diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index 9477500..bbed475 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -37,20 +37,26 @@ import java.util.concurrent.CompletableFuture; public abstract class RainbowModelProvider extends FabricModelProvider { private final CompletableFuture registries; - private final PackOutput.PathProvider bedrockPackPathProvider; private final Map, EquipmentClientInfo> equipmentInfos; + private final Path outputRoot; private Map itemInfos; private Map models; - public RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, - Map, EquipmentClientInfo> equipmentInfos) { + protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, + Map, EquipmentClientInfo> equipmentInfos, ResourceLocation outputRoot) { super(output); this.registries = registries; this.equipmentInfos = equipmentInfos; - bedrockPackPathProvider = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, "bedrock"); + this.outputRoot = output.createPathProvider(PackOutput.Target.RESOURCE_PACK, outputRoot.getPath()) + .file(outputRoot, "").getParent(); } - public RainbowModelProvider(FabricDataOutput output, CompletableFuture registries) { + protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, + Map, EquipmentClientInfo> equipmentInfos) { + this(output, registries, equipmentInfos, ResourceLocation.withDefaultNamespace("bedrock")); + } + + protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries) { this(output, registries, Map.of()); } @@ -61,7 +67,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { CompletableFuture bedrockPack = ClientPackLoader.openClientResources() .thenCompose(resourceManager -> registries.thenApply(registries -> { try (resourceManager) { - BedrockPack pack = createBedrockPack(new Serializer(output, registries, bedrockPackPathProvider), + BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, registries), new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build(); for (Item item : itemInfos.keySet()) { @@ -74,8 +80,8 @@ public abstract class RainbowModelProvider extends FabricModelProvider { return CompletableFuture.allOf(vanillaModels, bedrockPack.thenCompose(BedrockPack::save)); } - protected BedrockPack.Builder createBedrockPack(PackSerializer serializer, AssetResolver resolver) { - return BedrockPack.builder("rainbow", Path.of("geyser_mappings"), Path.of("pack"), serializer, resolver); + protected BedrockPack.Builder createBedrockPack(Path outputRoot, PackSerializer serializer, AssetResolver resolver) { + return BedrockPack.builder("rainbow", outputRoot.resolve("geyser_mappings.json"), outputRoot.resolve("pack"), serializer, resolver); } protected abstract Item getVanillaItem(Item modded); @@ -96,12 +102,11 @@ public abstract class RainbowModelProvider extends FabricModelProvider { this.models = models; } - private record Serializer(CachedOutput output, HolderLookup.Provider registries, PackOutput.PathProvider provider) implements PackSerializer { + private record Serializer(CachedOutput output, HolderLookup.Provider registries) implements PackSerializer { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { - ResourceLocation location = ResourceLocation.withDefaultNamespace(path.toString()); - return DataProvider.saveStable(output, registries, codec, object, provider.json(location)); + return DataProvider.saveStable(output, registries, codec, object, path); } @Override diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index cf94d06..72c1d7b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -81,6 +81,7 @@ public class BedrockItemMapper { } public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) { + // TODO Improve this, use resouce log in problemreporter ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(stack.get(DataComponents.ITEM_MODEL)).map(ClientItem::model).orElseThrow(); ProblemReporter childReporter = reporter.forChild(() -> "item model " + vanillaModel + " with custom model data " + customModelData + " "); if (vanillaModel instanceof RangeSelectItemModel.Unbaked(RangeSelectItemModelProperty property, float scale, List entries, Optional fallback)) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 54c9442..4252d29 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -174,8 +174,8 @@ public class BedrockPack { private static final Path GEOMETRY_DIRECTORY = Path.of("models/entity"); private static final Path ANIMATION_DIRECTORY = Path.of("animations"); - private static final Path MANIFEST_FILE = Path.of("manifest"); - private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture"); + private static final Path MANIFEST_FILE = Path.of("manifest.json"); + private static final Path ITEM_ATLAS_FILE = Path.of("textures/item_texture.json"); private final String name; private final Path mappingsPath; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java index 9131dad..263813e 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/BedrockAnimation.java @@ -26,7 +26,7 @@ public record BedrockAnimation(BedrockVersion formatVersion, Map save(PackSerializer serializer, Path animationDirectory, String identifier) { - return serializer.saveJson(CODEC, this, animationDirectory.resolve(identifier + ".animation")); + return serializer.saveJson(CODEC, this, animationDirectory.resolve(identifier + ".animation.json")); } public static Builder builder() { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java index baf940f..32b95d0 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java @@ -37,7 +37,7 @@ public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo inf public CompletableFuture save(PackSerializer serializer, Path attachablesDirectory) { // Get a safe attachable path by using Geyser's way of getting icons - return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.fileSafeResourceLocation(info.identifier))); + return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.fileSafeResourceLocation(info.identifier) + ".json")); } public static Builder builder(ResourceLocation identifier) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java index 665d445..29048fe 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java @@ -33,7 +33,7 @@ public record BedrockGeometry(BedrockVersion formatVersion, List save(PackSerializer serializer, Path geometryDirectory) { - return serializer.saveJson(CODEC, this, geometryDirectory.resolve(definitions.getFirst().info.identifier + ".geo")); + return serializer.saveJson(CODEC, this, geometryDirectory.resolve(definitions.getFirst().info.identifier + ".geo.json")); } public static BedrockGeometry of(GeometryDefinition... definitions) { From 3dc173e115aeccd921de592b3eef61c4103da470 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Wed, 15 Oct 2025 07:30:35 +0000 Subject: [PATCH 22/37] Export textures in datagen --- .../rainbow/datagen/RainbowModelProvider.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index bbed475..b9042a9 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -1,8 +1,10 @@ package org.geysermc.rainbow.datagen; +import com.google.common.hash.HashCode; import com.mojang.serialization.Codec; import net.fabricmc.fabric.api.client.datagen.v1.provider.FabricModelProvider; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; +import net.minecraft.Util; import net.minecraft.client.data.models.model.ModelInstance; import net.minecraft.client.renderer.block.model.BlockModel; import net.minecraft.client.renderer.item.ClientItem; @@ -28,6 +30,7 @@ import org.jetbrains.annotations.Nullable; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.StringReader; import java.nio.file.Path; import java.util.HashMap; @@ -67,7 +70,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { CompletableFuture bedrockPack = ClientPackLoader.openClientResources() .thenCompose(resourceManager -> registries.thenApply(registries -> { try (resourceManager) { - BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, registries), + BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, resourceManager, registries), new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build(); for (Item item : itemInfos.keySet()) { @@ -102,7 +105,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { this.models = models; } - private record Serializer(CachedOutput output, HolderLookup.Provider registries) implements PackSerializer { + private record Serializer(CachedOutput output, ResourceManager resourceManager, HolderLookup.Provider registries) implements PackSerializer { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { @@ -111,7 +114,15 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public CompletableFuture saveTexture(ResourceLocation texture, Path path) { - return CompletableFuture.completedFuture(null); + return CompletableFuture.runAsync(() -> { + ResourceLocation texturePath = texture.withPath(p -> "textures/" + p + ".png"); + try (InputStream inputTexture = resourceManager.open(texturePath)) { + byte[] textureBytes = inputTexture.readAllBytes(); + output.writeIfNeeded(path, textureBytes, HashCode.fromBytes(textureBytes)); + } catch (IOException exception) { + // TODO log + } + }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); } } From 94f73dbd0698db439620b6a533e55386909ecf86 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Wed, 15 Oct 2025 07:43:14 +0000 Subject: [PATCH 23/37] Ensure translations aren't loaded in datagen --- .../java/org/geysermc/rainbow/datagen/RainbowModelProvider.java | 2 +- .../java/org/geysermc/rainbow/mapping/BedrockItemMapper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index b9042a9..0562a3c 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -120,7 +120,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { byte[] textureBytes = inputTexture.readAllBytes(); output.writeIfNeeded(path, textureBytes, HashCode.fromBytes(textureBytes)); } catch (IOException exception) { - // TODO log + LOGGER.error("Failed to save file to {}", path, exception); } }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 72c1d7b..8e1e7d9 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -264,7 +264,7 @@ public class BedrockItemMapper { tags = List.of(); } - GeyserBaseDefinition base = new GeyserBaseDefinition(bedrockIdentifier, Optional.of(stack.getHoverName().getString()), predicateStack, + GeyserBaseDefinition base = new GeyserBaseDefinition(bedrockIdentifier, Optional.ofNullable(stack.getHoverName().tryCollapseToString()), predicateStack, new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, displayHandheld, calculateProtectionValue(stack), tags), stack.getComponentsPatch()); try { From 0ccc78e8272b390f14bffe54cc45ac87f037cf39 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Wed, 15 Oct 2025 16:41:02 +0000 Subject: [PATCH 24/37] Work on improving model generation/stitching of multiple textures --- .../client/MinecraftAssetResolver.java | 10 +++ .../client/MinecraftPackSerializer.java | 10 +-- .../geysermc/rainbow/client/PackManager.java | 4 +- .../rainbow/client/RainbowClient.java | 64 ++++++++++++- .../java/org/geysermc/rainbow/Rainbow.java | 9 ++ .../rainbow/image/NativeImageUtil.java | 44 +++++++++ .../rainbow/mapping/AssetResolver.java | 4 + .../rainbow/mapping/BedrockItemMapper.java | 89 +++++-------------- .../geysermc/rainbow/mapping/PackContext.java | 4 +- .../rainbow/mapping/PackSerializer.java | 3 +- .../mapping/attachable/AttachableMapper.java | 22 ++--- .../geometry/BedrockGeometryContext.java | 49 +++++++++- .../mapping/geometry/GeometryMapper.java | 42 ++++----- .../mapping/geometry/StitchedTextures.java | 77 ++++++++++++++++ .../mapping/geometry/TextureHolder.java | 14 +++ .../rainbow/mixin/FaceBakeryAccessor.java | 17 ++++ .../rainbow/mixin/NativeImageAccessor.java | 18 ++++ .../rainbow/mixin/SpriteContentsAccessor.java | 13 +++ .../rainbow/mixin/SpriteLoaderAccessor.java | 16 ++++ .../rainbow/mixin/TextureSlotsAccessor.java | 6 ++ .../geysermc/rainbow/pack/BedrockItem.java | 10 +-- .../geysermc/rainbow/pack/BedrockPack.java | 33 +++++-- .../rainbow/pack/BedrockTextures.java | 2 +- .../src/main/resources/rainbow.mixins.json | 8 +- 24 files changed, 438 insertions(+), 130 deletions(-) create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/image/NativeImageUtil.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mixin/FaceBakeryAccessor.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mixin/NativeImageAccessor.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteContentsAccessor.java create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteLoaderAccessor.java diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java index d3e2f7c..7a4acab 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java @@ -8,20 +8,25 @@ import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.client.accessor.ResolvedModelAccessor; import org.geysermc.rainbow.client.mixin.EntityRenderDispatcherAccessor; import org.geysermc.rainbow.mapping.AssetResolver; +import java.io.IOException; +import java.io.InputStream; import java.util.Optional; public class MinecraftAssetResolver implements AssetResolver { private final ModelManager modelManager; private final EquipmentAssetManager equipmentAssetManager; + private final ResourceManager resourceManager; public MinecraftAssetResolver(Minecraft minecraft) { modelManager = minecraft.getModelManager(); equipmentAssetManager = ((EntityRenderDispatcherAccessor) minecraft.getEntityRenderDispatcher()).getEquipmentAssets(); + resourceManager = minecraft.getResourceManager(); } @Override @@ -38,4 +43,9 @@ public class MinecraftAssetResolver implements AssetResolver { public Optional getEquipmentInfo(ResourceKey key) { return Optional.of(equipmentAssetManager.get(key)); } + + @Override + public InputStream openAsset(ResourceLocation location) throws IOException { + return resourceManager.open(location); + } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java index 6ee5895..61c1e39 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java @@ -16,7 +16,6 @@ import org.geysermc.rainbow.mapping.PackSerializer; import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; import java.io.OutputStream; import java.nio.file.Path; import java.util.Objects; @@ -24,11 +23,9 @@ import java.util.concurrent.CompletableFuture; public class MinecraftPackSerializer implements PackSerializer { private final HolderLookup.Provider registries; - private final ResourceManager resourceManager; public MinecraftPackSerializer(Minecraft minecraft) { registries = Objects.requireNonNull(minecraft.level).registryAccess(); - resourceManager = minecraft.getResourceManager(); } @Override @@ -44,13 +41,12 @@ public class MinecraftPackSerializer implements PackSerializer { } @Override - public CompletableFuture saveTexture(ResourceLocation texture, Path path) { + public CompletableFuture saveTexture(byte[] texture, Path path) { return CompletableFuture.runAsync(() -> { - ResourceLocation texturePath = texture.withPath(p -> "textures/" + p + ".png"); - try (InputStream inputTexture = resourceManager.open(texturePath)) { + try { CodecUtil.ensureDirectoryExists(path.getParent()); try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { - IOUtils.copy(inputTexture, outputTexture); + outputTexture.write(texture); } } catch (IOException exception) { // TODO log diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index e81b348..f6d9a52 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -88,8 +88,8 @@ public final class PackManager { Set bedrockItems = pack.getBedrockItems(); long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count(); - long geometries = bedrockItems.stream().filter(item -> item.geometry().isPresent()).count(); - long animations = bedrockItems.stream().filter(item -> item.animation().isPresent()).count(); + long geometries = bedrockItems.stream().filter(item -> item.geometry().geometry().isPresent()).count(); + long animations = bedrockItems.stream().filter(item -> item.geometry().animation().isPresent()).count(); return """ -- PACK GENERATION REPORT -- diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java index 1ef7cea..f004099 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java @@ -1,14 +1,33 @@ package org.geysermc.rainbow.client; +import com.mojang.blaze3d.platform.NativeImage; +import com.mojang.brigadier.arguments.StringArgumentType; import net.fabricmc.api.ClientModInitializer; +import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.metadata.animation.FrameSize; import net.minecraft.commands.synchronization.SingletonArgumentInfo; +import net.minecraft.data.AtlasIds; +import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.client.command.CommandSuggestionsArgumentType; import org.geysermc.rainbow.client.command.PackGeneratorCommand; import org.geysermc.rainbow.client.mapper.PackMapper; +import org.geysermc.rainbow.mixin.SpriteContentsAccessor; +import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.List; public class RainbowClient implements ClientModInitializer { @@ -18,10 +37,53 @@ public class RainbowClient implements ClientModInitializer { // TODO export language overrides @Override public void onInitializeClient() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper)); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> { + PackGeneratorCommand.register(dispatcher, packManager, packMapper); + + dispatcher.register( + ClientCommandManager.literal("DEBUGTEST") + .then(ClientCommandManager.argument("textures", StringArgumentType.greedyString()) + .executes(context -> { + SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); + List sprites = Arrays.stream(StringArgumentType.getString(context, "textures").split(" ")) + .map(ResourceLocation::tryParse) + .map(RainbowClient::readSpriteContents) + .toList(); + SpriteLoader.Preparations preparations = ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); + + try (NativeImage stitched = stitchTextureAtlas(preparations)) { + stitched.writeToFile(FabricLoader.getInstance().getGameDir().resolve("test.png")); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + return 0; + }) + ) + ); + }); ClientTickEvents.START_CLIENT_TICK.register(packMapper::tick); ArgumentTypeRegistry.registerArgumentType(Rainbow.getModdedLocation("command_suggestions"), CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); } + + private static SpriteContents readSpriteContents(ResourceLocation location) { + try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) { + NativeImage texture = NativeImage.read(textureStream); + return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + + private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { + NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), false); + for (TextureAtlasSprite sprite : preparations.regions().values()) { + try (SpriteContents contents = sprite.contents()) { + ((SpriteContentsAccessor) contents).getOriginalImage().copyRect(stitched, 0, 0, + sprite.getX(), sprite.getY(), contents.width(), contents.height(), false, false); + } + } + return stitched; + } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java index 3aa247c..aa3352d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -14,7 +14,16 @@ public class Rainbow { return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); } + // TODO rename remove file public static String fileSafeResourceLocation(ResourceLocation location) { return location.toString().replace(':', '.').replace('/', '_'); } + + public static ResourceLocation decorateResourceLocation(ResourceLocation location, String type, String extension) { + return location.withPath(path -> type + "/" + path + "." + extension); + } + + public static ResourceLocation decorateTextureLocation(ResourceLocation location) { + return decorateResourceLocation(location, "textures", "png"); + } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/image/NativeImageUtil.java b/rainbow/src/main/java/org/geysermc/rainbow/image/NativeImageUtil.java new file mode 100644 index 0000000..e79fbd8 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/image/NativeImageUtil.java @@ -0,0 +1,44 @@ +package org.geysermc.rainbow.image; + +import com.mojang.blaze3d.platform.NativeImage; +import org.geysermc.rainbow.mixin.NativeImageAccessor; +import org.lwjgl.stb.STBImage; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.Pipe; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.WritableByteChannel; + +public class NativeImageUtil { + + // Adjusted NativeImage#writeToFile + @SuppressWarnings("DataFlowIssue") + public static byte[] writeToByteArray(NativeImage image) throws IOException { + if (!image.format().supportedByStb()) { + throw new UnsupportedOperationException("Don't know how to write format " + image.format()); + } else { + ((NativeImageAccessor) (Object) image).invokeCheckAllocated(); + Pipe pipe = Pipe.open(); + try (WritableByteChannel outputChannel = pipe.sink()) { + if (!((NativeImageAccessor) (Object) image).invokeWriteToChannel(outputChannel)) { + throw new IOException("Could not write image to pipe: " + STBImage.stbi_failure_reason()); + } + } + + try (ReadableByteChannel inputChannel = pipe.source()) { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + ByteBuffer buffer = ByteBuffer.allocate(4096); + while (inputChannel.read(buffer) != -1) { + buffer.flip(); + while (buffer.hasRemaining()) { + bytes.write(buffer.get()); + } + buffer.clear(); + } + return bytes.toByteArray(); + } + } + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java index 53ee42b..922017e 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java @@ -7,6 +7,8 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.item.equipment.EquipmentAsset; +import java.io.IOException; +import java.io.InputStream; import java.util.Optional; public interface AssetResolver { @@ -16,4 +18,6 @@ public interface AssetResolver { Optional getClientItem(ResourceLocation location); Optional getEquipmentInfo(ResourceKey key); + + InputStream openAsset(ResourceLocation location) throws IOException; } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 8e1e7d9..02e12f2 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -21,8 +21,6 @@ import net.minecraft.client.renderer.item.properties.select.Charge; import net.minecraft.client.renderer.item.properties.select.ContextDimension; import net.minecraft.client.renderer.item.properties.select.DisplayContext; import net.minecraft.client.renderer.item.properties.select.TrimMaterialProperty; -import net.minecraft.client.resources.model.Material; -import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -37,11 +35,8 @@ import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.equipment.trim.TrimMaterial; import net.minecraft.world.level.Level; import org.apache.commons.lang3.ArrayUtils; -import org.geysermc.rainbow.mapping.animation.AnimationMapper; -import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.mapping.attachable.AttachableMapper; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; -import org.geysermc.rainbow.mapping.geometry.GeometryMapper; import org.geysermc.rainbow.definition.GeyserBaseDefinition; import org.geysermc.rainbow.definition.GeyserItemDefinition; import org.geysermc.rainbow.definition.GeyserLegacyDefinition; @@ -52,9 +47,7 @@ import org.geysermc.rainbow.definition.predicate.GeyserPredicate; import org.geysermc.rainbow.definition.predicate.GeyserRangeDispatchPredicate; import org.geysermc.rainbow.mixin.LateBoundIdMapperAccessor; import org.geysermc.rainbow.mixin.RangeSelectItemModelAccessor; -import org.geysermc.rainbow.mixin.TextureSlotsAccessor; import org.geysermc.rainbow.pack.BedrockItem; -import org.geysermc.rainbow.pack.BedrockTextures; import java.util.List; import java.util.Optional; @@ -62,9 +55,6 @@ import java.util.function.Function; import java.util.stream.Stream; public class BedrockItemMapper { - private static final List HANDHELD_MODELS = Stream.of("item/handheld", "item/handheld_rod", "item/handheld_mace") - .map(ResourceLocation::withDefaultNamespace) - .toList(); private static final List TRIMMABLE_ARMOR_TAGS = Stream.of("is_armor", "trimmable_armors") .map(ResourceLocation::withDefaultNamespace) .toList(); @@ -117,7 +107,7 @@ public class BedrockItemMapper { case ConditionalItemModel.Unbaked conditional -> mapConditionalModel(conditional, context.child("condition model ")); case RangeSelectItemModel.Unbaked rangeSelect -> mapRangeSelectModel(rangeSelect, context.child("range select model ")); case SelectItemModel.Unbaked select -> mapSelectModel(select, context.child("select model ")); - default -> context.reporter.report(() -> "unsupported item model " + getModelId(model)); + default -> context.report("unsupported item model " + getModelId(model)); } } @@ -126,10 +116,6 @@ public class BedrockItemMapper { context.packContext().assetResolver().getResolvedModel(itemModelLocation) .ifPresentOrElse(itemModel -> { - ResolvedModel parentModel = itemModel.parent(); - // debugName() returns the resource location of the model as a string - boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName())); - ResourceLocation bedrockIdentifier; if (itemModelLocation.getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) { bedrockIdentifier = ResourceLocation.fromNamespaceAndPath("geyser_mc", itemModelLocation.getPath()); @@ -137,29 +123,13 @@ public class BedrockItemMapper { bedrockIdentifier = itemModelLocation; } - Material layer0Texture = itemModel.getTopTextureSlots().getMaterial("layer0"); - Optional texture; - Optional customGeometry; - if (layer0Texture != null) { - texture = Optional.of(layer0Texture.texture()); - customGeometry = Optional.empty(); - } else { - // We can't stitch multiple textures together yet, so we just grab the first one we see - // This will only work properly for models with just one texture - texture = ((TextureSlotsAccessor) itemModel.getTopTextureSlots()).getResolvedValues().values().stream() - .map(Material::texture) - .findAny(); - // Unknown texture (doesn't use layer0), so we immediately assume the geometry is custom - // This check should probably be done differently - customGeometry = Optional.of(itemModel); - } - - texture.ifPresentOrElse(itemTexture -> { + BedrockGeometryContext geometry = BedrockGeometryContext.create(itemModel); + if (context.packContext.reportSuccesses()) { // Not a problem, but just report to get the model printed in the report file - context.reporter.report(() -> "creating mapping for block model " + itemModelLocation); - context.create(bedrockIdentifier, itemTexture, handheld, customGeometry); - }, () -> context.reporter.report(() -> "not mapping block model " + itemModelLocation + " because it has no texture")); - }, () -> context.reporter.report(() -> "missing block model " + itemModelLocation)); + context.report("creating mapping for block model " + itemModelLocation); + } + context.create(bedrockIdentifier, geometry); + }, () -> context.report("missing block model " + itemModelLocation)); } private static void mapConditionalModel(ConditionalItemModel.Unbaked model, MappingContext context) { @@ -176,7 +146,7 @@ public class BedrockItemMapper { ItemModel.Unbaked onFalse = model.onFalse(); if (predicateProperty == null) { - context.reporter.report(() -> "unsupported conditional model property " + property + ", only mapping on_false"); + context.report("unsupported conditional model property " + property + ", only mapping on_false"); mapItem(onFalse, context.child("condition on_false (unsupported property)")); return; } @@ -197,7 +167,7 @@ public class BedrockItemMapper { }; if (predicateProperty == null) { - context.reporter.report(() -> "unsupported range dispatch model property " + property + ", only mapping fallback, if it is present"); + context.report("unsupported range dispatch model property " + property + ", only mapping fallback, if it is present"); } else { for (RangeSelectItemModel.Entry entry : model.entries()) { mapItem(entry.model(), context.with(new GeyserRangeDispatchPredicate(predicateProperty, entry.threshold(), model.scale()), "threshold " + entry.threshold())); @@ -223,7 +193,7 @@ public class BedrockItemMapper { if (dataConstructor == null) { if (unbakedSwitch.property() instanceof DisplayContext) { - context.reporter.report(() -> "unsupported select model property display_context, only mapping \"gui\" case, if it exists"); + context.report("unsupported select model property display_context, only mapping \"gui\" case, if it exists"); for (SelectItemModel.SwitchCase switchCase : cases) { if (switchCase.values().contains(ItemDisplayContext.GUI)) { mapItem(switchCase.model(), context.child("select GUI display_context case (unsupported property) ")); @@ -231,7 +201,7 @@ public class BedrockItemMapper { } } } - context.reporter.report(() -> "unsupported select model property " + unbakedSwitch.property() + ", only mapping fallback, if present"); + context.report("unsupported select model property " + unbakedSwitch.property() + ", only mapping fallback, if present"); model.fallback().ifPresent(fallback -> mapItem(fallback, context.child("select fallback case (unsupported property) "))); return; } @@ -255,17 +225,11 @@ public class BedrockItemMapper { return new MappingContext(predicateStack, stack, reporter.forChild(() -> childName), definitionCreator, packContext); } - public void create(ResourceLocation bedrockIdentifier, ResourceLocation texture, boolean displayHandheld, - Optional customModel) { - List tags; - if (stack.is(ItemTags.TRIMMABLE_ARMOR)) { - tags = TRIMMABLE_ARMOR_TAGS; - } else { - tags = List.of(); - } + public void create(ResourceLocation bedrockIdentifier, BedrockGeometryContext geometry) { + List tags = stack.is(ItemTags.TRIMMABLE_ARMOR) ? TRIMMABLE_ARMOR_TAGS : List.of(); GeyserBaseDefinition base = new GeyserBaseDefinition(bedrockIdentifier, Optional.ofNullable(stack.getHoverName().tryCollapseToString()), predicateStack, - new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, displayHandheld, calculateProtectionValue(stack), tags), + new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, geometry.handheld(), calculateProtectionValue(stack), tags), stack.getComponentsPatch()); try { packContext.mappings().map(stack.getItemHolder(), definitionCreator.apply(base)); @@ -274,26 +238,13 @@ public class BedrockItemMapper { return; } - // TODO Should probably get a better way to get geometry texture - String safeIdentifier = base.textureName(); - String bone = "bone"; - ResourceLocation geometryTexture = texture; - Optional bedrockGeometry = customModel.flatMap(model -> GeometryMapper.mapGeometry(safeIdentifier, bone, model, geometryTexture)); - Optional bedrockAnimation = customModel.map(model -> AnimationMapper.mapAnimation(safeIdentifier, bone, model.getTopTransforms())); + // TODO move attachable mapping somewhere else for cleaner code? + packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), geometry, + AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, geometry, packContext.assetResolver(), packContext.additionalTextureConsumer()))); + } - boolean exportTexture = true; - if (customModel.isPresent()) { - texture = texture.withPath(path -> path + "_icon"); - // FIXME Bit of a hack, preferably render geometry at a later stage - exportTexture = !packContext.geometryRenderer().render(stack, packContext.paths().packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png")); - packContext.additionalTextureConsumer().accept(geometryTexture); - } - - packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), texture, exportTexture, - AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, bedrockGeometry, bedrockAnimation, - packContext.assetResolver(), - packContext.additionalTextureConsumer()), - bedrockGeometry.map(BedrockGeometryContext::geometry), bedrockAnimation.map(BedrockAnimationContext::animation))); + public void report(String problem) { + reporter.report(() -> problem); } private static int calculateProtectionValue(ItemStack stack) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index e32196a..d581eb3 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -1,13 +1,13 @@ package org.geysermc.rainbow.mapping; -import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; +import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.geysermc.rainbow.pack.PackPaths; import java.util.function.Consumer; public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, - GeometryRenderer geometryRenderer, Consumer additionalTextureConsumer, + GeometryRenderer geometryRenderer, Consumer additionalTextureConsumer, boolean reportSuccesses) { } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java index 5f74525..ef1b9e2 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackSerializer.java @@ -1,7 +1,6 @@ package org.geysermc.rainbow.mapping; import com.mojang.serialization.Codec; -import net.minecraft.resources.ResourceLocation; import java.nio.file.Path; import java.util.concurrent.CompletableFuture; @@ -10,5 +9,5 @@ public interface PackSerializer { CompletableFuture saveJson(Codec codec, T object, Path path); - CompletableFuture saveTexture(ResourceLocation texture, Path path); + CompletableFuture saveTexture(byte[] texture, Path path); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index 85a1487..c5fc157 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -9,8 +9,8 @@ import net.minecraft.resources.ResourceLocation; import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.equipment.Equippable; import org.geysermc.rainbow.mapping.AssetResolver; -import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; +import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; import java.util.List; @@ -19,12 +19,12 @@ import java.util.function.Consumer; public class AttachableMapper { - public static Optional mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, Optional customGeometry, - Optional customAnimation, AssetResolver assetResolver, Consumer textureConsumer) { + public static Optional mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, BedrockGeometryContext geometryContext, + AssetResolver assetResolver, Consumer textureConsumer) { // Crazy optional statement // Unfortunately we can't have both equippables and custom models, so we prefer the latter :( - return customGeometry - .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.geometry().definitions().getFirst(), geometry.texture().getPath())) + return geometryContext.geometry() + .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.texture().location().getPath())) .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) .flatMap(optional -> (Optional) optional) .flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info))) @@ -33,14 +33,14 @@ public class AttachableMapper { .mapSecond(info -> info.getLayers(getLayer(assetInfo.getFirst())))) .filter(assetInfo -> !assetInfo.getSecond().isEmpty()) .map(assetInfo -> { - ResourceLocation texture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst())); - textureConsumer.accept(texture); - return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), texture.getPath()); + ResourceLocation equipmentTexture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst())); + textureConsumer.accept(new TextureHolder(equipmentTexture)); + return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), equipmentTexture.getPath()); })) .map(attachable -> { - customAnimation.ifPresent(context -> { - attachable.withAnimation("first_person", context.firstPerson()); - attachable.withAnimation("third_person", context.thirdPerson()); + geometryContext.animation().ifPresent(animation -> { + attachable.withAnimation("first_person", animation.firstPerson()); + attachable.withAnimation("third_person", animation.thirdPerson()); attachable.withScript("animate", "first_person", "context.is_first_person == 1.0"); attachable.withScript("animate", "third_person", "context.is_first_person == 0.0"); }); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index 2e26ac2..026a606 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -1,6 +1,53 @@ package org.geysermc.rainbow.mapping.geometry; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.resources.model.Material; +import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.resources.ResourceLocation; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.animation.AnimationMapper; +import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; -public record BedrockGeometryContext(BedrockGeometry geometry, ResourceLocation texture) {} +import java.util.List; +import java.util.Optional; +import java.util.stream.Stream; + +public record BedrockGeometryContext(Optional geometry, Optional animation, TextureHolder texture, + boolean handheld) { + private static final List HANDHELD_MODELS = Stream.of("item/handheld", "item/handheld_rod", "item/handheld_mace") + .map(ResourceLocation::withDefaultNamespace) + .toList(); + + public static BedrockGeometryContext create(ResolvedModel model) { + ResolvedModel parentModel = model.parent(); + // debugName() returns the resource location of the model as a string + boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName())); + + TextureSlots textures = model.getTopTextureSlots(); + Material layer0Texture = textures.getMaterial("layer0"); + Optional geometry; + Optional animation; + TextureHolder texture; + + if (layer0Texture != null) { + geometry = Optional.empty(); + animation = Optional.empty(); + texture = new TextureHolder(layer0Texture.texture()); + } else { + // Unknown model (doesn't use layer0), so we immediately assume the geometry is custom + // This check should probably be done differently (actually check if the model is 2D or 3D) + + ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); + String safeIdentifier = Rainbow.fileSafeResourceLocation(modelLocation); + + StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures); + geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); + animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms())); + texture = new TextureHolder(modelLocation.withSuffix("_stitched"), Optional.of(stitchedTextures.stitched())); + // TODO geometry rendering + } + + return new BedrockGeometryContext(geometry, animation, texture, handheld); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java index 550095b..a2794ba 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java @@ -7,7 +7,7 @@ import net.minecraft.client.renderer.block.model.SimpleUnbakedGeometry; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.client.resources.model.UnbakedGeometry; import net.minecraft.core.Direction; -import net.minecraft.resources.ResourceLocation; +import org.geysermc.rainbow.mixin.FaceBakeryAccessor; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; import org.joml.Vector2f; import org.joml.Vector3f; @@ -19,7 +19,7 @@ import java.util.Optional; public class GeometryMapper { private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F); - public static Optional mapGeometry(String identifier, String boneName, ResolvedModel model, ResourceLocation texture) { + public static Optional mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) { UnbakedGeometry top = model.getTopGeometry(); if (top == UnbakedGeometry.EMPTY) { return Optional.empty(); @@ -31,9 +31,8 @@ public class GeometryMapper { builder.withVisibleBoundsHeight(4.0F); builder.withVisibleBoundsOffset(new Vector3f(0.0F, 0.75F, 0.0F)); - // TODO proper texture size - builder.withTextureWidth(16); - builder.withTextureHeight(16); + builder.withTextureWidth(textures.width()); + builder.withTextureHeight(textures.height()); BedrockGeometry.Bone.Builder bone = BedrockGeometry.bone(boneName); @@ -43,7 +42,7 @@ public class GeometryMapper { SimpleUnbakedGeometry geometry = (SimpleUnbakedGeometry) top; for (BlockElement element : geometry.elements()) { // TODO the origin here is wrong, some models seem to be mirrored weirdly in blockbench - BedrockGeometry.Cube cube = mapBlockElement(element).build(); + BedrockGeometry.Cube cube = mapBlockElement(element, textures).build(); bone.withCube(cube); min.min(cube.origin()); max.max(cube.origin().add(cube.size(), new Vector3f())); @@ -55,35 +54,36 @@ public class GeometryMapper { // Bind to the bone of the current item slot bone.withBinding("q.item_slot_to_bone_name(context.item_slot)"); - return Optional.of(new BedrockGeometryContext(builder.withBone(bone).build(), texture)); + return Optional.of(builder.withBone(bone).build()); } - private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element) { + private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element, StitchedTextures textures) { // The centre of the model is back by 8 in the X and Z direction on Java, so move the origin of the cube and the pivot like that BedrockGeometry.Cube.Builder builder = BedrockGeometry.cube(element.from().sub(CENTRE_OFFSET, new Vector3f()), element.to().sub(element.from(), new Vector3f())); for (Map.Entry faceEntry : element.faces().entrySet()) { - // TODO texture key Direction direction = faceEntry.getKey(); BlockElementFace face = faceEntry.getValue(); Vector2f uvOrigin; Vector2f uvSize; BlockElementFace.UVs uvs = face.uvs(); - if (uvs != null) { - // Up and down faces are special - if (direction.getAxis() == Direction.Axis.Y) { - uvOrigin = new Vector2f(uvs.maxU(), uvs.maxV()); - uvSize = new Vector2f(uvs.minU() - uvs.maxU(), uvs.minV() - uvs.maxV()); - } else { - uvOrigin = new Vector2f(uvs.minU(), uvs.minV()); - uvSize = new Vector2f(uvs.maxU() - uvs.minU(), uvs.maxV() - uvs.minV()); - } - } else { - uvOrigin = new Vector2f(); - uvSize = new Vector2f(); + if (uvs == null) { + // Java defaults to a set of UV values determined by the position of the face if no UV values were specified + uvs = FaceBakeryAccessor.invokeDefaultFaceUV(element.from(), element.to(), direction); } + // Up and down faces are special and have their UVs flipped + if (direction.getAxis() == Direction.Axis.Y) { + uvOrigin = new Vector2f(uvs.maxU(), uvs.maxV()); + uvSize = new Vector2f(uvs.minU() - uvs.maxU(), uvs.minV() - uvs.maxV()); + } else { + uvOrigin = new Vector2f(uvs.minU(), uvs.minV()); + uvSize = new Vector2f(uvs.maxU() - uvs.minU(), uvs.maxV() - uvs.minV()); + } + + // If the texture was stitched (which it should have been, unless it doesn't exist), offset the UVs by the texture's starting UV + textures.getSprite(face.texture()).ifPresent(sprite -> uvOrigin.add(sprite.getX(), sprite.getY())); builder.withFace(direction, uvOrigin, uvSize, face.rotation()); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java new file mode 100644 index 0000000..90dc0b6 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java @@ -0,0 +1,77 @@ +package org.geysermc.rainbow.mapping.geometry; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.Util; +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.block.model.TextureSlots; +import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.client.renderer.texture.SpriteLoader; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.metadata.animation.FrameSize; +import net.minecraft.client.resources.model.Material; +import net.minecraft.data.AtlasIds; +import net.minecraft.resources.ResourceLocation; +import org.geysermc.rainbow.mixin.SpriteContentsAccessor; +import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; +import org.geysermc.rainbow.mixin.TextureSlotsAccessor; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Supplier; +import java.util.stream.Stream; + +public record StitchedTextures(Map sprites, Supplier stitched, int width, int height) { + + public Optional getSprite(String key) { + if (TextureSlotsAccessor.invokeIsTextureReference(key)) { + key = key.substring(1); + } + return Optional.ofNullable(sprites.get(key)); + } + + public static StitchedTextures stitchModelTextures(TextureSlots textures) { + Map materials = ((TextureSlotsAccessor) textures).getResolvedValues(); + SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture)); + + Map sprites = new HashMap<>(); + for (Map.Entry material : materials.entrySet()) { + sprites.put(material.getKey(), preparations.getSprite(material.getValue().texture())); + } + return new StitchedTextures(Map.copyOf(sprites), () -> stitchTextureAtlas(preparations), preparations.width(), preparations.height()); + } + + private static SpriteLoader.Preparations prepareStitching(Stream textures) { + // Atlas ID doesn't matter much here, but BLOCKS is the most appropriate + // Not sure if 1024 should be the max supported texture size, but it seems to work + SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); + List sprites = textures.map(StitchedTextures::readSpriteContents).toList(); + return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); + } + + private static SpriteContents readSpriteContents(ResourceLocation location) { + // TODO decorate path util + // TODO don't use ResourceManager + // TODO IO is on main thread here? + try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) { + NativeImage texture = NativeImage.read(textureStream); + return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); + } catch (IOException exception) { + throw new RuntimeException(exception); + } + } + + private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { + NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), false); + for (TextureAtlasSprite sprite : preparations.regions().values()) { + try (SpriteContents contents = sprite.contents()) { + ((SpriteContentsAccessor) contents).getOriginalImage().copyRect(stitched, 0, 0, + sprite.getX(), sprite.getY(), contents.width(), contents.height(), false, false); + } + } + return stitched; + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java new file mode 100644 index 0000000..cef5482 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java @@ -0,0 +1,14 @@ +package org.geysermc.rainbow.mapping.geometry; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.resources.ResourceLocation; + +import java.util.Optional; +import java.util.function.Supplier; + +public record TextureHolder(ResourceLocation location, Optional> supplier) { + + public TextureHolder(ResourceLocation location) { + this(location, Optional.empty()); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/FaceBakeryAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/FaceBakeryAccessor.java new file mode 100644 index 0000000..2fa94a4 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mixin/FaceBakeryAccessor.java @@ -0,0 +1,17 @@ +package org.geysermc.rainbow.mixin; + +import net.minecraft.client.renderer.block.model.BlockElementFace; +import net.minecraft.client.renderer.block.model.FaceBakery; +import net.minecraft.core.Direction; +import org.joml.Vector3fc; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Mixin(FaceBakery.class) +public interface FaceBakeryAccessor { + + @Invoker + static BlockElementFace.UVs invokeDefaultFaceUV(Vector3fc posFrom, Vector3fc posTo, Direction facing) { + throw new AssertionError(); + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/NativeImageAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/NativeImageAccessor.java new file mode 100644 index 0000000..6840312 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mixin/NativeImageAccessor.java @@ -0,0 +1,18 @@ +package org.geysermc.rainbow.mixin; + +import com.mojang.blaze3d.platform.NativeImage; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.io.IOException; +import java.nio.channels.WritableByteChannel; + +@Mixin(NativeImage.class) +public interface NativeImageAccessor { + + @Invoker + void invokeCheckAllocated(); + + @Invoker + boolean invokeWriteToChannel(WritableByteChannel channel) throws IOException; +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteContentsAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteContentsAccessor.java new file mode 100644 index 0000000..e2793de --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteContentsAccessor.java @@ -0,0 +1,13 @@ +package org.geysermc.rainbow.mixin; + +import com.mojang.blaze3d.platform.NativeImage; +import net.minecraft.client.renderer.texture.SpriteContents; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(SpriteContents.class) +public interface SpriteContentsAccessor { + + @Accessor + NativeImage getOriginalImage(); +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteLoaderAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteLoaderAccessor.java new file mode 100644 index 0000000..4401cac --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mixin/SpriteLoaderAccessor.java @@ -0,0 +1,16 @@ +package org.geysermc.rainbow.mixin; + +import net.minecraft.client.renderer.texture.SpriteContents; +import net.minecraft.client.renderer.texture.SpriteLoader; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.List; +import java.util.concurrent.Executor; + +@Mixin(SpriteLoader.class) +public interface SpriteLoaderAccessor { + + @Invoker + SpriteLoader.Preparations invokeStitch(List contents, int mipLevel, Executor executor); +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java b/rainbow/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java index c957971..6c5af2f 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mixin/TextureSlotsAccessor.java @@ -4,6 +4,7 @@ import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.resources.model.Material; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; +import org.spongepowered.asm.mixin.gen.Invoker; import java.util.Map; @@ -12,4 +13,9 @@ public interface TextureSlotsAccessor { @Accessor Map getResolvedValues(); + + @Invoker + static boolean invokeIsTextureReference(String name) { + throw new AssertionError(); + } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index 929260a..2742206 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -3,9 +3,8 @@ package org.geysermc.rainbow.pack; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackSerializer; -import org.geysermc.rainbow.pack.animation.BedrockAnimation; +import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; -import org.geysermc.rainbow.pack.geometry.BedrockGeometry; import java.nio.file.Path; import java.util.List; @@ -13,15 +12,14 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; -public record BedrockItem(ResourceLocation identifier, String textureName, ResourceLocation texture, boolean exportTexture, Optional attachable, - Optional geometry, Optional animation) { +public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometry, Optional attachable) { public List> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory) { return Stream.concat( attachable.stream().map(present -> present.save(serializer, attachableDirectory)), Stream.concat( - geometry.stream().map(present -> present.save(serializer, geometryDirectory)), - animation.stream().map(present -> present.save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))) + geometry.geometry().stream().map(present -> present.save(serializer, geometryDirectory)), + geometry.animation().stream().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))) ) ).toList(); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 4252d29..5455726 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -12,6 +12,8 @@ import net.minecraft.world.item.ItemStack; import net.minecraft.world.item.component.CustomModelData; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.image.NativeImageUtil; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; @@ -19,9 +21,11 @@ import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; +import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.jetbrains.annotations.NotNull; import java.io.IOException; +import java.io.InputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; @@ -41,7 +45,7 @@ public class BedrockPack { private final BedrockTextures.Builder itemTextures = BedrockTextures.builder(); private final Set bedrockItems = new HashSet<>(); - private final Set texturesToExport = new HashSet<>(); + private final Set texturesToExport = new HashSet<>(); private final Set modelsMapped = new HashSet<>(); private final IntSet customModelDataMapped = new IntOpenHashSet(); @@ -59,9 +63,7 @@ public class BedrockPack { // Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet this.context = new PackContext(new GeyserMappings(), paths, item -> { itemTextures.withItemTexture(item); - if (item.exportTexture()) { - texturesToExport.add(item.texture()); - } + texturesToExport.add(item.geometry().texture()); bedrockItems.add(item); }, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses); this.reporter = reporter; @@ -130,8 +132,27 @@ public class BedrockPack { futures.addAll(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation())); } - for (ResourceLocation texture : texturesToExport) { - futures.add(serializer.saveTexture(texture, paths.packRoot().resolve(BedrockTextures.TEXTURES_FOLDER + texture.getPath() + ".png"))); + for (TextureHolder texture : texturesToExport) { + ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); + texture.supplier() + .flatMap(image -> { + try { + return Optional.of(NativeImageUtil.writeToByteArray(image.get())); + } catch (IOException exception) { + // TODO log + return Optional.empty(); + } + }) + .or(() -> { + try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) { + return Optional.of(textureStream.readAllBytes()); + } catch (IOException exception) { + // TODO log + return Optional.empty(); + } + }) + .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) + .ifPresent(futures::add); } if (paths.zipOutput().isPresent()) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java index 030da51..c14aae6 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java @@ -33,7 +33,7 @@ public record BedrockTextures(Map textures) { private final Map textures = new HashMap<>(); public Builder withItemTexture(BedrockItem item) { - return withTexture(item.textureName(), TEXTURES_FOLDER + item.texture().getPath()); + return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometry().texture().location().getPath()); } public Builder withTexture(String name, String texture) { diff --git a/rainbow/src/main/resources/rainbow.mixins.json b/rainbow/src/main/resources/rainbow.mixins.json index 5e7f32d..c3f5d53 100644 --- a/rainbow/src/main/resources/rainbow.mixins.json +++ b/rainbow/src/main/resources/rainbow.mixins.json @@ -5,10 +5,16 @@ "compatibilityLevel": "JAVA_21", "client": [ "LateBoundIdMapperAccessor", + "NativeImageAccessor", "RangeSelectItemModelAccessor", + "SpriteContentsAccessor", + "SpriteLoaderAccessor", "TextureSlotsAccessor" ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + "FaceBakeryAccessor" + ] } From 0c4a877220b3a82691b2fa237cb1600adab504c4 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 06:03:39 +0000 Subject: [PATCH 25/37] Clear stitched texture memory data before writing, don't stitch duplicate textures --- .../rainbow/client/RainbowClient.java | 64 +------------------ .../mapping/geometry/StitchedTextures.java | 4 +- 2 files changed, 3 insertions(+), 65 deletions(-) diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java index f004099..1ef7cea 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java @@ -1,33 +1,14 @@ package org.geysermc.rainbow.client; -import com.mojang.blaze3d.platform.NativeImage; -import com.mojang.brigadier.arguments.StringArgumentType; import net.fabricmc.api.ClientModInitializer; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; -import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.Util; -import net.minecraft.client.Minecraft; -import net.minecraft.client.renderer.texture.SpriteContents; -import net.minecraft.client.renderer.texture.SpriteLoader; -import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.metadata.animation.FrameSize; import net.minecraft.commands.synchronization.SingletonArgumentInfo; -import net.minecraft.data.AtlasIds; -import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.client.command.CommandSuggestionsArgumentType; import org.geysermc.rainbow.client.command.PackGeneratorCommand; import org.geysermc.rainbow.client.mapper.PackMapper; -import org.geysermc.rainbow.mixin.SpriteContentsAccessor; -import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; public class RainbowClient implements ClientModInitializer { @@ -37,53 +18,10 @@ public class RainbowClient implements ClientModInitializer { // TODO export language overrides @Override public void onInitializeClient() { - ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> { - PackGeneratorCommand.register(dispatcher, packManager, packMapper); - - dispatcher.register( - ClientCommandManager.literal("DEBUGTEST") - .then(ClientCommandManager.argument("textures", StringArgumentType.greedyString()) - .executes(context -> { - SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); - List sprites = Arrays.stream(StringArgumentType.getString(context, "textures").split(" ")) - .map(ResourceLocation::tryParse) - .map(RainbowClient::readSpriteContents) - .toList(); - SpriteLoader.Preparations preparations = ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); - - try (NativeImage stitched = stitchTextureAtlas(preparations)) { - stitched.writeToFile(FabricLoader.getInstance().getGameDir().resolve("test.png")); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - return 0; - }) - ) - ); - }); + ClientCommandRegistrationCallback.EVENT.register((dispatcher, buildContext) -> PackGeneratorCommand.register(dispatcher, packManager, packMapper)); ClientTickEvents.START_CLIENT_TICK.register(packMapper::tick); ArgumentTypeRegistry.registerArgumentType(Rainbow.getModdedLocation("command_suggestions"), CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); } - - private static SpriteContents readSpriteContents(ResourceLocation location) { - try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) { - NativeImage texture = NativeImage.read(textureStream); - return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - } - - private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { - NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), false); - for (TextureAtlasSprite sprite : preparations.regions().values()) { - try (SpriteContents contents = sprite.contents()) { - ((SpriteContentsAccessor) contents).getOriginalImage().copyRect(stitched, 0, 0, - sprite.getX(), sprite.getY(), contents.width(), contents.height(), false, false); - } - } - return stitched; - } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java index 90dc0b6..fab5938 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java @@ -48,7 +48,7 @@ public record StitchedTextures(Map sprites, Supplier // Atlas ID doesn't matter much here, but BLOCKS is the most appropriate // Not sure if 1024 should be the max supported texture size, but it seems to work SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); - List sprites = textures.map(StitchedTextures::readSpriteContents).toList(); + List sprites = textures.distinct().map(StitchedTextures::readSpriteContents).toList(); return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); } @@ -65,7 +65,7 @@ public record StitchedTextures(Map sprites, Supplier } private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { - NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), false); + NativeImage stitched = new NativeImage(preparations.width(), preparations.height(), true); for (TextureAtlasSprite sprite : preparations.regions().values()) { try (SpriteContents contents = sprite.contents()) { ((SpriteContentsAccessor) contents).getOriginalImage().copyRect(stitched, 0, 0, From 7f67a40c13fc424608dd6a67d9b17929b8f19574 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 07:38:59 +0000 Subject: [PATCH 26/37] Sort Geyser mappings, async texture stitching, fix datagen --- .../geysermc/rainbow/client/PackManager.java | 12 +++---- .../rainbow/datagen/RainbowModelProvider.java | 17 +++++---- .../definition/GeyserGroupDefinition.java | 29 ++++++++++++++- .../definition/GeyserItemDefinition.java | 9 +++++ .../rainbow/definition/GeyserMapping.java | 2 +- .../rainbow/definition/GeyserMappings.java | 6 +++- .../rainbow/mapping/BedrockItemMapper.java | 5 ++- .../geysermc/rainbow/mapping/PackContext.java | 7 +--- .../mapping/attachable/AttachableMapper.java | 14 +++++--- .../geometry/BedrockGeometryContext.java | 33 ++++++++++------- .../mapping/geometry/GeometryMapper.java | 7 ++-- .../mapping/geometry/StitchedTextures.java | 14 ++++---- .../mapping/geometry/TextureHolder.java | 4 +++ .../geysermc/rainbow/pack/BedrockItem.java | 35 +++++++++++++------ .../geysermc/rainbow/pack/BedrockPack.java | 22 +++++------- .../rainbow/pack/BedrockTextures.java | 2 +- .../pack/geometry/BedrockGeometry.java | 2 ++ 17 files changed, 145 insertions(+), 75 deletions(-) diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index f6d9a52..f3e92ab 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -87,9 +87,9 @@ public final class PackManager { } Set bedrockItems = pack.getBedrockItems(); - long attachables = bedrockItems.stream().filter(item -> item.attachable().isPresent()).count(); - long geometries = bedrockItems.stream().filter(item -> item.geometry().geometry().isPresent()).count(); - long animations = bedrockItems.stream().filter(item -> item.geometry().animation().isPresent()).count(); + //long attachables = bedrockItems.stream().filter(item -> item.attachableCreator().isPresent()).count(); + long geometries = bedrockItems.stream().filter(item -> item.geometryContext().geometry().isPresent()).count(); + long animations = bedrockItems.stream().filter(item -> item.geometryContext().animation().isPresent()).count(); return """ -- PACK GENERATION REPORT -- @@ -98,15 +98,15 @@ public final class PackManager { Generated pack: %s Mappings written: %d Item texture atlas size: %d -Attachables tried to export: %d +Attachables tried to export: FIXME Geometry files tried to export: %d Animations tried to export: %d -Textures tried to export: %d +Textures tried to export: FIXME -- MAPPING TREE REPORT -- %s """.formatted(randomSummaryComment(), pack.name(), pack.getMappings(), pack.getItemTextureAtlasSize(), - attachables, geometries, animations, pack.getAdditionalExportedTextures(), problems); + geometries, animations, problems); } private static String randomSummaryComment() { diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index 0562a3c..c807130 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -70,7 +70,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { CompletableFuture bedrockPack = ClientPackLoader.openClientResources() .thenCompose(resourceManager -> registries.thenApply(registries -> { try (resourceManager) { - BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, resourceManager, registries), + BedrockPack pack = createBedrockPack(outputRoot, new Serializer(output, registries), new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build(); for (Item item : itemInfos.keySet()) { @@ -105,7 +105,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { this.models = models; } - private record Serializer(CachedOutput output, ResourceManager resourceManager, HolderLookup.Provider registries) implements PackSerializer { + private record Serializer(CachedOutput output, HolderLookup.Provider registries) implements PackSerializer { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { @@ -113,12 +113,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider { } @Override - public CompletableFuture saveTexture(ResourceLocation texture, Path path) { + public CompletableFuture saveTexture(byte[] texture, Path path) { return CompletableFuture.runAsync(() -> { - ResourceLocation texturePath = texture.withPath(p -> "textures/" + p + ".png"); - try (InputStream inputTexture = resourceManager.open(texturePath)) { - byte[] textureBytes = inputTexture.readAllBytes(); - output.writeIfNeeded(path, textureBytes, HashCode.fromBytes(textureBytes)); + try { + output.writeIfNeeded(path, texture, HashCode.fromBytes(texture)); } catch (IOException exception) { LOGGER.error("Failed to save file to {}", path, exception); } @@ -181,5 +179,10 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public Optional getEquipmentInfo(ResourceKey key) { return Optional.ofNullable(equipmentInfos.get(key)); } + + @Override + public InputStream openAsset(ResourceLocation location) throws IOException { + return resourceManager.open(location); + } } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java index 20e1aa7..9f8a611 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java @@ -3,9 +3,12 @@ package org.geysermc.rainbow.definition; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; +import java.util.Comparator; import java.util.List; import java.util.Optional; +import java.util.function.Function; import java.util.stream.Stream; public record GeyserGroupDefinition(Optional model, List definitions) implements GeyserMapping { @@ -18,7 +21,9 @@ public record GeyserGroupDefinition(Optional model, List model) { @@ -53,4 +58,26 @@ public record GeyserGroupDefinition(Optional model, List otherModel, List otherDefinitions)) { + if (model.isPresent() && otherModel.isPresent()) { + return model.get().compareTo(otherModel.get()); + } else if (model.isPresent()) { + return 1; // Groups with models are always greater than groups without + } else if (otherModel.isPresent()) { + return -1; + } else if (definitions.isEmpty() && otherDefinitions.isEmpty()) { + return 0; + } else if (definitions.isEmpty()) { + return -1; // Groups with definitions are always greater than groups without + } else if (otherDefinitions.isEmpty()) { + return 1; + } + // Compare the first definition as a last resort + return definitions.getFirst().compareTo(otherDefinitions.getFirst()); + } + return 1; // Groups are always greater than individual mappings + } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java index d09894a..1d3fe96 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java @@ -1,6 +1,7 @@ package org.geysermc.rainbow.definition; import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -9,4 +10,12 @@ public interface GeyserItemDefinition extends GeyserMapping { GeyserBaseDefinition base(); boolean conflictsWith(Optional parentModel, GeyserItemDefinition other); + + @Override + default int compareTo(@NotNull GeyserMapping other) { + if (other instanceof GeyserItemDefinition itemDefinition) { + return base().bedrockIdentifier().compareTo(itemDefinition.base().bedrockIdentifier()); + } + return -1; // Groups are always greater than individual mappings + } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java index d3cc862..a46fd1c 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java @@ -6,7 +6,7 @@ import com.mojang.serialization.MapCodec; import net.minecraft.util.StringRepresentable; import org.jetbrains.annotations.NotNull; -public interface GeyserMapping { +public interface GeyserMapping extends Comparable { Codec CODEC = Codec.lazyInitialized(() -> Type.CODEC.dispatch(GeyserMapping::type, Type::codec)); // Not perfect since we're not checking single definitions in groups without a model... but good enough diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java index 5a887a2..40e789e 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMappings.java @@ -11,6 +11,7 @@ import org.geysermc.rainbow.CodecUtil; import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; @@ -26,7 +27,10 @@ public class GeyserMappings { ).apply(instance, (format, mappings) -> new GeyserMappings(mappings)) ); - private final Multimap, GeyserMapping> mappings = MultimapBuilder.hashKeys().hashSetValues().build(); + private final Multimap, GeyserMapping> mappings = MultimapBuilder + .hashKeys() + .treeSetValues(Comparator.comparing(mapping -> mapping)) + .build(); public GeyserMappings() {} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 02e12f2..9690619 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -123,7 +123,7 @@ public class BedrockItemMapper { bedrockIdentifier = itemModelLocation; } - BedrockGeometryContext geometry = BedrockGeometryContext.create(itemModel); + BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, context.packContext); if (context.packContext.reportSuccesses()) { // Not a problem, but just report to get the model printed in the report file context.report("creating mapping for block model " + itemModelLocation); @@ -238,9 +238,8 @@ public class BedrockItemMapper { return; } - // TODO move attachable mapping somewhere else for cleaner code? packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), geometry, - AttachableMapper.mapItem(stack.getComponentsPatch(), bedrockIdentifier, geometry, packContext.assetResolver(), packContext.additionalTextureConsumer()))); + AttachableMapper.mapItem(packContext.assetResolver(), geometry, stack.getComponentsPatch()))); } public void report(String problem) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index d581eb3..d632638 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -2,12 +2,7 @@ package org.geysermc.rainbow.mapping; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; -import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.geysermc.rainbow.pack.PackPaths; -import java.util.function.Consumer; - public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, - GeometryRenderer geometryRenderer, Consumer additionalTextureConsumer, - boolean reportSuccesses) { -} + GeometryRenderer geometryRenderer, boolean reportSuccesses) {} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index c5fc157..4a24acb 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -19,12 +19,12 @@ import java.util.function.Consumer; public class AttachableMapper { - public static Optional mapItem(DataComponentPatch components, ResourceLocation bedrockIdentifier, BedrockGeometryContext geometryContext, - AssetResolver assetResolver, Consumer textureConsumer) { + public static AttachableCreator mapItem(AssetResolver assetResolver, BedrockGeometryContext geometryContext, DataComponentPatch components) { // Crazy optional statement // Unfortunately we can't have both equippables and custom models, so we prefer the latter :( - return geometryContext.geometry() - .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.texture().location().getPath())) + return (bedrockIdentifier, stitchedGeometry, textureConsumer) -> stitchedGeometry + .map(BedrockGeometryContext.StitchedGeometry::geometry) + .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.icon().location().getPath())) .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) .flatMap(optional -> (Optional) optional) .flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info))) @@ -55,4 +55,10 @@ public class AttachableMapper { private static ResourceLocation getTexture(List info, EquipmentClientInfo.LayerType layer) { return info.getFirst().textureId().withPath(path -> "entity/equipment/" + layer.getSerializedName() + "/" + path); } + + @FunctionalInterface + public interface AttachableCreator { + + Optional create(ResourceLocation bedrockIdentifier, Optional geometry, Consumer textureConsumer); + } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index 026a606..56891f6 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -1,53 +1,62 @@ package org.geysermc.rainbow.mapping.geometry; +import com.google.common.base.Suppliers; import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.animation.AnimationMapper; import org.geysermc.rainbow.mapping.animation.BedrockAnimationContext; import org.geysermc.rainbow.pack.geometry.BedrockGeometry; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; import java.util.stream.Stream; -public record BedrockGeometryContext(Optional geometry, Optional animation, TextureHolder texture, +public record BedrockGeometryContext(Optional> geometry, + Optional animation, TextureHolder icon, boolean handheld) { private static final List HANDHELD_MODELS = Stream.of("item/handheld", "item/handheld_rod", "item/handheld_mace") .map(ResourceLocation::withDefaultNamespace) .toList(); - public static BedrockGeometryContext create(ResolvedModel model) { + public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ResolvedModel model, PackContext context) { ResolvedModel parentModel = model.parent(); // debugName() returns the resource location of the model as a string boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName())); TextureSlots textures = model.getTopTextureSlots(); Material layer0Texture = textures.getMaterial("layer0"); - Optional geometry; + Optional> geometry; Optional animation; - TextureHolder texture; + TextureHolder icon; if (layer0Texture != null) { geometry = Optional.empty(); animation = Optional.empty(); - texture = new TextureHolder(layer0Texture.texture()); + icon = new TextureHolder(layer0Texture.texture()); } else { // Unknown model (doesn't use layer0), so we immediately assume the geometry is custom // This check should probably be done differently (actually check if the model is 2D or 3D) ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); - String safeIdentifier = Rainbow.fileSafeResourceLocation(modelLocation); - - StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures); - geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); + String safeIdentifier = Rainbow.fileSafeResourceLocation(bedrockIdentifier); + + geometry = Optional.of(Suppliers.memoize(() -> { + StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context); + BedrockGeometry mappedGeometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); + return new StitchedGeometry(mappedGeometry, new TextureHolder(modelLocation.withSuffix("_stitched"), stitchedTextures.stitched())); + })); + animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms())); - texture = new TextureHolder(modelLocation.withSuffix("_stitched"), Optional.of(stitchedTextures.stitched())); - // TODO geometry rendering + icon = new TextureHolder(modelLocation); // TODO } - return new BedrockGeometryContext(geometry, animation, texture, handheld); + return new BedrockGeometryContext(geometry, animation, icon, handheld); } + + public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {} } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java index a2794ba..8722caf 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java @@ -14,15 +14,14 @@ import org.joml.Vector3f; import org.joml.Vector3fc; import java.util.Map; -import java.util.Optional; public class GeometryMapper { private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F); - public static Optional mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) { + public static BedrockGeometry mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) { UnbakedGeometry top = model.getTopGeometry(); if (top == UnbakedGeometry.EMPTY) { - return Optional.empty(); + return BedrockGeometry.EMPTY; } BedrockGeometry.Builder builder = BedrockGeometry.builder(identifier); @@ -54,7 +53,7 @@ public class GeometryMapper { // Bind to the bone of the current item slot bone.withBinding("q.item_slot_to_bone_name(context.item_slot)"); - return Optional.of(builder.withBone(bone).build()); + return builder.withBone(bone).build(); } private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element, StitchedTextures textures) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java index fab5938..eff4435 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java @@ -11,6 +11,8 @@ import net.minecraft.client.resources.metadata.animation.FrameSize; import net.minecraft.client.resources.model.Material; import net.minecraft.data.AtlasIds; import net.minecraft.resources.ResourceLocation; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mixin.SpriteContentsAccessor; import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; import org.geysermc.rainbow.mixin.TextureSlotsAccessor; @@ -33,9 +35,9 @@ public record StitchedTextures(Map sprites, Supplier return Optional.ofNullable(sprites.get(key)); } - public static StitchedTextures stitchModelTextures(TextureSlots textures) { + public static StitchedTextures stitchModelTextures(TextureSlots textures, PackContext context) { Map materials = ((TextureSlotsAccessor) textures).getResolvedValues(); - SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture)); + SpriteLoader.Preparations preparations = prepareStitching(materials.values().stream().map(Material::texture), context); Map sprites = new HashMap<>(); for (Map.Entry material : materials.entrySet()) { @@ -44,19 +46,19 @@ public record StitchedTextures(Map sprites, Supplier return new StitchedTextures(Map.copyOf(sprites), () -> stitchTextureAtlas(preparations), preparations.width(), preparations.height()); } - private static SpriteLoader.Preparations prepareStitching(Stream textures) { + private static SpriteLoader.Preparations prepareStitching(Stream textures, PackContext context) { // Atlas ID doesn't matter much here, but BLOCKS is the most appropriate // Not sure if 1024 should be the max supported texture size, but it seems to work SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); - List sprites = textures.distinct().map(StitchedTextures::readSpriteContents).toList(); + List sprites = textures.distinct().map(texture -> readSpriteContents(texture, context)).toList(); return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); } - private static SpriteContents readSpriteContents(ResourceLocation location) { + private static SpriteContents readSpriteContents(ResourceLocation location, PackContext context) { // TODO decorate path util // TODO don't use ResourceManager // TODO IO is on main thread here? - try (InputStream textureStream = Minecraft.getInstance().getResourceManager().open(location.withPath(path -> "textures/" + path + ".png"))) { + try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) { NativeImage texture = NativeImage.read(textureStream); return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); } catch (IOException exception) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java index cef5482..10813ca 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java @@ -8,6 +8,10 @@ import java.util.function.Supplier; public record TextureHolder(ResourceLocation location, Optional> supplier) { + public TextureHolder(ResourceLocation location, Supplier supplier) { + this(location, Optional.of(supplier)); + } + public TextureHolder(ResourceLocation location) { this(location, Optional.empty()); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index 2742206..847b9e0 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -3,24 +3,39 @@ package org.geysermc.rainbow.pack; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackSerializer; +import org.geysermc.rainbow.mapping.attachable.AttachableMapper; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; +import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; import java.nio.file.Path; +import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.concurrent.CompletableFuture; -import java.util.stream.Stream; +import java.util.function.Function; +import java.util.function.Supplier; -public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometry, Optional attachable) { +public record BedrockItem(ResourceLocation identifier, String textureName, BedrockGeometryContext geometryContext, AttachableMapper.AttachableCreator attachableCreator) { - public List> save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory) { - return Stream.concat( - attachable.stream().map(present -> present.save(serializer, attachableDirectory)), - Stream.concat( - geometry.geometry().stream().map(present -> present.save(serializer, geometryDirectory)), - geometry.animation().stream().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))) - ) - ).toList(); + public CompletableFuture save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory, + Function> textureSaver) { + return CompletableFuture.supplyAsync(() -> geometryContext.geometry().map(Supplier::get)) + .thenCompose(stitchedGeometry -> { + List attachableTextures = new ArrayList<>(); + Optional createdAttachable = attachableCreator.create(identifier, stitchedGeometry, attachableTextures::add); + return CompletableFuture.allOf( + textureSaver.apply(geometryContext.icon()), + createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()), + CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), + stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), + stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), + geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop()) + ); + }); + } + + private static CompletableFuture noop() { + return CompletableFuture.completedFuture(null); } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 5455726..a48b39d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -35,6 +35,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; import java.util.function.UnaryOperator; public class BedrockPack { @@ -45,7 +46,6 @@ public class BedrockPack { private final BedrockTextures.Builder itemTextures = BedrockTextures.builder(); private final Set bedrockItems = new HashSet<>(); - private final Set texturesToExport = new HashSet<>(); private final Set modelsMapped = new HashSet<>(); private final IntSet customModelDataMapped = new IntOpenHashSet(); @@ -63,9 +63,8 @@ public class BedrockPack { // Not reading existing item mappings/texture atlas for now since that doesn't work all that well yet this.context = new PackContext(new GeyserMappings(), paths, item -> { itemTextures.withItemTexture(item); - texturesToExport.add(item.geometry().texture()); bedrockItems.add(item); - }, assetResolver, geometryRenderer, texturesToExport::add, reportSuccesses); + }, assetResolver, geometryRenderer, reportSuccesses); this.reporter = reporter; } @@ -128,13 +127,10 @@ public class BedrockPack { futures.add(serializer.saveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings())); futures.add(serializer.saveJson(PackManifest.CODEC, manifest, paths.manifest())); futures.add(serializer.saveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas())); - for (BedrockItem item : bedrockItems) { - futures.addAll(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation())); - } - for (TextureHolder texture : texturesToExport) { + Function> textureSaver = texture -> { ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); - texture.supplier() + return texture.supplier() .flatMap(image -> { try { return Optional.of(NativeImageUtil.writeToByteArray(image.get())); @@ -152,7 +148,11 @@ public class BedrockPack { } }) .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) - .ifPresent(futures::add); + .orElse(CompletableFuture.completedFuture(null)); + }; + + for (BedrockItem item : bedrockItems) { + futures.add(item.save(serializer, paths.attachables(), paths.geometry(), paths.animation(), textureSaver)); } if (paths.zipOutput().isPresent()) { @@ -178,10 +178,6 @@ public class BedrockPack { return itemTextures.build().size(); } - public int getAdditionalExportedTextures() { - return texturesToExport.size(); - } - public ProblemReporter.Collector getReporter() { return reporter; } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java index c14aae6..2b2345f 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockTextures.java @@ -33,7 +33,7 @@ public record BedrockTextures(Map textures) { private final Map textures = new HashMap<>(); public Builder withItemTexture(BedrockItem item) { - return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometry().texture().location().getPath()); + return withTexture(item.textureName(), TEXTURES_FOLDER + item.geometryContext().icon().location().getPath()); } public Builder withTexture(String name, String texture) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java index 29048fe..e63a430 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/geometry/BedrockGeometry.java @@ -32,6 +32,8 @@ public record BedrockGeometry(BedrockVersion formatVersion, List save(PackSerializer serializer, Path geometryDirectory) { return serializer.saveJson(CODEC, this, geometryDirectory.resolve(definitions.getFirst().info.identifier + ".geo.json")); } From 535d40c46829a6a44335e0fef1bad1f9f2f5534c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 08:35:57 +0000 Subject: [PATCH 27/37] Work on re-introducing 3D icon generation --- .../render/MinecraftGeometryRenderer.java | 30 +++++++------------ .../rainbow/mapping/BedrockItemMapper.java | 2 +- .../geysermc/rainbow/mapping/PackContext.java | 4 ++- .../geometry/BedrockGeometryContext.java | 5 ++-- .../mapping/geometry/GeometryRenderer.java | 5 ++-- .../geometry/NoopGeometryRenderer.java | 14 --------- .../geysermc/rainbow/pack/BedrockItem.java | 28 +++++++++-------- .../geysermc/rainbow/pack/BedrockPack.java | 8 ++--- 8 files changed, 39 insertions(+), 57 deletions(-) delete mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java index d7e8f19..b86ba75 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/render/MinecraftGeometryRenderer.java @@ -14,13 +14,10 @@ import net.minecraft.client.gui.render.state.pip.OversizedItemRenderState; import net.minecraft.client.renderer.item.TrackingItemStackRenderState; import net.minecraft.world.item.ItemDisplayContext; import net.minecraft.world.item.ItemStack; -import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.client.mixin.PictureInPictureRendererAccessor; import org.joml.Matrix3x2fStack; -import java.io.IOException; -import java.nio.file.Path; import java.util.Objects; // TODO maybe just use this even for normal 2D items, not sure, could be useful for composite models and stuff @@ -29,7 +26,7 @@ public class MinecraftGeometryRenderer implements GeometryRenderer { public static final MinecraftGeometryRenderer INSTANCE = new MinecraftGeometryRenderer(); @Override - public boolean render(ItemStack stack, Path path) { + public NativeImage render(ItemStack stack) { TrackingItemStackRenderState itemRenderState = new TrackingItemStackRenderState(); Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stack, ItemDisplayContext.GUI, null, null, 0); itemRenderState.setOversizedInGui(true); @@ -43,13 +40,12 @@ public class MinecraftGeometryRenderer implements GeometryRenderer { //noinspection DataFlowIssue ((PictureInPictureCopyRenderer) itemRenderer).rainbow$allowTextureCopy(); itemRenderer.prepare(oversizedRenderState, new GuiRenderState(), 4); - writeAsPNG(path, ((PictureInPictureRendererAccessor) itemRenderer).getTexture()); + return writeToImage(((PictureInPictureRendererAccessor) itemRenderer).getTexture()); } - return true; } - // Simplified TextureUtil#writeAsPNG with some modifications to flip the image and just generate it at full size - private static void writeAsPNG(Path path, GpuTexture texture) { + // Simplified TextureUtil#writeAsPNG with some modifications to just write to a NativeImage, flip the image and just generate it at full size + private static NativeImage writeToImage(GpuTexture texture) { RenderSystem.assertOnRenderThread(); int width = texture.getWidth(0); int height = texture.getHeight(0); @@ -58,25 +54,21 @@ public class MinecraftGeometryRenderer implements GeometryRenderer { GpuBuffer buffer = RenderSystem.getDevice().createBuffer(() -> "Texture output buffer", GpuBuffer.USAGE_COPY_DST | GpuBuffer.USAGE_MAP_READ, bufferSize); CommandEncoder commandEncoder = RenderSystem.getDevice().createCommandEncoder(); + NativeImage image = new NativeImage(width, height, false); Runnable writer = () -> { try (GpuBuffer.MappedView mappedView = commandEncoder.mapBuffer(buffer, true, false)) { - try (NativeImage nativeImage = new NativeImage(width, height, false)) { - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int colour = mappedView.data().getInt((x + y * width) * texture.getFormat().pixelSize()); - nativeImage.setPixelABGR(x, height - y - 1, colour); - } + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int colour = mappedView.data().getInt((x + y * width) * texture.getFormat().pixelSize()); + image.setPixelABGR(x, height - y - 1, colour); } - - CodecUtil.ensureDirectoryExists(path.getParent()); - nativeImage.writeToFile(path); - } catch (IOException var19) { - // TODO } } buffer.close(); }; commandEncoder.copyTextureToBuffer(texture, buffer, 0, writer, 0); + + return image; } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 9690619..86ddd9a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -123,7 +123,7 @@ public class BedrockItemMapper { bedrockIdentifier = itemModelLocation; } - BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, context.packContext); + BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, context.stack, itemModel, context.packContext); if (context.packContext.reportSuccesses()) { // Not a problem, but just report to get the model printed in the report file context.report("creating mapping for block model " + itemModelLocation); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java index d632638..96ad637 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/PackContext.java @@ -4,5 +4,7 @@ import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; import org.geysermc.rainbow.pack.PackPaths; +import java.util.Optional; + public record PackContext(GeyserMappings mappings, PackPaths paths, BedrockItemConsumer itemConsumer, AssetResolver assetResolver, - GeometryRenderer geometryRenderer, boolean reportSuccesses) {} + Optional geometryRenderer, boolean reportSuccesses) {} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index 56891f6..63aa980 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -5,6 +5,7 @@ import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.resources.model.Material; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.animation.AnimationMapper; @@ -23,7 +24,7 @@ public record BedrockGeometryContext(Optional> geomet .map(ResourceLocation::withDefaultNamespace) .toList(); - public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ResolvedModel model, PackContext context) { + public static BedrockGeometryContext create(ResourceLocation bedrockIdentifier, ItemStack stackToRender, ResolvedModel model, PackContext context) { ResolvedModel parentModel = model.parent(); // debugName() returns the resource location of the model as a string boolean handheld = parentModel != null && HANDHELD_MODELS.contains(ResourceLocation.parse(parentModel.debugName())); @@ -52,7 +53,7 @@ public record BedrockGeometryContext(Optional> geomet })); animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms())); - icon = new TextureHolder(modelLocation); // TODO + icon = new TextureHolder(modelLocation, context.geometryRenderer().map(renderer -> () -> renderer.render(stackToRender))); } return new BedrockGeometryContext(geometry, animation, icon, handheld); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java index 37799d7..4607641 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryRenderer.java @@ -1,10 +1,9 @@ package org.geysermc.rainbow.mapping.geometry; +import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.world.item.ItemStack; -import java.nio.file.Path; - public interface GeometryRenderer { - boolean render(ItemStack stack, Path path); + NativeImage render(ItemStack stack); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java deleted file mode 100644 index 9836042..0000000 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/NoopGeometryRenderer.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.geysermc.rainbow.mapping.geometry; - -import net.minecraft.world.item.ItemStack; - -import java.nio.file.Path; - -public class NoopGeometryRenderer implements GeometryRenderer { - public static final NoopGeometryRenderer INSTANCE = new NoopGeometryRenderer(); - - @Override - public boolean render(ItemStack stack, Path path) { - return false; - } -} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index 847b9e0..237ea63 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -20,19 +20,21 @@ public record BedrockItem(ResourceLocation identifier, String textureName, Bedro public CompletableFuture save(PackSerializer serializer, Path attachableDirectory, Path geometryDirectory, Path animationDirectory, Function> textureSaver) { - return CompletableFuture.supplyAsync(() -> geometryContext.geometry().map(Supplier::get)) - .thenCompose(stitchedGeometry -> { - List attachableTextures = new ArrayList<>(); - Optional createdAttachable = attachableCreator.create(identifier, stitchedGeometry, attachableTextures::add); - return CompletableFuture.allOf( - textureSaver.apply(geometryContext.icon()), - createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()), - CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), - stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), - stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), - geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop()) - ); - }); + return CompletableFuture.allOf( + textureSaver.apply(geometryContext.icon()), + CompletableFuture.supplyAsync(() -> geometryContext.geometry().map(Supplier::get)) + .thenCompose(stitchedGeometry -> { + List attachableTextures = new ArrayList<>(); + Optional createdAttachable = attachableCreator.create(identifier, stitchedGeometry, attachableTextures::add); + return CompletableFuture.allOf( + createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()), + CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), + stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), + stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), + geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop()) + ); + }) + ); } private static CompletableFuture noop() { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index a48b39d..182855b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -19,7 +19,6 @@ import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; -import org.geysermc.rainbow.mapping.geometry.NoopGeometryRenderer; import org.geysermc.rainbow.definition.GeyserMappings; import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.jetbrains.annotations.NotNull; @@ -36,6 +35,7 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import java.util.function.Supplier; import java.util.function.UnaryOperator; public class BedrockPack { @@ -53,7 +53,7 @@ public class BedrockPack { private final ProblemReporter.Collector reporter; public BedrockPack(String name, PackManifest manifest, PackPaths paths, PackSerializer serializer, AssetResolver assetResolver, - GeometryRenderer geometryRenderer, ProblemReporter.Collector reporter, + Optional geometryRenderer, ProblemReporter.Collector reporter, boolean reportSuccesses) { this.name = name; this.manifest = manifest; @@ -206,7 +206,7 @@ public class BedrockPack { private UnaryOperator manifestPath = resolve(MANIFEST_FILE); private UnaryOperator itemAtlasPath = resolve(ITEM_ATLAS_FILE); private Path packZipFile = null; - private GeometryRenderer geometryRenderer = NoopGeometryRenderer.INSTANCE; + private GeometryRenderer geometryRenderer = null; private ProblemReporter.Collector reporter; private boolean reportSuccesses = false; @@ -294,7 +294,7 @@ public class BedrockPack { PackPaths paths = new PackPaths(mappingsPath, packRootPath, attachablesPath.apply(packRootPath), geometryPath.apply(packRootPath), animationPath.apply(packRootPath), manifestPath.apply(packRootPath), itemAtlasPath.apply(packRootPath), Optional.ofNullable(packZipFile)); - return new BedrockPack(name, manifest, paths, packSerializer, assetResolver, geometryRenderer, reporter, reportSuccesses); + return new BedrockPack(name, manifest, paths, packSerializer, assetResolver, Optional.ofNullable(geometryRenderer), reporter, reportSuccesses); } private static UnaryOperator resolve(Path child) { From e95e92253701fc51060f1caa2f00b596afe2dda8 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 08:46:47 +0000 Subject: [PATCH 28/37] Log problems in datagen --- .../geysermc/rainbow/client/PackManager.java | 3 ++- .../rainbow/datagen/RainbowModelProvider.java | 9 ++++++++- .../geysermc/rainbow/pack/BedrockPack.java | 20 ++++++++++++------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index f3e92ab..4cba01b 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -3,6 +3,7 @@ package org.geysermc.rainbow.client; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.components.SplashRenderer; +import net.minecraft.util.ProblemReporter; import net.minecraft.util.RandomSource; import net.minecraft.util.StringUtil; import org.geysermc.rainbow.CodecUtil; @@ -81,7 +82,7 @@ public final class PackManager { } private static String createPackSummary(BedrockPack pack) { - String problems = pack.getReporter().getTreeReport(); + String problems = ((ProblemReporter.Collector) pack.getReporter()).getTreeReport(); if (StringUtil.isBlank(problems)) { problems = "Well that's odd... there's nothing here!"; } diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index c807130..c3c70f3 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -19,14 +19,18 @@ import net.minecraft.data.PackOutput; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.util.ProblemReporter; import net.minecraft.world.item.Item; import net.minecraft.world.item.equipment.EquipmentAsset; +import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockPack; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.BufferedReader; import java.io.IOException; @@ -39,6 +43,8 @@ import java.util.Optional; import java.util.concurrent.CompletableFuture; public abstract class RainbowModelProvider extends FabricModelProvider { + private static final Logger PROBLEM_LOGGER = LoggerFactory.getLogger(Rainbow.MOD_ID); + private final CompletableFuture registries; private final Map, EquipmentClientInfo> equipmentInfos; private final Path outputRoot; @@ -84,7 +90,8 @@ public abstract class RainbowModelProvider extends FabricModelProvider { } protected BedrockPack.Builder createBedrockPack(Path outputRoot, PackSerializer serializer, AssetResolver resolver) { - return BedrockPack.builder("rainbow", outputRoot.resolve("geyser_mappings.json"), outputRoot.resolve("pack"), serializer, resolver); + return BedrockPack.builder("rainbow", outputRoot.resolve("geyser_mappings.json"), outputRoot.resolve("pack"), serializer, resolver) + .withReporter(path -> new ProblemReporter.ScopedCollector(path, PROBLEM_LOGGER)); } protected abstract Item getVanillaItem(Item modded); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 182855b..0cafc54 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -50,10 +50,10 @@ public class BedrockPack { private final IntSet customModelDataMapped = new IntOpenHashSet(); private final PackContext context; - private final ProblemReporter.Collector reporter; + private final ProblemReporter reporter; public BedrockPack(String name, PackManifest manifest, PackPaths paths, PackSerializer serializer, AssetResolver assetResolver, - Optional geometryRenderer, ProblemReporter.Collector reporter, + Optional geometryRenderer, ProblemReporter reporter, boolean reportSuccesses) { this.name = name; this.manifest = manifest; @@ -163,6 +163,12 @@ public class BedrockPack { } } + if (reporter instanceof AutoCloseable closeable) { + try { + closeable.close(); + } catch (Exception ignored) {} + } + return CompletableFuture.allOf(futures.toArray(CompletableFuture[]::new)); } @@ -178,7 +184,7 @@ public class BedrockPack { return itemTextures.build().size(); } - public ProblemReporter.Collector getReporter() { + public ProblemReporter getReporter() { return reporter; } @@ -207,14 +213,14 @@ public class BedrockPack { private UnaryOperator itemAtlasPath = resolve(ITEM_ATLAS_FILE); private Path packZipFile = null; private GeometryRenderer geometryRenderer = null; - private ProblemReporter.Collector reporter; + private Function reporter; private boolean reportSuccesses = false; public Builder(String name, Path mappingsPath, Path packRootPath, PackSerializer packSerializer, AssetResolver assetResolver) { this.name = name; this.mappingsPath = mappingsPath; this.packRootPath = packRootPath; - this.reporter = new ProblemReporter.Collector(() -> "Bedrock pack " + name + " "); + this.reporter = ProblemReporter.Collector::new; this.packSerializer = packSerializer; this.assetResolver = assetResolver; manifest = defaultManifest(name); @@ -280,7 +286,7 @@ public class BedrockPack { return this; } - public Builder withReporter(ProblemReporter.Collector reporter) { + public Builder withReporter(Function reporter) { this.reporter = reporter; return this; } @@ -294,7 +300,7 @@ public class BedrockPack { PackPaths paths = new PackPaths(mappingsPath, packRootPath, attachablesPath.apply(packRootPath), geometryPath.apply(packRootPath), animationPath.apply(packRootPath), manifestPath.apply(packRootPath), itemAtlasPath.apply(packRootPath), Optional.ofNullable(packZipFile)); - return new BedrockPack(name, manifest, paths, packSerializer, assetResolver, Optional.ofNullable(geometryRenderer), reporter, reportSuccesses); + return new BedrockPack(name, manifest, paths, packSerializer, assetResolver, Optional.ofNullable(geometryRenderer), reporter.apply(() -> "Bedrock pack " + name + " "), reportSuccesses); } private static UnaryOperator resolve(Path child) { From 97af13e3bd95e154b650a5c280b41215dbfef20a Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 09:18:34 +0000 Subject: [PATCH 29/37] Properly multiply UV in GeometryMapper, use the right stitched texture in attachables --- .../src/main/resources/rainbow-datagen.accesswidener | 4 ++-- .../rainbow/mapping/attachable/AttachableMapper.java | 6 +++--- .../mapping/geometry/BedrockGeometryContext.java | 2 -- .../rainbow/mapping/geometry/GeometryMapper.java | 11 +++++++++-- .../rainbow/mapping/geometry/StitchedGeometry.java | 5 +++++ .../java/org/geysermc/rainbow/pack/BedrockItem.java | 5 +++-- .../java/org/geysermc/rainbow/pack/BedrockPack.java | 3 +-- .../java/org/geysermc/rainbow/pack/PackManifest.java | 6 ++++++ rainbow/src/main/resources/rainbow.mixins.json | 6 ++---- 9 files changed, 31 insertions(+), 17 deletions(-) create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java diff --git a/datagen/src/main/resources/rainbow-datagen.accesswidener b/datagen/src/main/resources/rainbow-datagen.accesswidener index 71454eb..5ab114f 100644 --- a/datagen/src/main/resources/rainbow-datagen.accesswidener +++ b/datagen/src/main/resources/rainbow-datagen.accesswidener @@ -1,3 +1,3 @@ accessWidener v2 named -transitive-accessible class net/minecraft/client/data/models/ModelProvider$ItemInfoCollector -transitive-accessible class net/minecraft/client/data/models/ModelProvider$SimpleModelCollector +accessible class net/minecraft/client/data/models/ModelProvider$ItemInfoCollector +accessible class net/minecraft/client/data/models/ModelProvider$SimpleModelCollector diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index 4a24acb..80a4a61 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -10,6 +10,7 @@ import net.minecraft.world.entity.EquipmentSlot; import net.minecraft.world.item.equipment.Equippable; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; +import org.geysermc.rainbow.mapping.geometry.StitchedGeometry; import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; @@ -23,8 +24,7 @@ public class AttachableMapper { // Crazy optional statement // Unfortunately we can't have both equippables and custom models, so we prefer the latter :( return (bedrockIdentifier, stitchedGeometry, textureConsumer) -> stitchedGeometry - .map(BedrockGeometryContext.StitchedGeometry::geometry) - .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry.definitions().getFirst(), geometryContext.icon().location().getPath())) + .map(stitched -> BedrockAttachable.geometry(bedrockIdentifier, stitched.geometry().definitions().getFirst(), stitched.stitchedTextures().location().getPath())) .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) .flatMap(optional -> (Optional) optional) .flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info))) @@ -59,6 +59,6 @@ public class AttachableMapper { @FunctionalInterface public interface AttachableCreator { - Optional create(ResourceLocation bedrockIdentifier, Optional geometry, Consumer textureConsumer); + Optional create(ResourceLocation bedrockIdentifier, Optional geometry, Consumer textureConsumer); } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index 63aa980..a86d618 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -58,6 +58,4 @@ public record BedrockGeometryContext(Optional> geomet return new BedrockGeometryContext(geometry, animation, icon, handheld); } - - public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {} } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java index 8722caf..4ae3d2e 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/GeometryMapper.java @@ -81,8 +81,15 @@ public class GeometryMapper { uvSize = new Vector2f(uvs.maxU() - uvs.minU(), uvs.maxV() - uvs.minV()); } - // If the texture was stitched (which it should have been, unless it doesn't exist), offset the UVs by the texture's starting UV - textures.getSprite(face.texture()).ifPresent(sprite -> uvOrigin.add(sprite.getX(), sprite.getY())); + // If the texture was stitched (which it should have been, unless it doesn't exist), s UV values on Java are always in the [0;16] range, adjust the values properly to the texture size, + // and offset the UVs by the texture's starting UV + textures.getSprite(face.texture()).ifPresent(sprite -> { + float widthMultiplier = sprite.contents().width() / 16.0F; + float heightMultiplier = sprite.contents().height() / 16.0F; + uvOrigin.mul(widthMultiplier, heightMultiplier); + uvSize.mul(widthMultiplier, heightMultiplier); + uvOrigin.add(sprite.getX(), sprite.getY()); + }); builder.withFace(direction, uvOrigin, uvSize, face.rotation()); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java new file mode 100644 index 0000000..2ee4f48 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedGeometry.java @@ -0,0 +1,5 @@ +package org.geysermc.rainbow.mapping.geometry; + +import org.geysermc.rainbow.pack.geometry.BedrockGeometry; + +public record StitchedGeometry(BedrockGeometry geometry, TextureHolder stitchedTextures) {} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index 237ea63..b84a3bb 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -5,6 +5,7 @@ import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.attachable.AttachableMapper; import org.geysermc.rainbow.mapping.geometry.BedrockGeometryContext; +import org.geysermc.rainbow.mapping.geometry.StitchedGeometry; import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.geysermc.rainbow.pack.attachable.BedrockAttachable; @@ -29,8 +30,8 @@ public record BedrockItem(ResourceLocation identifier, String textureName, Bedro return CompletableFuture.allOf( createdAttachable.map(attachable -> attachable.save(serializer, attachableDirectory)).orElse(noop()), CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), - stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), - stitchedGeometry.map(BedrockGeometryContext.StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), + stitchedGeometry.map(StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), + stitchedGeometry.map(StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop()) ); }) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 0cafc54..c5a187b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -308,8 +308,7 @@ public class BedrockPack { } private static PackManifest defaultManifest(String name) { - return new PackManifest(new PackManifest.Header(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0), PackConstants.ENGINE_VERSION), - List.of(new PackManifest.Module(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0)))); + return PackManifest.create(name, PackConstants.DEFAULT_PACK_DESCRIPTION, UUID.randomUUID(), BedrockVersion.of(0)); } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/PackManifest.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/PackManifest.java index 456dc52..3611faf 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/PackManifest.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/PackManifest.java @@ -5,6 +5,7 @@ import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.core.UUIDUtil; import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.PackConstants; import java.util.List; import java.util.UUID; @@ -56,5 +57,10 @@ public record PackManifest(Header header, List modules) { return new Module(name, description, uuid, version.increment()); } } + + public static PackManifest create(String name, String description, UUID uuid, BedrockVersion version) { + return new PackManifest(new PackManifest.Header(name, description, uuid, version, PackConstants.ENGINE_VERSION), + List.of(new PackManifest.Module(name, description, uuid, version))); + } } diff --git a/rainbow/src/main/resources/rainbow.mixins.json b/rainbow/src/main/resources/rainbow.mixins.json index c3f5d53..43398d5 100644 --- a/rainbow/src/main/resources/rainbow.mixins.json +++ b/rainbow/src/main/resources/rainbow.mixins.json @@ -4,6 +4,7 @@ "package": "org.geysermc.rainbow.mixin", "compatibilityLevel": "JAVA_21", "client": [ + "FaceBakeryAccessor", "LateBoundIdMapperAccessor", "NativeImageAccessor", "RangeSelectItemModelAccessor", @@ -13,8 +14,5 @@ ], "injectors": { "defaultRequire": 1 - }, - "mixins": [ - "FaceBakeryAccessor" - ] + } } From 58b2c760176e99371782465df77c65cdcd886fd5 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 09:43:04 +0000 Subject: [PATCH 30/37] Fix actions (I think) --- .github/workflows/build.yml | 2 +- client/build.gradle.kts | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 688654e..41fae6e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -87,4 +87,4 @@ jobs: discordWebhook: ${{ secrets.DISCORD_WEBHOOK }} status: ${{ job.status }} body: ${{ steps.metadata.outputs.body }} - includeDownloads: ${{ success() && github.repository == 'GeyserMC/Rainbow' && github.ref_name == 'master' }} + includeDownloads: ${{ github.ref_name == 'master' }} diff --git a/client/build.gradle.kts b/client/build.gradle.kts index 7fa829d..f490a7d 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -1,3 +1,5 @@ +import net.fabricmc.loom.task.RemapJarTask + plugins { id("rainbow.base-conventions") id("rainbow.publish-conventions") @@ -8,3 +10,22 @@ dependencies { implementation(project(path = ":rainbow", configuration = "namedElements")) include(project(":rainbow")) } + +tasks { + val copyJarTask = register("copyRainbowClientJar") { + group = "build" + + val remapJarTask = getByName("remapJar") + dependsOn(remapJarTask) + + from(remapJarTask.archiveFile) + rename { + "Rainbow.jar" + } + into(project.layout.buildDirectory.file("libs")) + } + + named("build") { + dependsOn(copyJarTask) + } +} From 52cd33deffa86b45fca366d09b0006fc73c49b13 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 09:57:29 +0000 Subject: [PATCH 31/37] Export core to rainbow-core.jar, setup publishing in actions --- .github/workflows/build.yml | 7 +++++++ rainbow/gradle.properties | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 41fae6e..d98b79d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,6 +33,13 @@ jobs: env: BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }} + - name: Publish to Maven Repository + if: ${{ success() && github.repository == 'GeyserMC/Rainbow' && github.ref_name == 'master' }} + run: ./gradlew publish + env: + ORG_GRADLE_PROJECT_geysermcUsername: ${{ vars.DEPLOY_USER }} + ORG_GRADLE_PROJECT_geysermcPassword: ${{ secrets.DEPLOY_PASS }} + - name: Archive Artifacts uses: GeyserMC/actions/upload-multi-artifact@master if: success() diff --git a/rainbow/gradle.properties b/rainbow/gradle.properties index 19ae406..048092a 100644 --- a/rainbow/gradle.properties +++ b/rainbow/gradle.properties @@ -1 +1 @@ -archives_base_name=rainbow +archives_base_name=rainbow-core From 51ecbf1fd415006cb2dde48770ee8ad3edf2ab00 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 10:02:27 +0000 Subject: [PATCH 32/37] Take build artifact from client module --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d98b79d..e0494b5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -45,7 +45,7 @@ jobs: if: success() with: artifacts: | - rainbow:build/libs/Rainbow.jar + rainbow:client/build/libs/Rainbow.jar - name: Get Version if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Rainbow' }} @@ -76,7 +76,7 @@ jobs: privateKey: ${{ secrets.DOWNLOADS_PRIVATE_KEY }} host: ${{ secrets.DOWNLOADS_SERVER_IP }} files: | - build/libs/Rainbow.jar + client/build/libs/Rainbow.jar changelog: ${{ steps.metadata.outputs.body }} # - name: Publish to Modrinth From 2cc85d6c91adb022a742803fdeca04cd5d42cd9c Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 12:06:48 +0000 Subject: [PATCH 33/37] Fix 3rd-person animations a bit, maybe --- .../rainbow/mapping/animation/AnimationMapper.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java index 3901502..30a641d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/AnimationMapper.java @@ -6,15 +6,12 @@ import org.geysermc.rainbow.pack.animation.BedrockAnimation; import org.joml.Vector3f; import org.joml.Vector3fc; -// TODO these offset values are completely wrong, I think +// TODO these offset values are still not entirely right, I think public class AnimationMapper { - // These aren't perfect... but I spent over 1.5 hours trying to get these. It's good enough for me. private static final Vector3fc FIRST_PERSON_POSITION_OFFSET = new Vector3f(-7.0F, 22.5F, -7.0F); private static final Vector3fc FIRST_PERSON_ROTATION_OFFSET = new Vector3f(-22.5F, 50.0F, -32.5F); - private static final Vector3fc THIRD_PERSON_POSITION_OFFSET = new Vector3f(0.0F, 13.0F, -3.0F); - private static final Vector3fc THIRD_PERSON_ROTATION_OFFSET = new Vector3f(90.0F, -90.0F, 0.0F); - + // These transformations perfect... but I spent over 3 hours trying to get these. It's good enough for me. public static BedrockAnimationContext mapAnimation(String identifier, String bone, ItemTransforms transforms) { // I don't think it's possible to display separate animations for left- and right hands ItemTransform firstPerson = transforms.firstPersonRightHand(); @@ -23,8 +20,10 @@ public class AnimationMapper { Vector3f firstPersonScale = new Vector3f(firstPerson.scale()); ItemTransform thirdPerson = transforms.thirdPersonRightHand(); - Vector3f thirdPersonPosition = THIRD_PERSON_POSITION_OFFSET.add(thirdPerson.translation(), new Vector3f()); - Vector3f thirdPersonRotation = THIRD_PERSON_ROTATION_OFFSET.add(-thirdPerson.rotation().x(), thirdPerson.rotation().y(), thirdPerson.rotation().z(), new Vector3f()); + // Translation Y/Z axes are swapped on bedrock, bedrock displays the model lower than Java does, and the X/Y axes (Java) is inverted on bedrock + Vector3f thirdPersonPosition = new Vector3f(-thirdPerson.translation().x(), 10.0F + thirdPerson.translation().z(), -thirdPerson.translation().y()); + // Rotation X/Y axes are inverted on bedrock, bedrock needs a +90-degree rotation on the X axis, and I couldn't figure out how the Z axis works + Vector3f thirdPersonRotation = new Vector3f(-thirdPerson.rotation().x() + 90.0F, -thirdPerson.rotation().y(), 0.0F); Vector3f thirdPersonScale = new Vector3f(thirdPerson.scale()); return new BedrockAnimationContext(BedrockAnimation.builder() From 3296b5a59e24cbb346e8602a4be94a9e7a498698 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 14:07:22 +0000 Subject: [PATCH 34/37] More final tweaks --- .../client/MinecraftPackSerializer.java | 28 +++------- .../geysermc/rainbow/client/PackManager.java | 9 ++-- .../rainbow/datagen/RainbowModelProvider.java | 12 ++--- .../java/org/geysermc/rainbow/Rainbow.java | 3 +- .../java/org/geysermc/rainbow/RainbowIO.java | 51 +++++++++++++++++++ .../definition/GeyserBaseDefinition.java | 2 +- .../rainbow/mapping/BedrockItemMapper.java | 6 +-- .../geometry/BedrockGeometryContext.java | 2 +- .../mapping/geometry/StitchedTextures.java | 25 +++++---- .../geysermc/rainbow/pack/BedrockItem.java | 2 +- .../geysermc/rainbow/pack/BedrockPack.java | 25 +++------ .../pack/attachable/BedrockAttachable.java | 2 +- 12 files changed, 94 insertions(+), 73 deletions(-) create mode 100644 rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java index 61c1e39..f7fdbaa 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java @@ -8,14 +8,11 @@ import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.core.HolderLookup; import net.minecraft.resources.RegistryOps; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.packs.resources.ResourceManager; -import org.apache.commons.io.IOUtils; import org.geysermc.rainbow.CodecUtil; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.PackSerializer; import java.io.FileOutputStream; -import java.io.IOException; import java.io.OutputStream; import java.nio.file.Path; import java.util.Objects; @@ -31,26 +28,17 @@ public class MinecraftPackSerializer implements PackSerializer { @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { DynamicOps ops = RegistryOps.create(JsonOps.INSTANCE, registries); - return CompletableFuture.runAsync(() -> { - try { - CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops); - } catch (IOException exception) { - // TODO log - } - }, Util.backgroundExecutor().forName("PackSerializer-saveJson")); + return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> CodecUtil.trySaveJson(codec, object, path.resolveSibling(path.getFileName() + ".json"), ops)), + Util.backgroundExecutor().forName("PackSerializer-saveJson")); } @Override public CompletableFuture saveTexture(byte[] texture, Path path) { - return CompletableFuture.runAsync(() -> { - try { - CodecUtil.ensureDirectoryExists(path.getParent()); - try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { - outputTexture.write(texture); - } - } catch (IOException exception) { - // TODO log + return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> { + CodecUtil.ensureDirectoryExists(path.getParent()); + try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { + outputTexture.write(texture); } - }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); + }), Util.backgroundExecutor().forName("PackSerializer-saveTexture")); } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java index 4cba01b..172e576 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -8,6 +8,7 @@ import net.minecraft.util.RandomSource; import net.minecraft.util.StringUtil; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.client.mixin.SplashRendererAccessor; import org.geysermc.rainbow.client.render.MinecraftGeometryRenderer; import org.geysermc.rainbow.pack.BedrockItem; @@ -24,7 +25,7 @@ import java.util.function.Consumer; public final class PackManager { private static final List PACK_SUMMARY_COMMENTS = List.of("Use the custom item API v2 build!", "bugrock moment", "RORY", - "use !!plshelp", "rm -rf --no-preserve-root /*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!", + "use !!plshelp", "*message was deleted*", "welcome to the internet!", "beep beep. boop boop?", "FROG", "it is frog day", "it is cat day!", "eclipse will hear about this.", "you must now say the word 'frog' in the #general channel", "You Just Lost The Game", "you are now breathing manually", "you are now blinking manually", "you're eligible for a free hug token! <3", "don't mind me!", "hissss", "Gayser and Floodgayte, my favourite plugins.", "meow", "we'll be done here soon™", "got anything else to say?", "we're done now!", "this will be fixed by v6053", "expect it to be done within 180 business days!", @@ -69,11 +70,7 @@ public final class PackManager { public boolean finish() { currentPack.map(pack -> { - try { - Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack)); - } catch (IOException exception) { - // TODO log - } + RainbowIO.safeIO(() -> Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack))); return pack.save(); }).ifPresent(CompletableFuture::join); boolean wasPresent = currentPack.isPresent(); diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index c3c70f3..b009858 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -23,6 +23,7 @@ import net.minecraft.util.ProblemReporter; import net.minecraft.world.item.Item; import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockPack; @@ -125,7 +126,7 @@ public abstract class RainbowModelProvider extends FabricModelProvider { try { output.writeIfNeeded(path, texture, HashCode.fromBytes(texture)); } catch (IOException exception) { - LOGGER.error("Failed to save file to {}", path, exception); + LOGGER.error("Failed to save texture to {}", path, exception); } }, Util.backgroundExecutor().forName("PackSerializer-saveTexture")); } @@ -153,12 +154,11 @@ public abstract class RainbowModelProvider extends FabricModelProvider { public Optional getResolvedModel(ResourceLocation location) { return resolvedModelCache.computeIfAbsent(location, key -> Optional.ofNullable(models.get(location)) .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) - .or(() -> { + .or(() -> RainbowIO.safeIO(() -> { try (BufferedReader reader = resourceManager.openAsReader(location.withPrefix("models/").withSuffix(".json"))) { - return Optional.of(BlockModel.fromStream(reader)); - } catch (IOException ignored) {} - return Optional.empty(); - }) + return BlockModel.fromStream(reader); + } + })) .map(model -> new ResolvedModel() { @Override public @NotNull UnbakedModel wrapped() { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java index aa3352d..f2e80b1 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -14,8 +14,7 @@ public class Rainbow { return ResourceLocation.fromNamespaceAndPath(MOD_ID, path); } - // TODO rename remove file - public static String fileSafeResourceLocation(ResourceLocation location) { + public static String safeResourceLocation(ResourceLocation location) { return location.toString().replace(':', '.').replace('/', '_'); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java new file mode 100644 index 0000000..e2f7cbe --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java @@ -0,0 +1,51 @@ +package org.geysermc.rainbow; + +import com.mojang.logging.LogUtils; +import org.slf4j.Logger; + +import java.io.IOException; +import java.util.Optional; + +public final class RainbowIO { + private static final Logger LOGGER = LogUtils.getLogger(); + + private RainbowIO() {} + + public static T safeIO(IOSupplier supplier, T defaultValue) { + try { + return supplier.get(); + } catch (IOException exception) { + LOGGER.error("Failed to perform IO operation!", exception); + return defaultValue; + } + } + + public static Optional safeIO(IOSupplier supplier) { + try { + return Optional.ofNullable(supplier.get()); + } catch (IOException exception) { + LOGGER.error("Failed to perform IO operation!", exception); + return Optional.empty(); + } + } + + public static void safeIO(IORunnable runnable) { + try { + runnable.run(); + } catch (IOException exception) { + LOGGER.error("Failed to perform IO operation!", exception); + } + } + + @FunctionalInterface + public interface IOSupplier { + + T get() throws IOException; + } + + @FunctionalInterface + public interface IORunnable { + + void run() throws IOException; + } +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java index 304c011..66f3f04 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java @@ -61,7 +61,7 @@ public record GeyserBaseDefinition(ResourceLocation bedrockIdentifier, Optional< } public String textureName() { - return bedrockOptions.icon.orElse(Rainbow.fileSafeResourceLocation(bedrockIdentifier)); + return bedrockOptions.icon.orElse(Rainbow.safeResourceLocation(bedrockIdentifier)); } public record BedrockOptions(Optional icon, boolean allowOffhand, boolean displayHandheld, int protectionValue, List tags) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index 86ddd9a..c911d19 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -71,9 +71,9 @@ public class BedrockItemMapper { } public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) { - // TODO Improve this, use resouce log in problemreporter - ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(stack.get(DataComponents.ITEM_MODEL)).map(ClientItem::model).orElseThrow(); - ProblemReporter childReporter = reporter.forChild(() -> "item model " + vanillaModel + " with custom model data " + customModelData + " "); + ResourceLocation itemModel = stack.get(DataComponents.ITEM_MODEL); + ItemModel.Unbaked vanillaModel = context.assetResolver().getClientItem(itemModel).map(ClientItem::model).orElseThrow(); + ProblemReporter childReporter = reporter.forChild(() -> "item model " + itemModel + " with custom model data " + customModelData + " "); if (vanillaModel instanceof RangeSelectItemModel.Unbaked(RangeSelectItemModelProperty property, float scale, List entries, Optional fallback)) { // WHY, Mojang? if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index a86d618..3d306b1 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -44,7 +44,7 @@ public record BedrockGeometryContext(Optional> geomet // This check should probably be done differently (actually check if the model is 2D or 3D) ResourceLocation modelLocation = ResourceLocation.parse(model.debugName()); - String safeIdentifier = Rainbow.fileSafeResourceLocation(bedrockIdentifier); + String safeIdentifier = Rainbow.safeResourceLocation(bedrockIdentifier); geometry = Optional.of(Suppliers.memoize(() -> { StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java index eff4435..1ce8e85 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/StitchedTextures.java @@ -2,7 +2,6 @@ package org.geysermc.rainbow.mapping.geometry; import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.Util; -import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.block.model.TextureSlots; import net.minecraft.client.renderer.texture.SpriteContents; import net.minecraft.client.renderer.texture.SpriteLoader; @@ -12,12 +11,12 @@ import net.minecraft.client.resources.model.Material; import net.minecraft.data.AtlasIds; import net.minecraft.resources.ResourceLocation; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mixin.SpriteContentsAccessor; import org.geysermc.rainbow.mixin.SpriteLoaderAccessor; import org.geysermc.rainbow.mixin.TextureSlotsAccessor; -import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.List; @@ -50,20 +49,20 @@ public record StitchedTextures(Map sprites, Supplier // Atlas ID doesn't matter much here, but BLOCKS is the most appropriate // Not sure if 1024 should be the max supported texture size, but it seems to work SpriteLoader spriteLoader = new SpriteLoader(AtlasIds.BLOCKS, 1024, 16, 16); - List sprites = textures.distinct().map(texture -> readSpriteContents(texture, context)).toList(); + List sprites = textures.distinct() + .map(texture -> readSpriteContents(texture, context)) + .mapMulti(Optional::ifPresent) + .toList(); return ((SpriteLoaderAccessor) spriteLoader).invokeStitch(sprites, 0, Util.backgroundExecutor()); } - private static SpriteContents readSpriteContents(ResourceLocation location, PackContext context) { - // TODO decorate path util - // TODO don't use ResourceManager - // TODO IO is on main thread here? - try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) { - NativeImage texture = NativeImage.read(textureStream); - return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); - } catch (IOException exception) { - throw new RuntimeException(exception); - } + private static Optional readSpriteContents(ResourceLocation location, PackContext context) { + return RainbowIO.safeIO(() -> { + try (InputStream textureStream = context.assetResolver().openAsset(Rainbow.decorateTextureLocation(location))) { + NativeImage texture = NativeImage.read(textureStream); + return new SpriteContents(location, new FrameSize(texture.getWidth(), texture.getHeight()), texture); + } + }); } private static NativeImage stitchTextureAtlas(SpriteLoader.Preparations preparations) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java index b84a3bb..5481ac0 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockItem.java @@ -32,7 +32,7 @@ public record BedrockItem(ResourceLocation identifier, String textureName, Bedro CompletableFuture.allOf(attachableTextures.stream().map(textureSaver).toArray(CompletableFuture[]::new)), stitchedGeometry.map(StitchedGeometry::geometry).map(geometry -> geometry.save(serializer, geometryDirectory)).orElse(noop()), stitchedGeometry.map(StitchedGeometry::stitchedTextures).map(textureSaver).orElse(noop()), - geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.fileSafeResourceLocation(identifier))).orElse(noop()) + geometryContext.animation().map(context -> context.animation().save(serializer, animationDirectory, Rainbow.safeResourceLocation(identifier))).orElse(noop()) ); }) ); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index c5a187b..5959868 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -13,6 +13,7 @@ import net.minecraft.world.item.component.CustomModelData; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.image.NativeImageUtil; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; @@ -131,22 +132,12 @@ public class BedrockPack { Function> textureSaver = texture -> { ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); return texture.supplier() - .flatMap(image -> { - try { - return Optional.of(NativeImageUtil.writeToByteArray(image.get())); - } catch (IOException exception) { - // TODO log - return Optional.empty(); - } - }) - .or(() -> { + .flatMap(image -> RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(image.get()))) + .or(() -> RainbowIO.safeIO(() -> { try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) { - return Optional.of(textureStream.readAllBytes()); - } catch (IOException exception) { - // TODO log - return Optional.empty(); + return textureStream.readAllBytes(); } - }) + })) .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) .orElse(CompletableFuture.completedFuture(null)); }; @@ -156,11 +147,7 @@ public class BedrockPack { } if (paths.zipOutput().isPresent()) { - try { - CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get()); - } catch (IOException exception) { - // TODO log - } + RainbowIO.safeIO(() -> CodecUtil.tryZipDirectory(paths.packRoot(), paths.zipOutput().get())); } if (reporter instanceof AutoCloseable closeable) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java index 32b95d0..1178962 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/attachable/BedrockAttachable.java @@ -37,7 +37,7 @@ public record BedrockAttachable(BedrockVersion formatVersion, AttachableInfo inf public CompletableFuture save(PackSerializer serializer, Path attachablesDirectory) { // Get a safe attachable path by using Geyser's way of getting icons - return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.fileSafeResourceLocation(info.identifier) + ".json")); + return serializer.saveJson(CODEC, this, attachablesDirectory.resolve(Rainbow.safeResourceLocation(info.identifier) + ".json")); } public static Builder builder(ResourceLocation identifier) { From 6e1d6067d440624a2131a4497d71ca1b02c15254 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 14:21:05 +0000 Subject: [PATCH 35/37] Display toast upon IO exception on client --- .../rainbow/client/RainbowClient.java | 3 ++ .../client/RainbowClientIOHandler.java | 18 ++++++++++ .../resources/assets/rainbow/lang/en_us.json | 4 ++- .../java/org/geysermc/rainbow/RainbowIO.java | 34 ++++++++++++------- 4 files changed, 45 insertions(+), 14 deletions(-) create mode 100644 client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java index 1ef7cea..131846b 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClient.java @@ -6,6 +6,7 @@ import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents; import net.fabricmc.fabric.api.command.v2.ArgumentTypeRegistry; import net.minecraft.commands.synchronization.SingletonArgumentInfo; import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.client.command.CommandSuggestionsArgumentType; import org.geysermc.rainbow.client.command.PackGeneratorCommand; import org.geysermc.rainbow.client.mapper.PackMapper; @@ -23,5 +24,7 @@ public class RainbowClient implements ClientModInitializer { ArgumentTypeRegistry.registerArgumentType(Rainbow.getModdedLocation("command_suggestions"), CommandSuggestionsArgumentType.class, SingletonArgumentInfo.contextFree(CommandSuggestionsArgumentType::new)); + + RainbowIO.registerExceptionListener(new RainbowClientIOHandler()); } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java b/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java new file mode 100644 index 0000000..f31f1f8 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/RainbowClientIOHandler.java @@ -0,0 +1,18 @@ +package org.geysermc.rainbow.client; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.components.toasts.SystemToast; +import net.minecraft.network.chat.Component; +import org.geysermc.rainbow.RainbowIO; + +import java.io.IOException; + +public class RainbowClientIOHandler implements RainbowIO.IOExceptionListener { + private static final SystemToast.SystemToastId TOAST_ID = new SystemToast.SystemToastId(); + + @Override + public void error(IOException exception) { + Minecraft.getInstance().getToastManager().addToast(new SystemToast(TOAST_ID, + Component.translatable("toast.rainbow.io_exception.title"), Component.translatable("toast.rainbow.io_exception.description"))); + } +} diff --git a/client/src/main/resources/assets/rainbow/lang/en_us.json b/client/src/main/resources/assets/rainbow/lang/en_us.json index 7fe4de1..cc89808 100644 --- a/client/src/main/resources/assets/rainbow/lang/en_us.json +++ b/client/src/main/resources/assets/rainbow/lang/en_us.json @@ -13,5 +13,7 @@ "commands.rainbow.pack_created": "Created pack with name %s", "commands.rainbow.pack_finished_error": "Errors occurred whilst writing the pack to disk!", "commands.rainbow.pack_finished_successfully": "Wrote pack to disk", - "commands.rainbow.stopped_automatic_mapping": "Stopped automatic mapping of custom items" + "commands.rainbow.stopped_automatic_mapping": "Stopped automatic mapping of custom items", + "toast.rainbow.io_exception.description": "Please check your game logs for more information", + "toast.rainbow.io_exception.title": "A filesystem error occurred in Rainbow!" } \ No newline at end of file diff --git a/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java index e2f7cbe..5068ef6 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java @@ -4,37 +4,39 @@ import com.mojang.logging.LogUtils; import org.slf4j.Logger; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Optional; public final class RainbowIO { private static final Logger LOGGER = LogUtils.getLogger(); + private static final List listeners = new ArrayList<>(); private RainbowIO() {} - public static T safeIO(IOSupplier supplier, T defaultValue) { - try { - return supplier.get(); - } catch (IOException exception) { - LOGGER.error("Failed to perform IO operation!", exception); - return defaultValue; - } - } - public static Optional safeIO(IOSupplier supplier) { try { return Optional.ofNullable(supplier.get()); } catch (IOException exception) { LOGGER.error("Failed to perform IO operation!", exception); + listeners.forEach(listener -> listener.error(exception)); return Optional.empty(); } } + public static T safeIO(IOSupplier supplier, T defaultValue) { + return safeIO(supplier).orElse(defaultValue); + } + public static void safeIO(IORunnable runnable) { - try { + safeIO(() -> { runnable.run(); - } catch (IOException exception) { - LOGGER.error("Failed to perform IO operation!", exception); - } + return null; + }); + } + + public static void registerExceptionListener(IOExceptionListener listener) { + listeners.add(listener); } @FunctionalInterface @@ -48,4 +50,10 @@ public final class RainbowIO { void run() throws IOException; } + + @FunctionalInterface + public interface IOExceptionListener { + + void error(IOException exception); + } } From 8c5770117632c5ac18651bd465357f9361950ea2 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 15:09:04 +0000 Subject: [PATCH 36/37] Get rid of intermediary names in reports, don't export 3D icon if there is no renderer --- .../rainbow/datagen/RainbowModelProvider.java | 9 ++++- .../rainbow/mapping/BedrockItemMapper.java | 21 +++++++----- .../mapping/attachable/AttachableMapper.java | 2 +- .../geometry/BedrockGeometryContext.java | 7 ++-- .../mapping/geometry/TextureHolder.java | 34 ++++++++++++++++--- .../geysermc/rainbow/pack/BedrockPack.java | 12 +------ 6 files changed, 56 insertions(+), 29 deletions(-) diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java index b009858..b6496d2 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -7,6 +7,7 @@ import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.minecraft.Util; import net.minecraft.client.data.models.model.ModelInstance; import net.minecraft.client.renderer.block.model.BlockModel; +import net.minecraft.client.renderer.block.model.ItemModelGenerator; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.client.resources.model.ResolvedModel; @@ -153,7 +154,13 @@ public abstract class RainbowModelProvider extends FabricModelProvider { @Override public Optional getResolvedModel(ResourceLocation location) { return resolvedModelCache.computeIfAbsent(location, key -> Optional.ofNullable(models.get(location)) - .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) + .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) + .or(() -> { + if (location.equals(ItemModelGenerator.GENERATED_ITEM_MODEL_ID)) { + return Optional.of(new ItemModelGenerator()); + } + return Optional.empty(); + }) .or(() -> RainbowIO.safeIO(() -> { try (BufferedReader reader = resourceManager.openAsReader(location.withPrefix("models/").withSuffix(".json"))) { return BlockModel.fromStream(reader); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java index c911d19..8d7747a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -8,23 +8,27 @@ import net.minecraft.client.renderer.item.ItemModels; import net.minecraft.client.renderer.item.RangeSelectItemModel; import net.minecraft.client.renderer.item.SelectItemModel; import net.minecraft.client.renderer.item.properties.conditional.Broken; +import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperties; +import net.minecraft.client.renderer.item.properties.conditional.ConditionalItemModelProperty; import net.minecraft.client.renderer.item.properties.conditional.CustomModelDataProperty; import net.minecraft.client.renderer.item.properties.conditional.Damaged; import net.minecraft.client.renderer.item.properties.conditional.FishingRodCast; import net.minecraft.client.renderer.item.properties.conditional.HasComponent; -import net.minecraft.client.renderer.item.properties.conditional.ItemModelPropertyTest; import net.minecraft.client.renderer.item.properties.numeric.BundleFullness; import net.minecraft.client.renderer.item.properties.numeric.Count; import net.minecraft.client.renderer.item.properties.numeric.Damage; +import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperties; import net.minecraft.client.renderer.item.properties.numeric.RangeSelectItemModelProperty; import net.minecraft.client.renderer.item.properties.select.Charge; import net.minecraft.client.renderer.item.properties.select.ContextDimension; import net.minecraft.client.renderer.item.properties.select.DisplayContext; +import net.minecraft.client.renderer.item.properties.select.SelectItemModelProperties; import net.minecraft.client.renderer.item.properties.select.TrimMaterialProperty; import net.minecraft.core.component.DataComponents; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.ItemTags; +import net.minecraft.util.ExtraCodecs; import net.minecraft.util.ProblemReporter; import net.minecraft.world.entity.ai.attributes.AttributeModifier; import net.minecraft.world.entity.ai.attributes.Attributes; @@ -59,9 +63,10 @@ public class BedrockItemMapper { .map(ResourceLocation::withDefaultNamespace) .toList(); - private static ResourceLocation getModelId(ItemModel.Unbaked model) { + private static ResourceLocation getId(ExtraCodecs.LateBoundIdMapper mapper, + T type) { //noinspection unchecked - return ((LateBoundIdMapperAccessor) ItemModels.ID_MAPPER).getIdToValue().inverse().get(model.type()); + return ((LateBoundIdMapperAccessor) mapper).getIdToValue().inverse().get(type); } public static void tryMapStack(ItemStack stack, ResourceLocation modelLocation, ProblemReporter reporter, PackContext context) { @@ -107,7 +112,7 @@ public class BedrockItemMapper { case ConditionalItemModel.Unbaked conditional -> mapConditionalModel(conditional, context.child("condition model ")); case RangeSelectItemModel.Unbaked rangeSelect -> mapRangeSelectModel(rangeSelect, context.child("range select model ")); case SelectItemModel.Unbaked select -> mapSelectModel(select, context.child("select model ")); - default -> context.report("unsupported item model " + getModelId(model)); + default -> context.report("unsupported item model " + getId(ItemModels.ID_MAPPER, model.type())); } } @@ -133,7 +138,7 @@ public class BedrockItemMapper { } private static void mapConditionalModel(ConditionalItemModel.Unbaked model, MappingContext context) { - ItemModelPropertyTest property = model.property(); + ConditionalItemModelProperty property = model.property(); GeyserConditionPredicate.Property predicateProperty = switch (property) { case Broken ignored -> GeyserConditionPredicate.BROKEN; case Damaged ignored -> GeyserConditionPredicate.DAMAGED; @@ -146,7 +151,7 @@ public class BedrockItemMapper { ItemModel.Unbaked onFalse = model.onFalse(); if (predicateProperty == null) { - context.report("unsupported conditional model property " + property + ", only mapping on_false"); + context.report("unsupported conditional model property " + getId(ConditionalItemModelProperties.ID_MAPPER, property.type()) + ", only mapping on_false"); mapItem(onFalse, context.child("condition on_false (unsupported property)")); return; } @@ -167,7 +172,7 @@ public class BedrockItemMapper { }; if (predicateProperty == null) { - context.report("unsupported range dispatch model property " + property + ", only mapping fallback, if it is present"); + context.report("unsupported range dispatch model property " + getId(RangeSelectItemModelProperties.ID_MAPPER, property.type()) + ", only mapping fallback, if it is present"); } else { for (RangeSelectItemModel.Entry entry : model.entries()) { mapItem(entry.model(), context.with(new GeyserRangeDispatchPredicate(predicateProperty, entry.threshold(), model.scale()), "threshold " + entry.threshold())); @@ -201,7 +206,7 @@ public class BedrockItemMapper { } } } - context.report("unsupported select model property " + unbakedSwitch.property() + ", only mapping fallback, if present"); + context.report("unsupported select model property " + getId(SelectItemModelProperties.ID_MAPPER, unbakedSwitch.property().type()) + ", only mapping fallback, if present"); model.fallback().ifPresent(fallback -> mapItem(fallback, context.child("select fallback case (unsupported property) "))); return; } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java index 80a4a61..7b9579b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/AttachableMapper.java @@ -34,7 +34,7 @@ public class AttachableMapper { .filter(assetInfo -> !assetInfo.getSecond().isEmpty()) .map(assetInfo -> { ResourceLocation equipmentTexture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst())); - textureConsumer.accept(new TextureHolder(equipmentTexture)); + textureConsumer.accept(TextureHolder.createFromResources(equipmentTexture)); return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), equipmentTexture.getPath()); })) .map(attachable -> { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java index 3d306b1..25730ff 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/BedrockGeometryContext.java @@ -38,7 +38,7 @@ public record BedrockGeometryContext(Optional> geomet if (layer0Texture != null) { geometry = Optional.empty(); animation = Optional.empty(); - icon = new TextureHolder(layer0Texture.texture()); + icon = TextureHolder.createFromResources(layer0Texture.texture()); } else { // Unknown model (doesn't use layer0), so we immediately assume the geometry is custom // This check should probably be done differently (actually check if the model is 2D or 3D) @@ -49,11 +49,12 @@ public record BedrockGeometryContext(Optional> geomet geometry = Optional.of(Suppliers.memoize(() -> { StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(textures, context); BedrockGeometry mappedGeometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); - return new StitchedGeometry(mappedGeometry, new TextureHolder(modelLocation.withSuffix("_stitched"), stitchedTextures.stitched())); + return new StitchedGeometry(mappedGeometry, TextureHolder.createProvided(modelLocation.withSuffix("_stitched"), stitchedTextures.stitched())); })); animation = Optional.of(AnimationMapper.mapAnimation(safeIdentifier, "bone", model.getTopTransforms())); - icon = new TextureHolder(modelLocation, context.geometryRenderer().map(renderer -> () -> renderer.render(stackToRender))); + icon = context.geometryRenderer().isPresent() ? TextureHolder.createProvided(modelLocation, () -> context.geometryRenderer().orElseThrow().render(stackToRender)) + : TextureHolder.createNonExistent(modelLocation); } return new BedrockGeometryContext(geometry, animation, icon, handheld); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java index 10813ca..c209741 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/TextureHolder.java @@ -2,17 +2,41 @@ package org.geysermc.rainbow.mapping.geometry; import com.mojang.blaze3d.platform.NativeImage; import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.ProblemReporter; +import org.geysermc.rainbow.Rainbow; +import org.geysermc.rainbow.RainbowIO; +import org.geysermc.rainbow.image.NativeImageUtil; +import org.geysermc.rainbow.mapping.AssetResolver; +import java.io.InputStream; import java.util.Optional; import java.util.function.Supplier; -public record TextureHolder(ResourceLocation location, Optional> supplier) { +public record TextureHolder(ResourceLocation location, Optional> supplier, boolean existsInResources) { - public TextureHolder(ResourceLocation location, Supplier supplier) { - this(location, Optional.of(supplier)); + public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { + if (existsInResources) { + return RainbowIO.safeIO(() -> { + try (InputStream texture = assetResolver.openAsset(Rainbow.decorateTextureLocation(location))) { + return texture.readAllBytes(); + } + }); + } else if (supplier.isPresent()) { + return RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(supplier.get().get())); + } + reporter.report(() -> "missing texture for " + location + "; please provide it manually"); + return Optional.empty(); } - public TextureHolder(ResourceLocation location) { - this(location, Optional.empty()); + public static TextureHolder createProvided(ResourceLocation location, Supplier supplier) { + return new TextureHolder(location, Optional.of(supplier), false); + } + + public static TextureHolder createFromResources(ResourceLocation location) { + return new TextureHolder(location, Optional.empty(), true); + } + + public static TextureHolder createNonExistent(ResourceLocation location) { + return new TextureHolder(location, Optional.empty(), false); } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java index 5959868..9a38d69 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/BedrockPack.java @@ -14,7 +14,6 @@ import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.PackConstants; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.RainbowIO; -import org.geysermc.rainbow.image.NativeImageUtil; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.BedrockItemMapper; import org.geysermc.rainbow.mapping.PackContext; @@ -24,8 +23,6 @@ import org.geysermc.rainbow.definition.GeyserMappings; import org.geysermc.rainbow.mapping.geometry.TextureHolder; import org.jetbrains.annotations.NotNull; -import java.io.IOException; -import java.io.InputStream; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashSet; @@ -36,7 +33,6 @@ import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; -import java.util.function.Supplier; import java.util.function.UnaryOperator; public class BedrockPack { @@ -131,13 +127,7 @@ public class BedrockPack { Function> textureSaver = texture -> { ResourceLocation textureLocation = Rainbow.decorateTextureLocation(texture.location()); - return texture.supplier() - .flatMap(image -> RainbowIO.safeIO(() -> NativeImageUtil.writeToByteArray(image.get()))) - .or(() -> RainbowIO.safeIO(() -> { - try (InputStream textureStream = context.assetResolver().openAsset(textureLocation)) { - return textureStream.readAllBytes(); - } - })) + return texture.load(context.assetResolver(), reporter) .map(bytes -> serializer.saveTexture(bytes, paths.packRoot().resolve(textureLocation.getPath()))) .orElse(CompletableFuture.completedFuture(null)); }; From b2816770671d66e6509b3b883eb20efe5536cfb7 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 16 Oct 2025 15:19:08 +0000 Subject: [PATCH 37/37] Switch publishing to opencollab repo --- .../src/main/kotlin/rainbow.publish-conventions.gradle.kts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts b/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts index b72e89f..a434afb 100644 --- a/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/rainbow.publish-conventions.gradle.kts @@ -7,11 +7,11 @@ val archivesBaseName = properties["archives_base_name"]!! as String publishing { repositories { maven { - name = "eclipseisoffline" + name = "geysermc" url = uri( when { - version.toString().endsWith("-SNAPSHOT") -> "https://maven.eclipseisoffline.xyz/snapshots" - else -> "https://maven.eclipseisoffline.xyz/releases" + version.toString().endsWith("-SNAPSHOT") -> "https://repo.opencollab.dev/maven-snapshots" + else -> "https://repo.opencollab.dev/maven-releases" } ) credentials(PasswordCredentials::class)