diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cf9095f..335b660 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -26,7 +26,7 @@ jobs: - name: Setup Gradle uses: GeyserMC/actions/setup-gradle-composite@master with: - setup-java_java-version: 21 + setup-java_java-version: 25 - name: Build Rainbow run: ./gradlew build @@ -80,13 +80,13 @@ jobs: client/build/libs/Rainbow.jar changelog: ${{ steps.metadata.outputs.body }} -# - name: Publish to Modrinth -# if: ${{ success() && github.repository == 'GeyserMC/rainbow' && github.ref_name == 'master' }} -# env: -# CHANGELOG: ${{ steps.metadata.outputs.body }} -# BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }} -# MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} -# run: ./gradlew modrinth + - name: Publish to Modrinth + if: ${{ success() && github.repository == 'GeyserMC/Rainbow' && github.ref_name == 'master' }} + env: + CHANGELOG: ${{ steps.metadata.outputs.body }} + BUILD_NUMBER: ${{ steps.release-info.outputs.curentRelease }} + MODRINTH_TOKEN: ${{ secrets.MODRINTH_TOKEN }} + run: ./gradlew modrinth - name: Notify Discord if: ${{ (success() || failure()) && github.repository == 'GeyserMC/Rainbow' }} diff --git a/LICENSE b/LICENSE index c34824c..432ec9e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2025 GeyserMC +Copyright (c) 2025-2026 GeyserMC Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 4d0297e..da1dbe0 100644 --- a/README.md +++ b/README.md @@ -4,17 +4,11 @@ [![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.9 and 1.21.10. +for use with Geyser's [custom item API (v2)](https://geysermc.org/wiki/geyser/custom-items). Rainbow is available for Minecraft 26.1. -Rainbow is currently experimental and capable of the following: - -- Generating Geyser item mappings complete with data components and proper bedrock options, by detecting items with a custom `minecraft:item_model` component and analysing their components. - - Also includes generating mappings with predicates for more complicated Java item model definitions, such as checks for if an item is broken. - - Does not support range dispatch predicates yet. - - Also includes detecting if an item should be displayed handheld by looking at the item's model. -- Generating a simple bedrock resourcepack for simple 2D items, as well as: - - Simple custom armour items, by analysing an item's `minecraft:equippable` component and loaded equipment assets. - - 3D items (unlikely to work well as of now), by converting the Java model to a bedrock one, and generating an attachable and animations for it, as well as rendering a custom GUI icon. +Rainbow is currently experimental, and only capable of generating Geyser item mappings and bedrock resourcepacks for +somewhat simple 2D and 3D items. Features like animated textures are not yet supported. For a more descriptive list +of Rainbow's capabilities, see further below. Rainbow works by detecting custom items in your inventory, or a container/inventory menu you have opened. It analyses the components of detected items, and uses assets from loaded Java resourcepacks to gather information about item models, textures, @@ -22,12 +16,9 @@ and more. ## Usage -You can download the latest version of Rainbow [here](https://download.geysermc.org/v2/projects/rainbow/versions/latest/builds/latest/downloads/rainbow). - -To use Rainbow's generated item mappings, you must use a build of Geyser with support for the v2 item mappings format. -You can download those [here](https://github.com/geyserMC/geyser/pull/5189). +You can download the latest version of Rainbow [here](https://download.geysermc.org/v2/projects/rainbow/versions/latest/builds/latest/downloads/rainbow). Using Rainbow requires the [Fabric API](https://modrinth.com/mod/fabric-api) to be installed. -To use Rainbow itself, you must install it on your Minecraft client. Rainbow adds a few commands to the client. Generally, +To use Rainbow, you must install it on your Minecraft client. Rainbow adds a few commands to the client. Generally, you use them as follows: 1. First, start a new pack by running `/rainbow create `, replacing `` with the name of your pack. Your resourcepack and item mappings will be exported in the `.minecraft/rainbow/` folder. Anything in here can be overwritten! @@ -48,3 +39,34 @@ Once you have taken these steps, restart your server. Bedrock players should the and if everything went well, they should be able to see custom items! If you have any questions or run into any problems, please do feel free to ask for support in the Geyser Discord! + +## What Rainbow can do + +Rainbow is currently capable of the following: + +- Generating Geyser item mappings complete with data components and proper bedrock options, by detecting items with a custom `minecraft:item_model` component and analysing their components. + - Also includes generating mappings with predicates for more complicated Java item model definitions, such as checks for if an item is broken. The following definition types are currently supported by Rainbow: + - Plain item model definitions. + - Conditional item models, supported properties are: + - `broken`, + - `damaged`, + - `cutom_model_data`, + - `has_component`, and, + - `fishing_rod/cast`. + - Range dispatch item models, supported properties are: + - `bundle/fullness`, + - `count`, + - `custom_model_data`, and, + - `damage`. + - Select item models, supported properties are: + - `charge_type`, + - `trim_material`, + - `context_dimension`, and, + - `custom_model_data`. + - For the `display_context` property, the `gui` case is mapped, if present. + - Also includes detecting if an item should be displayed handheld by looking at the item's model. + - Also is able to detect and map items using the "legacy" `custom_model_data` range-dispatch style, and map them to Geyser's `legacy` item mappings. +- Generating a simple bedrock resourcepack for simple 2D items, as well as: + - Simple custom armour items, by analysing an item's `minecraft:equippable` component and loaded equipment assets. + - 3D items, by converting the Java model to a bedrock one, and generating an attachable and animations for it, as well as rendering a custom GUI icon. + - Is able to translate display transformations for the head, first-person and third-person item slots. diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 3c6afdc..b782492 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -15,4 +15,5 @@ dependencies { implementation(files(libs.javaClass.superclass.protectionDomain.codeSource.location)) implementation(libs.fabric.loom) + implementation(libs.minotaur) } diff --git a/build-logic/src/main/kotlin/extensions.kt b/build-logic/src/main/kotlin/extensions.kt new file mode 100644 index 0000000..f95387d --- /dev/null +++ b/build-logic/src/main/kotlin/extensions.kt @@ -0,0 +1,15 @@ +import org.gradle.api.Project + +// Nicely stolen from Geyser + +fun buildNumber(): Int { + return System.getenv()["BUILD_NUMBER"]?.let {Integer.parseInt(it)} ?: -1 +} + +fun projectVersion(project: Project): String { + return project.version.toString().replace("SNAPSHOT", "b" + buildNumber()) +} + +fun versionName(project: Project): String { + return "Rainbow-${projectVersion(project)}" +} 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 84a478c..a267911 100644 --- a/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/rainbow.base-conventions.gradle.kts @@ -1,52 +1,32 @@ plugins { - id("fabric-loom") + id("net.fabricmc.fabric-loom") } version = properties["mod_version"]!! as String group = properties["maven_group"]!! as String val archivesBaseName = properties["archives_base_name"]!! as String -val targetJavaVersion = 21 +val targetJavaVersion = 25 -val buildNumber = System.getenv()["BUILD_NUMBER"]?: "DEV" -val fmjVersion = "$version-$buildNumber" +val fmjVersion = projectVersion(project) base { archivesName = archivesBaseName } -repositories { - maven { - 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") - } -} +repositories {} dependencies { minecraft(libs.minecraft) - mappings(loom.layered { - officialMojangMappings() - parchment(libs.parchment) - }) - modImplementation(libs.fabric.loader) - modImplementation(libs.fabric.api) + implementation(libs.fabric.loader) + implementation(libs.fabric.api) } tasks { processResources { inputs.property("version", fmjVersion) - inputs.property("supported_versions", libs.versions.minecraft.supported.get()) + inputs.property("supported_versions", libs.versions.minecraft.release.get()) inputs.property("loader_version", libs.versions.fabric.loader.get()) filteringCharset = "UTF-8" @@ -54,7 +34,7 @@ tasks { expand( mapOf( "version" to fmjVersion, - "supported_versions" to libs.versions.minecraft.supported.get(), + "minecraft_version" to libs.versions.minecraft.release.get(), "loader_version" to libs.versions.fabric.loader.get() ) ) diff --git a/build-logic/src/main/kotlin/rainbow.modrinth-publish-conventions.gradle.kts b/build-logic/src/main/kotlin/rainbow.modrinth-publish-conventions.gradle.kts new file mode 100644 index 0000000..bdac834 --- /dev/null +++ b/build-logic/src/main/kotlin/rainbow.modrinth-publish-conventions.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("com.modrinth.minotaur") +} + +modrinth { + token.set(System.getenv("MODRINTH_TOKEN") ?: "") + debugMode.set(System.getenv("MODRINTH_TOKEN") == null) + projectId.set("QD7c9rxP") + versionName.set(versionName(project)) + versionNumber.set(projectVersion(project)) + versionType.set("beta") + changelog.set(System.getenv("CHANGELOG") ?: "") + gameVersions.addAll(libs.versions.minecraft.supported.modrinth.get().split(",")) + loaders.add("fabric") + + dependencies { + required.project("P7dR8mSH") // Fabric API + } +} diff --git a/client/build.gradle.kts b/client/build.gradle.kts index f490a7d..a2161e0 100644 --- a/client/build.gradle.kts +++ b/client/build.gradle.kts @@ -1,13 +1,11 @@ -import net.fabricmc.loom.task.RemapJarTask - plugins { id("rainbow.base-conventions") id("rainbow.publish-conventions") + id("rainbow.modrinth-publish-conventions") } dependencies { - // Implement namedElements so IDEs can use it correctly, but include the remapped build - implementation(project(path = ":rainbow", configuration = "namedElements")) + implementation(project(":rainbow")) include(project(":rainbow")) } @@ -15,13 +13,11 @@ tasks { val copyJarTask = register("copyRainbowClientJar") { group = "build" - val remapJarTask = getByName("remapJar") - dependsOn(remapJarTask) + val jarTask = getByName("jar") + dependsOn(jarTask) - from(remapJarTask.archiveFile) - rename { - "Rainbow.jar" - } + from(jarTask.archiveFile) + rename {"Rainbow.jar"} into(project.layout.buildDirectory.file("libs")) } @@ -29,3 +25,7 @@ tasks { dependsOn(copyJarTask) } } + +modrinth { + uploadFile.set(tasks.jar) +} diff --git a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java b/client/src/main/java/org/geysermc/rainbow/client/ClientPackSerializer.java similarity index 60% rename from client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java rename to client/src/main/java/org/geysermc/rainbow/client/ClientPackSerializer.java index 00a2a28..4aeda1f 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftPackSerializer.java +++ b/client/src/main/java/org/geysermc/rainbow/client/ClientPackSerializer.java @@ -4,36 +4,53 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; -import net.minecraft.client.Minecraft; import net.minecraft.core.HolderLookup; import net.minecraft.resources.RegistryOps; import net.minecraft.util.Util; import org.geysermc.rainbow.CodecUtil; import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.PackSerializer; +import org.jspecify.annotations.Nullable; import java.io.FileOutputStream; 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; +public class ClientPackSerializer implements PackSerializer { + private HolderLookup.@Nullable Provider registries = null; + private int jsonExported = 0; + private int texturesExported = 0; - public MinecraftPackSerializer(Minecraft minecraft) { - registries = Objects.requireNonNull(minecraft.level).registryAccess(); + public void prepare(HolderLookup.Provider registries) { + this.registries = registries; + jsonExported = 0; + texturesExported = 0; + } + + public int jsonExported() { + return jsonExported; + } + + public int texturesExported() { + return texturesExported; } @Override public CompletableFuture saveJson(Codec codec, T object, Path path) { + if (registries == null) { + throw new IllegalStateException("saveJson called whilst registries was null"); + } DynamicOps ops = RegistryOps.create(JsonOps.INSTANCE, registries); - return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> CodecUtil.trySaveJson(codec, object, path, ops)), - Util.backgroundExecutor().forName("PackSerializer-saveJson")); + jsonExported++; + return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> { + CodecUtil.trySaveJson(codec, object, path, ops); + }), Util.backgroundExecutor().forName("PackSerializer-saveJson")); } @Override public CompletableFuture saveTexture(byte[] texture, Path path) { + texturesExported++; return CompletableFuture.runAsync(() -> RainbowIO.safeIO(() -> { CodecUtil.ensureDirectoryExists(path.getParent()); try (OutputStream outputTexture = new FileOutputStream(path.toFile())) { 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 91ebd41..7c53740 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java +++ b/client/src/main/java/org/geysermc/rainbow/client/MinecraftAssetResolver.java @@ -5,11 +5,11 @@ import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.renderer.texture.TextureAtlas; import net.minecraft.client.renderer.texture.TextureAtlasSprite; -import net.minecraft.client.resources.model.AtlasManager; 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.client.resources.model.sprite.AtlasManager; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.server.packs.resources.ResourceManager; @@ -21,6 +21,7 @@ import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.texture.TextureResource; import org.geysermc.rainbow.mixin.SpriteContentsAccessor; +import org.jspecify.annotations.Nullable; import java.io.InputStream; import java.util.Optional; @@ -54,7 +55,7 @@ public Optional getEquipmentInfo(ResourceKey getTexture(Identifier atlasId, Identifier identifier) { + public Optional getTexture(@Nullable Identifier atlasId, Identifier identifier) { if (atlasId == null) { // Not in an atlas - so not animated, probably? return RainbowIO.safeIO(() -> { 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 7fa8b54..30be670 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/PackManager.java +++ b/client/src/main/java/org/geysermc/rainbow/client/PackManager.java @@ -18,9 +18,9 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.List; +import java.util.Objects; import java.util.Optional; import java.util.Set; -import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; public final class PackManager { @@ -39,16 +39,18 @@ public final class PackManager { private static final Path PACK_ZIP_FILE = Path.of("pack.zip"); private static final Path REPORT_FILE = Path.of("report.txt"); + private final ClientPackSerializer packSerializer = new ClientPackSerializer(); private Optional currentPack = Optional.empty(); public void startPack(String name) throws IOException { if (currentPack.isPresent()) { throw new IllegalStateException("Already started a pack (" + currentPack.get().name() + ")"); } + packSerializer.prepare(Objects.requireNonNull(Minecraft.getInstance().level).registryAccess()); Path packDirectory = createPackDirectory(name); - BedrockPack pack = BedrockPack.builder(name, packDirectory.resolve(MAPPINGS_FILE), packDirectory.resolve(PACK_DIRECTORY), - new MinecraftPackSerializer(Minecraft.getInstance()), new MinecraftAssetResolver(Minecraft.getInstance())) + BedrockPack pack = BedrockPack.builder(name, packDirectory.resolve(MAPPINGS_FILE), packDirectory.resolve(PACK_DIRECTORY), packSerializer, + new MinecraftAssetResolver(Minecraft.getInstance())) .withPackZipFile(packDirectory.resolve(PACK_ZIP_FILE)) .withGeometryRenderer(MinecraftGeometryRenderer.INSTANCE) .reportSuccesses() @@ -69,42 +71,68 @@ public Optional getExportPath() { } public boolean finish(Runnable onFinish) { - currentPack.map(pack -> { - RainbowIO.safeIO(() -> Files.writeString(getExportPath().orElseThrow().resolve(REPORT_FILE), createPackSummary(pack))); - return pack.save(); - }).ifPresent(future -> future.thenRun(onFinish)); + currentPack.ifPresent(pack -> { + Path reportPath = EXPORT_DIRECTORY.resolve(pack.name()).resolve(REPORT_FILE); + pack.save().thenRun(() -> { + RainbowIO.safeIO(() -> Files.writeString(reportPath, createPackSummary(pack, packSerializer))); + onFinish.run(); + }); + }); boolean wasPresent = currentPack.isPresent(); currentPack = Optional.empty(); return wasPresent; } - private static String createPackSummary(BedrockPack pack) { + private static String createPackSummary(BedrockPack pack, ClientPackSerializer packSerializer) { String problems = ((ProblemReporter.Collector) 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.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 """ +#### READ THIS FIRST #### +What do I do now? + +In this folder, you'll find 2 important files along with this one: + +- geyser_mappings.json: put this in the "custom_mappings" folder in Geyser's config folder. These are the generated item mappings. +- pack.zip: put this in the "packs" folder in Geyser's config folder. This is the generated bedrock resourcepack. + +Once you have taken those steps, restart your server. If everything went right, bedrock players should download +the generated pack and see your custom items. + +IF YOU EXPERIENCE ANY ISSUES, please go to our Discord (https://discord.gg/geysermc) for support. +Use the #custom-resource-packs channel, and make sure to include this report file. + +You can also open an issue report over at our issue tracker (https://github.com/GeyserMC/Rainbow/issues). +Again, be sure to include this report file, and please make sure your issue is not already reported! +If it is, you can help out by adding details to the existing report. + +Below, you'll find some statistics about the generated pack, and a mapping report, +which will list any models converted, and any problems that occurred during mapping. +######################### + -- PACK GENERATION REPORT -- // %s Generated pack: %s Mappings written: %d + Item texture atlas size: %d -Attachables tried to export: FIXME -Geometry files tried to export: %d -Animations tried to export: %d -Textures tried to export: FIXME +Geometries exported: %d +Animations exported: %d + +JSON-files written: %d +Textures exported: %d -- MAPPING TREE REPORT -- %s """.formatted(randomSummaryComment(), pack.name(), pack.getMappings(), pack.getItemTextureAtlasSize(), - geometries, animations, problems); + geometries, animations, packSerializer.jsonExported(), packSerializer.texturesExported(), problems); } private static String randomSummaryComment() { diff --git a/client/src/main/java/org/geysermc/rainbow/client/accessor/package-info.java b/client/src/main/java/org/geysermc/rainbow/client/accessor/package-info.java new file mode 100644 index 0000000..bae7885 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/accessor/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.client.accessor; + +import org.jspecify.annotations.NullMarked; diff --git a/client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java b/client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java index 23add2a..2c2eeff 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java +++ b/client/src/main/java/org/geysermc/rainbow/client/command/CommandSuggestionsArgumentType.java @@ -31,6 +31,7 @@ public CompletableFuture listSuggestions(CommandContext cont } public static Pair> getSuggestions(CommandContext context, String argument) { + //noinspection unchecked return context.getArgument(argument, Pair.class); } 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 02ca8c5..ca356a0 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 @@ -3,20 +3,25 @@ import com.mojang.brigadier.Command; import com.mojang.brigadier.CommandDispatcher; import com.mojang.brigadier.arguments.StringArgumentType; -import net.fabricmc.fabric.api.client.command.v2.ClientCommandManager; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.datafixers.util.Pair; +import net.fabricmc.fabric.api.client.command.v2.ClientCommands; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.ChatFormatting; 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 net.minecraft.world.item.ItemStackTemplate; import org.geysermc.rainbow.client.PackManager; import org.geysermc.rainbow.client.mapper.InventoryMapper; +import org.geysermc.rainbow.client.mapper.ItemSuggestionProvider; import org.geysermc.rainbow.client.mapper.PackMapper; import org.geysermc.rainbow.pack.BedrockPack; import java.nio.file.Path; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; public class PackGeneratorCommand { @@ -26,9 +31,9 @@ public class PackGeneratorCommand { .withClickEvent(new ClickEvent.SuggestCommand("/rainbow create ")))); public static void register(CommandDispatcher dispatcher, PackManager packManager, PackMapper packMapper) { - dispatcher.register(ClientCommandManager.literal("rainbow") - .then(ClientCommandManager.literal("create") - .then(ClientCommandManager.argument("name", StringArgumentType.word()) + dispatcher.register(ClientCommands.literal("rainbow") + .then(ClientCommands.literal("create") + .then(ClientCommands.argument("name", StringArgumentType.word()) .executes(context -> { String name = StringArgumentType.getString(context, "name"); try { @@ -42,28 +47,34 @@ public static void register(CommandDispatcher dispatc }) ) ) - .then(ClientCommandManager.literal("map") + .then(ClientCommands.literal("map") .executes(runWithPack(packManager, (source, pack) -> { ItemStack heldItem = source.getPlayer().getMainHandItem(); - switch (pack.map(heldItem)) { - case NONE_MAPPED -> source.sendError(Component.translatable("commands.rainbow.no_item_mapped")); - case PROBLEMS_OCCURRED -> source.sendFeedback(Component.translatable("commands.rainbow.mapped_held_item_problems")); - case MAPPED_SUCCESSFULLY -> source.sendFeedback(Component.translatable("commands.rainbow.mapped_held_item")); + if (heldItem.isEmpty()) { + source.sendError(Component.literal("Must hold an item to map")); + } else { + switch (pack.map(ItemStackTemplate.fromNonEmptyStack(heldItem))) { + case NONE_MAPPED -> source.sendError(Component.translatable("commands.rainbow.no_item_mapped")); + case PROBLEMS_OCCURRED -> source.sendFeedback(Component.translatable("commands.rainbow.mapped_held_item_problems")); + case MAPPED_SUCCESSFULLY -> source.sendFeedback(Component.translatable("commands.rainbow.mapped_held_item")); + } } })) ) - .then(ClientCommandManager.literal("mapinventory") + .then(ClientCommands.literal("mapinventory") .executes(runWithPack(packManager, (source, pack) -> { int mapped = 0; boolean errors = false; Inventory inventory = source.getPlayer().getInventory(); for (ItemStack stack : inventory) { - BedrockPack.MappingResult result = pack.map(stack); - if (result != BedrockPack.MappingResult.NONE_MAPPED) { - mapped++; - if (result == BedrockPack.MappingResult.PROBLEMS_OCCURRED) { - errors = true; + if (!stack.isEmpty()) { + BedrockPack.MappingResult result = pack.map(ItemStackTemplate.fromNonEmptyStack(stack)); + if (result != BedrockPack.MappingResult.NONE_MAPPED) { + mapped++; + if (result == BedrockPack.MappingResult.PROBLEMS_OCCURRED) { + errors = true; + } } } } @@ -78,10 +89,10 @@ public static void register(CommandDispatcher dispatc } })) ) - .then(ClientCommandManager.literal("auto") + .then(ClientCommands.literal("auto") /* This is disabled for now. - .then(ClientCommandManager.literal("command") - .then(ClientCommandManager.argument("suggestions", CommandSuggestionsArgumentType.TYPE) + .then(ClientCommands.literal("command") + .then(ClientCommands.argument("suggestions", CommandSuggestionsArgumentType.TYPE) .executes(context -> { Pair> suggestions = CommandSuggestionsArgumentType.getSuggestions(context, "suggestions"); String baseCommand = suggestions.getFirst(); @@ -97,21 +108,22 @@ public static void register(CommandDispatcher dispatc ) ) */ - .then(ClientCommandManager.literal("inventory") + .then(ClientCommands.literal("inventory") .executes(runWithPack(packManager, (source, pack) -> { packMapper.setItemProvider(InventoryMapper.INSTANCE); source.sendFeedback(Component.translatable("commands.rainbow.automatic_inventory_mapping")); })) ) - .then(ClientCommandManager.literal("stop") + .then(ClientCommands.literal("stop") .executes(runWithPack(packManager, (source, pack) -> { packMapper.setItemProvider(null); source.sendFeedback(Component.translatable("commands.rainbow.stopped_automatic_mapping")); })) ) ) - .then(ClientCommandManager.literal("finish") + .then(ClientCommands.literal("finish") .executes(context -> { + context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_finishing")); Optional exportPath = packManager.getExportPath(); Runnable onFinish = () -> context.getSource().sendFeedback(Component.translatable("commands.rainbow.pack_finished_successfully").withStyle(style -> style.withUnderlined(true).withClickEvent(new ClickEvent.OpenFile(exportPath.orElseThrow())))); diff --git a/client/src/main/java/org/geysermc/rainbow/client/command/package-info.java b/client/src/main/java/org/geysermc/rainbow/client/command/package-info.java new file mode 100644 index 0000000..42338c1 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/command/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.client.command; + +import org.jspecify.annotations.NullMarked; diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java index 8708f5d..a86423a 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/CustomItemProvider.java @@ -2,13 +2,13 @@ import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import java.util.stream.Stream; public interface CustomItemProvider { - Stream nextItems(LocalPlayer player, ClientPacketListener connection); + Stream nextItems(LocalPlayer player, ClientPacketListener connection); boolean isDone(); } diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java index 4bac740..144d83e 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/InventoryMapper.java @@ -2,7 +2,7 @@ import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import java.util.stream.Stream; @@ -12,8 +12,10 @@ public class InventoryMapper implements CustomItemProvider { private InventoryMapper() {} @Override - public Stream nextItems(LocalPlayer player, ClientPacketListener connection) { - return player.containerMenu.getItems().stream(); + public Stream nextItems(LocalPlayer player, ClientPacketListener connection) { + return player.containerMenu.getItems().stream() + .filter(stack -> !stack.isEmpty()) + .map(ItemStackTemplate::fromNonEmptyStack); } @Override diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java index 33ef127..31051d1 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/ItemSuggestionProvider.java @@ -3,7 +3,7 @@ import net.minecraft.client.multiplayer.ClientPacketListener; import net.minecraft.client.player.LocalPlayer; import net.minecraft.network.protocol.game.ServerboundChatCommandPacket; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import java.util.ArrayList; import java.util.List; @@ -19,7 +19,7 @@ public ItemSuggestionProvider(List commands) { remainingCommands = new ArrayList<>(commands); } - public Stream nextItems(LocalPlayer player, ClientPacketListener connection) { + public Stream nextItems(LocalPlayer player, ClientPacketListener connection) { if (!remainingCommands.isEmpty() || waitingOnItem) { if (waitingOnClear && player.getInventory().isEmpty()) { waitingOnClear = false; @@ -28,7 +28,9 @@ public Stream nextItems(LocalPlayer player, ClientPacketListener conn waitingOnItem = true; } else { if (!player.getInventory().isEmpty()) { - Stream items = player.getInventory().getNonEquipmentItems().stream(); + Stream items = player.getInventory().getNonEquipmentItems().stream() + .filter(stack -> !stack.isEmpty()) + .map(ItemStackTemplate::fromNonEmptyStack); connection.send(new ServerboundChatCommandPacket("clear")); waitingOnItem = false; diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java index cb56500..6c9ada4 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/PackMapper.java @@ -6,18 +6,19 @@ import net.minecraft.network.chat.Component; import org.geysermc.rainbow.client.PackManager; import org.geysermc.rainbow.pack.BedrockPack; +import org.jspecify.annotations.Nullable; import java.util.Objects; public class PackMapper { private final PackManager packManager; - private CustomItemProvider itemProvider; + private @Nullable CustomItemProvider itemProvider; public PackMapper(PackManager packManager) { this.packManager = packManager; } - public void setItemProvider(CustomItemProvider itemProvider) { + public void setItemProvider(@Nullable CustomItemProvider itemProvider) { this.itemProvider = itemProvider; } @@ -33,10 +34,10 @@ public void tick(Minecraft minecraft) { .filter(result -> result != BedrockPack.MappingResult.NONE_MAPPED) .count(); if (mapped != 0) { - player.displayClientMessage(Component.translatable("chat.rainbow.mapped_items", mapped), false); + player.sendSystemMessage(Component.translatable("chat.rainbow.mapped_items", mapped)); } if (itemProvider.isDone()) { - player.displayClientMessage(Component.translatable("chat.rainbow.automatic_mapping_finished"), false); + player.sendSystemMessage(Component.translatable("chat.rainbow.automatic_mapping_finished")); itemProvider = null; } }, () -> itemProvider = null); diff --git a/client/src/main/java/org/geysermc/rainbow/client/mapper/package-info.java b/client/src/main/java/org/geysermc/rainbow/client/mapper/package-info.java new file mode 100644 index 0000000..0db5e0d --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/mapper/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.client.mapper; + +import org.jspecify.annotations.NullMarked; diff --git a/client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java index 8b76205..d0f41c7 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/GuiItemRenderStateMixin.java @@ -1,7 +1,7 @@ package org.geysermc.rainbow.client.mixin; -import net.minecraft.client.gui.render.state.GuiItemRenderState; -import net.minecraft.client.gui.render.state.ScreenArea; +import net.minecraft.client.renderer.state.gui.GuiItemRenderState; +import net.minecraft.client.renderer.state.gui.ScreenArea; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.injection.Constant; import org.spongepowered.asm.mixin.injection.ModifyConstant; diff --git a/client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java index 31f1577..2a8693b 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/ModelManagerMixin.java @@ -1,50 +1,50 @@ package org.geysermc.rainbow.client.mixin; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import net.minecraft.client.Minecraft; import net.minecraft.client.renderer.item.ClientItem; +import net.minecraft.client.resources.model.BlockStateModelLoader; import net.minecraft.client.resources.model.ClientItemInfoLoader; import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.UnbakedModel; import net.minecraft.resources.Identifier; import net.minecraft.server.packs.resources.PreparableReloadListener; import org.geysermc.rainbow.client.accessor.ResolvedModelAccessor; +import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import java.lang.reflect.InvocationTargetException; import java.util.Map; import java.util.Optional; -import java.util.concurrent.CompletableFuture; @Mixin(ModelManager.class) public abstract class ModelManagerMixin implements PreparableReloadListener, AutoCloseable, ResolvedModelAccessor { @Unique - private Map unbakedResolvedModels; + private @Nullable Map unbakedResolvedModels; @Unique - private Map clientItems; + private @Nullable Map clientItems; - @WrapOperation(method = "method_65753", at = @At(value = "INVOKE", target = "Ljava/util/concurrent/CompletableFuture;join()Ljava/lang/Object;", ordinal = 2)) - private static Object setResolvedModels(CompletableFuture instance, Operation original) { - Object resolved = original.call(instance); + @Inject(method = "discoverModelDependencies", at = @At("TAIL")) + private static void setResolvedAndItemFields(Map allModels, BlockStateModelLoader.LoadedModels blockStateModels, ClientItemInfoLoader.LoadedClientInfos itemInfos, + // Method returns private record (ResolvedModels) + @SuppressWarnings("rawtypes") CallbackInfoReturnable callbackInfoReturnable) { + // Ideally we'd somehow use the "this" instance, but that's not possible here since the method we inject into is a static one + ModelManagerMixin thiz = ((ModelManagerMixin) (Object) Minecraft.getInstance().getModelManager()); + + // Couldn't be bothered setting up access wideners, this resolves the second component of the ResolvedModels record, which is called "models" try { - // Couldn't be bothered setting up access wideners, this resolves the second component of the ResolvedModels record, which is called "models" - // Ideally we'd somehow use the "this" instance, but that's not possible here since the lambda we inject into is a static one - ((ModelManagerMixin) (Object) Minecraft.getInstance().getModelManager()).unbakedResolvedModels = (Map) resolved.getClass().getRecordComponents()[1].getAccessor().invoke(resolved); - } catch (IllegalAccessException | InvocationTargetException | ClassCastException exception) { + Object returnValue = callbackInfoReturnable.getReturnValue(); + //noinspection unchecked + thiz.unbakedResolvedModels = (Map) returnValue.getClass().getRecordComponents()[1].getAccessor().invoke(returnValue); + } catch (InvocationTargetException | IllegalAccessException | ClassCastException exception) { throw new RuntimeException(exception); } - return resolved; - } - @WrapOperation(method = "method_65753", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/ClientItemInfoLoader$LoadedClientInfos;contents()Ljava/util/Map;")) - private static Map setClientItems(ClientItemInfoLoader.LoadedClientInfos instance, Operation> original) { - // Same note as above for not using "this" - ModelManagerMixin thiz = ((ModelManagerMixin) (Object) Minecraft.getInstance().getModelManager()); - thiz.clientItems = original.call(instance); - return thiz.clientItems; + thiz.clientItems = itemInfos.contents(); } @Override diff --git a/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java index 3329a71..115465f 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/PictureInPictureRendererMixin.java @@ -1,14 +1,14 @@ package org.geysermc.rainbow.client.mixin; +import com.llamalad7.mixinextras.injector.ModifyExpressionValue; import com.mojang.blaze3d.textures.GpuTexture; import net.minecraft.client.gui.render.pip.PictureInPictureRenderer; import org.geysermc.rainbow.client.render.PictureInPictureCopyRenderer; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; -import org.spongepowered.asm.mixin.injection.Constant; -import org.spongepowered.asm.mixin.injection.ModifyConstant; +import org.spongepowered.asm.mixin.injection.At; @Mixin(PictureInPictureRenderer.class) public abstract class PictureInPictureRendererMixin implements AutoCloseable, PictureInPictureCopyRenderer { @@ -27,7 +27,7 @@ public abstract class PictureInPictureRendererMixin implements AutoCloseable, Pi allowTextureCopy = true; } - @ModifyConstant(method = "prepareTexturesAndProjection", constant = @Constant(intValue = 12)) + @ModifyExpressionValue(method = "prepareTexturesAndProjection", at = @At(value = "CONSTANT", args = "intValue=13")) public int allowUsageCopySrc(int usage) { return allowTextureCopy ? usage | GpuTexture.USAGE_COPY_SRC : usage; } diff --git a/client/src/main/java/org/geysermc/rainbow/client/mixin/package-info.java b/client/src/main/java/org/geysermc/rainbow/client/mixin/package-info.java new file mode 100644 index 0000000..e39fede --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/mixin/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.client.mixin; + +import org.jspecify.annotations.NullMarked; diff --git a/client/src/main/java/org/geysermc/rainbow/client/package-info.java b/client/src/main/java/org/geysermc/rainbow/client/package-info.java new file mode 100644 index 0000000..a5b482f --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.client; + +import org.jspecify.annotations.NullMarked; 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 51d96e2..fa5956c 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 @@ -1,7 +1,7 @@ package org.geysermc.rainbow.client.render; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import org.geysermc.rainbow.mapping.geometry.GeometryRenderer; import org.geysermc.rainbow.mapping.texture.TextureHolder; @@ -11,7 +11,7 @@ public class MinecraftGeometryRenderer implements GeometryRenderer { public static final MinecraftGeometryRenderer INSTANCE = new MinecraftGeometryRenderer(); @Override - public TextureHolder render(Identifier identifier, ItemStack stack) { + public TextureHolder render(Identifier identifier, ItemStackTemplate stack) { return new RenderedTextureHolder(identifier, stack); } } diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java b/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java index a4342d9..98ba5ca 100644 --- a/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java +++ b/client/src/main/java/org/geysermc/rainbow/client/render/RenderedTextureHolder.java @@ -8,21 +8,21 @@ 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.client.renderer.state.gui.GuiItemRenderState; +import net.minecraft.client.renderer.state.gui.GuiRenderState; +import net.minecraft.client.renderer.state.gui.pip.OversizedItemRenderState; import net.minecraft.resources.Identifier; import net.minecraft.util.ProblemReporter; import net.minecraft.world.item.ItemDisplayContext; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.client.mixin.PictureInPictureRendererAccessor; import org.geysermc.rainbow.image.NativeImageUtil; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.texture.TextureHolder; -import org.joml.Matrix3x2fStack; +import org.joml.Matrix3x2f; import java.nio.file.Path; import java.util.Objects; @@ -33,9 +33,9 @@ import java.util.concurrent.locks.ReentrantLock; public class RenderedTextureHolder extends TextureHolder { - private final ItemStack stackToRender; + private final ItemStackTemplate stackToRender; - public RenderedTextureHolder(Identifier identifier, ItemStack stackToRender) { + public RenderedTextureHolder(Identifier identifier, ItemStackTemplate stackToRender) { super(identifier); this.stackToRender = stackToRender; } @@ -48,10 +48,10 @@ public Optional load(AssetResolver assetResolver, ProblemReporter report @Override public CompletableFuture save(AssetResolver assetResolver, PackSerializer serializer, Path path, ProblemReporter reporter) { TrackingItemStackRenderState itemRenderState = new TrackingItemStackRenderState(); - Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stackToRender, ItemDisplayContext.GUI, null, null, 0); + Minecraft.getInstance().getItemModelResolver().updateForTopItem(itemRenderState, stackToRender.create(), ItemDisplayContext.GUI, null, null, 0); itemRenderState.setOversizedInGui(true); - GuiItemRenderState guiItemRenderState = new GuiItemRenderState("geometry_render", new Matrix3x2fStack(16), itemRenderState, 0, 0, null); + GuiItemRenderState guiItemRenderState = new GuiItemRenderState(new Matrix3x2f(), 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); diff --git a/client/src/main/java/org/geysermc/rainbow/client/render/package-info.java b/client/src/main/java/org/geysermc/rainbow/client/render/package-info.java new file mode 100644 index 0000000..02bc283 --- /dev/null +++ b/client/src/main/java/org/geysermc/rainbow/client/render/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.client.render; + +import org.jspecify.annotations.NullMarked; 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 cc89808..f806752 100644 --- a/client/src/main/resources/assets/rainbow/lang/en_us.json +++ b/client/src/main/resources/assets/rainbow/lang/en_us.json @@ -13,6 +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.pack_finishing": "Writing pack to disk...", "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!" diff --git a/client/src/main/resources/fabric.mod.json b/client/src/main/resources/fabric.mod.json index 0dd34a6..dff7847 100644 --- a/client/src/main/resources/fabric.mod.json +++ b/client/src/main/resources/fabric.mod.json @@ -26,7 +26,7 @@ "depends": { "fabricloader": ">=${loader_version}", "fabric-api": "*", - "minecraft": "${supported_versions}" + "minecraft": "~${minecraft_version}" }, "custom": { "modmenu": { diff --git a/client/src/main/resources/rainbow-client.mixins.json b/client/src/main/resources/rainbow-client.mixins.json index a55d09a..5937e52 100644 --- a/client/src/main/resources/rainbow-client.mixins.json +++ b/client/src/main/resources/rainbow-client.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "org.geysermc.rainbow.client.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "client": [ "EntityRenderDispatcherAccessor", "GuiItemRenderStateMixin", diff --git a/datagen/build.gradle.kts b/datagen/build.gradle.kts index d0fae2f..f84a93d 100644 --- a/datagen/build.gradle.kts +++ b/datagen/build.gradle.kts @@ -4,9 +4,10 @@ plugins { } dependencies { - implementation(project(path = ":rainbow", configuration = "namedElements")) + implementation(project(":rainbow")) } loom { accessWidenerPath = file("src/main/resources/rainbow-datagen.accesswidener") + runs.clear() } diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java index 2b62fa0..386b5a7 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/ClientPackLoader.java @@ -13,7 +13,7 @@ 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 org.jspecify.annotations.Nullable; import java.io.File; import java.nio.file.Path; @@ -30,7 +30,7 @@ static ClientPackSource loadClientPackSource() { OptionSet parsed = parser.parse(FabricLoader.getInstance().getLaunchArguments(false)); return new ClientPackSource(getExternalAssetSource(parseArgument(parsed, assetIndexSpec), parseArgument(parsed, assetsDirSpec)), - new DirectoryValidator(path -> false)); + new DirectoryValidator(_ -> false)); } static CompletableFuture openClientResources() { @@ -44,12 +44,11 @@ static CompletableFuture openClientResources() { }); } - private static Path getExternalAssetSource(String assetIndex, File assetDirectory) { + private static Path getExternalAssetSource(@Nullable 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); 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 45d6028..d4c26fa 100644 --- a/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/RainbowModelProvider.java @@ -4,17 +4,19 @@ import com.mojang.blaze3d.platform.NativeImage; 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.fabricmc.fabric.api.datagen.v1.FabricPackOutput; 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.metadata.animation.AnimationMetadataSection; import net.minecraft.client.resources.model.EquipmentClientInfo; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.client.resources.model.UnbakedModel; +import net.minecraft.client.resources.model.cuboid.CuboidModel; +import net.minecraft.client.resources.model.cuboid.ItemModelGenerator; import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentMap; import net.minecraft.core.component.DataComponentPatch; +import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.data.CachedOutput; import net.minecraft.data.DataProvider; import net.minecraft.data.PackOutput; @@ -27,13 +29,13 @@ import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.RainbowIO; +import org.geysermc.rainbow.datagen.mixin.DataComponentInitializersAccessor; import org.geysermc.rainbow.mapping.AssetResolver; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.mapping.texture.TextureResource; import org.geysermc.rainbow.pack.BedrockPack; import org.jetbrains.annotations.ApiStatus; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -45,6 +47,7 @@ import java.util.Comparator; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; @@ -59,10 +62,12 @@ public abstract class RainbowModelProvider extends FabricModelProvider { private final Path geyserMappingsPath; private final Path packPath; - private Map itemInfos; - private Map models; + private @Nullable Map itemInfos; + private @Nullable Map models; - protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, + private @Nullable Map, DataComponentMap.Builder> initializedItemComponents; + + protected RainbowModelProvider(FabricPackOutput output, CompletableFuture registries, Map, EquipmentClientInfo> equipmentInfos, String packName, Identifier outputRoot, Path geyserMappingsPath, Path packPath) { super(output); @@ -76,34 +81,38 @@ protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, + protected RainbowModelProvider(FabricPackOutput output, CompletableFuture registries, Map, EquipmentClientInfo> equipmentInfos, String packName, Identifier outputRoot) { this(output, registries, equipmentInfos, packName, outputRoot, Path.of("geyser_mappings.json"), Path.of("pack")); } - protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, + protected RainbowModelProvider(FabricPackOutput output, CompletableFuture registries, Map, EquipmentClientInfo> equipmentInfos, String packName) { this(output, registries, equipmentInfos, packName, Identifier.withDefaultNamespace("bedrock")); } - protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries, String packName) { + protected RainbowModelProvider(FabricPackOutput output, CompletableFuture registries, String packName) { this(output, registries, Map.of(), packName); } - protected RainbowModelProvider(FabricDataOutput output, CompletableFuture registries) { + protected RainbowModelProvider(FabricPackOutput output, CompletableFuture registries) { this(output, registries, Rainbow.MOD_ID + "-generated"); } @Override - public @NotNull CompletableFuture run(CachedOutput output) { + public CompletableFuture run(CachedOutput output) { CompletableFuture vanillaModels = super.run(output); CompletableFuture bedrockPack = ClientPackLoader.openClientResources() .thenCompose(resourceManager -> registries.thenApply(registries -> { + // You're not really supposed to do this, but Rainbow *needs* the initialised components to function properly + initializedItemComponents = ((DataComponentInitializersAccessor) BuiltInRegistries.DATA_COMPONENT_INITIALIZERS).invokeRunInitializers(registries); + try (resourceManager) { BedrockPack pack = createBedrockPack(new Serializer(output, registries), - new DatagenResolver(resourceManager, equipmentInfos, itemInfos, models)).build(); + new DatagenResolver(resourceManager, equipmentInfos, + Objects.requireNonNull(itemInfos), Objects.requireNonNull(models))).build(); Set sortedItemInfos = new TreeSet<>(Comparator.comparing(item -> item.builtInRegistryHolder().key().identifier())); sortedItemInfos.addAll(itemInfos.keySet()); @@ -125,8 +134,11 @@ protected BedrockPack.Builder createBedrockPack(PackSerializer serializer, Asset protected abstract Item getVanillaItem(Item modded); protected DataComponentPatch getVanillaDataComponentPatch(Item modded) { + if (initializedItemComponents == null) { + throw new IllegalStateException("initializedItemComponents may not be null"); + } DataComponentPatch.Builder builder = DataComponentPatch.builder(); - modded.components().forEach(builder::set); + initializedItemComponents.get(modded.builtInRegistryHolder().key()).build().forEach(builder::set); return builder.build(); } @@ -179,8 +191,8 @@ private DatagenResolver(ResourceManager resourceManager, Map getResolvedModel(Identifier identifier) { - return resolvedModelCache.computeIfAbsent(identifier, key -> Optional.ofNullable(models.get(identifier)) - .map(instance -> BlockModel.fromStream(new StringReader(instance.get().toString()))) + return resolvedModelCache.computeIfAbsent(identifier, _ -> Optional.ofNullable(models.get(identifier)) + .map(instance -> CuboidModel.fromStream(new StringReader(instance.get().toString()))) .or(() -> { if (identifier.equals(ItemModelGenerator.GENERATED_ITEM_MODEL_ID)) { return Optional.of(new ItemModelGenerator()); @@ -189,12 +201,12 @@ public Optional getResolvedModel(Identifier identifier) { }) .or(() -> RainbowIO.safeIO(() -> { try (BufferedReader reader = resourceManager.openAsReader(identifier.withPrefix("models/").withSuffix(".json"))) { - return BlockModel.fromStream(reader); + return CuboidModel.fromStream(reader); } })) .map(model -> new ResolvedModel() { @Override - public @NotNull UnbakedModel wrapped() { + public UnbakedModel wrapped() { return model; } @@ -204,7 +216,7 @@ public Optional getResolvedModel(Identifier identifier) { } @Override - public @NotNull String debugName() { + public String debugName() { return identifier.toString(); } })); @@ -221,7 +233,7 @@ public Optional getEquipmentInfo(ResourceKey getTexture(Identifier atlas, Identifier identifier) { + public Optional getTexture(@Nullable Identifier atlas, Identifier identifier) { // We don't care about atlas since there are none loaded at datagen return resourceManager.getResource(Rainbow.decorateTextureIdentifier(identifier)) .flatMap(resource -> RainbowIO.safeIO(() -> { diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/DataComponentInitializersAccessor.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/DataComponentInitializersAccessor.java new file mode 100644 index 0000000..5621807 --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/DataComponentInitializersAccessor.java @@ -0,0 +1,17 @@ +package org.geysermc.rainbow.datagen.mixin; + +import net.minecraft.core.HolderLookup; +import net.minecraft.core.component.DataComponentInitializers; +import net.minecraft.core.component.DataComponentMap; +import net.minecraft.resources.ResourceKey; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Invoker; + +import java.util.Map; + +@Mixin(DataComponentInitializers.class) +public interface DataComponentInitializersAccessor { + + @Invoker + Map, DataComponentMap.Builder> invokeRunInitializers(final HolderLookup.Provider context); +} 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 69842c4..4bc401f 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 @@ -17,7 +17,8 @@ public abstract class ModelProviderMixin implements DataProvider { @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) { + @Local(name = "itemModels") ModelProvider.ItemInfoCollector itemInfoCollector, + @Local(name = "simpleModels") ModelProvider.SimpleModelCollector simpleModelCollector) { if ((Object) this instanceof RainbowModelProvider rainbowModelProvider) { rainbowModelProvider.setItemInfos(((ItemInfoCollectorAccessor) itemInfoCollector).getItemInfos()); rainbowModelProvider.setModels(((SimpleModelCollectorAccessor) simpleModelCollector).getModels()); diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/package-info.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/package-info.java new file mode 100644 index 0000000..c748996 --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/mixin/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.datagen.mixin; + +import org.jspecify.annotations.NullMarked; diff --git a/datagen/src/main/java/org/geysermc/rainbow/datagen/package-info.java b/datagen/src/main/java/org/geysermc/rainbow/datagen/package-info.java new file mode 100644 index 0000000..18dde9f --- /dev/null +++ b/datagen/src/main/java/org/geysermc/rainbow/datagen/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.datagen; + +import org.jspecify.annotations.NullMarked; diff --git a/datagen/src/main/resources/fabric.mod.json b/datagen/src/main/resources/fabric.mod.json index e1629a6..38ab8be 100644 --- a/datagen/src/main/resources/fabric.mod.json +++ b/datagen/src/main/resources/fabric.mod.json @@ -21,6 +21,6 @@ "depends": { "fabricloader": ">=${loader_version}", "fabric-api": "*", - "minecraft": "${supported_versions}" + "minecraft": "~${minecraft_version}" } } diff --git a/datagen/src/main/resources/rainbow-datagen.accesswidener b/datagen/src/main/resources/rainbow-datagen.accesswidener index 5ab114f..b8e5da6 100644 --- a/datagen/src/main/resources/rainbow-datagen.accesswidener +++ b/datagen/src/main/resources/rainbow-datagen.accesswidener @@ -1,3 +1,3 @@ -accessWidener v2 named +accessWidener v2 official accessible class net/minecraft/client/data/models/ModelProvider$ItemInfoCollector accessible class net/minecraft/client/data/models/ModelProvider$SimpleModelCollector diff --git a/datagen/src/main/resources/rainbow-datagen.mixins.json b/datagen/src/main/resources/rainbow-datagen.mixins.json index 574147d..141cb9b 100644 --- a/datagen/src/main/resources/rainbow-datagen.mixins.json +++ b/datagen/src/main/resources/rainbow-datagen.mixins.json @@ -2,7 +2,7 @@ "required": true, "minVersion": "0.8", "package": "org.geysermc.rainbow.datagen.mixin", - "compatibilityLevel": "JAVA_21", + "compatibilityLevel": "JAVA_25", "client": [ "ItemInfoCollectorAccessor", "ModelProviderMixin", @@ -10,5 +10,8 @@ ], "injectors": { "defaultRequire": 1 - } + }, + "mixins": [ + "DataComponentInitializersAccessor" + ] } diff --git a/gradle.properties b/gradle.properties index a75c2cb..1cbccbc 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,5 @@ org.gradle.jvmargs=-Xmx1G # Mod Properties -mod_version=0.2.0-1.21.11-SNAPSHOT +mod_version=0.2.0-26.1-SNAPSHOT maven_group=org.geysermc.rainbow diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 441490e..6764e50 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,25 +1,20 @@ [versions] -minecraft = "1.21.11" -minecraft-supported = "1.21.11" -parchment = "2025.10.12" +minecraft-release = "26.1.2" +minecraft-supported-modrinth = "26.1,26.1.1,26.1.2" -fabric-loom = "1.14-SNAPSHOT" -fabric-loader = "0.18.2" -fabric-api = "0.140.0+1.21.11" +fabric-loom = "1.16.1" +fabric-loader = "0.18.4" +fabric-api = "0.144.1+26.1" -creative = "817fa982c4" -packconverter = "3.4.1-20251013.173215-13" +minotaur = "2.9.0" [libraries] -minecraft = {group = "com.mojang", name = "minecraft", version.ref = "minecraft"} -parchment = {group = "org.parchmentmc.data", name = "parchment-1.21.10", version.ref = "parchment"} +minecraft = {group = "com.mojang", name = "minecraft", version.ref = "minecraft-release"} 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"} -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"} +minotaur = {group = "com.modrinth.minotaur", name = "com.modrinth.minotaur.gradle.plugin", version.ref = "minotaur"} [plugins] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f8e1ee3..d997cfc 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 23449a2..c61a118 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.2.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index adff685..f640dbc 100755 --- a/gradlew +++ b/gradlew @@ -57,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob//platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. diff --git a/rainbow/build.gradle.kts b/rainbow/build.gradle.kts index f89f209..9757089 100644 --- a/rainbow/build.gradle.kts +++ b/rainbow/build.gradle.kts @@ -2,3 +2,7 @@ plugins { id("rainbow.base-conventions") id("rainbow.publish-conventions") } + +loom { + runs.clear() +} diff --git a/rainbow/src/main/java/org/geysermc/rainbow/CodecUtil.java b/rainbow/src/main/java/org/geysermc/rainbow/CodecUtil.java index ee8436f..f980e6c 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/CodecUtil.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/CodecUtil.java @@ -8,11 +8,6 @@ import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; import com.mojang.serialization.codecs.RecordCodecBuilder; -import net.minecraft.util.ExtraCodecs; -import org.joml.Vector2f; -import org.joml.Vector2fc; -import org.joml.Vector3f; -import org.joml.Vector3fc; import java.io.IOException; import java.nio.file.FileSystem; @@ -25,10 +20,6 @@ import java.util.stream.Stream; public class CodecUtil { - // It's fine to cast to mutable here since codecs won't change the data - public static final Codec VECTOR2F_CODEC = ExtraCodecs.VECTOR2F.xmap(vector -> vector, vector -> (Vector2f) vector); - public static final Codec VECTOR3F_CODEC = ExtraCodecs.VECTOR3F.xmap(vector -> vector, vector -> (Vector3f) vector); - private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); public static RecordCodecBuilder unitVerifyCodec(Codec codec, String field, T value) { diff --git a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java index cb3977d..e7f9ca0 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/Rainbow.java @@ -1,9 +1,6 @@ package org.geysermc.rainbow; import com.mojang.logging.LogUtils; -import net.minecraft.client.renderer.texture.TextureAtlas; -import net.minecraft.client.resources.model.Material; -import net.minecraft.data.AtlasIds; import net.minecraft.resources.Identifier; import org.slf4j.Logger; @@ -28,14 +25,4 @@ public static Identifier decorateIdentifier(Identifier identifier, String type, public static Identifier decorateTextureIdentifier(Identifier identifier) { return decorateIdentifier(identifier, "textures", "png"); } - - public static Identifier getAtlasIdFromMaterial(Material material) { - Identifier atlasLocation = material.atlasLocation(); - if (atlasLocation.equals(TextureAtlas.LOCATION_BLOCKS)) { - return AtlasIds.BLOCKS; - } else if (atlasLocation.equals(TextureAtlas.LOCATION_ITEMS)) { - return AtlasIds.ITEMS; - } - return atlasLocation; - } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java index 5068ef6..6a0659e 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/RainbowIO.java @@ -1,6 +1,7 @@ package org.geysermc.rainbow; import com.mojang.logging.LogUtils; +import org.jspecify.annotations.Nullable; import org.slf4j.Logger; import java.io.IOException; @@ -42,7 +43,7 @@ public static void registerExceptionListener(IOExceptionListener listener) { @FunctionalInterface public interface IOSupplier { - T get() throws IOException; + @Nullable T get() throws IOException; } @FunctionalInterface 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 eacb266..3e60205 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserBaseDefinition.java @@ -28,6 +28,7 @@ public record GeyserBaseDefinition(Identifier bedrockIdentifier, Optional entry.getValue().isEmpty() || SUPPORTED_COMPONENTS.contains(entry.getKey())) .forEach(entry -> { if (entry.getValue().isPresent()) { + //noinspection unchecked filtered.set((DataComponentType) entry.getKey(), entry.getValue().orElseThrow()); } else { filtered.remove(entry.getKey()); 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 de35d66..e4a800d 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserGroupDefinition.java @@ -3,7 +3,6 @@ import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.resources.Identifier; -import org.jetbrains.annotations.NotNull; import java.util.Comparator; import java.util.List; @@ -60,7 +59,7 @@ public Type type() { } @Override - public int compareTo(@NotNull GeyserMapping other) { + public int compareTo(GeyserMapping other) { if (other instanceof GeyserGroupDefinition(Optional otherModel, List otherDefinitions)) { if (model.isPresent() && otherModel.isPresent()) { return model.get().compareTo(otherModel.get()); 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 e5b49dd..368d5c5 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserItemDefinition.java @@ -1,7 +1,6 @@ package org.geysermc.rainbow.definition; import net.minecraft.resources.Identifier; -import org.jetbrains.annotations.NotNull; import java.util.Optional; @@ -12,7 +11,7 @@ public interface GeyserItemDefinition extends GeyserMapping { boolean conflictsWith(Optional parentModel, GeyserItemDefinition other); @Override - default int compareTo(@NotNull GeyserMapping other) { + default int compareTo(GeyserMapping other) { if (other instanceof GeyserItemDefinition itemDefinition) { return base().bedrockIdentifier().compareTo(itemDefinition.base().bedrockIdentifier()); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java index 0f1ffe6..e110f01 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserLegacyDefinition.java @@ -18,8 +18,8 @@ public record GeyserLegacyDefinition(GeyserBaseDefinition base, int customModelD @Override public boolean conflictsWith(Optional parentModel, GeyserItemDefinition other) { - if (other instanceof GeyserLegacyDefinition otherLegacy) { - return customModelData == otherLegacy.customModelData && base.conflictsWith(otherLegacy.base); + if (other instanceof GeyserLegacyDefinition(GeyserBaseDefinition otherBase, int otherModelData)) { + return customModelData == otherModelData && base.conflictsWith(otherBase); } return false; } 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 a46fd1c..e76e2ec 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/GeyserMapping.java @@ -4,7 +4,6 @@ import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; public interface GeyserMapping extends Comparable { @@ -39,7 +38,7 @@ public MapCodec codec() { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/package-info.java new file mode 100644 index 0000000..9aeebfb --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.definition; + +import org.jspecify.annotations.NullMarked; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java index 79adca7..24d15f5 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserConditionPredicate.java @@ -7,7 +7,6 @@ import net.minecraft.core.component.DataComponentType; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -57,7 +56,7 @@ public MapCodec codec() { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java index 4e793a6..ccb594a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserMatchPredicate.java @@ -10,7 +10,6 @@ import net.minecraft.world.item.CrossbowItem; import net.minecraft.world.item.equipment.trim.TrimMaterial; import net.minecraft.world.level.Level; -import org.jetbrains.annotations.NotNull; import java.util.function.Function; @@ -50,7 +49,7 @@ public MapCodec codec() { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java index 7c6b0f3..e0d5013 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/GeyserPredicate.java @@ -4,7 +4,6 @@ import com.mojang.serialization.MapCodec; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; import java.util.List; @@ -35,7 +34,7 @@ public MapCodec codec() { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } } 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 d58c7bb..51419d1 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 @@ -6,7 +6,6 @@ import com.mojang.serialization.codecs.RecordCodecBuilder; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; -import org.jetbrains.annotations.NotNull; import java.util.function.Supplier; @@ -54,7 +53,7 @@ public MapCodec codec() { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/package-info.java new file mode 100644 index 0000000..749b21c --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/definition/predicate/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.definition.predicate; + +import org.jspecify.annotations.NullMarked; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/image/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/image/package-info.java new file mode 100644 index 0000000..b1a8f71 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/image/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.image; + +import org.jspecify.annotations.NullMarked; 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 3c606e7..840ee7a 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/AssetResolver.java @@ -2,13 +2,13 @@ import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.resources.model.EquipmentClientInfo; -import net.minecraft.client.resources.model.ModelManager; import net.minecraft.client.resources.model.ResolvedModel; import net.minecraft.data.AtlasIds; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.world.item.equipment.EquipmentAsset; import org.geysermc.rainbow.mapping.texture.TextureResource; +import org.jspecify.annotations.Nullable; import java.util.Optional; @@ -20,13 +20,13 @@ public interface AssetResolver { Optional getEquipmentInfo(ResourceKey key); - Optional getTexture(Identifier atlas, Identifier identifier); + Optional getTexture(@Nullable Identifier atlas, Identifier identifier); - default Optional getTextureSafely(Identifier atlas, Identifier identifier) { - if (atlas.equals(ModelManager.BLOCK_OR_ITEM)) { - return getTexture(AtlasIds.BLOCKS, identifier) - .or(() -> getTexture(AtlasIds.ITEMS, identifier)); - } - return getTexture(atlas, identifier); + default Optional getPossibleAtlasTextureSafely(Identifier identifier) { + // Vanilla behaviour: when baking a Material, check item atlas first, then block atlas + // (see ModelManager, MaterialBaker) + return getTexture(AtlasIds.ITEMS, identifier) + .or(() -> getTexture(AtlasIds.BLOCKS, identifier)) + .or(() -> getTexture(null, identifier)); } } 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 8d9f1b6..007b04b 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/BedrockItemMapper.java @@ -1,8 +1,9 @@ package org.geysermc.rainbow.mapping; -import net.minecraft.client.renderer.item.BlockModelWrapper; +import com.mojang.math.Transformation; import net.minecraft.client.renderer.item.ClientItem; import net.minecraft.client.renderer.item.ConditionalItemModel; +import net.minecraft.client.renderer.item.CuboidItemModelWrapper; import net.minecraft.client.renderer.item.ItemModel; import net.minecraft.client.renderer.item.ItemModels; import net.minecraft.client.renderer.item.RangeSelectItemModel; @@ -25,6 +26,7 @@ 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.network.chat.Component; import net.minecraft.resources.Identifier; import net.minecraft.resources.ResourceKey; import net.minecraft.tags.ItemTags; @@ -34,7 +36,7 @@ import net.minecraft.world.entity.ai.attributes.Attributes; import net.minecraft.world.item.CrossbowItem; import net.minecraft.world.item.ItemDisplayContext; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import net.minecraft.world.item.component.ItemAttributeModifiers; import net.minecraft.world.item.equipment.trim.TrimMaterial; import net.minecraft.world.level.Level; @@ -52,7 +54,6 @@ import org.geysermc.rainbow.mixin.LateBoundIdMapperAccessor; import org.geysermc.rainbow.mixin.RangeSelectItemModelAccessor; import org.geysermc.rainbow.pack.BedrockItem; -import org.jspecify.annotations.NonNull; import java.util.List; import java.util.Optional; @@ -64,23 +65,24 @@ public class BedrockItemMapper { .map(Identifier::withDefaultNamespace) .toList(); - private static Identifier getId(ExtraCodecs.LateBoundIdMapper<@NonNull Identifier, @NonNull T> mapper, + private static Identifier getId(ExtraCodecs.LateBoundIdMapper mapper, T type) { //noinspection unchecked return ((LateBoundIdMapperAccessor) mapper).getIdToValue().inverse().get(type); } - public static void tryMapStack(ItemStack stack, Identifier modelIdentifier, ProblemReporter reporter, PackContext context) { + public static void tryMapStack(ItemStackTemplate stack, Identifier modelIdentifier, ProblemReporter reporter, PackContext context) { context.assetResolver().getClientItem(modelIdentifier).map(ClientItem::model) .ifPresentOrElse(model -> mapItem(model, stack, reporter.forChild(() -> "client item definition " + modelIdentifier + " "), base -> new GeyserSingleDefinition(base, Optional.of(modelIdentifier)), context), () -> reporter.report(() -> "missing client item definition " + modelIdentifier)); } - public static void tryMapStack(ItemStack stack, int customModelData, ProblemReporter reporter, PackContext context) { + public static void tryMapStack(ItemStackTemplate stack, int customModelData, ProblemReporter reporter, PackContext context) { Identifier itemModel = stack.get(DataComponents.ITEM_MODEL); + assert itemModel != null; 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)) { + if (vanillaModel instanceof RangeSelectItemModel.Unbaked(Optional _, RangeSelectItemModelProperty property, float scale, List entries, Optional fallback)) { // WHY, Mojang? if (property instanceof net.minecraft.client.renderer.item.properties.numeric.CustomModelDataProperty(int index)) { if (index == 0) { @@ -102,14 +104,14 @@ public static void tryMapStack(ItemStack stack, int customModelData, ProblemRepo childReporter.report(() -> "item model is not range_dispatch, unable to apply custom model data"); } - public static void mapItem(ItemModel.Unbaked model, ItemStack stack, ProblemReporter reporter, + public static void mapItem(ItemModel.Unbaked model, ItemStackTemplate stack, ProblemReporter reporter, Function definitionCreator, PackContext packContext) { - mapItem(model, new MappingContext(List.of(), stack, reporter, definitionCreator, packContext)); + mapItem(model, new MappingContext(stack, reporter, definitionCreator, packContext)); } private static void mapItem(ItemModel.Unbaked model, MappingContext context) { switch (model) { - case BlockModelWrapper.Unbaked modelWrapper -> mapBlockModelWrapper(modelWrapper, context.child("plain model " + modelWrapper.model())); + case CuboidItemModelWrapper.Unbaked modelWrapper -> mapBlockModelWrapper(modelWrapper, context.child("plain model " + modelWrapper.model())); 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 ")); @@ -117,7 +119,7 @@ private static void mapItem(ItemModel.Unbaked model, MappingContext context) { } } - private static void mapBlockModelWrapper(BlockModelWrapper.Unbaked model, MappingContext context) { + private static void mapBlockModelWrapper(CuboidItemModelWrapper.Unbaked model, MappingContext context) { Identifier itemModelIdentifier = model.model(); context.packContext().assetResolver().getResolvedModel(itemModelIdentifier) @@ -129,7 +131,7 @@ private static void mapBlockModelWrapper(BlockModelWrapper.Unbaked model, Mappin bedrockIdentifier = itemModelIdentifier; } - BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, context.stack, context.packContext); + BedrockGeometryContext geometry = BedrockGeometryContext.create(bedrockIdentifier, itemModel, context.finaliseTransformation(model.transformation()), context.itemStack, 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 " + itemModelIdentifier); @@ -157,8 +159,8 @@ private static void mapConditionalModel(ConditionalItemModel.Unbaked model, Mapp return; } - mapItem(onTrue, context.with(new GeyserConditionPredicate(predicateProperty, true), "condition on true ")); - mapItem(onFalse, context.with(new GeyserConditionPredicate(predicateProperty, false), "condition on false ")); + mapItem(onTrue, context.with(new GeyserConditionPredicate(predicateProperty, true), model.transformation(), "condition on true ")); + mapItem(onFalse, context.with(new GeyserConditionPredicate(predicateProperty, false), model.transformation(), "condition on false ")); } private static void mapRangeSelectModel(RangeSelectItemModel.Unbaked model, MappingContext context) { @@ -176,11 +178,11 @@ private static void mapRangeSelectModel(RangeSelectItemModel.Unbaked model, Mapp 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())); + mapItem(entry.model(), context.with(new GeyserRangeDispatchPredicate(predicateProperty, entry.threshold(), model.scale()), model.transformation(), "threshold " + entry.threshold())); } } - model.fallback().ifPresent(fallback -> mapItem(fallback, context.child("range dispatch fallback"))); + model.fallback().ifPresent(fallback -> mapItem(fallback, context.with(model.transformation(), "range dispatch fallback"))); } @SuppressWarnings("unchecked") @@ -202,58 +204,77 @@ private static void mapSelectModel(SelectItemModel.Unbaked model, MappingContext 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) ")); + mapItem(switchCase.model(), context.with(model.transformation(), "select GUI display_context case (unsupported property) ")); return; } } } 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) "))); + model.fallback().ifPresent(fallback -> mapItem(fallback, context.with(model.transformation(), "select fallback case (unsupported property) "))); return; } cases.forEach(switchCase -> { switchCase.values().forEach(value -> { - mapItem(switchCase.model(), context.with(new GeyserMatchPredicate(dataConstructor.apply(value)), "select case " + value + " ")); + mapItem(switchCase.model(), context.with(new GeyserMatchPredicate(dataConstructor.apply(value)), model.transformation(), "select case " + value + " ")); }); }); - model.fallback().ifPresent(fallback -> mapItem(fallback, context.child("select fallback case "))); + model.fallback().ifPresent(fallback -> mapItem(fallback, context.with(model.transformation(), "select fallback case "))); } - private record MappingContext(List predicateStack, ItemStack stack, ProblemReporter reporter, + private record MappingContext(List predicateStack, Optional transformationStack, + ItemStackTemplate itemStack, ProblemReporter reporter, Function definitionCreator, PackContext packContext) { - public MappingContext with(GeyserPredicate predicate, String childName) { - return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), stack, reporter.forChild(() -> childName), definitionCreator, packContext); + public MappingContext(ItemStackTemplate stack, ProblemReporter reporter, Function definitionCreator, PackContext packContext) { + this(List.of(), Optional.empty(), stack, reporter, definitionCreator, packContext); + } + + public MappingContext with(GeyserPredicate predicate, Optional transformation, String childName) { + return new MappingContext(Stream.concat(predicateStack.stream(), Stream.of(predicate)).toList(), addTransformation(transformation), itemStack, reporter.forChild(() -> childName), definitionCreator, packContext); + } + + public MappingContext with(Optional transformation, String childName) { + return new MappingContext(predicateStack, addTransformation(transformation), itemStack, reporter.forChild(() -> childName), definitionCreator, packContext); } public MappingContext child(String childName) { - return new MappingContext(predicateStack, stack, reporter.forChild(() -> childName), definitionCreator, packContext); + return new MappingContext(predicateStack, transformationStack, itemStack, reporter.forChild(() -> childName), definitionCreator, packContext); + } + + public Transformation finaliseTransformation(Optional finalTransformation) { + return addTransformation(finalTransformation).orElse(Transformation.IDENTITY); } public void create(Identifier bedrockIdentifier, BedrockGeometryContext geometry) { - List tags = stack.is(ItemTags.TRIMMABLE_ARMOR) ? TRIMMABLE_ARMOR_TAGS : List.of(); + List tags = itemStack.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, geometry.handheld(), calculateProtectionValue(stack), tags), - stack.getComponentsPatch()); + GeyserBaseDefinition base = new GeyserBaseDefinition(bedrockIdentifier, + Optional.ofNullable(itemStack.components().split().added().get(DataComponents.ITEM_NAME)).map(Component::tryCollapseToString), + predicateStack, + new GeyserBaseDefinition.BedrockOptions(Optional.empty(), true, geometry.handheld(), calculateProtectionValue(itemStack), tags), + itemStack.components()); try { - packContext.mappings().map(stack.getItemHolder(), definitionCreator.apply(base)); + packContext.mappings().map(itemStack.item(), definitionCreator.apply(base)); } catch (Exception exception) { reporter.forChild(() -> "mapping with bedrock identifier " + bedrockIdentifier + " ").report(() -> "failed to pass mapping: " + exception.getMessage()); return; } packContext.itemConsumer().accept(new BedrockItem(bedrockIdentifier, base.textureName(), geometry, - AttachableMapper.mapItem(packContext.assetResolver(), geometry, stack.getComponentsPatch()))); + AttachableMapper.mapItem(packContext.assetResolver(), geometry, itemStack.components()))); } public void report(String problem) { reporter.report(() -> problem); } - private static int calculateProtectionValue(ItemStack stack) { - ItemAttributeModifiers modifiers = stack.get(DataComponents.ATTRIBUTE_MODIFIERS); + private Optional addTransformation(Optional optionalChild) { + return optionalChild.flatMap(child -> transformationStack.map(parent -> parent.compose(child)).or(() -> optionalChild)); + } + + private static int calculateProtectionValue(ItemStackTemplate stack) { + ItemAttributeModifiers modifiers = stack.components().split().added().get(DataComponents.ATTRIBUTE_MODIFIERS); if (modifiers != null) { return modifiers.modifiers().stream() .filter(modifier -> modifier.attribute() == Attributes.ARMOR && modifier.modifier().operation() == AttributeModifier.Operation.ADD_VALUE) 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 95e2697..4103cee 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 @@ -1,7 +1,7 @@ package org.geysermc.rainbow.mapping.animation; -import net.minecraft.client.renderer.block.model.ItemTransform; -import net.minecraft.client.renderer.block.model.ItemTransforms; +import net.minecraft.client.resources.model.cuboid.ItemTransform; +import net.minecraft.client.resources.model.cuboid.ItemTransforms; import org.geysermc.rainbow.pack.animation.BedrockAnimation; import org.joml.Vector3f; import org.joml.Vector3fc; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/package-info.java new file mode 100644 index 0000000..5e9b173 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/animation/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.mapping.animation; + +import org.jspecify.annotations.NullMarked; 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 94463f8..e4160e4 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 @@ -7,7 +7,6 @@ import net.minecraft.core.component.DataComponents; import net.minecraft.resources.Identifier; 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.texture.TextureHolder; @@ -24,8 +23,7 @@ public static AttachableCreator mapItem(AssetResolver assetResolver, BedrockGeom // Unfortunately we can't have both equippables and custom models, so we prefer the latter :( return (bedrockIdentifier, textureConsumer) -> geometryContext.geometry() .map(geometry -> BedrockAttachable.geometry(bedrockIdentifier, geometry)) - .or(() -> Optional.ofNullable(components.get(DataComponents.EQUIPPABLE)) - .flatMap(optional -> (Optional) optional) + .or(() -> Optional.ofNullable(components.split().added().get(DataComponents.EQUIPPABLE)) .flatMap(equippable -> equippable.assetId().flatMap(assetResolver::getEquipmentInfo).map(info -> Pair.of(equippable.slot(), info))) .filter(assetInfo -> assetInfo.getSecond() != EquipmentAssetManager.MISSING) .map(assetInfo -> assetInfo @@ -33,7 +31,7 @@ public static AttachableCreator mapItem(AssetResolver assetResolver, BedrockGeom .filter(assetInfo -> !assetInfo.getSecond().isEmpty()) .map(assetInfo -> { Identifier equipmentTexture = getTexture(assetInfo.getSecond(), getLayer(assetInfo.getFirst())); - textureConsumer.accept(TextureHolder.createBuiltIn(null, equipmentTexture)); + textureConsumer.accept(TextureHolder.createBuiltIn(equipmentTexture)); return BedrockAttachable.equipment(bedrockIdentifier, assetInfo.getFirst(), equipmentTexture.getPath()); })) .map(attachable -> { @@ -42,7 +40,7 @@ public static AttachableCreator mapItem(AssetResolver assetResolver, BedrockGeom attachable.withAnimation("third_person", animation.thirdPerson()); attachable.withAnimation("head", animation.head()); attachable.withScript("animate", "first_person", "context.is_first_person == 1.0"); - attachable.withScript("animate", "third_person", "context.is_first_person == 0.0 && context.item_slot != 'head'"); + attachable.withScript("animate", "third_person", "context.is_first_person == 0.0 && (context.item_slot == 'main_hand' || context.item_slot == 'off_hand')"); attachable.withScript("animate", "head", "context.is_first_person == 0.0 && context.item_slot == 'head'"); }); return attachable.build(); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/package-info.java new file mode 100644 index 0000000..512efbb --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/attachable/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.mapping.attachable; + +import org.jspecify.annotations.NullMarked; 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 f53a12f..65cf16d 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,10 +1,11 @@ package org.geysermc.rainbow.mapping.geometry; -import net.minecraft.client.renderer.block.model.TextureSlots; -import net.minecraft.client.resources.model.Material; +import com.mojang.math.Transformation; import net.minecraft.client.resources.model.ResolvedModel; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.client.resources.model.sprite.TextureSlots; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.animation.AnimationMapper; @@ -22,7 +23,7 @@ public record BedrockGeometryContext(Optional geometry, .map(Identifier::withDefaultNamespace) .toList(); - public static BedrockGeometryContext create(Identifier bedrockIdentifier, ResolvedModel model, ItemStack stackToRender, PackContext context) { + public static BedrockGeometryContext create(Identifier bedrockIdentifier, ResolvedModel model, Transformation definitionTransformation, ItemStackTemplate stackToRender, PackContext context) { ResolvedModel parentModel = model.parent(); // debugName() returns the resource location of the model as a string boolean handheld = parentModel != null && HANDHELD_MODELS.contains(Identifier.parse(parentModel.debugName())); @@ -36,12 +37,12 @@ public static BedrockGeometryContext create(Identifier bedrockIdentifier, Resolv if (layer0Texture != null) { geometry = Optional.empty(); animation = Optional.empty(); - icon = TextureHolder.createBuiltIn(Rainbow.getAtlasIdFromMaterial(layer0Texture), layer0Texture.texture()); + icon = TextureHolder.createBuiltIn(layer0Texture.sprite()); } 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) - geometry = Optional.of(context.geometryCache().mapGeometry(bedrockIdentifier, model, stackToRender, context)); + geometry = Optional.of(context.geometryCache().mapGeometry(bedrockIdentifier, model, definitionTransformation, stackToRender, context)); animation = Optional.of(AnimationMapper.mapAnimation(Rainbow.bedrockSafeIdentifier(bedrockIdentifier), "bone", model.getTopTransforms())); icon = geometry.get().icon(); } 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 b2614ed..9c031e1 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 @@ -1,11 +1,12 @@ package org.geysermc.rainbow.mapping.geometry; -import net.minecraft.client.renderer.block.model.BlockElement; -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 com.mojang.math.Transformation; import net.minecraft.client.resources.model.ResolvedModel; -import net.minecraft.client.resources.model.UnbakedGeometry; +import net.minecraft.client.resources.model.cuboid.CuboidFace; +import net.minecraft.client.resources.model.cuboid.CuboidModelElement; +import net.minecraft.client.resources.model.cuboid.CuboidRotation; +import net.minecraft.client.resources.model.cuboid.UnbakedCuboidGeometry; +import net.minecraft.client.resources.model.geometry.UnbakedGeometry; import net.minecraft.core.Direction; import org.geysermc.rainbow.mapping.texture.StitchedTextures; import org.geysermc.rainbow.mixin.FaceBakeryAccessor; @@ -14,12 +15,15 @@ import org.joml.Vector3f; import org.joml.Vector3fc; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Map; public class GeometryMapper { private static final Vector3fc CENTRE_OFFSET = new Vector3f(8.0F, 0.0F, 8.0F); - public static BedrockGeometry mapGeometry(String identifier, String boneName, ResolvedModel model, StitchedTextures textures) { + public static BedrockGeometry mapGeometry(String identifier, String boneName, ResolvedModel model, Transformation transformation, StitchedTextures textures) { UnbakedGeometry top = model.getTopGeometry(); if (top == UnbakedGeometry.EMPTY) { return BedrockGeometry.EMPTY; @@ -39,9 +43,9 @@ public static BedrockGeometry mapGeometry(String identifier, String boneName, Re Vector3f min = new Vector3f(Float.MAX_VALUE); Vector3f max = new Vector3f(Float.MIN_VALUE); - SimpleUnbakedGeometry geometry = (SimpleUnbakedGeometry) top; - for (BlockElement element : geometry.elements()) { - BedrockGeometry.Cube cube = mapBlockElement(element, textures).build(); + UnbakedCuboidGeometry geometry = transformGeometry((UnbakedCuboidGeometry) top, transformation); + for (CuboidModelElement element : geometry.elements()) { + BedrockGeometry.Cube cube = mapCuboidModelElement(element, textures).build(); bone.withCube(cube); min.min(cube.origin()); max.max(cube.origin().add(cube.size(), new Vector3f())); @@ -59,7 +63,7 @@ public static BedrockGeometry mapGeometry(String identifier, String boneName, Re // After hours of painfully suffering and 40 test builds of Rainbow, I finally got the right formula together and somehow made this mess of a code // work properly, or at least, I think it is. I physically jumped in the air and cheered as I saw my models convert properly. // Now, make sure you are ready to witness my deformed creation - private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element, StitchedTextures textures) { + private static BedrockGeometry.Cube.Builder mapCuboidModelElement(CuboidModelElement element, StitchedTextures textures) { // For some reason the X axis is inverted on bedrock (thanks Blockbench!!) // The centre of the model is back by 8 in the X and Z direction on bedrock, so start by move the from and to points of the cube, and later the pivot, like that @@ -76,13 +80,13 @@ private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element BedrockGeometry.Cube.Builder builder = BedrockGeometry.cube(origin, size); - for (Map.Entry faceEntry : element.faces().entrySet()) { + for (Map.Entry faceEntry : element.faces().entrySet()) { Direction direction = faceEntry.getKey(); - BlockElementFace face = faceEntry.getValue(); + CuboidFace face = faceEntry.getValue(); Vector2f uvOrigin; Vector2f uvSize; - BlockElementFace.UVs uvs = face.uvs(); + CuboidFace.UVs uvs = face.uvs(); 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); @@ -109,7 +113,7 @@ private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element builder.withFace(direction, uvOrigin, uvSize, face.rotation()); } - BlockElementRotation rotation = element.rotation(); + CuboidRotation rotation = element.rotation(); if (rotation != null) { // MC multiplies model origin by 0.0625 when loading rotation origin @@ -122,11 +126,11 @@ private static BedrockGeometry.Cube.Builder mapBlockElement(BlockElement element return builder; } - private static Vector3fc getBedrockRotation(BlockElementRotation.RotationValue rotation) { + private static Vector3fc getBedrockRotation(CuboidRotation.RotationValue rotation) { // Same as in the method above, but for some reason the Z axis too: X and Z axes have to be inverted (thanks again, so much, Blockbench!!!) return switch (rotation) { - case BlockElementRotation.EulerXYZRotation(float x, float y, float z) -> new Vector3f(-x, y, -z); // TODO check if these angle transformations are right, they should be - case BlockElementRotation.SingleAxisRotation(Direction.Axis axis, float angle) -> switch (axis) { + case CuboidRotation.EulerXYZRotation(float x, float y, float z) -> new Vector3f(-x, y, -z); // TODO check if these angle transformations are right, they should be + case CuboidRotation.SingleAxisRotation(Direction.Axis axis, float angle) -> switch (axis) { case X -> new Vector3f(-angle, 0.0F, 0.0F); case Y -> new Vector3f(0.0F, angle, 0.0F); case Z -> new Vector3f(0.0F, 0.0F, -angle); @@ -134,4 +138,23 @@ private static Vector3fc getBedrockRotation(BlockElementRotation.RotationValue r default -> throw new IllegalArgumentException("Don't know how to transform rotation of type " + rotation.getClass() + " to bedrock rotation"); }; } + + private static UnbakedCuboidGeometry transformGeometry(UnbakedCuboidGeometry geometry, Transformation transformation) { + if (transformation == Transformation.IDENTITY) { + return geometry; + } + // Doesn't do anything with rotation yet + List transformedElements = new ArrayList<>(); + for (CuboidModelElement element : geometry.elements()) { + transformedElements.add(new CuboidModelElement(transformVector(element.from(), transformation), + transformVector(element.to(), transformation), element.faces(), + element.rotation(), element.shade(), element.lightEmission())); + } + return new UnbakedCuboidGeometry(Collections.unmodifiableList(transformedElements)); + } + + private static Vector3fc transformVector(Vector3fc original, Transformation transformation) { + // Translate first, then scale + return original.add(transformation.translation(), new Vector3f()).mul(transformation.scale()); + } } 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 1fc3878..64412ca 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,10 @@ package org.geysermc.rainbow.mapping.geometry; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import org.geysermc.rainbow.mapping.texture.TextureHolder; public interface GeometryRenderer { - TextureHolder render(Identifier identifier, ItemStack stack); + TextureHolder render(Identifier identifier, ItemStackTemplate stack); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java index a794f2d..89ad574 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/MappedGeometryCache.java @@ -1,10 +1,11 @@ package org.geysermc.rainbow.mapping.geometry; -import net.minecraft.client.resources.model.Material; +import com.mojang.math.Transformation; import net.minecraft.client.resources.model.ResolvedModel; -import net.minecraft.client.resources.model.UnbakedGeometry; +import net.minecraft.client.resources.model.geometry.UnbakedGeometry; +import net.minecraft.client.resources.model.sprite.Material; import net.minecraft.resources.Identifier; -import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.ItemStackTemplate; import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mapping.texture.StitchedTextures; @@ -18,8 +19,8 @@ public class MappedGeometryCache { private final Map cachedGeometry = new HashMap<>(); - public MappedGeometry mapGeometry(Identifier bedrockIdentifier, ResolvedModel model, ItemStack stackToRender, PackContext context) { - GeometryCacheKey cacheKey = new GeometryCacheKey(model); + public MappedGeometry mapGeometry(Identifier bedrockIdentifier, ResolvedModel model, Transformation transformation, ItemStackTemplate stackToRender, PackContext context) { + GeometryCacheKey cacheKey = new GeometryCacheKey(model, transformation); MappedGeometry cached = cachedGeometry.get(cacheKey); if (cached != null) { return cached.cachedCopy(); @@ -30,7 +31,7 @@ public MappedGeometry mapGeometry(Identifier bedrockIdentifier, ResolvedModel mo String safeIdentifier = Rainbow.bedrockSafeIdentifier(bedrockIdentifier); StitchedTextures stitchedTextures = StitchedTextures.stitchModelTextures(model.getTopTextureSlots(), context); - BedrockGeometry geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, stitchedTextures); + BedrockGeometry geometry = GeometryMapper.mapGeometry(safeIdentifier, "bone", model, transformation, stitchedTextures); TextureHolder icon = context.geometryRenderer().isPresent() ? context.geometryRenderer().orElseThrow().render(modelIdentifier, stackToRender) : TextureHolder.createNonExistent(modelIdentifier); MappedGeometryInstance instance = new MappedGeometryInstance(geometry, TextureHolder.createCustom(stitchedTexturesIdentifier, stitchedTextures.stitched()), icon); @@ -38,10 +39,10 @@ public MappedGeometry mapGeometry(Identifier bedrockIdentifier, ResolvedModel mo return instance; } - private record GeometryCacheKey(UnbakedGeometry geometry, Map textures) { + private record GeometryCacheKey(UnbakedGeometry geometry, Transformation transformation, Map textures) { - private GeometryCacheKey(ResolvedModel model) { - this(model.getTopGeometry(), ((TextureSlotsAccessor) model.getTopTextureSlots()).getResolvedValues()); + private GeometryCacheKey(ResolvedModel model, Transformation transformation) { + this(model.getTopGeometry(), transformation, ((TextureSlotsAccessor) model.getTopTextureSlots()).getResolvedValues()); } } } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/package-info.java new file mode 100644 index 0000000..2672728 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/geometry/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.mapping.geometry; + +import org.jspecify.annotations.NullMarked; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/package-info.java new file mode 100644 index 0000000..a5979b1 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.mapping; + +import org.jspecify.annotations.NullMarked; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java index fec397d..d77ee59 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/BuiltInTextureHolder.java @@ -11,19 +11,17 @@ import java.util.Optional; public class BuiltInTextureHolder extends TextureHolder { - private final Identifier atlas; private final Identifier source; - public BuiltInTextureHolder(Identifier identifier, Identifier atlas, Identifier source) { + public BuiltInTextureHolder(Identifier identifier, Identifier source) { super(identifier); - this.atlas = atlas; this.source = source; } @Override public Optional load(AssetResolver assetResolver, ProblemReporter reporter) { return RainbowIO.safeIO(() -> { - try (TextureResource texture = assetResolver.getTextureSafely(atlas, source).orElse(null)) { + try (TextureResource texture = assetResolver.getPossibleAtlasTextureSafely(source).orElse(null)) { Objects.requireNonNull(texture); try (NativeImage firstFrame = texture.getFirstFrame(false)) { return NativeImageUtil.writeToByteArray(firstFrame); diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java index 3a76022..93175a3 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/StitchedTextures.java @@ -1,14 +1,13 @@ package org.geysermc.rainbow.mapping.texture; import com.mojang.blaze3d.platform.NativeImage; -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.model.Material; +import net.minecraft.client.resources.model.sprite.Material; +import net.minecraft.client.resources.model.sprite.TextureSlots; import net.minecraft.data.AtlasIds; import net.minecraft.util.Util; -import org.geysermc.rainbow.Rainbow; import org.geysermc.rainbow.RainbowIO; import org.geysermc.rainbow.mapping.PackContext; import org.geysermc.rainbow.mixin.SpriteContentsAccessor; @@ -41,7 +40,7 @@ public static StitchedTextures stitchModelTextures(TextureSlots textures, PackCo Map sprites = new HashMap<>(); for (Map.Entry material : materials.entrySet()) { - TextureAtlasSprite sprite = preparations.getSprite(material.getValue().texture()); + TextureAtlasSprite sprite = preparations.getSprite(material.getValue().sprite()); // Sprite could be null when this material wasn't stitched, which happens when the texture simply doesn't exist within the loaded resourcepacks if (sprite != null) { sprites.put(material.getKey(), sprite); @@ -62,9 +61,9 @@ private static SpriteLoader.Preparations prepareStitching(Stream mater private static Optional readSpriteContents(Material material, PackContext context) { return RainbowIO.safeIO(() -> { - try (TextureResource texture = context.assetResolver().getTextureSafely(Rainbow.getAtlasIdFromMaterial(material), material.texture()).orElse(null)) { + try (TextureResource texture = context.assetResolver().getPossibleAtlasTextureSafely(material.sprite()).orElse(null)) { if (texture != null) { - return new SpriteContents(material.texture(), texture.sizeOfFrame(), texture.getFirstFrame(true)); + return new SpriteContents(material.sprite(), texture.sizeOfFrame(), texture.getFirstFrame(true)); } } return null; diff --git a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java index b083713..e6fb4f3 100644 --- a/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java +++ b/rainbow/src/main/java/org/geysermc/rainbow/mapping/texture/TextureHolder.java @@ -30,12 +30,12 @@ public static TextureHolder createCustom(Identifier identifier, Supplier bedrockItems = new HashSet<>(); private final Set modelsMapped = new HashSet<>(); - private final Set> customModelDataMapped = new HashSet<>(); + private final Set, Integer>> customModelDataMapped = new HashSet<>(); private final PackContext context; private final ProblemReporter reporter; @@ -68,16 +68,12 @@ public String name() { return name; } - public MappingResult map(ItemStack stack) { - if (stack.isEmpty()) { - return MappingResult.NONE_MAPPED; - } - + public MappingResult map(ItemStackTemplate stack) { AtomicBoolean problems = new AtomicBoolean(); ProblemReporter mapReporter = new ProblemReporter() { @Override - public @NotNull ProblemReporter forChild(PathElement child) { + public ProblemReporter forChild(PathElement child) { return reporter.forChild(child); } @@ -88,19 +84,18 @@ public void report(Problem problem) { } }; - Optional patchedModel = stack.getComponentsPatch().get(DataComponents.ITEM_MODEL); - //noinspection OptionalAssignedToNull - annoying Mojang - if (patchedModel == null || patchedModel.isEmpty()) { - CustomModelData customModelData = stack.get(DataComponents.CUSTOM_MODEL_DATA); + if (!stack.components().split().added().has(DataComponents.ITEM_MODEL)) { + CustomModelData customModelData = stack.components().split().added().get(DataComponents.CUSTOM_MODEL_DATA); Float firstNumber; if (customModelData == null || (firstNumber = customModelData.getFloat(0)) == null - || !customModelDataMapped.add(Pair.of(stack.getItem(), firstNumber.intValue()))) { + || !customModelDataMapped.add(Pair.of(stack.item(), firstNumber.intValue()))) { return MappingResult.NONE_MAPPED; } BedrockItemMapper.tryMapStack(stack, firstNumber.intValue(), mapReporter, context); } else { - Identifier model = patchedModel.get(); + Identifier model = stack.components().split().added().get(DataComponents.ITEM_MODEL); + assert model != null; if (!modelsMapped.add(model)) { return MappingResult.NONE_MAPPED; } @@ -112,9 +107,7 @@ public void report(Problem problem) { } public MappingResult map(Holder item, DataComponentPatch patch) { - ItemStack stack = new ItemStack(item); - stack.applyComponents(patch); - return map(stack); + return map(new ItemStackTemplate(item, 1, patch)); } public CompletableFuture save() { @@ -122,7 +115,7 @@ public CompletableFuture save() { futures.add(serializer.saveJson(GeyserMappings.CODEC, context.mappings(), paths.mappings())); manifest.ifPresent(manifest -> futures.add(serializer.saveJson(PackManifest.CODEC, manifest, paths.manifest()))); - futures.add(serializer.saveJson(BedrockTextureAtlas.CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas())); + futures.add(serializer.saveJson(BedrockTextureAtlas.ITEM_ATLAS_CODEC, BedrockTextureAtlas.itemAtlas(name, itemTextures), paths.itemAtlas())); Function> textureSaver = texture -> { Identifier textureIdentifier = Rainbow.decorateTextureIdentifier(texture.location()); @@ -179,14 +172,14 @@ public static class Builder { private final Path packRootPath; private final PackSerializer packSerializer; private final AssetResolver assetResolver; - private PackManifest manifest; + private @Nullable PackManifest manifest; 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 = null; + private @Nullable Path packZipFile = null; + private @Nullable GeometryRenderer geometryRenderer = null; private Function reporter; private boolean reportSuccesses = false; @@ -200,13 +193,13 @@ public Builder(String name, Path mappingsPath, Path packRootPath, PackSerializer manifest = defaultManifest(name); } - public Builder withManifest(PackManifest manifest) { + public Builder withManifest(@Nullable PackManifest manifest) { this.manifest = manifest; return this; } public Builder withAttachablesPath(Path absolute) { - return withAttachablesPath(path -> absolute); + return withAttachablesPath(_ -> absolute); } public Builder withAttachablesPath(UnaryOperator path) { @@ -215,7 +208,7 @@ public Builder withAttachablesPath(UnaryOperator path) { } public Builder withGeometryPath(Path absolute) { - return withGeometryPath(path -> absolute); + return withGeometryPath(_ -> absolute); } public Builder withGeometryPath(UnaryOperator path) { @@ -224,7 +217,7 @@ public Builder withGeometryPath(UnaryOperator path) { } public Builder withAnimationPath(Path absolute) { - return withAnimationPath(path -> absolute); + return withAnimationPath(_ -> absolute); } public Builder withAnimationPath(UnaryOperator path) { @@ -233,7 +226,7 @@ public Builder withAnimationPath(UnaryOperator path) { } public Builder withManifestPath(Path absolute) { - return withManifestPath(path -> absolute); + return withManifestPath(_ -> absolute); } public Builder withManifestPath(UnaryOperator path) { @@ -242,7 +235,7 @@ public Builder withManifestPath(UnaryOperator path) { } public Builder withItemAtlasPath(Path absolute) { - return withItemAtlasPath(path -> absolute); + return withItemAtlasPath(_ -> absolute); } public Builder withItemAtlasPath(UnaryOperator path) { 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 263813e..71d32fe 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 @@ -4,7 +4,7 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.codecs.RecordCodecBuilder; -import org.geysermc.rainbow.CodecUtil; +import net.minecraft.util.ExtraCodecs; import org.geysermc.rainbow.mapping.PackSerializer; import org.geysermc.rainbow.pack.BedrockVersion; import org.joml.Vector3fc; @@ -141,9 +141,9 @@ public enum LoopMode { public record SimpleAnimation(Vector3fc position, Vector3fc rotation, Vector3fc scale) { public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group( - CodecUtil.VECTOR3F_CODEC.fieldOf("position").forGetter(SimpleAnimation::position), - CodecUtil.VECTOR3F_CODEC.fieldOf("rotation").forGetter(SimpleAnimation::rotation), - CodecUtil.VECTOR3F_CODEC.fieldOf("scale").forGetter(SimpleAnimation::scale) + ExtraCodecs.VECTOR3F.fieldOf("position").forGetter(SimpleAnimation::position), + ExtraCodecs.VECTOR3F.fieldOf("rotation").forGetter(SimpleAnimation::rotation), + ExtraCodecs.VECTOR3F.fieldOf("scale").forGetter(SimpleAnimation::scale) ).apply(instance, SimpleAnimation::new) ); } diff --git a/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/package-info.java b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/package-info.java new file mode 100644 index 0000000..1626b08 --- /dev/null +++ b/rainbow/src/main/java/org/geysermc/rainbow/pack/animation/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package org.geysermc.rainbow.pack.animation; + +import org.jspecify.annotations.NullMarked; 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 e6329db..babc9df 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 @@ -15,7 +15,6 @@ import org.geysermc.rainbow.mapping.geometry.MappedGeometry; import org.geysermc.rainbow.pack.BedrockTextures; import org.geysermc.rainbow.pack.BedrockVersion; -import org.jetbrains.annotations.NotNull; import java.nio.file.Path; import java.util.ArrayList; @@ -23,6 +22,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Stream; @@ -57,7 +57,7 @@ public static BedrockAttachable.Builder equipment(Identifier identifier, Equipme .withMaterial(DisplaySlot.ENCHANTED, VanillaMaterials.ARMOR_ENCHANTED) .withTexture(DisplaySlot.DEFAULT, texture) .withTexture(DisplaySlot.ENCHANTED, VanillaTextures.ENCHANTED_ACTOR_GLINT) - .withGeometry(DisplaySlot.DEFAULT, VanillaGeometries.fromEquipmentSlot(slot)) + .withGeometry(DisplaySlot.DEFAULT, Objects.requireNonNull(VanillaGeometries.fromEquipmentSlot(slot))) .withScript("parent_setup", script) .withRenderController(VanillaRenderControllers.ARMOR); } @@ -172,7 +172,7 @@ public enum DisplaySlot implements StringRepresentable { } @Override - public @NotNull String getSerializedName() { + public String getSerializedName() { return name; } } @@ -194,7 +194,7 @@ public record Script(String script, Optional condition) { script -> script.condition.map(condition -> DataResult.success(Map.of(script.script, condition))) .orElse(DataResult.error(() -> "Script must have a condition")) ); - public static final Codec